Redisson 作为分布式锁

官方文档:https://github.com/redisson/redisson/wiki

  1. 引入依赖

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

    2.配置redission

    @Configuration
    public class MyRedissonConfig {
    /**
    * 所有对 Redisson 的使用都是通过 RedissonClient
    *
    * @return
    * @throws IOException
    */
    @Bean(destroyMethod = "shutdown")
    public RedissonClient redisson() throws IOException {
    // 1、创建配置
    Config config = new Config();
    // Redis url should start with redis:// or rediss://
    config.useSingleServer().setAddress("redis://192.168.163.131:6379"); // 2、根据 Config 创建出 RedissonClient 实例
    return Redisson.create(config);
    }
    }

    3.测试

        @Autowired
    RedissonClient redissonClient; @Test
    public void redission()
    {
    System.out.println(redissonClient);
    }

    4.使用

     @ResponseBody
    @GetMapping("/hello")
    public String hello()
    {
    // 1. 获取一把锁
    RLock lock = redisson.getLock("my-lock");
    // 2. 加锁, 阻塞式等待
    lock.lock();
    try {
    System.out.println("加锁成功,执行业务...");
    Thread.sleep(15000);
    } catch (Exception e) {
    } finally {
    // 3. 解锁 假设解锁代码没有运行,Redisson 会出现死锁吗?(不会)
    System.out.println("释放锁"+Thread.currentThread().getId());
    lock.unlock();
    }
    return "hello";
    }

假设解锁代码没有运行,Redisson 会出现死锁吗?

不会

  • 锁的自动续期,如果业务时间很长,运行期间自动给锁续期 30 s,不用担心业务时间过长,锁自动过期被删掉;
  • 加锁的业务只要运行完成,就不会给当前锁续期,即使不手动续期,默认也会在 30 s 后解锁

源码分析-Redission如何解决死锁

Ctrl+Alt查看方法实现

这是一个加锁方法,不传过期时间

    public void lock() {
try {
//这里过期时间自动赋值成-1
this.lock(-1L, (TimeUnit)null, false);
} catch (InterruptedException var2) {
throw new IllegalStateException();
}
}

然后会调用 this.lock(-1L, (TimeUnit)null, false)方法

private void lock(long leaseTime, TimeUnit unit, boolean interruptibly) throws InterruptedException {
//得到线程ID
long threadId = Thread.currentThread().getId();
//通过线程ID获取到锁
Long ttl = this.tryAcquire(leaseTime, unit, threadId);
//如果没有获取到锁
if (ttl != null) {
RFuture<RedissonLockEntry> future = this.subscribe(threadId);
this.commandExecutor.syncSubscription(future); try {
while(true) {
ttl = this.tryAcquire(leaseTime, unit, threadId);
if (ttl == null) {
return;
} if (ttl >= 0L) {
try {
this.getEntry(threadId).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
} catch (InterruptedException var13) {
if (interruptibly) {
throw var13;
} this.getEntry(threadId).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
}
} else if (interruptibly) {
this.getEntry(threadId).getLatch().acquire();
} else {
this.getEntry(threadId).getLatch().acquireUninterruptibly();
}
}
} finally {
this.unsubscribe(future, threadId);
}
}
}

获取锁方法

    private Long tryAcquire(long leaseTime, TimeUnit unit, long threadId) {
return (Long)this.get(this.tryAcquireAsync(leaseTime, unit, threadId));
}

里面又调用了tryAcquireAsync

private <T> RFuture<Long> tryAcquireAsync(long leaseTime, TimeUnit unit, long threadId) {
//如果传了过期时间
if (leaseTime != -1L) {
return this.tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
}
//没有传过期时间
else {
RFuture<Long> ttlRemainingFuture = this.tryLockInnerAsync(this.commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
ttlRemainingFuture.onComplete((ttlRemaining, e) -> {
if (e == null) {
if (ttlRemaining == null) {
this.scheduleExpirationRenewal(threadId);
} }
});
return ttlRemainingFuture;
}
}

有指定过期时间走tryLockInnerAsync方法,尝试用异步加锁

    <T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
//先把时间转换成internalLockLeaseTime
this.internalLockLeaseTime = unit.toMillis(leaseTime);
//然后执行lua脚本 发给redis执行
return this.commandExecutor.evalWriteAsync(this.getName(), LongCodec.INSTANCE, command, "if (redis.call('exists', KEYS[1]) == 0) then redis.call('hset', KEYS[1], ARGV[2], 1); redis.call('pexpire', KEYS[1], ARGV[1]); return nil; end; if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then redis.call('hincrby', KEYS[1], ARGV[2], 1); redis.call('pexpire', KEYS[1], ARGV[1]); return nil; end; return redis.call('pttl', KEYS[1]);", Collections.singletonList(this.getName()), new Object[]{this.internalLockLeaseTime, this.getLockName(threadId)});
}

没有指定过期时间调用getLockWatchdogTimeout()方法,获取锁的默认看门狗时间,30秒

public long getLockWatchdogTimeout() {
return this.lockWatchdogTimeout;
}
this.lockWatchdogTimeout = 30000L;

还是调用tryLockInnerAsyncredis发送命令,占锁成功返回一个以不变异步编排的RFuture<Long>对象,来进行监听,里面有两个参数ttlRemaining, e

 ttlRemainingFuture.onComplete((ttlRemaining, e) -> {
if (e == null) {
if (ttlRemaining == null) {
this.scheduleExpirationRenewal(threadId);
} }
});

里面有个scheduleExpirationRenewal方法

    private void scheduleExpirationRenewal(long threadId) {
RedissonLock.ExpirationEntry entry = new RedissonLock.ExpirationEntry();
RedissonLock.ExpirationEntry oldEntry = (RedissonLock.ExpirationEntry)EXPIRATION_RENEWAL_MAP.putIfAbsent(this.getEntryName(), entry);
if (oldEntry != null) {
oldEntry.addThreadId(threadId);
} else {
entry.addThreadId(threadId);
//重新设置过期时间
this.renewExpiration();
} }

里面的关键方法renewExpiration执行定时任务,

 private void renewExpiration() {
RedissonLock.ExpirationEntry ee = (RedissonLock.ExpirationEntry)EXPIRATION_RENEWAL_MAP.get(this.getEntryName());
if (ee != null) {
//里面会执行一个定时任务
Timeout task = this.commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
public void run(Timeout timeout) throws Exception {
RedissonLock.ExpirationEntry ent = (RedissonLock.ExpirationEntry)RedissonLock.EXPIRATION_RENEWAL_MAP.get(RedissonLock.this.getEntryName());
if (ent != null) {
Long threadId = ent.getFirstThreadId();
if (threadId != null) {
RFuture<Boolean> future = RedissonLock.this.renewExpirationAsync(threadId);
future.onComplete((res, e) -> {
if (e != null) {
RedissonLock.log.error("Can't update lock " + RedissonLock.this.getName() + " expiration", e);
} else {
if (res) {
RedissonLock.this.renewExpiration();
} }
});
}
}
}
//看门狗时间/3 10秒钟重试一次
}, this.internalLockLeaseTime / 3L, TimeUnit.MILLISECONDS);
ee.setTimeout(task);
}
}

主要是来运行renewExpirationAsync这个方法

 protected RFuture<Boolean> renewExpirationAsync(long threadId) {
return this.commandExecutor.evalWriteAsync(this.getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then redis.call('pexpire', KEYS[1], ARGV[1]); return 1; end; return 0;", Collections.singletonList(this.getName()), new Object[]{this.internalLockLeaseTime, this.getLockName(threadId)});
}

里面传入了一个internalLockLeaseTime时间参数

又是获取看门狗时间

总结

  • 如果传了锁的超时时间,就发送给redis执行脚本,进行占锁,默认超时就是我们指定的时间

  • 如果未指定锁的超时时间,就是使用lockWatchdogTimeout的默认时间30秒,只要占锁成功就会启动一个定时任务【重新给所设置时间,新的过期时间就是lockWatchdogTimeout的默认时间】

    最佳实践使用自定义过期时间,省掉了自动续期时间,自动加锁

读写锁测试

@GetMapping("/write")
@ResponseBody
public String writeValue()
{
RReadWriteLock readWriteLock = redisson.getReadWriteLock("rw-lock");
String s="";
RLock rLock=readWriteLock.writeLock();
try{
//加写锁
rLock.lock();
s= UUID.randomUUID().toString();
Thread.sleep(30000);
redisTemplate.opsForValue().set("writeValue",s);
}catch (Exception e){
e.printStackTrace();
}
finally {
rLock.unlock();
}
return s;
} @GetMapping("/read")
@ResponseBody
public String readValue()
{
RReadWriteLock readWriteLock = redisson.getReadWriteLock("rw-lock");
String s="";
//加读锁
RLock rLock=readWriteLock.readLock();
rLock.lock();
try{
s=redisTemplate.opsForValue().get("writeValue");
}catch (Exception e){
e.printStackTrace();
}
finally {
rLock.unlock();
}
return s;
}

写锁没释放读锁就必须等待,没有写锁读锁都可以读

保证数据的一致性,写锁是一个排他锁、互斥锁,读锁是共享锁。

读读共享、读写互斥、写写互斥、写读互斥,只要有写的存在都必须等待

信号量测试

像车库停车,每进来一辆车,车库减少一个车位,只有当车库还有车位才可以停车

    @GetMapping("/park")
@ResponseBody
public String park() throws InterruptedException {
RSemaphore park = redisson.getSemaphore("park");
//获取一个信号 占一个值
park.acquire();
return "ok";
} @GetMapping("/go")
@ResponseBody
public String go(){
RSemaphore park = redisson.getSemaphore("park");
//释放一个车位
park.release();
return "ok";
}

访问:

gulimall.com/park

gulimall.com/go

信号量可以用作分布式的限流

闭锁

只有等待所有活动都完成才发生,例如当所有班级放学走完才关闭学校大门

    @GetMapping("/lockdoor")
@ResponseBody
public String lockDoor() throws InterruptedException {
RCountDownLatch door = redisson.getCountDownLatch("door");
door.trySetCount(5);
door.await();//等待闭锁都完成
return "放假啦....";
} @GetMapping("/gogo/{id}")
@ResponseBody
public String gogogo(@PathVariable("id") Long id) throws InterruptedException {
RCountDownLatch door = redisson.getCountDownLatch("door");
door.countDown();
return id+"班都走了";
}

缓存一致性解决

在我们读缓存的时候可能会有数据被修改过,为了让我们能够读到最新的数据,有两种处理方法:

双写模式

在把数据写入数据库的时候,同时写入到缓存中

问题:在写的过程中,可能会在第一个线程缓存还没写进,但是第二个查询到缓存又开始写数据,读到的最新数据有延迟,导致产生脏数据

失效模式

在把数据写入数据更新的时候,把缓存删除,下次查询没有缓存再添加缓存

问题:在线程1更新数据的时候消耗大量时间,还没删缓存,线程2进来也没有缓存,读取到原来老的数据,然后更新缓存

我们系统的一致性解决方案

1、缓存的所有数据都有过期时间,数据过期下一次查询触发主动更新

2、读写数据的时候,加上分布式的读写锁

3、遇到实时性、一致性要求高的数据,就应该查数据库,即使慢点。

分布式锁Redission的更多相关文章

  1. redis整理:常用命令,雪崩击穿穿透原因及方案,分布式锁实现思路,分布式锁redission(更新中)

    redis个人整理笔记 reids常见数据结构 基本类型 String: 普通key-value Hash: 类似hashMap List: 双向链表 Set: 不可重复 SortedSet: 不可重 ...

  2. 分布式锁实现(一):Redis

    前言 单机环境下我们可以通过JAVA的Synchronized和Lock来实现进程内部的锁,但是随着分布式应用和集群环境的出现,系统资源的竞争从单进程多线程的竞争变成了多进程的竞争,这时候就需要分布式 ...

  3. redission 分布式锁

    https://my.oschina.net/haogrgr/blog/469439   分布式锁和Redisson实现 Aug 20, 2017 CONTENTS 概述 分布式锁特性 Redis实现 ...

  4. 基于Redisson+SpringBoot的Redission分布式锁

    原文:https://blog.csdn.net/sunct/article/details/80178197 定义分布式锁接口 package com.redis.lock.redisson_spr ...

  5. redis分布式锁实践

    分布式锁在多实例部署,分布式系统中经常会使用到,这是因为基于jvm的锁无法满足多实例中锁的需求,本篇将讲下redis如何通过Lua脚本实现分布式锁,不同于网上的redission,完全是手动实现的 我 ...

  6. Spring Cloud分布式微服务系统中利用redssion实现分布式锁

    在非分布式系统中要实现锁的机制很简单,利用java.util.concurrent.locks包下的Lock和关键字synchronized都可以实现.但是在分布式系统中,如何实现各个单独的微服务需要 ...

  7. Redisson分布式锁实现

    转: Redisson分布式锁实现 2018年09月07日 15:30:32 校长我错了 阅读数:3303   转:分布式锁和Redisson实现 概述 分布式系统有一个著名的理论CAP,指在一个分布 ...

  8. Redisson 分布式锁

    Redisson_百度百科https://baike.baidu.com/item/Redisson/20856570 redission 分布式锁 - 穆穆兔兔 - 博客园https://www.c ...

  9. Redlock:Redis分布式锁最牛逼的实现

    普通实现 说道Redis分布式锁大部分人都会想到:setnx+lua,或者知道set key value px milliseconds nx.后一种方式的核心实现命令如下: - 获取锁(unique ...

随机推荐

  1. servlet中servletContext的五大作用(五)

    1.    获取web的上下文路径 2.    获取全局的参数 3.    作为域对象使用 4.    请求转发 5.    读取web项目的资源文件 package day10.about_serv ...

  2. Java从文件路径中获取文件名的几种方法

    举例:String fName =" G:\Java_Source\navigation_tigra_menu\demo1\img\lev1_arrow.gif " 方法一: 1 ...

  3. SpringCloud升级之路2020.0.x版-24.测试Spring Cloud LoadBalancer

    本系列代码地址:https://github.com/HashZhang/spring-cloud-scaffold/tree/master/spring-cloud-iiford 通过单元测试,我们 ...

  4. 刷题-力扣-1738. 找出第 K 大的异或坐标值

    1738. 找出第 K 大的异或坐标值 题目链接 来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/find-kth-largest-xor-co ...

  5. 通过WebGoat学习java反序列化漏洞

    首发于freebuff. WebGoat-Insecure Deserialization Insecure Deserialization 01 概念 本课程描述了什么是序列化,以及如何操纵它来执行 ...

  6. 前后端数据交互(四)——fetch 请求详解

    fetch 是 XMLHttpRequest 的升级版,使用js脚本发出网络请求,但是与 XMLHttpRequest 不同的是,fetch 方式使用 Promise,相比 XMLHttpReques ...

  7. Structs2的作用是什么??

    struts2是一种重量级的框架,位于MVC架构中的controller,可以分析出来,它是用于接受页面信息然后通过内部处理,将结果返回. 同时struts2也是一个web层的MVC框架,那么什么是s ...

  8. Spring Boot 入门系列(二十五)读取配置文件的几种方式详解!

    在项目开发中经常会用到配置文件,之前介绍过Spring Boot 资源文件属性配置的方法,但是很多朋友反馈说介绍的不够详细全面.所以, 今天完整的分享Spring Boot读取配置文件的几种方式! S ...

  9. idea创建Maven项目没有src目录,且依赖也没有更新

    刚开始用idea的时候,重新配置安装了Maven,但是创建项目的时候发现创建的目录少了很多东西,今天重新查看了一下,发现了原因....... 话不多说,安装配置Maven的方法网上都有,我之前改路径的 ...

  10. [推荐]MyBatis 核心技术与面试 34 讲

    MyBatis 核心技术与面试 34 讲 职业生涯中常被问到: 如何成为某方面的高手? 如何快速搞定某项技术? 我现在的水平处于什么阶段? -- 我暗暗想,我们从小学到中学到大学,经历了大考三六九.小 ...