Kafka权威指南笔记 kafka版本0.9.01
一 第三章生产者
    1.内容:生产者的设计,组件,使用,配置选项,分区方法和序列号器。
    2.消息的场景:每个消息都很重要?是否允许丢失消息?重复消息是否可接受?严格的延迟和吞吐量要求。例如:不允许消息丢失和重复消费,可以接受延迟为500ms,吞吐量要求高。
    3.生产者发送消息的过程:ProducerRecord对象为应用层的消息对象,包含目标主题和内容,可选配置键和分区,发送时生产者先将键和值对象序列化为字节数组。然后到达分区器,如果没指定分区,根据键来选择一个分区,然后将消息发送到主题的分区下,消息被添加到一个记录批次里,一个批次内的消息会发送到相同的主题和分区上。最后一个独立的线程负责将这些记录批次发送到相应的broker。成功写入kafka会返回RecordMetaData对象,包含主题和分区信息以及分区偏移量。失败写入返回错误,重试失败后会返回错误信息。
    4.kafka3个必须配置属性
        1.bootstrap.servers:指定broker的地址清单,建议两个
        2.key.serializer:键对象序列号器,必须设置,提供ByteArraySerializer,StringSerializer,IntegerSerializer
        3.value.serializer:值对象序列号器
    5.生产者发送消息3种方式:
        1.发送并忘记(fire-and-forget):会自动重试
        2.同步发送:返回Future对象
        3.异步发送:指定回调函数
    6.生产者的其他配置
        1.acks 指定多少分区副本写入认为是成功写入
            acks=0,不等待broker响应,吞吐量极高
            acks=1,集群leader节点收到消息,成功响应(会有丢消息情况,没有错误响应返回,新首领节点未收到消息),影响吞吐量的因素为同步发送还是异步发送
            acks=all,当所有参与复制的节点收到消息,成功响应。最安全,延迟最高。
        2.buffer.memory 指定生产者内存缓存区的大小
        3.compression.type 指定压缩方式
            1.snappy cpu占用少,较好性能和较好压缩比,偏重性能和网络带宽
            2.gzip cpu占用多,更高的压缩比,偏重降低传输开销和存储开销(kafka瓶颈)
        4.retries 指定重发消息次数,建议设置重试次数和重试时间间隔前测试实际恢复崩溃节点所需要的时间,让总重试时间长于崩溃恢复需要时间
        5.batch.size 
        多个消息需要被发送到同一个分区时,生产者会同一批次一起发送,制定了一个批次可以使用的内存大小,按字节数计算。
        6.linger.ms
        指定了生产者在发送批次之前等待更多消息加入批次的时间。默认情况下只要有可用的线程,生产者就会把消息发送出去
        7.client.id
        该参数是任意的字符串,识别消息来源,在日志和配额指标使用
        8.max.in.flight.requests.per.connection
        指定了生产者在收到服务器响应之前可以发送多少消息。值越高会占用更多内存,更大的吞吐量。设置为1可以保证消息按照发送的顺序写入服务器,即使重试发生。
        9.timeout.ms,request.timeout.ms,metadata.fetch.timeout.ms
        超时时间设置,request为生产者发送数据时等待服务器返回响应时间,metadata为生产者在获取元数据时等待服务器响应时间。timeout.ms指定了broker等待同步副本返回消息确认的时间,与asks配置匹配,broker会返回错误。
        10.max.block.ms
        指定了调用send方法和partitionsFor()的生产者阻塞时间。在生产者发送缓冲区已满或没有可用元数据时阻塞,超时会抛出超时异常。
        11.max.request.size
        控制生产者发送请求的大小。指定发送的单个消息最大值,以及指定单个请求内所有消息总的大小。和broker接收消息的message.max.bytes参数匹配。
        12.receive.buffer.bytes,send.buffer.bytes
        制定了TCP socket接收和发送数据包的缓冲区大小,-1表示使用操作系统的默认值。
    7.序列化器
        序列化器需要找到schema,除了将schema添加到每个消息内之外推荐schema注册表来实现。
    8.分区
        消息的对象ProducerRecord,包含topic(主题),key(键),值(value)。kafka消息为键值对结构,ProducerRecord可以只包含topic和value,键默认为null。键的用途:消息的附加信息,决定消息被写到主题的分区位置。拥有相同键的消息被写到同一个分区。
        1.键为null,默认分区器:随机发送到topic内可用分区上,分区器使用轮询(Round Robin)算法均衡到各个分区上。
        2.键不为空,默认分区器:散列,映射特定分区,不建议增加新分区

二 第四章消费者
    1.消费者,消费者群组
        kafka消费者从属于消费者群组,一个群组里的消费者订阅同一个主题,每个消费者接收主题一部分分区消息。
        消费者数量超过分区数量,消费者群组会有闲置。群组总是消费所有消息,群组之间互不影响。
    2.消费者群组 分区再均衡
        消费者群组发生变动或分区情况发送变动,分区重分配,消费者群组会再均衡,进行分区所有权的转移。
        再均衡期间服务不可用,分配新消费者之前的读取状态会丢失。

        1.再均衡触发时机:消费者通过被指派为群组协调器的broker发送心跳维持群组的从属关系以及分区所有权关系。消费者在轮询消息或提交偏移量时发送心跳,心跳超时会话过期,群组协调器认为消费者失效,触发再均衡。
        2.在均衡情况:一个服务器down掉,停止读取消息,群组协调器会等待几秒钟确认消费者死亡之后触发再均衡。几秒钟内,消费者不会再去读取分区消息。清理消费者时,消费者通知协调器离开,协调器会立即触发一次在均衡。
    3.分区过程
        第一个加入群组的消费者成为群主,从协调器那里取得群组成员列表,负责分配分区,完成后结果返回给协调器,协调器转发所有消费者。每个消费者只能看到自己的分配信息,只有群主知道群组里所有消费者的分配信息。
    4.轮询
        消费者持续轮询向kafka请求数据,否则会被认为已经死亡。
        poll()方法返回一个记录列表,记录包括topic,partition,offset,kv,方法入参为超时时间。
        consumer.close()方法关闭消费者,会关闭网络连接和socket,立即触发一次再均衡。
        第一次调用poll()方法会查找群组协调器,加入群组,接受分配的分区,如果触发了再均衡,会在poll()方法内进行。
        心跳在轮询中发出。
    5.消费者配置
        1.fetch.min.bytes 消费者获取记录的最小字节数。broker会等到byte数达到时返回。
        2.fetch.max.wait.ms
        broker最大等待时间。与fetch.min.bytes匹配,指定broker等待时间,默认500ms,都是broker返回消费者请求的边界条件。
        3.max.partition.fetch.bytes
        指定服务器从每个分区里返回给消费者的最大字节数,在消费者对应多个分区的情况下生效。这个值必须大于broker能接收的最大消息的字节数(max.message.size),不然无法读取消息,导致挂起重试。
        需要考虑消费者处理数据的时间,poll()返回数据过多,导致下一次轮询过慢。
        4.session.timeout.ms
        会话超时时间,超时会被群组协调器判定死亡,触发再均衡,配合属性heartbeat.interval.ms(心跳频率)。
        5.auto.offset.reset
        无偏移量分区和偏移量无效的处理策略,默认值为latest,将从最新的(消费者启动之后生成的记录)记录开始读取。另一个值为earliest,将从起始位置读取分区记录。
        6.enable.auto.commit
        消费者是否自动提交偏移量,默认为true。设为false,自己控制何时提交偏移量可以避免出现重复数据和数据丢失。true的情况下,配置auto.commit.interval.ms控制提交的频率。
        7.patition.assignment.strategy
        分区分配策略。
        Range,主题的若干个连续分区分配给消费者,每个主题独立分配,消费者被分配到的分区数会差多一些。
        RoundRobin,主题的所有分区逐个分配消费者,在消费者订阅相同主题的情况下最多差一个分区。
        8.client.id
        任意字符串。broker用来标识消息,用于日志,度量,配额。
        9.max.poll.records
        指定单词call()方法能够返回的记录数量,可以控制在轮询里需要处理的数据量。
        10.receive.buffer.bytes send.buffer.bytes
        socket读写数据的TCP缓冲区设置,-1使用操作系统默认值。
    6.提交和偏移量
        poll()方法会返回生产者写入到kafka未被消费者读取过的记录。
        更新分区当前位置的操作叫做提交。消费者往_consumer_offset的特殊主题发送消息,消息里包含每个分区的偏移量。如果消费者一直运行状态,那么偏移量就没有用处。当消费者崩溃或者有新的消费者加入群组,就会触发再均衡,消费者被分配到新分区需要读取偏移量,然后继续处理。
        1.提交的偏移量小于客户端处理的最后一个消息的偏移量,会从提交的小的偏移量开始重复处理。
        2.提交的偏移量大于客户端处理的最后一个消息的偏移量,两个偏移量之间的消息会丢失。
        3.偏移量提交
            1.自动提交。
                enable.auto.commit=true,每过5s消费者会自动把poll()方法接收到的最大偏移量提交上去。配合参数auto.commit.interval.ms控制。
                自动提交在轮询里进行,由轮询时进行检查。
                再均衡发生后,消费者会重新到上一次提交的位置开始处理,不可避免的会处理重复消息,即使修改提交时间间隔也无法避免。
            2.提交当前偏移量
                消费者api手动提交当前偏移量,auto.commit.offset=false。
                commitSync()方法提交偏移量,会提交poll()方法返回的最新偏移量,提交成功后马上返回,持续重试,失败则抛出异常。
            3.异步提交
                broker对请求回应之前,程序会阻塞,限制消费者吞吐量。可以降低提交频率提高吞吐量,发生再均衡会增加重复消息的数量。
                commitAsync()不会进行重试,支持回调。
                重试异步提交:维护使用一个单调递增序列号维护异步提交的顺序,每次提交偏移量之后或回调函数中更新序列号。在进行重试前,先检查回调的序号和即将提交的偏移量是否相等,若相等则没有新的提交,可以重试,如果序列号比较大,说明要重试的偏移量已成功处理过,无需重试。
            4.同步异步组合提交
                关闭消费者和再均衡前的最后一次提交,需要确保成功。
                普通消费使用异步提交,关闭消费者使用commitSync();
            5.提交特定的偏移量
                在消费过程中阶段手动提交。
    7.再均衡监听器
        消费者退出和进行再均衡时会进行清理工作,在消费者失去分区所有权前提交最后一个已处理记录的偏移量。消费者准备了一个缓冲区用于处理偶发事件,在失去分区所有权之前,需要处理在缓冲区积累下来的数据,关闭文件句柄数据库连接等。
        添加再均衡监听器,subscribe()传入一个ConsumerRebalanceListener实例,会在为消费者分配新分区或移除旧分区时调用监听器。
        实现方法:
            1.void onPartitionsRevoked(Collections<TopicPartition> patitions);会在再均衡之前和消费者停止读取消息之后被调用。如果在这里提交偏移量,下一个接管分区的消费者,就可以找到正确的处理位置。
            2.void onPartitionsAssigned(Collections<TopicPartition> patitions);方法会在重新分配分区和消费者开始读取消息前调用。
    8.从特定偏移量处开始处理
        seekToBeginning(Collections<TopicPartition> patitions);
        seekToEnd(Collections<TopicPartition> patitions);
        seek();
    9.优雅地退出
        非消费者线程调用consumer.wakeup()。这个方法是唯一一个可以从其他线程里安全调用消费者内的方法。退出poll(),抛出WakeupException异常,如果不在poll()方法内,会在下次调用时抛出。consumer.close()提交数据,向群组协调器发送消息,触发再均衡。
    10.反序列化器
    11.独立消费者-为什么以及怎样使用没有群组的消费者
        consumer.assign();
        consumer.partitionsFor()检查是否有新分区加入。

三 第五章深入Kafka
    话题:1Kafka如何复制 2Kafka如何处理来自生产者和消费者的请求 3Kafka的存储细节,如文件格式和索引
    1.控制器
        控制器是一个broker,还负责分区首领的选举。集群第一个启动的broker在Zookeeper里创建一个临时节点/controller让自己成为控制器。其他broker在启动尝试创建时会收到节点存在的异常。其他broker节点在控制器节点上创建Zookeeper watch对象,可以收到节点的变更通知。确保集群一次只有一个控制器存在,控制器不等于首领节点。
        控制器被关闭或与Zookeeper断开连接,Zookeeper上的临时节点就会消失。集群里其他broker通过watch对象得到控制器的节点消失通知。第一个在Zookeeper里成功创建控制器节点的broker就会成为新的控制器,其他尝试节点收到异常。然后在新的控制器节点上再次创建watch对象。每个控制器通过zookeeper的条件递增操作获得一个全新的数值更大的controller epoch。其他broker在知道当前controller epoch后,如果收到旧的epoch消息,就会忽略。
        当控制器发现broker离开集群,如果有的分区首领在这个离开的broker上,需要为失去首领的分区确立新首领。控制器遍历确立新的首领分区(分区副本列表里的下一个副本),向所有包含新首领或现有跟随者的broker发送请求。请求包含了谁是新首领以及分区跟随者的信息。
        控制器发现一个broker加入集群,会使用brokerID检查新加入的broker是否包含现有分区的副本。如果有,控制器就把变更通知发送给新加入的broker和其他broker,新broker上的副本开始从首领那里复制消息。
        Kafka使用Zookeeper的临时节点选举控制器,在节点加入集群或退出集群时通知控制器。控制器负责在节点加入或者离开集群时进行分区首领选举。控制器使用epoch来避免脑裂。脑裂是指两个节点同时认为自己是当前的控制器。
    2.复制
        复制功能是Kafka架构的核心。
        Kafka使用主题组织数据,每个主题分为若干个分区,每个分区有多个副本,副本保存在broker上,每个broker可以保存上千个属于不同主题和分区的副本。
        副本类型:
        1.首领副本 每个分区都有一个首领副本。保证一致性,所有生产者请求和消费者请求都会经过这个副本。
        2.跟随者副本 首领以外的副本都是跟随者副本。跟随者副本不处理来自客户端的请求,只从首领副本复制消息,保持与首领一致的状态。首领发生崩溃,其中一个提升为新首领。
        首领失效时,只有同步副本才能被选为新首领。
        除了当前首领之外,每个分区都有一个首选首领,创建主题时选定的首领就是分区的首选首领。原因是创建分区时,需要broker之间均衡首领(broker间分布副本和首领的算法)。预期在首选首领成为真正的首领时,broker间的负载最终会得到均衡。默认情况下,auto.leader.rebalance.enable=true,会检查首选首领是不是当前首领,不是,且该副本为同步的,会触发首领选举,首选首领成为当前首领。
    3.处理请求
        broker处理客户端、分区副本、控制器发送给分区首领的请求。Kafka有二进制协议(TCP),指定请求消息的格式以及broker如何对请求作出响应,包括成功处理请求或在处理请求过程中遇到错误。broker有序消费请求。
        请求消息包含一个标准消息头:
            1.Request type(API key);
            2.Request version (broker 可以处理不同版本的客户端请求,并根据客户端版本作出不同的响应)
            3.Correlation ID 一个具有唯一性的数字,用于标识请求消息,同时会出现在响应消息和错误日志中
            4.ClientID 标识发送请求的客户端
        broker会在监听的端口上面运行一个Acceptor线程,这个线程创建连接并交给Processor线程去处理。Processor线程的数量是可配置的,网络线程负责从客户端获取请求消息,把他们放进请求队列,然后从响应队列获取响应消息,把他们发送给客户端。
        请求:生产请求和获取请求
            发送给分区的首领副本,若针对特定分区的请求而首领副本在另一个broker上,那么请求的客户端会收到一个非分区首领的错误。
        元数据请求,包含客户端感兴趣的主题列表,服务端响应信息就会指明主题包含的分区,副本及首领信息。元数据请求可以发送给任意一个broker,所有broker都缓存了信息。
            客户端刷新元数据metadata.max.age.ms间隔配置。
        1.生产请求
            broker对生产请求的验证
                1.发送数据的用户是否有主题写入权限
                2.请求里包含的acks值是否有效
                3.acks=all,是否足够多的同步副本保证消息被安全写入
            消息写入到文件系统缓存中,不保证何时会刷新到磁盘上。
            响应:写入分区首领后,检查acks参数,如果为0或1,那么broker立即返回响应;如果acks为all,保留请求到炼狱缓冲区里,首领发现全部复制消息之后才会返回。
        2.获取请求
            客户端发送请求到broker,请求的是主题分区里具有特定偏移量的消息。客户端限制broker返回的数据最大条数。
            Kafka使用零复制技术向客户端发送消息,Kafka直接把消息从文件(Linux文件系统缓存)里发送到网络通道,不经过任何中间缓冲区。
            影响消息到达消费者的原因可以有Kafka自身的分区复制问题,配置参数为replica.lag.time.max.ms设置副本在复制消息时可被允许的最大延迟时间。
        3.其他请求
    4.物理存储
        Kafka的基本存储单元是分区。
        存储分区的目录清单,log.dirs
        1.分区分配
            均匀分配,首领副本和跟随者副本不在同一个broker上,若制定了机架信息则也会尽可能将分区副本分配到不同机架的broker上面。
        2.文件管理
            保留数据是Kafka的一个基本特性,Kafka不会一直保留数据,也不会等到所有消费者都读取了消息之后才删除消息。每个主题配置了数据保留期限,规定数据删除之前可以保留多长时间,或清理数据之前可以保留的数据量大小。
            分区分为了若干个片段。默认情况下,每个片段包含1GB或一周的数据。在broker往分区写入数据时,如果达到了片段上限,就关闭当前文件,并打开一个新文件。
            活跃片段不会被删除。
            broker会为分区里的每一个片段打开一个文件句柄。
        3.文件格式
            保持在磁盘上面的数据格式和从生产者发送的或发送到消费者的消息格式是一致的。零复制技术给消费者发送消息,同时避免了对生产者已经压缩过的消息进行解压和再压缩。
            如果是压缩过的消息,同批次消息会被压缩在一起。
        4.索引
            broker-分区-片段,为了快速定位偏移量数据,Kafka为每个分区维护了一个索引。索引把偏移量映射到片段文件和偏移量在文件里的位置。
            Kafka不维护索引的校验和,索引损坏Kafka会通过重读消息重新生成索引。删除索引是绝对安全的。
        5.清理
            Kafka根据设置的时间删除数据,超时的旧数据删除掉。可以为改变主题的保留策略来满足各种使用场景。主题若包含null键,清理就会失效。
        6.清理的原理
            日志片段分为两个部分:
                1.干净的部分指被清理过,每个键只有一个对应的值,值是上一次清理保留下来的。
                2.污浊的部分,在上一次消息清理之后写入的。
            如果Kafka启动清理功能,log.cleaner.enabled参数,每个broker会启动一个清理管理线程和多个清理线程。
            清理过程:
                1.创建map,必须存在一个可以读完所有污浊消息的map大小可以创建,不然会报错。
                2.首先map存放所有的污浊的消息,每个键值对占用24b。
                3.然后从干净片段读取消息,从最旧的消息开始,进行比对。若消息键值不存在,则复制到替换片段上。存在则会忽略。(时效性来说干净的片段是老消息段,污浊的片段是新消息段)。
                4.完成上述复制之后,将替换片段和原始片段进行交换。
        7.被删除事件
            为彻底删除一个键,需要发送一个该键的消息且值为null(墓碑消息)。当清理线程发现了该消息时,会先进行常规清理,只保留为null的消息。该消息会被保留一段时间,消费者可以看到这个消息并清理自身,之后kafka会清除掉这个消息。
        8.何时清理主题
            delete策略不会删除当前活跃片段,compact策略不会清理当前片段,旧片段里面的消息会被清理。
            0.10.0及之前的版本,Kafka会在包含脏记录的主题数量达到50%时进行清理。
四 第六章可靠数据传递
    1.可靠性保证
        Kafka保证分区消息的顺序;
        消息被写入所有的同步副本时(非磁盘),被认为已提交;
        只要还有一个副本是活跃的,已提交的消息就不会丢失;
        消费者只能读取已提交的消息。
    2.复制
        Kafka的复制机制和分区的多副本架构是Kafka可靠性保证的核心。
        1.主题被分为多个分区,分区是基本的数据块。分区存储在单个磁盘上,Kafka可以保证分区里的事件是有序的,分区可以在线也可以离线。每个分区有多个副本,只有一个副本是首领。所有的事件都可以直接发送给首领副本,或者直接从首领副本读取事件。其他副本只需要与首领保持同步,并复制最新的事件。当首领副本不可用时,同步副本会成为新首领。
            同步副本的判定:
                1.与Zookeeper之间有一个活跃会话,在6s内发送过心跳。
                2.在10s内从首领副本获取过消息。
                3.在10s内从首领副本获取过最新消息。
            多个副本在同步和非同步状态之间快速切换,说明集群内部出现了问题,通常是Java不合适垃圾回收导致的停顿引发的。
    3.broker配置
        1.复制系数 replication.factor default.replication.refactor
            n 每个分区会被n个不同的broker复制n次;
        2.不完全的首领选举
            unclean.leader.election broker级别集群范围配置 默认true
            完全选举:被选为新首领的同步副本有所有提交的数据,选举是完全的。
            当首领不可用时,其他副本不同步两种情景:
                1.3个副本,2个跟随者不可用。然后首领副本会确认提交所有的消息,然后首领副本崩溃,跟随者启动,分区唯一不同步副本。
                2.3个副本,两个跟随者不同步,消息确认慢于首领副本,首领副本作为唯一的同步副本接收消息,首领不可用,另外的跟随者也无法同步。
            方案:1.不允许不同步副本成为新首领副本,等旧首领副本恢复。
                2.允许不同步副本成为新首领副本,新旧首领偏移量冲突,导致消费者读取到不同的消息,数据不一致,数据丢失。
        3.最少同步副本
            min.insync.replicas
            当达不到最小同步副本数量时,broker会停止接受生产者请求
    4.可靠系统
        1.发送确认,配置acks
        2.生产者重试,异常处理
    5.可靠系统消费者
        消费者配置 groupid auto.offset.reset enable.auto.commit auto.commit.interval.ms
        显式提交偏移量
            1.处理完成后提交偏移量
            2.提交频度是性能和重复消费数量之间的权衡
            3.提交的偏移量业务意义
            4.再均衡
            5.消费者重试
                1.#30失败,提交#31会提交#30.
                    可重试错误,提交最后一次处理成功的偏移量,consumer.pause(),保持轮询的同时尝试重新处理。comsumer.resume()恢复。
                2.写入独立主题,单独进行重试,类似死信队列。
            6.消费者可能需要维护状态
                消费者内部状态维护,Kafka没提供事务支持
            7.长时间处理
                暂停消费者,保持轮询,另外的工作线程进行处理
            8.仅一次传递
                唯一键,幂等性写入。外部支持事务的系统进行手动修正偏移量。
    6.6验证系统可靠性
        1.配置验证
            VerifiableProducer VerifiableConsumer
        2.应用程序验证
            自定义的错误处理,偏移量提交方式,在均衡监听器,其他和kafka客户端相关的地方
        3.生产环境监控可靠性
            Kafka的java客户端自带JMX度量指标,生产者是消息的error-rate,retry-rate.消费者是consumer-lag,指明了消费者的处理速度与最近提交的偏移量的差距。
五 第七章构建数据管道
    使用Kafka构建数据管道,1Kafka作为数据管道的两个端点之一2.Kafka作为数据管道两个端点的中间媒介。
    价值:作为各个数据段之间的大型缓冲区,解耦管道数据的生产者和消费者。解耦能力,安全和效率的可靠性。
    1.数据管道需求
        1.及时性 kafka作用是大型缓冲区
        2.可靠性 至少一次传递 仅一次传递
        3.高吞吐量和动态吞吐量 高吞吐量的分布式系统
        4.数据格式
        5.转换
        6.安全性 支持加密传输
        7.故障处理能力 Kafka会长时间保留数据
        8.耦合性和灵活性
    2.ConnectAPI
        1.连接器和任务
        2.work进程 容器。
        3.转化器和Connect的数据模型
        4.偏移量管理,消息确认管理 与Kafka实现的生产者消费者客户端框架类似
六 第八章跨集群数据镜像
    集群间的数据复制
    1.使用场景
        1.区域集群和中心集群
        2.冗余
        3.云迁移
    2.多集群架构
        1.跨数据中心通信
            高延迟,有限带宽,高成本,
        2.Hub和Spoke架构 一个中心Kafka和多个本地Kafka
        3.双活架构
        4.主备架构
        5.延展集群