MQ坑【done】【重要】
1 消费端自动消费ack还是手动
1.1 自动,抓取,然后断电怎么办——无解
1.2 手动,db处理好了,然后断电ack没发出去怎么办
二阶段分布式锁
第一把锁放顶上,表示开始处理,putifabsent(thread_id),两个场景,
1.1)防止短期重复投递,比如生产端ack延迟客户端投了两次
1.2)锁后消费者当机
第二把锁放尾部,表示这个消息正式处理好了,set紧跟数据库commit,尽可能原子,两个场景,
2.1)db处理好后,ack发送失败或当机
2.2)消费者ack了到一个节点,该节点立马挂了,mq集群来不及对ack共识,超时重复投递给消费者
1 pull
get key if == 2, then goto 7 防场景1.1生产端重复投递
2 putifabsentexpire(key, thread_id, t<mq timeout,防场景1.2停电,那么mq重发时这个key已经没了)
get key == thread_id? goto 3 : quit
3 start transaction
4 do sth
5 commit transaction
6 set(key, 2, >mq timeout,如果6之后停电,那么mq重发时这个key还在就知道db已经提交了,防2.1+2.2)
7 send ack to mq
1后面断电,mq会超时重发
2后面断电,mq多次超时重发,10s后其他consumer节点消费
3后面断电,同2
4后面断电,同2,db心跳超时回滚
5后面断电,无解,所以4和5要近,尽可能原子化
6后面断电,mq超时重发,业务线程检测到status是2,直接ack
注意
redis expire的不准确性
消费者的运行时间要尽可能精确,依赖阻塞超时精确,<生产端/消费端timeout
这把复合锁
防重,时间要尽可能长,至少比消费timeout长
防少,时间要尽可能少,趋于0,至少比消费timeout短
2 重复投递和幂等:生产方放一个id(雪花/redis)进去,然后redis putifabsentexpire/数据库唯一索引+0,以写入操作确保强一致性,路由到主节点,牺牲可用性(主节点可能挂)保护一致性场景;(2.1可做主从不一致保护,无需牺牲性能)期间qps注意保护(性能不够可用对redis id分片)
2.1 putifabsent之坑,由于从节点延时,不能完全相信putifabsent返回,可以再加一个判断是否里面的值是本线程
2.2 假如putifabsent了,这个时候断电了,mq没有收到手动ack则会超时重传,所以expire应该比mq这个超时时间小一点,1s(客户端重复投递可能间隔)<redis expire<mq timeout
不保险,无法处理
2.3 异常最好能够抓住并直接删除分布式锁(虽然redis会expire早于mq超时重传);多次则进入死信队列
为什么业务层一定要自己做幂等?目前没有mq能原生保证exactly once(只能at most once)


1 rocketmq可以基本保证生产端幂等;kafka要应用层自己做msgid确保消费者能幂等以及生产者有序

2 有序:单生产者线程-单partition/队列;生产方放一个有序id(redis)(rocketmq就这样),消费者视具体业务决定错序时的措施


2.1 多线程生产者,必然会乱序,比如A比B入库早,但是A线程失去时间片更久或者其他原因,导致A的消息进内核缓冲区晚
2.2 单线程生产者,则消息进内核一定顺序?指令重排?超时重传?保险做法➕内存屏障?
2.3 传输层重传-假设消息进内核顺序,则接下去操作系统tcp协议会负责顺序性
2.4 应用层重传,只能依托消费端处理

消息丢失(生产端ack/重传,mq集群路由强一致性(2.2)?mq页缓存强刷(2.3)?,rocketmq事务,rocketmq order msg
消费跟不上,则增加消费者 along with partition分片

浙公网安备 33010602011771号