https://www.bilibili.com/video/BV1Xf4y1u7uD?p=38
https://www.bilibili.com/video/BV1cf4y157sz?p=102
尚硅谷rocketMq:https://www.bilibili.com/video/BV1cf4y157sz?p=1&share_medium=android&share_plat=android&share_session_id=18c0028f-b7b6-4fb6-b2ea-34a5b100ccaf&share_source=WEIXIN&share_tag=s_i×tamp=1639821947&unique_k=zcZy8VO
kafka
kafka中的ZooKeeper是做什么的呢?
它是一个分布式协调框架,负责协调管理并保存 Kafka 集群的所有元数据信息,比如集群都有哪些 Broker 在运行、创建了哪些 Topic,每个 Topic 都有多少分区以及这些分区的 Leader 副本都在哪些机器上等信息。
- /brokers/ids: 临时节点,保存所有broker节点信息,存储broker的物理地址、版本信息、启动时间等,节点名称为brokerID(broker定时发送心跳到zk,如果断开,则删除该brokerID对应的节点)
- /broker/topics: 临时节点,节点保存broker节点下所有topic信息,每一个topic节点下包含一个固定的partition节点,partition的子节点就是topic的分区,每个分区下保存了一个state节点,保存着当前leader分区和ISR的brokerID,state节点由leader创建,若leader宕机该节点会被删除,知道有新的leader选举产生,重新生成state节点
- /consumer/[组id]/offset/[topic]/[broker_id-partition_id]:分区消息的消费进度offset
- /consumer/[组id]/owners/[topic]/[broker_id-partition_id]:维护消费者和分区的注册关系
Kafka对节点的存活定义有两个条件:ISR集合中
a) 节点必须和ZK保持会话
b) 如果这个节点是某个分区的备份副本,它必须对分区主副本的写操作进行复制,并且复制的进度不能落后太多。(follower的leo落后leader的leo超过阈值)
主题topic是一个逻辑上的概念,它还可以细分为多个分区,“一个分区只属于单个主题”,很多时候也会把分区称为主题分区(Topic-Partition)。
1.分区在存储层面可以看作一个可追加的日志(Log)文件
2.消息在被追加到分区日志文件的时候都会分配一个特定的偏移量(offset)
3.偏移量offset 是消息在分区中的唯一标识,是一个单调递增且不变的值。Kafka 通过它来保证消息在分区内的顺序性,不过 offset 并不跨越分区,也就是说,Kafka 保证的是分区有序而不是主题有序
主从是以分区为单位的(不是以topic为单位的),一个topic包含多个分区,多个分区可以横跨多个broker,每个分区包含副本,一个个分区形成自己从主从。
- 分区:类似于分片条带化,支持并发读写,提高读写效率(一个分区有一个日志
- 副本:冗余备份,高可用(主读写,从只做数据同步)
与sql/redis不同,从节点不提供读能力
- kafka并不是“读多写少”的读写分离场景,它通常涉及到生产/消费msg
- Read-your-writes:当你使用生产者 API 向 Kafka 成功写入消息后,马上使用消费者 API 去读取刚才生产的消息
副本同步机制
需要先理解几个概念:Leo、HW
1. ISR/OSR - In/Out-of-Sync Replicas - 主从切换
2. HW/LEO - 高水位, ISR 集合中最小的 LEO 即为分区的HW /Log End Offset,当前日志文件中下一条待写入消息的offset,分区ISR集合中的每个副本都会维护自身的LEO
主从同步过程
- leader收到消息后,会更新本地的leo;leader还维护了follower的leo(即remote leo)
- follower发出fetch同步数据请求时(携带自身的leo)
- leader会更新remote的leo,更新自己分区的HW,然后将数据响应给follower,之后follower更新自身的HW
消费模型 push / pull —— kafka是pull模型
- push
缺点:1)没考虑消费者的消费能力 2)推送完消息后设置消费成功,但是消费者挂了,推送的消息会丢失。需要复杂的逻辑来保证一致性
优点:及时性强
- pull
缺点:及时性差
优点:1)消费者可以根据自己的消费能力拉取消息 2)消费成功后,修改offset,消息不会丢失
一对一 / 发布-订阅 / 消费者组
消费者组成员上下线怎么识别?答:与协调者发送心跳(借助Kafka Broker端的Coordinator组件)。所谓协调者,在Kafka中对应的术语是Coordinator,它专门为ConsumerGroup服务,负责为Group执行Rebalance以及提供位移管理和组成员管理等
kafka采用reactor模式,接收和处理客户端的请求
kafka高性能的原因
- 日志采用: 顺序追加写+log_segment
- page-cache
向 Kafka 发送数据并不是真要等数据被写入磁盘才会认为成功,而是只要数据被写入到操作系统的页缓存(Page Cache)上就可以了,随后操作系统根据 LRU 算法会定期将页缓存上的“脏”数据落盘到物理磁盘上。这个定期就是由提交时间来确定的,默认是 5 秒。一般情况下我们会认为这个时间太频繁了,可以适当地增加提交间隔来降低物理磁盘的写操作。当然你可能会有这样的疑问:如果在页缓存中的数据在写入到磁盘前机器宕机了,那岂不是数据就丢失了。的确,这种情况数据确实就丢失了,但鉴于 Kafka 在软件层面已经提供了多副本的冗余机制,因此这里稍微拉大提交间隔去换取性能还是一个合理的做法。
- 发送消息:批量+压缩,降低带宽
- 零拷贝:消费者消费消息时,需要从磁盘中读取消息,发送到消费者进程(消费者进程通常涉及到不同的网络节点)
对比:传统读取磁盘文件的数据在每次发送到网络时,具体步骤如下:
1)操作系统将数据从磁盘中读取文件到内核空间里的页面缓存
2)应用程序将数据从内核空间读入用户空间的缓冲区
3)应用程序将读到的数据写回内核空间并放入socket缓冲区
4)操作系统将数据从socket缓冲区复制到网卡接口,此时数据才能通过网络发送出去
为什么kafka能使用零拷贝呢?:结合Kafka的消息有多个订阅者的使用场景,生产者发布的消息一般会被不同的消费者消费多次。
零拷贝:磁盘文件==>内核空间的读取缓冲区==>网卡接口==>消费者进程
1)只需将磁盘文件的数据,读取到内核缓冲区中(只需要读取一次)
2)将内核缓冲去中的数据,直接发送到网卡中(发送给不同的使用者时,都可以重复使用同一个页面缓存),避免了重复的复制操作。这样,消息使用的速度基本上等同于网络连接的速度了。
kafka如何保证消息不丢失
- 配置
1)ack
0 - 不需要任何的broker收到消息,就立即返回ack给生产者
1 - Leader收到消息,消息写入到log,才返回ack给生产者
-1或all,min.insync.replicas>1
2)unclean,leader,election,enable配置为false(不允许选择OSR中的从节点作为主节点)
- 生产者:消息发送+回调
1)producer.send(msg, callback)——捕获失败的消息,保存到db中,重试
2)重试次数>1
- 消费者:手动提交消息
- 减小broker刷盘间隔
消息顺序性保证(rocketMQ实现了该机制)
说明:MQ只能保证分区内的局部有序,不能保证全局有序
- 生产者:需要有序的一组消息,通过指定partition发送到同一个partition中
- 消费者:注册有序的监听
幂等
- 数据库/缓存
- 全局唯一ID: 带业务表示的ID,来进行幂等判断
- 本地消息表
消息顺序消费
生产者:保证消息按顺序消费,且消息不丢失——使用同步的发送,ack设置成非0的 值。
消费者:主题只能设置一个分区,消费组中只能有一个消费者
消息堆积
- 消息pull时间间隔过大
- 消费耗时
- 消费并发度
- 单线程计算
- 如果你使用的是消费者组,确保没有频繁地发生rebalance
视频:https://www.bilibili.com/video/BV1HP4y157tx?p=29
重平衡 rebalance:消费组中的消费者与topic下的分区重新匹配的过程
危害:消费暂停、消费突增、消费重复
重平衡Rebalance: 让一个消费者组下的“多个消费者”就如何消费“订阅主题的所有分区”达成共识的过程
发生rebalance的条件:1)消费者组成员数变化 2)订阅主题的分区数发生变更 3)订阅主题数发生变化
1)消费者分区分配策略:范围、轮询、Sticky
2)发生时机:topic个数(正则topic)、消费组中消费者个数、分区个数、消费消息超时
3)Coordinator:通常是分区的leader节点所在的broker,通过心跳机制监控消费组中的consumer是否存活,判断consumer是否消费超时
- Coordinator通过心跳返回,通知consumer,此时要开始进行rebalance
- consumer请求Coordinator,说我要加入组
- 所有的consumer加入组后,Coordinator选举中产生leader consumer
- leader consumer从Coordinator获取所有的consumer,发送syncGroup(分配信息)给到Coordinator
- Coordinator通过心跳机制将syncGroup下发给consumer
- 完成rebalance
rebalance过程中存在消息不一致问题
场景:
- 如果消费者C1消费超时,(此时,还没有提交offset),触发了rebalance
- 重新分配后,由于之前的消息消费失败,生产者重试,此时可能出现这种情况:该消息会发到其他消费者C2上。当C2完成消费后,提交了offset
- 此时,C1消费者真正的完成了消费动作,有执行提交offset(此时,就存在错误了:因为一个msg,却提交了2次offset)
解决方案:
- Coordinator每次执行rebalance,都会标记一个epoch给到consumer(每次rebalance该epoch会+1)
- consumer提交offset时,Coordinator会比对epoch,不一致则拒绝提交
3种发送方式
at most once:丢失消息
at least once:消息重复
exactly once:不丢消息,消息不重复
死信队列
-
用来存放消费失败超过设置次数的消息,通常用来作为消息重试
-
特征
-
消息不会被消费者正常消费
-
有效期与正常消息相同,均为3天
-
死信队列就是一个特殊的topic
-
如果一个消费这组未产生死信消息,则不会为其创建相应的死信队列
延时队列
存放在指定时间后被处理的消息,通常用来处理一些具有过期性操作的业务(如10min内未支付则取消订单)
rocketMQ
基本概念
1)消息
2)product
3)consumer
4)topic
5)queue / partition
6)tag 消息标识:每个消息拥有唯一的messageID,且可以携带具有业务表示的key
7)NameServer:作为broker与topic路由的注册中心,支持broker的动态注册与发现
NameServer 功能
- broker管理:接收broker集群的注册信息&&保存下来作为陆游与信息的基本数据;提供心跳检测机制,检查broker是否存活
- 路由信息管理:保存了broker集群的整个路由信息和用于客户端查询的队列信息。product和consumer通过NameServer可以获取整个broker集群的路由信息,从而进行消息的投递与消费。
2.1)路由注册:broker维持和NameServer的心跳,每30s发送一次
2.2)路由踢出
2.3)路由发现:rocketMQ的路由发现采用pull模型。客户端每隔30s主动拉取topic路由信息
工作流程
- 启动NameServer,NameServer启动后开始监听端口,等待broker、producter、consumer连接
- 启动broker时,broker会与所有的NameServer建立并保持长连接,每隔30s向NameServer定时发送心跳包
- 收发消息前,可以县创建topic,创建topic时需要指定该topic要存储在那些broker上(broker与topic的绑定关系写入到NameServer中)
- producter发送消息:从NameServer获取topic路由信息,根据分配算法选择queue发送
- consumer接收消息:从NameServer获取订阅的topic的路由信息,然后根据分配算法选择queue数据消费
阿里技术:深度剖析 Kafka/RocketMQ 顺序消息的一些坑
https://www.bilibili.com/read/cv8757048
生产者:保证单线程同步发送,将顺序消息发送到同一个分区
- Kafka 集群中有哪些意外情况会打乱消息的顺序
- 生产者异步发送消息,比如异步发送了消息1-2-3,2消息发送异常重试发送,这时顺序就乱了
- 分区个数发生变更:key取模算法
- 分区个数不变更
- 分区单副本:假设此时集群有两个分区的主题 A,副本因子为 1,生产端需要往分区 1 发送 3 条顺序消息,前两条消息已成功发送到分区 1,此时分区 1 所在的 broker 挂了(由于副本因子只有 1,因此会导致分区 1 不可用),当生产端发送第三条消息时发现分区 1 不可用,就会导致发送失败,然后尝试进行重试发送,如果此时分区 1 还未恢复可用,这时生产端会将消息路由到其它分区,导致了这三条消息不在同一个分区
- 分区多副本:
- 针对分区单副本情况,我们自然会想到将分区设置为多副本不就可以避免这种情况发生吗?多副本情况下,发送端同步发送,acks = all,即保证消息都同步到全部副本后,才返回发送成功,保证了所有副本都处在 ISR 列表中,如果此时其中一个 broker 宕机了,也不会导致分区不可用的情况,看起来确实避免了分区单副本分区不可用导致消息路由到其它分区的情况发生。==> 但我想说的是,还有一种极端的现象会发生,当某个 broker 宕机了,处在这个 broker 上的 leader 副本就不可用了,此时 controller 会进行该分区的 leader 选举,在选举过程中分区 leader不可用,生产端会短暂报 no leader 警告,这时生产端也会出现消息被路由到其它分区的可能
消费者
- Kafka 集群中有哪些意外情况会打乱消息的顺序
- rebalance导致扥去被分配给其他的消费者
- 解决方案
- 多分区:每个线程维护kafkaConsumer实例,并且是一条一条去拉取消息进行消费
- 单分区:由于不存在reblance,都可以保证消息消费的顺序性
rocketmq事务消息
https://blog.csdn.net/hosaos/article/details/90050276
**场景:**同时保证(本地事务+发送消息到MQ)都成功,例如,生成订单(插入到订单表),增加积分(发送消息到mq)
提前理解几个概念
- 本地事务
- 生产者
- broker
- 消费者
- 两个topic
- 半消息队列:此时,消息不能被consumer消费
- 半消息op队列:执行commit/rollback的消息,能被consumer消费
Rocketmq执行过程(类似2阶段提交)
- producter:sendMessage(msg, callback)
- producter生产半消息half-msg到broker的半消息队列(此时的消息不能被consumer消费)
- 发送到半消息队列后,会执行注册的callback(回调函数一般是执行本地事务)
- 当本地事务执行成功后,根据本地事务的执行状态succ/fail,发送commit/rollback/unknow到broker中的半消息op队列。① commit:消费者能消费改消息 ② rollback:消息会被丢弃 ③ unknow:定时回查
broker定时回查事务状态
- 场景:发送commit/rollback到broker中的半消息op队列可能会丢失
- 解决方案:broker定时去查询本地事务的执行结果,查询到commit/rollback后,执行消息的提交/丢弃