redis单机实现分布式锁

单节点redis下分布式锁。

  1. 原理:使用setnx,设置成功返回1,失败返回0,由于redis也是单线程的,所以一次只能有一个线程获取成功。
  2. 程序异常情况:设置超时时间,避免程序挂掉锁无法释放。
  3. 执行超时情况:为避免代码运行时间超过key设置的超时时间,从而释放了其他进程的锁问题。需要保存当前线程的value。在释放之前先检查key设置的value是否跟当前相等,类似CAS的思想,比较再删除。
  4. 资源释放:如果查询key的value与当前设置的value相等,再进行删除。这两步不是原子操作的,也会有问题。比如两个操作之前有网络延迟。 可以使用lua脚本来释放锁。

分布式锁代码如下:

public class RedisDscLock {
    /**
     * 设置key过期时间
     */
    private int lockExpireTime = 2;

    /**
     * 加锁成功后返回value
     *
     * @param key
     * @param timeOutInSecond
     * @return
     */

    public String getLock(Jedis jedis, String key, int timeOutInSecond) throws TimeoutException {
        //设置成功返回1,否则返回0
        timeOutInSecond = timeOutInSecond * 1000;
        SetParams params = new SetParams();
        params.nx().ex(lockExpireTime);
        Long startTime = System.currentTimeMillis();
        String value = null;
        key = getLockKey(key);

        value = UUID.randomUUID().toString();
        while (System.currentTimeMillis() - startTime < timeOutInSecond) {
            String res = jedis.set(key, value, params);
            if ("OK".equals(res)) {
                System.out.println(Thread.currentThread().getName() + " get lock");
                return value;
            }
        }
        //用完之后,必须释资源,否则会导致无连接池可用。
        throw new TimeoutException("获取锁超时");

    }

    private String getLockKey(String key) {
        return "lock:" + key;
    }

    /**
     * 执行结束的脚本
     */
    private static String lua = " if redis.call(\"get\",KEYS[1])== ARGV[1] " +
            "then  return redis.call(\"del\",ARGV[1]) " +
            "else return 0 " +
            "end";

    /**
     * 使用lua脚本 查询和删除的原子操作
     *
     * @param key
     * @param value
     * @return
     */
    public Long releaseLockByLua(Jedis jedis, String key, String value) {
        if (value == null) {
            return 0L;
        }
        System.out.println(Thread.currentThread().getName() + " release lock");
        key = getLockKey(key);
        long res = (Long) jedis.eval(lua, Arrays.asList(key), Arrays.asList(value));
        System.out.println(Thread.currentThread().getName() + " release lock success");

        jedis.close();
        return res;
    }
}

测试代码

public class RedisLockDemo {
    private static ExecutorService pool = new ThreadPoolExecutor(10, 20, 5L, TimeUnit.SECONDS, new ArrayBlockingQueue(100));

    private static String serverIp = "xxx";
    private static volatile int goodsNum = 100;
    private static int threadNum = 20;
    private static CountDownLatch countDownLatch = new CountDownLatch(threadNum);
    private static JedisPool jedisPool;

    static {
        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxTotal(100);
        config.setMaxIdle(100);
        config.setMinIdle(10);
        config.setTestOnBorrow(false);
        config.setTestOnReturn(false);
        config.setTestOnCreate(false);
        config.setBlockWhenExhausted(true);
        config.setMaxWaitMillis(1000);
        jedisPool = new JedisPool(config, serverIp);
    }

    public static void main(String[] args) throws InterruptedException, TimeoutException, BrokenBarrierException {
        RedisDscLock lock = new RedisDscLock();
        for (int tNum = 0; tNum < threadNum; tNum++) {
            pool.submit(new Runnable() {
                @Override
                public void run() {
                    String value = null;
                    Jedis jedis = null;
                    try {
                        jedis = jedisPool.getResource();
                        System.out.println(Thread.currentThread().getName() + "buying");
                        value = lock.getLock(jedis, "goodsNum", 100);
                        goodsNum--;
                        System.out.println("after buging" + goodsNum);
                    } catch (TimeoutException e) {
                        e.printStackTrace();
                    } catch (Exception e) {
                        e.printStackTrace();
                    } finally {
                        try {
                            lock.releaseLockByLua(jedis, "goodsNum", value);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                        if (jedis != null) {
                            jedis.close();
                        }
                        countDownLatch.countDown();
                    }
                }
            });
        }
        //等待所有线程执行完毕
        countDownLatch.await();
        System.out.println("remain goods" + goodsNum);
        pool.shutdown();
    }
}

只是一个实验版本,您要是有问题欢迎留言沟通。

你可能感兴趣的:(redis,单机,分布式锁,java,分布式框架)