1:kafka 与rocketMQ的零拷贝
kafka 在使用时更偏向sendFile方式, sendfile只能用来搬运数据,不能对数据进行操作。整个过程在内核态完成,不经过用户态。
rocketMq使用时更偏向Mmap方式。通过内存映射的方式,用户态通过虚拟地址,可以直接读取修改内核态数据。
kafka 与rocketMQ , 在brocke 发送到消费者时,使用sendFile 进行发送数据,发送时消息在内核态就完成了数据的发送。
kafka 的 Mmap方式,操作index索引文件,timeIndex 时间索引文件进行写入。这些操作是写入到pagecache后同步步完成的。 index索引文件是稀疏索引,不会每条消息都添加索引。timeIndex也是。
写入真正消息log文件时直接通过,调用操作系统write函数以顺序写方式写入log文件,没有使用 零拷贝技术,因为kafka是将一批消息数据,直接写入log文件,直接通过消息的量级,分摊了单条消息写入性能。
rocketMQ 也支持一批数据直接发送到brocker,但是需要对每条消息进行拆解,对单条消息做操作。
rocketMQ 是通过Mmap写入 commitLog 文件的, rocketMq 写入消息需要对单条数据进行操作,比如计算消息的tag,索引,偏移量,更新索引文件,延时信息,所有每条消息在写入时都需要进行解析。
所以rocketMQ 批量发送无法向kafka那样单次将一批数据刷入pagecache。他使用了Mmap方式
将内核态数据直接映射到用户态,通过映射直接操作内核态数据,省去数据在用户态写入后再复制到内核态的复制开销。
无法支持将消息批量刷入pagecache,使用Mmap方式降低消息写入的消耗。
2:批量发送
kafka的生产者会自动将消息惨成批次后发送到brocker,brocker会直接通过调用操作系统的write 直接将数据写入到pagecache。
rocketMq生产者需要手动组成list后发送到brocker,brocker 会对每条消息进行拆解,计算每条消息的偏移量,tag的hash等,然后同步索引文件。
3:文件格式
kafka 每个Partition 存在一个log文件存储消息,大小过1g后自动切换下一个。每个parttion 会有一个文件夹存储,里面存放多个segment文件夹,每个segment由.log,index,timeIndex文件组成。 当顺序写入的消息达到默认4kb 时,同步 更新index,与timeIndex文件信息,增加索引项(稀疏索引 )。
查找:先通过索引定位到大概位置,在扫描一小段数据。
rocketMq 整体使用一个commitlog文件存储所有messageQueue消息。大小过1g后自动切换下一个。会为每一条消息建立索引(稠密索引 )。每个队列都有不同的ConsumeQueue 索引文件(数据和索引分离),IndexFile 索引文件是全局的。
存储结构:store目录下,存在commitlog,consumequeue,indexFIle文件夹,
commitlog # 目录1:存放所有消息实体,以偏移量命名
consumerlog # 目录2:存放队列索引(每个Topic/Queue独立)每个topic文件夹下存放多个队列,队列下根据单文件默认存 30 万个条目(约 5.72MB),写满 30 万条索引 后滚动生成新文件。每个条目是定长的 20 字节。
index # 目录3:存放全局哈希索引(IndexFile)------ 按创建时间戳命名的文件, 默认开启但可配置。文件达到 约 400MB 时创建新文件。
索引建立:异步线程每1ms检查commitLog文件新增数据,存在新增数据,重放后解析创建对应的索引,写入consumerQueue,与indexFile索引文件。
bash
$HOME/store/
├── commitlog/ # 目录1:存放所有消息实体(CommitLog)
│ ├── 00000000000000000000
│ └── 00000000001073741824
│
├── consumequeue/ # 目录2:存放队列索引(每个Topic/Queue独立)
│ ├── TopicA/
│ │ ├── 0/
│ │ │ └── 00000000000000000000
│ │ └── 1/
│ │ └── 00000000000000000000
│ └── TopicB/
│ └── 0/
│ └── 00000000000000000000
│
└── index/ # 目录3:存放全局哈希索引(IndexFile)
├── 20240521123056123 # 按创建时间戳命名的文件
└── 20240521144562123 # 上一个写满后滚动生成的新文件
如何对 messageQueue或partition扩容
1:停机扩容
停止所有生产者服务,队列中所有消息被消费者消费完后,进行扩容操作,新增队列,启动生产者,新消息按照新队列数路由
缺点:不适合需要持续服务的生产环境。
2:成倍扩容,
队列数成倍增加,如8个到16个,保证部分旧key路由到相同队列。
缺点:只能减少乱序概率,无法全部避免
3:应用层双写,灰度迁移
(顺序消息)创建新topic,修改生产者代码,将新消息根据特定逻辑进行分流 / 选择性写入新旧 两个topic。消息100%迁移到新topic后,保证生产者不在向旧topic发送消息。消费者处理完旧topic消息后不要立即删除,可以将旧topic磁盘数据备份,或者停止消费者保留旧topic数据。
回滚预案:在确定新topic稳定运行前一定不能直接删除旧topic, 如果brocker出现异常,大量消息发送失败时,直接切回旧topic。及时止损。
特定逻辑进行分流:
如 工控机台,批次信息,新批次进入新topic,旧批次依然进入旧topic。或者分机台进入不同topic。
如 订单号分流,根据订单号的范围 ,或者用户类型,普通用户的订单,vip用户的订单,测试用户的订单。或者根据订单产生的城市位置。先切
测试订单,在百分之10,50,100 的用用户订单。
如果不需要保证顺序,直接进行Partition,messageQueue增加,但是要注意消费端重分配问题。