注意,本文说的副本包括Leader和Follower副本
Kafka 允许 topic 的 partition 拥有若干副本,你可以在server端配置partition 的副本数量。当集群中的节点出现故障时,能自动进行故障转移,保证数据的可用性。正常情况下, 每个分区都有一个 leader 和零或多个 followers 。 总的副本数是包含 leader 的总和。 所有的读写操作都由 leader 处理。Follower不处理来自客户端的请求,它们唯一的任务就是从Leader那里复制消息,保持与Leader一致的状态。注意Follower除了从Leader那里复制消息,不做任何事,所以没有所谓读写分离之类的说法。
Kafka 判断节点是否存活有两种方式。
我们认为满足这两个条件的节点处于 “in sync” 状态,区别于 “alive” 和 “failed” 。 Leader会追踪所有 “in sync” 的节点。如果有节点挂掉了, 或是写超时, 或是心跳超时, leader 就会把它从同步副本列表中移除。 同步超时和写超时的时间由 replica.lag.time.max.ms 配置确定。
选择写入时候需要保证一定数量的副本(通常是过半)写入成功,这样的读写机制称为 Quorum。Quorum:常见于paxos或类似协议。
这种方式可以很好的保证数据一致性。但是根据我们的经验,在一个系统中,仅仅靠冗余来避免单点故障是不够的,但是每写5次,对磁盘空间需求是5倍, 吞吐量下降到 1/5,这对于处理海量数据问题是不切实际的。这可能是为什么 quorum 算法更常用于共享集群配置(如 ZooKeeper ), 而不适用于原始数据存储的原因
所以kafka采取了一种稍微不同的方法来选择它的投票集。Kafka 动态维护了一个同步状态的备份的集合 (a set of in-sync replicas), 简称 ISR ,在这个集合中的节点都是和 leader 保持高度一致的,只有这个集合的成员才 有资格被选举为 leader,一条消息必须被这个集合 所有 节点读取并追加到日志中了,这条消息才能视为提交。
因为 ISR 模型和 f+1 副本,一个 Kafka topic 冗余 f 个节点故障而不会丢失任何已经提交的消息。
follower可以像consumer一样从leader获取数据。如果ISR中有一个成员掉线了,且其他follower数据此时跟leader不同步。因为follower会从leader获取数据,则当其中一个follower不断拉取数据直到其跟leader数据一致时,是否自动“晋升”到ISR?
而ISR的集合大小,是否由min.insync.replicas等复制参数来控制?
向 Kafka 写数据时,producers 设置 ack 是否提交完成, 0:不等待broker返回确认消息,1: leader保存成功返回或, -1(all): 所有备份都保存成功返回.请注意. 设置 “ack = all” 并不能保证所有的副本都写入了消息。默认情况下,当 acks = all 时,只要 ISR 副本同步完成,就会返回消息已经写入。
对副本进行数据复制会造成延迟,延迟时间可以通过参数replica.lag. time.max .ms 来配置,它指定了副本在复制消息时可被允许的最大延迟时间。
指定最小的 ISR 集合大小,只有当 ISR 的大小大于最小值,分区才能接受写入操作,以防止仅写入单个备份的消息丢失造成消息不可用的情况,这个设置只有在生产者使用 acks = all 的情况下才会生效,这至少保证消息被 ISR 副本写入。此设置是一致性和可用性 之间的折衷,对于设置更大的最小ISR大小保证了更好的一致性,因为它保证将消息被写入了更多的备份,减少了消息丢失的可能性。但是,这会降低可用性,因为如果 ISR 副本的数量低于最小阈值,那么分区将无法写入。
主题级别的配置参数是 replication.factor,而在 broker 级别则可以通过 default.replication.factor来配置自动创建的主题。Kafka 的默认复制系数就是 3,也就是说每个分区总共会被 3 个不同的broker 复制 3 次。如果复制系数为 N,那么在N-l 个 broker 失效的情况下,仍然能够从主题读取数据或向主题写入数据,此时还剩一个broker
使用 broker.rack 参数来为每个broker 配置所在机架的名字。如果配置了机架名字, Kafka 会保证分区的副本被分布在多个机架上,从而获得更高的可用性。
在主题级别和 broker 级别上,这个参数都叫min.insync.replicas 。尽管为一个主题配置了 3 个副本,还是会出现只有一个同步副本的情况。对于一个包含 3 个副本的主题,如果min.insync.replicas被设为 2,那么至少要存在2个同步副本才能向分区写入数据。
传统的读写分离通常是主写从读,或者主读写,从读。很显然读写分离可以让从节点分担主节点的负载压力,对提高吞吐量有明显的好处。但为何kafka不支持读写分离?
原因如下:
所以,kafka不支持读写分离,是一种简单而高效的做法
分区的所有权从一个消费者转移到另一个消费者,这样的行为被称为再均衡。当有消费者发生崩溃,井停止读取消息,就会触发再均衡,把它所处理的分区转交给组内的其他消费者继续处理,或者新加入一个消费者,其他消费者能把分区匀出一个给它。
前面已经看过消费者组了,消费者组有两个问题要解决:
这两个问题都是由组协调器来解决的
kafka集群中,会有一个controller,集群中任一个节点都可以作为controller。集群启动时,节点们会去zookeeper注册/controller节点,只有第一个注册的节点会成功,然后成为控制器,而其他节点则会收到节点已存在的异常,由此保证集群中仅有一个控制器。如果 controller 节点挂了,其他存活的 broker 都可能成为新的 controller 节点。
在 broker 启动的时候,它通过创建临时节点把自己的 ID 注册到 Zookeeper,要知道zookeeper的临时节点仅会话期有效,所以broker一旦下线,临时节点也会消失。所以controller很容易检测到节点是否存活
controller 节点负责 检测 brokers 级别故障,并负责在 broker 故障的情况下更改这个故障 Broker 中的 partition 的 leadership 。这种方式可以批量的通知主从关系的变化,使得对于拥有大量partition 的leader选举过程的代价更低并且速度更快。
当leader挂掉,这会被控制器检测到,然后控制器就触发leader选举。然后从ISR中选出一个follower作为首选leader。假设分区的副本列表为1,5,9,则节点1首选为节点5或9的 leader ,因为它在副本列表中较早。
比如在topic刚刚创建时,显然各个分区没有任何数据,这时候各个分区就会选出自己的首选leader。然后经过leader再均衡后,此时首选leader就成为真正的leader了。
默认情况下,Kafka 的 auto.leader.rebalance .enable 被设为 true,让leader间可以实现再均衡。如果设为false,那就需要自己使用kafka的bin下的工具手动触发leader选举了:
kafka-preferred-replica-election.sh --zookeeper zk_host:port/chroot
这里考虑一种极端情况,一个分区的所有备份节点都挂掉。这种情况怎么去保证数据不会丢失,这里有两种实现的方法
这2种策略的选择,需要根据可用性和数据一致性进行权衡,想快速恢复,就用第二种策略,想保证数据一致性,就用第一种策略。
其中第二种策略称为unclean leader,因为这时候的leader的数据可能是不完整的。如果不允许这种情况,可以禁用 unclean leader 选举机制。
unclean.leader.election 只能在 broker 级别(实际上是在集群范围内)进行配置, 它的默认值是 true 。
如果把 unclean.leader.election.enable 设为 true ,就是允许不同步的副本成为Leader(也就是“不完全的选举)
假设创建有3个机子的集群,拷贝2份config/server.properties给另外2部机子,也就是最终有3个server.properties文件,文件名分别为server0.properties,server1.properties,server.properties。其配置如下(仅列出三者不同的部分):
config/server-1.properties:
broker.id=1
listeners=PLAINTEXT://:9093
log.dir=/tmp/kafka-logs-1
config/server-2.properties:
broker.id=2
listeners=PLAINTEXT://:9094
log.dir=/tmp/kafka-logs-2
config/server-0.properties基本类似,所以不列出来。然后用这3个配置文件启动kafka即可。因为3个kafka的zookeeper的配置是一样的,所以就形成一个集群
kafka-topics.sh --describe --zookeeper localhost:2181 --topic my-replicated-topic
创建信息如下(为方便起见,对信息做了排版);
Topic:my-replicated-topic
PartitionCount:1
ReplicationFactor:3 Configs:
Topic: my-replicated-topic
Partition: 0
Leader: 1
Replicas: 1,2,0
Isr: 1,2,0
Linux上杀掉leader:
ps aux | grep server-1.properties
输出如下,第一列就是pid:
7564 ttys002 0:15.91 /System/Library/Frameworks/JavaVM.framework/Versions/1.8/Home/bin/java...
Windows上杀掉leader:
wmic process where "caption = 'java.exe' and commandline like '%server-1.properties%'" get processid
6016
查看效果:
执行kafka-topics.sh --describe --zookeeper localhost:2181 --topic my-replicated-topic
输出如下:
Topic:my-replicated-topic
PartitionCount:1
ReplicationFactor:3 Configs:
Topic: my-replicated-topic
Partition: 0
Leader: 2
Replicas: 1,2,0
Isr: 2,0
可以看到leader变成2了。
扩容很简单,只要再弄一个机子,然后指定一个唯一的broker ID,zookeeper地址跟集群中的相同即可。
新的服务器不会自动分配到任何数据分区,只有新的topic的数据才会分配到新的服务器。要将旧的数据迁移到新的机器,可以使用bin目录下的重新分区工具:kafka-reassign-partitions。该工具用于将数据从一个broker集迁移到另一个broker集
分区重新分配工具可以以3种互斥方式运行:
新建一个topics-to-move.json文件,内容如下:
{"topics": [{"topic": "foo1"},
{"topic": "foo2"}],
"version":1
}
执行kafka-reassign-partitions.sh --zookeeper localhost:2181 --topics-to-move-json-file topics-to-move.json --broker-list "5,6" --generate
其中topics-to-move-json-file指向我们刚刚创建的文件
然后输出如下:
当前分区副本分配
{"version":1,
"partitions":[{"topic":"foo1","partition":2,"replicas":[1,2]},
{"topic":"foo1","partition":0,"replicas":[3,4]},
{"topic":"foo2","partition":2,"replicas":[1,2]},
{"topic":"foo2","partition":0,"replicas":[3,4]},
{"topic":"foo1","partition":1,"replicas":[2,3]},
{"topic":"foo2","partition":1,"replicas":[2,3]}]
}
建议的分区重新分配配置
{"version":1,
"partitions":[{"topic":"foo1","partition":2,"replicas":[5,6]},
{"topic":"foo1","partition":0,"replicas":[5,6]},
{"topic":"foo2","partition":2,"replicas":[5,6]},
{"topic":"foo2","partition":0,"replicas":[5,6]},
{"topic":"foo1","partition":1,"replicas":[5,6]},
{"topic":"foo2","partition":1,"replicas":[5,6]}]
}
可以看到foo1和foo2这2个topic的分区情况。这个信息显示,新的分区将把foo1和foo2分配到broker5和6。不过在实际工作中,不要这么做,2个broker分3个分区,显然不太好
请注意,这个时候,分区操作还没有开始,它只是告诉你当前的任务和建议的新任务。应该保存当前的分配,以防您想要回滚到它。新的任务应该保存在一个json文件(例如expand-cluster-reassignment.json)中,并用--execute选项输入到工具中,如下所示:
kafka-reassign-partitions.sh --zookeeper localhost:2181 --reassignment-json-file expand-cluster-reassignment.json --execute
这时候就执行分区了。
例如,以下示例将 topic foo1的分区0 移到 broker 5,6中和将 topic foo2的分区1移到 broker 2,3中:
建立custom-reassignment.json,内容如下:
{"version":1,
"partitions":[{"topic":"foo1","partition":0,"replicas":[5,6]},
{"topic":"foo2","partition":1,"replicas":[2,3]}]
}
然后基本跟前面一样,使用—execute即可。
新建increase-replication-factor.json文件,内容如下:
{"version":1,
"partitions":[{"topic":"foo","partition":0,"replicas":[5,6,7]}]}
然后用—execute执行即可。此时分区0就被拆分为3个分区,并分配到broker5,6,7。
添加—throttle选项,比如—throttle 50000000,表示限制带宽为50000000 B/s
Kafka集群将自动检测到任何 broker 关机或故障,并为该机器上的分区选择新的 leader。无论服务器出现故障还是因为维护或配置更改而故意停机,都会发生这种情况。 对于后一种情况,Kafka支持更优雅的停止服务器的机制,而不仅仅是杀死它。 当一个服务器正常停止时,它将采取两种优化措施:
只要服务器的停止不是通过直接杀死,同步日志就会自动发生,但控制 leader 迁移需要使用特殊的设置:
controlled.shutdown.enable=true
请注意,只有当 broker 托管的分区具有副本(即,复制因子大于1 且至少其中一个副本处于活动状态)时,对关闭的控制才会成功。 这通常是你想要的,因为关闭最后一个副本会使 topic 分区不可用。
机架感知功能可以跨不同机架传播相同分区的副本。通过向 broker 配置添加属性来指定 broker 属于的特定机架:
broker.rack=my-rack-id
当 topic 创建,修改或副本重新分配时, 机架约束将得到保证,确保副本跨越尽可能多的机架(一个分区将跨越 min(#racks,replication-factor) 个不同的机架)。
如果 broker 返回的错误可以通过重试来解决,那么生产者会自动处理这些错误。参考生产者配置retries
broker验证
客户端验证
kafka基于JMX实现监控,所以最简单的方式就是打开jconsole进行查看,参考:
http://kafka.apache.org/22/documentation.html#monitoring
Kafka 集群可以对客户端请求进行配额,控制集群资源的使用。Kafka broker 可以对客户端做两种类型资源的配额限制,同一个group的client 共享配额。
资源配额可以针对 (user,client-id),users 或者client-id groups 三种规则进行配置。(user, client-id)是元组,即user跟client-id都相同的客户端就认为它们属于同一个元组。举个例子,如果 (user="test-user", client-id="test-client") 客户端producer 有10MB/sec 的生产资源配置,这10MB/sec 的资源在所有 "test-user" 用户,client-id是 "test-client" 的producer实例中是共享的。
User 和 (user, client-id) 规则的配额配置会写到zookeeper的 /config/users路径下,client-id 配额的配置会写到 /config/clients 路径下,这些配置的覆盖会被所有的 brokers 实时的监听到并生效。
配额配置的优先级顺序是:
/config/clients/
如果有客户端使用的资源超出配额,broker不会返回error,broker 会计算出将客户端限制到配额之下的延迟时间,并且延迟response响应。
用于数据迁移
使用kafka/bin下的kafka-topics.sh可以对kafka topic进行修改。
kafka-topics.sh --zookeeper zk_host:port/chroot --alter --topic my_topic_name --partitions 40
通过kafka-topics -help可查看帮助
当前,Kafka 不支持减少一个 topic 的分区数。
添加配置:
kafka-configs.sh --zookeeper zk_host:port/chroot --entity-type topics --entity-name my_topic_name --alter --add-config x=y
删除一个配置项:
> bin/kafka-configs.sh --zookeeper zk_host:port/chroot --entity-type topics --entity-name my_topic_name --alter --delete-config x
kafka-topics.sh --zookeeper zk_host:port/chroot --delete --topic my_topic_name
要在名为my-group的 consumer 组上运行此工具,消费一个名为my-topic的 topic 将如下所示:
kafka-consumer-groups.sh --bootstrap-server localhost:9092 --describe --group my-group
注意:这将仅显示使用Java consumer API(基于非ZooKeeper的 consumer)的 consumer 的信息。
主题 |
|
|
|
|
|
|
|
0 |
2 |
4 |
2 |
|
|
|
1 |
2 |
3 |
1 |
|
|
|
2 |
2 |
3 |
1 |
|
|
这个工具也适用于基于ZooKeeper的 consumer:
kafka-consumer-groups.sh --zookeeper localhost:2181 --describe --group my-group
显示信息跟上面基本一致
通过 ConsumerGroupCommand 工具,我们可以列出,描述或删除 consumer 组。请注意,删除仅在组元数据存储在ZooKeeper中时可用。
示例,要列出所有 topic 中的所有 consumer 组:
kafka-consumer-groups.sh --bootstrap-server localhost:9092 --list