记一次需要对消息排冲的处理

问题出现

  微信给业务系统发送消息通知时,同时落了两条表记录。

问题排查

1. 从服务端拿下来日志进行分析,定位到下图的信息,发现在同一时间有两个http的线程进行了操作,所以继续往上追溯查看这两个线程是什么时候创建的?

2. 找到了两个线程的创建位置,发现他们的时间差了5s,想起了微信发送消息的机制,于是去查阅。

 

3. 在微信的文档中果然发现了是业务系统在5s内没有执行完成,微信于是重新发了一条消息,然后在落库的时间点正好卡到了一起,并不是我一开始想的插入数据库的问题被执行了两次。

 

4.这样就排查到了问题所在

解决方案

微信文档上给出了解决方案,可以对消息进行排重就可以了。对该问题进行了分析,其实就是如何保证消息的幂等性。大概想了几种解决方案。

1.  接收到消息的时候写入到redis,下次接收到消息的时候去redis中去查重。由于项目本身没有使用redis,这种方案有点用开天斧劈柴,大材小用了,所以放弃了。

2.  对落库的数据进行添加一个唯一性的字段,这样第二次插入的时候就不会重复了。但这种方案有点头疼医疼,脚疼医脚,并没有从源头去解决问题,所以放弃了。

3.  这种方案其实有点对第一种方案的变种,redis如我们所知,是一种内存数据库。那么其实就可以直接用java存储到内存中,然后这种排重很适合map的结构,用key进行记录排重的key,用value的不同值来判断是否消费过,如null没有消费过,1就是消费过。

那么我们就选择了map,由于可能或多个线程进行操作所以选择了ConcurrentHashMap这个类,但是同时也要考虑到这个map如果一直赋值的话,可能会把内存塞满,导致oom。所以我们还需要对这个map实现一个内存淘汰机制,内存淘汰机制在java中最简单的实现可以用LinkedHashMap来实现,但是LinkedHashMap并不是线程安全的。这里两种就冲突了。在这两种中就进行了选择,是使用ConcurrentHashMap来实现内存淘汰机制,还是使用LinkedHashMap来实现线程安全。思考一番,发现后者实现起来会比较方便,只要实现一个继承于LinkedHashMap的子类,同时对put和get方法使用lock或者synchronized都可以实现线程安全。

 

代码就不贴了,主要讲一个思路,毕竟代码其实很简单。

 

posted @ 2021-03-31 21:22  死水流年  阅读(84)  评论(0)    收藏  举报