1. 案例
有甲乙两个公司,甲选择一批自己的订单(一个订单有多个货物),生成的6位数字验证码,乙通过验证码接收订单,接收后需要将订单及订单中的货物的操作权限归属乙,甲不再拥有操作权限,要保证订单和货物的权限必须同时更改成功,我选择了开启了事务。在程序中我首先校验验证码,然后去redis查询对应内容,如果验证码正确则进行更改订单及货物的权限,然后移除Redis中的验证码。
@Transactional
public String receiveOrder(String code){
//第一步 校验验证码
......
//第二步 获取验证码对应的内容
......
//第三步 执行对应的更改权限逻辑
......
//第四步 移除验证码
......
//返回结果信息
return .....;
}
在进行测试的时候发现有问题,第一种情况,有时候可以执行成功,有时候失败,于是直接打印日志,发现失败的时候获取的内容是空的,实际是存在的;第二种,执行成功后还能继续执行成功,验证码没有被移除掉。
经过查询相关信息发现了问题的原因竟然是因为事务导致的,是因为当开启事务的时候,Redis将进行队列模式执行,Redis会按照队列顺序进行执行,会进行等待。
2. 具体原因:
Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,Redis对事物支持不会很复杂,当一个客服端连接Redis服务时,发出了MULTI命令时,这个连接会进入事物,在执行MULTI命令之后,执行所有的命令都不会执行,会先放到一个队列中,会提示正在Query,当最后执行EXEC命令之后,Redis会按照之前的进入队列的顺序,执行命令。
3. 解决方案
解决办法是利用了Spring事务代理的特性将Redis执行的部分抽离出来,导致Redis部分事务失效,这样也能保证权限的问题。
@Transactional
public String receiveOrder(String code){
//第一步 校验验证码
......
//第二步 获取验证码对应的内容
String result = getCodeContent(code);
//第三步 执行对应的更改权限逻辑
......
//第四步 移除验证码
if(!removeCode(code)){
//如果移除失败 触发事务回滚
}
//返回结果信息
return .....;
}
//获取验证码内容
private String getCodeContent(String code){
//获取验证码对应的内容
......
//返回结果信息
return .....;
}
//移除验证码
private boolean removeCode(String code){
//移除验证码
......
//返回结果信息
return .....;
}