Compartir tecnología

caché - caché utiliza 2

2024-07-12

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

1. Avería, penetración y avalancha de caché

1. Penetración de caché

Se refiere a consultar datos que no deben existir. Dado que el caché no se accede, se consultará la base de datos, pero la base de datos no tiene dicho registro. No escribimos el valor nulo de esta consulta en el caché, lo que resultará. en cada solicitud de estos datos inexistentes, debe ir a la capa de almacenamiento para realizar la consulta, lo que pierde el significado de almacenamiento en caché.

riesgo:

El uso de datos inexistentes para llevar a cabo ataques aumentará la presión instantánea sobre la base de datos y eventualmente conducirá a un colapso.

resolver:

El resultado nulo se almacena en caché y se agrega un tiempo de vencimiento breve.

2. Avalancha de caché

Avalancha de caché: Avalancha de caché significa que cuando configuramos el caché, la clave adopta el mismo tiempo de vencimiento, lo que hace que el caché deje de ser válido en un momento determinado, todas las solicitudes se reenvían a la base de datos y la base de datos está bajo presión y avalanchas instantáneas.

Solución: agregue un valor aleatorio al tiempo de vencimiento original, como de 1 a 5 minutos al azar, de modo que la tasa de repetición de cada tiempo de vencimiento almacenado en caché se reduzca, lo que dificulta la causa de eventos de falla colectiva.

3. Desglose del caché

Desglose del caché:

  • Para algunas claves con un tiempo de vencimiento establecido, si se puede acceder a estas claves de manera muy simultánea en ciertos momentos, se trata de datos muy "candentes".
  • Si esta clave de punto de acceso caduca justo antes de que llegue una gran cantidad de solicitudes al mismo tiempo, todas las consultas de datos para esta clave recaerán en la base de datos, lo que se denomina desglose de caché.

resolver:

Cerrar

Una gran cantidad de concurrencia permite que solo uno verifique, y los demás esperan. Después de verificar, se libera el bloqueo y los demás adquieren el bloqueo, se desplazan y verifican el caché primero, y habrá datos sin tener que ir a la base de datos.

2. Resuelve el problema

Resuelva primero la penetración de caché y la avalancha

  1. private static final String CATALOG_JSON="CATALOG_JSON";
  2. @Override
  3. public Map<String, List<Catelog2Vo>> getCatalogJson() {
  4. /**
  5. * 空结果缓存:解决缓存穿透
  6. * 设置过期时间(加随机值) 缓存雪崩
  7. * 加锁 解决缓存击穿
  8. */
  9. Object result = redisTemplate.opsForValue().get(CATALOG_JSON);
  10. if(result!=null){
  11. return (Map<String, List<Catelog2Vo>>) result;
  12. }
  13. Map<String, List<Catelog2Vo>> map = getCatalogJsonFromDB();
  14. if (map==null){
  15. /**
  16. * 解决缓存穿透
  17. */
  18. map=new HashMap<>();
  19. }
  20. redisTemplate.opsForValue().set(CATALOG_JSON,map, Duration.ofDays(1));
  21. return map;
  22. }

Resolver el desglose del caché

1. Utilice el bloqueo local para resolver

El objeto contenedor springboot está en modo singleton de forma predeterminada, por lo que el mismo objeto se puede sincronizar y bloquear. Cuando se usa el modo de detección dual, se puede ejecutar al mismo tiempo.

  1. public synchronized Map<String, List<Catelog2Vo>> getCatalogJsonFromDB() {
  2. Object result = redisTemplate.opsForValue().get(CATALOG_JSON);
  3. if (result != null) {
  4. return (Map<String, List<Catelog2Vo>>) result;
  5. }
  6. //1.查出所有1级分类
  7. List<CategoryEntity> selectList = baseMapper.selectList(null);
  8. /**
  9. * 将数据库的多次查询变成一次
  10. */
  11. //2. 封装数据
  12. List<CategoryEntity> level1Category = selectList.stream().filter(s -> s.getParentCid().equals(0L)).collect(Collectors.toList());
  13. Map<String, List<Catelog2Vo>> map = level1Category.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
  14. //1.每一个的一级分类,查到1级分类的所有二级分类
  15. List<CategoryEntity> categoryEntities = selectList.stream().filter(s -> s.getParentCid().equals(v.getCatId())).collect(Collectors.toList());
  16. List<Catelog2Vo> catelog2VoList = categoryEntities.stream().map(c -> {
  17. Catelog2Vo catelog2Vo = new Catelog2Vo();
  18. catelog2Vo.setId(c.getCatId().toString());
  19. catelog2Vo.setName(c.getName());
  20. catelog2Vo.setCatalog1Id(v.getCatId().toString());
  21. List<CategoryEntity> categoryEntities1 = selectList.stream().filter(s -> s.getParentCid().equals(c.getCatId())).collect(Collectors.toList());
  22. List<Catelog2Vo.Catelog3Vo> collect = categoryEntities1.stream().map(c3 -> {
  23. Catelog2Vo.Catelog3Vo catelog3Vo = new Catelog2Vo.Catelog3Vo();
  24. catelog3Vo.setId(c3.getCatId().toString());
  25. catelog3Vo.setName(c3.getName());
  26. catelog3Vo.setCatalog2Id(c.getCatId().toString());
  27. return catelog3Vo;
  28. }).collect(Collectors.toList());
  29. catelog2Vo.setCatalog3List(collect);
  30. return catelog2Vo;
  31. }).collect(Collectors.toList());
  32. return catelog2VoList;
  33. }));
  34. return map;
  35. }
  1. public Map<String, List<Catelog2Vo>> getCatalogJson() {
  2. /**
  3. * 空结果缓存:解决缓存穿透
  4. * 设置过期时间(加随机值) 缓存雪崩
  5. * 加锁 解决缓存击穿
  6. */
  7. Object result = redisTemplate.opsForValue().get(CATALOG_JSON);
  8. if (result != null) {
  9. return (Map<String, List<Catelog2Vo>>) result;
  10. }
  11. Map<String, List<Catelog2Vo>> map = getCatalogJsonFromDB();
  12. if (map == null) {
  13. /**
  14. * 解决缓存穿透
  15. */
  16. map = new HashMap<>();
  17. }
  18. redisTemplate.opsForValue().set(CATALOG_JSON, map, Duration.ofDays(1));
  19. return map;
  20. }

Todavía habrá problemas con la lógica del código anterior. Cuando se ejecute simultáneamente, el hilo No. 1 terminará de verificar la base de datos y liberará el bloqueo antes de colocarlo en el caché. Como resultado, el hilo No. 2 consultará el caché. si no hay datos y verifique la base de datos nuevamente. No hay garantía de que solo habrá un subproceso para verificar la base de datos.

Enfoque correcto

  1. public synchronized Map<String, List<Catelog2Vo>> getCatalogJsonFromDB() {
  2. Object result = redisTemplate.opsForValue().get(CATALOG_JSON);
  3. if (result != null) {
  4. return (Map<String, List<Catelog2Vo>>) result;
  5. }
  6. //1.查出所有1级分类
  7. List<CategoryEntity> selectList = baseMapper.selectList(null);
  8. /**
  9. * 将数据库的多次查询变成一次
  10. */
  11. //2. 封装数据
  12. List<CategoryEntity> level1Category = selectList.stream().filter(s -> s.getParentCid().equals(0L)).collect(Collectors.toList());
  13. Map<String, List<Catelog2Vo>> map = level1Category.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
  14. //1.每一个的一级分类,查到1级分类的所有二级分类
  15. List<CategoryEntity> categoryEntities = selectList.stream().filter(s -> s.getParentCid().equals(v.getCatId())).collect(Collectors.toList());
  16. List<Catelog2Vo> catelog2VoList = categoryEntities.stream().map(c -> {
  17. Catelog2Vo catelog2Vo = new Catelog2Vo();
  18. catelog2Vo.setId(c.getCatId().toString());
  19. catelog2Vo.setName(c.getName());
  20. catelog2Vo.setCatalog1Id(v.getCatId().toString());
  21. List<CategoryEntity> categoryEntities1 = selectList.stream().filter(s -> s.getParentCid().equals(c.getCatId())).collect(Collectors.toList());
  22. List<Catelog2Vo.Catelog3Vo> collect = categoryEntities1.stream().map(c3 -> {
  23. Catelog2Vo.Catelog3Vo catelog3Vo = new Catelog2Vo.Catelog3Vo();
  24. catelog3Vo.setId(c3.getCatId().toString());
  25. catelog3Vo.setName(c3.getName());
  26. catelog3Vo.setCatalog2Id(c.getCatId().toString());
  27. return catelog3Vo;
  28. }).collect(Collectors.toList());
  29. catelog2Vo.setCatalog3List(collect);
  30. return catelog2Vo;
  31. }).collect(Collectors.toList());
  32. return catelog2VoList;
  33. }));
  34. if (map == null) {
  35. /**
  36. * 解决缓存穿透
  37. */
  38. map = new HashMap<>();
  39. }
  40. redisTemplate.opsForValue().set(CATALOG_JSON, map, Duration.ofDays(1));
  41. return map;
  42. }

Coloque la operación de guardar en el caché en el bloque de código de sincronización

  1. @Override
  2. public Map<String, List<Catelog2Vo>> getCatalogJson() {
  3. /**
  4. * 空结果缓存:解决缓存穿透
  5. * 设置过期时间(加随机值) 缓存雪崩
  6. * 加锁 解决缓存击穿
  7. */
  8. Object result = redisTemplate.opsForValue().get(CATALOG_JSON);
  9. if (result != null) {
  10. return (Map<String, List<Catelog2Vo>>) result;
  11. }
  12. Map<String, List<Catelog2Vo>> map = getCatalogJsonFromDB();
  13. return map;
  14. }

 

 

Los bloqueos locales solo pueden bloquear el proceso actual, por lo que necesitamos bloqueos distribuidos

3. Problemas con bloqueos locales en entornos distribuidos

Es decir, cada bloqueo solo puede bloquear el proceso actual, es decir, cada servicio verificará la base de datos.