消息队列
-
- 优缺点
- 特点
- 解耦
- 异步
- 削峰
- 缺点
- 系统可用性降低
- 兜底:代码中try、catch 异常捕捉后直接进行数据库操作,或者 搭建高可用集群,Kafka集群、RocketMQ集群
- 提高复杂度
- 消息重复(消费端的幂等性设计)、消息丢失(主要集中RabbitMQ)、消息的顺序(业务:1,下单 2,支付 3,发送物流 4,通知)
- 一致性问题
- a,b,c三个系统,a、b两个写入数据库成功了,c系统写库失败?使用分布式事务来控制。RocketMQ提供了,其他的seta方式
- 系统可用性降低
- 特点
- 解耦
- 异步
- 削峰
- 缺点
- 系统可用性降低
- 兜底:代码中try、catch 异常捕捉后直接进行数据库操作,或者 搭建高可用集群,Kafka集群、RocketMQ集群
- 提高复杂度
- 消息重复(消费端的幂等性设计)、消息丢失(主要集中RabbitMQ)、消息的顺序(业务:1,下单 2,支付 3,发送物流 4,通知)
- 一致性问题
- a,b,c三个系统,a、b两个写入数据库成功了,c系统写库失败?使用分布式事务来控制。RocketMQ提供了,其他的seta方式
- 系统可用性降低
- 特点
- RabbitMQ、Kafka、RocketMQ 对比
- 性能角度
- RabbitMQ 1.2w
- Kafka 100w
- Rocket MQ 10w
- 集群扩展支持
- Rabbit MQ集群很弱(确保高可用 不能扩展性能)
- Kafka 天生分布式,支持动态扩展
- Rocket MQ 天生分布式,支持动态扩展
- 功能
- Rabbit MQ比较丰富(死信消息、延迟消息)
- Kafka比较弱
- Rocket MQ比较丰富(死信、延迟、消息回溯、消息过滤)
- 性能角度
- 常见问题及解决方案
- 重复消费
- 死信消息
- MVCC(多版本控制)
- 对整体业务改动较大,使用很不便利,代价较高
- 去重表(MySQL、Redis)
- 表上构建唯一性索引
- try{处理业务:插入数据/判断是否存在}catch(exception e){return xxx (直接返回)}
- 消费失败
- 补偿机制(如 catch中处理)
- 购买电影票,选座位未支付,锁定座位等场景
- 延迟消息
- 如何设计一个消息队列
- 存储层:高可用--磁盘存储,顺序读写、零拷贝技术
- 可伸缩:分布式,参考kafka broker—》topic->partition
- 消息的丢失:多主多从、多副本、raft协议 一台主服务器 宕机 选举机制
- 网络框架:Netty 高效NIO框架
- 重复消费
- 核心技术原理分析
- 零拷贝机制
- Rabbit MQ
- 磁盘-》文件读取缓冲区(内存)-》应用缓冲区(Buffer)-》套接字发送缓冲区-》网卡-》消费者
- DMA拷贝----------------------------》CPU拷贝------------》CPU拷贝------------------》DMA拷贝->拉去消息进行消费
- Rocket MQ(MMAP)
- 磁盘-》文件读取缓冲区(内存)-》应用缓冲区(Buffer)-》套接字发送缓冲区-》网卡-》消费者
- DMA拷贝-----》调用操作系统的mmap函数内存映射--》CPU拷贝---------》DMA拷贝->拉去消息进行消费
- Kafka (sendfile)
- 磁盘-》文件读取缓冲区(内存)-》应用缓冲区(Buffer)-》套接字发送缓冲区-》网卡-》消费者
- DMA拷贝-----》文件描述符传递(类似指针)-------》文件描述符传递---------》DMA拷贝->拉去消息进行消费
- 磁盘-》文件读取缓冲区(内存)- - - - 》共享- - - - -》套接字发送缓冲区-》网卡-》消费者
- Rabbit MQ
- DMA拷贝
- direct Memory Access 直接内存读取
- 一般是 驱动(内核、操作系统)
- 没有DMA 通过CPU的大量中断来负载,性能会很慢
- 对比性能:CPU中断及DMA技术,相差大概100倍左右
- MMAP的零拷贝技术
- 内存映射
- 数据不会到应用层,无法做死信消息、延迟消息等需要利用数据流转来进行的功能
- 零拷贝机制
- 三大消息队列原理分析
- Kafka
- rebalance机制
- 消费者分区分配策略
- Range范围分区(默认的)
- RoundRobin轮询分区
- Sticky策略
- 触发Rebalance的时机
- 组成员个数发生变化。例如 有新的consumer实例加入该消费者组或者离开组
- 订阅的Topic个数发生变化
- 订阅Topic的分区数发生变化
- Coordinator协调过程
- 消费者如何发现协调者
- 消费者如何确定分配策略
- 消费者分区分配策略
- 消息丢失问题分析
- 消息丢失场景
- 生产者在生产过程中的消息丢失
- Broker在故障后的消息丢失
- 消费者在消费过程中的消息丢失
- ACK机制
- ack=0
- 生产者在生产过程中的消息丢失,简单说就是 produce发送一次就不再发送了,不管是否发送成功
- ack=1
- Broker在故障后的消息丢失。
- 简单说就是 produce只要收到一个分区副本成功写入的通知就认为推送消息成功了,需注意该副本必须是leader副本,只有leader副本成功写入,produce才会认为消息发送成功
- 默认值,该值就是吞吐量与可靠性的一个折中方案
- ack=-1
- 生产侧和存储侧不会丢失数据
- 简单说 producer 只有收到分区内所有副本的成功写入的通知才认为推送消息成功
- offset机制
- at-most-once:最多一次,可能丢数据
- al-least-once:最少一次,可能重复消费消息
- exact-once-message:精确一次
- ack=0
- 消息丢失场景
- Kafka中ZK的作用
- kafka中使用了zookeeper的分布式锁和分布式配置及统一命名的分布式协调解决方案
- 在kafka的broker集群中的controller的选择,是通过zk的临时节点争抢获得的
- brokerId等如果自增的话也是通过zk的节点version实现的全局唯一
- kafka中broker中的状态数据也是存储在zk中,不过这里要注意,zk不是数据库,所以存储的属于元数据,新旧版本变化中,就把曾经的offset从zk中迁移出了zk
- kafka中高性能如何保障
- broker在持久化数据时使用了磁盘的顺序读写
- 零拷贝技术(sendfile)
- 可靠性与可用性的权衡,ack的0,1,-1取值
- rebalance机制
- Rabbit MQ
- AMQP架构
- Broker:提供一种传输服务,它的角色就是维护一条从生产者到消费者的路线,保证数据能按照指定的方式进行传输
- Exchange:消息交换机,指定消息按什么规则,路由到哪个队列
- Queue:消息的载体,每个消息都会被投到一个或多个队列
- Binding:它的作用就是把exchange和queue按照路由规则绑定起来
- Routing Key:路由关键字,exchange根据这个关键字进行消息投递
- vhost:虚拟主机,一个broker里可以有多个vhost,用作不同用户的权限分离
- Producer:消息生产者
- Consumer:消息消费者
- Channel:消息通道,在客户端的每个连接里,可建立多个channel
- 核心概念
- 在mq领域中,producer将msg发送到queue,然后consumer通过消费queue完成 P.C解耦
- kafka是由producer决定msg发送到哪个queue
- rabbitmq是由Exchange决定msg应该怎么样发送到目标queue,这就是binding及对应的策略
- Exchange
- Direct Exchange:直接匹配,通过Exchange名称+RountingKey来发送与接收消息
- Fanout Exchange:广播订阅,向所有的消费者发布消息,但是只有消费者将队列绑定到该路由器才能接收到消息
- Topic Exchange:主题匹配订阅,这里的主题指的是RoutingKey,可以通过采用通配符 * 或 #等。RoutingKey命名采用 . 来分隔多个词,只有消息者将队列绑定到该路由器且指定RoutingKey符合匹配规则时才能收到消息
- Headers Exchange:消息头订阅,消息发布前,为消息定义一个或多个键值对的消息头,然后消费者接收消息同时需要定义类似的键值对请求头(如:x-match=all或x_match=any),只有请求头与消息头匹配,才能接收消息
- 默认的exchange:如果用空字符串去声明一个exchange,那么系统就会使用 “amq.direct”这个exchange,我们创建一个queue时,默认的都会有一个和新建queue同名的routingKey绑定到这个默认的exchange上去
- 事务消息
- 发送方事务
- 开启事务,发送多条数据
- 消费方事务
- 消费行为会触发queue中msg是否删除、是否重新放回队列等行为,类增删改。消费方的ack是要手动提交的,且最终确定以事务的提交或回滚决定
- 发送方事务
- 消息发送及消息接收
- 消息发送
- ConfirmCallback方法
- 是一个回调接口,消息发送到Broker触发回调,确认消息是否到达Broker服务器,也就是只确定是否正确到达Exchange中
- ReturnCallback
- 通过实现ReturnCallback接口,启动消息失败返回,此接口是在交换器路由不到队列时触发回调,该方法可以不使用,因为交换器和队列是在代码里默认绑定的
- ConfirmCallback方法
- 消息接收
- ACK机制,默认是自动确认
- 消息确认模式
- AcknowledgeMode.None:自动确认
- AcknowledgeMode.Auto:根据情况确认
- AcknowledgeMode.MANUAL:手动确认
- 手动确认消息命令
- Basic.Ack:用于确认当前消息
- Basic.Nack:用于否定当前消息(注意:这是 AMQP 0-9-1的RabbitMQ扩展
- Basic.Reject:用于拒绝当前消息
- 消息发送
- 死信队列、延迟队列
- 死信队列
- DLX(Dead Letter Exchange) 死信交换器,当队列中的消息被拒绝、或者过期会变成死信,死信可以被重新发布到另一个交换器,这个交换器就是DLX,与DLX绑定的队列称为死信队列
- 造成死信的原因
- 信息被拒绝
- 信息超时
- 超过了队列的最大长度
- 过期消息的设置方式
- 队列设置:在队列申明的时候使用 x-message-tt 参数,单位为 毫秒,设置后,该队列中所有的消息都存在相同的过期时间
- 单个消息设置:设置消息属性 expiration 参数的值,单位为毫秒
- 同时使用以上两种方式,会以过期时间小的那个数值为准,当消息达到过期时间还没有被消费就会成为死信消息
- 延迟队列
- 延迟队列存储的是延迟消息
- 延迟消息指 当消息被发布出去后,并不立即投递给消费者,而是在指定时间后投递
- RabbitMQ 不直接支持延迟队列,可通过死信队列实现。在死信队列中,可以为普通交换器绑定多个消息队列,假设绑定过期时间为5分钟,10分钟和30分钟,3个消息队列,然后为每个消息队列设置DLX,为每个DLX关联一个死信队列,当消息过期后,被转存到对应的死信队列中,然后投递给指定的消费者消费
- 死信队列
- AMQP架构
- Rocket MQ
- 刷盘策略
- 同步刷盘
- 生产者发送消息->Memory(Master1)->同步到Disk(Master1)-》返回
- 异步刷盘
- 生产者发送消息->Memory(Master1)->异步到Disk(Master1) 并返回
- 同步刷盘
- Rocket MQ中Broker的部署方式
- 单机Broker模式
- 多Master模式
- 多Master多Slave模式(一般用于生产环境)
- 几百万的消息积压如何解决
- 消费者端临时扩容
- 队列临时扩大10倍、消费端服务器扩大10倍
- 性能高的原因
- 网络通信层:使用Netty NIO框架
- 使用大量多线程、异步
- 使用零拷贝技术(MMAP)
- 采用文件存储,顺序读写
- 锁优化(CAS机制 无锁化)
- 存储设计:读写分离
- 存储机制
- 生产者发送的消息-》Commit Log文件
- 异步线程监听 Commit Log文件,Commit Log文件主要用来写文件
- 异步线程会将Commit Log文件内容的索引信息存储在Comsumequeue队列中
- 消息的消费从Comsumequeue队列中获取索引后再获取数据
- 增加 Comsumequeue 的目的在于防止大并发情况下消息topic增多产生的线程IO切换频繁问题
- 如何保证高可用
- master节点与slave节点之间 同步复制
- master内部 Memory与Disk之间 异步刷盘
- 多主多从模式
- 刷盘策略
- Kafka
- 优缺点