最近工作中呢,频频用到消息中心,包括异步转同步的功能,分布式收集日志信息等功能,在面试中也常会问到候选人关于消息中心的知识点,但大多数程序员,尤其是工作两三年的,虽然平时工作中都有用到消息中心,但都总是不能够说明白其中的原理,于是觉得有必要把消息中心作为一个篇章,专门进行总结梳理一番~看的时候,建议大家不妨先看看问题,自己先尝试回答一下,再看答案。看看自己掌握得如何了
- 消息通讯:可以作为基本的消息通讯,比如聊天室等工具的使用
- 异步处理 : 将一些实时性要求不是很强的业务异步处理,起到缓冲的作用,一定程度上也会避免因为有些消费者处理的太慢或者网络问题导致的通讯等待太久,因而导致的单个服务崩溃,甚至产生多个服务间的雪崩效应;
- 应用解耦 : 消息队列将消息生产者和消费者分离开来,可以实现应用解耦
- 流量削峰: 可以通过在应用前端采用消息队列来接收请求,可以达到削峰的目的:请求超过队列长度直接不处理,重定向至错误页面。类似于网关限流的作用冗余存储:消息队列把数据进行持久化,直到它们已经被完全处理,通过这一方式规避了数据丢失风险
- 可靠性:Kafka是分布式的、可分区的、数据可备份的、高度容错的
- 高性能:Kafka的消息发布订阅具有很高的吞吐量,即便存储了TB级的消息,它依然能保持稳定的性能
我个人接触的项目中,Kafka使用最多的场景,就是用它与FileBeats和ELK组成典型的日志收集、分析处理以及展示的框架该图为FileBeats+Kafka+ELK集群架构。Kafka在框架中,作为消息缓冲队列
FileBeats先将数据传递给消息队列,Logstash server(二级Logstash)拉取消息队列中的数据,进行过滤和分析,然后将数据传递给Elasticsearch进行存储,最后,再由Kibana将日志和数据呈现给用户
由于引入了Kafka缓冲机制,即使远端Logstash server因故障停止运行,数据也不会丢失,可靠性得到了大大的提升
2)用户轨迹跟踪:kafka经常被用来记录web用户或者app用户的各种活动,如浏览网页、搜索、点击等操作,这些活动信息被各个服务器发布到kafka的topic中,然后消费者通过订阅这些topic来做实时的监控分析,当然也可以保存到数据库3)运营指标:kafka也经常用来记录运营监控数据。包括收集各种分布式应用的数据,生产各种操作的集中反馈,比如报警和报告4)流式处理:比如spark streaming和storm04.Kafka使用哪种方式消费消息,pull还是push?
Kafka的消费者使用pull(拉)的方式将消息从broker中拉下来1)Kafka可以根据consumer的消费能力以适当的速率消费消息2)消费者可以控制自己的消费方式:可以使用批量消费,也可以选择逐条消费3)消费者还可以选择不同的提交方式来实现不同的传输语义,要是使用了push的方式,就没有这些优点了会出现一种情况:如果Kafka没有数据,消费者会专门有个线程去等待数据,可能会陷入循环等待中我们可以通过在拉请求中设置参数,允许消费者请求在等待数据到达的“长轮询”中进行阻塞(并且可选地等待到给定的字节数,以确保大的传输大小)来避免这一问题1)相对于pull的方式来说,它不需要专门有一个消息去等待,而可能造成线程循环等待的问题push(推)模式一般是会以同样的速率将消息推给消费者,很难适应消费速率不同的消费者,这样很容易造成有些消费能力比较低的consumer来不及处理消息,导致出现拒绝服务以及网络拥塞的情况
Kafka的数据会存储在zookeeper上。包括broker和消费者consumer的信息
broker信息:包含各个broker的服务器信息、Topic信息
消费者信息:主要存储每个消费者消费的topic的offset的值总的来说,Kafka是分为三个角色:Producer、Kafka集群以及Consumer生产者将消息发送到Kafka集群,然后消费者再去Kafka集群进行消息的消费
1)图中,除了包含前面说到的生产者Producer、Kafka集群以及消费者Consumer三个角色之外,还包含了用于存储信息的注册中心-Zookeeper
2)生产者:很明显,它是消息的生产者,用于发送消息的客户端
4)消费者组:kafka的消费者角色,还有消费者组的概念,也就是说每个消费者组中可以包含多个consumer。其中,Kafka规定,消费者组中的消费者不能同时消费topic中的同一分区比如说,图中,消费者组中包含Consumer A 和Consumer B两个,对于有两个分区的topic A,Consumer A消费了partition0,这时Consumer B就不能消费partition0的消息了,它只能消费partition1中的消息因为队列的先进先出的特点,保证了消息在发送的时候是有序的,而在同一个分区中,它是被一个消费者所消费的,那么它就也可以在一个分区中,保证消费消息时的顺序性。而在一个有两个及两个以上的topic内的话,就不能保证消息的顺序性了
因此,想要保证消息的顺序性,只在新建topic时,指定一个分区即可5)Kafka集群:消息存储转发的地方,一般是集群的方式存在,而每个集群节点称为一个broker6)Zookeeper:用于存储broker信息和消费者信息7)broker:即Kafka集群的一台机器,可包含多个Topic9)Partation: 队列Topic的分区,一个Topic可以分为多个分区,用于高并发场景的负载功能;实际上Topic只是一个逻辑概念,真正存在的是分区
10)Offset:即队列中当前读取消息的位置。顺便说一下,kafka的存储文件都是按照offset.kafka来命名,使用offset做名字,就方便查找。例如你想找位于2035的位置,只要找到2035.kafka的文件即可无论消息是否被消费,kafka都会保留所有消息。而当消息的大小,大于设置的最大值log.retention.bytes(默认为1073741824)的值,也就是说这个缓冲池满了的时候,Kafka便会清除掉旧消息topic的分区partitions,被分为一个个小segment,按照segment为单位进行删除(segment的大小也可以进行配置,默认log.segment.bytes = 1024 * 1024 * 1024),由时间从远到近的顺序进行删除此外,Kafka还支持基于时间策略进行删除数据,过期时间默认为:log.retention.hours=168注意:因为Kafka读取特定消息的时间复杂度为O(1),即与文件大小无关,所以这里删除过期文件与提高 Kafka 性能无关
- 最多一次(<=1): 消息不会被重复发送,最多被传输一次,但也有可能一次不传输
- 最少一次(>=1):消息不会被漏发送,最少被传输一次,但也有可能被重复传输
- 精确的一次(Exactly once)(=1): 不会漏传输也不会重复传输,每个消息都传输被一次而且仅仅被传输一次,这是大家所期望的
- 最多一次:consumer先读消息,记录offset,最后再处理消息
这样,不可避免地存在一种可能:在记录offset之后,还没处理消息就出现故障了,新的consumer会继续从这个offset处理,那么就会出现有些消息永远不会被处理。那么这种机制,就是消息最多被处理一次最少一次:consumer可以先读取消息,处理消息,最后记录offset当然如果在记录offset之前就crash了,新的consumer会重复的消费一些消息。那么这种机制,就是消息最多被处理一次
精确一次:可以通过将提交分为两个阶段来解决:保存了offset后提交一次,消息处理成功之后再提交一次。当然也可以直接将消息的offset和消息被处理后的结果保存在一起,这样就能够保证消息能够被精确地消费一次1)消息发送的时候,如果发送出去以后,消息可能因为网络问题并没有发送成功
2)消息消费的时候,消费者在消费消息的时候,若还未做处理的时候,服务挂了,那这个消息不就丢失了
3)分区中的leader所在的broker挂了之后我们知道,Kafka的Topic中的分区Partition是leader与follower的主从机制,发送消息与消费消息都直接面向leader分区,并不与follower交互,follower则会去leader中拉取消息,进行消息的备份,这样保证了一定的可靠性但是,当leader副本所在的broker突然挂掉,那么就要从follower中选举一个leader,但leader的数据在挂掉之前并没有同步到follower的这部分消息肯定就会丢失掉我们知道,操作系统每次从磁盘读写数据的时候,都需要找到数据在磁盘上的地址,再进行读写。而如果是机械硬盘,寻址需要的时间往往会比较长而一般来说,如果把数据存储在内存上面,少了寻址的过程,性能会好很多;但Kafka 的数据存储在磁盘上面,依然性能很好,这是为什么呢?这是因为,Kafka采用的是顺序写,直接追加数据到末尾。实际上,磁盘顺序写的性能极高,在磁盘个数一定,转数一定的情况下,基本和内存速度一致因此,磁盘的顺序写这一机制,极大地保证了Kafka本身的性能
buffer = File.read
Socket.send(buffer)
传统方式实现:
先读取、再发送,实际会经过以下四次复制1、将磁盘文件,读取到操作系统内核缓冲区Read Buffer
2、将内核缓冲区的数据,复制到应用程序缓冲区Application Buffer
3、将应用程序缓冲区Application Buffer中的数据,复制到socket网络发送缓冲区
4、将Socket buffer的数据,复制到网卡,由网卡进行网络传输
传统方式,读取磁盘文件并进行网络发送,经过的四次数据copy是非常繁琐的
重新思考传统IO方式,会注意到在读取磁盘文件后,不需要做其他处理,直接用网络发送出去的这种场景下,第二次和第三次数据的复制过程,不仅没有任何帮助,反而带来了巨大的开销。那么这里使用了零拷贝,也就是说,直接由内核缓冲区Read Buffer将数据复制到网卡,省去第二步和第三步的复制。
这样必定会大大减少读取的开销,使得Kafka读取消息的性能有一个质的提升本次文章通过10个常见的Kafka经典面试题,带大家再次重新学习了一次Kafka,相信大家能够掌握得更深入一些了。想想自己当年在毕业第一年,实际上就已经开始使用Kafka了,但是当时就只知道它是个消息中心,对于它的特点啊,原理啊一无所知;第二年,因为要做新项目,有机会了解了一下,大概知道了它的框架,一些基本概念,但当时的学习,也还只是照本宣科,网上说多少是可以得到多少,但很多东西根本没法消化,记了很多笔记也总是没法给别人讲得特别清楚;直到现在,有了一定的学习和实战经验,再去看它,你会发现似乎一切都变得简单,你的脑子里很容易就出现了它的架构全局,它的一些特点,对于它的一些设计思路表示亲切与赞赏。而且很多东西,在看的时候,你会觉得更轻松,也会有一些自己的理解,并且基本不用做太多的笔记,便可以很快理解它的原理,因为这次是真正地学会了
所以,每个阶段都有每个阶段该做的事,可能正因为之前的填鸭式地学习基础,才会有今天的快速理解的效果。不断学习才是真理,它会让你不断发现惊喜,包括对外界的,也包括对你自己的