第5章,深入Kafka

5.2 控制器

简而言之,Kafka使用Zookeeper的临时节点来选举控制器,并在节点加入集群或退出集群时通知控制器。控制器负责在节点加入或离开集群时进行分区首领选举。控制器使用epoch来避免“脑裂”。“脑裂”是指两个节点同时认为自己是当前的控制器。

~需要了解。

5.3 复制

跟随者的正常不活跃时间或在成为不同步副本之前的时间是通过replica.lag.time.max.ms参数来配置的。这个时间间隔直接影响着首领选举期间的客户端行为和数据保留机制。

~很重要。

5.4 处理请求

那么客户端怎么知道该往哪里发送请求呢?客户端使用了另一种请求类型,也就是元数据请求。这种请求包含了客户端感兴趣的主题列表。服务器端的响应消息里指明了这些主题所包含的分区、每个分区都有哪些副本,以及哪个副本是首领。元数据请求可以发送给任意一个broker,因为所有broker都缓存了这些信息。

~这也是我的一个疑问。

5.4.2 获取请求

如果请求的偏移量存在,broker将按照客户端指定的数量上限从分区里读取消息,再把消息返回给客户端。Kafka使用零复制技术向客户端发送消息——也就是说,Kafka直接把消息从文件(或者更确切地说是Linux文件系统缓存)里发送到网络通道,而不需要经过任何中间缓冲区。这是Kafka与其他大部分数据库系统不一样的地方,其他数据库在将数据发送给客户端之前会先把它们保存在本地缓存里。这项技术避免了字节复制,也不需要管理内存缓冲区,从而获得更好的性能。

~那我就有一个问题了,那么为什么其他系统不采用这种技术呢?既然这种技术能够提升性能。

有意思的是,并不是所有保存在分区首领上的数据都可以被客户端读取。大部分客户端只能读取已经被写入所有同步副本的消息(跟随者副本也不行,尽管它们也是消费者——否则复制功能就无法工作)。分区首领知道每个消息会被复制到哪个副本上,在消息还没有被写入所有同步副本之前,是不会发送给消费者的——尝试获取这些消息的请求会得到空的响应而不是错误。

~也就是说当读取的消息速度有点慢时,有可能是分区复制的速度有问题。

5.5.1 分区分配

为了实现这个目标,我们先随机选择一个broker(假设是4),然后使用轮询的方式给每个broker分配分区来确定首领分区的位置。于是,首领分区0会在broker 4上,首领分区1会在broker 5上,首领分区2会在broker 0上(只有6个broker),并以此类推。然后,我们从分区首领开始,依次分配跟随者副本。如果分区0的首领在broker 4上,那么它的第一个跟随者副本会在broker 5上,第二个跟随者副本会在broker 0上。分区1的首领在broker 5上,那么它的第一个跟随者副本在broker 0上,第二个跟随者副本在broker 1上。
如果配置了机架信息,那么就不是按照数字顺序来选择bro-ker了,而是按照交替机架的方式来选择broker。假设broker 0、broker 1和broker 2放置在同一个机架上,broker 3、broker 4和broker 5分别放置在其他不同的机架上。我们不是按照从0到5的顺序来选择broker,而是按照0,3,1,4,2,5的顺序来选择,这样每个相邻的broker都在不同的机架上(如图5-5所示)。于是,如果分区0的首领在broker 4上,那么第一个跟随者副本会在broker 2上,这两个broker在不同的机架上。

~上面所说对于理解如何分区非常重要,特别在配置了机架信息的时候。

5.5.6 清理的工作原理

为了清理分区,清理线程会读取分区的污浊部分,并在内存里创建一个map。map里的每个元素包含了消息键的散列值和消息的偏移量,键的散列值是16B,加上偏移量总共是24B。
管理员在配置Kafka时可以对map使用的内存大小进行配置。每个线程都有自己的map,而这个参数指的是所有线程可使用的内存总大小。如果你为map分配了1GB内存,并使用了5个清理线程,那么每个线程可以使用200MB内存来创建自己的map。
清理线程在创建好偏移量map后,开始从干净的片段处读取消息,从最旧的消息开始,把它们的内容与map里的内容进行比对。它会检查消息的键是否存在于map中,如果不存在,那么说明消息的值是最新的,就把消息复制到替换片段上。如果键已存在,消息会被忽略,因为在分区的后部已经有一个具有相同键的消息存在。在复制完所有的消息之后,我们就将替换片段与原始片段进行交换,然后开始清理下一个片段。

~此处的清除应该是指清除某些键的陈旧值,也就是说当某个键已经有了最新值时,那么原有的值就失去了意义,比如用户的最新状态等,可以在一定时间之后清除掉。

@最后修改日期:2020/04/01