Redis-4 分布式锁

Redis分布式锁实现-java

分布式锁一般有三种实现方式:1. 数据库乐观锁;2. 基于Redis的分布式锁;3. 基于ZooKeeper的分布式锁。本篇博客将介绍第二种方式,基于Redis实现分布式锁。本章给予的java工具类分布式锁实现方式是经过验证且在大型互联网企业应用的代码。

1 可靠性

为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件:

  1. 互斥性。在任意时刻,只有一个客户端能持有锁。
  2. 不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
  3. 具有容错性。只要大部分的Redis节点正常运行,客户端就可以加锁和解锁。
  4. 解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。

2 代码

Spring Mvc或者是SpringBoot整合Redis本章不做介绍。

2.1 获取锁

/**
* 使用redis的set命令实现获取分布式锁
* @param lockKey 可以就是锁
* @param requestId 请求ID,客户端id标识,可以使用时间戳或者uuid
* @param expireTime 过期时间,避免死锁
* @return true:获取锁成功 false:获取锁失败
*/
public static boolean getLock(String lockKey,String requestId,int expireTime) {
	//NX:保证互斥性
	String result = jedis.set(lockKey, requestId, "NX", "EX", expireTime);
	if("OK".equals(result)) {
		return true;
	} 
	return false;
}
  • jedis.set(key, requestId,NX , EX, time),这个set()方法一共有五个形参:
  1. key,我们使用key来当锁,因为key是唯一的。
  2. requestId,我们传的是requestId,很多童鞋可能不明白,有key作为锁不就够了吗,为什么还要用到requestId?原因就是我们在上面讲到可靠性时,分布式锁要满足第四个条件解铃还须系铃人,通过requestId,我们就知道这把锁是哪个请求加的了,在解锁的时候就可以有依据。requestId可以使用UUID.randomUUID().toString()方法生成。即加锁与解锁的人必须是同一个用户。
  3. NX,意思是SET IF NOT EXIST,即当key不存在时,我们进行set操作;若key已经存在,则不做任何操作;
  4. EX,设置指定到期时间单位为秒。
  5. time,与第四个参数相呼应,代表key的过期时间。

总的来说,执行上面的set()方法就只会导致两种结果:1. 当前没有锁(key不存在),那么就进行加锁操作,并对锁设置个有效期,同时requestId表示加锁的客户端。2. 已有锁存在,不做任何操作。

2.2 释放锁

/**
* 使用redis的set命令实现获取分布式锁
* @param lockKey 可以就是锁
* @param requestId 请求ID,,客户端id标识,可以使用时间戳或者uuid
* @return true:释放锁成功 false:释放锁失败
*/
public static boolean releaseLock(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));
	if (result.equals(1L)) {
		return true;
	} 
	return false;
}

3 SpringBoot新版本使用lettuce取代了jedis连接redis,分布式锁实现代码如下

注意:SpringBoot高版本2.x以上,redis连接使用lettuce替代jedis

package com.exp.demo.util;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.stereotype.Component;

import java.nio.charset.StandardCharsets;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * 分布式锁工具类
 */
@Component
public class RedisLockUtil {

    private Logger logger = LoggerFactory.getLogger(RedisLockUtil.class);


    @Autowired
    private RedisTemplate<String, Object> redisTemplate;


    /**
     * 释放锁脚本,原子操作,lua脚本
     */
    private static final String UNLOCK_LUA;

    /**
     * 默认过期时间(30ms)
     */
    private static final long DEFAULT_EXPIRE = 30L;


    static {
        StringBuilder sb = new StringBuilder();
        sb.append("if redis.call(\"get\",KEYS[1]) == ARGV[1] ");
        sb.append("then ");
        sb.append("    return redis.call(\"del\",KEYS[1]) ");
        sb.append("else ");
        sb.append("    return 0 ");
        sb.append("end ");
        UNLOCK_LUA = sb.toString();
    }

    /**
     * 获取分布式锁,原子操作
     *
     * @param lockKey        锁
     * @param requestIdValue 请求ID,客户端id标识,可以使用时间戳或者uuid 标识由谁创建分布式锁
     * @return 是否加锁成功  true-成功 false-
     */
    public boolean lock(String lockKey, String requestIdValue) {
        return this.lock(lockKey, requestIdValue, DEFAULT_EXPIRE, TimeUnit.MILLISECONDS);
    }

    /**
     * 获取分布式锁,原子操作
     *
     * @param lockKey        锁
     * @param requestIdValue 请求ID,客户端id标识,可以使用时间戳或者uuid
     * @param expire         过期时间
     * @param timeUnit       时间单位
     * @return 是否加锁成功
     */
    public boolean lock(String lockKey, String requestIdValue, long expire, TimeUnit timeUnit) {
        try {
            RedisCallback<Boolean> callback = (connection) -> connection.set(lockKey.getBytes(StandardCharsets.UTF_8),
                    requestIdValue.getBytes(StandardCharsets.UTF_8), Expiration.seconds(timeUnit.toSeconds(expire)),
                    RedisStringCommands.SetOption.SET_IF_ABSENT);
            return redisTemplate.execute(callback);
        } catch (Exception e) {
            logger.error("redis lock error ,lock key: {}, value : {}, error info : {}", lockKey, requestIdValue, e);
        }
        return false;
    }

    /**
     * 释放锁
     *
     * @param lockKey   锁
     * @param lockValue 唯一ID
     * @return 执行结果
     */
    public boolean unlock(String lockKey, String lockValue) {
        RedisCallback<Boolean> callback = (connection) -> connection.eval(UNLOCK_LUA.getBytes(), ReturnType.BOOLEAN, 1, lockKey.getBytes(StandardCharsets.UTF_8), lockValue.getBytes(StandardCharsets.UTF_8));
        return redisTemplate.execute(callback);
    }

    /**
     * 获取Redis锁的value值
     *
     * @param lockKey 锁
     */
    public String get(String lockKey) {
        try {
            RedisCallback<String> callback = (connection) -> new String(Objects.requireNonNull(connection.get(lockKey.getBytes())), StandardCharsets.UTF_8);
            return redisTemplate.execute(callback);
        } catch (Exception e) {
            logger.error("get redis value occurred an exception,the key is {}, error is {}", lockKey, e);
        }
        return null;
    }

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值