秒杀项目

消息丢失(不太必要)

消息丢失可能的场景
项目中消息中间件的各种重试问题 - 图1
如何保证零消息丢失
项目中消息中间件的各种重试问题 - 图2
1、场景1中保证消息不丢失的方案是使用RocketMQ自带的事务机制来发送消息,大致流程为
首先生产者发送half消息到RocketMQ中,此时消费者是无法消费half消息的,若half消息就发送失败了,则执行相应的回滚逻辑
消息回查机制
half消息发送成功之后,且RocketMQ返回成功响应,则执行生产者的核心链路
如果生产者自己的核心链路执行失败,则回滚,并通知RocketMQ删除half消息
如果生产者的核心链路执行成功,则通知RocketMQ commit half消息,让消费者可以消费这条数据

2、在场景2中要保证消息不丢失,首先需要将os cache的异步刷盘策略改为同步刷盘,这一步需要修改Broker的配置文件,将flushDiskType改为SYNC_FLUSH同步刷盘策略,默认的是ASYNC_FLUSH异步刷盘。
一旦同步刷盘返回成功,那么就一定保证消息已经持久化到磁盘中了;为了保证磁盘损坏不会丢失数据,我们需要对RocketMQ采用主从机构,集群部署,Leader中的数据在多个Follower中都存有备份,防止单点故障。
3、在场景3中,消息到达了消费者,RocketMQ在代码中就能保证消息不会丢失
RocketMQ在消费者中注册了一个监听器,当消费者获取到了消息,就会去回调这个监听器函数,去处理里面的消息
当你的消息处理完毕之后,才会返回ConsumeConcurrentlyStatus.CONSUME_SUCCESS 只有返回了CONSUME_SUCCESS,消费者才会告诉RocketMQ我已经消费完了,此时如果消费者宕机,消息已经处理完了,也就不会丢失消息了
如果消费者还没有返回CONSUME_SUCCESS时就宕机了,那么RocketMQ就会认为你这个消费者节点挂掉了,会自动故障转移,将消息交给消费者组的其他消费者去消费这个消息,保证消息不会丢失
为了保证消息不会丢失,在consumeMessage方法中就直接写消息消费的业务逻辑就可以了,如果非要搞一些骚操作,比如下面的代码

最后兜底

消息变成死信有以下几种情况

  • 消息被拒绝(basic.reject / basic.nack),并且requeue = false
  • 消息TTL过期
  • 队列达到最大长度(队列满了,无法再添加数据到mq中)

例如消费端重试16次之后,或者ttl过期时间到了,或者队列达到最大长度
死信队列指的是种正常情况下无法被消费的消息称为死信消息(Dead-Letter Message),存储死信消息的特殊队列称为死信队列(Dead-Letter Queue)。
,在消息队列。可以在控制台Topic列表中看到“DLQ”相关的Topic,默认命名是:%RETRY%消费组名称(重试Topic)%DLQ%消费组名称(死信Topic)死信队列也可以被订阅和消费,并且也会过期RocketMQ 中其中包括重试之后也无法消费的消息也会

如何查看哪一条消息丢失?

利用RocketMq可视化工具,查看带有%RETRY%,%DLQ%,队列,查看重试和死信队列的消息,里边有消息ID,去查对应的数据重发就ok

死信队列应用场景
如我们平时下单后未在指定时间内付款,过来这个时间,我们的订单会被放入死信队列中。当我们再去付款时候,会发现订单已经被取消,此时我们只需要去死信队列中查该订单是否存在。比如一般设定都是24小时,如当一些消息出现异常迟迟未被消费(或者最大重试次数后也未成功消费),这时候就会将消息存放到死信队列中。

解决方法

其实这个就看你具体的需求了,比如我们可以专门开一个后台线程,订阅“%DLQ%WMSConsumerGroup”这个死信队列,对死信队列中的消息进行不停的重试。即使停机之后,开启之后也会继续重试

消息重试

同步默认去重试两次,异步只重试一次。也可设置重试次数为0,保证效率

单向发送(无重试)保证效率单向发送是指只负责发送消息而不等待服务器回应且没有回调函数触发,适用于某些耗时非常短但对可靠性要求并不高的场景,例如日志收集。
并不是所有异常都会去重试,只有生产者客户端异常,broker,消费端等发生异常才回去重试,比如超时异常就直接返回失败
应用场景:适用于某些耗时非常短,但对可靠性要求并不高的场景,例如日志收集。
通过这段源码很明显可以看出以下几点

  1. 如果是异步发送 那么重试次数只有1次
  2. 对于同步而言,超时异常也是不会再去重试。
  3. 如果发生重试是在一个for 循环里去重试,所以它是立即重试而不是隔一段时间去重试。

注重消费端重试
一般设置成这样的代码这里的代码意思很明显: 主动抛出一个异常,然后如果超过3次,那么就不继续重试下去,而是将该条记录保存到数据库由人工来兜底。
Timeout
说明 这里的超时异常并非真正意义上的超时,它指的是指获取消息后,因为某种原因没有给RocketMQ返回消费的状态,那么 RocketMQ会认为该消息没有发送,会一直发送。因为它会认为该消息根本就没有发送给消费者,所以肯定没消费。
及时关掉进程,再次重启之后还是会消费的,当获得 当前消费重试次数为 = 0 后 , 关掉该进程。再重新启动该进程,那么依然能够获取该条消息
消费者默认是重试16次,16次之后就不再重试。
并且重试时间间隔逐步增加1s,5s,10s,30s,1m,2m,3m,4m,5m,6m,7m,8m,9m,10m,20m,30m,1h,2h

消息幂等(必要)

幂等性:就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用,数据库的结果都是唯一的,不可变的。
只要保持幂等性,不管来多少条重复消息,最后处理的结果都一样,需要业务端来实现。
去重策略:
保证每条消息都有唯一编号(比如唯一流水号),且保证消息处理成功与去重表的日志同时出现。
建立一个消息表,拿到这个消息做数据库的insert操作。给这个消息做一个唯一主键(primary key)或者唯一约束,那么就算出现重复消费的情况,就会导致主键冲突,那么就不再处理这条消息。
消息重复
消息领域有一个对消息投递的QoS定义,分为:
最多一次(At most once)
至少一次(At least once)
仅一次( Exactly once)
RocketMQ没有内置消息去重的解决方案,最新版本是否支持还需确认。
不同的消息队列发送的确认信息形式不同,例如RabbitMQ是发送一个ACK确认消息,RocketMQ是返回一个CONSUME_SUCCESS成功标志,

分布式事务(必要)

消息堆积(必要)

消息中间件的主要功能是异步解耦,还有个重要功能是挡住前端的数据洪峰,保证后端系统的稳定性,这就要求消息中间件具有一定的消息堆积能力,消息堆积分以下两种情况:
消息堆积在内存Buffer,一旦超过内存Buffer,可以根据一定的丢弃策略来丢弃消息,如CORBA Notification规范中描述。适合能容忍丢弃消息的业务,这种情况消息的堆积能力主要在于内存Buffer大小,而且消息堆积后,性能下降不会太大,因为内存中数据多少对于对外提供的访问能力影响有限。
消息堆积到持久化存储系统中,例如DB,KV存储,文件记录形式。 当消息不能在内存Cache命中时,要不可避免的访问磁盘,会产生大量读IO,读IO的吞吐量直接决定了消息堆积后的访问能力

解决

线上故障了,怎么处理
消息堆积了10小时,有几千万条消息待处理,现在怎么办?
修复consumer, 然后慢慢消费?也需要几小时才可以消费完成,新的消息怎么办
核心思想:紧急临时扩容,更快的速度去消费数据
- 修复Consumer不消费问题,使其恢复正常消费,根据业务需要看是否要暂停
- 临时topic队列扩容,并提高消费者能力,但是如果增加Consumer数量,但是堆积的topic里面的message queue数量固定,过多的consumer不能分配到message queue

  • 编写临时处理分发程序,从旧topic快速读取到临时新topic中,新topic的queue数量扩容多倍,然后再启动更多consumer进行在临时新的topic里消费

  • 直到堆积的消息处理完成,再还原到正常的机器数量,删除这个临时队列。

    如何保证顺序消息?(必要)

    在某些业务中,consumer在消费消息时,是需要按照生产者发送消息的顺序进行消费的,比如在电商系统中,订单的消息,会有创建订单、订单支付、订单完成,如果消息的顺序发生改变,那么这样的消息就没有意义了。 生产者,消费者都要保证,从同一个队列
    这玩意是阿里开源的,生产者消费者一般需要保证顺序消息的话,可能就是一个业务场景下的,比如订单的创建、支付、发货、收货。
    那这些东西是不是一个订单号呢?一个订单的肯定是一个订单号的说,那简单了呀。
    一个topic下有多个队列,为了保证发送有序,RocketMQ提供了MessageQueueSelector队列选择机制,他有三种实现: 用唯一id进行hash取模,hash值不唯一
    我们可使用Hash取模法,让同一个订单发送到同一个队列中,再使用同步发送,只有同个订单的创建消息发送成功,再发送支付消息。这样,我们保证了发送有序。
    RocketMQ的topic内的队列机制,可以保证存储满足FIFO(First Input First Output 简单说就是指先进先出),剩下的只需要消费者顺序消费即可。
    RocketMQ仅保证顺序发送,顺序消费由消费者业务保证!!!
    这里很好理解,一个订单你发送的时候放到一个队列里面去,你同一个的订单号Hash一下是不是还是一样的结果,那肯定是一个消费者消费,那顺序是不是就保证了?

    简单集群模式及问题?

    一般采用 我只了解这一个
    多 master 模式:
    多个 master 节点组成集群,单个 master 节点宕机或者重启对应用没有影响。
    优点:所有模式中性能最高
    缺点:单个 master 节点宕机期间,未被消费的消息在节点恢复之前不可用,消息的实时性就受到影响。
    注意:使用同步刷盘可以保证消息不丢失,同时 Topic 相对应的 queue 应该分布在集群中各个节点,而不是只在某各节点上,否则,该节点宕机会对订阅该 topic 的应用造成影响。