1 Kafka
1.1 kafka简介:
消息队列的好处 : 解耦合 、 异步处理 、 流量削峰
消息队列的缺点 系统可用性降低、系统复杂度提高、一致性问题
1.1 消费模式:
一对一消费 : 消息生产者发布消息到Queue队列中,通知消费者从队列中拉取消息进行消费。消息被消费之后则删除,Queue支持多个消费者,但对于一条消息而言,只有一个消费者可以消费,即一条消息只能被一个消费者消费。
一对多消费 :也就是发布订阅模式,发布到topic中的数据可以被多个消费者消费,消费之后,数据不会被清除,kafka默认保留一段时间,再删除。
1.2 文件存储方式
Kafka文件存储也是通过本地落盘的方式存储的,主要是通过相应的log与index等文件保存具体的消息文件。生产者不断向log文件追加消息。为了防止文件过大,导致定位效率下降,常选择每写1G文件,重新创建一个log文件。kafka使用分片+索引的方式来进行定位。
1.3 kafka如何保证数据安全?
- 生产者ISR(同步副本集) : 为保证producer发送的数据能够可靠的发送到指定的topic中,topic的每个partition收到producer发送的数据后,都需要向producer发送ack,如果producer收到ack就会进行下一轮的发送,否则重新发送数据。
- 副本数据同步 :全部follower同步完成完成发送ack,这种方案在容错率上面更加有优势,同时对于分区的数据而言,每个分区都有大量的数据。虽然网络延迟较高,但是网络延迟对于Kafka的影响较小。
1.4 如何保证消息不被重复消费,保证消息的幂等性?
这个需要结合具体业务来处理,下面举几个例子:
- 要写数据库,那么就根据该消息生成唯一主键,在写入之前,先判断该主键是否存在,若存在,则不需要插入改为更新操作
- 写redis , 没有问题,每次操作都是set,天然具备幂等性
- 复杂一些,需要生产者发送消息时,每次都带上一个全局唯一的id值,类型与订单号,处理之前先根据id去查询一下,看看是否已经处理过该条消息。
- 基于数据库的唯一键来保证重复数据不会差入多条。
1.5 数据一致性问题:
-
follower故障: follower发生故障后会被临时提出ISR,等待该follower恢复后,follower会读取本地磁盘记录的上次的HW,并将log文件高于HW的部分截取掉,从HW开始向leader进行同步,等待该follower的LEO大于等于该partition的HW,即follower追上leader之后,就可以重新加入ISR了。
-
leader故障: 从follower中选举出一个 ,将其升级为leader。为了保证多个副本之间的数据的一致性,其余的follower会先将各自的log文件高于HW的部分截掉,然后从新的leader中同步数据。
1.6 如何保证消息安全传输,不丢失?
1.6.1 生产者弄丢数据
如设置了acks=all,一定不会丢,要求是,你的leader接收到消息,所有的follower都同步到了消息之后,才认为本次写成功了。如果没满足这个条件,生产者会自动不断的重试,重试无限次。
1.6.2 kafka弄丢数据
给每一个leader配置至少一个follower ; leader至少感知到一个follower已经复制过消息之后,才能返回消息写入成功的信息。
1.6.3 消费者弄丢数据
kafka会自动提交offset,只要把自动提交关闭,在处理结束后手动提交offset,就可以保证数据不会丢失。但是这种情况可能会导致消息的重复消费,需要额外的操作保证它自己的幂等性。
1.7 kafka如何保证消息消费的顺序
写N个内存queue,具有相同key值的数据都放在同一个内存queue中,然后对于每一个N线程来讲,内部的消息消费都一定是顺序消费的。
1.8 如何解决消息队列的延时以及过期消费的问题?消息满了要如何处理?
1.8.1 消息堆积
- 修复consumer的问题
- 新建一个topic,容量是原来的10倍那么大
- 写一个临时发访consumer的程序,并征召其他的机器一起以10倍的速度来消费堆积的消息
- 堆积的数据消费完毕之后,再恢复到原来的配置
1.8.2 消息过期失效了怎么办?
批量重导 当消息大量堆积的时候,可以先将消息丢弃掉,等过掉高峰期的时候 ,再编写程序,将丢失的消息重新查到,写入到消息队列中。
1.8.3 消息满了 (存疑)
临时写程序,接入数据来消费。消费一个丢弃一个,快速消费掉所有的数据,然后在等到有空的时候,将数据补回来。
2 RabbitMQ
2.1 RabbitMQ的高可用性:
RabbitMQ有三种模式:单机模式、普通集群模式、镜像集群模式,其中单机模式只是自己玩玩,没实际应用价值;普通集群模式没有高可用性,只是一个普通的集群,这个模式主要是提高吞吐量。镜像集群模式可以保证高可用性:创建的queue,无论是元数据还是queue中的消息,都会存在于多个实例上,即每个RabbitMQ节点都有这个queue的一个完整镜像。然后每次你写消息到 queue 的时候,都会自动把消息同步到多个实例的 queue 上。
这样做,可以保证任何一个机器宕机后,其他节点仍然保存有queue的完整数据。但是性能开销过大,而且不是分布式的 ,没有扩展性可言。
2.2 如何保证消息安全传输,不丢失?
2.2.1 生产者弄丢了数据
可以开启RabbitMQ提供的事务功能,在消息成功被RabbitMQ接收后,再完成事务,否则进行回滚操作。但是这样子吞吐量会下降。可以在生产者设置开启confirm模式,每条需要写入的消息都会分配一个全局唯一的id值,写入成功后,RabbitMQ会返回一个ACK,表明接收成功。若超过一定时间没有接收到这个确认信号的话 ,重新写入。这二者的区别在于,事务处理是同步的 ,而confirm是异步的 ,可以减轻系统的压力。
2.2.2 RabbitMQ自己弄丢了数据
开启RabbitMQ的持久化操作,
2.2.3 消费端弄丢了数据
比如刚刚消费到,但是还没处理,进行就挂了,消息就丢失了。针对这个问题,需要用到RabbitMQ提供的ack机制。关闭RabbitMQ的自动ack,在自己代码里确保处理完的时候,再在程序里面ack。
2.3 RabbitMQ如何保证消息的有序消费
拆分多个queue,每个queue对应一个consumer,只是多一些queue,就可以做到有序消费。
3 如何自己设计一个消息队列呢
写一点自己的思考:
- 消息队列 ,首先肯定要支持自动伸缩,也就是在线扩容。可以参考kafka的设计理念:broker-topic-partition,每个partition放一个机器,如果容量不够了,增加机器就好
- 消息的持久化机制,要落地磁盘的。落磁盘的话,采用顺序写的方式,效率更高。
- 消息队列的可用性:参考kafka的高可用机制,采用多副本机制进行处理
- 支持数据0丢失:等副本里面也存储了消息之后,才算消息写入成功。