java实现Redis锁

示例1(存在问题)


import redis.clients.jedis.Jedis;

import java.util.UUID;

/**
 * @author lee
 * @date 2020/8/26 22:01
 */
public class RedisLock {

    /**
     * 上锁
     *
     * @param key
     * @return
     */
    public static String lock(String key) {
        Jedis jedis = null;
        try {
            jedis = new Jedis("localhost", 6379);
            String value = fetchLockValue();
            if ("ok".equals(jedis.set(key, value))) {
                jedis.expire(key, 10);
                return value;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
        return null;
    }

    /**
     * 解锁
     *
     * @param key
     * @param value
     * @return
     */
    public static boolean unLock(String key, String value) {
        if (key == null || value == null) {
            return true;
        }
        Jedis jedis = null;
        try {
            jedis = new Jedis("localhost", 6379);
            if (jedis.exists(key) && jedis.get(key).equals(value)) {
                jedis.del(key);
                return true;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
        return false;
    }

    /**
     * 获取唯一value
     *
     * @return
     */
    public static String fetchLockValue() {
        return UUID.randomUUID().toString() + "_" + System.currentTimeMillis();
    }

    public static void main(String[] args) {
        String key = "订单号";
        String value = lock(key);
        try {
            //to do something
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            unLock(key, value);
        }

    }
}

上述代码存在以下问题:

  • 在上锁时,如果成功了,但是在执行expire时系统崩溃了,那么这个锁将不会被释放掉
  • 在解锁时,如果一个客户端执行完“判断是否自己持有锁”步骤后,得出自己持有锁的结论,此时锁的过期时间到了,自动被 redis 释放了,同时另一个客户端又基于这个 key 加锁成功,如果第一个客户端还继续执行删除 key-value的操作,就将不属于自己的锁给释放了。

示例2(正确的写法)

package com.chenguangli.spring.redis;

import redis.clients.jedis.Jedis;

import java.util.Collections;
import java.util.UUID;

/**
 * @author lee
 * @date 2020/8/27 22:53
 */
public class RedisLock2 {
    /**
     * 上锁
     *
     * @param key
     * @return
     */
    public static String lock(String key) {
        Jedis jedis = null;
        try {
            jedis = new Jedis("localhost", 6379);
            String value = fetchLockValue();
            if ("ok".equals(jedis.set(key, value, "NX", "EX", 10))) {
                return value;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
        return null;
    }

    /**
     * 解锁
     *
     * @param key
     * @param value
     * @return
     */
    public static boolean unLock(String key, String value) {
        if (key == null || value == null) {
            return true;
        }
        Jedis jedis = null;
        try {
            jedis = new Jedis("localhost", 6379);
            String command = "if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
            if (jedis.eval(command, Collections.singletonList(key),Collections.singletonList(value)).equals(1L)) {
                return true;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
        return false;
    }

    /**
     * 获取唯一value
     *
     * @return
     */
    public static String fetchLockValue() {
        return UUID.randomUUID().toString() + "_" + System.currentTimeMillis();
    }

    public static void main(String[] args) {
        String key = "订单号";
        String value = lock(key);
        try {
            //to do something
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            unLock(key, value);
        }

    }
}
  • 上锁时使用jedis.set(key, value, "NX", "EX", 10)),保证set key 和设置过期时间是原子性的
  • 解锁时使用eval脚本保证自己持有锁的机制是用加锁的时候的 key-value 来判断当前的 key 的值是否等于自己持有锁时获得的值。

你可能感兴趣的:(java实现Redis锁)