-
分布式锁的基本原理
多个服务同时去一个地方“占坑”,如果占到就执行逻辑,否则就继续等待,直到释放锁.“占坑”可以去redis,也可以去数据库,或任何所有服务都能访问的地方.等待可以使用自旋的方式. -
分布式锁的优化过程
锁的自动续期先设置一个较大的过期时间. -
使用redis实现分布式锁
@Override public Map<String, List<Catelog2Vo>> getCatalogJson(){ // redis缓存存在的三个问题: // 缓存穿透: 数据库中不存在数据多次请求,每次都请求数据库。 解决办法:将null值放入缓存,并设置一个过期时间 // 缓存雪崩: 多个缓存设置过期时间相同,同时失效,导致数据库同时处理大量请求。 解决办法: 过期时间加上一个随机值 // 缓存击穿: 在缓存过期时间刚到期时有大量请求,导致数据库同时处理大量请求。 解决办法:加锁,只让一个请求请求数据库,从数据库中获取数据后放入缓存,其他请求获取锁后先从缓存获取 // 从redis中查询 String catalogJSON = redisTemplate.opsForValue().get("catalogJSON"); // 如果redis中没有,则从数据库中查询 if(StringUtils.isEmpty(catalogJSON)){ return getCatalogJsonFromDbWithRedisLock(); } // 转为指定的类型 return JSON.parseObject(catalogJSON, new TypeReference<Map<String, List<Catelog2Vo>>>(){ }); } /** * redis分布式锁实现 * @return */ public Map<String, List<Catelog2Vo>> getCatalogJsonFromDbWithRedisLock(){ // 1.占分布式锁,去redis占坑 String uuid = UUID.randomUUID().toString(); // 当lock锁不存在的时候加锁,锁的值为UUID值,同时设置过期时间 Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 300, TimeUnit.SECONDS); if(lock){ System.out.println("获取分布式锁成功"); // 2. 加锁成功,执行业务 Map<String, List<Catelog2Vo>> dataFromDb; try{ dataFromDb = getDataFromDb(); }finally { // 3. 通过lua脚本实现比较和删除锁同时执行(比较uuid和lock锁对应的value是否相等,如果相等则删除锁,返回1,否则返回0) String script = "if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; redisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList("lock"), uuid); } return dataFromDb; }else{ try{ Thread.sleep(200); }catch (Exception e){ } System.out.println("获取分布式锁失败。。等待重试"); // 自旋 return getCatalogJsonFromDbWithRedisLock(); } } /** * 从数据库中获取数据 * @return */ public Map<String, List<Catelog2Vo>> getDataFromDb(){ String catalogJSON = redisTemplate.opsForValue().get("catalogJSON"); if(!StringUtils.isEmpty(catalogJSON)){ return JSON.parseObject(catalogJSON, new TypeReference<Map<String, List<Catelog2Vo>>>(){ }); } // 将数据的多次查询变成一次 List<CategoryEntity> selectList = baseMapper.selectList(null); // 1. 查出所有的一级分类 List<CategoryEntity> level1Categorys = getParent_cid(selectList, 0L); // 2. 封装数据 Map<String, List<Catelog2Vo>> parent_cid = level1Categorys.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> { // 根据一级分类,查到二级分类 List<CategoryEntity> categoryEntities = getParent_cid(selectList, v.getCatId()); // 封装上面的结果 List<Catelog2Vo> catelog2Vos = null; if (categoryEntities != null) { catelog2Vos = categoryEntities.stream().map(l2 -> { Catelog2Vo catelog2Vo = new Catelog2Vo(v.getCatId().toString(), null, l2.getCatId().toString(), l2.getName()); // 1. 根据二级分类查到三级分类封装成vo List<CategoryEntity> level3Catelog = getParent_cid(selectList, l2.getCatId()); if(level3Catelog != null){ List<Catelog2Vo.Catelog3Vo> catelog3Vos = level3Catelog.stream().map(l3->{ Catelog2Vo.Catelog3Vo catelog3Vo = new Catelog2Vo.Catelog3Vo(l2.getCatId().toString(), l3.getCatId().toString(), l3.getName()); return catelog3Vo; }).collect(Collectors.toList()); catelog2Vo.setCatalog3List(catelog3Vos); } return catelog2Vo; }).collect(Collectors.toList()); } return catelog2Vos; })); // 从数据库中查到数据后,将数据转为json放入缓存中(因为json具有跨语言、跨平台兼容的特性) String s = JSON.toJSONString(parent_cid); redisTemplate.opsForValue().set("catalogJSON", s, 1, TimeUnit.DAYS); return parent_cid; }
-
使用redisson实现分布式锁
/** * 使用redisson实现分布式锁 * getCatalogJson方法和getDataFromDb方法同上 * @return */ public Map<String, List<Catelog2Vo>> getCatalogJsonFromDbWithRedissonLock() { RLock lock = redisson.getLock("CatalogJson-lock"); lock.lock(); Map<String, List<Catelog2Vo>> dataFromDb; try{ dataFromDb = getDataFromDb(); }finally { lock.unlock(); } return dataFromDb; }
-
redisson一般锁实现
@ResponseBody @GetMapping("/hello") public String hello(){ // 1. 获取一把锁,只要锁的名字一样,就是同一把锁 RLock lock = redisson.getLock("my-lock"); // 2. 加锁. 堵塞式等待,默认加的锁都是30s时间 // 锁的自动续期:如果业务时间较长,运行期间会自动给锁续上新的30s,不用担心业务时间长导致锁自动过期被删掉 // 加锁的业务只要运行完成,就不会给当前锁续期,即使不手动解锁,锁默认在30s后自动删除 lock.lock(); // 10s自动解锁,自动解锁时间一定要大于业务执行时间(因为在在业务执行期间,锁过期时间到了之后,不会自动续期) // 1)如果指定了锁的超时时间,就会发送给redis lua执行脚本,进行占锁,默认超时时间就是指定的时间 // 2)如果没有指定锁的超时时间,就是用LockWatchdogTimeout(看门狗的默认超时时间:30*1000,即30s) // 只要占锁成功,就会启动一个定时任务(重新给锁设置过期时间,新的过期时间就是看门狗的默认时间),每隔看门狗的默认时间/3(即默认10s),就会自动续期 // lock.lock(10, TimeUnit.SECONDS); try{ System.out.println("加锁成功,执行业务。。。" + Thread.currentThread().getId()); Thread.sleep(30000); }catch (Exception e){ }finally { // 3. 解锁 即使解锁代码没有运行,默认30s后也会自动释放锁(前提是业务代码执行完毕,否则会自动延时) System.out.println("释放锁。。。" + Thread.currentThread().getId()); lock.unlock(); } return "hello"; }
-
redisson读写锁实现
// 读写锁能够保证一定能读到最新数据。修改期间,写锁是一个排他锁(互斥锁),读锁是一个共享锁 // 写锁没释放,读和写都必须等待;读锁没释放,都可以读,写必须等待 @GetMapping("/write") @ResponseBody public String writeValue(){ RReadWriteLock lock = redisson.getReadWriteLock("rw-lock"); String s = ""; RLock rLock = lock.writeLock(); try{ // 改数据加写锁,读数据加读锁 rLock.lock(); s = UUID.randomUUID().toString(); Thread.sleep(30000); redisTemplate.opsForValue().set("writeValue", s); }catch (InterruptedException e){ e.printStackTrace(); }finally { rLock.unlock(); } return s; } @GetMapping("/read") @ResponseBody public String readValue(){ String s = ""; RReadWriteLock lock = redisson.getReadWriteLock("rw-lock"); // 加读锁 RLock rLock = lock.readLock(); rLock.lock(); try{ s = redisTemplate.opsForValue().get("writeValue"); }catch (Exception e){ e.printStackTrace(); }finally { rLock.unlock(); } return s; }
-
redisson闭锁实现
/** * 闭锁(确保某些活动直到其他活动都完成后才继续执行)之锁门测试用例 * 5个班的人都走了才可以锁大门 * @return */ @GetMapping("/lockDoor") @ResponseBody public String lockDoor() throws InterruptedException { RCountDownLatch door = redisson.getCountDownLatch("door"); door.trySetCount(5); door.await(); return "放假了"; } @GetMapping("/gogogo/{id}") public String gogogo(@PathVariable("id") Long id){ RCountDownLatch door = redisson.getCountDownLatch("door"); door.countDown();; return id + "班的人都走了"; }
-
redisson信号量实现
/** * 信号量之停车用例测试 */ @GetMapping("/park") @ResponseBody public String park() throws InterruptedException { RSemaphore park = redisson.getSemaphore("park"); park.acquire(); // 获取一个信号,占一个车位 return "ok"; } @GetMapping("/go") public String go(){ RSemaphore park = redisson.getSemaphore("park"); park.release(); // 释放一个车位 return "ok"; }