消息中间件MessageQuene
- 解耦且可扩展:业务复杂度的提升带来的也是耦合度的提高,消息队列在处理过程中间插入了一个隐含的、基于数据的接口层,两边的处理过程都要实现这一接口。这允许你独立的扩展或修改两边的处理过程,只要确保它们遵守同样的接口约束。
- 冗余:有些业务在处理过程中如果失败了,数据在未进行持久化的时候就已经消失,消息队列把数据持久化直到他们被处理,避免了数据的丢失
- 处理并发:大数据量访问的时候我们可以将消息放入队列中,然后在队列里面按照系统的吞吐能力来进行稳定的抽取数据并进行业务处理。
- 可恢复:一部分系统出现问题,可能影响整个程序稳定,消息队列由于将数据持久化,所以在出现问题的时候可以起到一个备份的作用,系统稳定之后可以进行数据重新消费。
- 送达保证:大多数消息队列都有一套自己的消息处理机制,一般分为消息处理多次,消息至少被处理一次等情况,这使得我们处理业务减少了数据丢失情况的发生。
- 顺序处理:按照一定的顺序发送消息,使得消息在队列中是有序存在的,所以在消费数据的时候我们也是有序处理的(先进先出)。
- 异步通信:很多时候,你不想也不需要立即处理消息。消息队列提供了异步处理机制,允许你把一个消息放入队列,但并不立即处理它。你想向队列中放入多少消息就放多少,然后在你乐意的时候再去处理它们。
常用MQ对比
- RedisMQ:Redis是基于Key-Value的NoSql数据库,本身支持MQ队列操作,是一个轻量级的队列服务,在使用Redis作为缓存的项目中,可以优先使用RedisMQ作为消息队列进行业务处理。优点:轻量级,容易开发。缺点:数据大的时候入队速度较慢
- ActiveMQ:Apache的一个开源子项目,支持多种语言以及网络协议。优点:容易开发,并且有自带重连机制。缺点:传入文件不方便,数据大的时候效率一般,消费失败的数据将会丢失。
- Kafka:Apache的一个开源子项目,高性能大吞吐量并且能够满足跨语言平台分布式。优点:快速持久化,大吞吐量,支持hadoop数据并行加载,可以进行离线消息处理。缺点:开发困难,配置文件复杂,开源代码较少。
Kafka元素介绍
- broker:kafka搭建的集群服务器称为broker,集群中一台服务器可以搭载多个broker。
- Topic:每条发布到Kafka集群的消息都有一个类别,这个类别被称为topic。我们可以认为同一种类型的消息存到同一个topic下,这点和Map的key值有相似之处。
- Partiton:parition是物理上的概念,每个topic包含一个或多个partition,创建topic的时候可以指定partiton的数量。每一个partition对应一个文件夹。文件夹里面存放索引文件以及数据文件。
- Producer:负责发布消息到Broker中
- Consumer:每个消费者属于特定的消费者组,每个消费者组只能消费一个partition的数据,多个组可以同时消费同一个partition的数据。
Kafka的架构
一个典型的kafka集群中包含若干producer,若干broker(Kafka支持水平扩展,一般broker数量越多,集群吞吐率越高), 若干consumer group,以及一个 Zookeeper 集群。Kafka通过Zookeeper管理集群配置,选举leader,以及在consumer group发生变化时进行rebalance。producer使用push模式将消息发布到broker,consumer使用pull模式从 broker订阅并消费消息。
Kafka处理消息的机制
- producer:Producer向Broker推送消息(push)
- consumer:consumer从broker拉取消息(pull)
- push模式下很难适应不同消费速率的消费者处理消息,因为发送消息的速度是由broker决定的。push模式的目的是为了尽快的消费,但是这样容易造成consumer来不及处理消息。典型的表现就是网络阻塞和拒绝服务。
- pull模式可以根据消费者消费速率而抓取消息,这样避免了网络阻塞的情况发生。
Topic与Partition认识——简单分布
Partition分布我们主要分成单Broker和多个Broker:
单个Broker:
创建一个partition为3,Replica为1,Topic名字为order的topic。我们得到的分布式在配置好的LOG文件夹中生成三个分别为:order-0、order-1、order-2的文件夹用来存储Partition下的信息的.index文件.log文件和.timeindex文件。
多个Broker:
创建一个partition为3,Replica为1,Topic名字为order的topic。我们在Broker0中对应的LOG文件夹中只是发现了order-0的文件夹,在其他Broker中分别发现了Partition的文件夹。如果Broker数大于Partition数,那么有Broker中没有对应的Partition;如果Broker小于Partition数,Broker中会存在多个Partition。
下面以一个Kafka集群中4个Broker举例,创建1个topic包含4个Partition,2个 Replication:
当集群中新增2节点,Partition增加到6个时分布情况:
Topic与Partition认识——分布逻辑
副本分配逻辑:
- 在kafka集群中,每个Broker分配Partition的leader机会是均等的
- 每个Broker(按照brokerid有序)依次分配leader的Partition,下一个Broker为副本,如此循环迭代分配,多副本也遵循此规则。
副本分配算法:
- 将所有的Broker和待分配的Partition进行排序
- 将第i个Partition分配到第(i mod n)个Broker上
- 将第i个Partition的第j个副本分配到第((i + j) mod n)个Broker上.
Partition——Leader与Follower
Leader和Follower:
- opic的Replication不是必选
- 如果Replication中的Leader宕机了,那么副本Follower将作为Leader Follower的数据是从Leader中拉取的,而不是Leader发送的
- 判断节点是否alive:首先节点必须可以维护和zookeeper的连接,zookeeper通过心跳机制检查每个节点的链接;其次是如果这个节点是follower,必须能够同步leader的写操作,延时不能太久。(符合条件的节点为同步中状态,如果出现上述问题的节点,leader将会把节点移除,时间由replica.lag.time.max.ms决定,消息由replica.lag.max.messages决定)
- 如果有副本,那么当所有副本都加入日志中才算这条信息commited,只有消息是commited状态的才能被消费者消费。(Producer可以根据acks参数来决定是否等待消息提交通知反馈)
- Kafka只要保证有一个节点是同步中的,已经commited的消息就不会丢失。
Partition——Leader选举
- 分布式Leader选举机制有Follower投票,以及Follower命中率机制来等,这两种机制都是根据副本节点的状态来动态选择的。而kafka并不使用这两种。
- 虽然kafka使用zookeeper进行leader选择,但是它采用FastLeaderElection的方式和传统方式有所不同。
- kafka自己维护同步状态的副本集合(ISR),这个里面的节点都是和Leader数据保持高度一致的,任何一条消息保证每个节点的消息追加到日志中才会告诉Leader这个消息已经commited,所以任何Follower节点在Leader宕掉之候都可能被选择为Leader,并且这些节点都是通过zookeeper管理维护。
- Follower节点为N,如果N-1个节点都宕,kafka依然可以正常工作,如果某一个宕掉节点又重新alive,由会被重新加入到ISR中。
- 如果N个节点都宕掉了,就会等待ISR中任何一个重新alive的节点作为Leader,或者选择所有节点中重新alive的节点作为Leader(貌似新版本kafka中可以配置Leader选举的这种情况的选择方式)
Partition——文件存储方式
- 每个partion(目录)相当于一个巨型文件被平均分配到多个大小相等segment(段)数据文件中。但每个段segment file消息数量不一定相等,这种特性方便old segment file快速被删除。
- 每个partiton只需要支持顺序读写就行了,segment文件生命周期由服务端配置参数决定。
- 磁盘在进行非线性写入的效率很低,在知道读写顺序的情况下效率很高,这是kafka效率高,满足高吞吐量的条件。
Partition——文件
- 段落文件由三部分组成:.index文件、.log文件以及.timeindex文件。(0.8版本之前的kafka没有timeindex文件)
- index文件为索引文件,命名规则为从0开始到,后续的由上一个文件的最大的offset偏移量来开头(19位数字字符长度)
- .log文件为数据文件,存放具体消息数据
- .timeindex文件,是kafka的具体时间日志
- 如果设置log失效时间比较短,就会出现下面这种状况
Partition——index和log的匹配关系
索引文件存储大量元数据,数据文件存储大量消息,索引文件中元数据指向对应数据文件中message的物理偏移地址。其中以索引文件中 元数据3,497为例,依次在数据文件中表示第3个message(在全局partiton表示第368772个message)、以及该消息的物理偏移 地址为497。
Partition——Message的物理结构
Partition——通过Offset查找Message
读取Offset为368776的Message:
1.查询段落文件:
假设partition有两个index文件,分别为:0...0.index文件和0...383562.index文件,由于我们第二个的偏移量初始值为383562+1,而要查询的文件为368776,所以我们查询的Message在第一个0...0.log中。使用offset **二分查找**文件列表,就可以快速定位到具体文件。
2.段落文件索引与Message文件进行匹配:
通过segment file查找message通过第一步定位到segment file,当offset=368776时,依次定位到0...000.index的元数据物理位置和 0..000.log的物理偏移地址,然后再通过0...0.log顺序查找直到 offset=368776为止
Consumer——High Level
很多时候,客户程序只是希望从Kafka读取数据,不太关心消息offset的处理。同时也希望提供一些语义,例如同一条消息只被某一个Consumer消费(单播)或被所有Consumer消费(广播)。因此,Kafka Hight Level Consumer提供了一个从Kafka消费数据的高层抽象,从而屏蔽掉其中的细节并提供丰富的语义。
Consumer——Consumer Group
每一个consumer实例都属于一个consumer group,每一条消息只会被同一个consumer group里的一个consumer实例消费。(不同consumer group可以同时消费同一条消息)
Consumer——Rebalance
Kafka保证同一Consumer Group中只有一个Consumer会消费某条消息,实际上,Kafka保证的是稳定状态下每一个Consumer实例只会消费某一个或多个特定Partition的数据,而某个Partition的数据只会被某一个特定的Consumer实例所消费。
Kafka对消息的分配是以Partition为单位分配的。这样设计的劣势是无法保证同一个消费者组里的Consumer均匀消费数据,优势是每个Consumer不用都跟大量的Broker通信,减少通信开销,同时也降低了分配难度,实现也更简单。另外,因为同一个Partition里的数据是有序的,这种设计可以保证每个Partition里的数据可以被有序消费。
如果某Consumer Group中Consumer数量少于Partition数量,则至少有一个Consumer会消费多个Partition的数据,如果Consumer的数量与Partition数量相同,则正好一个Consumer消费一个Partition的数据。而如果Consumer的数量多于Partition的数量时,会有部分Consumer无法消费该Topic下任何一条消息。
kafkar的Rebalance的算法:
- 将目标Topic下的所有Partirtion排序,存于PT
- 对某Consumer Group下所有Consumer排序,存于CG
- 第i个Consumer记为Ci
- N=size(PT)/size(CG),向上取整
- 解除Ci对原来分配的Partition的消费权(i从0开始)
- 将第i∗N到(i+1)∗N−1个Partition分配给Ci
分区的算法:
分区数=Tt/Max(Tp,Tc)
Tp:producer吞吐量 Tc:consumer吞吐量 Tt目标的吞吐量
Consumer——Low Level
使用Low Level(Simple Consumer)原因是用户希望比ConsumerGroup更好的控制数据的消费:
- 同一条消息消费多次
- 只读某一个topic下的固定分区数据
- 管理事务,某一条消息仅被消费一次
额外工作:
- 必须在应用程序中跟踪offset,从而确定下一条应该消费哪条消息
- 应用程序需要通过程序获知每个Partition的Leader是谁
- 必须处理Leader的变化