为什么要使用消息队列
我们从以下三个方面去回答:解耦、异步、削峰
解耦
- 传统模式下的系统架构由于系统耦合性太强,如果后续有其他系统接入,需要修改代码。
- 使用中间件:将消息写入消息队列,需要消息的系统自己从消息队列中订阅,原始系统不需要做任何修改。
异步
- 传统模式下:以同步方式运行程序,会出现同步高延迟的情况。例子:200ms+200ms+300ms=700ms
- 使用中间件:将消息写入消息队列,业务逻辑以异步方式运行,加快响应速度。例子200ms=200ms
削峰(流量削峰)
- 传统模式下:高并发请求下,所有请求直接请求数据库,严重会造成数据库连接异常。
- 使用中间件:系统按照自己并发量,从消息队列中获取信息,在生产中,短暂的高峰期积压是允许的。
消息队列有什么缺点?
- 系统可用性降低:系统引入的外部依赖越多,越容易挂掉。
- 系统复杂性提高:保证消息是否重复消费?处理消息丢失的情况?怎么保证消息的顺序性?
- 一致性问题:A系统处理完了直接返回成功了,人都以为你这个请求就成功了;但是问题是,要是BCD三个系统那里,BD两个系统写库成功了,结果C系统写库失败了,咋整?你这数据就不一致了。
acitveMq、rabbitmq、rocketmq、kafka都有什么优缺点?
特效 | acitveMq | rabbitmq | rocketmq | kafka |
---|---|---|---|---|
单机吞吐量 | 万级 | 万级 | 10W | 10W |
topic数量对吞吐量的影响 | 10Wtopic可以达到几百,几千个的级别,吞吐量会有较小幅度的下降这是RocketMQ的一大优势,在同等机器下,可以支撑大量的topic | topic从几十个到几百个的时候,吞吐量会大幅度下降. 所以在同等机器下,kafka尽量保证topic数量不要过多。如果要支撑大规模topic,需要增加更多的机器资源 | ||
时效性 | 微秒级,这是rabbitmq的一大特点,延迟是最低的 | 万级 | ms | ms |
可用性 | 高,基于主从架构实现高可用性 | 高,基于主从架构实现高可用性 | 非常高,分布式架构 | 非常高,kafka是分布式的,一个数据多个副本,少数机器宕机,不会丢失数据,不会导致不可用 |
消息可靠性 | 有较低的概率丢失数据 | 经过参数优化配置,可以做到0丢失 | 经过参数优化配置,消息可以做到0丢失 | |
功能支持 | MQ领域的功能极其完备 | 基于erlang开发,所以并发能力很强,性能极其好,延时很低 | MQ功能较为完善,还是分布式的,扩展性好 | 功能较为简单,主要支持简单的MQ功能,在大数据领域的实时计算以及日志采集被大规模使用,是事实上的标准 |
综合上面的,小公司rabbitMq.大中小rocketMq就可以满足基本需求了。
如何保证消息队列的高可用?
高可用基本定义:通过设计减少系统不能提供服务的时间。
目标:消除基础架构中的单点故障(应用程序中没有冗余的正常功能的部件被认为是单一故障点)
rabbitMq高可用
三种部署模式
单机模式
适用于个人搭建,生产不会用到。
普通集群
多台机器部署MQ,通过同步更新某一个实例的queue来进行消息处理。如果放queue实例宕机了,其他实例无法拉取。如果开启了消息持久化,消息不一定会丢,但是实例恢复启动后,才能继续从queue拉取数据。
只是提高了信息的吞吐量,并没有做到高可用
镜像集群
跟普通集群不一样的是,queue会存在多个实例上,每次写消息到queue,会自动同步到其他实例的queue。
缺点
- 性能开销大 对网络以及服务器都是巨大开销
- 没有什么扩展性 因为即使加机器也只是同步之前queue的数据 不是分布式的
如何开启
- rabbitMq管理台中可以打开这个策略,指定的时候可以要求数据同步到所有节点。
怎么防止雪崩?
HA
rocketMq高可用
master slave 配合,master 支持读、写,slave 只读,producer 只能和 master 连接写入消息,consumer 可以连接 master 和 slave。
consumer 高可用
当 master 不可用或者繁忙时,consumer 会被自动切换到 slave 读。所以,即使 master 出现故障,consumer 仍然可以从 slave 读消息,不受影响。
producer 高可用
创建 topic 时,把 message queue 创建在多个 broker 组上(brokerName 一样,brokerId 不同),当一个 broker 组的 master 不可用后,其他组的 master 仍然可以用,producer 可以继续发消息。
kafka高可用
kafka一个最基本的架构认识:多个broker组成,每个broker是一个节点;你创建一个topic,这个topic可以划分为多个partition,每个partition可以存在于不同的broker上,每个partition就放一部分数据。
kafka0.8版本后,提供了HA机制。就是replica副本机制。每个partition的数据都会同步到其他机器上,形成自己的多个replica副本。然后所有replica会选举一个leader出来,那么生产和消费都跟这个leader打交道,然后其他replica就是follower。写的时候,leader会负责把数据同步到所有follower上去,读的时候就直接读leader上数据即可。只能读写leader?很简单,要是你可以随意读写每个follower,那么就要care数据一致性的问题,系统复杂度太高,很容易出问题。kafka会均匀的将一个partition的所有replica分布在不同的机器上,这样才可以提高容错性。
如何保证消息不被重复消费?(幂等性)
简单来说,幂等性就是一个数据或者一个请求,给你重复来了多次,你得确保对应的数据是不会改变的,不能出错。
无论是哪一种MQ都会出现消息重复的问题,这个问题通常不是有MQ来保证的,由消费方自己决定的。
kafka可能出现重复消费的问题:主要的原因还是在指定的时间内,没有进行kafka的位移提交,导致根据上一次的位移重新poll出新的数据,而这个数据就是上一次没有消费处理完全的(即没有进行offset提交的,消费者定时提交出现了问题),这也是导致kafka重复数据的原因
- 数据库:根据主键检查或者唯一索引
- redis:天然幂等性
- 复杂点:消息加全局id,消费者消费时,去redis查一下。
- 具体还是要结合自己的业务去做衡量。
怎么处理消息丢失的问题?
https://www.cnblogs.com/wt645631686/p/11437590.html
一条消息从生产到消费完,可以划分三个阶段。
- 生产阶段: 在这个阶段,从消息在 Producer 创建出来,经过网络传输发送到 Broker 端。
- 存储阶段: 在这个阶段,消息在 Broker 端存储,如果是集群,消息会在这个阶段被复制到其他的副本上。
- 消费阶段: 在这个阶段,Consumer 从 Broker 上拉取消息,经过网络传输发送到 Consumer 上。
rabbitMq
生产者:
- 事务:同步 影响性能
- +confirm:异步,每次消息会分配一个id MQ会告诉生产者已接受,如果未接受,生产者可以重发。
MQ:
- rabbitMq开启持久化,但是会有很小的概率出现还没持久化,rabbitMq就挂的情况产生。
- 设置持久化有两个步骤,第一个是创建queue的时候将其设置为持久化的,这样就可以保证RabbitMQ持久化queue的元数据,但是不会持久化queue里的数据;第二个是发送消息的时候将消息的deliveryMode设置为2,就是将消息设置为持久化的,此时RabbitMQ就会将消息持久化到磁盘上去。必须要同时设置这两个持久化才行,RabbitMQ哪怕是挂了,再次重启,也会从磁盘上重启恢复queue,恢复这个queue里的数据。
- 而且持久化可以跟生产者那边的confirm机制配合起来,只有消息被持久化到磁盘之后,才会通知生产者ack了,所以哪怕是在持久化到磁盘之前,RabbitMQ挂了,数据丢了,生产者收不到ack,你也是可以自己重发的。
消费者
- RabbitMQ提供的ack机制,简单来说,就是你关闭RabbitMQ自动ack,可以通过一个api来调用就行,然后每次你自己代码里确保处理完的时候,再程序里ack一把。
kafka
生产者
- 在producer端设置acks=all:这个是要求每条数据,必须是写入所有replica之后,才能认为是写成功了,如果没有成功就不断发送。
MQ
-
在topic设置replication.factor参数:这个值必须大于1,要求每个partition必须有至少2个副本
-
在kafka服务端设置min.insync.replicas参数:这个值必须大于1,这个是要求一个leader至少感知到有至少一个follower还跟自己保持联系,没掉队,这样才能确保leader挂了还有一个follower
-
在producer端设置acks=all:这个是要求每条数据,必须是写入所有replica之后,才能认为是写成功了
-
在producer端设置retries=MAX(无限次重试的意思):这个是要求一旦写入失败,就无限重试,卡在这里了
消费者
- 那么只要关闭自动提交offset,在处理完之后自己手动提交offset,就可以保证数据不会丢。但是会有自己挂的情况,此时需要处理消息的幂等性。
个人小结
- 在生产阶段,你需要捕获消息发送的错误,并重发消息。
- 在存储阶段,你可以通过配置刷盘和复制相关的参数,让消息写入到多个副本的磁盘上,来确保消息不会因为某个 Broker 宕机或者磁盘损坏而丢失。
- 在消费阶段,你需要在处理完全部消费业务逻辑之后,再发送消费确认。
怎么保证消息顺序执行?
为什么要保证消息顺序消费?
- 如果消息是对同一个数据库进行操作,必须要保证先后顺序。
顺序错乱的场景
- rabbitMq:一个queue,多个消费者
- kafka:一个topic,一个part,一个consumer,内部多线程消费。
- 还有就是网络延迟导致造成的消息错乱()
解决
- rabbitMq:一个queue对应一个consumet。或者consumer内存在排列。
- kafaka: consumer内部单线程消费,写N个内存queue,然后多个线程消费一个queue(写入一个part的数据一定时顺序的,生产者写入的时候指定key)
消息队列中消息积压怎么处理?
第一种情况:consumer消费的太慢了
1)先修复consumer的问题,确保其恢复消费速度,然后将现有cnosumer都停掉
2)新建一个topic,partition是原来的10倍,临时建立好原先10倍或者20倍的queue数量
3)然后写一个临时的分发数据的consumer程序,这个程序部署上去消费积压的数据,消费之后不做耗时的处理,直接均匀轮询写入临时建立好的10倍数量的queue
4)接着临时征用10倍的机器来部署consumer,每一批consumer消费一个临时queue的数据
5)这种做法相当于是临时将queue资源和consumer资源扩大10倍,以正常的10倍速度来消费数据
6)等快速消费完积压数据之后,得恢复原先部署架构,重新用原先的consumer机器来消费消息
MQ消息过期
将丢失的那批数据,写个临时程序,一点一点的查出来,然后重新灌入mq里面去,把白天丢的数据给他补回来。也只能是这样了。
MQ磁盘满了
临时写程序,接入数据来消费,消费一个丢弃一个,都不要了,快速消费掉所有的消息。然后走第二个方案,到了晚上再补数据吧。