微服务测试开发-项目中用到了rocketmq该怎么测试

1,503 阅读6分钟

1、什么是MQ

mq是消息中间件,最简单的理解就是A服务发送一条消息给B服务,B服务收到消息后做一系列的动作

这个能解决什么问题呢

  • 削峰,服务之间如果通过接口调用的话,无论是http还是rpc,但是属于同步的动作,意味着会存在大量的数据同一时刻到被调用服务,而使用mq完美解决该问题
  • 解耦,服务之间数据交互不再通过mysql,redis等,实现服务之间的解耦

2、什么情况会用到mq呢?

服务之间有数据交互时,但不要求时效性

image.png

比如:

  1. 电商场景中,订单系统生成订单后会通知仓库生成出库单,会通知配送系统生成配送单,而这种场景为什么不用rpc或者http接口呢,因为http接口会存在调用失败导致数据丢失,rpc也存在同样的问题,第2个原因,某一时刻大量订单生成时,极有可能导致仓库服务或者配送服务被打挂。
  2. 日志服务,线上多台服务器会把日志保存到统一日志服务上,方便查看线上日志,这种场景也是非常适合使用mq

3、为什么测试同学需要掌握这个技能

在服务拆分的现在,大量的微服务场景出现,大量使用mq的场景,如果不进行相应的专项测试,大概率会出现线上问题。

4、mq的架构是什么样的?

image.png

生产者发送消息给topic(broker),broker再把消息发送给消费者 每个消费者都有1个offset(偏移位),作用是记录这个消息是否被消费过,防止重复消费

最重要的是幂等问题,当网络出现问题的时,broker同样的消息可能会给消费者发送多次

5、如何搭建本地rocketMQ

1、下载rocketMQ并解压 rocketmq.apache.org/release_not…

2、启动nameserver

sh mqnamesrv

3、启动broker

sh bin/mqbroker -n localhost:9876

4、使用自带工具测试

export NAMESRV_ADDR=localhost:9876
sh tools.sh org.apache.rocketmq.example.quickstart.Producer
SendResult [sendStatus=SEND_OK, msgId=C0A80064502A355DA2545C7FD53503E5, offsetMsgId=C0A8006400002A9F000000000002BC96, messageQueue=MessageQueue [topic=TopicTest, brokerName=taomindeMacBook-Pro-2.local, queueId=1], queueOffset=249]
SendResult [sendStatus=SEND_OK, msgId=C0A80064502A355DA2545C7FD53603E6, offsetMsgId=C0A8006400002A9F000000000002BD4A, messageQueue=MessageQueue [topic=TopicTest, brokerName=taomindeMacBook-Pro-2.local, queueId=2], queueOffset=249]

到目前位置,本地的rocketMQ环境已经搭建好了,接下来是怎么使用spring-boot进行rocketMQ接入

5、使用命令行创建topic和消费组

创建topic
sh mqadmin updateTopic -n 192.168.0.103:9876 -b 192.168.0.103:10911 -t message-topic
创建消费组
sh mqadmin updateSubGroup -c DefaultCluster  -n 127.0.0.1:9876 -g nexcusdemo

6、在项目中引入依赖(pom.xml)

<dependency>
    <groupId>org.apache.rocketmq</groupId>
    <artifactId>rocketmq-spring-boot-starter</artifactId>
    <version>2.0.3</version>
</dependency>
<dependency>
    <groupId>org.apache.rocketmq</groupId>
    <artifactId>rocketmq-client</artifactId>
    <version>4.5.1</version>
</dependency>
<dependency>
    <groupId>org.apache.rocketmq</groupId>
    <artifactId>rocketmq-common</artifactId>
    <version>4.5.1</version>
</dependency>

7、在消费者中配置(application.properties)

rocketmq.name-server=localhost:9876

8、生产者配置

# rocketMQ配置
rocketmq.name-server=127.0.0.1:9876
rocketmq.producer.group=test

9、生产者代码

@GetMapping("/send")
public String sendMessage(String content){
    mqService.sendHello(content);
    return "ok";
}

@Autowired
private RocketMQTemplate rocketMQTemplate;

@Autowired
private RocketMQProducer<String> stringRocketMQProducer;

public void sendHello(String message){
    UserInfoDto userInfoDto = new UserInfoDto();
    userInfoDto.setPassword("123");
    userInfoDto.setUsername(message);
    userInfoDto.setId(1L);
    rocketMQTemplate.convertAndSend("message-topic", userInfoDto);
}

10、消费者配置

rocketmq.name-server=127.0.0.1:9876

11、消费者代码

@Log4j2
@Component
@RocketMQMessageListener(topic = "message-topic", consumerGroup = "nexcusdemo")
public class MessageListener implements RocketMQListener<UserInfoDto> {

    @Override
    public void onMessage(UserInfoDto message) {
        log.info("接收到消息: {}", message);
    }
}

到这里,通过Java来调用rocketMQ发送消息的代码和配置已经完成,进行结果演示

image.png

12、通过postman演示调用A服务接口,然后在B服务上查看到日志打印

  1. 通过postman调用

image.png

  1. 查看A服务日志,其中有发送消息的日志 image.png

  2. 查看B服务日志,其中有接收到消息的日志 image.png

6、python如何调用rocketMQ进行自动化测试

  1. 安装rocketMQ的C++依赖 macos:
wget https://github.com/apache/rocketmq-client-cpp/releases/download/2.0.0/rocketmq-client-cpp-2.0.0-bin-release.darwin.tar.gz
tar -xzf rocketmq-client-cpp-2.0.0-bin-release.darwin.tar.gz
cd rocketmq-client-cpp
mkdir /usr/local/include/rocketmq
cp include/* /usr/local/include/rocketmq
cp lib/* /usr/local/lib
install_name_tool -id "@rpath/librocketmq.dylib" /usr/local/lib/librocketmq.dylib

centos:

wget https://github.com/apache/rocketmq-client-cpp/releases/download/2.0.0/rocketmq-client-cpp-2.0.0-centos7.x86_64.rpm
sudo rpm -ivh rocketmq-client-cpp-2.0.0-centos7.x86_64.rpm
  1. 安装python依赖
pip install rocketmq-client-python
  1. 作为生产者
from rocketmq.client import Producer, Message
import json


producer = Producer("test")
producer.set_name_server_address('127.0.0.1:9876')
producer.start()
msg = Message('message-topic')
# msg.set_keys('XXX')
# msg.set_tags('XXX')
msg.set_body(json.dumps({"username":"python", "password":"python3", "id":2}))
ret = producer.send_sync(msg)
print(ret.status, ret.msg_id, ret.offset)
producer.shutdown()
  1. 执行生产者代码,查看Java消费者是否收到

image.png

  1. 作为消费者
import time

from rocketmq.client import PushConsumer, ConsumeStatus


def callback(msg):
    print(msg.id, msg.body)
    return ConsumeStatus.CONSUME_SUCCESS


consumer = PushConsumer('nexcusdemo')
consumer.set_name_server_address('127.0.0.1:9876')
consumer.subscribe('message-topic', callback)
consumer.start()

while True:
    time.sleep(3600)

consumer.shutdown()
  1. 查看通过POST发送请求后是否能够接收到消息

image.png

7、常见的问题,幂等测试

  1. 场景如下
用户调用A服务订单接口,A服务会发送MQ消息给B服务,生成1个配送单
存在的问题是:如果A服务发送消息的时候,因为网络抖动等问题,broker会给B服务发送重复消息,导致生成2个配送单的问题,1个订单应该有几个配送单

image.png

  1. A服务(订单服务)在生成1个订单后会发送mq消息给B服务(配送服务)生成配送单 查看A服务代码
//生成订单接口
@PostMapping("/order/create")
public Result<String> createOrder(@RequestBody OrderQuery orderQuery){
    // 生成订单,并发送消息给配送服务生成配送单
    mqService.createOrder(orderQuery);
    return new Result<String>().success("ok");
}

// 生成订单方法,并发送消息给配送服务
public boolean createOrder(OrderQuery orderQuery){

    HcOrder hcOrder = new HcOrder();
    hcOrder.setSku(orderQuery.getSku());
    hcOrder.setOrderNo(OrderUtil.getLeafNo());
    hcOrder.setQuantity(String.valueOf(orderQuery.getQuantity()));
    // 订单数据落库
    hcOrderMapper.insert(hcOrder);

    // 发送消息给配送服务
    sendOrder2Transport(hcOrder);
    return true;
}

// 发送消息给配送服务
public void sendOrder2Transport(HcOrder hcOrder){
     String order = JSONObject.toJSONString(hcOrder);
     rocketMQTemplate.convertAndSend("order-topic", order);
}

那么我们可以确定A服务发送消息给B服务,实际发送的就是1个json,这个json就是hcOrder对象,实际就是

{
    "sku": "1",  //表示是哪个商品
    "orderNo": "hc1111",  //表示哪个订单号
    "quantity": "2"  // 表示买了几件
}
  1. 查看B服务代码
@Override
public void onMessage(String order) {
    log.info("接收到消息: {}", order);
    HcOrder hcOrder = JSONUtil.convertString2Object(order, HcOrder.class);
    String orderNo = hcOrder.getOrderNo();

    // 幂等处理,先查询,如果有就直接返回(如果不处理,就有可能出现1个订单对应对个配送单)
    LambdaQueryWrapper<DeliveryOrder> deliveryOrderLambdaQueryWrapper = new LambdaQueryWrapper<>();
    deliveryOrderLambdaQueryWrapper.eq(DeliveryOrder::getOrderNo, orderNo);
    DeliveryOrder deliveryOrder1 = deliveryMapper.selectOne(deliveryOrderLambdaQueryWrapper);
    if (deliveryOrder1 != null){
        log.info("orderNo: {},对应的配送单: {}, 已存在", orderNo, deliveryOrder1.getDeliveryNo());
        return;
    }

    DeliveryOrder deliveryOrder = new DeliveryOrder();
    deliveryOrder.setOrderNo(orderNo);
    deliveryOrder.setDeliveryNo(DeliveryOrderUtil.getLeafNo());
    deliveryMapper.insert(deliveryOrder);
    }
  1. 如何用配送验证是否做了幂等 首先,查看配送单表中存在hc1624011377626对应的配送单

image.png 如果我们用python再发送一条消息给B服务,查看B服务是否继续往数据库里在插入一条数据就能判断是否做了幂等

from rocketmq.client import Message, Producer
import json

producer = Producer("test")
producer.set_name_server_address("127.0.0.1:9876")
producer.start()

msg = Message("order-topic")
msg.set_body(json.dumps({"orderNo":"hc1624011377626", "sku":"1", "quantity":"2"}))
res = producer.send_sync(msg)
print(res.status, res.msg_id, res.offset)
producer.shutdown()
  1. 执行python脚本查看日志和数据库,日志打印数据重复,订单对应的配送单也只有1个,最终证明幂等生效

image.png

image.png

8、写在最后

MQ消息中间件是微服务测试中很小的一部分,后续还会有各种微服务测试开发需要掌握的技能,大家有什么问题可以在下面评论