消息中间件
原创
©著作权归作者所有:来自51CTO博客作者王金龙_社会你龙哥的原创作品,请联系作者获取转载授权,否则将追究法律责任
Kafka
一、为什么使用消息队列
用户下订单,在后台可能创建多个服务数据订单,减库存、加积分、给优惠券等
如果同步方式来和用户交互,中间得考虑网络不稳定延迟,本身延迟等
用户体验较差,存在性能和稳定性问题瓶颈。
二、异步改造
在上游和下游中间加一层,加消息队列,针对同步方式来说,异步的方式,可以快速提高用户体验,提高吞吐量
在分布式系统中,通过多个服务的分布式事务,保障业务执行的一致性。
三、消息队列解决了什么问题
解决了通信问题
表面是异步和解耦,实质是解决通信问题,屏蔽了底层复杂的通信协议
四、消息队列选型
- rabbitMQ 内部可玩性比较强
- rocketMQ 阿里员工基于kafka开发的,功能更多
- Kafka 性能最好的MQ
- zeroMQ
broker是什么:是一个中转站,来控制中转站内部的消息分发规则
1、有Broker的MQ
重Topic(kafka、rocketMQ、activeMQ)
整个broker依据topic进行消息中转
轻Topic(rabbitMQ)
topic只是一种中转模式
2、无Broker的MQ
zeroMQ:他把MQ理解为高级的socket,就是解决通信的,
五、kafka介绍
支持分布式、支持分区(partition)、多副本、基于zookeeper
1、使用场景
日志收集:收集各种log
消息系统:解耦生产者和消费者,缓存消息
用户活动跟踪:记录web用户或者app各种活动,如网页浏览、搜索、点击等活动,发布到topic中,然后做实时分析
运营指标:收集各种应用分布式数据,如告警信息等
六、kafka安装
前提:安装jdk、zookeeper
安装包管网下载
解压出来的目录结构
bin #可执行的文件
config #配置文件,修改其中server.properties
libs
基本配置
server.properties
broker_id=0 #集群id不一样
listener=plaintext://IP:9092 #监听地址和提供对外服务的默认端口
log.dirs= #日志文件的地址
zookeeper配置信息 #只要是存储broker集群的信息
zookeeper.connect=IP:2181
启动Kafka
进入bin/目录下:执行./kafka-server-start.sh -daemon ../config/server.properties
ps -ef | grep kafka
启动kafka之后可以进入zk的客户端去看下信息
关注目录下的broker
ids #存broker_id=0的信息
topic
seqid
七、kafka基本概念
broker:消息中间件处理节点,一个kafka节点就是一个broker,一个或者多个broker可以组成一个Kafka集群
topic:Kafka根据topic对消息进行归类,发布到kafka集群的每条消息都需要指定一个Topic
producer:消息生产者,向broker发送消息的客户端
consumer:消息消费者,从broker读取消息客户端
创建topic命令
创建一个名为test的topic,只有一个副本,一个分区
./bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic test1
运行命令查看所有的topic: ./bin/kafka-topics.sh --list --zookeeper localhost:2181
kafka自带一个生产者的客户端,可以从本地读取内容,也可以在命令行输入内容,并将内容以消息的形式,发送
给kafka集群,默认情况下,每一个行会被当作一个独立的消息
1、发送消息
用Kafka的console-producer在topic test1 生产消息
运行命令:
./bin/kafka-console-producer.sh --broker-list localhost:9092 --topic test1
2、消费消息
用Kafka的console-consumer 消费topic test1的消息
从头消费(--from-beginning)
运行命令:./bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic test1 --from-beginning
默认消费最新消息(偏移量offset位置+1开始消费)
./bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic test1
生产者将消息发送给broker,broker会将消息保存在本地的日志文件中,就是下面定义的地址,
/usr/local/kafka/data/kafka-logs/主题-分区/000000.log里
log.dirs= /usr/local/kafka/data/ #日志文件的地址
消息的保存是有序的,通过offset偏移量来描述消息的有序性
消费者也是通过偏移量来描述当前要消费的具体偏移量位置
八、kafka单播和多播消息
1、单播消息
测试:1台生产者(开一个客户端),2台消费者(开两个客户端,指定相同的组,然后生产者开始向两个客户端的同topic发消息测试)
启动两个消费者,一个生产者,生产者发送消息,这条消息会被两个消费者消费吗?
如果多个消费者在同一个消费组里,那么只有一个消费者可以收到订阅的topic中的消息,换言之,同一个消费组中
只能有一个消费者收到一个topic中的消息。
./bin/kafka-console-producer.sh --broker-list localhost:9092 --topic test1
./bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --consumer-property group.id =testGroup --topic test1
2、多播消息
不同的消费组订阅同一个topic,实际是多个消费组中的多个消费者收到同一个消息
./bin/kafka-console-producer.sh --broker-list localhost:9092 --topic test1
./bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --consumer-property group.id =testGroup --topic test1
./bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --consumer-property group.id =testGroup1 --topic test1
查看当前主题下消费组
./kafka-consumer-groups.sh --bootstrap-server ip:port --list
查看消费组的详细信息
./kafka-consuemr-groups.sh --bootstrap-server ip:port --group 消费组名称 --describe
关注几个字段信息:
current-offset:最后被消费的消息的偏移量
Log-end-offset:消息总量(最后一条消息的偏移量)
lag:积压的消息量
九、kafka主题和分区的概念
主题:topic #kafka逻辑的概念,进行分类的,消息会被保存到日志文件中,为了解决文件过大的问题
出现了分区的概念
分区(partition) #通过partition将一个topic中的消息分区存储
好处:
分区存储,可以解决统一文件过大的问题
提高了读写的吞吐量,读写都可以在多个不同分区进行
创建多分区主题:
./bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 2 --topic test1
日志文件中保存的内容
000000.log #保存消息
_consumer_offsets-49 #kafka内部自己创建了50个offset分区
每个消费者都会自己维护主题的偏移量,每个消费者把消费主题的偏移量自主上报给Kafka的默认_consumer_offsets-xxx主题,提升主题的并发性,默认设置50个_consumer_offsets分区
至于提交到哪个分区,是根据公式:hash(consumerGroup)%_consumer_offsets
offsets_topic.num.partition设置具体_consumer_offsets数量
提交该主题的内容是:key-value形式
key:consumerGroup+topic+分区号
value:当前的offset值
文件中保存的消息,默认保存7天
十、kafka集群搭建
搭建集群
3个broker
准备3个server.properties
1、server0.properties
broker_id=0
listener=plaintext://IP:9092
log.dirs=/usr/local/data/kafka-logs
2、server1.properties
broker_id=1
listener=plaintext://IP:9093
log.dirs=/usr/local/data/kafka-logs-1
3、server2.properties
broker_id=2
listener=plaintext://IP:9094
log.dirs=/usr/local/data/kafka-logs-2
启动
./kafka-server-start.sh -daemon ../config/server0.properties
./kafka-server-start.sh -daemon ../config/server1.properties
./kafka-server-start.sh -daemon ../config/server2.properties
进入zk客户端查看id
[0 1 2]
十一、副本的概念
副本是对分区的备份
isr:可以同步的broker节点,已经同步的broker节点,存放在isr集合中,如果isr中节点性能较差,会被踢出isr集合
leader:kafka的读写操作都在leader上,leader负责把数据同步给follower,当leader挂了,从多个follower中
选举产生一个新的leader
follower:接受leader的同步的数据
集群中有多个broker,创建主题时可以指明主题有多个分区(把消息拆分到不同分区中存储),可以为分区创建多个副本
,不同副本存放在不同的broker里。
创建1个topic,2个分区,3个副本
./bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 3 --partitions 2 --topic test1
查看主题:topic test1
./bin/kafka-topics.sh --describe --zookeeper localhost:2181 --topic test1
十二、kafka集群消息
指定消费组
向集群发送消息,指定组消费消息
一个partition只能被一个消费组中的一个消费者消费,保证顺序性,但多个Partiton的多个消费者消费的总的顺序无法保证
同一个消费组中消费者的数量不要超过Partition的数量,否则多的消费者消费不到消息
如果消费者挂了,会触发reblance机制
十三、java客户端API实现生产者
十四、生产者发送消息
同步:
生产者的同步和异步
如果生产者发送消息没有收到ack确认,生产者会阻塞,阻塞3s时间,如果还没收到消息,会进行重试,重试次数
为3次
得ack返回才能执行下面的业务
异步:
异步可能引起消息丢失,一般会用同步的多一些
生产者发送消息后就可以执行之后的业务,Broker在收到消息之后异步调用生产者提供的callback方法
在同步发送的前提下,生产者在获得集群返回ack之前会一直阻塞,那么集群什么时候返回ack呢,此时有以下3个配置
生产者的ack确认:
ack=0: kafka集群不需要任何的broker收到消息,就立即返回消息给生产者,最容易丢消息,效率最高
ack=1: 多副本之间的leader已经收到消息,并把消息写入到本地的log中,才会返回ack给生产者,性能和安全性
均衡,leader挂了,就丢消息了
ack=-1/all: 并且默认配置min.insync.replicas=2(默认是1,推荐min.insync.replicas大于2)最安全,但性能最差,此时需要Leader和一个follower同步完后,才会返回ack
发送消息的缓冲区机制:
kafka默认会创建一个消息缓冲区,用来存放要发的消息,缓冲区是32m
kafka本地线程回去缓冲区中一次拉16k的数据,发送到broker集群
如果线程拉不到16k的数据,间隔10ms也会将已经拉到的数据发送给broker
本地缓冲区(生产者): 32M
本地线程池(生产者):
十五、消费者消费消息
java代码
1、创建一个消费者客户端
2、消费者订阅主题列表
3、poll消息
4、解析消息
offset会放在broker_id=0的机器上
消费者无论是自动提交还是手动提交,都需要把所属的消费组+消费的某个主题+消费的某个分区及消费的偏移量,这样的
信息提交到集群的__consumer_offsets主题里
自动或者手动,决定于修改java API里的参数True False
offset自动提交:(丢消息的因果)自动提交有一定的间隔
Poll下来后就提交offset,poll下来,还没消费,但消费者挂了,offset已经提交了,这时另开一个消费者,它会从上一次提交的offset加1的位置开始消费,那么提交offset之前的消息就丢了
offset手动提交:
在消费消息后提交offset
1、手动同步提交:阻塞集群,返回ack之前一直阻塞,一般使用同步提交
2、手动异步提交:提交不等ack直接向下执行,设置回调方法
消费者与broker之间建立长连接,开始poll消息,默认一次poll 500条消息
代码中设置的长连接的时间是1S, 如果一次poll 500条,就直接for循环,如果没有poll 500条,且时间在1s内,
则长连接继续poll,要么到500条,要么到1s。
如果多次poll都没达到500条,且时间到1s了,那么就直接for循环
如果两次poll的时间超出了30s的时间间隔,kafka会认为消费能力弱,将其踢出消费组,触发rebalance机制,rebalance机制会造成性能的开销,将分区分配给其他消费者,想解决的话可以调整poll的条数,来缩短时间隔。
poll一次性拉去消息的最大条数:500条 #会根据消费的速度来调整这个值
consumer健康状态检查:消费者每隔1s向kafka集群发送心跳,如果集群在10s内没有收到消费者的心跳,将被踢出
消费组,触发该消费组的rebalance机制,将该分区交给消费组里的其他消费组进行消费。
消费者指定具体主题的具体分区消费:
消费者回溯消费(从头消费):
消费者指定offset消费:
消费者指定时间点消费:根据时间找到offset,根据offset在消费
新消费组的消费规则:
新消费组中消费者在启动以后,默认会从当前分区的最后一条消息的offset+1开始消费(消费新消息)
可以通过设置,让新的消费者第一次从头开始消费,之后开始消费新消息(offset+1开始消费)。
Latest:默认,消费新消息
earliest:第一次从头开始消费,之后开始消费新消息(offset+1开始消费)
十六、Springboot中使用kafka
十七、kafka集群中controller
每个broker启动时,会向zk创建一个临时序号节点,获得的序号最小的那个broker将会作为集群中的controller
当某个分区的leader副本出现故障时,由控制器负责为该分区选举新的leader副本,规则是从isr集合最左边开始选择
当检测到某个分区的isr集合发生变化时,由控制器负责通知所有的broker更新其元数据信息
当使用kafka-topic.sh脚本为某个topic增加分区数量时,同样还是由控制器负责让新的分区和其他节点感知到
十八、kafka的rebalance
前期:消费组中的消费者没有指定分区来消费
触发的条件:当消费组中的消费者和分区的关系发生变化时
三种策略:
range:根据公式计算
轮训:
sticky:粘合策略,如果需要rebalance,会在之前已经分配的基础上调整,不会改变之前的分配情况,如果
这个策略没有开,那么就要进行全部重新分配,建议开启。
十九、kafka的HW和LEO
LEO:是某个副本最后消息的消费位置(log-end-offset)
HW: 是已完成同步的位置,消息在写入broker时,且每个broker完成这条消息的同步后,hw才会变化,
在这之前消费者是消费不到这条消息的,在同步完成之后,hw更新之后,消费者才能消费到这条消息,这样的
目的是防止消息的丢失。
二十、kafka线上问题优化
1、如何防止消息丢失:
不可能做到100%不丢失,
生产者:
1、同步发送
2、把ACK设置成1或者all,并且设置同步分区数>=2
消费者:
1、把自动提交改为手动提交
2、如何防止消息重复的消费:
在防止消息丢失的方案中,如果生产者发送完消息后,因为网络抖动,没有收到Ack,但实际上broker已经收
到了,此时消费者会进行重试,于是broker就会收到多条相同的消息,造成消费者重复消费。
解决方案:(两种思路)
1、生产者关闭重试,会造成丢消息,不建议这样做。
2、消费者端来解决(解决幂等性)
什么是幂等性:多次访问的结果是一样的
2.1、在数据库中创建联合主键,防止相同的主键创建出多条记录
2.2、使用分布式锁,保证只有一条记录创建成功(以业务id为锁)
3、如何做到顺序消费
生产者:保证消息按顺序消费,且消息不丢失,使用同步的发送,ack设置成非0的值
消费者:主题只能设置一个分区,消费组中只能有一个消费者
顺序消费,肯定是牺牲性能,使用场景不多,但阿里的rocketMQ有专门针对顺序消费的功能
4、解决消息积压的问题
产生消息积压:消息的消费者的消费速度远赶不上生产者的生产消息的速度,导致kafka中有大量的数据没有被
消费,随着没有被消费的数据堆积越多,消费者寻址的性能会越来越差,最后导致整个kafka对外提供的服务的
性能很差,从而造成其他服务访问速度变慢,造成服务雪崩。
消息积压的解决办法:
1、在这个消费者中,使用多线程,充分利用机器的性能进行消息消费。
2、创建多个消费组,多个消费者,部署到其他机器上,一起消费,提供消费者的消费速度
3、创建一个消费者,该消费者在kafka另建一个主题,配上多个分区,多个分区在配上多个消费者,
该消费者将poll下来的消息,不进行消费,直接转发到新建的主题上,此时,新的主题的多个分区
的多个消费者就开始一起消费了,不常用
4、通过业务架构设计,提升业务代码优化
5、延迟队列实现
创建订单30分钟没付款,就取消订单
kafka中创建相应的主题
消费者消费该主题的消息(轮训)
消费者消费消息时判断消息的创建时间和当前时间是否超过30分钟(前提是订单未支付)
如果是:去数据库中修改订单状态的记录为已取消
如果否:记录当前的offset,并不在继续消费之后的消息,等待1分钟后,再次向Kafka拉取该offset及
之后的消息,继续进行判断。
二十一、kafka-eagle监控平台
去官网下载安装包
http://download.kafka-eagle.org/
安装jdk
解压缩包
给kafka-eagle配置环境变量
修改kafka-eagle配置文件
修改zk和mysql地址
进入到bin启动
登录界面