高并发系统:
- 系统拆分 2. 缓存 3. MQ
4. 分库分表 5. 读写分离 6. ElasticSearch
MQ==》解耦、异步、削峰
通过MQ,Pub/Sub发布订阅消息的一个模型,系统之间就可以进行解耦合了
剖析项目
一个模块,调用了那些系统或模块,相互之间调用关系,维护起来很麻烦,只要这个调用不需要直接同步调用接口,用MQ进行异步化解耦===》MQ做解耦
MQ缺点解决
MQ缺点解决:如何保证RocketMQ高可用、一致性问题、系统复杂度提高了(如何保证消费没有重复消费—>幂等性操作、消息丢失情况)
保证消息不被重复消费
- Redis天然幂等性
- 数据库查看
消息不丢失
-
Producer阶段
方案一:send的三种方法
- 同步发送
发消息的时候会同步阻塞等待broker返回的结果,如果没成功,则不会收到SendResult,这种是最可靠的 - 异步发送
异步发送,再回调方法里可以得知是否发送成功 - 单向发送
最不靠谱的一种发送方式,我们无法保证消息真正可达
- 同步发送
//{@link org.apache.rocketmq.client.producer.DefaultMQProducer}
// 同步发送
public SendResult send(Message msg) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {}
// 异步发送,sendCallback作为回调
public void send(Message msg,SendCallback sendCallback) throws MQClientException, RemotingException, InterruptedException {}
// 单向发送,不关心发送结果,最不靠谱
public void sendOneway(Message msg) throws MQClientException, RemotingException, InterruptedException {}
方案二:失败或超时,自动重试发送,默认重试三次
方案三: 多Master多Slave
Producer保证发送阶段消息可达: 同步发送+自动重试机制+Master节点
-
Broker存储阶段
Broker先把消息放入内存,然后根据刷盘策略持久化到硬盘中,收到Producer,再读到内存中,若此时异常宕机,导致消息丢失?
-
方案一:修改刷盘方式
MQ持久化分为同步刷盘和异步刷盘,默认异步刷盘
配置//默认情况为 ASYNC_FLUSH,修改为同步刷盘:SYNC_FLUSH, //同步刷盘效率不如异步刷盘高。异步默认10s执行一次 flushDiskType = SYNC_FLUSH
-
方案二: 主从模式
根据业务判断,是否是Master刷盘通知Producer还是Master和Slave同时刷完盘再通知Producer## 默认为 ASYNC_MASTER brokerRole=SYNC_MASTER
-
-
Consumer存储阶段
-
方案一
消费者把消息拉取到本地,业务逻辑执行完成后手动sck确认,代表真正消费完成 -
方案二
消息消费失败自动重试,若消费消息失败,没有ack确认,自动重试,配置好重试策略和次数即可
-
RocketMQ保证队列完全顺序消费
生产者将消息顺序发往同一个queue,依靠先进先出机制FIFO===》消息的消费顺序和产生顺序相同
-
全局顺序消费
设置读写队列数量为1,保证了并发时,消息消费的顺序性----》系统吞吐量低 -
局部顺序消费
消息顺序消费—> 1.消息顺序发送 2.消息顺序存储 3.消息顺序消费,满足这三点即可- 消息顺序发送,多线程发送的消息无法保证有序性,但是同一个业务的消息需保证再一个线程内顺序发送
- 消息顺序存储,MQ的topic下存在多个queue,要保证消息的顺序存储,同一个业务编号需发送到一个queue中(使用MessageQueueSeletor来选择要发送的queue,一般对业务编号hash,然后根据队列数量hash值取模,将消息发送到一个queue)
- 消息顺序消费,保证同一个queue只能被一个消费者消费即可
1、2两点,通过mq同步发送和MessageQueueSelector保证即可
RocketMQ原理
服务组件
NameServer、Broker、FilterServer
- Name Server
是RocketMQ的寻址服务,用于把Broker的路由信息做聚合。客户端依靠Name Server决定去获取对应topic的路由信息,从而决定对哪些Broker做连接 - Broker
是处理消息存储,转发等处理的服务器- Broker以group分开,每个group只允许一个master,若干个slave
- 只有master才能进行写入操作,slave不可以
- slave从master中同步数据
- 客户端可以从master或slave消费,master挂掉后,客户端从Name Server感知Broker挂机,从slave消费
- Filter Server
Rocket运行消费者上传一个Java类给Filter Server过滤
角色
- Producer
生产者。发送消息的客户端角色,发送消息需知道Topic - Consumer
消费者,消费消息的客户端角色。通常是后台处理异步消费的系统- 实现方式
- PushConsumer
推送模式的消费者–>内部已处理如线程池消费、流控、负载均衡、异常处理 - PullConsumer
拉取模式的消费者,应用主动控制拉取的动机,如何拉取、如何消费—>主动权更高,但要自行处理各种场景
- PushConsumer
- 实现方式
Producer Group : 表示发送同一类消息的Producer,做标识使用
Consumer Group : 表示消费同一类消息,表示一类Consumer的集合名称
Topic : 标识一类信息的逻辑名字,消息的逻辑管理单位(生产者、消费者都需要指定Topic)
Message Queue : 一个Topic可能有若干Q,若Topic同时创建在不同Broker,则不同Broker有若干Q=====》Producer发送消息的时候,会预先选择好该Topic下面的某一条Q发送,Consumer消费的时候也会负载均衡的分配若干Q,拉取对应Q信息
NameServer 路由注册与路由发现
Broker–》路由的注册,消息的存储与投递
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U4JkDSVd-1629904140433)(/Users/wujiewei/Library/Application Support/typora-user-images/image-20210824102935192.png)]
Brokers轮询对所有NameServer进行注册
主题Topic
Topic : message 1:n message: Topic 1:1
一个生产者可以同时发送多种Topic消息
一个消费者只能针对某种特定的Topic的消费
Producer:Topic 1:n Consumer:topic 1:1
队列Queue
存储消息的物理实体,一个Topic可以包含多个Queue,每个Queue中存放的就是该Topic的消息,Topic的Queue也被称之为Topic中消息的分区(Partition)
一个Topic的Queue中的消息只能被一个消费者组中的一个消费者消费
一个Queue中的消息同一个消费者组中的多个消费者同时消费
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FwujAJAb-1629904140434)(/Users/wujiewei/Desktop/wcw/Photo/other/项目/0008.分区分片.png)]
分区分片
- 消息的数据结构在Producer端是Message,到了Broker,Broker就会给消息加字段,变成了MessageExt,MessageExt继承自Message
- RocketMQ在MessageListenerConcurrently接口的consumeMessage的参数List是一个List消息列表
- consumeMessage只有两种返回值—成功消费和失败稍后重试
- 只要是不返回成功消费,就会执行稍后重试
- 调用
consumer.setConsumeMessageBatchMaxSize(10);
可以设置该List的最大长度,即最多允许一次消费多少条。(注:这种批量消费的思路很棒,因为有的业务场景有可能处理一个消息需要一秒,处理十条消息仅需要五秒,所以批量消费效率有时候会高一些) - 管理消费进度,保证消费,ack只在PushConsumer中有实现的
- PushConsumer为了保证消息肯定消费成功,只有使用方明确表示消费成功,RocketMQ才会认为消息消费成功。中途断电,抛出异常等都不会认为成功——即都会重新投递
- 为了保证消息是肯定被至少消费成功一次,RocketMQ会把这批消息重发回Broker(topic不是原topic而是这个消费租的RETRY topic),在延迟的某个时间点(默认是10秒,业务可设置)后,再次投递到这个ConsumerGroup。而如果一直这样重复消费都持续失败到一定次数(默认16次),就会投递到DLQ死信队列。应用可以监控死信队列来做人工干预
- 如果最大批量消费条数为10条,我们在consumeMessage方法中遍历List,并一条一条的发送,当发送到第5条的时候出错了====》客户端就会帮我们把n之后的消息全部重试掉
消息ACK机制
RocketMQ以consumer group+queue为单位来管理消费进度的,以一个consumer offset标记这个消费组在这条queue上的消费进度====》某个已经存在的消费组出现了新消费实例的时候,依靠这个组的消费进度,就可以判断第一次是从哪里开始拉取消息的
文件刷盘机制
RocketMQ的消息是存储在磁盘上的,两个优点:
- 保证断电后恢复
- 让存储的消息量超出内存的限制
高可用
Name Server高可用
NameServer节点是无状态的且各个节点之间的数据是一致的,所以存在多个NameServer节点的情况下,部分NameServer不可用也能保证MQ服务正常运行
BrokerServer高可用
RocketMQ通过Master和Slave配合达到BrokerServer模块的高可用性的,Master-Slaver组
消息消费高可用
依赖于Master-Slave配置的,Master支持读写消息,Slave支持读消息,Master繁忙或宕机时,Consumer可自动切换到从Slave读取
消息发送高可用
创建Topic时,把Topic的多个Message Queue创建到多个Broker组上,当一个Broker组的Master不可用后,其他组Master仍可用,Producer仍可以发送消息
消息主从复制
一个Broker组有一个Master、若干Slave,消息从Master复制到Slave上有同步复制和异步复制两种方式
同步复制
等Master和Slave都写成功后,才反馈给客户端写成功状态==》可靠性高,Master宕机,Slave有全部的备份数据,容易恢复,但系统吞吐量低
异步复制
只要Master写成功,就反馈给客户端成功状态==》若Master宕机,可能存在一些数据没来及写入Slave,导致丢失,但写入延迟低,吞吐量高
- 配置方式
对broker配置文件里的BrokerRole参数配置
ASYNC_MASTER | 异步复制 |
---|---|
SYNC_MASTER | 同步复制 |
- 实际应用
同步刷盘方式会频繁触发磁盘写操作,明显降低性能
刷盘方式 | ASYNC_FLUSH | 异步刷盘 |
---|---|---|
主从复制 | SYNC_MASTER | 同步复制 |
异步刷盘能避免频繁触发磁盘写操作,除非服务器宕机,否则不会造成消息丢失
主从同步复制能保证消息不丢失,即使Master节点异常,也能保证Slave节点存储所有消息能正常消费掉
消息重复问题
发送时消息重复
当一条消息已成功发送到服务器并完成持久化,此时网络或客户端宕机,导致服务端对客户端应答失败,生产者意识消息发送失败,尝试再次发送消息,消费者会收到两条内容相同且Message ID也想通的消息
投递时消息重复
消息已投递到消费者并完成业务处理,当客户端给服务端反馈应答时,网络闪断,为了保证消息至少被消费一次,RocketMQ服务端会在网络恢复后再次尝试投递处理过的消息,消费者会收到两条内容相同并且 Message ID 也相同的消息
解决
在消费者端统一进行幂等处理就能够实现消息幂等
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Cp41eIH9-1629904140436)(/Users/wujiewei/Desktop/wcw/Photo/0008消息幂等.png)]
@ConditionalOnProperty(prefix = “rocketmq.producer”, value = “isOnOff”, havingValue = “on”)
-
如果消费者处理一批,部分失败返回,如何记录消费偏移?全部重复投递?还是部分投递?
-
消费者消费了,但是服务器没有收到成功返回值,怎么处理?===》重复消费?本地记录?—》Redis幂等
-
生产者发送消息
-
Master死掉后,会从Slave中选举一个Master吗?
-
消费者,在向服务器返回成功,操作数据库失败
TPS:transaction per second代表每秒执行的事务数量,TPS=事务数/时间(秒)
QPS:Queries-per-second每秒查询率,QPS=req/sec
一个TPS可能包含多个QPS