Introduction
Apache Kafka
是一个开源的分布式事件流平台, 用于高性能数据管道、流分析、数据集成
事件流是从事件源(如数据库、传感器、移动设备、云服务和软件应用程序)以事件流的形式实时捕获数据的实践;持久存储这些事件流以供以后检索;实时和回顾性地操作、处理和响应事件流;并根据需要将事件流路由到不同的目标技术
- publish/subscribe(write/read)事件流
- 存储事件流
- 实时或追溯处理事件流
Design
目标
与传统的MQ相比,更像数据库日志
- 支持高吞吐
- 支持处理大量数据积压
- 支持低延迟
- 机器故障时支持容错
持久化
文件系统
Kafka非常依赖文件系统进行存储和缓存
使用内存缓存的坏处:
- OS会将内存转移到磁盘缓存
- Java的对象内存开销大
- 堆内存使用越多GC越慢
- 服务重启时重建缓存慢
进行的优化:
- 自动访问空闲内存
- 保存byte[]而不是Java对象
- NO 在内存中维护尽可能多的内容,并在空间不足时将其全部刷新到文件系统
- YES 所有数据都会立即写入文件系统上的持久日志, 不必刷新到磁盘(实际上只是被转移到内核的页面缓存中)
常数时间
Queue建立在对文件的read和append上(在日志系统中很常见), 而BTree等数据结构如果触发磁盘寻道会开销很大
优点:
- 操作的时间复杂度O(1)
- 读写不会阻塞
- 性能与数据量无关
- 支持消息保存较长时间
效率
避免效率低的磁盘访问:
- 过多的小型IO操作
- 过多的byte复制
如何避免:
- 将数据抽象为
message set
, 网络请求将消息组合在一起, 服务器将消息一次性append, 消费者一次性获取大数据块 - 使用标准化二进制格式, 这样数据传输时不用修改, 就可以利用OS提供的
zero-copy
, 在Linux中为sendfile
系统调用. 使用pagecache
+sendfile
使磁盘几乎不会有高负载
端到端批量压缩
大部分信息冗余来源于同一种类型的不同消息, Kafka支持一批消息批量压缩
Producer
负载均衡
客户端可以控制消息的目标partition, 支持设置使用指定字段作为分区根据, 这样设计使consumer可以做局部敏感数据处理
异步发送
producer支持异步, 在内存中保留一定消息然后批处理发送以提高效率, 可以配置缓存的大小和有效时间
Consumer
consumer在每个请求中指定想要访问的偏移量
PUSH vs PULL
Kafka使用支持阻塞的PULL请求
PULL的优点
- consumer不会收到超出可处理上限的消息, 不同的consumer可以按照自己的处理能力获取消息
- PUSH需要针对可能的阻塞设置推送延迟, PULL不需要设置
简单的PULL的缺点
- 没有消息的时候consumer浪费CPU资源进行空轮询
Consumer记录位置
MQ需要确认消息是否被成功消费, 一些MQ引入一个中间状态”已发送未消费”
Kafka的topic的每个分区记录一个consumer位置, 不需要维护每个消息的确认状态
消息传递语义
可以在所有情况下提供多种保证()
- 最多一次, 消息可能会丢失, 不会重复
- 最少一次, 消息可能会重复, 不会丢失
- 精确一次
Kafka默认保证最少一次, 事务性producer/consumer可以实现精确一次
producer
Kafka支持idempotent delivery
幂等生产, 重新发送消息不会导致broker中出现重复消息
consumer
Kafka需要支持一个consumer崩溃后切换到其他consumer的场景
- 最多一次, consumer读消息->保存位置->处理消息
- 至少一次, consumer读消息->处理消息->保存位置
- 精确一次, 使用事务更新, broker同时更新consumer的位置和处理结果
Replication
replication的单位是partition, 每个partition都有1个主节点0或多个从节点, 主写从读
replication的心跳使用ZooKeeper维护
分布式一致性
Implementation
网络层
NIO服务器
消息格式
key/value不透明