在Java中使用redis分布式锁,业务逻辑跨多个方法,怎么释放分布式锁

在Java中使用Redis实现分布式锁时,遵循谁用谁释放原则。避免等待锁自动过期,以免浪费资源。在before方法加锁后,正确的释放时机是在业务逻辑执行完毕后,但需要注意线程延迟以确保业务执行。使用lua脚本可以在after方法中进行业务判断,减少锁的使用范围,提高并发性能。悲观锁可能导致高并发场景下的问题,建议采用乐观锁策略,快速加锁并立即释放。

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

boolean before(){
	lock(){//并发下单加分布式锁
		//判断是否有下单资格 是否超过每日下单数
		if(true){
			return true;
		}else{
			return false;
		}
	}
};
validate(){};//参数校验之类的
after(){
	redis.inrc();//统计每日下单数
}
//业务主线
if(before()){
	validate();
	after();
}
/**
     * 获取分布式锁
     * @param jedis
     * @param lockKey 分布式锁Key
     * @param expireTime 分布式锁过期时间
     * @param timeoutTime 获取分布式锁最大时间
     * @return 分布式锁持有者Key,未获取到锁为null
     */
    public static String getDistributedLock(Jedis jedis, String lockKey, int expireTime,int timeoutTime) {
        boolean result = false;
        // 随机生成一个value
        String requestId = UUID.randomUUID().toString();
        // 获取锁的超时时间,超过这个时间则放弃获取锁
        long end = System.currentTimeMillis() + timeoutTime;
        while (!result && System.currentTimeMillis() < end) {
            result = tryGetDistributedLock(jedis, lockKey, requestId, expireTime);
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
        return result ? requestId : null;
    }
/**
     * 尝试获取分布式锁
     * @param jedis Redis客户端
     * @param lockKey 锁
     * @param requestId 请求标识
     * @param expireTime 超期时间
     * @return 是否获取成功
     */
    public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {
        String result = jedis.set(lockKey, requestId, "NX", "PX", expireTime);
        if (LOCK_SUCCESS.equals(result)) {
            return true;
        }
        return false;
    }
/**
     * 释放分布式锁
     * @param jedis Redis客户端
     * @param lockKey 锁
     * @param requestId 请求标识
     * @return 是否释放成功
     */
    public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
        returnResource(jedis);
        if (RELEASE_SUCCESS.equals(result)) {
            return true;
        }
        return false;
    }

/**
     * 释放jedis资源
     * @param jedis
     */
    public static void returnResource(final Jedis jedis) {
        if (jedis != null) {
        	jedis.close();
        }
    }

对于分布式锁,我们应该秉持 谁用谁释放的原则,不可以等到锁过期自动释放。
以上伪代码在before方法添加分布式锁后,什么时候释放呢?
按照正常的业务逻辑,应该是在before加锁,等到after执行完后释放,但是此时可能会释放掉其他的锁,造成业务混乱。如果等锁自动过期,并发时必定会消耗大量的系统资源,造成不可预知的问题。
如果在before里面在方法执行完后释放锁,在前一个请求执行validate() after()逻辑时,下一个订单可能早就进before来了,就失去了限量的目的。
最后怎么解决的呢?

  1. getDistributedLock(" “,” ",2*1000,60*1000)正常的业务逻辑一般2s内肯定可以执行完。
  2. 在before try catch finally 调用releaseDistributedLock释放锁时 使用线程延迟1s释放,给业务足够执行的时间。
String requestId = RedisUtil.getDistributedLock(jedis, lockKey, 2*1000, 60*1000);
if (null != requestId) {
try {
	...		
} catch (Exception e) {
	log.error("系统下单限量管控error:"+e);
} finally {
	new Thread(new Runnable() {
		@Override
		public void run() {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			} finally {
				releaseDistributedLock(jedis, lockKey, requestId);
			}
		}
	}).start();
}
}

后来踩坑发现这样处理是有问题的:

1.业务还没有执行完,锁都释放了,会报java.io.IOException: Connection reset by peer
2.分布式锁比较影响性能,不能在锁大面积业务
3.最后使用lua表达式 在after里完成业务判断

沿用同事的一句话: 我们现在使用的悲观锁的方式,每次拿数据就上锁,处理完后在释放锁,其实这样对于高并发是致命的,我们可以参考乐观锁的实现方式,每次获取数据不加锁,只是在最后判断数据有没有达到限制,达到就立马返回,立马释放锁,尽量把加群控制到最小范围,快速加锁快速释放

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值