首先说下顺序性消费带来的问题,都不考虑链式调用业务,就单纯互斥操作的业务,单机mq,单机redis环境,在mq消息等待被消费时,A消息进入队列等待被消费,B消息进入队列,此时A消息未消费完毕,B要根据A消息的结果进行操作,目前发生的问题就是多线程并发调用时,mq消费并没有按着先进先出的顺序进行消费,在同一时刻库里存在相同数据,但这是不允许的,在此期间如果其他人去查看数据,如果来自数据库,那必定是不准确的。所以部分业务查的redis,但更坏的情况,此时redis凉了怎么办?
业务的ABA问题
业务1将数据A从缓存中取到,业务B将数据A从缓存中取到并将A变成了B,然后又将B变成了A,业务1发现此时数据仍是A,A操作成功,尽管业务操作时成功的,但不代表整个过程就没问题。另外,虽然顺序性问题有可能带来的结果是一致的,但是不代表这个过程中影响的其他数据就没问题。
如图,同一时刻库中存入相同数据6条,但严谨逻辑只能允许库中存在相同数据只有一条,如果此时其他业务查询相关数据,必定会出现多结果返回
设想解决方案一:原子计数
方案:在每条数据进行操作之前,先判断两次操作数据是否为同一个,即redis的kv为同一个,如果相同则此时对key进行标记incr,步进1,在mq进行消费时,从redis中取出此key的步进值与传进来的步进值比较,按理说只需要记录想用kv值得最后最大步进值得操作
Long incr = redisService.incr(key,1);
Object o = redisService.getIncrValue("key");
if(o.toString().equals(vo.getIncr())){
//业务逻辑
}
结果测试:同样会出现此问题。一开始想到当前页面为同一用户对同一信息的操作,不会出现或者点不出如此高的并发,那不考虑接口被恶意攻击的情况,使用jemeter进行动态传参结果如何呢?
每次的参数入参读取文件值,使每次结果为不同的操作,或者两台机器为同一账户对同一信息进行操作,必定会产生网络延迟造成对结果不一致的问题。
设想解决方案二:分布式锁
方案:对当前操作进行加锁,并保证同一值得锁操作为同一把锁时进行业务处理,集群模式下对消息进行分组,具体方案不成熟,引入redisson处理,但也疑问为了保证顺序性问题引入分布式锁的成本与性能问题,最终没能达到理想结果。
public Boolean tryLock(String lockKey, TimeUnit unit, int waitTime, int leaseTime) {
RLock lock = redissonClient.getLock(lockKey);
try {
return lock.tryLock(waitTime,unit);
} catch (Exception e) {
return false;
}
}