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解决数据异构
数据一致性解决方案根据系统来
- 缓存的所有数据都有过期时间,数据过期下一次查询触发主动更新
- 读写数据的时候,加上分布式的读写锁。经常写经常读,肯定有影响。