Redisson《一:可重入锁》

1,771 阅读5分钟

简介:

Redisson是架设在Redis基础上的一个Java驻内存数据网格(In-Memory Data Grid)。充分的利用了Redis键值数据库提供的一系列优势,基于Java实用工具包中常用接口,为使用者提供了一系列具有分布式特性的常用工具类。使得原本作为协调单机多线程并发程序的工具包获得了协调分布式多机多线程并发系统的能力,大大降低了设计和研发大规模分布式系统的难度。同时结合各富特色的分布式服务,更进一步简化了分布式环境中程序相互之间的协作。

Jedis,Redisson,Lettuce三者的区别

共同点:都提供了基于Redis操作的Java API,只是封装程度,具体实现稍有不同。

不同点: Jedis 是Redis的Java实现的客户端。支持基本的数据类型如:String、Hash、List、Set、Sorted Set。 特点:使用阻塞的I/O,方法调用同步,程序流需要等到socket处理完I/O才能执行,不支持异步操作。Jedis客户端实例不是线程安全的,需要通过连接池来使用Jedis。 Redisson 优点点:分布式锁,分布式集合,可通过Redis支持延迟队列。 Lettuce 用于线程安全同步,异步和响应使用,支持集群,Sentinel,管道和编码器。 基于Netty框架的事件驱动的通信层,其方法调用是异步的。Lettuce的API是线程安全的,所以可以操作单个Lettuce连接来完成各种操作

一:如何使用redisson

1.1导入依赖

由于是第一次使用redisson,我们选择第一个依赖,详细了解一下redisson的配置参数等 image.png

<!-- 使用redisson作为分布式锁,以及分布式对象等功能框架 -->
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.12.0</version>
</dependency>

1.2配置resisson

1.2.1文件方式配置

Redisson的配置文件可以是或YAML格式。 也通过调用config.fromYAML方法并指定一个File实例来实现读取YAML格式的配置:

Config config = Config.fromYAML(new File("config-file.yaml"));
RedissonClient redisson = Redisson.create(config);

调用config.toYAML方法可以将一个Config配置实例序列化为一个含有YAML数据类型的字符串:

Config config = new Config();
// ... 省略许多其他的设置
String jsonFormat = config.toYAML();
1.2.2程序化配置方法
/**
 * 关于redisson相关配置类
 */
@Configuration
public class MyRedissonConfig {
    @Bean(destroyMethod = "shutdown")//随容器停止而销毁
    public RedissonClient Redisson(){
        Config config=new Config();
        // Redis url should start with redis:// or rediss:// (for SSL connection)
        config.useSingleServer().setAddress("redis://192.168.56.10:6379");
        RedissonClient redissonClient= Redisson.create(config);
        return  redissonClient;
    }
}

1.3测试RedissonClient是否配置成功

编写测试类

@SpringBootTest
@RunWith(SpringRunner.class)
public class RedissonTest {
    @Autowired
    private RedissonClient redissonClient;
    @Test
    public void redisson(){
        System.out.println(redissonClient);
    }
}

执行测试类方法

image.png 发现已经获取到RedissonClient对象,至此redisson配置成功

二:使用redisson的lock可重入锁

主要代码如下:

    public  String hello(){
        RLock mylock = redissonClient.getLock("mylock");//获取锁
        //2:加锁
        mylock.lock();
        //mylock.lock(10, TimeUnit.SECONDS);
        try {
            System.out.println("加锁成功,执行业务---》 对应线程号是:"+Thread.currentThread().getId()+",对应线程名是:"+Thread.currentThread().getName());
            Thread.sleep(30000);//线程休眠30秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            System.out.println("释放当前锁----》 对应线程号是:"+Thread.currentThread().getId());
            mylock.unlock();
        }
        return "hello,world!";
    }

lock可重入锁(Reentrant Lock)实现起来比较简单,下面主要详述一下lock()方法里面带不带超时时间的区别

2.1默认不带超时时间

如果是mylock.lock();这种形式加锁,这种加锁是一种阻塞式等待,我们查看源码得知默认超时时间是30s,

RFuture<Long> ttlRemainingFuture = this.tryLockInnerAsync(this.commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
this.lockWatchdogTimeout = 30000L;

//1)锁的自动续期,如果业务超长,运行期间自动给锁续上新的30s,不用担心业务时间长,锁自动过期被删除 //2)加索的业务只要运行完成,就不会给当前锁续期,即使不手动解锁,锁默认在30s以后自动删除。 //3)未指定锁的超时时间,就会使用30*1000【lockWatchdogTimeout看门狗的默认时间】; //只要占锁成功,就会启动一个定时任务【重新给锁设置过期时间,新的过期时间就是看门狗的默认时间】,每隔10s都会自动续期 //this.internalLockLeaseTime / 3L this.internalLockLeaseTime = commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout() //每隔10s就会自动续期。

2.2lock方法带上超时时间

mylock.lock(10, TimeUnit.SECONDS);

10秒自动解锁,自动解锁时间一定要大于业务执行时间。 如果我们传递了锁的超时时间,就会发送给redis执行脚本,进行占锁,默认超时就是我们指定的时间,不会自动续期。

三:lock可重入锁总结

此外,redisson还提供了tryLock,lockAsync加锁方法,详情可参考官方文档 github.com/redisson/re…

四:对getCatalogJsonWithRedisLock方法改造

在未使用redisson前,我们用的方法如下:

public Map<String, List<Catelog2Vo>> getCatalogJsonWithRedisLock() {
        //  synchronized (this) {}
    String uuId= UUID.randomUUID().toString();
    //Boolean mykey = redisTemplate.opsForValue().setIfAbsent("mykey", "1111");
    //设置5分钟的过期时间
    Boolean mykey =redisTemplate.opsForValue().setIfAbsent("lock",uuId,300,TimeUnit.SECONDS);
    Map<String, List<Catelog2Vo>>  dataFromRedis=null;
    if(mykey){
        try{
            dataFromRedis=getCatalogJsonWithRedisCatch();
        }catch (Exception e){
        }finally {
            String lua="if redis.call("get",KEYS[1]) == ARGV[1] then\n" +
                    "    return redis.call("del",KEYS[1])\n" +
                    "else\n" +
                    "    return 0\n" +
                    "end";
         redisTemplate.execute(new DefaultRedisScript<Long>(lua,Long.class), Arrays.asList("lock"),uuId);
            //执行完业务后释放锁
            System.out.println("释放锁--->");
        }
        return dataFromRedis;
    }else{
            System.out.println("重试获取锁--->");
            try {
                TimeUnit.MILLISECONDS.sleep(100);
            } catch (InterruptedException e) { e.printStackTrace(); }
            return  getCatalogJsonWithRedisLock();
        }
    }

使用redisson进行改造,代码如下:

public Map<String, List<Catelog2Vo>> getCatalogJsonWithRedissonLock() {
    //锁的粒度,越细越快
    //锁的粒度,具体缓存的是某个数据,比如11号商品:product-11-lock product-12-lock
    RLock lock = redissonClient.getLock("CatalogJson-lock");
    Map<String,List<Catelog2Vo>> map=null;
    lock.lock();
    try {
        map=getCatalogJsonWithRedisCatch();
    }catch (Exception e){
        log.info("获取到锁,执行业务发生异常,异常原因是{}",e);
    }finally {
        lock.unlock();
    }

    return map;
}

效果一目了然!!!

思考一个问题:实际开发场景下会不会传leaseTime参数呢,原因是啥呢?