文章目录
一、MQ简介
1、消息队列需求场景
在基于微服务开发的电商项目中,商品的查询和商品的修改是通过两个服务实现的,如果修改了商品的价格,如何保证商品查询服务查询出来的商品价格同步更新呢?
服务与服务之间的通信方式有两种:同步调用和异步消息调用
- 同步调用:远程过程调用,REST和RPC
- 异步消息调用:消息队列
1)、为了保证数据的一致性,当“商户商品修改服务”在完成对A库中商品信息的修改之后,需要调用“商户商品查询服务”及“自媒体商品查询服务”同步完成B库及C库中商品信息的修改;
2)、如果“商户商品修改服务”使用Ribbon或者Feign同步调用“商户商品查询服务”及“自媒体商品查询服务”虽然能够实现数据的同步修改,但是大大增加了“商户商品修改服务”对用户的响应时间
3)、为了缩短“商户商品修改服务”对用户的响应时间,我们可以在“商户商品修改服务”完成对A库修改之后,通过异步消息队列通知“商户商品查询服务”及“自媒体商品查询服务”。
2、消息队列概念
MQ全称为Message Queue,消息队列(MQ)是一种应用程序对应用程序的通信方法。应用程序通过读写出入队列的消息(针对应用程序的数据)来通信,而无需专用连接来链接它们。
消息传递指的是程序之间通过在消息中发送数据进行通信,而不是通过直接调用彼此来通信,直接调用通常是用于诸如远程过程调用的技术。排队指的是应用程序通过队列来通信。队列的使用除去了接收和发送应用程序同时执行的要求。
3、常见消息队列中间件
RabbitMQ、ActiveMQ、RocketMQ、Kafka
RabbitMQ
:稳定可靠,数据一致,支持多协议,有消息确认,基于erlang语言Kafka
:高吞吐,高性能,快速持久化,无消息确认,无消息遗漏,可能会有有重复消息,依赖于zookeeper,成本高.ActiveMQ
:不够灵活轻巧,对队列较多情况支持不好.易于开发RocketMQ
:性能好,高吞吐,高可用性,支持大规模分布式,协议支持单一
4、消息队列作用
解耦
场景说明:用户下单后,订单系统需要通知库存系统
传统做法
传统的做法是,订单系统调用库存系统的接口。如下图:
传统模式的缺点:假如库存系统无法访问,则订单减库存将失败,从而导致订单失败,订单系统与库存系统耦合
如何解决以上问题呢?
使用消息队列
引入应用消息队列后的方案,如下图:
订单系统:用户下单后,订单系统完成持久化处理,将消息写入消息队列,返回用户订单下单成功
库存系统:订阅下单的消息,采用拉/推的方式,获取下单信息,库存系统根据下单信息,进行库存操作
在下单时库存系统不能正常使用。也不影响正常下单,因为下单后,订单系统写入消息队列就不再关心其他的后续操作了。实现订单系统与库存系统的应用解耦
异步
场景说明:用户注册后,需要发注册邮件和注册短信
传统做法
a.串行:将注册信息写入数据库成功后,发送注册邮件,再发送注册短信
b.并行:将注册信息写入数据库成功后,发送注册邮件的同时,发送注册短信
使用消息队列
将不是必须的业务逻辑,异步处理。改造后的架构如下:
流量削峰
场景说明:商品秒杀业务,一般会因为流量过大,导致流量暴增,应用挂掉
传统做法
限制用户数量
使用消息队列
用户的请求,服务器接收后,首先写入消息队列,秒杀业务根据消息队列中的请求信息,再做后续处理
假如消息队列长度超过最大数量,则直接抛弃用户请求或跳转到错误页面。
消息通讯
消息通讯是指,消息队列一般都内置了高效的通信机制,因此也可以用在纯的消息通讯。比如实现点对点消息队列,或者聊天室等
使用消息队列实现点对点通信
客户端A和客户端B使用同一队列,进行消息通讯
使用消息队列实现聊天室通信
客户端A,客户端B,客户端N订阅同一主题,进行消息发布和接收。实现类似聊天室效果
日志处理
日志处理是指将消息队列用在日志处理中,比如Kafka的应用,解决大量日志传输的问题
使用消息队列完成日志处理
- 日志采集客户端,负责日志数据采集,定时写受写入Kafka队列
- Kafka消息队列,负责日志数据的接收,存储和转发
- 日志处理应用:订阅并消费kafka队列中的日志数据
二、Rocket MQ简介
如图所示为RocketMQ基本的部署结构,主要分为NameServer集群、Broker集群、Producer集群和Consumer集群四个部分。
NameServer集群
NameServer的作用是注册中心,类似于Zookeeper,但又有区别于它的地方。每个NameServer节点互相之间是独立的,没有任何信息交互,也就不存在任何的选主或者主从切换之类的问题,因此NameServer与Zookeeper相比更轻量级。单个NameServer节点中存储了活跃的Broker列表(包括master和slave),这里活跃的定义是与NameServer保持有心跳。
Broker集群
Broker是具体提供业务的服务器,存储消息内容,单个Broker节点与所有的NameServer节点保持长连接及心跳,并会定时将Topic信息注册到NameServer,其底层的通信和连接都是基于Netty实现的。 Broker中 分master和slave两种角色,每个master可以对应多个slave,但一个slave只能对应一个master, master和slave通过指定相同的Brokername,不同的BrokerId (master为0)成为一个组。master和 slave之间的同步方式分为同步双写和异步复制,异步复制方式master和slave之间虽然会存在少量的延迟,但性能较同步双写方式要高出10%左右。
另外,Broker中还存在一些非常重要的名词需要说明:
Topic和Queue
RocketMQ的Topic/Queue和JMS中的Topic/Queue概念有一定的差异,JMS中所有消费者都会消费一个Topic消息的副本,而Queue中消息只会被一个消费者消费;但到了RocketMQ中Topic只代表普通的消息队列,而Queue是组成Topic的更小单元,集群消费模式下一个消费者只消费该Topic中部分Queue中的消息,当一个消费者开启广播模式时则会消费该Topic下所有Queue中的消息。Topic和 Queue的具体关系可以参考下图
Tags Tags是Topic下的次级消息类型(注:Tags也支持 TagA || TagB 这样的表达式),可以在同一个Topic下基于Tags进行消息过滤。Tags的过滤需要经过两次比对,首先会在Broker端通过Tag hashcode进行一次比对过滤,匹配成功传到consumer端后再对具体Tags进行比对,以防止Tag hashcode重复的情况。Queue中具体的存储单元结构如下图:
Producer集群
与nameserver的关系
单个Producer和一台nameserver保持长连接,定时查询topic配置信息,如果该nameserver挂掉,生产者会自动连接下一个nameserver,直到有可用连接为止,并能自动重连。与nameserver之间没有心跳。
与broker的关系
单个Producer和与其关联的所有broker保持长连接,并维持心跳。默认情况下消息发送采用轮询方式,会均匀发到对应Topic的所有queue中。
最佳实践
1)、一个应用尽可能只使用一个 Topic,消息子类型用 tags 来标识,tags 可以由应用自由设置。只有发送消息设置了tags,消费方在订阅消息时,才可以利用 tags 在 broker 做消息过滤。
2)、每个消息在业务层面的唯一标识码,要设置到 keys 字段,方便将来定位消息丢失问题。服务器会为每个消息创建索引(哈希索引),应用可以通过 Topic,key 来查询返条消息内容,以及消息被谁消费。由于是哈希索引,请务必保证 key 尽可能唯一,这样可以避免潜在的哈希冲突。
3)、消息发送成功或者失败,要打印消息日志,务必要打印 sendresult 和 key 字段。
4)、对于消息不可丢失应用,务必要有消息重发机制。例如:消息发送失败,存储到数据库,能有定时程序尝试重发或者人工触发重发。
5)、某些应用如果不关注消息是否发送成功,请直接使用sendOneWay方法发送消息。
Consumer集群
与nameserver的关系
单个Consumer和一台nameserver保持长连接,定时查询topic配置信息,如果该nameserver挂掉,消费者会自动连接下一个nameserver,直到有可用连接为止,并能自动重连。与nameserver之间没有心跳。
与broker的关系
单个Consumer和与其关联的所有broker保持长连接,并维持心跳,失去心跳后,则关闭连接,并向该消费者分组的所有消费者发出通知,分组内消费者重新分配队列继续消费。
最佳实践
1)、Consumer 数量要小于等于queue的总数量,由于Topic下的queue会被相对均匀的分配给Consumer,如果 Consumer 超过queue的数量,那多余的 Consumer 将没有queue可以消费消息。
2)、消费过程要做到幂等(即消费端去重),RocketMQ为了保证性能并不支持严格的消息去重。
3)、尽量使用批量方式消费,Rocket