Kafka 如何保证消息不丢失?
kafka 是一个用来实现异步消息通信的中间件,它的整个架构由Producer、 Consumer、Broker 组成。
对于kafka 如何保证消息不丢失这个问题,可以从三个方面来考虑和实现。
首先是Producer 端
需要确保消息能够到达Broker 并实现消息存储,在这个层面,有可能出现网络问题,导致消息发送失败,所以,针对Producer 端,可以通过2 种方式来避免消息丢失,把异步发送改成同步发送,这样producer 就能实时知道消息发送的结果。添加异步回调函数来监听消息发送的结果,如果发送失败,可以在回调中重试。
然后是Broker 端
Broker 需要确保Producer 发送过来的消息不会丢失,也就是只需要把消息持久化到磁盘就可以了。
最后,就是Consumer
必须要能消费到这个消息,实际上,我认为,只要producer和broker 的消息可靠的到了保障,那么消费端是不太可能出现消息无法消费的问题,除非是Consumer 没有消费完这个消息就直接提交了,但是即便是这个情况,也可以通过调整offset 的值来重新消费。
kafka 的零拷贝原理?
在实际应用中,如果我们需要把磁盘中的某个文件内容发送到远程服务器上 那么它必须要经过几个拷贝的过程,如图(贴图)。
- 从磁盘中读取目标文件内容拷贝到内核缓冲区
- CPU 控制器再把内核缓冲区的数据赋值到用户空间的缓冲区中
- 接着在应用程序中,调用write()方法,把用户空间缓冲区中的数据拷贝到内核下 的Socket Buffer 中。
- 最后,把在内核模式下的SocketBuffer 中的数据赋值到网卡缓冲区(NIC Buffer)
- 网卡缓冲区再把数据传输到目标服务器上。
在这个过程中我们可以发现,数据从磁盘到最终发送出去,要经历4 次拷贝,而在这四 次拷贝过程中,有两次拷贝是浪费的,分别是:
- 从内核空间赋值到用户空间
- 从用户空间再次复制到内核空间
除此之外,由于用户空间和内核空间的切换会带来CPU 的上线文切换,对于CPU 性能 也会造成性能影响。
而零拷贝,就是把这两次多于的拷贝省略掉,应用程序可以直接把磁盘中的数据从内核 中直接传输给Socket,而不需要再经过应用程序所在的用户空间,如下图所示。
零拷贝通过DMA(Direct Memory Access)技术把文件内容复制到内核空间中的Read Buffer, 接着把包含数据位置和长度信息的文件描述符加载到Socket Buffer 中,DMA 引擎直 接可以把数据从内核空间中传递给网卡设备。 在这个流程中,数据只经历了两次拷贝就发送到了网卡中,并且减少了2 次cpu 的上 下文切换,对于效率有非常大的提高。 所以,所谓零拷贝,并不是完全没有数据赋值,只是相对于用户空间来说,不再需要进 行数据拷贝。对于前面说的整个流程来说,零拷贝只是减少了不必要的拷贝次数而已。 在程序中如何实现零拷贝呢?
- 在Linux 中,零拷贝技术依赖于底层的sendfile()方法实现
- 在Java 中,FileChannal.transferTo() 方法的底层实现就是sendfile() 方法。 除此之外,还有一个mmap 的文件映射机制 它的原理是:将磁盘文件映射到内存, 用户通过修改内存就能修改磁盘文件。使用这种 方式可以获取很大的I/O 提升,省去了用户空间到内核空间复制的开销。
RabbitMQ 的消息如何实现路由?
RabbitMQ 是一个基于AMQP 协议实现的分布式消息中间件。AMQP 的具体工作机制是,生产者把消息发送到RabbitMQ Broker 上的 Exchange 交换机上。
Exchange 交换机把收到的消息根据路由规则发给绑定的队列(Queue)。最后再把消息投递给订阅了这个队列的消费者,从而完成消息的异步通讯。
其中,Exchange 是一个消息交换机,它里面定义了消息路由的规则,也就是这个消息路由到那个队列。然后Queue 表示消息的载体,每个消息可以根据路由规则路由到一个或者多个队列里面。
而关于消息的路由机制,核心的组件是Exchange。它负责接收生产者的消息然后把消息路由到消息队列,而消息的路由规则由ExchangeType 和Binding 决定。
Binding 表示建立Queue 和Exchange 之间的绑定关系,每一个绑定关系会存在一个BindingKey。通过这种方式相当于在Exchange 中建立了一个路由关系表。
生产者发送消息的时候,需要声明一个routingKey(路由键),Exchange拿到routingKey 之后,根据RoutingKey 和路由表里面的BindingKey 进行匹配,而匹配的规则是通过ExchangeType 来决定的。
在RabbitMQ 中,有三种类型的Exchange:direct ,fanout 和topic。
- direct: 完整匹配方式,也就是Routing key 和Binding Key 完全一致,相当于点对点的发送。
- fanout: 广播机制,这种方式不会基于Routing key 来匹配,而是把消息广播给绑定到当前Exchange 上的所有队列上。
- topic: 正则表达式匹配,根据Routi
如何保证RabbitMQ 的消息可靠传输
首先,在RabbitMQ 的整个消息传递过程中,有三种情况会存在丢失。
生产者把消息发送到RabbitMQ Server 的过程中丢失
RabbitMQ Server 收到消息后在持久化之前宕机导致数据丢失
消费端收到消息还没来得及处理宕机,导致RabbitMQ Server 认为这个消息已签收 所以,我认为只需要从这三个纬度去保证消息的可靠性传输就行了。
从生产者发送消息的角度来说
RabbitMQ 提供了一个Confirm(消息确认)机制,生产者发送消息到Server 端以后,如果消息处理成功,Server 端会返回一个ack 消息。客户端可以根据消息的处理结果来决定是否要做消息的重新发送,从而确保消息一定到达RabbitMQ Server 上。
RabbitMQ Server 端来说
可以开启消息的持久化机制,也就是收到消息之后持久化到磁盘里面。设置消息的持久化有两个步骤。1: 创建Queue 的时候设置为持久化,2:发送消息的时候,把消息投递模式设置为持久化投递。
从消费端的角度来说
我们可以把消息的自动确认机制修改成手动确认,也就是说消费端只有手动调用消息确认方法才表示消息已经被签收。这种方式可能会造成重复消费问题,所以这里需要考虑到幂等性的设计。
RabbitMQ 如何实现高可用
在分布式架构下,高可用是最基础的设计。也就是说,一旦依赖的某个服务出现故障,不能影响业务的正常执行。 RabbitMQ 提供了两种集群模式:
普通集群模式
一个Queue 的消息只会存在集群的一个节点上,集群里面的其他节点会同步Queue 所在节点的元数据,消息在生产和消费的时候,不管请求发送到集群的哪个节点,最终都会路由到Queue 所在节点上去存储和拉取消息。这种方式并不能保证Queue 的高可用性,但是它可以提升RabbitMQ 的消息吞吐能力
镜像集群模式
集群里面的每个节点都会存储Queue 的数据副本。意味着每次生产消息的时候,都需要把消息内容同步给集群中的其他节点。这种方式能够保证Queue 的高可用性,但是集群副本之间的同步会带来性能的损耗。
如何处理消息队列的消息积压问题
通常来说,消息积压的原因是生产者的消息生产速度大于消费者的消费速度,遇到这个问题的时候,需要排查具体的原因再提出解决方案。
如果当前不是因为系统bug 导致的,那我们可以优化消费端的逻辑,比如通过异步的方式来处理消息、或者通过批量处理的方式来消费。
如果通过这两种优化方式还没有缓解,可以考虑对消费端进行水平扩容,从而扩大消费端的消费能力。
如果是因为系统bug 导致大量消息堆积,那么首先需要解决系统bug,然后临时做紧急扩容来完成大量消息的消费。
多线程异步和MQ 有什么区别?
多线程和MQ 虽然在特性上都支持程序的异步操作,但是在实现本质上区别比较大,
我简单说一下较大区别的几个点
处理任务的维度不同,多线程是同一个进程中的多个线程并行处理任务,MQ 是通过把消息发送到不同应用节点的不同进程来
处理任务
数据可靠性不同,多线程异步处理任务时,数据是基于共享内存来交互的,一旦程序崩溃,内存中的数据会丢失;使用MQ 时,可以通过消息队列的持久化机制来保证消息的可靠性
分布式能力方面,MQ 具备分布式能力,可以把消息分发到不同的节点存储和消费、多线程只能在一个进程中处理任务。