kafka 是一种消息引擎,了这一步,你可以熟读一遍Kafka官网文档,确保你理解了那些可能影响可靠性和性能的参数。
如何根据实际业务需求评估、搭建生产线上环境将是你主要的学习目标。另外对生产环境的监控也是重中之重的工作,Kafka提供了超多的JMX监控指标,你可以选择任意你熟知的框架进行监控。有了监控数据,作为系统
运维管理员的你,势必要观测真实业务负载下的Kafka集群表现。之后如何利用已有的监控指标来找出系统瓶颈,然后提升整个系统的吞吐量,这也是最能体现你工作价值的地方
消息引擎系统是一种规范。企业利用这组规范在不同的系统之间传递语义准确的消息,实现松耦合的异步式数据传递。
纯二进制的字节序列
Raft算法:共识算法
Leader Follower
kafka的副本是和分区绑定一起的
能否把数据分割成多份保存在不同的Broker上?
如果你就是这么想的,那么恭喜你,Kafka就是这么设计的。
这种机制就是所谓的分区(Partitioning)
Kafka中的分区机制指的是将每个主题划分成多个分区(Partition),每个分区是一组有序的消息日志。生产者生产的每条消息只会被发送到一个分区中,也就是说如果向一个双分区的主题发送一条消息,这条消息要么在分区0中,要么在分区1中。如你所见,Kafka的分区编号是从0开始的,如果Topic有100个分区,那
么它们的分区号就是从0到99。
讲到这里,你可能有这样的疑问:刚才提到的副本如何与这里的分区联系在一起呢?
实际上,副本是在分区这个层级定义的。
消息:Record。Kafka是消息引擎嘛,这里的消息就是指Kafka处理的主要对象
主题:Topic。主题是承载消息的逻辑容器,在实际使用中多用来区分具体的业务。
分区:Partition。一个有序不变的消息序列。每个主题下可以有多个分区。消息位移:Offset。表示分区中每条消息的位置信息,是一个单调递增且不变的值。
副本:Replica。Kafka中同一条消息能够被拷贝到多个地方以提供数据冗余,这些地方就是所谓的副本。副本还分为领导者副本和追随者副本,各自有不同的角色划分。副本是在分区层级下的,即每个分区可配置多个副本实现高可用。
生产者:Producer。向主题发布新消息的应用程序。
消费者:Consumer。从主题订阅新消息的应用程序。
消费者位移:Consumer Offset。表征消费者消费进度,每个消费者都有自己的消费者位移。
消费者组:Consumer Group。多个消费者实例共同组成的一个组,同时消费多个分区以实现高吞吐。
重平衡:Rebalance。消费者组内某个消费者实例挂掉后,其他消费者实例自动重新分配订阅主题分区的过程。Rebalance是Kafka消费者端实现高可用的重要手段。
请思考一下为什么Kafka不像MySQL那样允许追随者副本对外提供读服务?
不从follower读几个原因:
1,kafka的分区已经让读是从多个broker读从而负载均衡,不是MySQL的主从,压力都在主上;
2,kafka保存的数据和数据库的性质有实质的区别就是数据具有消费的概念,是流数据,kafka是消息队列,所以消费需要位移,而数据库是实体数据不存在这个概念,如果从kafka的follower读,消费端offset控制更复杂;
3,生产者来说,kafka可以通过配置来控制是否等待follower对消息确认的,如果从上面读,也需要所有的follower都确认了才可以回复生产者,造成性能下降,如果follower出问题了也不好处理 [4赞]
Kafka是消息引擎系统,也是分布式流处理平台
Kafka线上集群部署方案怎么做?
I/O模型的使用
在Linux上的实现机制是epoll
数据网络传输效率
在Linux部署Kafka能够享受到零拷贝技术所带来的快速数据传输特性。
社区支持度
追求性价比的公司可以不搭建RAID,使用普通磁盘组成存储空间即可。
使用机械磁盘完全能够胜任Kafka线上环境
我们来计算一下:每天1亿条1KB大小的消息,保存两份且留存两周的时间,那么总的空间大小就等于1亿 *
1KB * 2 / 1000 / 1000 = 200GB。一般情况下Kafka集群除了消息数据还有其他类型的数据,比如索引数据
等,故我们再为这些数据预留出10%的磁盘空间,因此总的存储容量就是220GB。既然要保存两周,那么整
体容量即为220GB * 14,大约3TB左右。Kafka支持数据的压缩,假设压缩比是0.75,那么最后你需要规划
的存储空间就是0.75 * 3 = 2.25TB。
总之在规划磁盘容量时你需要考虑下面这几个元素
新增消息数
消息留存时间
平均消息大小
备份数
是否启用压缩
一些关键参数
auto.create.topics.enable=false
unclean.leader.election.enable=false
auto.leader.rebalance.enable=false
这log.retention.{hour|minutes|ms}:这是个“三兄弟”,都是控制一条消息数据被保存多长时间。从优先级上来说ms设置最高、minutes次之、hour最低。
log.retention.bytes:这是指定Broker为消息保存的总磁盘容量大小。
message.max.bytes:控制Broker能够接收的最大消息大小
最后一组参数是数据留存方面的,即:
先说这个“三兄弟”,虽然ms设置有最高的优先级,但是通常情况下我们还是设置hour级别的多一些,比如log.retention.hour=168表示默认保存7天的数据,自动删除7天前的数据。很多公司把Kafka当做存储来使用,那么这个值就要相应地调大。
其次是这个log.retention.bytes。这个值默认是-1,表明你想在这台Broker上保存多少数据都可以,至少在容量方面Broker绝对为你开绿灯,不会做任何阻拦。这个参数真正发挥作用的场景其实是在云上构建多租户的Kafka集群:设想你要做一个云上的Kafka服务,每个租户只能使用100GB的磁盘空间,为了避免有个“恶意”租户使用过多的磁盘空间,设置这个参数就显得至关重要了。
message.max.bytes
最后说说message.max.bytes。实际上今天我和你说的重要参数都是指那些不能使用默认值的参数,个参数也是一样,默认的1000012太少了,还不到1KB。实际场景中突破1MB的消息都是屡见不鲜的,因此在线上环境中设置一个比较大的值还是比较保险的做法。毕竟它只是一个标尺而已,仅仅衡量Broker能够处理的最大消息大小,即使设置大一点也不会耗费什么磁盘空间的
Topic级别参数
说起Topic级别的参数,你可能会有这样的疑问:如果同时设置了Topic级别参数和全局Broker参数,到底听谁的呢?哪个说了算呢?答案就是Topic级别参数会覆盖全局Broker参数的值,而每个Topic都能设置自己的参数值,这就是所谓的Topic级别参数。
retention.ms:规定了该Topic消息被保存的时长。默认是7天,即该Topic只保存最近7天的消息。一旦设置了这个值,它会覆盖掉Broker端的全局参数值。
retention.bytes:规定了要为该Topic预留多大的磁盘空间。和全局参数作用相似,这个值通常在多租户的Kafka集群中会有用武之地。当前默认值是-1,表示可以无限使用磁盘空间。
将你的JVM堆大小设置成6GB吧
我想无脑给出一个通用的建议:将你的JVM堆大小设置成6GB吧,这是目前业界比较公认的一个合理值
KAFKA_HEAP_OPTS:指定堆大小。
KAFKA_JVM_PERFORMANCE_OPTS:指定GC参数
$> export KAFKA_HEAP_OPTS=--Xms6g --Xmx6g
$> export KAFKA_JVM_PERFORMANCE_OPTS= -server -XX:+UseG1GC -XX:MaxGCPauseMillis=20 -XX:InitiatingHeapOccupancyPercent=35 -XX:+ExplicitGCInvokesConcurrent -Djava.awt.headless=true
$> bin/kafka-server-start.sh config/server.properties
Java8默认的新生代垃圾回收器是:UseParallelGC,可以用-XX:+PrintCommandLineFlags -version查看,
还有如果显示指定 -XX:+UseCurrentMarkSweepGC 的话,会默认开启 -XX:+UseParallelGC
G1是jdk9中默认的,jdk8还是需要显式指定的
文件描述符限制
ulimit -n 1000000
文件系统类型
Swappiness
基于这个考虑,我个人建议将swappniess配置成一个接近0但不为0的值,比如1
提交时间
最后是提交时间或者说是Flush落盘时间。向Kafka发送数据并不是真要等数据被写入磁盘才会认为成功,而是只要数据被写入到操作系统的页缓存(Page Cache)上就可以了,随后操作系统根据LRU算法会定期将页缓存上的“脏”数据落盘到物理磁盘上。这个定期就是由提交时间来确定的,默认是5秒。一般情况下我们会认为这个时间太频繁了,可以适当地增加提交间隔来降低物理磁盘的写操作。当然你可能会有这样的疑问:如果在页缓存中的数据在写入到磁盘前机器宕机了,那岂不是数据就丢失了。的确,这种情况数据确实
就丢失了,但鉴于Kafka在软件层面已经提供了多副本的冗余机制,因此这里稍微拉大提交间隔去换取性能还是一个合理的做法。
生产者消息分区机制原理剖析
所谓分区策略是决定生产者将消息发送到哪个分区的算法。Kafka为我们提供了默认的分区策略,同时它也支持你自定义分区策略
轮询策略有非常优秀的负载均衡表现,它总是能保证消息最大限度地被平均分配到所有分区上,故默认情况下它是最合理的分区策略,也是我们最常用的分区策略之一
怎么压缩?
不论是哪个版本,Kafka的消息层次都分为两层:消息集合(message set)以及消息(message)。一个消
息集合中包含若干条日志项(record item),而日志项才是真正封装消息的地方。Kafka底层的消息日志由
一系列消息集合日志项组成。Kafka通常不会直接操作具体的一条条消息,它总是在消息集合这个层面上进
行写入操作
所谓“ZeroCopy”就是“零拷贝”,说的是当数据在磁盘和网络进行传输时避免昂贵的内核态数据拷贝,从而实现快速的数据传输
除了CPU资源充足这一条件,如果你的环境中带宽资源有限,那么我也建议你开启压缩
CPU 资源 充足 + 带宽有限 === 开启压缩
如何配置Kafka无消息丢失。
在Kafka的世界里什么才算是消息丢失,或者说Kafka在什么情况下能保证消息不丢失
一句话概括,Kafka只对“已提交”的消息(committed message)做有限度的持久化保证
第一个核心要素是“已提交的消息”。什么是已提交的消息?当Kafka的若干个Broker成功地接收到一条消息并写入到日志文件后,它们会告诉生产者程序这条消息已成功提交。此时,这条消息在Kafka看来就正式变为“已提交”消息了。
这种发送方式有个有趣的名字,叫“fire and forget”,翻译一下就是“发射后不管”。这个术语原本属于
导弹制导领域,后来被借鉴到计算机领域中,它的意思是,执行完一个操作后不去管它的结果是否成功。调
用producer.send(msg)就属于典型的“fire and forget”,因此如果出现消息丢失,我们是无法知晓的。这
个发送方式挺不靠谱吧,不过有些公司真的就是在使用这个API发送消息。
同理,Kafka中Consumer端的消息丢失就是这么一回事。要对抗这种消息丢失,办法很简单:维持先消费
消息(阅读),再更新位移(书签)的顺序即可。这样就能最大限度地保证消息不丢失
12-客户端都有哪些不常见但是很高级的功能?
事实上,我们可以利用拦截器满足实际的需求,比如端到端系统性能检测、消息审计等
Kafka的Java生产者是如何管理TCP连接的
从社区的角度来看,在开发客户端时,人们能够利用TCP本身提供的一些高级功能,比如多路复用请求以及
同时轮询多个连接的能力。
所谓的多路复用请求,即multiplexing request,是指将两个或多个数据流合并到底层单一物理连接中的过
程。TCP的多路复用请求会在一条物理连接上创建若干个虚拟连接,每个虚拟连接负责流转各自对应的数据
流。其实严格来说,TCP并不能多路复用,它只是提供可靠的消息交付语义保证,比如自动重传丢失的报
文
首先,生产者应用在创建KafkaProducer实例时是会建立与Broker的TCP连接的。其实这种表述也不是很准
确,应该这样说:在创建KafkaProducer实例时,生产者应用会在后台创建并启动一个名为Sender的线程,
该Sender线程开始运行时首先会创建与Broker的连接。
你也许会问:怎么可能是这样?如果不调用send方法,这个Producer都不知道给哪个主题发消息,它又怎
么能知道连接哪个Broker呢?难不成它会连接bootstrap.servers参数指定的所有Broker吗?嗯,是的,Java
Producer目前还真是这样设计的
Producer端关闭TCP连接的方式有两种:一种是用户主动关闭;一种是Kafka自动关闭。
对最新版本的Kafka(2.1.0)而言,Java Producer端管理TCP连接的方式是:
- KafkaProducer实例创建时启动Sender线程,从而创建与bootstrap.servers中所有Broker的TCP连接。
- KafkaProducer实例首次更新元数据信息之后,还会再次创建与集群中所有Broker的TCP连接。
- 如果Producer端发送消息到某台Broker时发现没有与该Broker的TCP连接,那么也会立即创建连接。
- 如果设置Producer端connections.max.idle.ms参数大于0,则步骤1中创建的TCP连接会被自动关闭;如
果设置该参数=-1,那么步骤1中创建的TCP连接将无法被关闭,从而成为“僵尸”连接
幂等生产者和事务生产者是一回事吗?
那么问题来了,Kafka是怎么做到精确一次的呢?简单来说,这是通过两种机制:幂等性(Idempotence)
和事务(Transaction)。它们分别是什么机制?两者是一回事吗?要回答这些问题,我们首先来说说什么
是幂等性。
幂等性Producer
在Kafka中,Producer默认不是幂等性的,但我们可以创建幂等性Producer。它其实是0.11.0.0版本引入的
新功能。在此之前,Kafka向分区发送数据时,可能会出现同一条消息被发送了多次,导致消息重复的情
况。在0.11之后,指定Producer幂等性的方法很简单,仅需要设置一个参数即可,即
props.put(“enable.idempotence”, ture),或
props.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, true)。
enable.idempotence被设置成true后,Producer自动升级成幂等性Producer,其他所有的代码逻辑都不需
要改变。Kafka自动帮你做消息的重复去重。底层具体的原理很简单,就是经典的用空间去换时间的优化思
路,即在Broker端多保存一些字段。当Producer发送了具有相同字段值的消息后,Broker能够自动知晓这
些消息已经重复了,于是可以在后台默默地把它们“丢弃”掉。当然,实际的实现原理并没有这么简单,但
你大致可以这么理解。
首先,它只能保证单分区上的幂等性,即一个幂等性Producer能够保证某个主题的一个分区上不出现重复
消息,它无法实现多个分区的幂等性。其次,它只能实现单会话上的幂等性,不能实现跨会话的幂等性。这
里的会话,你可以理解为Producer进程的一次运行。当你重启了Producer进程之后,这种幂等性保证就丧
失了。
那么你可能会问,如果我想实现多分区以及多会话上的消息无重复,应该怎么做呢?答案就是事务
(transaction)或者依赖事务型Producer。这也是幂等性Producer和事务型Producer的最大区别!
好在对于已提交读(read committed)隔离级别的提法,各大主流数据库厂商都比较统一。所谓
的read committed,指的是当读取数据库时,你只能看到已提交的数据,即无脏读。同时,当写入数据库
时,你也只能覆盖掉已提交的数据,即无脏写。
消费者组到底是什么?
那么何谓Consumer Group呢?用一句话概括就是:Consumer Group是Kafka提供的可扩展且具有容错性的消费者机制。既然是一个组,那么组内必然可以有多个消费者或消费者实例(Consumer Instance),它们共享一个公共的ID,这个ID被称为Group ID。组内的所有消费者协调在一起来消费订阅主题(Subscribed Topics)的所有分区(Partition)。
当然,每个分区只能由同一个消费者组内的一个Consumer实例来消费。
消费者组三个特性
- Consumer Group下可以有一个或多个Consumer实例。这里的实例可以是一个单独的进程,也可以是同
一进程下的线程。在实际场景中,使用进程更为常见一些。 - Group ID是一个字符串,在一个Kafka集群中,它标识唯一的一个Consumer Group。
- Consumer Group下所有实例订阅的主题的单个分区,只能分配给组内的某个Consumer实例消费。这个
分区当然也可以被其他的Group消费
Consumer Group之间彼此独立,互不影响,它们能够订阅相同的一组主题而互不干涉。再加上Broker端的
消息留存机制,Kafka的Consumer Group完美地规避了上面提到的伸缩性差的问题。可以这么说,Kafka仅
仅使用Consumer Group这一种机制,却同时实现了传统消息引擎系统的两大模型:如果所有实例都属于同
一个Group,那么它实现的就是消息队列模型;如果所有实例分别属于不同的Group,那么它实现的就是发
布/订阅模型
理想情况下,Consumer实例的数量应该等于该Group订阅主题的分区总数
不过,慢慢地人们发现了一个问题,即ZooKeeper这类元框架其实并不适合进行频繁的写更新,而
Consumer Group的位移更新却是一个非常频繁的操作。这种大吞吐量的写操作会极大地拖慢ZooKeeper集
群的性能,因此Kafka社区渐渐有了这样的共识:将Consumer位移保存在ZooKeeper中是不合适的做法
Rebalance发生时,Group下所有的Consumer实例都会协调在一起共同参与。你可能会问,每个Consumer
实例怎么知道应该消费订阅主题的哪些分区呢?这就需要分配策略的协助了。
揭开神秘的“位移主题”面纱
__consumer_offsets在Kafka源码中有个更为正式的名字,叫位移主题,即Offsets Topic。
新版本Consumer的位移管理机制其实也很简单,就是将Consumer的位移数据作为一条条普通的Kafka消
息,提交到__consumer_offsets中。可以这么说,__consumer_offsets的主要作用是保存Kafka消费者的位移
信息。它要求这个提交过程不仅要实现高持久性,还要支持高频的写操作。显然,Kafka的主题设计天然就
满足这两个条件,因此,使用Kafka主题来保存位移这件事情,实际上就是一个水到渠成的想法了。
Key和Value分别表示消息的键值和消息体,在Kafka中它们就是字节数组而已。想象一下,如果让你来
设计这个主题,你觉得消息格式应该长什么样子呢?我先不说社区的设计方案,我们自己先来设计一下。
首先从Key说起。一个Kafka集群中的Consumer数量会有很多,既然这个主题保存的是Consumer的位移数
据,那么消息格式中必须要有字段来标识这个位移数据是哪个Consumer的。这种数据放在哪个字段比较合
适呢?显然放在Key中比较合适。
好了,我们来总结一下我们的结论。位移主题的Key中应该保存3部分内容:<Group ID,主题名,分区号
。如果你认同这样的结论,那么恭喜你,社区就是这么设计的!
总结一下,如果位移主题是Kafka自动创建的,那么该主题的分区数是50,副本数是3