基于redis的分布式锁

目前业务组新参与了一个项目,涉及到用户积分的增删改查核销等等,预计上线后qps大概在20k左右,我们使用了基于redis的分布式锁来保证数据的一致性。

java 复制代码
@Slf4j
@Service
public class RedisLockService {
    /**
     * 加锁lua脚本
     */
    private static final String REDIS_LOCK_LUA_SCRIPT = "if redis.call('setnx',KEYS[1],ARGV[1]) == 1 then  return redis.call('expire',KEYS[1],ARGV[2])  else return 0 end";
    /**
     * 解锁lua脚本
     */
    private static final String REDIS_UN_LOCK_LUA_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

    /**
     * 数据库操作key
     */
    private static final String DB_OPERATION_KEY_PREFIX = "operation_key_";
    @Resource
    private RedisClient redisClient;


    /**
     * 添加DB数据库锁
     *
     * @param userId 用户名
     * @return 加锁成功之后,返回sequenceId
     */
    public String dbLock(int userId) {
        String sequenceId = String.valueOf(System.nanoTime());
        if (lock(DB_OPERATION_KEY_PREFIX + userId, sequenceId, 10, TimeUnit.SECONDS)) {
            return sequenceId;
        } else {
            recordOne("redis_lock_service_db_lock_fail");
            return null;
        }
    }

    /**
     * 解数据库锁
     *
     * @param userId     用户名
     * @param sequenceId 序列号
     * @return 返回是否解锁成功
     */
    public boolean dbUnlock(int userId, String sequenceId) {

        boolean result = unLock(DB_OPERATION_KEY_PREFIX + userId, sequenceId);
        if (!result) {
            recordOne("redis_lock_service_db_un_lock_fail");
        }
        return result;
    }

    /**
     * 添加分布式锁
     *
     * @param key        锁的key
     * @param sequenceId 唯一Id
     * @param expireTime 过期时间
     * @param unit       过期时间单位
     * @return 返回是否加锁成功
     */
    public boolean lock(String key, String sequenceId, int expireTime, TimeUnit unit) {
        long start = System.currentTimeMillis();

        try {
            if (tryLock(key, sequenceId, expireTime, unit, 0)) {
                return true;
            } else {
                log.error("添加分布式锁失败:key={}", key);
                recordOne("redis_lock_service_lock_fail");
                return false;
            }
        } catch (Exception e) {
            log.error("添加分布式锁异常:key={}", key, e);
            recordOne("redis_lock_service_lock_exception");
        } finally {
            recordQuantile("redis_lock_service_lock", System.currentTimeMillis() - start);
        }
        return false;
    }

    public boolean lock(String key, String sequenceId, int expireTime, TimeUnit unit, int waitTimeInMills) {
        long start = System.currentTimeMillis();

        try {
            if (tryLock(key, sequenceId, expireTime, unit, waitTimeInMills)) {
                return true;
            } else {
                log.error("添加分布式锁失败:key={}", key);
                recordOne("redis_lock_service_lock_fail");
                return false;
            }
        } catch (Exception e) {
            log.error("添加分布式锁异常:key={}", key, e);
            recordOne("redis_lock_service_lock_exception");
        } finally {
            recordQuantile("redis_lock_service_lock", System.currentTimeMillis() - start);
        }
        return false;
    }



    public String lockWithWaitTime(String key, int expireTime, int waitTimeInMills) {
        long start = System.currentTimeMillis();
        String sequenceId = String.valueOf(System.nanoTime());
        if (tryLock(DB_OPERATION_KEY_PREFIX + key, sequenceId, expireTime, TimeUnit.SECONDS, waitTimeInMills)) {
            recordQuantile("redis_lockNew_service_lock", System.currentTimeMillis() - start);
            return sequenceId;
        } else {
            log.error("lockNew 添加分布式锁失败:key={}", key);
            recordOne("redis_lockNew_service_lock_fail");
            return null;
        }
    }

    /**
     * 解分布式锁
     *
     * @param key        解锁key
     * @param sequenceId 唯一的一个Id
     * @return 返回解锁是否成功
     */
    public boolean unLock(String key, String sequenceId) {
        long start = System.currentTimeMillis();

        try {
            long result = redisClient.getSession(key, true).getSingleConnectionExecutor().evalAutoSha(REDIS_UN_LOCK_LUA_SCRIPT, ResultType.INTEGER, new String[]{key}, new String[]{sequenceId}).get(500, TimeUnit.MILLISECONDS);
            if (result > 0) {
                return true;
            } else {
                log.error("解锁失败:key={}", key);
                recordOne("redis_lock_service_un_lock_fail");
                return false;
            }
        } catch (Exception e) {
            recordOne("redis_lock_service_un_lock_exception");
            log.error("解锁异常:key={}", key, e);
        } finally {
            recordQuantile("redis_lock_service_un_lock", System.currentTimeMillis() - start);
        }
        return false;
    }

    /**
     * Redis 尝试加锁
     *
     * @param key              加锁的Key
     * @param expireTime       过期时间
     * @param timeUnit         时间单位
     * @param waitTimeInMills 等待时间
     * @return 返回加锁是否成功
     */
    private boolean tryLock(final String key, String sequenceId, int expireTime, TimeUnit timeUnit, int waitTimeInMills) {
        log.info("redis_lock_service_tryLock_add, userId={}, sequenceId={}", key, sequenceId);
        long start = System.currentTimeMillis();
        long expireTimeSecond = timeUnit.toSeconds(expireTime);

        try {
            while (redisClient.getSession(key, true).getSingleConnectionExecutor().evalAutoSha(REDIS_LOCK_LUA_SCRIPT, ResultType.INTEGER, new String[]{key}, new String[]{sequenceId, String.valueOf(expireTimeSecond)}).get(500, TimeUnit.MILLISECONDS) <= 0) {
                long time = System.currentTimeMillis() - start;
                if (time > waitTimeInMills) {
                    log.error("尝试添加分布式锁失败:key={} time:{}, 超时返回", key, time);
                    log.info("redis_lock_service_tryLock_timeout_fail, userId={}, sequenceId={}", key, sequenceId);
                    recordOne("redis_lock_service_tryLock_timeout_fail");
                    return false;
                }
                log.info("尝试添加分布式锁成功:key={} time:{}, 等待中", key, time);
                TimeUnit.MILLISECONDS.sleep(10);
            }
            return true;
        } catch (Exception e) {
            Long unlockResult = null;
            try {
                unlockResult = redisClient.getSession(key, true).getSingleConnectionExecutor().evalAutoSha(REDIS_UN_LOCK_LUA_SCRIPT, ResultType.INTEGER, new String[]{key}, new String[]{sequenceId}).get(500, TimeUnit.MILLISECONDS);
            } catch (Exception exception) {
                log.error("尝试添加分布式锁失败后解锁异常:key={}", key, e);
                recordOne("redis_lock_service_try_lock_unlock_exception");
            }
            log.error("尝试添加分布式锁失败:key={}, 解锁结果:{}", key, unlockResult, e);
            recordOne("redis_lock_service_try_lock_exception");
        } finally {
            recordQuantile("redis_lock_service_try_lock", System.currentTimeMillis() - start);
        }
        log.info("redis_lock_service_tryLock_fail, userId={}, sequenceId={}", key, sequenceId);
        return false;
    }
}
相关推荐
时间静止不是简史1 小时前
生产事故排查:Redis 宕机导致补偿队列消费线程死亡,通话录音未上送业务平台
java·redis
2301_32241428044 小时前
基于SpringBoot+Vue的休闲零食超市管理系统的设计与实现59004-计算机毕设 原创(免费领源码+部署教程+开发环境)
java·vue.js·spring boot·redis·mysql·决策树·推荐算法
Web极客码7 小时前
我用 Claude 分析 Redis 哨兵日志,实现跨机房数据不一致自动对齐
数据库·redis·php
每天一道题13 小时前
Kafka 是如何工作的?从 Broker、Partition、Offset 到 Consumer Group
分布式·kafka
用户14944848132015 小时前
为什么大厂很少直接用 2PC/TCC?聊聊分布式事务在真实业务里的落地套路
分布式·后端
一条泥憨鱼16 小时前
【Redis | 黑马点评】基于Redis的消息队列 | stream消息队列 源码解读
java·数据库·redis·缓存·lua
每天一道题16 小时前
Redis 缓存为什么会出问题?穿透、击穿、雪崩与缓存一致性
数据库·redis·缓存
Qianxueban16 小时前
pexpire key底层逻辑是如何实现这个延迟功能?
redis
code_slave(码畜)17 小时前
微服务架构落地:分库分表实战(下) —— 分布式主键、跨分片查询与分页聚合防坑
数据库·spring boot·分布式·spring cloud·微服务·架构·分库分表
德迅云安全-上官17 小时前
新一代智能分布式云接入:应用加速守护 TCP 业务安全与体验
分布式·tcp/ip·安全