飞道的博客

缓存失效问题和分布式锁引进

457人阅读  评论(0)

缓存失效问题

先来解决大并发读情况下的缓存失效问题;

1、缓存穿透

 缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中,将去查询数据库,但是数 据库也无此记录,我们没有将这次查询的 null 写入缓存,这将导致这个不存在的数据每次 请求都要到存储层去查询,失去了缓存的意义。

 在流量大时,可能 DB 就挂掉了,要是有人利用不存在的 key 频繁攻击我们的应用,这就是 漏洞。

 解决: 缓存空结果、并且设置短的过期时间。

2、缓存雪崩

 缓存雪崩是指在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失 效,请求全部转发到 DB,DB 瞬时压力过重雪崩。

 解决:

原有的失效时间基础上增加一个随机值,比如 1-5 分钟随机,这样每一个缓存的过期时间的 重复率就会降低,就很难引发集体失效的事件。

3、缓存击穿

 对于一些设置了过期时间的 key,如果这些 key 可能会在某些时间点被超高并发地访问, 是一种非常“热点”的数据。

 这个时候,需要考虑一个问题:如果这个 key 在大量请求同时进来前正好失效,那么所 有对这个 key 的数据查询都落到 db,我们称为缓存击穿。

 解决: 加锁

 分布式锁

1、分布式锁与本地锁

2、分布式锁实现 

使用 RedisTemplate 操作分布式锁 

抽取业务代码


  
  1. private Map<String, List<Catelog2Vo>> getDataFromDb () {
  2. String catalogJSON = redisTemplate.opsForValue().get( "catalogJSON");
  3. if (StringUtils.isEmpty(catalogJSON)) {
  4. Map<String, List<Catelog2Vo>> result = JSON.parseObject(catalogJSON, new TypeReference<Map<String, List<Catelog2Vo>>>() {
  5. });
  6. return result;
  7. }
  8. System.out.println( "查询了数据库...");
  9. List<CategoryEntity> selectList = baseMapper.selectList( null);
  10. //1.查出所有一级分类
  11. List<CategoryEntity> level1Categorys = getLevel1Categorys();
  12. //2封装数据
  13. Map<String, List<Catelog2Vo>> parent_cid = level1Categorys.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
  14. //1.每一个的一级分类,查到这个一级分类的二级分类
  15. List<CategoryEntity> categoryEntities = baseMapper.selectList( new QueryWrapper<CategoryEntity>()
  16. .eq( "parent_cid", v.getCatId()));
  17. //2.封装上面的结果
  18. List<Catelog2Vo> catelog2Vos = null;
  19. if (categoryEntities != null) {
  20. catelog2Vos = categoryEntities.stream().map(l2 -> {
  21. Catelog2Vo catelog2Vo = new Catelog2Vo(
  22. v.getParentCid().toString(), null, v.getName().toString(), v.getCatId().toString()
  23. );
  24. //找出当前二级分类的三级分类分装成vo
  25. List<CategoryEntity> categoryEntities1 = baseMapper.selectList( new QueryWrapper<CategoryEntity>().eq( "parent_cid", l2.getCatId()));
  26. List<Catelog2Vo.Catelog3Vo> collect = null;
  27. if (categoryEntities1 != null) {
  28. collect = categoryEntities1.stream().map(l3 -> {
  29. Catelog2Vo. Catelog3Vo catelog3Vo = new Catelog2Vo.Catelog3Vo(l2.getCatId().toString(), l3.getName(), l3.getCatId().toString());
  30. return catelog3Vo;
  31. }).collect(Collectors.toList());
  32. }
  33. catelog2Vo.setCatalog3List(collect);
  34. return catelog2Vo;
  35. }).collect(Collectors.toList());
  36. }
  37. return catelog2Vos;
  38. }));
  39. //3.查到的数据再放入缓存中,将对象转为json放进
  40. String s = JSON.toJSONString(parent_cid);
  41. redisTemplate.opsForValue().set( "catalogJSON", s);
  42. return parent_cid;
  43. }

 分布锁情况代码


  
  1. public Map<String, List<Catelog2Vo>> getCatalogJsonForDbWithRedisLock () {
  2. //占分布式锁,去redis坑
  3. //设置过期时间 ---2设置过期时间和加锁应为原子性同步
  4. String uuid = UUID.randomUUID().toString();
  5. Boolean lock = redisTemplate.opsForValue().setIfAbsent( "lock", uuid, 30,TimeUnit.SECONDS);
  6. if (lock){
  7. //加锁成功,执行业务
  8. //设置过期时间 ---1
  9. // redisTemplate.expire("lock",30, TimeUnit.SECONDS);
  10. System.out.println( "获取分布式锁成功...");
  11. Map<String, List<Catelog2Vo>> dataFromDb = null;
  12. try {
  13. dataFromDb = getDataFromDb();
  14. } finally {
  15. //获取值对比+对比成功删除=》原子操作 lua脚本
  16. String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
  17. Integer lock1 = redisTemplate.execute( new DefaultRedisScript<Integer>(script, Integer.class),
  18. Arrays.asList( "lock"), uuid);
  19. String lockValue = redisTemplate.opsForValue().get( "lock");
  20. // if (lockValue.equals(s)){
  21. // redisTemplate.delete("lock");//删除锁
  22. // }
  23. }
  24. return dataFromDb;
  25. } else {
  26. System.out.println( "获取分布式锁失败...等待重试");
  27. //加锁失败...重试synchronized ()
  28. //休眠100ms重试
  29. try {
  30. Thread.sleep( 200);
  31. } catch (Exception e){
  32. System.out.println(e);
  33. }
  34. return getCatalogJsonForDbWithRedisLock(); //自旋
  35. }
  36. }
//占分布式锁,去redis坑
//设置过期时间 ---设置过期时间和加锁应为原子性同步
Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock",uuid,30,TimeUnit.SECONDS);
//获取值对比+对比成功删除=》原子操作 lua脚本
lua脚本:
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

执行脚本
Integer lock1 = redisTemplate.execute(new DefaultRedisScript<Integer>(script, Integer.class),
        Arrays.asList("lock"), uuid);

本地锁代码 


  
  1. /**
  2. * 从数据库查询数据得到数据
  3. * @return
  4. */
  5. public Map<String, List<Catelog2Vo>> getCatalogJsonForDbWithLocalLock () {
  6. //只要是同一把锁,就能锁住需要这个锁的所有线程
  7. //1.synchronized (this) springboot所有的组件在容器中都是单例的
  8. //todo 本地锁synchronized juc(lock),分布式下应使用分布式锁
  9. synchronized ( this){
  10. String catalogJSON = redisTemplate.opsForValue().get( "catalogJSON");
  11. if (StringUtils.isEmpty(catalogJSON)){
  12. Map<String, List<Catelog2Vo>> result = JSON.parseObject(catalogJSON, new TypeReference<Map<String, List<Catelog2Vo>>>() {});
  13. return result;
  14. }
  15. /**
  16. * 1.将数据库的数据只查一次
  17. */
  18. List<CategoryEntity> selectList = baseMapper.selectList( null);
  19. //1.查出所有一级分类
  20. List<CategoryEntity> level1Categorys = getLevel1Categorys();
  21. //2封装数据
  22. Map<String, List<Catelog2Vo>> parent_cid = level1Categorys.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
  23. //1.每一个的一级分类,查到这个一级分类的二级分类
  24. List<CategoryEntity> categoryEntities = baseMapper.selectList( new QueryWrapper<CategoryEntity>()
  25. .eq( "parent_cid", v.getCatId()));
  26. //2.封装上面的结果
  27. List<Catelog2Vo> catelog2Vos = null;
  28. if (categoryEntities != null) {
  29. catelog2Vos = categoryEntities.stream().map(l2 -> {
  30. Catelog2Vo catelog2Vo = new Catelog2Vo(
  31. v.getParentCid().toString(), null, v.getName().toString(), v.getCatId().toString()
  32. );
  33. //找出当前二级分类的三级分类分装成vo
  34. List<CategoryEntity> categoryEntities1 = baseMapper.selectList( new QueryWrapper<CategoryEntity>().eq( "parent_cid", l2.getCatId()));
  35. List<Catelog2Vo.Catelog3Vo> collect= null;
  36. if (categoryEntities1!= null){
  37. collect = categoryEntities1.stream().map(l3 -> {
  38. Catelog2Vo. Catelog3Vo catelog3Vo = new Catelog2Vo.Catelog3Vo(l2.getCatId().toString(), l3.getName(), l3.getCatId().toString());
  39. return catelog3Vo;
  40. }).collect(Collectors.toList());
  41. }
  42. catelog2Vo.setCatalog3List(collect);
  43. return catelog2Vo;
  44. }).collect(Collectors.toList());
  45. }
  46. return catelog2Vos;
  47. }));
  48. //3.查到的数据再放入缓存中,将对象转为json放进
  49. String s = JSON.toJSONString(parent_cid);
  50. redisTemplate.opsForValue().set( "catalogJSON",s);
  51. return parent_cid;
  52. }
  53. }

 //只要是同一把锁,就能锁住需要这个锁的所有线程
  //1.synchronized (this) springboot所有的组件在容器中都是单例的

  //3.查到的数据再放入缓存中,将对象转为json放进


转载:https://blog.csdn.net/m0_62436868/article/details/128759692
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场