通过模拟发送mq消息来测试实现-依据支付凭证不能重复入账

通过模拟发送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

posted on 2024-12-12 19:29  oktokeep  阅读(9)  评论(0编辑  收藏  举报