这是我参与「第三届青训营 -后端场」笔记创作活动的的第7篇笔记。
1、消息队列
1、1 四个场景
- 系统崩溃
- 服务处理能力有限
- 链路耗时长尾
- 日志如何处理 解耦、削锋、异步、消息通讯
1、2 什么是消息队列
消息队列中间件是分布式系统中重要的组件,主要解决应用解耦,异步消息,流量削锋等问题,实现高性能,高可用,可伸缩和最终一致性架构。目前使用较多的消息队列有ActiveMQ,RabbitMQ,ZeroMQ,Kafka,MetaMQ,RocketMQ。
1、3 解耦
可以使不同的微服务之间不直接互相调用,而是发起方给mq发个消息,接收方从mq接收消息,从而实现业务解耦,避免链路卡在某个地方。消息队列使利用发布-订阅模式工作,消息发送者(生产者)发布消息,一个或多个消息接受者(消费者)订阅消息。 从上图可以看到消息发送者(生产者)和消息接受者(消费者)之间没有直接耦合,消息发送者将消息发送至分布式消息队列即结束对消息的处理,消息接受者从分布式消息队列获取该消息后进行后续处理,并不需要知道该消息从何而来。对新增业务,只要对该类消息感兴趣,即可订阅该消息,对原有系统和业务没有任何影响,从而实现网站业务的可扩展性设计。
1、4 流量削锋
流量削锋也是消息队列中的常用场景,一般在秒杀或团抢活动中使用广泛。假如消息队列长度超过最大数量,则直接抛弃用户请求或跳转到错误页面。秒杀业务根据消息队列中的请求信息,再做后续处理。
1、5 异步
消息队列采用异步的方式,避免业务停留在这里,加快业务的响应时间。
2 Kafka
Kafka是一种高吞吐量的分布式发布订阅消息系统,它可以处理消费者在网站中的所有动作流数据。 这种动作(网页浏览,搜索和其他用户的行动)是在现代网络上的许多社会功能的一个关键因素。 这些数据通常是由于吞吐量的要求而通过处理日志和日志聚合来解决。然而其也存在一些缺点。
因此需要学习使用MQ中间件。使用最广泛的应该就是RocketMq。我也曾使用过,在微服务之间互相通信确实是非常方便的,做到了业务解耦,异步,负载均衡、数据同步等等。
3、RocketMq
- Producer:就是消息生产者,可以集群部署。它会先和 NameServer 集群中的随机一台建立长连接,得知当前要发送的 Topic 存在哪台 Broker Master上,然后再与其建立长连接,支持多种负载平衡模式发送消息。
- Consumer:消息消费者,也可以集群部署。它也会先和 NameServer 集群中的随机一台建立长连接,得知当前要消息的 Topic 存在哪台 Broker Master、Slave上,然后它们建立长连接,支持集群消费和广播消费消息。
- Broker:主要负责消息的存储、查询消费,支持主从部署,一个 Master 可以对应多个 Slave,Master 支持读写,Slave 只支持读。Broker 会向集群中的每一台 NameServer 注册自己的路由信息。
- NameServer:是一个很简单的 Topic 路由注册中心,支持 Broker 的动态注册和发现,保存 Topic 和 Borker 之间的关系。通常也是集群部署,但是各 NameServer 之间不会互相通信, 各 NameServer 都有完整的路由信息,即无状态。 消息发送失败怎么办?重新发送机制
实际运用
以Java为例,导入相关的maven依赖后,在生产方指定一个topic和tag,给mq队列发送消息,消费者则监听相关的topic和tag,监听到就解析消息类,并进行相关的业务处理,实现异步调用,业务解耦。
- 生产者
/**
* 需要给coupon传入 userId 参数,useTime和 orderId 默认为空
* @param coupon
*/
@Override
public void insert(TradeCoupon coupon) {
coupon.setCouponId(idWorker.nextId());
Date nowTime = new Date();
coupon.setExpireTime(new Date(nowTime.getTime() + EXPIRATION));
try {
//给rocketMq发送延迟消息,到期后自动删除优惠券
CouponMessage couponMessage = new CouponMessage();
couponMessage.setCouponId(coupon.getCouponId());
couponMessage.setExpireTime(coupon.getExpireTime());
couponMessage.setUserId(coupon.getUserId());
Message message = new Message(TOPIC,TAG,couponMessage.toString().getBytes(StandardCharsets.UTF_8));
message.setDelayTimeLevel(21);
rocketMQTemplate.getProducer().setRetryTimesWhenSendFailed(10);
rocketMQTemplate.getProducer().send(message);
} catch (Exception e) {
e.printStackTrace();
}
//可以举行各种各样的活动来发放优惠券,此处是无门槛五块钱优惠券
coupon.setCouponPrice(5f);
coupon.setIsUsed(0);
couponMapper.insert(coupon);
}
- 消费者
@RocketMQMessageListener(topic = "deleteCouponTopic",consumerGroup =
"mall-coupon",messageModel = MessageModel.CLUSTERING)
@Component
@Slf4j
public class CouponMessageListener implements RocketMQListener<CouponMessage> {
@Autowired
private CouponService couponService;
@Autowired
private CouponMapper couponMapper;
@Override
public void onMessage(CouponMessage couponMessage) {
log.info("接收到优惠券过期消息"+couponMessage);
TradeCoupon tradeCoupon =
couponMapper.selectById(couponMessage.getCouponId());
couponService.delete(tradeCoupon);
}
}