Redis - 多线程并发锁 - 使用余额

分布式锁服务实现
本文介绍了一个基于Redis的分布式锁服务实现,通过使用setnx和expire命令确保锁的互斥性和时效性,支持业务逻辑的原子操作,并在锁超时或获取失败时提供了重试机制。
package com.das.service.lock.impl;

import com.das.common.constant.EtcConstant;
import com.das.common.exception.DasException;
import com.das.redis.DasJedis;
import com.das.service.lock.LockService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Random;
import java.util.concurrent.TimeUnit;

/**
 * @Author liangmy
 * @Date 2019/10/29
 */
@Service
@Slf4j
public class LockServiceImpl implements LockService {

    @Autowired
    private DasJedis dasJedis;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Object doSomething2(Long carOwnerUserId, EtcConstant.UserMoneyConstant.ModifyMoneyProgress strategy) {
        String key = EtcConstant.getKey(EtcConstant.UserMoneyConstant.modifyMoneyRedisKey, carOwnerUserId);
        for (int i = 0; i < 3; i++) {
            Boolean setnxResult = dasJedis.setnx(key, String.valueOf(System.currentTimeMillis() + EtcConstant.UserMoneyConstant.timeout));
            if (setnxResult) {
                // 1.1获取锁成功
                // 2. 重置 分布锁 的过期时间
                dasJedis.expire(key, EtcConstant.UserMoneyConstant.timeout);
                // 3. 执行业务逻辑
                if (null != strategy) {
                    try {
                        Object obj = strategy.progress();
                        return obj;
                    } catch (Exception e) {
                        throw DasException.internalError(e, e.getMessage());
                    } finally {
                        // 4. 释放锁
                        dasJedis.del(key);
                    }
                }
            } else {
                // 获取锁失败,尝试重新获取分布锁
                String lockValueA = dasJedis.get(key);
                if (StringUtils.isNotBlank(lockValueA) && System.currentTimeMillis() > Long.valueOf(lockValueA)) {
                    // 当前时间大于分布锁设置的超时时间,说明可以获取分布锁
                    String lockValueB = dasJedis.getset(key, String.valueOf(System.currentTimeMillis() + EtcConstant.UserMoneyConstant.timeout));
                    if (StringUtils.isBlank(lockValueB) || StringUtils.equals(lockValueA, lockValueB)) {
                        // B is null 说明在 get 和 getset 的过程中,没有进程修改closeOrderScheduledRedisKey已经过期, lock 是可以获取的。
                        // A == B 说明在get 和 getset 的过程中,没有进程修改closeOrderScheduledRedisKey 对应的 值 ,即 lock 是可以获取的。
                        dasJedis.expire(key, EtcConstant.UserMoneyConstant.timeout);
                        if (null != strategy) {
                            try {
                                Object obj = strategy.progress();
                                return obj;
                            } catch (Exception e) {
                                throw DasException.internalError(e, e.getMessage());
                            } finally {
                                // 4. 释放锁
                                dasJedis.del(key);
                            }
                        }
                    }
                }
            }
            try {
                int nextInt = new Random().nextInt(1000 * 2);
                log.info("获取独占锁失败, 尝试{}毫秒后重试!", nextInt);
                TimeUnit.MILLISECONDS.sleep(nextInt);
            } catch (InterruptedException e) {
                throw DasException.getLockFail(e, e.getMessage());
            }
        }
        throw DasException.getLockFail(null, "获取锁失败, 请稍后重试!");
    }
}

啦啦啦

<think>首先,用户的问题是关于使用Redis分布式来保证订单事务一致性的实现方案。我需要参考提供的引用内容,但用户提到这些引用仅供参考,我应该基于用户的实际需求来回答。 用户需求:使用Redis分布式保证订单相关逻辑的一致性。具体来说,是保证订单事务一致性。 从引用中提取关键点: - 引用[1]:讨论了分布式的实现方式,包括基于Redis,用于保证数据原子操作的一致性。 - 引用[2]:描述了Redis分布式的原理,用于控制多个节点之间对任务的调度权限,确保任务的唯一性和正确性。 - 引用[3]:提到了Redisson,一个Java库,它解决了过期释放但业务未执行完的问题,通过一个看门狗机制来延长的生时间。 用户的问题集中在订单事务一致性上。订单事务通常涉及数据库操作,如创建订单、扣减库等,需要保证这些操作的原子性和一致性。 关键点: - Redis分布式用于在分布式系统中协调多个实例对共享资源的访问。 -订单场景中,可以防止多个请求同时修改同一个订单或相关资源,从而避免并发问题。 实现方案: 1. **基本实现**:使用Redis的SET命令或Redisson库来获取。 2. **的细节**:设置的键(key),如基于订单ID;设置超时时间(expire time),防止死使用唯一值(如UUID)作为的值,确保只有持有的客户端可以释放。 3. **事务一致性**:在获取后,执行数据库事务(如使用数据库事务保证ACID),然后释放。 4. **处理过期**:使用Redisson的看门狗机制,自动延长的生时间,防止业务逻辑未完成时过期。 步骤: - 获取使用SET命令,如SET lock_key unique_value NX PX timeout。 - 执行业务逻辑:在的保护下,执行订单创建、库扣减等操作。确保这些操作在数据库事务中完成。 - 释放使用Lua脚本确保原子性释放(检查值匹配后再删除)。 最佳实践: - 使用Redisson库简化实现,因为它提供了内置的看门狗和重试机制。 - 考虑的粒度:基于订单ID加,避免住整个系统。 - 错误处理:处理获取失败、过期等异常情况。 回答结构: 1. 介绍Redis分布式的基本原理。 2. 描述如何用于保证订单事务一致性。 3. 给出实现方案,包括代码示例。 4. 讨论注意事项和优化。 5. 生成相关问题。 系统指令要求: - 使用中文回答。 - 行内数学表达式用$...$。 - 独立公式用$$...$$。 - 生成相关问题。 - 在回答末尾添加引用标识。 生成相关问题: - 基于主题,如Redis的其他应用、性能优化等。 最终回答结构: - 清晰解释。 - 代码示例(用Python或其他语言)。 - 相关问题。</think>### Redis分布式保证订单事务一致性实现方案 在分布式系统中,订单事务通常涉及多个操作(如创建订单、扣减库、更新账户余额等),需要确保这些操作的原子性和一致性,防止并发冲突。Redis分布式是一种常见机制,通过协调多个服务实例对共享资源(如订单ID)的访问,来保证事务一致性。下面我将逐步解释原理、实现方案和最佳实践。 #### 1. Redis分布式的基本原理 Redis分布式的核心是使用Redis的原子操作(如SET命令)来实现互斥访问: - **的获取**:客户端尝试设置一个唯一的键值对(key-value),如果键不在则设置成功(获取),并设置超时时间(防止死)。例如,键可以是订单ID(如 `order_lock:123`),值是一个唯一标识(如UUID)。 - **的释放**:客户端在完成事务后,通过检查值匹配来删除键,确保只有持有的客户端能释放- **原子性保证**:Redis命令是单线程执行的,因此SET和删除操作是原子的,避免了竞态条件。 在数学上,的获取和释放可以建模为一个互斥协议:设$L$为状态(0表示未,1表示已),$T$为超时时间,则获取的条件是$L = 0$,释放后$L = 0$。分布式确保在任意时刻,最多一个客户端持有,从而保证事务的串行执行。 #### 2. 实现方案:保证订单事务一致性 订单事务一致性的关键是在的保护下执行数据库操作,确保: - 所有操作(如订单创建、库扣减)要么全部成功,要么全部回滚。 - 的超时机制防止业务逻辑未完成时被永久占用。 以下是基于Redis的完整实现方案(使用Python示例,结合Redis-py库)。方案采用Redisson的看门狗机制(引用[3])来解决过期问题,确保业务逻辑执行期间不会意外释放。 ##### 步骤1: 获取分布式 - 使用Redis的SET命令设置,结合NX(Not eXists)和PX(毫秒超时)选项。 - 设置唯一值(如UUID)作为的值,防止其他客户端误释放。 - 超时时间需根据业务逻辑预估设置(如10秒),避免死。 ```python import redis import uuid import time # 连接到Redis服务器 r = redis.Redis(host='localhost', port=6379, db=0) def acquire_lock(lock_key, timeout_ms=10000): """尝试获取分布式""" identifier = str(uuid.uuid4()) # 唯一标识符 # 使用SET命令设置,NX表示键不在时设置,PX设置超时时间(毫秒) result = r.set(lock_key, identifier, nx=True, px=timeout_ms) return identifier if result else None ``` ##### 步骤2: 执行订单事务逻辑 -的保护下执行数据库事务(如使用SQL事务保证ACID)。 - 集成看门狗机制:后台线程定期延长的超时时间(如每10秒检查一次),防止业务逻辑执行时间超过超时(引用[3])。这可以通过Redisson库简化,但这里展示手动实现。 ```python def renew_lock(lock_key, identifier, interval=10): """看门狗机制:定期延长超时时间""" while True: time.sleep(interval) # 每10秒检查一次 # 检查是否仍被当前客户端持有 if r.get(lock_key) == identifier.encode(): r.pexpire(lock_key, interval * 1000) # 延长超时 else: break # 已释放或被抢占,退出 def execute_order_transaction(order_id, user_id, product_id, quantity): """在保护下执行订单事务""" lock_key = f"order_lock:{order_id}" identifier = acquire_lock(lock_key) if not identifier: raise Exception("获取失败,请重试") try: # 启动看门狗线程(伪代码,实际需用多线程实现) import threading watchdog = threading.Thread(target=renew_lock, args=(lock_key, identifier)) watchdog.daemon = True watchdog.start() # 执行数据库事务(示例:使用SQLAlchemy) from sqlalchemy import create_engine engine = create_engine('sqlite:///orders.db') with engine.begin() as conn: # 步骤1: 扣减库(原子操作) conn.execute("UPDATE products SET stock = stock - ? WHERE id = ?", (quantity, product_id)) # 步骤2: 创建订单 conn.execute("INSERT INTO orders (order_id, user_id, product_id, quantity) VALUES (?, ?, ?, ?)", (order_id, user_id, product_id, quantity)) # 步骤3: 更新账户(可选) # conn.execute("UPDATE accounts SET balance = balance - ? WHERE user_id = ?", (price, user_id)) # 事务提交,所有操作要么全部成功,要么回滚 finally: # 释放使用Lua脚本确保原子性) release_lock(lock_key, identifier) def release_lock(lock_key, identifier): """释放分布式:原子操作检查值匹配""" lua_script = """ if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end """ r.eval(lua_script, 1, lock_key, identifier) # 1表示键的数量 ``` ##### 步骤3: 错误处理与重试机制 - **获取失败**:实现重试逻辑(如指数退避),避免饥饿问题。 - **过期处理**:如果业务逻辑超时,自动释放,其他客户端可获取;结合数据库事务回滚,确保数据一致性。 - **事务回滚**:如果在内数据库操作失败,需捕获异常并回滚事务,然后释放。 ```python def create_order_with_retry(order_id, user_id, product_id, quantity, max_retries=3): """带重试的订单创建逻辑""" retries = 0 while retries < max_retries: try: execute_order_transaction(order_id, user_id, product_id, quantity) return True except Exception as e: retries += 1 time.sleep(2 ** retries) # 指数退避 raise Exception("订单创建失败,请检查系统") ``` #### 3. 最佳实践与注意事项 - **粒度**:基于订单ID加(如 `order_lock:{order_id}`),而非全局,提高并发性能。数学上,粒度$G$应满足$G \propto \frac{1}{concurrency}$,以最小化冲突。 - **超时设置**:初始超时时间(如10秒)应大于业务逻辑平均执行时间;结合看门狗机制动态延长(引用[3])。 - **一致性保证**:Redis本身不保证强一致性(如Redis故障时),需结合数据库事务。订单操作应在数据库事务中完成,仅用于协调分布式访问。 - **性能优化**:使用Redisson库(Java)或类似库简化实现,它内置看门狗、重试和异步(引用[3])。避免竞争,可通过分区或队列减少冲突。 - **高可用**:在Redis集群中,使用Redlock算法(多节点加)增强可靠性(引用[3]),但需权衡复杂性和性能。 - **测试**:模拟并发场景(如多个请求同时修改同一订单),验证的有效性。 #### 4. 为什么这个方案有效? - **事务一致性**:确保同一订单ID的操作串行化,防止并发修改;数据库事务保证操作原子性(如库扣减和订单创建要么都成功,要么都失败)。 - **Redis优势**:Redis高性能(微秒级响应)和原子操作适合高并发订单场景(引用[1])。相比数据库Redis减少数据库负载。 - **适用场景**:电商订单处理、库管理、支付系统等需要强一致性的业务(引用[2])。 通过此方案,您可以有效保证订单事务的一致性,避免超卖、重复下单等问题。如果您使用Java,推荐直接集成Redisson库(引用[3]),它提供了更完善的分布式API。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值