моя контактная информация
Почтамезофия@protonmail.com
2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
Относится к запросу данных, которые не должны существовать. Поскольку кеш не затронут, будет выполнен запрос к базе данных, но в базе данных нет такой записи. Мы не записали нулевое значение этого запроса в кеш, что приведет к результату. при каждом запросе этих несуществующих данных приходится обращаться к уровню хранения для запроса, что теряет смысл кеширования.
риск:
Использование несуществующих данных для проведения атак увеличит мгновенную нагрузку на базу данных и в конечном итоге приведет к ее краху.
решать:
Нулевой результат кэшируется и добавляется короткий срок действия.
Лавина кэша: Лавина кэша означает, что когда мы устанавливаем кеш, ключ принимает одно и то же время истечения срока действия, в результате чего кеш становится недействительным в определенный момент, все запросы перенаправляются в БД, а БД находится под мгновенным давлением и лавинами.
Решение. Добавьте случайное значение к исходному сроку действия, например 1–5 минут, в случайном порядке, чтобы уменьшить частоту повторения каждого срока действия кэша, что затруднит возникновение событий коллективного сбоя.
Разбивка кэша:
решать:
Замок
Большой объем параллелизма позволяет проверять только одному, а остальные ждут. После проверки блокировка снимается, а остальные приобретают блокировку, наводят курсор, сначала проверяют кэш, и будут данные, не обращаясь к бд.
Сначала решите проблему проникновения в тайник и лавины
- private static final String CATALOG_JSON="CATALOG_JSON";
- @Override
- public Map<String, List<Catelog2Vo>> getCatalogJson() {
-
- /**
- * 空结果缓存:解决缓存穿透
- * 设置过期时间(加随机值) 缓存雪崩
- * 加锁 解决缓存击穿
- */
- Object result = redisTemplate.opsForValue().get(CATALOG_JSON);
- if(result!=null){
- return (Map<String, List<Catelog2Vo>>) result;
- }
-
- Map<String, List<Catelog2Vo>> map = getCatalogJsonFromDB();
- if (map==null){
- /**
- * 解决缓存穿透
- */
- map=new HashMap<>();
- }
- redisTemplate.opsForValue().set(CATALOG_JSON,map, Duration.ofDays(1));
- return map;
- }
Объект-контейнер Springboot по умолчанию находится в одноэлементном режиме, поэтому один и тот же объект можно синхронизировать и заблокировать. При использовании режима двойного обнаружения он может выполняться одновременно.
- public synchronized Map<String, List<Catelog2Vo>> getCatalogJsonFromDB() {
-
- Object result = redisTemplate.opsForValue().get(CATALOG_JSON);
- if (result != null) {
- return (Map<String, List<Catelog2Vo>>) result;
- }
-
-
- //1.查出所有1级分类
- List<CategoryEntity> selectList = baseMapper.selectList(null);
- /**
- * 将数据库的多次查询变成一次
- */
-
- //2. 封装数据
- List<CategoryEntity> level1Category = selectList.stream().filter(s -> s.getParentCid().equals(0L)).collect(Collectors.toList());
- Map<String, List<Catelog2Vo>> map = level1Category.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
- //1.每一个的一级分类,查到1级分类的所有二级分类
- List<CategoryEntity> categoryEntities = selectList.stream().filter(s -> s.getParentCid().equals(v.getCatId())).collect(Collectors.toList());
-
- List<Catelog2Vo> catelog2VoList = categoryEntities.stream().map(c -> {
- Catelog2Vo catelog2Vo = new Catelog2Vo();
- catelog2Vo.setId(c.getCatId().toString());
- catelog2Vo.setName(c.getName());
- catelog2Vo.setCatalog1Id(v.getCatId().toString());
-
- List<CategoryEntity> categoryEntities1 = selectList.stream().filter(s -> s.getParentCid().equals(c.getCatId())).collect(Collectors.toList());
- List<Catelog2Vo.Catelog3Vo> collect = categoryEntities1.stream().map(c3 -> {
- Catelog2Vo.Catelog3Vo catelog3Vo = new Catelog2Vo.Catelog3Vo();
- catelog3Vo.setId(c3.getCatId().toString());
- catelog3Vo.setName(c3.getName());
- catelog3Vo.setCatalog2Id(c.getCatId().toString());
- return catelog3Vo;
- }).collect(Collectors.toList());
-
- catelog2Vo.setCatalog3List(collect);
-
- return catelog2Vo;
- }).collect(Collectors.toList());
-
-
- return catelog2VoList;
- }));
- return map;
- }
- public Map<String, List<Catelog2Vo>> getCatalogJson() {
-
- /**
- * 空结果缓存:解决缓存穿透
- * 设置过期时间(加随机值) 缓存雪崩
- * 加锁 解决缓存击穿
- */
- Object result = redisTemplate.opsForValue().get(CATALOG_JSON);
- if (result != null) {
- return (Map<String, List<Catelog2Vo>>) result;
- }
-
- Map<String, List<Catelog2Vo>> map = getCatalogJsonFromDB();
- if (map == null) {
- /**
- * 解决缓存穿透
- */
- map = new HashMap<>();
- }
- redisTemplate.opsForValue().set(CATALOG_JSON, map, Duration.ofDays(1));
- return map;
- }
Проблемы с логикой приведенного выше кода по-прежнему будут.При одновременном запуске первый поток завершит проверку базы данных и снимет блокировку перед помещением ее в кеш. В результате второй поток будет проверять кеш. и данных нет, поэтому он снова проверит базу данных. Нет никакой гарантии, что для проверки базы данных будет только один поток.
Правильный подход
- public synchronized Map<String, List<Catelog2Vo>> getCatalogJsonFromDB() {
-
- Object result = redisTemplate.opsForValue().get(CATALOG_JSON);
- if (result != null) {
- return (Map<String, List<Catelog2Vo>>) result;
- }
-
-
- //1.查出所有1级分类
- List<CategoryEntity> selectList = baseMapper.selectList(null);
- /**
- * 将数据库的多次查询变成一次
- */
-
- //2. 封装数据
- List<CategoryEntity> level1Category = selectList.stream().filter(s -> s.getParentCid().equals(0L)).collect(Collectors.toList());
- Map<String, List<Catelog2Vo>> map = level1Category.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
- //1.每一个的一级分类,查到1级分类的所有二级分类
- List<CategoryEntity> categoryEntities = selectList.stream().filter(s -> s.getParentCid().equals(v.getCatId())).collect(Collectors.toList());
-
- List<Catelog2Vo> catelog2VoList = categoryEntities.stream().map(c -> {
- Catelog2Vo catelog2Vo = new Catelog2Vo();
- catelog2Vo.setId(c.getCatId().toString());
- catelog2Vo.setName(c.getName());
- catelog2Vo.setCatalog1Id(v.getCatId().toString());
-
- List<CategoryEntity> categoryEntities1 = selectList.stream().filter(s -> s.getParentCid().equals(c.getCatId())).collect(Collectors.toList());
- List<Catelog2Vo.Catelog3Vo> collect = categoryEntities1.stream().map(c3 -> {
- Catelog2Vo.Catelog3Vo catelog3Vo = new Catelog2Vo.Catelog3Vo();
- catelog3Vo.setId(c3.getCatId().toString());
- catelog3Vo.setName(c3.getName());
- catelog3Vo.setCatalog2Id(c.getCatId().toString());
- return catelog3Vo;
- }).collect(Collectors.toList());
-
- catelog2Vo.setCatalog3List(collect);
-
- return catelog2Vo;
- }).collect(Collectors.toList());
-
-
- return catelog2VoList;
- }));
- if (map == null) {
- /**
- * 解决缓存穿透
- */
- map = new HashMap<>();
- }
- redisTemplate.opsForValue().set(CATALOG_JSON, map, Duration.ofDays(1));
- return map;
- }
Поместите операцию сохранения в кэш в блок кода синхронизации
- @Override
- public Map<String, List<Catelog2Vo>> getCatalogJson() {
-
- /**
- * 空结果缓存:解决缓存穿透
- * 设置过期时间(加随机值) 缓存雪崩
- * 加锁 解决缓存击穿
- */
- Object result = redisTemplate.opsForValue().get(CATALOG_JSON);
- if (result != null) {
- return (Map<String, List<Catelog2Vo>>) result;
- }
-
- Map<String, List<Catelog2Vo>> map = getCatalogJsonFromDB();
-
- return map;
- }
Локальные блокировки могут блокировать только текущий процесс, поэтому нам нужны распределенные блокировки.
То есть каждая блокировка может заблокировать только текущий процесс, то есть каждый сервис будет проверять базу данных.