前言
系统上线一段时间后,发现消费消息中间件的listener处理新增数据,新增了两条重复数据。消息中间件中难免有发送重复的消息的情况,代码中也考虑到了这样的情况,每次消费消息的时候先去判断数据库中是否有相同唯一标识的数据,如果没有则执行新增,如果有则执行修改。但是这样的处理方法仍然会出现新增了多条重复数据的情况。
原因
仔细分析原因,如果把判断数据库是否有相同唯一标识的数据与新增或修改数据库看成一个操作的话,这个操作不是原子的。
由于消息中间件的消费者(我们的系统)是一个集群,当消息中间件发送两条重复的消息,集群中的一个机器A消费消息时判断数据库中没有有相同唯一标识的数据,正准备新增数据,同时机器B正消费另一条相同的消息,此时机器A新增数据操作没有执行,机器B判断数据库中没有有相同唯一标识的数据,也去新增数据。这样就造成了新增了两条重复的数据。
相关的流程可以用图表示:其中1步骤为判断数据库中是否有相同唯一标识的数据;2步骤为新增数据。机器A与机器B同时判断出没有唯一标识的数据,都去数据库新增了数据。没有解决消息中间件消息重复的情况。
解决方法方法一
方法一是在数据库中的唯一标识加上唯一标识,如果在新增唯一标识的数据,mysql会抛出异常,新增会不成功。
如果使用mysql数据库,可以使用语句,这个语句的意思是新增如果出现重复则执行操作。
例子
如一个需求是提供一个接口给 IOS 端,用于上报用户 id,设备,在线状态等信息,接口以 GET 方式传参,用户每次打开或退出 APP 就请求接口。可以使用这样的语句:
方法二
将判断数据库中是否有相同唯一标识的数据与新增数据变成一个事务。这样这两个操作变成一个原子操作。
当操作抛出异常,事务会catch异常,回滚操作;这样就不能重试消费失败的消息,因为消息重试是需要抛出异常。
同时变成事务后,会对数据库加锁,造成阻塞,性能比方法一较差。
领取专属 10元无门槛券
私享最新 技术干货