date
Jul 2, 2022
type
Page
status
Invisible
slug
distributed-lock-redisson
summary
概要:基于Redisson实现的分布式锁方案
tags
微服务
spring-cloud-alibaba
商城
category
商城实战
password
Property
Sep 27, 2022 02:57 AM
icon

Redisson分布式锁

1. Redisson的整合

添加对应的依赖
       <dependency>            <groupId>org.redisson</groupId>            <artifactId>redisson</artifactId>            <version>3.16.1</version>        </dependency>
添加对应的配置类
@Configuration public class MyRedisConfig {    @Bean    public RedissonClient redissonClient(){        Config config = new Config();        // 配置连接的信息        config.useSingleServer()               .setAddress("redis://192.168.56.100:6379");        RedissonClient redissonClient = Redisson.create(config);        return  redissonClient;   } }

2. 可重入锁

/**     * 1.锁会自动续期,如果业务时间超长,运行期间Redisson会自动给锁重新添加30s,不用担心业务时间,锁自动过去而造成的数据安全问题     * 2.加锁的业务只要执行完成, 那么就不会给当前的锁续期,即使我们不去主动的释放锁,锁在默认30s之后也会自动的删除     * @return     */    @ResponseBody    @GetMapping("/hello")    public String hello(){        RLock myLock = redissonClient.getLock("myLock");        // 加锁        myLock.lock();        try {            System.out.println("加锁成功...业务处理....." + Thread.currentThread().getName());            Thread.sleep(30000);       }catch (Exception e){       }finally {            System.out.println("释放锁成功..." +  Thread.currentThread().getName());            // 释放锁            myLock.unlock();       }        return "hello";   }

3. 读写锁

根据业务操作我们可以分为读写操作,读操作其实不会影响数据,那么如果还对读操作做串行处理,效率会很低,这时我们可以通过读写锁来解决这个问题
@GetMapping("/writer")    @ResponseBody    public String writerValue(){        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("rw-lock");        // 加写锁        RLock rLock = readWriteLock.writeLock();        String s = null;        rLock.lock(); // 加写锁        try {            s = UUID.randomUUID().toString();            stringRedisTemplate.opsForValue().set("msg",s);            Thread.sleep(30000);       } catch (InterruptedException e) {            e.printStackTrace();       } finally {            rLock.unlock();       }        return s;   }    @GetMapping("/reader")    @ResponseBody    public String readValue(){        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("rw-lock");        // 加读锁        RLock rLock = readWriteLock.readLock();        rLock.lock();        String s = null;        try {            s = stringRedisTemplate.opsForValue().get("msg");       }finally {            rLock.unlock();       }        return s;   }
在读写锁中,只有读读的行为是共享锁,相互之间不影响,只要有写的行为存在,那么就是一个互斥锁(排他锁)

4 闭锁

基于Redisson的Redisson分布式闭锁(CountDownLatch)Java对象 RCountDownLatch采用了与 java.util.concurrent.CountDownLatch相似的接口和用法。
@GetMapping("/lockDoor")    @ResponseBody    public String lockDoor(){        RCountDownLatch door = redissonClient.getCountDownLatch("door");        door.trySetCount(5);        try {            door.await(); // 等待数量降低到0       } catch (InterruptedException e) {            e.printStackTrace();       }        return "关门熄灯...";   }    @GetMapping("/goHome/{id}")    @ResponseBody    public String goHome(@PathVariable Long id){        RCountDownLatch door = redissonClient.getCountDownLatch("door");        door.countDown(); // 递减的操作        return id + "下班走人";   }

5. 信号量(Semaphore)

基于Redis的Redisson的分布式信号量(Semaphore)Java对象RSemaphore采用了与 java.util.concurrent.Semaphore相似的接口和用法。同时还提供了异步(Async)反射式(Reactive)RxJava2标准的接口。
@GetMapping("/park")    @ResponseBody    public String park(){        RSemaphore park = redissonClient.getSemaphore("park");        boolean b = true;        try {            // park.acquire(); // 获取信号 阻塞到获取成功            b = park.tryAcquire();// 返回获取成功还是失败       } catch (Exception e) {            e.printStackTrace();       }        return "停车是否成功:" + b;   }    @GetMapping("/release")    @ResponseBody    public String release(){        RSemaphore park = redissonClient.getSemaphore("park");        park.release();        return "释放了一个车位";   }

6. 缓存数据一致性问题

notion image
notion image
notion image
notion image
针对于上的两种解决方案我们怎么选择?
  1. 缓存的所有数据我们都加上过期时间,数据过期之后主动触发更新操作
  1. 使用读写锁来处理,读读的操作是不相互影响的
无论是双写模式还是失效模式,都会导致缓存的不一致问题。即多个实例同时更新会出事。怎么办?
  1. 如果是用户纬度数据(订单数据、用户数据),这种并发几率非常小,不用考虑这个问题,缓存数据加 上过期时间,每隔一段时间触发读的主动更新即可
  1. 如果是菜单,商品介绍等基础数据,也可以去使用canal订阅binlog的方式。
  1. 缓存数据+过期时间也足够解决大部分业务对于缓存的要求。
  1. 通过加锁保证并发读写,写写的时候按顺序排好队。读读无所谓。所以适合使用读写锁。(业务不关心 脏数据,允许临时脏数据可忽略)

总结:

  • 我们能放入缓存的数据本就不应该是实时性、一致性要求超高的。所以缓存数据的时候加上过期时间,保证每天拿到当前最新数据即可。
  • 我们不应该过度设计,增加系统的复杂性
  • 遇到实时性、一致性要求高的数据,就应该查数据库,即使慢点。