分布式锁

Redisson

1.引入redission 作为分布式锁框架

https://github.com/redisson/redisson/wiki/2.-%E9%85%8D%E7%BD%AE%E6%96%B9%E6%B3%95  (中文配置文档地址)

<!-- https://mvnrepository.com/artifact/org.redisson/redisson -->
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.13.2</version>
</dependency>
 @Bean(destroyMethod = "shutdown")
    RedissonClient redisson() throws IOException{
        //1.创建配置
        Config config = new Config();
        config.useSingleServer().setAddress("redis://192.168.56.10:6379");
        //2.根据config,创建RedissonClient对象
        return Redisson.create(config);
    }

2.分布式锁和同步器

可重入锁

A、B方法共用一号锁,可重入锁 避免死锁

   //1.调用getLock 获取一把锁,只要锁名一样就是同一把锁
        RLock lock = redissonClient.getLock("my-lock");
        //2.加锁
        lock.lock();
        try{
            System.out.println("加锁成功,执行业务..."+Thread.currentThread().getId());
            Thread.sleep(30000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            //3.解锁
            System.out.println("释放锁。。。"+Thread.currentThread().getId());
            lock.unlock();
        }

Redisson-lock

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

读写锁

/**
     * 保证一定能读到最新数据,修改期间,写锁是一个排他锁(互斥锁),读锁是一个共享锁
     * 写锁没释放读就必须等待
     * 读+读:相当于无锁,并发读,只会在redis中记录所有读锁,他们都会同时加锁成功
     * 写+读:等待写锁释放
     * 写+写:阻塞
     * 读+写:有读锁,写也需要等待
     * 只要有写的地方都必须等待
     * @return
     */
    @GetMapping("/write")
    @ResponseBody
    public String writeValue() {
        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("rw-lock");
        String s = "";
        RLock rLock = readWriteLock.writeLock();
        try {
            rLock.lock();
            System.out.println("写锁加锁成功。。。"+Thread.currentThread().getId());
            s = UUID.randomUUID().toString();
            redisTemplate.opsForValue().set("writeValue", s);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            rLock.unlock();
            System.out.println("写锁释放。。。"+Thread.currentThread().getId());
        }
        return s;
    }

    @RequestMapping("read")
    @ResponseBody
    public String read() {
        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("rw-lock");
        String s = "";
        RLock rLock = readWriteLock.readLock();
        try {
            rLock.lock();
            System.out.println("读锁加锁成功。。。"+Thread.currentThread().getId());
            Thread.sleep(30000);
            s = (String) redisTemplate.opsForValue().get("writeValue");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            rLock.unlock();
            System.out.println("读锁释放。。。"+Thread.currentThread().getId());
        }
        return s;
    }

闭锁(CountDownLatch)

@GetMapping("/lockDoor")
    @ResponseBody
    public String lockDoor() throws InterruptedException {
        RCountDownLatch door = redissonClient.getCountDownLatch("door");
        door.trySetCount(5);
        door.await();
        return "放假了。。。";
    }

    @GetMapping("/gogogo/{id}")
    @ResponseBody
    public String gogogo(@PathVariable("id") Long id){
        RCountDownLatch door = redissonClient.getCountDownLatch("door");
        door.countDown();//计数-1
        return id+"班人都走了。。。";
    }

信号量(Semaphore)

/**
     * 车位停车
     * 3车位
     * 信号量用于分布式限流
     */
    @GetMapping("/park")
    @ResponseBody
    public String park() throws InterruptedException {
        RSemaphore park = redissonClient.getSemaphore("park");
        //park.acquire();//获取一个信号,获取一个值,占一个车位
        boolean b = park.tryAcquire();
        return "ok=>"+b;
    }

    @GetMapping("/go")
    @ResponseBody
    public String gos() throws InterruptedException {
        RSemaphore park = redissonClient.getSemaphore("park");
        park.release();//释放一个值,车位
        return "ok";
    }

缓存数据一致性

双写模式,失效模式 都会导致缓存不一致问题。

1.如果是用户维度数据(订单数据,用户数据),这种并发几率很小,不用考虑。缓存加上过期时间,每隔一段时间触发读的主动更新即可

2.如果是菜单、商品介绍等基础数据,也可以去使用canal订阅binlog的方式

3.缓存数据+过期时间也足够解决大部分业务对于缓存的要求

4.通过加锁保证并发读写,写写的时候按顺序排队。读读无所谓。所以适合使用读写锁

总结:

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

缓存一致性-解决-Canal

  • 使用Canal更新缓存
  • 使用Canal解决数据异构

数据一致性解决方案根据系统来

  1. 缓存的所有数据都有过期时间,数据过期下一次查询触发主动更新
  2. 读写数据的时候,加上分布式的读写锁。经常写经常读,肯定有影响。
原文地址:https://www.cnblogs.com/sgrslimJ/p/13716162.html