目录
消息队列的流派
- 有Broker的MQ
① 重Topic
Kafka,RocketMQ(根据Kafka内部原理开发的MQ)
② 轻Topic
RabbitMQ - 无Broker的MQ
Kafka的安装
Kafka安装基于Zookeeper
- 安装并部署Zookeeper服务
- 安装JDK
- 下载Kafka安装包,上传服务器并解压
- 修改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地址和端口
- 进入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的主题、分区概念
-
主题Topic
kafka通过topic将消息进行分类,不同的topic会被订阅该topic的消费者消费但是有个问题,如果说这个topic中的消息非常多,多到需要几个T来存,因为消息是保存在log日志文件中的,为了解决这个问题,kafka给出分区解决
-
分区Partition
一个Topic里有多个分区
创建多分区主题:./kafka-topics.sh --create --zookeeper localhost:2181 --topic test1 --replication-factor 1 --partitions 2
好处:
- 可以分区存储大文件
- 提高吞吐量,可以并行进行读写
Kafka默认有一个偏移量consumer_offsets主题,且默认有50个分区。这个主题用来存储消费者的偏移量相关信息。
Kafka集群
搭建3个broker:
- 创建并修改各自的配置文件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地址和端口
- 进入bin目录,分别执行命令启动Kafka(指定配置文件)
./kafka-server-start.sh -daemon ../config/server.properties #建议使用这种方式,不需要启动多个窗口
- 查看是否启动成功
进入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集合。
Leader
kafka的写和读操作都发生在leader上,leader负责把数据同步给follower,当leader挂了,经过主从选举,从follower中选举产生一个新的leader
Follower
接受leader同步的数据
Kafka集群下收发消息
为了保证消费消息的循序性,一个Partition只能被一个消费组里的某一个消费者消费,但一个消费者可以同时消费多个Partition。
-
集群发送消息
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
-
引入依赖
<dependency> <groupId>org.springframework.kafka</groupId> <artifactId>spring-kafka</artifactId> </dependency>
-
编写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
-
编写生产者代码
@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!"); } }
-
编写消费者代码
@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
- controller
集群中谁来充当controller:
每个broker启动时会向zk创建一个临时序号节点,获得的序号最小的那个broker将会作为集群中的controller
负责几件事:
- 当集群中有一个副本的leader挂掉,需要在集群中选举出一个新的leader,选举的规则是从isr集合中最左边获得。
- 当集群中有broker新增或减少,controller会同步信息给其他broker
- 当集群中有分区新增或减少,controller会同步信息给其他broker
- rebalance机制
前提:消费组中的消费者没有指明分区来消费
触发的条件:当消费组中的消费者和分区的关系发生变化的时候
分区分配的策略:在rebalance之前,分区怎么分配会有三种策略
- range:根据公式计算得到每个消费者消费哪几个区域:前面的消费者是:(分区总数 / 消费者数量)+1,之后的消费者是:分区总数 / 消费者数量
- 轮询:大家轮着来
- sticky:粘合策略,如果需要rebalance,会在之前已分配的基础上进行调整,不会改变之前的分配情况。如果这个策略没有开,那么就要进行全部的重新分配,建议开始。
-
HW和LEO
LEO是某个副本最后消息的消息位置(log-end-offset)HW是已完成同步的位置。消息在写入broker时,且每个broker完成这条消息的同步后,hw才会变化。在这之前消费者是消费不到这条消息的。在同步完成之后,HW更新之后,消费者才能消费到这条消息,这样的目的是防止消息的丢失。
Kafka中的优化问题
-
如何防止消息丢失
生产者:1) 使用同步发送 2) 把ack设成1或者all,并且设置同步的分区数>=2
消费者:把自动提交改成手动提交 -
如何防止重复消费
在防止消息丢失的方案中,如果生产者发送完消息后,因为网络抖动,没有收到ack,但实际上broker已经收到了。此时生产者会进行重试,于是broker就会收到多条相同的消息,而造成消费者的重复消费。
怎么解决:
– 生产者关闭重试:会造成丢失消息(不建议)
– 消费者解决非幂等性消费问题:
所谓幂等性:多次访问的结果是一样的。对于restful的请求(get(幂等), post(非幂等), put(幂等), delete(幂等))
解决方案:
在数据库中创建联合索引,防止相同的主键创建出多条记录
使用分布式锁,以业务id为锁,保证只有一条记录能够创建成功 -
如何做到消息的顺序消费
– 生产者:保证消息按顺序消费,且消息不丢失 – 使用同步的发送,ack设置成非0的值
– 消费者:主题只能设置一个分区,消费组中只能有一个消费者。
kafka的顺序消费使用场景不多,因为牺牲掉了性能,但是比如RocketMQ在这一块有专门的功能已设计好。 -
如何解决消息积压问题
消息积压问题的出现:
消息的消费者的消费速度,远赶不上生产者的生产消息的速度,导致kafka中有大量的数据没有被消费。随着没有被消费的数据堆积越多,消费者寻址的性能会越来越差,最后导致整个kafka对外提供的服务的性能很差,从而造成其他服务也访问速度变慢,造成服务雪崩。消息积压的解决方案:
– 在这个消费者中,使用多线程,充分利用机器的性能进行消息消费。
– 通过业务的架构设计,提升业务层面消费的性能。
– 创建多个消费组,多个消费者,部署到其他机器上,一起消费,提高消费者的消费速度。
– 创建一个消费者,该消费者在kafka另建一个主题,配上多个分区,多个分区再配上多个消费者。该消费者将poll下来的消息,不进行消费,直接转发到新建的主题上。此时,新的主题的多个分区的多个消费者就开始一起消费了(不常用)。
实现延时队列的效果
Kafka本身不带延迟机制,故只能以消费者逻辑实现的方式到达延时队列的效果。
- kafka中创建相应的主题
- 消费者消费该主题的消息(轮询)
- 消费者消费消息时判断消息的创建时间和当前时间是否超过30分钟(前提是订单没支付)
– 如果是:去数据库中修改订单状态为已取消
– 如果否:记录当前消息的offset,并不再继续消费之后的消息,等待1分钟之后,再次向kafka拉取该offset及之后的消息,继续进行判断,以此反复。
可视化监控平台Kafka-eagle
可以通过可视化界面查看Kafka运行情况。安装略。