高并发系统数据幂等的技术尝试

本文讨论了高并发系统中确保幂等性的方法,包括查询API的天然幂等性、MVCC方案、单独的去重表、分布式锁、删除数据的幂等性和插入数据的唯一索引。此外,文章还介绍了状态机幂等性在业务单据和任务相关业务中的应用。

高并发系统数据幂等的技术尝试

 

前言

在系统开发过程中,经常遇到数据重复插入、重复更新、消息重发发送等等问题,因为应用系统的复杂逻辑以及网络交互存在的不确定性,会导致这一重复现象,但是有些逻辑是需要有幂等特性的,否则造成的后果会比较严重,例如订单重复创建,这时候带来的问题可是非同一般啊。

 

什么是系统的幂等性

幂等是数据中得一个概念,表示N次变换和1次变换的结果相同。

 

高并发的系统如何保证幂等性?

 

  • 查询

查询的API,可以说是天然的幂等性,因为你查询一次和查询两次,对于系统来讲,没有任何数据的变更,所以,查询一次和查询多次一样的。

 

  • MVCC方案

多版本并发控制,update with condition,更新带条件,这也是在系统设计的时候,合理的选择乐观锁,通过version或者其他条件,来做乐观锁,这样保证更新及时在并发的情况下,也不会有太大的问题。例如update table_xxx set name=#name#,version=version+1 where version=#version# ,或者是 update table_xxx set quality=quality-#subQuality# where quality-#subQuality# >= 0 。

 

  • 单独的去重表

如果涉及到的去重的地方特别多,例如ERP系统中有各种各样的业务单据,每一种业务单据都需要去重,这时候,可以单独搞一张去重表,在插入数据的时候,插入去重表,利用数据库的唯一索引特性,保证唯一的逻辑。

 

  • 分布式锁

还是拿插入数据的例子,如果是分布是系统,构建唯一索引比较困难,例如唯一性的字段没法确定,这时候可以引入分布式锁,通过第三方的系统,在业务系统插入数据或者更新数据,获取分布式锁,然后做操作,之后释放锁,这样其实是把多线程并发的锁的思路,引入多多个系统,也就是分布式系统中得解决思路。

 

  • 删除数据

删除数据,仅仅第一次删除是真正的操作数据,第二次甚至第三次删除,直接返回成功,这样保证了幂等。

 

  • 插入数据的唯一索引

插入数据的唯一性,可以通过业务主键来进行约束,例如一个特定的业务场景,三个字段肯定确定唯一性,那么,可以在数据库表添加唯一索引来进行标示。

 

这里有一个场景,API层面的幂等,例如提交数据,如何控制重复提交,这里可以在提交数据的form表单或者客户端软件,增加一个唯一标示,然后服务端,根据这个UUID来进行去重,这样就能比较好的做到API层面的唯一标示。

 

  • 状态机幂等

 

在设计单据相关的业务,或者是任务相关的业务,肯定会涉及到状态机,就是业务单据上面有个状态,状态在不同的情况下会发生变更,一般情况下存在有限状态机,这时候,如果状态机已经处于下一个状态,这时候来了一个上一个状态的变更,理论上是不能够变更的,这样的话,保证了有限状态机的幂等。

### 实现高并发场景下的等性解决方案 #### 1. 等性的定义与重要性 等性是指对于同一条请求,无论执行多少次,其产生的效果都是相同的。在高并发环境中,由于网络抖动、客户端重试机制以及用户误操作等因素,可能会导致同一请求被多次发送到服务器[^1]。如果未妥善处理这些重复请求,则可能导致数据异常或业务逻辑错误。 --- #### 2. 常见的等性问题场景 - **订单重复提交**:用户因页面加载缓慢或其他原因反复点击按钮,造成多个相同订单生成。 - **支付接口调用**:支付过程中可能出现超时情况,客户重新发起交易请求,从而引发重复扣款等问题。 - **HTTP资源请求方式**:GET 和 HEAD 方法天然具备等属性,而 POST、PUT 及 DELETE 则需额外考虑如何保障其等行为[^3]。 --- #### 3. 解决方案分类及其适用范围 ##### (1)基于数据库的设计 通过利用关系型数据库的功能特性来达成等目标: - **唯一约束(Unique Index)** 创建联合索引来确保某些字段组合不会出现重复记录。例如,在银联付款案例中采用 `source` 加上 `seq` 的形式作为键值对设置成唯一索引,以此避免重复写入[^5]。 ```sql CREATE UNIQUE INDEX idx_unique_payment ON payment(source, seq); ``` - **乐观锁(Optimistic Locking)** 使用版本号或者时间戳字段配合 SQL 更新语句中的 WHERE 条件判断当前行是否已被修改。只有满足条件的数据才会成功更新,否则抛出冲突提示给前端应用层进一步处理[^2]。 ```java @Version private Integer version; ``` 对应SQL如下所示: ```sql UPDATE orders SET status='PAID', amount=100, version=version+1 WHERE id=? AND version=? IF ROW_COUNT() = 0 THEN THROW EXCEPTION 'CONCURRENT_MODIFICATION'; END IF; ``` ##### (2)基于缓存的技术手段 借助 Redis 或 Memcached 这样的内存存储工具快速拦截重复请求并减少主库压力的同时也实现了高效的等功能支持: - **布隆过滤器 (Bloom Filter)** 将所有已接收过的流水号加入布隆集合里,后续每次接收到新请求前先查此集合作为前置校验步骤之一。虽然存在一定的假阳性概率但可以极大降低实际碰撞几率且性能优越[^4]. - **分布式锁服务(Distributed Lock Service)** 当某个节点获取到了针对特定事务的操作权限之后其他竞争者就必须等待直至释放为止才能继续尝试获得许可进而完成整个流程控制过程达到串行化的效果间接完成了去重目的[^3]. ```python import redis from redis.lock import Lock r = redis.Redis() lock_key = f"lock:{order_id}" with r.lock(lock_key, timeout=10): # 设置最长持有时间为10秒 process_order(order_id) ``` --- #### 4. 综合策略——“一锁二判三更新” 这是一种较为通用的做法,即首先加锁保护关键路径免受竞态影响;接着验证是否存在对应实体对象实例;最后才实施必要的变更动作[^3]: ```pseudo-code function handleRequest(requestId){ acquireDistributedLock(requestId); // Step One: Acquire Distributed Lock if(isProcessedBefore(requestId)){ releaseDistributedLock(); return getPreviousResult(); // Step Two: Check If Already Processed And Return Result Directly. } executeBusinessLogicAndPersistData(); // Step Three: Perform Actual Business Logic & Persist Data Safely Within Transaction Boundary. markAsCompletedSuccessfully(requestId); releaseDistributedLock(); } ``` --- ### 结论 综上所述,面对不同类型的业务需求可以选择相应的技术路线解决高并发背景下面临的各种挑战。无论是单纯依赖于底层基础设施还是结合高层抽象模型都可以有效提升系统的稳定性和可靠性水平^。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值