kafka系列3----可靠性与运维

1、说明

注意,本文说的副本包括Leader和Follower副本

2、复制

Kafka 允许 topic 的 partition 拥有若干副本,你可以在server端配置partition 的副本数量。当集群中的节点出现故障时,能自动进行故障转移,保证数据的可用性。正常情况下, 每个分区都有一个 leader 和零或多个 followers 。 总的副本数是包含 leader 的总和。 所有的读写操作都由 leader 处理Follower不处理来自客户端的请求,它们唯一的任务就是从Leader那里复制消息,保持与Leader一致的状态注意Follower除了从Leader那里复制消息,不做任何事,所以没有所谓读写分离之类的说法。

Kafka 判断节点是否存活有两种方式。

  1. 节点必须可以维护和 ZooKeeper 的连接,Zookeeper 通过心跳机制检查每个节点的连接。
  2. 如果节点是个 follower ,它必须能及时的同步 leader 的写操作,并且延时不能太久。

我们认为满足这两个条件的节点处于 “in sync” 状态,区别于 “alive” 和 “failed” 。 Leader会追踪所有 “in sync” 的节点。如果有节点挂掉了, 或是写超时, 或是心跳超时, leader 就会把它从同步副本列表中移除。 同步超时和写超时的时间由 replica.lag.time.max.ms 配置确定。

2.1 kafka的Quorum实现方式:ISR

选择写入时候需要保证一定数量的副本(通常是过半)写入成功,这样的读写机制称为 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 个节点故障而不会丢失任何已经提交的消息。

2.2 关于follower的一点猜想

follower可以像consumer一样从leader获取数据。如果ISR中有一个成员掉线了,且其他follower数据此时跟leader不同步。因为follower会从leader获取数据,则当其中一个follower不断拉取数据直到其跟leader数据一致时,是否自动“晋升”到ISR?

而ISR的集合大小,是否由min.insync.replicas等复制参数来控制?

2.3 ack

向 Kafka 写数据时,producers 设置 ack 是否提交完成, 0:不等待broker返回确认消息,1: leader保存成功返回或, -1(all): 所有备份都保存成功返回.请注意. 设置 “ack = all” 并不能保证所有的副本都写入了消息。默认情况下,当 acks = all 时,只要 ISR 副本同步完成,就会返回消息已经写入。

2.4 限定复制shi'时间

对副本进行数据复制会造成延迟,延迟时间可以通过参数replica.lag. time.max .ms 来配置,它指定了副本在复制消息时可被允许的最大延迟时间。

2.5 指定ISR集合大小

指定最小的 ISR 集合大小,只有当 ISR 的大小大于最小值,分区才能接受写入操作,以防止仅写入单个备份的消息丢失造成消息不可用的情况,这个设置只有在生产者使用 acks = all 的情况下才会生效,这至少保证消息被 ISR 副本写入。此设置是一致性和可用性 之间的折衷,对于设置更大的最小ISR大小保证了更好的一致性,因为它保证将消息被写入了更多的备份,减少了消息丢失的可能性。但是,这会降低可用性,因为如果 ISR 副本的数量低于最小阈值,那么分区将无法写入

2.6 复制系数

主题级别的配置参数是 replication.factor,而在 broker 级别则可以通过 default.replication.factor来配置自动创建的主题。Kafka 的默认复制系数就是 3,也就是说每个分区总共会被 3 个不同的broker 复制 3 次。如果复制系数为 N,那么在N-l 个 broker 失效的情况下,仍然能够从主题读取数据或向主题写入数据,此时还剩一个broker

使用 broker.rack 参数来为每个broker 配置所在机架的名字。如果配置了机架名字, Kafka 会保证分区的副本被分布在多个机架上,从而获得更高的可用性。

2.7 最小同步副本

在主题级别和 broker 级别上,这个参数都叫min.insync.replicas 。尽管为一个主题配置了 3 个副本,还是会出现只有一个同步副本的情况。对于一个包含 3 个副本的主题,如果min.insync.replicas被设为 2,那么至少要存在2个同步副本才能向分区写入数据。

2.8 为何kafka不支持读写分离

传统的读写分离通常是主写从读,或者主读写,从读。很显然读写分离可以让从节点分担主节点的负载压力,对提高吞吐量有明显的好处。但为何kafka不支持读写分离?

原因如下:

  1. 数据一致性问题。支持读写分离,意味着主从数据需要一致。然而主节点把数据复制到从节点总是有时延的,在主节点把数据复制到从节点之前,主从节点数据是不一致的。要想保持数据一致性,可能会让有些东西变得复杂,比如ack,当ack为all就可能保持数据一致性,但是ack很多时候可能并不为all,也就是主从数据不一致。
  2. 延时问题。考虑这样2个场景:
    1. 保持主从数据一致,这样消费者就可以从任意一个主从节点中读取数据。这显然需要所有从节点跟主节点保持同步(这里先抛开上一点的ack来谈)。但是同步的代价是很高的。据测试,异步的性能比同步快40倍以上。所以这里假设单机能处理1万个请求每秒,而同步每秒能处理250个请求,而且kafka性能是极高的,所以采用读写分离性能很可能比不支持要低
    2. 数据不保持一致。因为数据不一致,当消费者要获取不一致的数据时,比如leader有数据A,而此时follower没有A,然后consumer从follower中要获取A,这时候显然会失败。然后consumer就会从新请求获取A。因为kafka的高性能,这多次请求去获得A的时间可能足够没有读写分离的kafka处理多个请求了

所以,kafka不支持读写分离,是一种简单而高效的做法

3、节点协调

3.1 群组协调器与在均衡

分区的所有权从一个消费者转移到另一个消费者,这样的行为被称为再均衡。当有消费者发生崩溃,井停止读取消息,就会触发再均衡,把它所处理的分区转交给组内的其他消费者继续处理,或者新加入一个消费者,其他消费者能把分区匀出一个给它。

前面已经看过消费者组了,消费者组有两个问题要解决:

  1. 把分区均匀分给消费者
  2. 当一个在工作的消费者出现问题,或者新加入一个消费者,需要能进行再均衡

这两个问题都是由组协调器来解决的

3.2 控制器

kafka集群中,会有一个controller,集群中任一个节点都可以作为controller。集群启动时,节点们会去zookeeper注册/controller节点,只有第一个注册的节点会成功,然后成为控制器,而其他节点则会收到节点已存在的异常,由此保证集群中仅有一个控制器。如果 controller 节点挂了,其他存活的 broker 都可能成为新的 controller 节点。

在 broker 启动的时候,它通过创建临时节点把自己的 ID 注册到 Zookeeper,要知道zookeeper的临时节点仅会话期有效,所以broker一旦下线,临时节点也会消失。所以controller很容易检测到节点是否存活

controller 节点负责 检测 brokers 级别故障,并负责在 broker 故障的情况下更改这个故障 Broker 中的 partition 的 leadership 。这种方式可以批量的通知主从关系的变化,使得对于拥有大量partition 的leader选举过程的代价更低并且速度更快。

3.3 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

3.4 不完全Leader选举:允许不同步副本成为leader

这里考虑一种极端情况,一个分区的所有备份节点都挂掉。这种情况怎么去保证数据不会丢失,这里有两种实现的方法

  1. 等待一个 ISR 的副本重新恢复正常服务,并选择这个副本作为领 leader (它有极大可能拥有全部数据)。
  2. 选择第一个重新恢复正常服务的副本(不一定是 ISR 中的)作为leader。

这2种策略的选择,需要根据可用性和数据一致性进行权衡,想快速恢复,就用第二种策略,想保证数据一致性,就用第一种策略。

其中第二种策略称为unclean leader,因为这时候的leader的数据可能是不完整的。如果不允许这种情况,可以禁用 unclean leader 选举机制。

unclean.leader.election 只能在 broker 级别(实际上是在集群范围内)进行配置, 它的默认值是 true 。

如果把 unclean.leader.election.enable 设为 true ,就是允许不同步的副本成为Leader(也就是“不完全的选举)

4、kafka集群和主从

 

假设创建有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的配置是一样的,所以就形成一个集群

4.1 在集群中创建topic

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

  1. “leader”是负责给定分区所有读写操作的节点。每个节点都是随机选择的部分分区的领导者。从上面信息可以看到,leader是broker.id为1的机子
  2. “replicas”是复制分区日志的节点列表,不管这些节点是leader还是仅仅活着。
  3. “isr”是一组“同步”replicas,是replicas列表的子集,它活着并被指到leader。这里ISR是1、2、0,说明broker.id为1,或2或0的机子都在这个集合中

4.2 杀掉leader看看效果

Linux上杀掉leader:

  1. 获取leader的pid

ps aux | grep server-1.properties

输出如下,第一列就是pid:

7564 ttys002    0:15.91 /System/Library/Frameworks/JavaVM.framework/Versions/1.8/Home/bin/java...

  1. kill -9 7564

 

Windows上杀掉leader:

  1. 获取leader的pid

wmic process where "caption = 'java.exe' and commandline like '%server-1.properties%'" get processid

6016

  1. 杀掉6016进程:taskkill /pid 6016 /f

 

查看效果:

执行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了。

4.3 扩容与缩容

扩容很简单,只要再弄一个机子,然后指定一个唯一的broker ID,zookeeper地址跟集群中的相同即可。

新的服务器不会自动分配到任何数据分区,只有新的topic的数据才会分配到新的服务器。要将旧的数据迁移到新的机器,可以使用bin目录下的重新分区工具:kafka-reassign-partitions。该工具用于将数据从一个broker集迁移到另一个broker集

4.3.1 kafka-reassign-partitions

分区重新分配工具可以以3种互斥方式运行:

  1. --generate: 在此模式下,给定一个 topic 列表和一个 broker 列表,该工具会生成一个候选重新分配,以将指定的 topic 的所有分区移动到新的broker。此选项仅提供了一种便捷的方式,可以根据 tpoc 和目标 broker 列表生成分区重新分配计划。
  2. --execute: 在此模式下,该工具基于用户提供的重新分配计划启动分区重新分配。(使用--reassignment-json-file选项)。这可以是由管理员制作的自定义重新分配计划,也可以是使用--generate选项提供的自定义重新分配计划。
  3. --verify: 在此模式下,该工具将验证最近用 --execute 模式执行间的所有分区的重新分配状态。状态可以是成功完成,失败或正在进行

 

新建一个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

这时候就执行分区了。

4.3.2 自定义分区和迁移

例如,以下示例将 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即可。

4.3.3 增加复制因子

新建increase-replication-factor.json文件,内容如下:

{"version":1,

"partitions":[{"topic":"foo","partition":0,"replicas":[5,6,7]}]}

然后用—execute执行即可。此时分区0就被拆分为3个分区,并分配到broker5,6,7。

4.3.4 限制数据迁移过程中的带宽使用

添加—throttle选项,比如—throttle 50000000,表示限制带宽为50000000 B/s

4.4 优雅关机

Kafka集群将自动检测到任何 broker 关机或故障,并为该机器上的分区选择新的 leader。无论服务器出现故障还是因为维护或配置更改而故意停机,都会发生这种情况。 对于后一种情况,Kafka支持更优雅的停止服务器的机制,而不仅仅是杀死它。 当一个服务器正常停止时,它将采取两种优化措施:

  1. 它将所有日志同步到磁盘,以避免在重新启动时需要进行任何日志恢复活动(即验证日志尾部的所有消息的校验和)。由于日志恢复需要时间,所以从侧面加速了重新启动操作。
  2. 它将在关闭之前将以该服务器为 leader 的任何分区迁移到其他副本。这将使 leader 角色传递更快,并将每个分区不可用的时间缩短到几毫秒。

只要服务器的停止不是通过直接杀死,同步日志就会自动发生,但控制 leader 迁移需要使用特殊的设置:

    controlled.shutdown.enable=true

请注意,只有当 broker 托管的分区具有副本(即,复制因子大于1 且至少其中一个副本处于活动状态)时,对关闭的控制才会成功。 这通常是你想要的,因为关闭最后一个副本会使 topic 分区不可用。

4.5 机架感知

机架感知功能可以跨不同机架传播相同分区的副本。通过向 broker 配置添加属性来指定 broker 属于的特定机架:

broker.rack=my-rack-id

当 topic 创建,修改或副本重新分配时, 机架约束将得到保证,确保副本跨越尽可能多的机架(一个分区将跨越 min(#racks,replication-factor) 个不同的机架)。

5、配置生产者的重试参数

如果 broker 返回的错误可以通过重试来解决,那么生产者会自动处理这些错误。参考生产者配置retries

6、可靠性验证的一些方法

broker验证

  1. Leader选举 :如果我停掉Leader会发生什么事情?生产者和消费者重新恢复正常状态需要多长时间?
  2. 控制器选举 : 重启控制器后系统需要多少时间来恢复状态?
  3. 依次重启:可以依次重启 broker 而不丢失任何数据吗?
  4. 不完全Leader选举测试:如果依次停止所有副本(确保每个副本都变为不同步的),然后启动一个不同步的 broker 会发生什么?要怎样恢复正常?这样做是可接受的吗?

 

客户端验证

  1. 客户端从服务器断开连接(系统管理员可以帮忙模拟网络故障)
  2. Leader选举 3
  3. 依次重启 broker;
  4. 依次重启消费者;
  5. 依次重启生产者。

7、监控

kafka基于JMX实现监控,所以最简单的方式就是打开jconsole进行查看,参考:

http://kafka.apache.org/22/documentation.html#monitoring

8、Quotas资源配额

Kafka 集群可以对客户端请求进行配额,控制集群资源的使用。Kafka broker 可以对客户端做两种类型资源的配额限制,同一个group的client 共享配额。

  1. 定义字节率的阈值来限定网络带宽的配额。 (从 0.9 版本开始)
  2. request 请求率的配额,网络和 I/O线程 cpu利用率的百分比。 (从 0.11 版本开始)。(官方解释:请求速率的配额定义了一个客户端可以使用 broker request handler I/O 线程和网络线程在一个配额窗口时间内使用的百分比。 n% 的配置代表一个线程的 n%的使用率,所以这种配额是建立在总容量 ((num.io.threads + num.network.threads) * 100)%之上的. 每个 group 的client 的资源在被限制之前可以使用单位配额时间窗口内I/O线程和网络线程利用率的 n%。 由于分配给 I/O和网络线程的数量是基于 broker 的核数,所以请求量的配额代表每个group 的client 使用cpu的百分比。)

资源配额可以针对 (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 实时的监听到并生效。

配额配置的优先级顺序是:

  1. /config/users//clients/
  2. /config/users//clients/
  3. /config/users/
  4. /config/users//clients/
  5. /config/users//clients/
  6. /config/users/
  7. /config/clients/

/config/clients/

8.1 超出配额处理

如果有客户端使用的资源超出配额,broker不会返回error,broker 会计算出将客户端限制到配额之下的延迟时间,并且延迟response响应。

9、运维工具

 

9.1 mirror-maker

用于数据迁移

9.2 topics工具

9.2.1 修改分区

使用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 的分区数。

9.2.2 修改配置

添加配置:

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

9.2.3 删除topic

kafka-topics.sh --zookeeper zk_host:port/chroot --delete --topic my_topic_name

9.3 consumer工具

9.3.1 检测consumer位置

要在名为my-group的 consumer 组上运行此工具,消费一个名为my-topic的 topic 将如下所示:

kafka-consumer-groups.sh --bootstrap-server localhost:9092 --describe --group my-group

注意:这将仅显示使用Java consumer API(基于非ZooKeeper的 consumer)的 consumer 的信息。

主题

分区

当前偏移量

LOG-END-OFFSET

LAG

CONSUMER-ID

HOST

my-topic

0

2

4

2

consumer-1-029af89c-873c-4751-a720-cefd41a669d6

/127.0.0.1

my-topic

1

2

3

1

consumer-1-029af89c-873c-4751-a720-cefd41a669d6

/127.0.0.1

my-topic

2

2

3

1

consumer-2-42c1abd4-e3b2-425d-a8bb-e1ea49b29bb2

/127.0.0.1

这个工具也适用于基于ZooKeeper的 consumer:

kafka-consumer-groups.sh --zookeeper localhost:2181 --describe --group my-group

显示信息跟上面基本一致

9.3.2 管理 Consumer 组

通过 ConsumerGroupCommand 工具,我们可以列出,描述或删除 consumer 组。请注意,删除仅在组元数据存储在ZooKeeper中时可用。

示例,要列出所有 topic 中的所有 consumer 组:

kafka-consumer-groups.sh --bootstrap-server localhost:9092 --list

 

你可能感兴趣的:(消息中间件,kafka,消息中间件,JMS,可靠性,系统架构)