前言

上回说到如何应用zookeeper实现分布式锁,它是通过节点的新建和删除来实现的,这种频繁的io操作在并发很高的状况下必定是不实用的,那这节咱们来看看如何应用redis实现分布式锁。

咱们都晓得redis最大的劣势就是速度快,大部分操作都是在内存中间接实现的,这就和io操作不在一个数量级上了。

手动实现分布式锁

实现逻辑

首先咱们要理清redis实现分布式锁的逻辑:在redis中,咱们次要通过setnx这个命令来实现分布式锁,这个命令什么意思呢?set not exit,只有在key不存在时,才会设值胜利,这就能够模拟出各个客户端过去拿锁的过程,当多个客户端同时过去获取锁时,当有一个setnx胜利时,那其余客户端都会失败,那这些失败的客户端怎么办呢?咱们能够通过循环期待来反复获取锁,直到获取锁胜利。

获取到锁的客户端能够通过删除节点来开释锁,这里有一点须要留神:在zookeeper中能够通过长期节点来避免客户端宕机等起因造成的死锁问题,那在redis中就须要无效工夫来避免了,当客户端在肯定工夫范畴内还没有开释锁的话,就须要主动删除节点来开释锁。

留神点

1、在获取锁的时候须要setnx和设置无效工夫,这两步必须原子操作,否则在高并发状况下必定会出问题。

从 Redis 2.6.12 版本开始, set 命令的行为能够通过一系列参数来批改:SET key value [EX seconds] [PX milliseconds] [NX|XX],通过参数设值,能够同时实现setnx和setex两种成果,因而不必散布执行,这就保障了操作的原子性。

2、在获取锁设值的时候,须要设值一个线程的惟一id,这里能够应用uuid来实现,在开释锁的时候,也就是删除key须要判断值是否和以后线程的uuid一样,一样才示意是以后持有的锁。

代码实现

测试redis客户端选用jeds,须要引入jedis包

<dependency>    <groupId>redis.clients</groupId>    <artifactId>jedis</artifactId></dependency>

写一个工具类来获取jedis

public class RedisUtil {    private static volatile JedisPool jedisPool = null;    private RedisUtil(){}    public static JedisPool getJedisPool() {        if (jedisPool == null) {            synchronized (RedisUtil.class) {                if (jedisPool == null) {                    GenericObjectPoolConfig config = new GenericObjectPoolConfig();                    config.setMaxTotal(100);                    config.setMaxIdle(20);                    jedisPool = new JedisPool(config, "127.0.0.1", 6379);                }            }        }        return jedisPool;    }    public static Jedis getJedis() {        return getJedisPool().getResource();    }}

下面jedis连接池应用单例来保障唯一性,如果应用spring框架的话,能够交给spring来治理,我这里只是单纯测试类。

接着写获取锁和开释锁办法

public interface RedisLock {    boolean tryLock(String key, String value);    void unLock(String key, String value) throws Exception;}public class RedisLockImpl implements RedisLock {    @Override    public boolean tryLock(String key, String value) {        try {            Jedis jedis = RedisUtil.getJedis();            // 胜利返回OK,失败返回null            String set = jedis.set(key, value, "NX", "PX", 10000);            return set != null;        } catch (Exception e) {            e.printStackTrace();        }        return false;    }    @Override    public void unLock(String key, String value) throws Exception{        Jedis jedis = RedisUtil.getJedis();        String s = jedis.get(key);        // 判断以后uuid是否和redis中一样        if (s == null || !s.equals(value)) {            throw new Exception("此对象没有获取锁,无奈开释");        }        jedis.del(key);    }}

最初咱们来测试一下,因为在本地是单机状况下,能够应用多线程来模仿分布式。

public class RedisLockTest {    private static ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 20, 0, TimeUnit.MILLISECONDS, new LinkedBlockingDeque<Runnable>(1024));    public static void main(String[] args) throws Exception{        RedisLock redisLock = new RedisLockImpl();        String key = "lock:demo:1";        for (int i = 0; i < 2; i++) {            threadPoolExecutor.execute(() -> {                // 线程惟一id                String value = UUID.randomUUID().toString();                // 循环期待获取锁                while (true) {                    try {                        boolean flag = redisLock.tryLock(key, value);                        if (flag) {                            System.out.println(Thread.currentThread().getName() + "取得锁胜利,开始工作");                            Thread.sleep(1000);                            System.out.println(Thread.currentThread().getName() + "工作一秒实现,开释锁");                            redisLock.unLock(key, value);                            break;                        } else {                            System.out.println(Thread.currentThread().getName() + "取得锁失败");                            Thread.sleep(2000);                            System.out.println(Thread.currentThread().getName() + "期待2s");                        }                    } catch (Exception e) {                        e.printStackTrace();                    }                }            });        }    }}后果:pool-1-thread-1取得锁胜利,开始工作pool-1-thread-2取得锁失败pool-1-thread-1工作一秒实现,开释锁pool-1-thread-2期待2spool-1-thread-2取得锁胜利,开始工作pool-1-thread-2工作一秒实现,开释锁

下面只是简略的实现了redis分布式锁的次要逻辑,外面还有很多有余的中央,比方开释锁的时候,咱们先是查问值再比拟,最初再删除,这就不能保障原子性了,这种状况能够应用lua脚本来实现,这里就不过多介绍。

在实在工作中,如果要应用redis分布式锁,根本都是应用开源框架redisson来实现。

Redisson实现分布式锁

应用redisson来实现分布式锁就很简略了,外面实现的细节框架都曾经帮咱们实现好了。

首先引入redisson包

<dependency>    <groupId>org.redisson</groupId>    <artifactId>redisson</artifactId>    <version>3.11.5</version></dependency>

在本地同样应用多线程来模仿分布式环境

public class RedissonTest {    private static RedissonClient redissonClient;    static {        Config config = new Config();        config.useSingleServer().setAddress("redis://127.0.0.1:6379");        redissonClient = Redisson.create(config);    }    public static void main(String[] args) {        for (int i = 0; i < 2; i++) {            new Thread(() -> {                RLock lock = redissonClient.getLock("redisson:lock");                lock.lock();                System.out.println(Thread.currentThread().getName()+"获取到锁");                try {                    System.out.println(Thread.currentThread().getName()+"操作");                    Thread.sleep(3000);                    System.out.println(Thread.currentThread().getName()+"操作完结");                }catch (Exception e){                    e.printStackTrace();                }finally {                    System.out.println(Thread.currentThread().getName()+"开释锁");                    lock.unlock();                }            }).start();        }    }}输入:Thread-1获取到锁Thread-1操作Thread-1操作完结Thread-1开释锁Thread-2获取到锁Thread-2操作Thread-2操作完结Thread-2开释锁

总结

在分布式环境下,如果要应用分布式锁,须要联合本身业务需要来抉择适合的分布式锁;咱们都晓得,分布式中有个CAP准则,一致性(Consistency)、可用性(Availability)、分区容错性(Partition tolerance);个别分布式只能保障两点,AP或者CP,如果零碎最求一致性,那么就抉择zookeeper分布式锁,如果零碎最求可用性,那么就抉择redis分布式锁;


<center>扫一扫,关注我</center>