zoukankan      html  css  js  c++  java
  • redis分布式锁的实现

    1. 实现原理

    1.1. 使用setnx命令

    1. 加锁:setnx(lock_key,val),根据返回结果若值设置成功,则key不存在,加锁成功,反之key已经存在,加锁失败。
    2. 解锁:del(lock_key)
    3. 死锁问题:线程1获取锁成功,在未执行完任务时挂掉,没有显示的释放锁,那么其它线程就永远无法获取改锁造成死锁。所以需要设置过期时间,可以利用
      expire命令,但是setnx和expire命令是两个动作无法保证加锁操作原子性。还有个问题,假设线程1设置锁成功,但是任务没有执行完时锁已经超时,此时线程2抢占了锁,然后线程1执行完了进行del解锁,此时将会错误的对线程2进行锁释放。

    1.2. 使用set(locl_key,val ,expire_time,NX)命令

    针对setnx的问题,可以利用set(locl_key,val ,expire_time,NX)命令,该命令类似setnx并且可以设置过期时间,将val值设置成服务器节点地址加上当前线程id。如果业务代码执行时间大于过期时间,针对这个问题,我们可以让获得锁的线程开启一个守护线程,使用expire命令用来给快要过期的锁“续航”。比如,设置过期时间为60s,每当50s该key还存在时就进行续命50s。

    2. 实现代码

    2.1. 锁实现 RedisLock.java

    import redis.clients.jedis.Jedis;
    import redis.clients.jedis.JedisPool;
    import redis.clients.jedis.params.SetParams;
    
    import java.net.InetAddress;
    import java.util.Objects;
    
    /**
     * @author 陈玉林
     * @desc TODO
     * @date 2020/6/28 9:42
     */
    public class RedisLock {
        private String lockKey;
        private static final int EXPIRE_TIME = 30;
        private static final String SUCCESS = "OK";
        private static JedisPool jedisPool = new JedisPool();
    
        public RedisLock(String lockKey) {
            this.lockKey = lockKey;
        }
    
        public boolean lock() {
            SetParams setParams = new SetParams().nx().ex(EXPIRE_TIME);
            final String lockVal = getLockValue();
            final Jedis jedis = jedisPool.getResource();
            final String lockResult = jedis.set(lockKey, lockVal, setParams);
            jedis.close();
            boolean result = Objects.nonNull(lockResult) && SUCCESS.equals(lockResult);
            if (result) {
                GuardThread guardThread = new GuardThread(lockKey, lockVal);
                guardThread.setDaemon(true);
                guardThread.start();
            }
            return result;
        }
    
        /**
         * 使用主机ip+线程id作为锁的内容,在锁续命时判断
         * @return String
         */
        private String getLockValue() {
            try {
                InetAddress addr = InetAddress.getLocalHost();
                return addr.getHostAddress() + Thread.currentThread().getId();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    
    
        private class GuardThread extends Thread {
            private String key;
            private String lockVal;
    
            public GuardThread(String key, String lockVal) {
                super("GuardThread");
                this.key = key;
                this.lockVal = lockVal;
            }
    
            @Override
            public void run() {
                while (true) {
                    try {
                        //间隔1s检测一次,节约cpu资源
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    final Jedis jedis = jedisPool.getResource();
                    String lockVal = jedis.get(key);
                    if (this.lockVal.equals(lockVal)) {
                        final Long ttl = jedis.ttl(key);
                        System.out.println(String.format("线程【%s】持有锁剩余时间:%s", lockVal, ttl));
                        //锁寿命小于10s进行续命
                        if (ttl < 10) {
                            jedis.expire(key, EXPIRE_TIME);
                            System.out.println(String.format("线程【%s】续期后剩余时间:%s", lockVal, jedis.ttl(key)));
                        }
                    }
                    jedis.close();
                }
            }
        }
    
    
        public void unLock() {
            final Jedis jedis = jedisPool.getResource();
            jedis.del(lockKey);
            jedis.close();
        }
    
    }
    

    2.2. 测试

    package com.maxch.test;
    
    import java.util.concurrent.*;
    
    /**
     * @author 陈玉林
     * @desc TODO
     * @date 2020/6/29 14:38
     */
    public class RedisLockTest {
        public static void main(String[] args) {
            ExecutorService service = Executors.newFixedThreadPool(3);
            service.execute(new MyTask());
            service.execute(new MyTask());
            service.execute(new MyTask());
            service.shutdown();
        }
    
        private static class MyTask implements Runnable {
    
            @Override
            public void run() {
                RedisLock redisLock = new RedisLock("1");
                boolean lock = redisLock.lock();
                if (lock) {
                    System.out.println(String.format("线程【%s】获取锁成功!", Thread.currentThread().getId()));
                    try {
                        //模拟业务处理时间超过锁超时时间,使用守护线程为锁续命
                        System.out.println(String.format("线程【%s】模拟阻塞40秒!", Thread.currentThread().getId()));
                        Thread.sleep(40000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        boolean unLock = redisLock.unLock();
                        if (!unLock) {
                            System.out.println(String.format("线程【%s】解锁失败!", Thread.currentThread().getId()));
                        }
                    }
                } else {
                    System.out.println(String.format("线程【%s】获取锁失败!", Thread.currentThread().getId()));
                }
            }
        }
    }
    
    只有把命运掌握在自己手中,从今天起开始努力,即使暂时看不到希望,也要相信自己。因为比你牛几倍的人,依然在努力。
  • 相关阅读:
    Qemu之Network Device全虚拟方案三: I/O虚拟化
    FusionCharts简单教程(一)---建立第一个FusionCharts图形
    ubuntu下安装xlrd模块,Mysqldb模块
    ubuntu 步步为营之uclinux编译和移植(完整版)
    设置Android设备在睡眠期间始终保持WLAN开启的代码实现
    x86汇编指令具体解释
    你的flume-ng的第一篇博客
    IT运维管理市场
    Java实现第八届蓝桥杯外星日历
    Java实现第八届蓝桥杯外星日历
  • 原文地址:https://www.cnblogs.com/freesky168/p/14358169.html
Copyright © 2011-2022 走看看