redis分布式锁实现与思考

373 阅读2分钟

分布式锁

  • 说明: 在java中我们最常使用的加锁方式就是 synchronized关键字和各种 Lock锁,但是这种方式加的锁只能保证在单项目或者说同一个jvm中起作用.但是在现在的分布式环境下就不能很好的应对分布式环境的加锁需求,所以有了分布式锁
  • 分布式锁: 分布式锁就是一种思想,指的是能在分布式环境中,在多个地方使用一个锁的时候,保证只能同时有一个持锁对象.一般是加锁的地方由常规的java的锁,变成 第三方组件或工具实现(比如:redis,memcached,基于数据库的实现,zookeeper 等等)

redis 的实现

一般过程分为:加锁,解锁

  • 加锁: 一般是通过 NX(即当key不存在时,我们进行set操作;若key已经存在,则不做任何操作) 命令,设置 一个key (一般会以为某个系统用的参数) ,和一个随机字符串(即设置的key对应的value),和(PX)超时时间, 命令如下: SET key value NX PX 2000
  • 解锁 解锁的过程就是安全删除这个key的过程,通过对当前的锁持有的字符串和 redis中的key的锁值进行匹配,如果配对则可以移除;或者超过超时时间,锁也会自动退出被其他线程使用
  • 环境 使用的是 jedis 2.9.0
    <dependency>
       <groupId>redis.clients</groupId>
       <artifactId>jedis</artifactId>
       <version>2.9.0</version>
   </dependency>

代码实现

说明:一般设置分布式锁的时候,比如:金额操作会在多个地方,使用同一个key比如用户账号,这个时候如果没获取到锁,肯定不如直接返回报错或异常或者直接结束,应该有个重试竞争机制

/**
     * @param jedis             jedis 连接
     * @param lockKey           传入的锁标识
     * @param tryTimeoutMs      尝试获取锁的时间
     * @param lockTimeoutMS     锁的超时时间
     * @return                  返回设置key对应的value值
     */
    public static String lock(Jedis jedis,String lockKey, long tryTimeoutMs, int lockTimeoutMS) {
        String retTip = null;
        String identifier = UUID.randomUUID().toString();
        try {
            long end = System.currentTimeMillis() + tryTimeoutMs;
            while (System.currentTimeMillis() < end) {
                String result = jedis.set(lockKey, identifier, "NX", "PX", lockTimeoutMS);
                if ("OK".equals(result)) {
                    retTip = identifier;
                    break;
                }
                try {
                    Thread.sleep(10);
                } catch (InterruptedException ie) {
                    Thread.currentThread().interrupt();
                }
            }
        } catch (Exception e) {
            return "err";
        } finally {
            // 异常释放连接
            if (jedis != null) {
                jedis.close();
            }
        }
        return retTip;
    }
  • 重试调用和释放锁的例子
// 调用,循环多次尝试获取锁
	public static void test(String[] args) {
        String key = "xxx";// key 标识
        try {
            // 进行对应的操作
            int cnt=0;
            while(true) {
                cnt++;
                if (cnt > 5) {
                    throw new RuntimeException();
                }
                String resTip = lock(jedisPool.getResource(),key, 6000, 5000);
                if (!org.springframework.util.StringUtils.isEmpty(resTip)) {
                    if ("err".equals(resTip)) {//redis 出错
                        throw new RuntimeException();// 异常退出
                    }
                    // 获取锁之后的操作
                    releaseLock(key, resTip);
                    break;
                }
            }
        }catch (Exception e) {
            e.printStackTrace();
        }
    }



	private static final Long RELEASE_SUCCESS = 1L;

        /**
         * 释放分布式锁
         * @param jedis Redis客户端
         * @param lockKey 锁 key
         * @param requestId 请求标识 value
         * @return 是否释放成功
         */
    public static boolean releaseLock(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));

        if (RELEASE_SUCCESS.equals(result)) {
            return true;
        }
        return false;

    }