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

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

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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的基本概念和机制。悲观在数据库里通常是先获取再操作数据,防止其他进程干扰。那在Redis中是怎么实现的呢? 首先,应该想到Redis本身没有内置的悲观,但可以通过命令实现。常见的可能是用SETNX命令,或者后来的SET命令带参数。比如SET key value NX EX超时时间,这样可以实现一个互斥。需要确保原子性,否则可能有竞争条件。 然后,要考虑使用步骤。比如获取时设置一个唯一值,释放时要检查是否是自己持有的,避免误删其他客户端的。可能还需要考虑超时机制,防止死。这里可能会提到Lua脚本,因为Redis执行Lua是原子的,可以保证检查和删除操作的原子性。 接着,用户可能想知道悲观的适用场景。比如在高并发下,对数据一致性要求高的操作,如库扣减。但也要提到缺点,比如性能开销,因为每次操作都要加,可能影响并发能力。 可能还需要对比乐观,比如用WATCH命令实现的CAS机制。这样用户能理解两者的区别,悲观是假设最坏情况,先加,而乐观是更新时检查版本。 还要注意常见的问题,比如超时时间的设置,如果业务执行时间超过超时时间,会导致提前释放,引发问题。所以可能需要守护线程续期,或者评估业务执行时间。 另外,是否要提到RedLock算法?可能用户会问到分布式环境下的多节点,但如果是单实例的话,可能不需要。不过作为介绍,简单提一下RedLock可以扩展知识面。 最后,整理结构,先定义,再实现方式,步骤,代码示例,优缺点,适用场景,注意事项。这样用户能清晰理解。确保使用正确的术语,比如互斥、原子操作、Lua脚本等。还要注意中文表达,避免技术术语翻译错误。</think>### Redis 悲观详解 #### 1. 基本概念 **悲观**是一种并发控制机制,假设数据在操作过程中大概率会发生冲突,因此在访问数据时**先加**,确保独占资源。Redis 本身没有内置的悲观,但可通过以下方式实现类似机制。 --- #### 2. 实现原理 ##### 核心命令 - **`SET key value NX EX timeout`** 原子性操作:若 `key` 不在(`NX` 选项),则设置键值,并添加超时时间(`EX` 单位秒)。此命令用于**获取**。 - **Lua 脚本** 通过原子性执行 `GET` + `DEL` 操作,确保**释放**时验证持有者身份,防止误删。 --- #### 3. 使用步骤 1. **加** ```bash SET lock:order_123 "client1" NX EX 30 ``` - 若返回 `OK`,表示获取成功,有效期 30 秒 - 若返回 `nil`,表示已被其他客户端占用 2. **执行业务操作** 在的有效期内完成数据操作(如修改库订单状态等)。 3. **解** 通过 Lua 脚本验证归属后删除: ```lua if redis.call("GET", KEYS[1]) == ARGV[1] then return redis.call("DEL", KEYS[1]) else return 0 end ``` --- #### 4. 代码示例(Python) ```python import redis r = redis.Redis() # 加 lock_key = "lock:order_123" client_id = "client1" if r.set(lock_key, client_id, nx=True, ex=30): try: # 执行业务逻辑 print("执行业务操作...") finally: # 解 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, client_id) else: print("获取失败") ``` --- #### 5. 优缺点对比 | **优点** | **缺点** | |------------------------------|-----------------------------------| | 强一致性保证 | 性能开销较大(频繁加/解) | | 实现简单直观 | 可能产生死(需合理设置超时) | | 适合高冲突场景 | 超时时间需精细控制 | --- #### 6. 适用场景 - **库扣减**:防止超卖 - **订单支付**:避免重复支付 - **敏感数据修改**:如账户余额变更 --- #### 7. 注意事项 1. **超时时间** 需根据业务耗时设置合理值,避免过早释放或长期占用。 2. **续期机制** 复杂业务可通过后台线程定期续期(类似 Redisson 的 `watchdog`)。 3. **集群环境** 单节点Redis 集群中可能失效,需使用 RedLock 等分布式算法。 --- #### 8. 扩展对比:悲观 vs 乐观 | **维度** | **悲观** | **乐观Redis WATCH)** | |----------------|--------------------------------|-----------------------------------| | 冲突假设 | 默认高冲突 | 默认低冲突 | | 实现方式 | 先加再操作 | 先监听键,提交时校验版本 | | 性能 | 较低(阻塞等待) | 较高(无竞争时) | | 典型场景 | 强一致性需求 | 读多写少场景 |
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值