Condivisione della tecnologia

cache: la cache utilizza 2

2024-07-12

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

1. Rottura della cache, penetrazione, valanga

1. Penetrazione della cache

Si riferisce all'interrogazione di dati che non devono esistere Poiché la cache non viene colpita, il database verrà interrogato, ma il database non dispone di tale record. Non abbiamo scritto il valore null di questa query nella cache, che risulterà in ogni richiesta di questi dati inesistenti devi andare al livello di archiviazione per interrogare, il che perde il significato della memorizzazione nella cache.

rischio:

L'utilizzo di dati inesistenti per effettuare attacchi aumenterà la pressione istantanea sul database e alla fine porterà al collasso.

risolvere:

Il risultato nullo viene memorizzato nella cache e viene aggiunta una breve scadenza.

2. Valanga di cache

Cache avalanche: Cache avalanche significa che quando impostiamo la cache, la chiave adotta lo stesso tempo di scadenza, facendo sì che la cache diventi non valida in un determinato momento, tutte le richieste vengono inoltrate al DB e il DB è sotto pressione istantanea e valanghe.

Soluzione: aggiungere un valore casuale alla scadenza originale, ad esempio 1-5 minuti in modo casuale, in modo che la frequenza di ripetizione di ciascuna scadenza memorizzata nella cache venga ridotta, rendendo difficile causare eventi di errore collettivi.

3. Suddivisione della cache

Suddivisione della cache:

  • Per alcune chiavi con una data di scadenza impostata, se è possibile accedere a queste chiavi contemporaneamente in determinati momenti, si tratta di dati molto "caldi".
  • Se questa chiave hotspot scade appena prima che arrivino un numero elevato di richieste contemporaneamente, tutte le query di dati per questa chiave ricadranno nel db, operazione chiamata suddivisione della cache.

risolvere:

Serratura

Una grande quantità di concorrenza consente solo a uno di controllare e gli altri aspettano. Dopo il controllo, il blocco viene rilasciato e gli altri acquisiscono il blocco, passano il mouse, controllano prima la cache e ci saranno dati, senza andare al db.

2. Risolvi il problema

Risolvi prima la penetrazione della cache e la valanga

  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. }

Risolvi la ripartizione della cache

1. Utilizzare il blocco locale per risolvere

L'oggetto contenitore springboot è in modalità singleton per impostazione predefinita, quindi lo stesso oggetto può essere sincronizzato e bloccato quando si utilizza la modalità di rilevamento doppio, può essere eseguito contemporaneamente.

  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. }

Ci saranno ancora problemi con la logica del codice precedente. Quando viene eseguito contemporaneamente, il primo thread finirà di controllare il database e rilascerà il blocco prima di inserirlo nella cache. Di conseguenza, il secondo thread controllerà la cache e non ci sono dati, quindi controllerà nuovamente il database. Non vi è alcuna garanzia che ci sarà un solo thread per controllare il database

Approccio corretto

  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. }

Inserisci l'operazione di salvataggio nella cache nel blocco del codice di sincronizzazione

  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. }

 

 

I lock locali possono bloccare solo il processo corrente, quindi abbiamo bisogno di lock distribuiti

3. Problemi con serrature locali in ambienti distribuiti

Cioè, ogni blocco può bloccare solo il processo corrente, ovvero ogni servizio controllerà il database.