消息队列(message queue)的概念
消息是在两台计算机之间传递的数据单位,它可以是简单的字符串,也可以是复杂的嵌入对象。消息队列是消息传递过程中有序保存消息的容器,将消息从源头中继到
目标时充当中间人的角色。
消息队列的作用
解耦、异步、削峰。
使用场景:
当系统中出现生产和消费的速度和稳定性等因素不一致的时候,使用消息队列,作
为中间层,来弥合双方的差异。
点对点模式(一对一,消费者主动拉取数据,消息收到后消息清除)
消息生产者生产消息发送到Queue中,然后消息消费者从Queue中取出并且消费消息。
消息被消费以后,queue中不再有存储,所以消息消费者不可能消费到已经被消费的消息。
Queue支持存在多个消费者,但是对一个消息而言,只会有一个消费者可以消费。
发布/订阅模式(一对多,消费者消费数据之后不会清除消息)
消息生产者(发布)将消息发布到topic中,同时有多个消息消费者(订阅)消费该消息。和点对点方式不同,发布到
topic
的消息会被所有订阅者消费。
常见的MQ框架
kafka
activeMQ
rabbitMQ
zeroMQ metaMQ
rocketMQ
等等。。。
kafka简介
kafka是由apache软件基金会开发的一个开源流处理框架,由JAVA和scala语言编写。是一个高吞吐量的分布式的发布和订阅消息的一个系统。Kafka® 用于构建实时的数据管道和流式
的app.它可以水平扩展,高可用,速度快,并且已经运行在数千家公司的生产环境。
基本术语
topic(话题):
kafka将消息分门别类,每一类的消息称之
话题
,是逻辑上的一个概念,如果是真正到磁盘上,映射的是一个
partition(分区)
的一个目录。
生产者(
producer):
发布消息的对象称之为生产者,只负责数据的产生,生
产的来源,可以不在kafka集群上,而是来自其他的业务系统。
消费者(consumer):
订阅消息并处理发布消息的对象,称为消费者。
消费者组(consumerGroup):
多个消费者可以构成消费者组,同一个消费者组
的消费者,只能消费一个topic数据,不能重复消费。
broker :
kafka本身可以是一个集群,集群中的每一个服务器都是一个代理,这个代理称为broker。只负责消息的存储,不管生产者和消费者,和他们没有任何关系。
在集群中每个broker有唯一个ID,不能重复。
kafka集群的搭建,启动和关闭
搭建kafka集群
在现有cluster1,2,3上搭建
上传kafka压缩包,到linux系统上
解压缩:
tar -xzvf /root/software/kafka_2.12-2.7.0.tgz -C /usr/
修改名称:
mv /usr/kafka_2.12-2.7.0/ /usr/kafka
配置环境变量:
vim /etc/profile
复制下面内容:
export KAFKA_HOME=/usr/kafka
export PATH=$PATH:$JAVA_HOME/bin:$ZK_HOME/bin:/usr/apache-tomcat-
9.0.52/bin:$KAFKA_HOME/bin
让配置文件生效:
source /etc/profile
测试:
echo $KAFKA_HOME
进入kafka目录:
cd /usr/kafka
创建目录(存放消息),为后面配置做准备
mkdir logs
修改配置server.properties文件:
vim /usr/kafka/config/server.properties
修改下面内容:
#broker的全局唯一编号,不能重复 21行broker.id=0#是否允许删除topic 22行delete.topic.enable=true#处理网络请求和响应的线程数量 42行num.network.threads=3#用来处理磁盘IO的线程数量 45num.io.threads=8#发送套接字的缓冲区大小 48socket.send.buffer.bytes=102400#接收套接字的缓冲区大小 51socket.receive.buffer.bytes=102400#请求套接字的最大缓冲区大小 54socket.request.max.bytes=104857600#kafka运行日志存放的路径 60log.dirs=/usr/kafka/logs#topic在当前broker上的分区个数 65num.partitions=1#用来恢复和清理data下数据的线程数量 69num.recovery.threads.per.data.dir=1#以下配置控制日志段的处理。可以将策略设置为在一段时间后或在给定大小累积后删除段。只要满足这些条件中的*任一*项,就会删除段。删除总是从日志的末尾开始。#segment文件保留的最长时间,超时将被删除,单位小时,默认是168小时,也就是7天 103log.retention.hours=168#基于大小的日志保留策略。除非剩余的段低于log.retention.bytes,否则将从日志中删除段。独立于log.retention.hours的功能#log.retention.bytes=1073741824#日志段文件的最大大小。当达到此大小时,将创建一个新的日志段。log.segment.bytes=1073741824#检查日志段以查看是否可以根据保留策略删除日志段的间隔log.retention.check.interval.ms=300000#配置连接Zookeeper地址(集群环境多台机器用逗号分隔) 123zookeeper.connect=hdcluster1:2181注意:zookeeper datadir要修改,myid要修改,防火墙要关闭 有问题要重启
因为配置文件中使用的zk主机名称链接,所以配置本地域名:
vim /etc/hosts完整的hosts:192.168.206.131 cluster1192.168.206.132 cluster2192.168.206.133 cluster3
修改producer.properties:
vim /usr/kafka/config/producer.properties修改21行为:bootstrap.servers=cluster1:9092,cluster2:9092,cluster3:9092
修改consumer.properties:
vim /usr/kafka/config/consumer.properties
修改19行为:
bootstrap.servers= cluster1:9092,cluster2:9092,cluster3:9092
发送配置好的kafka到另外两台机子(先做免密登录)
:
ssh-keygen -t rsassh-copy-id cluster2ssh-copy-id cluster3scp -r /usr/kafka/ cluster2:/usr/scp -r /usr/kafka/ cluster3:/usr/
检查发送是否成功,在all session执行:
ls /usr
修改broker.id(切记)
在
cluster
2和
cluster
3上修改broker.id
vim /usr/kafka/config/server.properties
修改21行为
broker.id=1broker.id=2
发送环境变量配置文件
:
scp -r /etc/profile cluster2:/etc/scp -r /etc/profile cluster3:/etc/
在all session执行:
source /etc/profileecho $KAFKA_HOME
发送hosts配置文件:
scp -r /etc/hosts cluster2:/etc/scp -r /etc/hosts cluster3:/etc/
测试是否成功:
在all session执行:cat /etc/hosts
集群的启动和关闭
启动kafka之前一定要保证zk在启动,并且可用:
启动zk:
/root/shelldir/zk-start-stop.sh
测试是否启动:
jps //在all session执行:
启动kafka:
//在all session执行
kafka-server-start.sh -daemon /usr/kafka/config/server.properties
jps (查看java程序进程)
停止kafka:
kafka-server-stop.shjps
常用命令
查看当前服务器中的所有topic主题:
kafka-topics.sh --zookeeper cluster1:2181 --list
如果是zk集群可以使用这样的命令:
kafka-topics.sh --zookeeper cluster1:2181,cluster2:2181,cluster3:2181 --list
创建topic: list
kafka-topics.sh --zookeeper cluster2:2181 --create --replication-factor 3 --partitions 5 --topic ordertopickafka-topics.sh --zookeeper cluster2:2181 --create --replication-factor 2 --partitions 2 --topic goodstopic
参数说明:
--zookeeper 链接zk
--replication-factor 指定副本数目(副本数目不能大于总的brokers数目)
--partitions 指定分区数
--topic 指定topic名称
删除topic:
kafka-topics.sh --zookeeper cluster1:2181 --delete --topic tp3
This will have no impact if
delete.topic.enable
is not set to true
如果这个配置没有设置为true,则消息是假删除
生产消息:
kafka-console-producer.sh --broker-list cluster2:9092 --topic goodstopic
消费消息:
kafka-console-consumer.sh --bootstrap-server cluster2:9092 --from-beginning --topic goodstopic
同组消费者消费消息(多个窗口):
kafka-console-consumer.sh --bootstrap-server kafka1:9092 --consumer-property group.id=gtest --from-beginning --topic tp1
查看一个topic详情:
kafka-topics.sh --zookeeper cluster2:2181,cluster1:2181 --describe --topic tp1
partitioncount 分区总数量
replicationfactor 副本数量
partition 分区
leader 每个分区有3个副本,每个副本都有leader
replicas 所有副本节点,不管leader follower
isr: 正在服务中的节点
kafka的整体工作流程
Kafka中消息是以
topic
进行分类的,生产者生产消息,消费者消费消息,都是面向topic的。
topic是逻辑上的概念,而
partition
是物理上的概念,每个
partition
对应于一个
log
文件,该
log
文件中存储的就是
producer
生产的数据。
Producer
生产的数据会被不断追加到该log文件末
端,且每条数据都有自己的
offset
。消费者组中的每个消费者,都会实时记录自己消费到了哪
个
offset
,以便出错恢复时,从上次的位置继续消费。
由于生产者生产的消息会不断追加到log文件末尾,为防止log文件过大导致数据定位效率
低下,Kafka采取了
分片
和
索引
机制,将每个partition分为多个segment。每个segment对应两
个文件——
“.index”
文件和
“.log”
文件。这些文件位于一个文件夹下,该文件夹的命名规则为:
topic名称+分区序号。例如,first这个
topic
有三个分区,则其对应的文件夹为first
-0,
first
-1,
first
-2
。
分区的好处
方便集群的伸缩
每个
Partition
可以通过调整以适应它所在的机器,而一个
topic
又可以有多个
Partition
组成,因此整个集群就可以适应任意大小的数据了
可以提高并发
可以以
Partition
为单位读写,提高集群的读写速度
分区是如何分配到broker上
目的:
1. 保证所有的分区以及副本可以均衡在分布上所有的broker上
2. 保证同一个分区及其副本尽量不要分布在同一个broker上
规则:
让分区和broker进行排序 partition leader排序: p0 p1 p2 p3
partition 副本: p0r p1r p2r p3r
broker排序: br0 br1 br2
分区怎么分配到broker上: p0随机一个broker topic越多,每个topic都对应有一个p0
越平均的分配到不同的broker
leader p0->br1 p1->br2 p2 ->br0 p3->br1(p0随机,以后的放入下一
个)
副本随机: p0r->br0 p1r->br1 p2r->br1 p3r->br2
副本(replication)好处
提高kafka的系统的可靠性和稳定性,同一个partitation对应一个或者多个副本,创建topic时就可以设置(--replication-factor 2)。没有副本,一旦当前保存消息的服务器宕
机,就会造成消息丢失,如果有replication,当保存消息的服务器宕机后,从新选举新的leader,
继续进行消息读写,不会造成消息丢失。
zk保存kafka数据的目录结构是什么样子的,在kafka集群中的作用
1,broker在zk中注册:集群启动时,每个broker都会在/brokers/ids/下注册(创临时节点),如果broker挂掉了,zk就会删除该节点。
2,topic会在zk中注册:创建topic时,每个topic都会在/brokers/topics/下注册,topic删除,节点失效。每个broker和topic的对应关系也是由zk进行维护。
3,consumer(消费者)在zk注册:当新的消费者都会zk进行注册,zk在/consumers/consumer-group/ 创建3个节点 ids offsets(偏移量) owners
ids: 记录当前消费者组所有的消费者id
offsets:消费者在消费topic每个partition时,消费到哪个位置(offset 偏移量)
owners:记录该消费者组消费的topic信息(订阅了哪些topic)
新版本无效
生产者如何写入消息的
get /brokers/topics/tpa/partitions/0/state
{"controller_epoch":7,"leader":2,"version":1,"leader_epoch":19,"isr":[2]}
ack=-1/all 讲解画图实例
为保证
producer
发送的数据,能可靠的发送到指定的
topic
,
topic
的每个
partition
收到producer
发送的数据后,都需要向
producer
发送
ack
(acknowledgement
确认收到),如果
producer
收到
ack
,就会进行下一轮的发送,否则重新发送数据。
消息写入时,放入分区的规则:
1,
指定分区
,直接按照指定分区写入
2,
没有指定分区,但是消息中含有key(一般消息是key value的方式)
,通过key的值进行hash运
算,计算得到一个partition,写入到这个分区。(aaa hash运算后可能得到一个和aaa没有任何
关系的一个数值123132,对分区的总数量取模,根据结果,得到分区)
3,
如果没有指定分区,key都没有,使用轮询
(第一次调用时随机生成一个整数(后面每次调用
在这个整数上自增),将这个值与 topic 可用的 partition 总数取余得到 partition 值,也就是常说的 round-robin 算法),找出一个分区,并写入。
ack应答机制
0 producer不等待broker同步完成的确认,继续发送下一条(批)信息提供了最低的延迟。但是最弱的持久性,当服务器发生故障时,就很可能发生数据丢
失。例如leader已经死亡,producer不知情,还会继续发送消息broker接收不到数据就
会数据丢失
1 producer要等待leader成功收到数据并得到确认,才发送下一条message。此选项提供了较好的持久性较低的延迟性。Partition的Leader死亡,follwer尚未复制,数据就会
丢失
-1/all 意味着producer得到leader和所有follwer确认,才发送下一条数据
ISR(In Sync Replicas):
当ack配置-1时 leader收到数据,所有follower都开始同步数据,但有一个follower,因为某种故障,迟迟不能与leader进行同步,那leader就要一直等下去,直到它完成同步,才能发
送ack。这个问题怎么解决呢?
Leader维护了一个动态的in-sync replica set (ISR),意为和leader保持同步的follower集
合。当ISR中的follower完成数据的同步之后,leader就会给follower发送ack。如果follower长
时间未向leader同步数据,则该follower将被踢出ISR,该时间阈值由replica.log.time.max.ms参
数设定。Leader发生故障之后,就会从ISR中选举新的leader。
消费者消费要点
消息的消费模式有两种:
推送模式(push) 和拉取模式(pull)
kafka采取的拉取模式(pull),由消费者自己记录消费状态(消费者自己记录自己的消费位置
(offset偏移量)),每个消费者相互独立的消费每个一个分区的消息。每个消费者消费完了后,
对消息本身不做任何处理,决定该消息是否能被删除,跟消费者没有任何关系,与配置的消息过
期时间和消息总容量的大小配置参数有关(
log.retention.hours=168
log.retention.bytes=1G
)。
消费者是以消费者组( consumer group)的方式,由一个或者多个消费者组成一个组,共同消费一个topic(主题),在同一时刻,只能由同一个组的一个消费者去消费同一个分区。
pull模式不足之处是,如果kafka没有数据,消费者可能会陷入循环中,一直返回空数据。
针对这一点,Kafka的消费者在消费数据时会传入一个时长参数timeout,如果当前没有数据可供消费,consumer会等待一段时间之后再返回,这段时长即为timeout。