通过模拟发送mq消息来测试实现-依据支付凭证不能重复入账
1.依据MQ消息的json串转换为md5记录,作为收银台表的唯一约束。如果支付状态发生变化,则payMd5会跟随着变化。
2.消息流程
客户支付成功 > 微信支付微服务接收到微信支付的异步通知回调通知 > 发送给支付网关微服务(发送mq消息在本地数据库落库记录) >
发送支付成功的MQ消息,rabbitmq topic方式 (防止MQ消息在服务重启等丢失,加固方案:同时加上接口推送双重保险)
业务微服务接收到支付成功的通知
1.首先检查该条记录的唯一约束字段payMd5,select查询数据库收银台表是否存在且状态是否成功。
2.存在且成功,则不做处理。 >> 幂等处理,防止Mq和接口的双重通知,接口推送延迟1秒处理,防止造成数据的重复。
3.不存在,则更新添加收银台表记录,更新费用总表记录,更新费用明细进出流水记录。
问题点:
支付网关发送MQ时间,收银台表创建时间,业务系统的接收MQ时间都是同一时间,到秒。
且支付网关发送了2条记录。导致业务系统费用总表记账金额*2倍,费用明细进出流水记录2条。收银台表因为加了payMd5数据库唯一约束,只有一条记录。
排查推测点:
问题可能出在:mysql并发查询,select查询数据库收银台表是否存在且状态是否成功。 并发查询的时候,没有查询出来结果,可能上一个事务没有提交等原因。
导致记录重复了。
--如果MySQL并发查询导致没有查询结果,可能是因为查询时刻数据被其他事务修改或删除。--
--MySQL在执行高并发查询时,可能会出现数据重复,这通常是因为事务没有正确管理,导致查询期间可以看到其他事务尚未提交的数据。--
解决方法:
在mysql select查询的验证的提前,加上一层redis锁来防止重复,锁定时间:60秒
String redisKey = "redisKey" + payMd5; //走redis查询 第一层逻辑 redisKey = redisKey+notifyDataVo.getPayMd5(); if(stringRedisTemplate.hasKey(redisKey)){ //已被锁定,直接返回,幂等结果返回true,提前返回结束,不在继续后续的业务逻辑的执行。 return true; } //锁定60秒 if(StringUtils.isNotBlank(redisKey)){ stringRedisTemplate.opsForValue().set(redisKey,"1",60, TimeUnit.SECONDS); } //走数据库查询 第二层逻辑 boolean flag = cashierService.checkCashierPayMd5(notifyDataVo); if(flag) { //成功 return flag; } } }
测试方法:
MQ消息通过rabbitmq客户端重复多次发送,查看拦截日志。
link:支付回调MQ消息的幂等处理及MD5字符串es中的使用及支付宝预授权完成
https://www.cnblogs.com/oktokeep/p/17263287.html