高并发高可用之Kafka

目录

  • 消息队列的流派
  • Kafka的安装
  • Kafka基本使用
  • 单播和多播
  • Kafka的主题、分区概念
  • Kafka集群
  • 副本Replication
  • Kafka集群下收发消息
  • 生产者同步与异步发送消息
  • 消费者自动与手动提交offset
  • SpringBoot整合Kafka
  • Kafka集群中的controller, rebalance, HW
  • Kafka中的优化问题
  • 实现延时队列的效果
  • 可视化监控平台Kafka-eagle

消息队列的流派

  1. 有Broker的MQ
    ① 重Topic
    Kafka,RocketMQ(根据Kafka内部原理开发的MQ)
    ② 轻Topic
    RabbitMQ
  2. 无Broker的MQ

Kafka的安装

Kafka安装基于Zookeeper

  1. 安装并部署Zookeeper服务
  2. 安装JDK
  3. 下载Kafka安装包,上传服务器并解压
  4. 修改config目录下的server.properties
broker.id=0  # 集群中必须唯一
port=9092 #端口号 
host.name=localhost #Kafka部署机器的IP
log.dirs=/usr/local/kafka/data/kafka-logs #日志存放路径可修改可不修改
zookeeper.connect=localhost:2181 #zookeeper地址和端口
  1. 进入bin目录,执行命令启动Kafka(指定配置文件)
./kafka-server-start.sh -daemon  ../config/server.properties      #建议使用这种方式,不需要启动多个窗口

Kafka基本使用

  • 创建主题Topic
    通过Kafka命令向zk中创建一个主题:

    bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic demo
    

    查看当前zk中所有主题:

    bin/kafka-topics.sh --list --zookeeper localhost:2181
    
  • 发送消息

    bin/kafka-console-producer.sh --broker-list 192.168.xxx.xxx:9092 --topic demo
    
  • 消费消息

    bin/kafka-console-consumer.sh --bootstrap-server 192.168.xxx.xxx:9092 --topic demo --from-beginning
    

    - -from-beginning:加上表示从头开始消费,不加表示从最后一条消息后准备开始消费。

生产者将消息发送给broker,broker会将消息保存在本地日志文件中:

/usr/local/kafka/data/kafka-logs/主题-分区/00000000.log

单播和多播

  • 单播消息
    如果多个消费者在同一个消费组,那么只有一个消费者可以收到订阅的topic中的消息

    ./kafka-console-consumer.sh --bootstrap-server 192.168.xxx.xxx:9092 --topic demo --from-beginning --consumer-property group.id=testgroup1
    
  • 多播消息
    不同的消费组订阅同一个topic,每个消费组里都会有一个消费者能收到消息

    ./kafka-console-consumer.sh --bootstrap-server 192.168.xxx.xxx:9092 --topic demo --from-beginning --consumer-property group.id=testgroup1
     
    ./kafka-console-consumer.sh --bootstrap-server 192.168.xxx.xxx:9092 --topic demo --from-beginning --consumer-property group.id=testgroup2
    

另外,查看消费组详情命令:

./kafka-consumer-groups.sh --bootstrap-server 192.168.xxx.xxx:9092 --describe --group testgroup1

Kafka的主题、分区概念

  1. 主题Topic
    kafka通过topic将消息进行分类,不同的topic会被订阅该topic的消费者消费

    但是有个问题,如果说这个topic中的消息非常多,多到需要几个T来存,因为消息是保存在log日志文件中的,为了解决这个问题,kafka给出分区解决

  2. 分区Partition
    一个Topic里有多个分区 高并发高可用之Kafka_第1张图片
    创建多分区主题:

    ./kafka-topics.sh --create --zookeeper localhost:2181 --topic test1 --replication-factor 1 --partitions 2
    

好处:
- 可以分区存储大文件
- 提高吞吐量,可以并行进行读写

Kafka默认有一个偏移量consumer_offsets主题,且默认有50个分区。这个主题用来存储消费者的偏移量相关信息。

Kafka集群

搭建3个broker:

  1. 创建并修改各自的配置文件server.properties
    # 分别配置0、1、2
    broker.id=0  # 集群中必须唯一
    # 分别配置9092、9093、9094
    port=9092 #端口号 
    host.name=localhost #Kafka部署机器的IP
    # 分别配置kafka-logs、kafka-logs-1、kafka-logs-2
    log.dirs=/usr/local/kafka/data/kafka-logs #日志存放路径可修改可不修改
    # 集群节点连接同一个zookeeper
    zookeeper.connect=localhost:2181 #zookeeper地址和端口
    
  2. 进入bin目录,分别执行命令启动Kafka(指定配置文件)
    ./kafka-server-start.sh -daemon  ../config/server.properties      #建议使用这种方式,不需要启动多个窗口
    
  3. 查看是否启动成功
    进入zookeeper中/brokers/ids查看节点

副本Replication

在集群中创建1个主题,2个分区,3个副本:

bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 3 --partitions 2 --topic demo

查看集群中主题的详情信息:

bin/kafka-topics.sh --describe --zookeeper localhost:2181 --topic demo

执行命令可以看出分区0的Leader是broker2,分区1的Leader是broker0:
Isr:可以同步和已经同步的节点会存入Isr集合。但如果Isr中的节点性能较差,会被踢出Isr集合。
高并发高可用之Kafka_第2张图片
Leader
kafka的写和读操作都发生在leader上,leader负责把数据同步给follower,当leader挂了,经过主从选举,从follower中选举产生一个新的leader

Follower
接受leader同步的数据

Kafka集群下收发消息

为了保证消费消息的循序性,一个Partition只能被一个消费组里的某一个消费者消费,但一个消费者可以同时消费多个Partition。高并发高可用之Kafka_第3张图片

  • 集群发送消息

    bin/kafka-console-producer.sh --broker-list 192.168.xxx.xxx:9092,192.168.xxx.xxx:9093,192.168.xxx.xxx:9094 --topic demo
    
  • 集群消费消息

    bin/kafka-console-consumer.sh --bootstrap-server 192.168.xxx.xxx:9092,192.168.xxx.xxx:9093,192.168.xxx.xxx:9094 --topic demo --from-beginning --consumer-property group.id=testgroup1
    

生产者同步与异步发送消息

生产者向kafka发送消息有同步和异步两种方式,此阶段异步容易丢消息且性能提升不明显,故常用同步方式
同步方式需要等待kafka发送成功的ack,关于kafka返回ack的时机有三个配置:
在这里插入图片描述

消费者自动与手动提交offset

自动提交:消费者把消息poll下来以后自动直接提交offset
手动提交:在消费消息后在手动提交offset

SpringBoot整合Kafka

  1. 引入依赖

     <dependency>
         <groupId>org.springframework.kafkagroupId>
         <artifactId>spring-kafkaartifactId>
     dependency>
    
  2. 编写springboot配置文件

    spring:
      application:
        name: hello-kafka
      kafka:
        listener:
          #设置是否批量消费,默认 single(单条),batch(批量)
          type: single
        # 集群地址
        bootstrap-servers: 127.0.0.1:9092,127.0.0.1:9093,127.0.0.1:9094
        # 生产者配置
        producer:
          # 重试次数
          retries: 3
          # 应答级别
          # acks=0 把消息发送到kafka就认为发送成功
          # acks=1 把消息发送到kafka leader分区,并且写入磁盘就认为发送成功
          # acks=all 把消息发送到kafka leader分区,并且leader分区的副本follower对消息进行了同步就任务发送成功
          acks: all
          # 批量处理的最大大小 单位 byte
          batch-size: 4096
          # 发送延时,当生产端积累的消息达到batch-size或接收到消息linger.ms后,生产者就会将消息提交给kafka
          buffer-memory: 33554432
          # 客户端ID
          client-id: hello-kafka
          # Key 序列化类
          key-serializer: org.apache.kafka.common.serialization.StringSerializer
          # Value 序列化类
          value-serializer: org.apache.kafka.common.serialization.StringSerializer
          # 消息压缩:none、lz4、gzip、snappy,默认为 none。
          compression-type: gzip
          properties:
            partitioner:
              #指定自定义分区器
              class: top.zysite.hello.kafka.partitioner.MyPartitioner
            linger:
              # 发送延时,当生产端积累的消息达到batch-size或接收到消息linger.ms后,生产者就会将消息提交给kafka
              ms: 1000
            max:
              block:
                # KafkaProducer.send() 和 partitionsFor() 方法的最长阻塞时间 单位 ms
                ms: 6000
        # 消费者配置
        consumer:
          # 默认消费者组
          group-id: testGroup
          # 自动提交 offset 默认 true
          enable-auto-commit: false
          # 自动提交的频率 单位 ms
          auto-commit-interval: 1000
          # 批量消费最大数量
          max-poll-records: 100
          # Key 反序列化类
          key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
          # Value 反序列化类
          value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
          # 当kafka中没有初始offset或offset超出范围时将自动重置offset
          # earliest:重置为分区中最小的offset
          # latest:重置为分区中最新的offset(消费分区中新产生的数据)
          # none:只要有一个分区不存在已提交的offset,就抛出异常
          auto-offset-reset: latest
          properties:
            interceptor:
              classes: top.zysite.hello.kafka.interceptor.MyConsumerInterceptor
            session:
              timeout:
                # session超时,超过这个时间consumer没有发送心跳,就会触发rebalance操作
                ms: 120000
            request:
              timeout:
                # 请求超时
                ms: 120000
    
    
  3. 编写生产者代码

    @Service
    public class KafkaProducerService  {
        private final static String TOPIC_NAME="my-topic";
        
        @Autowired
        private KafkaTemplate<String, String> kafkaTemplate;
    
    
        public void sendMessage(){
            kafkaTemplate.send(TOPIC_NAME,0,"key","this is a message!");
        }
    }
    
    
  4. 编写消费者代码

    @Service
    public class KafkaConsumerService   {
       
        @KafkaListener(topics = "my-topic",groupId = "MyGroup1")
        public void listenMessage(ConsumerRecord<String,String> record, Acknowledgment ack){
            System.out.println(record.value());
            
            //手动提交offset
            ack.acknowledge();
        }
    }
    

参考:https://blog.csdn.net/qq_39340792/article/details/117534578
参考视频:https://www.bilibili.com/video/BV1Xy4y1G7zA?p=23&spm_id_from=pageDriver&vd_source=d902bddfa2b1d73669d22889b25198a2

Kafka集群中的controller, rebalance, HW

  1. controller
    集群中谁来充当controller:
    每个broker启动时会向zk创建一个临时序号节点,获得的序号最小的那个broker将会作为集群中的controller

负责几件事:

  • 当集群中有一个副本的leader挂掉,需要在集群中选举出一个新的leader,选举的规则是从isr集合中最左边获得。
  • 当集群中有broker新增或减少,controller会同步信息给其他broker
  • 当集群中有分区新增或减少,controller会同步信息给其他broker
  1. rebalance机制
    前提:消费组中的消费者没有指明分区来消费
    触发的条件:当消费组中的消费者和分区的关系发生变化的时候
    分区分配的策略:在rebalance之前,分区怎么分配会有三种策略
  • range:根据公式计算得到每个消费者消费哪几个区域:前面的消费者是:(分区总数 / 消费者数量)+1,之后的消费者是:分区总数 / 消费者数量
  • 轮询:大家轮着来
  • sticky:粘合策略,如果需要rebalance,会在之前已分配的基础上进行调整,不会改变之前的分配情况。如果这个策略没有开,那么就要进行全部的重新分配,建议开始。
  1. HW和LEO
    LEO是某个副本最后消息的消息位置(log-end-offset)

    HW是已完成同步的位置。消息在写入broker时,且每个broker完成这条消息的同步后,hw才会变化。在这之前消费者是消费不到这条消息的。在同步完成之后,HW更新之后,消费者才能消费到这条消息,这样的目的是防止消息的丢失。
    高并发高可用之Kafka_第4张图片

Kafka中的优化问题

  1. 如何防止消息丢失
    生产者:1) 使用同步发送 2) 把ack设成1或者all,并且设置同步的分区数>=2
    消费者:把自动提交改成手动提交

  2. 如何防止重复消费
    在防止消息丢失的方案中,如果生产者发送完消息后,因为网络抖动,没有收到ack,但实际上broker已经收到了。此时生产者会进行重试,于是broker就会收到多条相同的消息,而造成消费者的重复消费。
    怎么解决:
    – 生产者关闭重试:会造成丢失消息(不建议)
    – 消费者解决非幂等性消费问题:
    所谓幂等性:多次访问的结果是一样的。对于restful的请求(get(幂等), post(非幂等), put(幂等), delete(幂等))
    解决方案:
    在数据库中创建联合索引,防止相同的主键创建出多条记录
    使用分布式锁,以业务id为锁,保证只有一条记录能够创建成功

  3. 如何做到消息的顺序消费
    – 生产者:保证消息按顺序消费,且消息不丢失 – 使用同步的发送,ack设置成非0的值
    – 消费者:主题只能设置一个分区,消费组中只能有一个消费者。
    kafka的顺序消费使用场景不多,因为牺牲掉了性能,但是比如RocketMQ在这一块有专门的功能已设计好。

  4. 如何解决消息积压问题
    消息积压问题的出现:
    消息的消费者的消费速度,远赶不上生产者的生产消息的速度,导致kafka中有大量的数据没有被消费。随着没有被消费的数据堆积越多,消费者寻址的性能会越来越差,最后导致整个kafka对外提供的服务的性能很差,从而造成其他服务也访问速度变慢,造成服务雪崩。

    消息积压的解决方案:
    – 在这个消费者中,使用多线程,充分利用机器的性能进行消息消费。
    – 通过业务的架构设计,提升业务层面消费的性能。
    – 创建多个消费组,多个消费者,部署到其他机器上,一起消费,提高消费者的消费速度。
    – 创建一个消费者,该消费者在kafka另建一个主题,配上多个分区,多个分区再配上多个消费者。该消费者将poll下来的消息,不进行消费,直接转发到新建的主题上。此时,新的主题的多个分区的多个消费者就开始一起消费了(不常用)。

实现延时队列的效果

Kafka本身不带延迟机制,故只能以消费者逻辑实现的方式到达延时队列的效果。

  • kafka中创建相应的主题
  • 消费者消费该主题的消息(轮询)
  • 消费者消费消息时判断消息的创建时间和当前时间是否超过30分钟(前提是订单没支付)
    – 如果是:去数据库中修改订单状态为已取消
    – 如果否:记录当前消息的offset,并不再继续消费之后的消息,等待1分钟之后,再次向kafka拉取该offset及之后的消息,继续进行判断,以此反复。
    高并发高可用之Kafka_第5张图片

可视化监控平台Kafka-eagle

可以通过可视化界面查看Kafka运行情况。安装略。

你可能感兴趣的:(Java笔记,kafka,分布式,java)