kafka
MQ
MQ(message queue),从字面意思上看,本质是个队列,FIFO 先入先出,只不过队列中存放的内容是 message 而已,还是一种跨进程的通信机制,用于上下游传递消息。在互联网架构中,MQ 是一种非常常 见的上下游「逻辑解耦 + 物理解耦」的消息通信服务。使用了 MQ 之后,消息发送上游只需要依赖 MQ,不用依赖其他服务。
MQ优势
异步处理- 相比于传统的串行、并行方式,提高了系统吞吐量。应用解耦- 系统间通过消息通信,不用关心其他系统的处理。流量削锋- 可以通过消息队列长度控制请求量;可以缓解短时间内的高并发请求。日志处理- 解决大量日志传输。消息通讯- 消息队列一般都内置了高效的通信机制,因此也可以用在纯的消息通讯。比如实现点对点消息队列,或者聊天室等。
解耦、异步、削峰
流量消峰 先将短时间高并发产生的事务消息存储在消息队列中,然后后端服务再慢慢根据自己的能力去消费这些消息,这样就避免直接把后端服务打垮掉。
应用解耦 生产者(客户端)发送消息到消息队列中去,接受者(服务端)处理消息,需要消费的系统直接去消息队列取消息进行消费即可而不需要和其他系统有耦合,这显然也提高了系统的扩展性。
异步 允许用户把一个消息放入队列,但并不立即处理它,然后在需要的时候再去处理它们。将用户的请求数据存储到消息队列之后就立即返回结果。随后,系统再对消息进行消费。使用消息队列进行异步处理之后,需要适当修改业务流程进行配合。
Kafka基础
Kafka传统定义:Kafka是一个分布式的基于发布/订阅模式的消息队列(Message Queue),主要应用于大数据实时处理领域。
Kafka 最 新 定 义 : Kafka 是 一 个 开 源 的 分 布 式 事 件 流 平 台 ( Event Streaming Platform),被用于高性能数据管道、流分析、数据集成和关键任务应用。
模型
将消息以 topic 为单位进行归纳。
向 Kafka topic 发布消息的程序成为 producers. 将预订 topics 并消费消息的程序成为 consumer.
Kafka 以集群的方式运行,可以由一个或多个服务组成,每个服务叫做一个 broker.
发布订阅模型(Pub-Sub) 使用主题(Topic) 作为消息通信载体,类似于广播模式;发布者发布一条消息,该消息通过主题传递给所有的订阅者,在一条消息广播之后才订阅的用户则是收不到该条消息的。
- Producer(生产者) : 产生消息的一方。
- Consumer(消费者) : 消费消息的一方。
- Broker(代理) : 可以看作是一个独立的 Kafka 实例。多个 Kafka Broker 组成一个 Kafka Cluster。
ack取值
1(默认) 数据发送到Kafka后,经过leader成功接收消息的的确认,就算是发送成功了。
0 生产者将数据发送出去就不管了,不去等待任何返回。
-1producer需要等待ISR中的所有follower都确认接收到数据后才算一次发送完成,可靠性最高。
消息接发模式
生产者使用push模式将消息发布到Broker,消费者使用pull模式从Broker订阅消息。
Kafka 有个参数可以让 consumer 阻塞知道新消息到达(当然也可以阻塞知道消息的数量达到某个特定的量这样就可以批量发)
Kafka优势
- Cache Filesystem Cache PageCache缓存
顺序写:由于现代的操作系统提供了预读和写技术,磁盘的顺序写大多数情况下比随机写内存还要快。Zero-copy:零拷技术减少拷贝次数Batching of Messages:批量处理。合并小的请求,然后以流的方式进行交互,直顶网络上限。Pull 拉模式:使用拉模式进行消息的获取消费,与消费端处理能力相符。
顺序写
Kafka 用的是顺序写,追加数据是追加到末尾,磁盘顺序写的性能极高,在磁盘个数一定,转数达到一定的情况下,基本和内存速度一致。 随机写是在文件的某个位置修改数据,性能会较低。
零拷贝
Zero Copy使用场景的地方有两处:基于mmap的索引和日志文件读写所用的TransportLayer。
索引都是基于MappedByteBuffer的,也就是让用户态和内核态共享内核态的数据缓冲区,此时,数据不需要复制到用户态空间。
TransportLayer是Kafka传输层的接口。它的某个实现类使用了FileChannel的transferTo方法。该方法底层使用sendfile实现了Zero Copy。对Kafka而言,如果I/O通道使用普通的PLAINTEXT,那么,Kafka就可以利用Zero Copy特性,直接将页缓存中的数据发送到网卡的Buffer中,避免中间的多次拷贝。相反,如果I/O通道启用了SSL,那么,Kafka便无法利用Zero Copy特性了。
Kafka消费
消费顺序
主要需要考虑如下两点:
- 如何保证消息在
Kafka中顺序性; - 如何保证消费者处理消费的顺序性。
使用**Partition(分区) **保证消息的顺序。在订单 topic 中我们可以指定订单 id 作为 key,那么相同订单 id 的数据,一定会被分发到同一个 partition 中去,而且这个 partition 中的数据一定是有顺序的。消费者从 partition 中取出来数据的时候,也一定是有顺序的。通过制定 key 的方式首先可以保证在 kafka 内部消息是有序的。 每次添加消息到 Partition(分区) 的时候都会采用尾加法。
消息在被追加到 Partition(分区)的时候都会分配一个特定的偏移量(offset)。Kafka 通过偏移量(offset)来保证消息在分区内的顺序性。在Kafka中,每个主题分区下的每条消息都被赋予了一个唯一的ID数值,用于标识它在分区中的位置。这个ID数值,就被称为位移,或者叫偏移量。一旦消息被写入到分区日志,它的位移值将不能被修改。
保证消息在 Kafka 中顺序性,两种方法:
- 1 个 Topic 只对应一个 Partition。
- (推荐)发送消息的时候指定 key/Partition。
对于多线程消费我们可以预先设置 N 个内存 Queue,具有相同 key 的数据都放到同一个内存 Queue 中;然后开启 N 个线程,每个线程分别消费一个内存 Queue 的数据即可,这样就能保证顺序性。
消息丢失
Consumer消费端消息丢失
手动关闭自动提交 offset,每次在真正消费完消息之后再自己手动提交 offset,比如消费者刚处理完,还没提交 offset,这时自己宕机了,此时这条消息肯定会被重复消费一次,这就需要消费者根据实际情况保证幂等性。
Producer生产端消息丢失
正确处理返回值或者捕获异常,就可以保证这个阶段消息不会丢失。通过在 producer 端设置 acks=-1/all 来处理,这个参数是要求 leader 接收到消息后,需要等到所有的 follower 都同步到了消息之后,才认为本次写成功了。如果没满足这个条件,生产者会自动不断的重试。
Broker存储端消息丢失
- 给
topic设置replication.factor参数:这个值必须大于1,要求每个partition必须有至少2个副本; - 在
Kafka服务端设置min.insync.replicas参数:这个值必须大于1,这个参数的含义是一个leader至少感知到有至少一个follower还跟自己保持联系,没掉队,这样才能确保leader挂了还有一个follower节点。 - 在
producer端设置acks=all,这个是要求每条数据,必须是写入所有replica之后,才能认为是写成功了; - 在
producer端设置retries=MAX(很大很大很大的一个值,无限次重试的意思):这个参数的含义是一旦写入失败,就无限重试,卡在这里了。
Kafka 为分区(Partition)引入了多副本(Replica)机制。分区(Partition)中的多个副本之间会有一个叫做 leader 的家伙,其他副本称为 follower。生产者和消费者只与 leader 副本交互。
重复消费
重复消费的原因:
- 服务端侧已经消费的数据没有成功提交 offset(根本原因)。
- Kafka 由于服务端处理业务时间长或者网络链接等等原因让 Kafka 认为服务假死,触发了分区 rebalance。
解决方案:
- 消费消息服务做幂等校验,比如 Redis 的 set、MySQL 的主键等天然的幂等功能。这种方法最有效。
- 将
enable.auto.commit参数设置为 false,关闭自动提交,开发者在代码中手动提交 offset。
幂等性:多次调用方法或者接口不会改变业务状态,可以保证重复调用的结果和单次调用的结果一致。
消息积压
主要原因是,对于绝大多数使用消息队列的业务来说,消息队列本身的处理能力要远大于业务系统的处理能力。
- 发送端:检查发送端发送消息前业务逻辑耗时。
- 消费端:保证消费端的消费性能要高于生产端的发送性能。增加消费端的并发数,在扩容 Consumer 的实例数量的同时,必须同步扩容主题中的分区(也叫队列)数量,确保 Consumer 的实例数和分区数量是相等的。如果 Consumer 的实例数量超过分区数量,这样的扩容实际上是没有效果的。原因因为对于消费者来说,在每个分区上实际上只能支持单线程消费。
处理方法:
- 如果是单位时间发送的消息增多,短时间内不太可能优化消费端的代码来提升消费性能,唯一的方法是通过扩容消费端的实例数来提升总体的消费能力。
- 将系统降级,通过关闭一些不重要的业务,减少发送方发送的数据量,最低限度让系统还能正常运转,服务一些重要业务。
- 消费失败导致的一条消息反复消费,这种情况也会拖慢整个系统的消费速度。
不支持读写分离
在 Kafka 中,生产者写入消息、消费者读取消息的操作都是与 leader 副本进行交互的,从而实现的是一种主写主读的生产消费模型。
主写从读有 2 个很明显的缺点:
- 数据一致性问题。数据从主节点转到从节点必然会有一个延时的时间窗口,这个时间窗口会导致主从节点之间的数据不一致。
- 延时问题。数据从写入主节点到同步至从节点中的过程需要经历
网络→主节点内存→网络→从节点内存这几个阶段,整个过程会耗费一定的时间。而在 Kafka 中,它需要经历网络→主节点内存→主节点磁盘→网络→从节点内存→从节点磁盘这几个阶段。对延时敏感的应用而言,主写从读的功能并不太适用。