生成订单幂等性(防止订单重复提交)

news2024/11/13 16:10:40
订单唯一性(防止重复下单)方案

重复下单产生原因:

  1. 客户端原因

比如下单的按键在点按之后,在没有收到服务器请求之前,按键的状态没有设为已禁用状态,还可以被按。又或者,在触摸屏下,用户手指的点按可能被手机操作系统识别为多次点击。

  1. 请求超时原因

用户的设备与服务器之间可能是不稳定的网络。这样一个下单请求过去,返回不一定回得来。超时最大的问题是: 从用户的角度,他无法确定下单的请求是还没到服务器,还是已经到了服务器但是返回丢失了。——用户无法区分到底这个单下了还是没下

  1. 用户各种无脑操作

心急的用户可能会重启流程/重启App/重启手机。在这种强制的手段下,任何技术手段都会失效。

解决方案:

方案一:提交订单按钮置灰

防止用户提交,最常规的做法,就是客户端点击下单之后,在收到服务端响应之前,按钮置灰。

前端页面直接防止用户重复提交表单,但网络错误会导致重传,很多RPC框架、网关都有自动重试机制,所以重复请求在前端侧无法完全避免。

当然,这种方案也不是真的没有价值。

这种方案可以在高并发场景下,从浏览器端去拦住一部分请求,减少后端服务器的处理压力,达到过滤流量的效果。

优点:简单。基本可以防止重复点击提交按钮造成的重复提交问题。

缺点:前进后退操作,或者F5刷新页面等问题并不能得到解决。

方案二:请求唯一ID+数据库唯一索引约束

需要客户端在请求下单接口的时候,需要生成一个唯一的请求号:requestId,服务端拿这个请求号,判断是否重复请求。实现的逻辑,流程如下:

当用户进入订单提交界面的时候,调用后端获取请求唯一ID,并将唯一ID值埋点在页面里面。

当用户点击提交按钮时,后端检查这个唯一ID是否用过,如果没有用过,继续后续逻辑;如果用过,就提示重复提交。

最关键的一步操作,就是把这个唯一ID 存入业务表中,同时设置这个字段为唯一索引类型,从数据库层面做防止重复提交。

优点:对于下单流量不算高的系统,可以采用这种 请求唯一ID + 数据表增加唯一索引约束`的方式,来防止接口重复提交!

缺点:并发量太低,10wqps高并发, 这个根本没法满足。

方案三:reids分布式锁+请求唯一ID

随着业务的快速增长,每一秒的下单请求次数,可能从几十上升到几百甚至几万。

面对这种下单流量越来越高的场景,此时数据库的访问压力会急剧上升,数据库会成为整个下单流程的瓶颈。

对于这样的场景,我们可以选择引入缓存中间件来缓解数据库高并发场景下的压力,实现的逻辑,流程如下:

当用户进入订单提交界面的时候,调用后端获取请求唯一 ID,同时后端将请求唯一ID存储到redis中再返回给前端,前端将唯一 ID 值埋点在页面里面。

当用户点击提交按钮时,后端检查这个请求唯一 ID 是否存在,如果不存在,提示错误信息;如果存在,继续后续检查流程。

使用redis的分布式锁服务,对请求 ID 在限定的时间内进行加锁,如果加锁成功,继续后续流程;如果加锁失败,提示说明:服务正在处理,请勿重复提交。

最后一步,如果加锁成功后,需要将锁手动释放掉,以免再次请求时,提示同样的信息;同时如果任务执行成功,需要将redis中的请求唯一 ID 清理掉。

优点:可以满足 10wqps高并发要求。

缺点:每次提交订单的时候,都需要调用服务端获取请求唯一ID

方案四:redis分布式锁+token:

创建订单的时候,用订单信息计算一个哈希值,去生成token ,大致 流程如下:

用户点击提交按钮,服务端接受到请求后,通过规则计算出本次请求唯一ID值

使用redis的分布式锁服务,对请求 ID 在限定的时间内尝试进行加锁,如果加锁成功,继续后续流程;如果加锁失败,说明服务正在处理,请勿重复提交。

最后一步,如果加锁成功后,需要将锁手动释放掉,以免再次请求时,提示同样的信息。

优点减少一次客户端与服务端之间的交互次数,提高下单流程效率

用订单信息计算一个哈希值生成token
    /**
     * 提交订单
     */
    @PostMapping("/add")
    public Result add(@RequestBody OrdersVO ordersVO) {
        StpUtil.checkRoleOr("admin", "user");
        int hashKey = ordersVO.hashCode();//获取token
        String key = LOCK_KEY + hashKey;
        //获取锁
        Boolean isLock = redisTemplate.opsForValue().setIfAbsent(key, hashKey, 60, TimeUnit.SECONDS);
        if (!isLock) {//未获取到锁 说明订单重复提交 返回错误信息
            return Result.fail(ShopMsgConstant.REPEAT_SUBMIT.getCode(),ShopMsgConstant.REPEAT_SUBMIT.getMsg());
        }
        try {
            log.info("--------------获取锁成功,生成订单------------------");
            return goodsOrderService.add(ordersVO);
        } finally {
            if (hashKey == (int)redisTemplate.opsForValue().get(key) ){
                log.info("--------------释放锁------------------");
                redisTemplate.delete(key);
            }
        }
    }

方案五:技术+产品+运营支持

如果经过上述方案处理,还是会有用户误操作,直到收到两份商品才发现下重了。

在实际设计中,无论多么好的技术,也不可能100%的拦截所有的可能性,必须依靠**技术+产品设计+运营支持**的综合手段才能解决这类问题。

此时就得依靠运营/客服的支持了。

****推荐使用方案四+方案五

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1995329.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

15秒完成作文自动批阅!璞公英与恩施三中正式签约试点,AI 大模型自动批阅+精准教学助力学校教育升级!

在国家大力推动教育数字化战略行动的浪潮中,通过科技创新赋能教育,促进教育公平与质量双提升的重要签约仪式,于恩施市第三高级中学隆重举行。一直以来,恩施市第三高级中学积极寻求创新与突破,引入璞公英的先进教育技术…

MyBatis动态SQL标签2

4.choose (when, otherwise)标签是使用举例 类似switch...case,从上到下匹配,找到匹配的条件,就结束匹配其他的! 5.set标签是使用举例 set这个标签是用在更新操作上的 set标签代替sql中的set关键字,可以把set语句后多…

DC-8靶机渗透测试

DC-8靶机 文章目录 DC-8靶机信息收集web渗透获取权限权限提升靶机总结 信息收集 1.nmap扫描出主机IP为192.168.78.156 开放80和22端口,没扫出来什么漏洞,但是发现robots.txt文件,给出了后台登录地址/usr/login web渗透 1.登陆到主页发现是…

Java有哪些数据类型?

Java有哪些数据类型 1. 基本数据类型 2. 引用数据类型 3. 总结 🎈边走、边悟🎈迟早会好 Java 中的数据类型分为两大类:基本数据类型(primitive types)和引用数据类型(reference types)。 …

代发考生战报:8月6号 西安 ,HCIE存储H13-629考试通过

代发考生战报:8月6号 西安 ,HCIE存储H13-629考试通过,目前这个考试变题了,我找客服买的包过服务,有老师指导,考试通过了,里面的新题我看了,我是一点也不会,自己考的还是不…

Java代码混淆加密之Xjar

使用背景 代码安全性需求:在交付给客户或第三方时,保护源代码不被泄露或反编译是许多企业的核心需求。尤其是在竞争激烈的市场中,代码泄露可能导致严重的经济损失。 传统工具的局限性:虽然有很多混淆和加密工具,比如 …

【学习笔记】用线段树维护区间计数问题

前言 简单的区间计数问题可能直接推式子就行了。 但有些问题必须要数据结构维护。线段树就是一个比较好的处理区间的数据结构。 Gym102222L 思路 满足条件的区间特征: max ⁡ { a i } − min ⁡ { a i } 1 − c n t 0 \max\{a_i\}-\min\{a_i\}1-cnt0 max{ai​}…

uniapp离线打包热更新失败-AndroidStudio离线打包apk后无法下载打开-热更新失败-plus.runtime.install失败

效果图 仅安卓 前言 1.plus.runtime.install一直fail(20240808), uni.openDocument可以打开本地apk文件 2.权限问题需小心 跑通前提 1.先确定apk地址有效,浏览器中手动下载可安装 2.确保已添加离线打包AndroidStudio的“android.permission.INSTALL_PACKAGES”权…

初识自然语言处理NLP

文章目录 1、简介2、自然语言处理的发展简史3、语言学理论句法学(Syntax)语义学(Semantics)语用学(Pragmatics)形态学(Morphology) 4、统计与机器学习方法n-gram 模型隐马尔可夫模型…

JNPF快速开发平台赋能数字办公方式转变

随着信息技术的飞速发展,数字化转型已成为各行各业提升效率、优化流程的重要手段。JNPF快速开发平台正是在这样的背景下应运而生,它通过简化开发流程,使得非技术人员也能参与到应用的构建中来,从而加速了数字办公方式的转变。 数字…

解决r2dbc连mysql时timestamp字段的时区问题

现象: 在两个mysql库用相同SQL都建了某表,都有created_at字段: created_at timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, 。但是在往此表insert记录时,B库的created_at字段比当前时间慢了8个小时,而A…

MySQL学习(19):锁

1.什么是锁 锁是计算机协调多个进程或线程并发访问某一资源的机制。 在数据库中,数据是供许多用户共享的资源,数据库必须保证数据并发访问的一致性、有效性,这就要靠锁来协调实现。 MySOL中的锁,分为以下三类: &am…

深度学习基础 - 向量投影

深度学习基础 - 向量投影 flyfish 给定两个向量 a \mathbf{a} a 和 b \mathbf{b} b,我们想要计算 a \mathbf{a} a 在 b \mathbf{b} b 上的投影。 点乘 (Dot Product) 点乘是一个标量,表示两个向量在相同方向上的程度。公式为: a ⋅ b …

行为验证码热门行业解决方案

行为式验证码是一种较为流行的验证码。从字面来理解,就是通过用户的操作行为来完成验证,而无需去读懂扭曲的图片文字,以下是常见的案例。 ​​热门行业解决方案 教育解决方案 教育资源不断线上化,使得违法违规内容风险不断提高&…

最近在西安召开的学术会议:EI检索超快,信息系统与计算技术领域!

第十二届信息系统与计算技术国际会议(ISCTech 2024)将于2024年11月8日-11月11日在中国西安盛大举行,由长沙理工大学主办,同济大学、西北工业大学联合协办。会议聚焦信息系统与计算技术等相关研究领域,广泛邀请国内外知…

货运物流App项目开发功能介绍

货运小程序通常具备多种功能,以便于用户管理和跟踪货物运输。以下是一些常见的功能介绍: 注册和登录: 用户可以通过手机号码或邮箱注册并登录账户。 货物发布: 用户可以创建货运订单,填写货物信息(如品名、…

Proxmox vzdump backup ct vm

vzdump 参考 vzdump 备份参数参考 创建一键Proxmox vzdump 备份ct 虚拟机 vzdump 备份参数参考vmid 105 106 22-29行是环境变量,根据主机的实际情况更改ct 模板默认下载目录/var/lib/vz/template/cache所有备份默认目录/var/lib/vz/dumpiso 存放/var/lib/vz/template/iso# -*…

【扒代码】CCFF跨尺度特征融合

import torch import torch.nn as nn import torch.nn.functional as Fclass RepVggBlock(nn.Module):def __init__(self, ch_in, ch_out, actrelu):super().__init__()self.ch_in ch_in # 输入通道数self.ch_out ch_out # 输出通道数# 第一个卷积层,使用 3x3 卷…

iOS ------ UIKit相关

UIView和CALayer UIView UIView表示屏幕上的一块矩形区域,它是基本上iOS中所有可视化控件的父类。UIView可以管理矩形区域里的内容,处理矩形区域的事件,包括子视图的管理以及动画的实现。 UIKit相关类的继承关系 UIView继承自UIResponde…

封装加载(raect18+antd)

该组件主要是anted的组件中自带的loading属性&#xff0c; 1、封装loading组件 import React from react;function WithLoading(WrappedComponent: React.ComponentType<any>) {return (props: any) > {const [isLoading, setIsLoading] React.useState(true);Reac…