一. Kafka入门
01. 消息引擎系统
国外名字Messaging System,国内翻译成消息引擎。
消息引擎系统:消息引擎是一组规范,企业利用这组规范在不同系统之间传递语义准确的消息,实现松耦合的一步式消息传递。通俗的讲,就是系统A发送消息给消息引擎系统,系统B从消息引擎系统中读取A发送的消息。
消息引设定具体的传输协议,用什么方法把消息传出去:
- 点对点模型
- 发布/订阅模型
Kafka同时支持这两种消息引擎模型。
Kafka引入的优点:
- 削峰填谷:上游系统不再与下游系统进行交互,Kafka能够将瞬时增加的订单流量全部以消息形式保存在对应的主题中,既不影响上游服务的TPS:Transactions Per Second(每秒传输的事物处理个数),也能够给下游子服务留出充足的时间去消费他们。
- 松耦合:发送方和接收方的松耦合,在一定程度上简化了应用开发减少了系统间不必要的交互。
02. 一篇文章带你快速搞定Kafka术语
Record(消息):Kafka处理的主要对象
Topic(主题):主题是承载消息的逻辑容器,在实际使用中多用来区分具体的业务。
Partitioning(分区):一个有序不变的消息序列。每个主题下可以有多个分区。
Offset(消息位移):表示分区中每条消息的位置信息,是一个单调递增且不变的值。
Producer(生产者):向主题发布消息的应用程序。
Consumer(消费者):从主题订阅消息的应用程序。
Client(客户端):Producer(生产者)和Consumer(消费者)统称为Client(客户端)。
Broker(服务器端):Kafka的服务端由被称为Broker的服务进程构成,即一个Kafka集群由多个Broker组成,Broker负责接收和处理客户端发送过来的请求,以及对消息进行持久化。
Consumer Offset(消费者位移):表示消费者消费进度,每个消费者都有自己的消费者位移。
Consumer Group(消费者组):多个消费者实例共同组成的一个组,同时消费多个分区以实现高吞吐。
Rebalance(重平衡):消费者组内某个消费者实例挂掉后,其他消费者实例自动重新分配订阅主题分区的过程。
Replica(副本):
- Leader Replica(领导者副本):对外提供服务,对外服务指的是与客户端进行交互。
- Follower Replica(追随者副本):被动的追随领导者副本,不能与外界交互。
- 之前的Master-Slave即现在的Leader-Follower
Kafka三层消息架构
- 第一层是主题层,每个主题可以配置M个分区,而每个分区又可以配置N个副本。
- 第二层是分区层,每个分区的N个副本中只能有一个充当领导者角色,对外提供服务,其他N-1个副本是追随者副本,知识提供数据冗余之用。
- 第三层是消息层,分区中包含若干条消息,每条消息的位移从0开始,依次递增。
- 最后,客户端程序只能与分区的领导者副本进行交互。
03. 我应该选择哪种Kafka?
- Apache Kafka:社区版本
优势:开发人数最多,掌控高
劣势:仅提供最基础的组件,没有提供任何监控工具和框架
- Confluent Kafka:Kafka创始人离开Linkedin后创办公司
免费版:比社区版多了Schema注册中心,REST proxy,多种连接器
企业版:跨数据中心备份,集群监控
劣势:相关文档资料不全,没有太多可供参考的范例
- Cloudera/Hortonworks Kafka
优势:操作简单,节省运维成本
劣势:把控度低,演进速度慢
04. 聊聊Kafka的版本号
Kafka目前总共演进了7个大版本,分别是0.7,0.8,0.9,0.10,0.11,1.0,2.0
0.8:引入了副本机制
0.9:增加了基础的安全认证/权限功能,使用java重写了Producter API,在这个版本中算比较稳定了,不建议使用新版本的Consumer API。引入了Kafka Connet组件
0.10:引入了Kafka Streams,Consumer API算是稳定了,修复了Producter的bug
0.11:提供幂等性Producer API以及事务API;对Kafka消息格式做了重构
1.0 & 2.0:Kafka Streams的各种改进
二. Kafka基本使用
05. kafka线上集群部署方案怎么做?** **
磁盘阵列(RAID)优势:
- 提供冗余的磁盘存储空间
- 提供负载均衡**
**
因素 | 考量点 | 建议 |
---|---|---|
操作系统 | 操作系统I/O模型 | 将Kafka部署在Linux系统上 |
磁盘 | 磁盘I/O性能 | 普通环境使用机械磁盘,不需要搭建RAID |
磁盘容量 | 根据消息数、留存时间预估磁盘容量 | 实际使用中建议预留20%~30%的磁盘空间 |
带宽 | 根据实际带宽资源和业务SLA预估服务器数量 | 对于千兆网络,建议每台服务器按照700Mbps来计算,避免大流量下的丢包 |
06. 最重要的集群参数配置(上)
- 针对存储相关的参数(Broker端参数)
- log.dirs:指定的Broker需要使用的若干个文件的目录路径,无默认值
- log.dir:指定的Broker需要使用的单个文件的目录路径
例如 /home/kafka1,/home/kafka2,/home/kafka3
- 针对Kafka与ZooKeeper
- zookeeper.connect
例如 zk1:2181,zk2:2181,zk3:2181(2181是zookeeper默认的端口)
如果多个Kafka集群使用一个zookeeper集群,这样设置,zk1:2181,zk2:2181,zk3:2181/kafka1和zk1:2181,zk2:2181,zk3:2181/kafka2
- 与Broker连接相关的参数
- listerers(监听器)告诉外部连接者要通过什么协议访问指定主机名和端口开发的Kafka服务
- advertised.listeners,这组监听器是Broker用于对外发布的
- 关于Topic管理的参数
- auto.create.topics.enable:是否允许自动创建Topic
建议射程false,不允许随便创建topic
- unclean.leader.election.enable:是否允许unclean leader选举
kafka有多副本策略,设置成false,剑指不能让数据落后太多的副本竞选leader,这样做的后果是这个分区就不可用了,因为没有leader了。反之如果是true,那么kafka允许从那些“跑的慢”的副本中选一个出来当leader,这样做的后果是数据有可能丢失。建议设置成false。
- auto.leader.rebalance.enable:是否允许定期进行leader选举
建议设置成false。如果设置成true,有可能一段时间leader A被强制换成leader B,换一次leader代价很高的,而且这种换leader本质上没有任何性能收益。
- 关于数据留存的参数
- log.retention.{hours|minutes|ms}:控制一条消息数据被保存多长时间,优先级上来说ms设置最高、minutes辞职、hours最低。
- log.retention.bytes:指定Broker为消息保存可以占用的磁盘容量大小
这个默认值是-1,表示想在这台Broker保存多少数据都可以
- message.max.bytes:控制Broker能够接收的最大消息大小
默认值不到1MB,建议根据实际来,可以设置成稍微大一点。
07. 最总要的集群参数配置(下)
- Topic级别参数
说起 Topic 级别的参数,你可能会有这样的疑问:如果同时设置了 Topic 级别参数和全局 Broker 参数,到底听谁的呢?哪个说了算呢?答案就是 Topic 级别参数会覆盖全局 Broker 参数的值,而每个 Topic 都能设置自己的参数值,这就是所谓的 Topic 级别参数。
- retention.ms:规定了该topic消息被保存的时长,默认为7天。
- retention.bytes:规定了要为该topic预留多大的磁盘空间,默认值为-1,表示可以无限使用磁盘空间。
- max.message.bytes:它决定了Kafka Broker能够正常接收该Topic的最大消息大小
- JVM参数
Kafka 服务器端代码是用 Scala 语言编写的,但终归还是编译成 Class 文件在 JVM 上运行,因此 JVM 参数设置对于 Kafka 集群的重要性不言而喻。另外 Kafka 自 2.0.0 版本开始,已经正式摒弃对 Java 7 的支持了,所以有条件的话至少使用 Java 8 吧。
-
KAFKA_HEAP_OPTS:指定堆大小。JVM 堆大小设置成 6GB 吧,这是目前业界比较公认的一个合理值。
-
KAFKA_JVM_PERFORMANCE_OPTS:指定GC参数
-
- GC参数(java7)
如果 Broker 所在机器的 CPU 资源非常充裕,建议使用 CMS 收集器。启用方法是指定–
-XX:+UseCurrentMarkSweepGC。否则,使用吞吐量收集器。开启方法是指定-XX:+UseParallelGC。
-
- GC参数(java8)
用默认的 G1 收集器就好了。在没有任何调优的情况下,G1 表现得要比 CMS 出色,主要体现在更少的 Full GC,需要调整的参数更少等,所以使用 G1 就好了。
- 操作系统参数
- 文件描述符限制:通常情况下将它设置成一个超大的值是合理的做法,比如ulimit -n 1000000。通常情况下将它设置成一个超大的值是合理的做法,比如ulimit -n 1000000。
- 文件系统类型:这里所说的文件系统指的是如 ext3、ext4 或 XFS 这样的日志型文件系统。根据官网的测试报告,XFS 的性能要强于 ext4,所以生产环境最好还是使用 XFS。对了,最近有个 Kafka 使用 ZFS 的数据报告,貌似性能更加强劲。
- Swappiness:网上很多文章都提到设置其为 0,将 swap 完全禁掉以防止 Kafka 进程使用 swap 空间。我个人反倒觉得还是不要设置成 0 比较好,我们可以设置成一个较小的值。为什么呢?因为一旦设置成 0,当物理内存耗尽时,操作系统会触发 OOM killer 这个组件,它会随机挑选一个进程然后 kill 掉,即根本不给用户任何的预警。但如果设置成一个比较小的值,当开始使用 swap 空间时,你至少能够观测到 Broker 性能开始出现急剧下降,从而给你进一步调优和诊断问题的时间。基于这个考虑,我个人建议将 swappniess 配置成一个接近 0 但不为 0 的值,比如 1。
- 提交时间:或者说是Flush落盘时间。。向 Kafka 发送数据并不是真要等数据被写入磁盘才会认为成功,而是只要数据被写入到操作系统的页缓存(Page Cache)上就可以了,随后操作系统根据 LRU 算法会定期将页缓存上的“脏”数据落盘到物理磁盘上。这个定期就是由提交时间来确定的,
三. 客户端实践及原理剖析
08. 生产者消息分区机制原理剖析
在使用Apache Kafka生产和消费消息的时候,肯定希望能够将数据均匀地分配到所有服务器上,如果将大数据量均****匀地分配到Kafka的哥哥Broker上,成为一个非常重要的问题。
为什么分区?
其实分区的作用就是提供负载均衡的能力,或者说对数据进行分区的主要原因,就是为了实现系统的高伸缩性(Scalability)。
值得注意的是,不同的分布式系统对分区的叫法也不尽相同,比如在Kafka中叫分区,在MongoDB和Elasticsearch中就叫分片Shard,而在HBase中则叫Region,在Cassandra中又叫vnode。
- Kafka的消息组织方式实际上是三级结构:主题-分区-消息。主题下的每条消息知乎包存在某一个分区中,而不会在多个分区中保存多份。
- 分区是实现负载均衡及高吞吐量的关键
分区策略有哪些?
分区策略也就是决定生产者将消息发送到哪个分区的算法,分区策略常见分类:
- 轮询策略(默认分区策略)
也称Round-robin策略,即顺序分配。如果一个主题下有3个分区,那么第一条消息被发送到分区0,第二条被发送到分区1,第三条被发送到分区2,以此类推。当生产第4条消息又会重新开始,即将其分配到分区0 。
- 随机策略
也称Randomness策略。所谓随机策略就是我们随意地将消息放知道任意一个分区上。但从实际上来看,它要逊于轮询策略。
- 按消息键保存策略
也称Key-ordering策略。Kafka允许为每条消息定义消息键,简称为key。由于每个分区下的消息处理都是有顺序的,故这个策略被称为按消息键保存策略。
10. 无消息丢失配置怎么实现?
Kafka在什么情况下才能保证消息不丢失?
Kafka支队"已提交"的消息(committed message)做有限度的持久化保证。
- 已提交的消息:
当 Kafka 的若干个 Broker 成功地接收到一条消息并写入到日志文件后,它们会告诉生产者程序这条消息已成功提交。此时,这条消息在 Kafka 看来就正式变为“已提交”消息了。
这里的若干个可以设置为只要有一个Broker成功保存该消息就算是已提交,也可以是令所有Broker都车更难过保存该消息才算是已提交。
- 有限度的持久化保证:
Kafka不丢失消息是有前提条件的,假如你的消息包存在N个Kafka Broker上,前提条件就是这个N个Broker中至少有一个存活。
- "消息丢失"情况分类
a. 生产者程序丢失数据
目前 Kafka Producer 是异步发送消息的,也就是说如果你调用的是 producer.send(msg) 这个 API,那么它通常会立即返回,但此时你不能认为消息发送已成功完成。
这种发送方式有个有趣的名字,叫“fire and forget”,翻译一下就是“发射后不管”。如果用这个方式,可能有很多原因导致消息压根久没哟欧发送到Broker端,或者消息本身不合格导致Broker拒绝接收。
实际上,解决此问题的方法非常简单:Producer 永远要使用带有回调通知的发送 API,也就是说不要使用 producer.send(msg),而要使用 producer.send(msg, callback)。
b. 消费者程序丢失数据
消费消息与更新位移的顺序不对。
对抗这种消息丢失,办法很简单:维持先消费消息(阅读),再更新位移(书签)的顺序即可。这样就能最大限度地保证消息不丢失。当然这种方式可能带来的问题是消息的重复处理。
c. 消费端多线程消费消息出现数据丢失
Consumer 程序从 Kafka 获取到消息后开启了多个线程异步处理消息,而 Consumer 程序自动地向前更新位 移。假如其中某个线程运行失败了,它负责的消息没有被成功处理,但位移已经被更新了,因此这条消息对于 Consumer 而言实际上是丢失了。
这个问题的解决方案也很简单:如果是多线程异步处理消费消息,Consumer 程序不要开启自动提交位移,而是要应用程序手动提交位移。
-
最佳实践
-
- 不要使用producer.send(msg),而要使用producer. send(msg,callback)
- 设置acks=all,acks是producer的一个参数,代表了比对"已提交"消息的定义,如果设置成all,则表明所有副本broker都要接收到消息,该消息才算是"已提交",这是最高等级的"已提交"定义。
- 设置retries为一个较大的值,这里的retries同样是producer的参数,对应前面提到的producer的参数,对应前面提到的producer自动重试。当出现网络的瞬间抖动时,消息发送可能会失败,此时配置了retries大于0的producer能够自动重试消息发送,避免消息丢失。
- 设置 unclean.leader.election.enable = false。这是 Broker 端的参数,它控制的是哪些 Broker 有资格竞选分区的 Leader。如果一个 Broker 落后原先的 Leader 太多,那么它一旦成为新的 Leader,必然会造成消息的丢失。故一般都要将该参数设置成 false,即不允许这种情况的发生。
- 设置 replication.factor >= 3。这也是 Broker 端的参数。其实这里想表述的是,最好将消息多保存几份,毕竟目前防止消息丢失的主要机制就是冗余。
- 设置 min.insync.replicas > 1。这依然是 Broker 端参数,控制的是消息至少要被写入到多少个副本才算是“已提交”。设置成大于 1 可以提升消息持久性。在实际环境中千万不要使用默认值 1。
- 确保 replication.factor > min.insync.replicas。如果两者相等,那么只要有一个副本挂机,整个分区就无法正常工作了。我们不仅要改善消息的持久性,防止数据丢失,还要在不降低可用性的基础上完成。推荐设置成 replication.factor = min.insync.replicas + 1。
- 确保消息消费完成再提交。Consumer 端有个参数 enable.auto.commit,最好把它设置成 false,并采用手动提交位移的方式。就像前面说的,这对于单 Consumer 多线程处理的场景而言是至关重要的。