기술나눔

캐시 분산 잠금 원리 및 기본 사용법

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

분산 잠금 원리 및 용도

 

회전

  1. public Map<String, List<Catelog2Vo>> getCatalogJsonFromDBWithRedisLock() {
  2. Boolean b = redisTemplate.opsForValue().setIfAbsent(Lock, Lock, Duration.ofMinutes(1));
  3. if (!b) {
  4. int i = 10;
  5. while (i > 0) {
  6. Object result = redisTemplate.opsForValue().get(CATALOG_JSON);
  7. try {
  8. TimeUnit.MILLISECONDS.sleep(100);
  9. } catch (InterruptedException e) {
  10. throw new RuntimeException(e);
  11. }
  12. if (result != null) {
  13. System.out.println("命中缓存 db lock");
  14. return (Map<String, List<Catelog2Vo>>) result;
  15. }
  16. i--;
  17. }
  18. throw new RuntimeException("系统繁忙,请重新访问");
  19. }
  20. //1.查出所有1级分类
  21. List<CategoryEntity> selectList = baseMapper.selectList(null);
  22. /**
  23. * 将数据库的多次查询变成一次
  24. */
  25. System.out.println("查询了数据库");
  26. //2. 封装数据
  27. List<CategoryEntity> level1Category = selectList.stream().filter(s -> s.getParentCid().equals(0L)).collect(Collectors.toList());
  28. Map<String, List<Catelog2Vo>> map = level1Category.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
  29. //1.每一个的一级分类,查到1级分类的所有二级分类
  30. List<CategoryEntity> categoryEntities = selectList.stream().filter(s -> s.getParentCid().equals(v.getCatId())).collect(Collectors.toList());
  31. List<Catelog2Vo> catelog2VoList = categoryEntities.stream().map(c -> {
  32. Catelog2Vo catelog2Vo = new Catelog2Vo();
  33. catelog2Vo.setId(c.getCatId().toString());
  34. catelog2Vo.setName(c.getName());
  35. catelog2Vo.setCatalog1Id(v.getCatId().toString());
  36. List<CategoryEntity> categoryEntities1 = selectList.stream().filter(s -> s.getParentCid().equals(c.getCatId())).collect(Collectors.toList());
  37. List<Catelog2Vo.Catelog3Vo> collect = categoryEntities1.stream().map(c3 -> {
  38. Catelog2Vo.Catelog3Vo catelog3Vo = new Catelog2Vo.Catelog3Vo();
  39. catelog3Vo.setId(c3.getCatId().toString());
  40. catelog3Vo.setName(c3.getName());
  41. catelog3Vo.setCatalog2Id(c.getCatId().toString());
  42. return catelog3Vo;
  43. }).collect(Collectors.toList());
  44. catelog2Vo.setCatalog3List(collect);
  45. return catelog2Vo;
  46. }).collect(Collectors.toList());
  47. return catelog2VoList;
  48. }));
  49. if (map == null) {
  50. /**
  51. * 解决缓存穿透
  52. */
  53. map = new HashMap<>();
  54. }
  55. redisTemplate.opsForValue().set(CATALOG_JSON, map, Duration.ofDays(1));
  56. redisTemplate.delete(Lock);
  57. return map;
  58. }

  1. public Map<String, List<Catelog2Vo>> getCatalogJsonFromDBWithRedisLock() {
  2. String uuid = UUID.randomUUID().toString();
  3. Boolean b = redisTemplate.opsForValue().setIfAbsent(Lock, uuid, Duration.ofMinutes(1));
  4. if (!b) {
  5. int i = 10;
  6. while (i > 0) {
  7. Object result = redisTemplate.opsForValue().get(CATALOG_JSON);
  8. try {
  9. TimeUnit.MILLISECONDS.sleep(100);
  10. } catch (InterruptedException e) {
  11. throw new RuntimeException(e);
  12. }
  13. if (result != null) {
  14. System.out.println("命中缓存 db lock");
  15. return (Map<String, List<Catelog2Vo>>) result;
  16. }
  17. i--;
  18. }
  19. throw new RuntimeException("系统繁忙,请重新访问");
  20. }
  21. //1.查出所有1级分类
  22. List<CategoryEntity> selectList = baseMapper.selectList(null);
  23. /**
  24. * 将数据库的多次查询变成一次
  25. */
  26. System.out.println("查询了数据库");
  27. //2. 封装数据
  28. List<CategoryEntity> level1Category = selectList.stream().filter(s -> s.getParentCid().equals(0L)).collect(Collectors.toList());
  29. Map<String, List<Catelog2Vo>> map = level1Category.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
  30. //1.每一个的一级分类,查到1级分类的所有二级分类
  31. List<CategoryEntity> categoryEntities = selectList.stream().filter(s -> s.getParentCid().equals(v.getCatId())).collect(Collectors.toList());
  32. List<Catelog2Vo> catelog2VoList = categoryEntities.stream().map(c -> {
  33. Catelog2Vo catelog2Vo = new Catelog2Vo();
  34. catelog2Vo.setId(c.getCatId().toString());
  35. catelog2Vo.setName(c.getName());
  36. catelog2Vo.setCatalog1Id(v.getCatId().toString());
  37. List<CategoryEntity> categoryEntities1 = selectList.stream().filter(s -> s.getParentCid().equals(c.getCatId())).collect(Collectors.toList());
  38. List<Catelog2Vo.Catelog3Vo> collect = categoryEntities1.stream().map(c3 -> {
  39. Catelog2Vo.Catelog3Vo catelog3Vo = new Catelog2Vo.Catelog3Vo();
  40. catelog3Vo.setId(c3.getCatId().toString());
  41. catelog3Vo.setName(c3.getName());
  42. catelog3Vo.setCatalog2Id(c.getCatId().toString());
  43. return catelog3Vo;
  44. }).collect(Collectors.toList());
  45. catelog2Vo.setCatalog3List(collect);
  46. return catelog2Vo;
  47. }).collect(Collectors.toList());
  48. return catelog2VoList;
  49. }));
  50. if (map == null) {
  51. /**
  52. * 解决缓存穿透
  53. */
  54. map = new HashMap<>();
  55. }
  56. redisTemplate.opsForValue().set(CATALOG_JSON, map, Duration.ofDays(1));
  57. Object o = redisTemplate.opsForValue().get(Lock);
  58. if (o != null && o.equals(uuid)) {
  59. redisTemplate.delete(Lock);
  60. }
  61. return map;
  62. }

여전히 문제가 있습니다

전송 과정에 시간이 걸리므로 이때 KEY가 만료되면 다른 스레드가 들어와서 KEY를 생성한 후 이전 스레드로 데이터를 반환합니다. KEY가 삭제되면 다른 스레드에서 새로 추가한 키가 반환됩니다. 삭제되었습니다.

값 비교 가져오기 + 성공적으로 비교 삭제 = 원자적 작업

redis+lua 스크립트 구현

 public static final String Lock = "Lock";
  1. public Map<String, List<Catelog2Vo>> getCatalogJsonFromDBWithRedisLock() {
  2. String uuid = UUID.randomUUID().toString();
  3. Boolean b = redisTemplate.opsForValue().setIfAbsent(Lock, uuid, Duration.ofMinutes(5));
  4. if (!b) {
  5. System.out.println("获取分布式锁失败,等待重试");
  6. int i = 10;
  7. while (i > 0) {
  8. Object result = redisTemplate.opsForValue().get(CATALOG_JSON);
  9. try {
  10. TimeUnit.MILLISECONDS.sleep(100);
  11. } catch (InterruptedException e) {
  12. e.printStackTrace();
  13. }
  14. if (result != null) {
  15. System.out.println("命中缓存 db lock");
  16. return (Map<String, List<Catelog2Vo>>) result;
  17. }
  18. i--;
  19. }
  20. throw new RuntimeException("系统繁忙,请重新访问");
  21. }
  22. //1.查出所有1级分类
  23. /**
  24. * 将数据库的多次查询变成一次
  25. */
  26. System.out.println("获取分布式锁成功");
  27. //2. 封装数据
  28. Map<String, List<Catelog2Vo>> map = null;
  29. try {
  30. System.out.println("查询了数据库");
  31. List<CategoryEntity> selectList = baseMapper.selectList(null);
  32. List<CategoryEntity> level1Category = selectList.stream().filter(s -> s.getParentCid().equals(0L)).collect(Collectors.toList());
  33. map = level1Category.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
  34. //1.每一个的一级分类,查到1级分类的所有二级分类
  35. List<CategoryEntity> categoryEntities = selectList.stream().filter(s -> s.getParentCid().equals(v.getCatId())).collect(Collectors.toList());
  36. List<Catelog2Vo> catelog2VoList = categoryEntities.stream().map(c -> {
  37. Catelog2Vo catelog2Vo = new Catelog2Vo();
  38. catelog2Vo.setId(c.getCatId().toString());
  39. catelog2Vo.setName(c.getName());
  40. catelog2Vo.setCatalog1Id(v.getCatId().toString());
  41. List<CategoryEntity> categoryEntities1 = selectList.stream().filter(s -> s.getParentCid().equals(c.getCatId())).collect(Collectors.toList());
  42. List<Catelog2Vo.Catelog3Vo> collect = categoryEntities1.stream().map(c3 -> {
  43. Catelog2Vo.Catelog3Vo catelog3Vo = new Catelog2Vo.Catelog3Vo();
  44. catelog3Vo.setId(c3.getCatId().toString());
  45. catelog3Vo.setName(c3.getName());
  46. catelog3Vo.setCatalog2Id(c.getCatId().toString());
  47. return catelog3Vo;
  48. }).collect(Collectors.toList());
  49. catelog2Vo.setCatalog3List(collect);
  50. return catelog2Vo;
  51. }).collect(Collectors.toList());
  52. return catelog2VoList;
  53. }));
  54. if (map == null) {
  55. /**
  56. * 解决缓存穿透
  57. */
  58. map = new HashMap<>();
  59. }
  60. redisTemplate.opsForValue().set(CATALOG_JSON, map, Duration.ofDays(1));
  61. } catch (Exception e) {
  62. e.printStackTrace();
  63. } finally {
  64. //lua脚本解锁
  65. //如果获取key等于传过来的值,就执行删除操作,否则就不执行
  66. String script="if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
  67. Long execute = redisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList(Lock), uuid);
  68. if (execute==1){
  69. System.out.println("原子删锁成功");
  70. }else {
  71. System.out.println("原子删锁失败");
  72. }
  73. }
  74. return map;
  75. }

단 한 명만 데이터베이스에 쿼리했습니다.