3Redis方式 · SpringCloud微服务实战 · 看云

导航

本节代码地址


了解Redis的同学那么应该对setNx(set if not exist)方法不陌生,如果不存在则更新,其可以很好的用来实现我们的分布式锁。对于某个资源加锁我们只需要

setNx key value

这里有个问题,加锁了之后如果机器宕机那么这个锁就不会得到释放所以会加入过期时间,加入过期时间需要和setNx同一个原子操作,在Redis2.8之前我们需要使用Lua脚本达到我们的目的,但是redis2.8之后redis支持nx和ex操作是同一原子操作。

set key value ex 10 nx

常见的EX、PX、NX、XX的含义

  • EXseconds – 设置键key的过期时间,单位时秒
  • PXmilliseconds – 设置键key的过期时间,单位时毫秒
  • NX – 只有键key不存在的时候才会设置key的值
  • XX – 只有键key存在的时候才会设置key的值

本节我们使用Redisson 框架举例说明,它已经封装了一套基于Redis的分布式框架,使用起来也很简单

1. 新建模块

我们在这个模块单独编写Redisson 示例
d14421d19250a5485de8a65d398444d1_MD5.webp

1.1 maven 包引入

需要在maven中引入redisson的包

<!-- Redisson 实现分布式锁 -->
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>2.11.5</version>
</dependency>

1.2 redission配置

  • 针对redis 单实例
    需要配置redis的地址和密码
@Bean
public RedissonClient redissonClient()
{
    Config config = new Config();
    config.useSingleServer()
            .setAddress(redissonAddress).setPassword(redissonPassword);
    RedissonClient redisson = Redisson.create(config);
    return redisson;
}

  • 针对redis 哨兵
    需要配置redis哨兵的地址和密码
@Bean
public RedissonClient redissonClient()
{
    Config config = new Config();
    config.useSentinelServers()
            .addSentinelAddress("redis://127.0.0.1:26379")
            .addSentinelAddress("redis://127.0.0.2:26379")
            .addSentinelAddress("redis://127.0.0.3:26379")
            .setPassword(redissonPassword);
    RedissonClient redisson = Redisson.create(config);
    return redisson;
}

  • 针对redis 集群
    需要配置redis集群的地址和密码,并设置集群的扫描间隔时间
@Bean
public RedissonClient redissonClient()
{
    Config config = new Config();
    config.useClusterServers()
            
            .setScanInterval(2000)
            
            .addNodeAddress("redis://127.0.0.1:6379" )
            .addNodeAddress("redis://127.0.0.1:6380")
            .addNodeAddress("redis://127.0.0.1:6381")
            .addNodeAddress("redis://127.0.0.1:6382")
            .addNodeAddress("redis://127.0.0.1:6383")
            .addNodeAddress("redis://127.0.0.1:6384")
            .setPassword(redissonPassword);
    RedissonClient redisson = Redisson.create(config);
    return redisson;
}

1.3 核心方法

下面是基于Redisson实现的分布式锁帮助类,可以拿去直接使用,包含加锁、释放锁、带时间的加锁、尝试获取锁等。


@Slf4j
@Component
public class RedisLockHelper {


    @Autowired
    private RedissonClient redissonClient;


    
    public  RLock lock(String lockKey) {
        RLock lock = redissonClient.getLock(lockKey);
        lock.lock();
        return lock;
    }

    
    public  void unlock(String lockKey) {
        RLock lock = redissonClient.getLock(lockKey);
        lock.unlock();
    }

    
    public  void unlock(RLock lock) {
        lock.unlock();
    }

    
    public  RLock lock(String lockKey, int timeout) {
        RLock lock = redissonClient.getLock(lockKey);
        lock.lock(timeout, TimeUnit.SECONDS);
        return lock;
    }

    
    public  RLock lock(String lockKey, TimeUnit unit ,int timeout) {
        RLock lock = redissonClient.getLock(lockKey);
        lock.lock(timeout, unit);
        return lock;
    }

    
    public  boolean tryLock(String lockKey, int waitTime, int leaseTime) {
        RLock lock = redissonClient.getLock(lockKey);
        try {
            return lock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            return false;
        }
    }

    
    public  boolean tryLock(String lockKey, TimeUnit unit, int waitTime, int leaseTime) {
        RLock lock = redissonClient.getLock(lockKey);
        try {
            return lock.tryLock(waitTime, leaseTime, unit);
        } catch (InterruptedException e) {
            return false;
        }
    }
}

2. 下面测试下

编写一个单元测试类,开5个线程获取锁,锁的超时时间是1秒,因为每次获取锁会延迟4秒,因此只有一个可以获取到锁。

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Slf4j
public class RedisLockHelperTest {
    @Autowired
    private RedisLockHelper redisLockHelper;

    @Test
    public void testDistributedLock() {



        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 5; i++) {
            fixedThreadPool.submit(new Runnable() {
                @Override
                public void run() {
                    boolean flag = false;
                    try {
                        flag = redisLockHelper.tryLock("test",1,1);
                        if (flag) {
                            log.info("获取锁成功,{}" , Thread.currentThread().getName());
                        } else {
                            log.info("获取锁失败,{}" , Thread.currentThread().getName());
                        }
                        
                        Thread.sleep(4000);
                    } catch (Exception e) {
                        e.printStackTrace();
                    } finally {
                        if (flag) {
                            try {
                                redisLockHelper.unlock("test");
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            });

        }
        try {
            fixedThreadPool.awaitTermination(10,TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

控制台日志如下

2020-03-19 19:10:05.164  INFO 43864 --- [pool-2-thread-2] com.yisu.lock.redis.RedisLockHelperTest  : 获取锁成功,pool-2-thread-2
2020-03-19 19:10:06.145  INFO 43864 --- [pool-2-thread-5] com.yisu.lock.redis.RedisLockHelperTest  : 获取锁失败,pool-2-thread-5
2020-03-19 19:10:06.147  INFO 43864 --- [pool-2-thread-4] com.yisu.lock.redis.RedisLockHelperTest  : 获取锁失败,pool-2-thread-4
2020-03-19 19:10:06.147  INFO 43864 --- [pool-2-thread-1] com.yisu.lock.redis.RedisLockHelperTest  : 获取锁失败,pool-2-thread-1
2020-03-19 19:10:06.148  INFO 43864 --- [pool-2-thread-3] com.yisu.lock.redis.RedisLockHelperTest  : 获取锁失败,pool-2-thread-3