Technologieaustausch

Cache – Cache verwendet 2

2024-07-12

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

1. Cache-Zusammenbruch, Eindringen, Lawine

1. Cache-Penetration

Bezieht sich auf das Abfragen von Daten, die nicht vorhanden sein dürfen. Da der Cache nicht vorhanden ist, wird die Datenbank abgefragt, aber die Datenbank verfügt nicht über einen solchen Datensatz. Wir haben den Nullwert dieser Abfrage nicht in den Cache geschrieben Bei jeder Anfrage nach diesen nicht vorhandenen Daten müssen Sie zur Abfrage auf die Speicherebene gehen, wodurch die Bedeutung des Caching verloren geht.

Risiko:

Die Nutzung nicht vorhandener Daten zur Durchführung von Angriffen erhöht den momentanen Druck auf die Datenbank und führt schließlich zum Zusammenbruch.

lösen:

Das Nullergebnis wird zwischengespeichert und mit einer kurzen Ablaufzeit versehen.

2. Cache-Lawine

Cache-Lawine: Cache-Lawine bedeutet, dass der Schlüssel beim Festlegen des Caches dieselbe Ablaufzeit annimmt, wodurch der Cache zu einem bestimmten Zeitpunkt ungültig wird, alle Anforderungen an die Datenbank weitergeleitet werden und die Datenbank sofort unter Druck und Lawinen steht.

Lösung: Fügen Sie der ursprünglichen Ablaufzeit einen zufälligen Wert hinzu, z. B. 1–5 Minuten nach dem Zufallsprinzip, sodass die Wiederholungsrate jeder zwischengespeicherten Ablaufzeit verringert wird und es schwierig wird, kollektive Fehlerereignisse zu verursachen.

3. Cache-Aufschlüsselung

Cache-Aufschlüsselung:

  • Bei einigen Schlüsseln mit festgelegter Ablaufzeit handelt es sich um sehr „heiße“ Daten, wenn zu bestimmten Zeitpunkten sehr gleichzeitig auf diese Schlüssel zugegriffen werden kann.
  • Wenn dieser Hotspot-Schlüssel kurz vor dem gleichzeitigen Eingang einer großen Anzahl von Anfragen abläuft, fallen alle Datenabfragen für diesen Schlüssel an die Datenbank, was als Cache-Aufschlüsselung bezeichnet wird.

lösen:

Sperren

Eine große Menge an Parallelität ermöglicht es nur einem, die Sperre zu überprüfen, und die anderen warten.

2. Lösen Sie das Problem

Lösen Sie zuerst die Cache-Penetration und Lawine

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

Cache-Fehler beheben

1. Verwenden Sie zum Lösen eine lokale Sperre

Das Springboot-Containerobjekt befindet sich standardmäßig im Singleton-Modus, sodass dasselbe Objekt synchronisiert und gesperrt werden kann. Im Dual-Erkennungsmodus kann es gleichzeitig ausgeführt werden.

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

Bei gleichzeitiger Ausführung treten weiterhin Probleme mit der Logik des oben genannten Codes auf. Dies führt dazu, dass der erste Thread die Überprüfung der Datenbank beendet und die Sperre aufhebt, bevor er sie in den Cache legt und es sind keine Daten vorhanden, daher wird die Datenbank erneut überprüft. Es gibt keine Garantie dafür, dass es nur einen Thread gibt, der die Datenbank überprüft

Richtiger Ansatz

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

Platzieren Sie den Vorgang zum Speichern im Cache im Synchronisationscodeblock

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

 

 

Lokale Sperren können nur den aktuellen Prozess sperren, daher benötigen wir verteilte Sperren

3. Probleme mit lokalen Sperren in verteilten Umgebungen

Das heißt, jede Sperre kann nur den aktuellen Prozess sperren, dh jeder Dienst überprüft die Datenbank.