Kafka
MQ概念
MQ是什么:Message Queue 消息队列,在消息的传输过程中用于保存消息的容器
MQ应用场景
1. 应用解耦
2. 异步提速
3. 限流削峰
常用MQ比对
Kafka概述与环境搭建
Kafka 概述
Kakfa是一个分布式的基于发布/订阅模式的消息队列(message queue),主要应用于大数据的实时处理领域。具有以下特性
1、高吞吐量、低延迟
2、持久性、可靠性
3、高并发
4、容错性
5、可扩展性
6、消息顺序性
Kafka应用场景
1.消息队列
2.日志收集
3.网站活性跟踪
4.运营指标监控
5.流式处理
KafKa 资源
GITHUB:https://github.com/apache/kafka
英文文档:https://kafka.apache.org/documentation/
中文文档:https://kafka.apache.org/34/documentation.html
环境安装
KafKa 架构设计
Broker
- Broker就是Kafka的服务器,用于存储和管理消息,默认是9092的端口
- 生产者和Broker建立连接,将消息发送到服务器上、存储起来
- 消费者跟Broker建立连接,订阅和消费服务器上存储的消息
Record
- 客户端之间传输的数据叫做消息,在Kafka中也叫Record(记录)
- Record在客户端中是一个KV键值对(ProducerRecord、ConsumerRecord)
- Record在服务端中的存储格式也是KV键值对(RecordBatch 或Record)
Producer
- 发送消息的一方叫做生产者
- Kafka为提升消息发送速率,生产者默认采用批量发送的方式发送消息至Broker
- 一次发送多少条由参数 batch.size 决定
Consumer
- 订阅、接收消息的一方叫做消费者
- 消费者端获取消息有两种模式:Pull模式[拉]、Push模式[推]
- Pull模式,消费者可以自己控制一次到底获取多少条消息(max.poll.records)
Topic
- Topic(主题)是一个逻辑概念,可以理解为一组消息的集合
- 生产者和消费者通过Topic进行消息的写入和读取
- 生产者发送消息时,若Topic不存在,是否自动创建:auto.create.topics.enable
Partintion
- 所谓分区(Partition)就是把一个Topic分成几个不同的部分
- 一个Topic可以在创建时划分成多个分区
- 若没有指定分区数,默认分区数为1,通过参数可修改(num.partitions)
- Kafka中修改分区的规则:可加、不可减
Replica 机制
- Replica(副本)是Partition(分区)的副本,每个分区可以有若干个副本
- 副本必须在不同的Broker节点上,副本包括了主从节点(Leader、Follower)
- 服务端可以通过参数控制默认副本数(offsets.topic.replication.factor)
Segment
- Segment(段)的目的是:将一个分区中的数据划分、存储到不同的文件中
- 每个Segment至少由1个数据文件和2个索引文件构成,3个文件是成套出现的
- 引入段带来的意义:加快查询效率;删除数据时减少逐条IO
- Segment大小控制:按时间周期生成(log.roll.hours);按文件大小生成(log.segment.bytes)
Consumer Group
- 使用消费者组,提升消费效率和吞吐量
- 同一个Group中的消费者,不能消费相同的Partition(group id相同,在一个组中)
Consumer Offset
- Offset(偏移量)的目的在于:记录消费者的消费位置
- Kafka现行版本将Offset保存在服务器(__consumer_offsets)主题中
Kafka架构图示
Kafka 特性
磁盘顺序I/O
索引
Broker端原理数据存储
Offset索引、时间戳索引、稀疏索引
批量读写和压缩算法
收发消息时批量处理
压缩算法进行压缩后传递
零拷贝
Kafka Java API
Java API
Kafka与Springboot集成
Kafka 高阶功能
消息幂等性
开启生产者幂等性: enable.idempotence = true(默认值)
幂等性生产者发送消息流程总结:
1、Producer端发送消息(消息本身、PID、Sequence Number)
2、Broker端接收到消息(将消息和PID、Sequence Number一起保存)
3、若ACK响应失败,生产者重试,再次发送消息
生产者事务
Kafka事务API相关方法
- initTransactions() 初始化事务
- beginTransaction() 开启事务
- commitTransaction() 提交事务
- abortTransaction() 中止事务
- sendOffsetsToTransaction()
Producer 原理
1 生产者发送消息流程
消息发送流程分析
Kafka在发送消息的过程中, 主要涉及两个线程 main线程和 sender线程。
在main线程中创建了一个双端队列 RecordAccumulator. main线程将消息发送给 RecordAccumulator。
Sender线程不断从RecordAccumulator 中拉取消息发送到Kafka Broker。
拦截器
拦截器的作用是实现消息的定制化(类似于Spring Interceptor、MyBatis的插件、Quartz的监听器)。
拦截器使用. KafkaProducer.java
public Future<RecordMetadata> send(ProducerRecord<K, V> record, Callback callback) {
ProducerRecord<K, V> interceptedRecord = this.interceptors.onSend(record);
return this.doSend(interceptedRecord, callback);
}
自定义拦截器
/**
* 自定义拦截器
*/
public class ChargingInterceptor implements ProducerInterceptor<String, String> {
// 发送消息的时候触发
@Override
public ProducerRecord<String, String> onSend(ProducerRecord<String, String> record) {
System.out.println("1分钱1条消息,不管那么多反正先扣钱");
return record;
}
// 收到服务端的ACK的时候触发
@Override
public void onAcknowledgement(RecordMetadata metadata, Exception exception) {
System.out.println("消息被服务端接收啦");
}
@Override
public void close() {
System.out.println("生产者关闭了");
}
// 用键值对配置的时候触发
@Override
public void configure(Map<String, ?> configs) {
System.out.println("configure...");
}
}
装配自定义拦截器
// 添加拦截器
List<String> interceptors = new ArrayList<>();
interceptors.add("com.mq.kafka.javaapi.interceptor.ChargingInterceptor");
props.put(ProducerConfig.INTERCEPTOR_CLASSES_CONFIG, interceptors);
序列化
接下来是利用指定的工具对key和value进行序列化:
pros.put("key.serializer","org.apache.kafka.common.serialization.StringSerializer"); pros.put("value.serializer","org.apache.kafka.common.serialization.StringSerializer");
分区器
分区指定
int partition = this.partition(record, serializedKey, serializedValue, cluster);
一条消息会发送到哪个partition呢?它返回的是一个分区的编号,从0开始。有四种情况:
- 指定了partition——直接将指定的值直接作为partiton值。
- 没有指定partition,自定义了分区器——将使用自定义的分区器算法选择分区。
- 没有指定partition,没有自定义分区器,但是key不为空——使用默认分区器DefaultPartitioner,将key的hash值与topic的partition数进行取余得到partition值;
- 没有指定partition,没有自定义分区器,但是key是空的——第一次调用时随机生成一个整数(后面每次调用在这个整数上自增),将这个值与topic可用的partition总数取余得到 partition值,也就是常说的round-robin算法。
2 生产者数据可靠性保证-ACK机制
服务器端响应策略
生产者的消息是不是发出去就完事了?如果说网络出了问题,或者说kafka服务端接收的时候出了问题,这个消息发送失败了,生产者是不知道的。所以,kafka服务端应该要有一种响应客户端的方式,只有在服务端确认以后,生产者才发送下一轮的消息,否则重新发送数据。
服务端什么时候才算接收成功呢?因为消息是存储在不同的partition里面的,所以是写入到partition之后响应生产者。当然,单个partition (leader)写入成功,还是不够可靠,如果有多个副本,follower 也要写入成功才可以。
为了安全性考虑,Kafka会等待所有的follower全部完成同步,才发送ACK给客户端,延迟相对来说高一些,但是节点挂掉的影响相对来说小一些,因为所有的节点数据都是完整的。
ISR机制(in-sync replica set)
然而以上方案仍然存在问题:假设leader收到数据,所有follower都开始同步数据,但是有一个follower出了问题,没有办法从leader同步数据。按照这个规则,leader就要一致等待,无法发送 ack。
从概率的角度来讲,这种问题肯定是会出现的,就是某个follower出问题了,怎么解决呢?所以我们的规则就不能那么粗暴了,把规则改一下,不是所有的follower都有权利 让我等待,而是只有那些正常工作的follower同步数据的时候我才会等待。
我们应该把那些正常和leader保持同步的replica维护起来,放到一个动态set里面,这个就叫做in-sync replica set (ISR)。现在只要ISR里面的follower同步完数据之后,我就给客户端发送ACK。如果一个follower长时间不同步数据,就要从ISR剔除。同样,如果后面这个follower重新与leader保持同步,就会重新加入ISR。时间间隔由以下参数决定:
replica.lag.time.max.ms=10000
ACK应答机制
当然,如果所有的数据都一视同仁,而且这种策略只能由服务端决定,这就不是很灵活了。有一些数据丢了无所谓,我只想要快,不管它落没落盘同没同步,怎么办呢?
Kafka为客户端提供了三种可靠性级别,用户根据对可靠性和延迟的要求进行权衡,选择相应的配置。
- acks=0: producer不等待broker的ack,这一操作提供了一个最低的延迟,broker一接收到还没有写入磁盘就已经返回,当broker故障时有可能丢失数据;
- acks=1(默认): producer 等待 broker 的 ack, partition 的 leader 落盘成功后 返回ack,如果在follower同步成功之前leader故障,那么将会丢失数据;
- acks=-1 (all) : producer等待 broker 的 ack, partition 的 Ieader和 follower全部落盘成功后才返回ack。
三种机制,性能依次递减(producer吞吐量降低),数据健壮性则依次递增。我们可 以根据业务场景使用不同的参数。
Broker原理
文件存储结构
Partition分区
- 为了实现横向扩展,把不同的数据存放在不同的Broker上,同时降低单台服务器的访问压力,我们把一个Topic中的数据分隔成多个Partition;
- 每个Partition中的消息是有序的,顺序写入,但是全局不一定有序;
- 在服务器上,每个Partition都有一个物理目录(TopicN)后面的数字代表分区
Replica副本
- 为了提高分区的可靠性,Kafka设计了副本机制
- 副本数必须小于等于节点数,而不能大于Broker的数量
- Leader对外提供读写服务,Follower唯一的任务就是从Leader异步拉取数据
谁是Leader
Broker中的副本分布
实际副本分布规则是由AdminUtils.scala——assignReplicasToBrokers决定的,具体规则如下:
- 副本因子不能大于Broker的个数;
- 第一个分区(编号为0)的第一个副本放置位置是随机从 brokerList 选择的;
- 其他分区的第一个副本放置位置相对于第0个分区依次往后移 (nextReplicaShift)。
这样设计可以提高容灾能力。在每个分区的第一个副本错开之后,一般第一个分区的第一个副本(按Broker编号排序)都是leader。leader是错开的,不至于一挂影响太大。
Segment
为了防止Log不断追加导致文件过大,导致检索消息效率变低,一个Partition又被划分成多个Segment来组织数据
log.segment.bytes:根据日志文件大小 默认1GB
log.roll.hours、log.roll.ms:根据时间戳差值
log.index.size.max.bytes:根据索引文件大小
每个Segment由一个log文件和2个index文件组成
索引
在Kafka中设计了两种索引:偏移量索引文件记录的是。offset和消息物理地址(在log文件中的位置)的映射关系。时间戳索引文件记录的是时间戳和offset的关系
Kafka索引文件中记录的Offset不是连续的,而是采用了稀疏索引。至于这个索引有多稀疏,由以下参数决定:
log.index.interval.bytes=4096
只要写入的消息超过了 4KB,偏移量索引文件index和时间戳索引文件.timeindex就会増加一条索引记录(索引项)。这个值设置越小,索引越密集。值设置越大,索引越稀疏。相对来说,越稠密的索引检索数据更快,但是会消耗更多的存储空间。越的稀疏索引占用存储空间小,但是插入和删除时所需的维护开销也小。Kafka索引的时间复杂度为O(log2n)+O(m),n是索引文件里索引的个数,m为稀疏程度。
第二种索引类型是时间戳索引。时间戳有两种,一种是消息创建的时间戳,一种是消费在Broker追加写入的时间。到底用哪个时间由以下参数控制:
log.message.timestamp.type=CreateTime
默认是创建时间。如果要改成日志追加时间,则修改为LogAppendTime。査看最早的10条时间戳索引:
./kafka-dump-log.sh —files /tmp/kafka-logs/mytopic-0/00000000000000000000.timeindex|head -n 10
那么Kafka如何基于索引快速检索消息?比如我要检索偏移量是10002673的消息。
- 消费的时候是能够确定分区的,所以第一步是找到在哪个segment中。Segment 文件是用base offset命名的,所以可以用二分法很快确定(找到名字不大于10002673 的 最大segment)。
- 这个segment有对应的索引文件,它们是成套出现的。所以现在要在索引文件中根据offset找position。
- 得到position之后,到对应的log文件开始査找offset,和消息的offset进行比较,直到找到消息。
消息保留[清理]机制
开关与策略
由于Kafka的日志并没有在消费后马上删除,随着时间的推移,日志越来越多,需要有相应的清理策略来保证系统的健康,日志清理的开关由以下参数控制:
log.cleaner.enable=true
Kafka提供了两种清理方式,分别是删除delete和压缩compact,由以下参数控制:
log.cleanup.policy=delete
删除策略
Kafka可以通过定时任务实现日志数据的删除,默认5分钟执行一次
log.retention.check.interval.ms=300000
删除数据的配置项
log.retention.hours(默认值是168个小时,时间戳超过的数据会被删除)
log.retention.minutes(默认值是空,优先级比小时高)
log.retention.ms(默认值是空,优先级比分钟高)
若产生消息的速度不均匀,有时多、有时少,就可以根据日志大小删除
log.retention.bytes(表示所有日志文件的总大小,默认值是-1,代表不限制大小)
log.segment.bytes(对单个Segment文件大小进行限制,默认值1G)
压缩策略
若设置为compact,则表示不清除日志,只对日志数据进行压缩处理
Broker高可用架构
Controller选举
Controller(控制器)其实就是一个Broker,由它负责选举出新的Leader
分区副本Leader选举
Controller确定以后,开始进行leader选举。副本有3个概念:
- Assigned-Replicas (AR):一个分区的所有副本
- In-Sync Replicas (ISR):一个分区中跟leader数据保持一定程度的同步的副本
- Out-Sync-Replicas (OSR):跟leader副本同步滞后过多的副本
只有ISR有资格参加leader选举。而且这个ISR不是固定不变的,它是一个动态的列表。
如果同步延迟超过30秒,就踢出ISR,进入OSR。如果赶上来了, 就加入ISR。默认情况下,当leader副本发生故障时,只有在ISR集合中的副本才有资格被选举为新的leader。极端情况下,如果ISR为空,则可以允许ISR之外的副本参与选举,由以下参数控制:
unclean.leader.election.enable=true
Kafka采用了类似于继位传嫡的选举协议,选择ISR中位置靠前的节点成为新Leader
主从同步
Leader确定之后,客户端的读写只能操作leader节点。follower需要向leader同步数据。不同的raplica的offset是不一样的,同步到底怎么同步呢?
首先需要认识几个概念:
LEO (Log End Offset):下一条等待写入的消息的offset (最新的offset + 1),图中分别是9, 8, 6。
HW (Hign Watermark): ISR中最小的LEO。Leader会管理所有ISR中最小的 LEO作为HW,目前是6。
consumer最多只能消费到HW之前的位置(消费到offset 5的消息)。也就是说: 其他的副本没有同步过去的消息,是不能被消费的。
从节点和主节点的同步过程
1、首先,Follower节点向Leader发送一个fetch请求
2、然后,Leader向Follower发送数据
3、接着,Follower接收到数据响应后,依次写入消息、并更新LEO值
4、最后,Leader更新HW(ISR最小的LEO)
5、循环上述过程,直至所有Follower完成数据同步
Kafka设计的ISR复制,既可以在保障数据一致性、又可以提供高吞吐量。
Replica故障处理
follower故障
首先follower发生故障,会被先踢出ISR。follower恢复之后,从哪里开始同步数据呢?假设第1个replica宕机(中间这个)。
恢复以后,首先根据之前记录的HW (6),把高于HW的消息截掉(6、7)。然 后向leader同步消息。追上leader之后(30秒),重新加入ISR。
leader故障
假设图中leader发生故障。首先选一个leader。因为replica 1 (中间这个)优先,它成为leader。为了保证数据一致,其他的follower需要把高于HW的消息截取掉(这里没有消息需要截取)。然后replica2同步数据。注意:这种机制只能保证副本之间的数据一致性,并不能保证数据不丢失或者不重复。
Consumer原理
flush.message 有多少条消息就刷盘
flush.ms 间隔多少毫秒就刷盘
Offset的维护
Offset的存储
我们知道在partition中,消息是不会删除的,所以才可以追加写入,写入的消息连续有序的。这种特性决定了 kafka可以消费历史消息,而且按照消息的顺序消费指定消息,而不是只能消费队头的消息。正常情况下,我们希望消费没有被消费过的数据,而且是从最先发送(序号小的) 的开始消费(这样才是有序和公平的)。
那么对于一个partition,消费者组怎么才能做到接着上次消费的位置(offset)继续消费呢?肯定要把这个对应关系保存起来,下次消费的时候查找一下。
那么这个对应关系到底是保存在哪里的呢?
首先肯定是不可能放在消费者本地的。因为所有的消费者都可以使用这个consumer group id,放在本地是做不到统一维护的,肯定要放到服务端。
Kafka早期的版本把消费者组和partition的offset直接维护在ZK中,但是读写的性能消耗太大了。后来就放在一个特殊的topic中,名字叫—consumer_offsets,默认有 50 个分区(offsets.topic.num.partitions 默认是 50),每个分区默认一个replication。
那么这样一个特殊的Topic怎么存储消费者组gp-assign-group-1对于分区的偏移量的?Topic里面是可以存放对象类型的value的(经过序列化和反序列化)。这个Topic 里面主要存储两种对象:
GroupMetadata:保存了消费者组中各个消费者的信息(每个消费者有编号)。
OffsetAndMetadata:保存了消费者组和各个partition的offset位移信息元数据。
./kafka-console-consumer.sh —topic consumer_offsets --bootstrap-server 192.168.44.161:9093,192.168.44.161:9094,192.168.44.161:9095 -formatter "kafka.coordinator.group.GroupMetadataManager\$OffsetsMessageFormatter" —from-beginning
怎么知道一个consumer group的offset会放在这个特殊的Topic的哪个分区呢? 可以通过哈希取模计算得到:
Math.abs("gp-assign-group-1".hashCode()) % 50;
如果找不到Offset
如果增加了一个新的消费者组去消费—个topic的某个partion,没有offset的记录,这个时候应该从哪里开始消费呢? 由以下参数控制:
auto.offset.reset=latest
latest: 从最新的消息(最后发送的)开始消费。
earliest: 从最早的(最先发送的)消息开始消费。可以消费到历史消息。
none: 如果consumer group在服务端找不到offset会报错。
Offset的更新
消费者组的Offset储存在Broker上,由消费者上报给Broker。并不是消费者组消费了消息,Offset就会更新,消费者必须要有一个commit (提交)的动作。消费者可以自动提交或者手动提交。由以下参数控制:
enable.auto.commit=true 自动提交
auto.commit.interval.ms=5000 (默认值5秒钟)自动提交的频率
enable.auto.commit=false 手动提交
consumer.commitSync() 手动同步提交
consumer.commitAsync() 手动异步提交
Offset若不提交或提交失败,Broker的Offset就不会更新,消息会被重复消费
消费者消费策略
消费策略
- 使用消费者组,提升消费效率和吞吐量
- 同一个Group中的消费者,不能消费相同的Partition
- RangeAssignor:范围策略(按范围连续分配)
- RoundRobinAssignor:轮询策略
- StickyAssignor:粘滞策略
粘滞的分配策略较为复杂,它的核心思想是在分区重新分配时保证最小的移动(类似Redis的一致性hash思想,实现方式不同)。
第一次分配类似轮询,结果如下:
C1:P0 P3 P6
C2:P1 P4 P7
C3:P2 P5
假设此时C2挂掉:
若按照RoundRobin,结果如下:
C1:P0 P2 P4 P6
C3:P1 P3 P5 P7
sticky结果如下(尽量保证P0 P3 P6 P2 P5不动):
C1:P0 P3 P6 P1
C3:P2 P5 P4 P7
我们知道在分区分配时是会造成性能损耗的,若采用sticky分配策略可以尽可能减少性能损耗。该思想与Redis的一致性Hash是类似的,只是实现方式不同。
Rebalance 分区再均衡
1. 什么是ReBalance?
分区再均衡本质上是一种协议(管理Consumer与Partition的匹配关系)
2. 何时发生ReBalance?
消费者组的消费者数量发生变化
Topic的分区数发生变更
3. ReBalance 分区再均衡
- 确定协调者coordinator,通常由集群中负载最小的Broker承担。
- 所有consumer向coordinator发送join group请求,确认自己是该组成员。
- coordinator在所有consumer中确定leader(通常是第一个)并由leader确定分区分配结果。
- coordinator向所有consumer发送分区分配结果
Kafka 为什么这么快
磁盘顺序I/O
Kafka的message是不断追加到本地磁盘文件末尾的,而不是随机的写入,这使得 Kafka写入吞吐量得到了显著提升。
索引机制
我们在写入日志的时候会建立关于Offset和时间的稀疏索引,提升了查找效率,这个上面已经提到过了。
批量操作和压紧
Kafka无论是生产者发送消息还是消费者消费消息都是批量操作的,大大提高读写性能。
零拷贝
RocketMQ
RocketMQ基本介绍
特点
- 单队列中可靠的FIFO 和 严格的顺序传递
- 支持:推(push)、拉(pull)两种消息消费模式Ø 单一队列百万消息的堆积能力
- 支持多种消息协议
- 分布式高可用的部署架构
- 自带Dashboard
优势
- 支持事务型消息(分布式)
- 支持18个级别的延迟消息
- 支持指定次数和时间间隔的失败消息重发
- 支持Tag过滤,减少不必要的网络传输
- 支持重复消费
RocketMQ环境安装
RocketMQ单机安装
RocketMQ集群安装
RocketMQ系统架构
主要构成
Producer
- 消息生产者,负责生产消息
- 定时从Name Server拉取消息,并且和Broker建立TCP的长连接
- Producer只能往Master节点中写入数据
- 发送消息时,通过MQ的负载均衡模块自动选择相应的Broker集群队列进行消息投递
- RocketMQ中的消息生产者都是以Producer Group的形式出现
- RocketMQ提供了多种发送方式: 同步发送, 异步发送,单向发送
Consumer
- 消息消费者,负责消费消息
- Consumer既可以从Master订阅消息,也可以从Slave订阅消息
- RocketMQ中的消息消费者都是以Consumer Group的形式出现
关于消费者组,需要注意:
1、消费者组中Consumer的数量应该小于等于订阅Topic的Queue数量
2、一个组中的消费者必须订阅完全相同的Topic
Broker
- 角色:消息中转,负责:存储消息、转发消息
- RocketMQ的Broker同时还存储了消息相关的元数据
- Broker有Master和Slave两种类型: Master可读可写; Slave可读不可写
- 生产者生成消息后,存入Broker的队列中,消费者来消费
- 消息并发量增加后,单个queue可能出现性能瓶颈,可以在Broker中使用多队列
- 可以使用消息日志,提供消息的持久化存储
- 为了提升Broker的可用性,还支持Master-Slave主从复制
Name Server
- RocketMQ的服务注册中心(指挥中心)
- Name Server是无状态节点,可集群部署
- Name Server是一个Broker与Topic路由的注册中心,支持Broker的动态注册与发现
重要概念
Message - 消息
- 消息系统所传输信息的物理载体,生产和消费数据的最小单位
- 消息不可变性、消息落盘
- 消息类型:Normal普通消息、FIFO顺序消息、Delay延时消息、Transaction事务消息
Topic - 主题
- 消息的逻辑组织形式,真正存储消息的是队列
- 一个Topic只收发一种类型的消息,用以实现业务数据的隔离
- 生产环境下要规避主题的自动化管理
Tag - 标签
- 消息的标签,用于区分同一主题下、不同类型的消息
- Topic:消息一级分类(业务分类)、Tag:消息二级分类(子主题)
Queue - 队列
- RocketMQ中消息存储和传输的实际容器,也是消息的最小存储单元
- 单个队列内的消息天然存在顺序关系,队列是负载均衡过程中资源分配的基本单元
Sharding - 分片
- 一个Topic在每个Broker上面的Queue的集合,目的在于水平扩展
RocketMQ工作流程
1)启动NameServer,NameServer启动后开始监听端口,等待Broker、Producer、Consumer连接。
2)启动Broker时,Broker会与所有的NameServer建立并保持长连接,然后每30秒向NameServer定时发送心跳包。
3)发送消息前,需要先创建Topic,创建Topic时需要指定该Topic要存储在哪些Broker上以及在这些broker上创建几个Queue,在创建Topic时也会将Topic与Broker的关系写入到NameServer中。
)Producer发送消息,启动时与NameServer集群中的其中一台建立长链接,并从NameServer中获取路由信息,即当前发送的Topic消息的Queue与Broker的地址(IP+Port)的映射关系。然后根据算法策略选择一个Queue,与队列所在的broker建立长链接从而向broker发送消息。在获取到路由信息后,Producer会首先将路由信息缓存到本地,再每隔30s从NameServer更新一个路由信息。
5)Consumer跟Producer类似,跟其中一台NameServer建立长连接,获取其所订阅Topic的路由信息,然后根据算法策略从路由信息中获取到其所要消费的Queue,然后直接跟Broker建立长连接,开始消费其中的消息。Consumer在获取到路由信息后,同样也会每30秒从NameServer更新一次路由信息。不过不同于Producer的是,Consumer还会向Broker发送心跳,以确保Broker的存活状态。
RocketMQ设计理念
- Name Server 的极简设计
- 高效的 IO 存储机制
- 暴露设计缺陷(消费端需业务去重)
分布式事务管理
概念与思路
什么叫分布式事务
在本地事务的基础上,保证在分布式环境下数据的一致性
产生的原因?--- 分库分表;服务拆分
分布式事务的使用场景? ---支付;在线下单;跨行转账
RocketMQ事务消息思路
将本地事务和发消息放置在一个分布式事务中,保证两者要么都成功,要么都失败
RocketMQ事务消息主线流程:
1、发送一个半事务消息
2、执行本地事务
3、提交事务执行状态
事务消息实现过程
原理分析
TransactionListenner 接口
发送半消息流程[Producer端]
发送半消息流程[Broker端]
事务消息回查流程[Broker端]