后端怎样防止重复提交订单?

 
 

点击上方关注 “终端研发部

设为“星标”,和你一起掌握更多数据库知识

一般我们都是这样做的:

创建订单的时候,用订单信息计算一个哈希值,判断redis中是否有key,有则不允许重复提交,没有则生成一个新key,放到redis中设置个过期时间,然后创建订单。其实就是在一段时间内不可重复相同的操作

5831bd510f0f0dd30b3855a67acae15c.jpeg

第二种方式:利用唯一索引机制的验证

需要原子性操作,想到了数据库的唯一索引。新建一个TradeLock表:

CREATE TABLE `TradeLock` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`type` int(11) NOT NULL COMMENT '锁类型',
`lockId` int(11) NOT NULL DEFAULT '0' COMMENT '业务ID',
`status` int(11) NOT NULL DEFAULT '0' COMMENT '锁状态',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='Trade锁机制';

● 每次request进来则往表里面插入数据:

成功,则可以继续操作(相当于获取锁);

失败,则说明有操作在进行。

● 操作完成后,删除此条记录。(相当于释放锁)。

第二种方法(利用数据库完整性约束)最简便,但是会访问(读写)数据库,给数据库造成一定的压力;
同时也有个隐患,程序执行中途故障了(网络垮了,服务宕了...),后面重复提交,就无法成功了

也有解决方法:定时器清理这个hash数据库表

第三种方式:利用redis的setNX

第四种方式:利用redis的分布式锁(同上setNx命令

  1. set命令

  2. Redission框架)

如果是防重设计,流程图要改改:

208701126683615ed94d92272b48522a.png

利用AOP+Redis实现案例

1.自定义注解

/**
 * @author Tzeao
 */
@Target(ElementType.METHOD) // 作用到方法上
@Retention(RetentionPolicy.RUNTIME) // 运行时有效
public @interface NoRepeatSubmit {

    //名称,如果不给就是要默认的
    String name() default "name";
}

2.使用AOP实现该注解

/**
 * @author Tzeao
 */
@Aspect
@Component
@Slf4j
public class NoRepeatSubmitAop {

    @Autowired
    private RedisService redisService;

    /**
     * 切入点
     */
    @Pointcut("@annotation(com.qwt.part_time_admin_api.common.validation.NoRepeatSubmit)")
    public void pt() {
    }

    @Around("pt()")
    public Object arround(ProceedingJoinPoint joinPoint) throws Throwable {

        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        assert attributes != null;
        HttpServletRequest request = attributes.getRequest();
        //这里是唯一标识 根据情况而定
        String key = "1" + "-" + request.getServletPath();
        // 如果缓存中有这个url视为重复提交
        if (!redisService.haskey(key)) {
            //通过,执行下一步
            Object o = joinPoint.proceed();
            //然后存入redis 并且设置15s倒计时
            redisService.setCacheObject(key, 0, 15, TimeUnit.SECONDS);
            //返回结果
            return o;
        } else {
            return Result.fail(400, "请勿重复提交或者操作过于频繁!");
        }

    }
}

3、测试

@NoRepeatSubmit(name = "test") // 也可以不给名字,这样就会走默认名字
    @GetMapping("test")
    public Result test() {
        return Result.success("测试阶段!");
    }

悲观锁和乐观锁

具体步骤:

先根据id查询用户信息,包含version字段

根据id和version字段值作为where条件的参数,更新用户信息,同时version+1

判断操作影响行数,如果影响1行,则说明是一次请求,可以做其他数据操作。

如果影响0行,说明是重复请求,则直接返回成功。

在更新数据之前先查询一下数据:

select id,amount,version from user id=123;

如果数据存在,假设查到的version等于1,再使用idversion字段作为查询条件更新数据:

update user set amount=amount+100,version=version+1
where id=123 and version=1;

更新数据的同时version+1,然后判断本次update操作的影响行数,如果大于0,则说明本次更新成功,如果等于0,则说明本次更新没有让数据变更。

由于第一次请求version等于1是可以成功的,操作成功后version变成2了。这时如果并发的请求过来,再执行相同的sql:

update user set amount=amount+100,version=version+1
where id=123 and version=1;

update操作不会真正更新数据,最终sql的执行结果影响行数是0,因为version已经变成2了,where中的version=1肯定无法满足条件。但为了保证接口幂等性,接口可以直接返回成功,因为version值已经修改了,那么前面必定已经成功过一次,后面都是重复的请求。

具体流程如下:

2ef03964a148b982458b3ffb12900c9e.jpeg

对于创建订单和更新订单

创建订单服务,可通过预生成订单号,然后利用DB的订单号唯一约束,避免重复写入订单,实现创建订单服务的幂等性

更新订单服务,通过一个版本号机制,每次更新数据前校验版本号,更新数据同时自增版本号,这样的方式,来解决ABA问题,确保更新订单服务的幂等性

总结

对于重复提交请求的问题,我们单纯的只从前端或后端控制,带来的用户体验都不是最好的。只有两者结合起来,才能在确保功能正常的前提下,保证用户体验效果。

PS:如果想学习技术,或者在学习技术的过程中有疑问,对编程方向的选择,可以来这里找小于哥,一个有思想有规划,被代码延误的心灵导师,可咨询offer的选择,职业规划,学习路线,技术开发中的问题

我是终端研发部的小于哥

@终端研发部

天专注技术开发小技巧,技术教程进阶,职场经验,面试的分享,希望我的回答能够帮助到你哈,笔芯~

cdecd104bb18f3137105ace4e743d3e1.jpeg

回复 【idea激活】即可获得idea的激活方式

回复 【Java】获取java相关的视频教程和资料

回复 【SpringCloud】获取SpringCloud相关多的学习资料

回复 【python】获取全套0基础Python知识手册

回复 【2020】获取2020java相关面试题教程

回复 【加群】即可加入终端研发部相关的技术交流群

阅读更多

用 Spring 的 BeanUtils 前,建议你先了解这几个坑!

lazy-mock ,一个生成后端模拟数据的懒人工具

在华为鸿蒙 OS 上尝鲜,我的第一个“hello world”,起飞!

字节跳动一面:i++ 是线程安全的吗?

一条 SQL 引发的事故,同事直接被开除!!

太扎心!排查阿里云 ECS 的 CPU 居然达100%

一款vue编写的功能强大的swagger-ui,有点秀(附开源地址)

相信自己,没有做不到的,只有想不到的

在这里获得的不仅仅是技术!

0535b33f218abf45383e8346c304bdc5.png

36cfe462992ed1cde3a2e350f54794d3.gif

喜欢就给个“在看e4d68cd0a20544a6fb41ed499320b52f.gif

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

androidstarjack

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值