Kafka学习

·  阅读 199

1.术语

1.broker

broker是Kafka的实例,每个服务器可以有一个或者多个Kafka实例,Kafka集群内的broker有不同的编号

2.topic与消息

Kafka将所有的消息组织成多个topic的形式存储,而每个topic又可以被拆分成多个partition,每个partition又是由一个一个的消息组成,每个消息都被标识了一个递增的序列号代表其进来的先后顺序,并且按照顺序存储在partition中。

3.partition

每个topic划分为一个或多个partition,提前划分合适的partition有利于后续Kafka集群的扩容和提高并发消费能力,且Kafka保证了partition内消息有序性

4.Replication

每一个分区都有多个副本,副本的作用是做备胎。当主分区(Leader)故障的时候会选择一个备胎(Follower)上位,成为Leader。在kafka中默认副本的最大数量是10个,且副本的数量不能大于Broker的数量,follower和leader绝对是在不同的机器,同一机器对同一个分区也只可能存放一个副本(包括自己)

4.producer

生产者生产消息需要以下几个必要参数,指定往哪个topic生产消息,如何根据key将消息分区到不同的partition中,消息的具体内容

5.consumer

每个consumer将自己标记为某一个consumer group,之后系统会将consumer按group分组,将消息发送给所有的分组,每个分组只有一个consumer能消费这条消息。

一般消息系统中consumer存在两种消费模型,push: 优势在于消息实时性高,但是没有考虑consumer消费能力和饱和度,容易出现producer压垮consumer;pull: 优势在于可以控制消费速度,保证consumer不会出现过饱和,但是在没有数据时会出现空轮训,消耗cpu

6.消息发送的语义

  1. producer角度

    1. 消息最多发送一次,producer异步发送消息,或者同步发消息但重试次数为0
    2. 消息至少发送一次,producer同步发送消息,失败、超时都会出发重试
    3. 消息发且仅发一次
  2. consumer角度

    1. 消息最多消费一次,consumer先读取消息,再确认position,最后处理消息
    2. 消息至少消费一次,consumer先读取消息,再处理消息,最后确认position
    3. 消息消费且消费一次

7.Kafka特性

1.可用性

在Kafka中,正常情况下所有node处于同步中状态,当某个node处于非同步中状态,也就意味着整个系统出问题,需要容错处理。同步中代表了:该node与zookeeper能连通;该node如果是follower,那么consumer position与leader不能差距太大。某个分区内同步中的node组成一个集合,即该分区的ISR(In-Sync Replicas)。

Kafka通过两个手段容错:数据备份,以partition为单位备份,副本数可设置,当副本数为N时,代表一个leader,N-1个followers,followers可以视为leader的consumer,拉取leader的消息,append到自己的系统中;failover,当leader处于非同步中时,系统从ISR中选取新leader,当某个follower处于非同步中时,leader会将此follower剔除ISR,当此follower恢复并完成数据同步后再次进入ISR。当producer生产消息时,只有当消息被所有的ISR确认时,才表示该消息成功提交,只有提交成功的消息才能被consumer消费。

脏leader选举。假设N个副本全挂了,node恢复后面临同步数据的过程,这期间ISR中没有node,会导致该分区服务不可用,Kafka采用一种降级措施来处理,选举第一个恢复的node作为leader提供服务,以他的数据为基准

2.一致性

上面的方案保证的数据的可用性,有时高可用是以牺牲一定的一致性为代价,如果希望达到强一致性,可以采取如下措施:

禁用脏leader选举

设置最小ISR数量min_isr

3.持久性

Kafka依赖磁盘而非内存。

顺序读,数据结构选取queue,操作只有根据offset读和append,基于queue时间复杂度只有O(1),

2.Kafka-Producer

参考 blog.csdn.net/qq_28410283…

  1. 参数

    1. bootstrap.servers,用于找到Kafka集群

      该参数指定了一组host:port 对,用于创建向 Kafka broker 服务器的连接,比如:kl:9092,k2:9092,k3:9092。

      如果 Kafka 集群中机器数很多,那么只需要指定部分 broker 即可,不需要列出所有的机器。因为不管指定几台机器,producer 都会通过该参数找到井发现集群中所有的 broker;为该参数指定多台机器只是为了故障转移使用。这样即使某一台 broker 挂掉了,producer 重启后依然可以通过该参数指定的其他 broker 连入 Kafka 集群。

    2. key.serializer、value.serialize ,序列化数据 被发送到 broker 端的任何消息的格式都必须是字节数组,因此消息的各个组件必须首先做序列化,然后才能发送到 broker。该参数就是为消息的 key 做序列化之用的。这个参数指定的是实现org.apache.kafka.common.serialization.Serializer接口的类的全限定名称。

    3. acks,保证消息持久性

      acks 参数用于控制 producer 生产消息的持久性(durability);对于 producer 而言, Kafka在乎的是“己提交”消息的持久性。一旦消息被成功提交,那么只要有任何一个保存了该消息的副本“存活”,这条消息就会被视为“不会丢失的” 。acks 指定了在给 producer 发送响应前, leader broker 必须要确保己成功写入该消息的副本数 。 当前 acks 有 3 个取值: 0、 1和 all 。

    4. buffer.memory,缓存待发送数据

      该参数指定了 producer 端用于缓存消息的缓冲区大小,单位是字节,默认值是 33554432,即 32MB 。由于采用了异步发送消息的设计架构, Java 版本 producer 启动时会首先创建一块内存缓冲区用于保存待发送的消息,然后由另 一个专属线程负责从缓冲区中读取消息执行真正的发送。这部分内存空间的大小即是由 buffer.memory 参数指定的。若 producer 向缓冲区写消息的速度超过了专属 I/0 线程发送消息的速度,那么必然造成该缓冲区空间的不断增大。此时 producer 会停止手头的工作等待 I/0 线程追上来,若一段时间之后 I/0 线程还是无法追上 producer 的进度, 就会抛出异常;若 producer 程序要给很多分区发送消息,那么就需要仔细地设置这个参数,以防止过小的内存缓冲区降低了producer 程序整体的吞吐量。

    5. compression.type,如何压缩数据

      设置 producer 端是否压缩消息,默认值是 none ,即不压缩消息 。Kafka 的 producer 端引入压缩后可以显著地降低网络 I/O 传输开销从而提升整体吞吐量,但也会增加 producer 端机器的 CPU 开销。另外,如果 broker 端的压缩参数设置得与 producer 不同, broker 端在写入消息时也会额外使用 CPU 资源对消息进行对应的解压缩-重新压缩操作。目前 Kafka 支持 3 种压缩算法:GZIP、Snappy 和 LZ4。根据实际使用经验来看 producer 结合 LZ4 的性能是最好; LZ4 > Snappy > GZIP;

    6. retries,重试次数

      Kafka broker 在处理写入请求时可能因为瞬时的故障(比如瞬时的leader选举或者网络抖动)导致消息发送失败。这种故障通常都是可以自行恢复的,如果把这些错误封装进回调函数的异常中返还给 producer,producer程序也并没有太多可以做的,只能简单地在回调函数中重新尝试发送消息。与其这样,还不如 producer 内部自动实现重试。因此 Java 版本 producer 在内部自动实现了重试,当然前提就是要设置retries参数。

    7. batch.size,批量发送大小

      producer 会将发往同一分区的多条消息封装进一个 batch中,当 batch 满了的时候, producer 会发送 batch 中的所有消息 。不过, producer并不总是等待batch满了才发送消息,很有可能当batch还有很多空闲空间时 producer 就发送该 batch 。显然,batch 的大小就显得非常重要 。通常来说,一个小的 batch 中包含的消息数很少,因而一次发送请求能够写入的消息数也很少,所以 producer 的吞吐量会很低;一个 batch 非常之巨大,那么会给内存使用带来极大的压力,因为不管是否能够填满,producer 都会为该batch 分配固定大小的内存。因此batch.size 参数的设置其实是一种时间与空间权衡的体现 。batch.size 参数默认值是 16384 ,即 16KB 。这其实是一个非常保守的数字。 在实际使用过程中合理地增加该参数值,通常都会发现 producer 的吞吐量得到了相应的增加 。

    8. linger.ms,延迟发送 批量发送时满足batch.size和linger.ms之一,producer便开始发送消息。

  2. 自定义partition选择器

3.Kafka-Consumer

参考blog.csdn.net/qq_28410283…

  1. 参数

    1. bootstrap.servers 用于找到Kafka集群

      和 Java 版本 producer 相同

    2. group.id 分组管理consumer

      该参数指定的是 consumer group 的名字,同一个消费组内消息只被消费一次

    3. key.deserializer,value.deserializer 反序列化数据

      反序列化数据

    4. session.timeout.ms 协调者检测成员崩溃所需要的时间

      consumer group 检测组内成员发送崩溃的时间,假设你设置该参数为 5 分钟,那么当某个 group 成员突然崩攒了(比如被 kill -9 或岩机), 管理 group 的 Kafka 组件(即消费者组协调者,也称 group coordinator)有可能需要 5 分钟才能感知到这个崩溃。显然我们想要缩短这个时间,让coordinator 能够更快地检测到 consumer 失败 ​ consumer 消息处理逻辑的最大时间,倘若 consumer 两次 poll 之间的间隔超过了该参数所设置的阑值,那么 coordinator 就会认为这个 consumer 己经追不上组内其他成员的消费进度了,因此会将该 consumer 实例“踢出”组,该 consumer 负责的分区也会被分配给其他 consumer

      两种情况都会导致rebalance,第二种情况是多余的。

      在0 .10.1.0 版本及以后的版本中, session.timeout.ms 参数被明确为“ coordinator 检测失败的时间” 。

    5. max.poll.interval.ms 消息处理最大时间

      session. neout.ms 中“ consumer 处理逻辑最大时间”的含义被剥离出来了,max.poll.interval.ms就承担此职责

    6. auto.offset. reset offset非法时的兜底策略

      指定了无位移信息或位移越界(即 consumer 要消费的消息的位移不在当前消息日志的合理区间范围)时 Kafka 的应对策略 。 特别要注意这里的无位移信息或位移越界,只有满足这两个条件中的任何一个时该参数才有效果 。

      • earliest:指定从最早的位移开始消费 。 注意这里最早的位移不一定就是 0 。
      • latest:指定从最新处位移开始消费 。
      • none :指定如果未发现位移信息或位移越界,则抛出异常。笔者在实际使用过程中几乎从未见过将该参数设置为 none 的用法,因此该值在真实业务场景中使用甚少。
    7. enable.auto.commit 自动提交位移

      该参数指定 consumer 是否自动提交位移 。若设置为 true,则 consumer 在后台自动提交位移;否则,用户需要手动提交位移。

    8. fetch.max.bytes

      一次拉取消息最大字节数

    9. max.poll.records

      一次拉取最大消息数

    10. heartbeat.interval.ms 帮助consumer group组内成员快速感知其他节点的情况,尽快开始rebalance

      这里的关键在于要搞清楚 consumer group 的其他成员,如何得知要开启新一轮 rebalance;当 coordinator 决定开启新一轮 rebalance 时,它会将这个决定以 REBALANCE_IN_PROGRESS 异常的形式“塞进” consumer 心跳请求的 response 中,这样其他成员拿到 response 后才能知道它需要重新加入 group。显然这个过程越快越好,而heartbeat. interval.ms 就是用来做这件事情的 。

    11. connections.max.idle.ms 连接空闲时间

      连接最大空闲时间,socket连接超过这个时间后就会关闭该socket,如果不介意socket开销,可以设置为-1,永远不关闭连接

  2. @KafkaListener原理

    1. KafkaListenerAnnotationBeanPostProcessor负责处理该注解,继承关系如下

      //该后置处理器在bean初始化前后调用,
      //KafkaListenerAnnotationBeanPostProcessor实现postProcessAfterInitialization,将使用该注解的方法注册到 //KafkaListenerEndpointRegistrar registrar 中
      public interface BeanPostProcessor {
          @Nullable
          default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
              return bean;
          }
      ​
          @Nullable
          default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
              return bean;
          }
      }
      //在KafkaListenerAnnotationBeanPostProcessor实现afterSingletonsInstantiated,初始化MessageListenerContainer
      public interface SmartInitializingSingleton {
        void afterSingletonsInstantiated();
      ​
      }
      public class KafkaListenerAnnotationBeanPostProcessor<K, V>
            implements BeanPostProcessor, Ordered, BeanFactoryAware, SmartInitializingSingleton{
      }
      复制代码
    2. MessageListenerContainer间接继承Lifecycle,doStart()方法在初始化完成后被调用,ListenerConsumer循环调用Consumer的poll方法获取消息,然后调用使用Listener注解标注的方法,处理业务逻辑

      public class KafkaMessageListenerContainer<K, V> // NOSONAR line count
            extends AbstractMessageListenerContainer<K, V> {
        @Override
        protected void doStart() {
          if (isRunning()) {
            return;
          }
          if (this.clientIdSuffix == null) { // stand-alone container
            checkTopics();
          }
          ContainerProperties containerProperties = getContainerProperties();
          checkAckMode(containerProperties);
      ​
          Object messageListener = containerProperties.getMessageListener();
          if (containerProperties.getConsumerTaskExecutor() == null) {
            SimpleAsyncTaskExecutor consumerExecutor = new SimpleAsyncTaskExecutor(
                (getBeanName() == null ? "" : getBeanName()) + "-C-");
            containerProperties.setConsumerTaskExecutor(consumerExecutor);
          }
          GenericMessageListener<?> listener = (GenericMessageListener<?>) messageListener;
          ListenerType listenerType = determineListenerType(listener);
          this.listenerConsumer = new ListenerConsumer(listener, listenerType);
          setRunning(true);
          this.startLatch = new CountDownLatch(1);
          this.listenerConsumerFuture = containerProperties
              .getConsumerTaskExecutor()
              .submitListenable(this.listenerConsumer);
          try {
            if (!this.startLatch.await(containerProperties.getConsumerStartTimout().toMillis(), TimeUnit.MILLISECONDS)) {
              this.logger.error("Consumer thread failed to start - does the configured task executor "
                  + "have enough threads to support all containers and concurrency?");
              publishConsumerFailedToStart();
            }
          }
          catch (@SuppressWarnings(UNUSED) InterruptedException e) {
            Thread.currentThread().interrupt();
          }
        }
      }
      复制代码

4.Kafka服务端原理参数分析

参考 blog.csdn.net/Dream_Flyin…

1.参数分析

  1. broker.id

    kafka使用唯一的一个整数来标识broker。该参数默认值是-1,如果不指定,kafka会自动生成一个唯一值。

  2. log.dirs

    非常重要的参数,kafka持久化消息的目录,该参数可以设置多个目录,用逗号分隔,这样kafka会将负载均匀地分配到多个目录下。如果每个目录都在不同的磁盘上,那么还能提升整体写消息的吞吐量。默认情况下是/tmp/kafka-logs。

  3. zookeeper.connect

    此参数没有默认值,必须指定,该参数可以是一个csv列表,如果使用一套zookeeper管理多个kafka集群,则zookeeper的chroot必须指定。

  4. listeners,advertised.listeners

    listeners是broker监听的csv列表,格式是[协议]://[主机名]:[端口],[协议]://[主机名]:[端口]。该参数用于客户端连接broker使用。如果不指定,默认绑定网卡;如果主机名是0.0.0.0,则表示绑定所有网卡。Kafka当前支持的协议类型包括:PLAINTEXT、SSL以及SASL_SSL等。advertised.listeners和listeners类似,该参数也是用于发布给client的监听器,不过该参数主要用于IaaS环境,比如云上的机器通常都配有多块网卡(私网网卡和公网网卡)。对于这种机器,用户可以设置该参数绑定公网IP供外部client使用。在公司内网部署 kafka 集群只需要用到 listeners,内外网需要作区分时 才需要用到advertised.listeners。

  5. unclean.leader.election.enable

    是否开启unclean leader选举。

  6. delete.topic.enable

    是否允许kafka删除topic。默认情况下,Kafka集群允许用户删除topic及其数据。

  7. log.retention.{hours|minutes|ms}

    这组参数控制了消息数据的存留时间。如果同时设置,优先选取ms的设置,minutes次之,hours最后。Kafka会根据日志文件的最后修改时间(last modified time)进行判断。

  8. log.retention.bytes

    上面的参数是时间维度,这个参数就是空间维度。它控制着Kafka集群需要为每个消息日志保存多大的数据。对于超过该参数的分区日志而言,Kafka会自动清理该分区的过期日志段文件。该参数默认值为-1,表示kafka永远不会根据消息日志文件总大小来删除日志。

  9. min.insync.replicas

    该参数实际是和product的acks参数配合使用的。它指定了broker端必须成功响应client消息发送的最小副本数。如果broker段无法满足,则client的消息并不会被认为是成功的。它与product的acks配置使用可以令kafka集群达到最高等级的消息持久化。

  10. num.network.threads

    控制broker在后台处理来自网络请求的线程数,默认是3。主要处理网络io,读写缓冲区数据,基本没有io等待,配置线程数量建议为cpu核数加1

  11. num.io.threads

    该参数控制broker实际处理网络请求的线程数,默认是8。即Kafka创建8个线程,采用轮询的方式监听转发过来的网络请求并进行实时处理。num.io.threads主要进行磁盘io操作,高峰期可能有些io等待,因此配置需要大些。配置线程数量为cpu核数2倍,最大不超过3倍

  12. message.max.bytes

    Kafka broker能够接受的最大消息数,默认值是977KB。

  13. log.flush.interval.messages=10000

    每当producer写入10000条消息时,刷数据到磁盘

  14. log.flush.interval.ms=1000

    每间隔1秒钟时间,刷数据到磁盘

  15. socket.send.buffer.bytes,socket.receive.buffer.bytes,socket.request.max.bytes

    socket的发送缓冲区; socket的接受缓冲区;socket请求的最大数值,防止serverOOM,message.max.bytes必然要小于socket.request.max.bytes

2.rebalance分析

分类:
后端
标签:
分类:
后端
标签: