前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Redis从入门到放弃(11):雪崩、击穿、穿透

Redis从入门到放弃(11):雪崩、击穿、穿透

作者头像
夕阳也是醉了
发布2023-10-16 09:28:27
2080
发布2023-10-16 09:28:27
举报
文章被收录于专栏:夕阳醉了夕阳醉了

1、前言

Redis作为一款高性能的缓存数据库,为许多应用提供了快速的数据访问和存储能力。然而,在使用Redis时,我们不可避免地会面对一些常见的问题,如缓存雪崩、缓存穿透和缓存击穿。本文将深入探讨这些问题的本质,以及针对这些问题的解决方案。

2、缓存雪崩

2.1、问题描述

  • 在某个时间点,缓存中的大量数据同时过期失效。
  • Redis宕机。 因以上两点导致大量请求直接打到数据库,从而引发数据库压力激增,甚至崩溃的现象。

2.2、解决方案

将 redis 中的 key 设置为永不过期,或者TTL过期时间间隔开

代码语言:javascript
复制
import redis.clients.jedis.Jedis;

public class RedisExpirationDemo {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("localhost", 6379);

        // Key for caching
        String key = "my_data_key";
        String value = "cached_value";

        int randomExpiration = (int) (Math.random() * 60) + 1; // Random value between 1 and 60 seconds
        jedis.setex(key, randomExpiration, value);//设置过期时间

		jedis.set(hotKey, hotValue);//永不过期

        // Retrieving data
        String cachedValue = jedis.get(key);
        System.out.println("Cached Value: " + cachedValue);

        // Closing the connection
        jedis.close();
    }
}

使用 redis 缓存集群,实现主从集群高可用

《Redis从入门到放弃(9):集群模式》

ehcache本地缓存 + redis 缓存

代码语言:javascript
复制
import org.ehcache.Cache;
import org.ehcache.CacheManager;
import org.ehcache.config.builders.CacheConfigurationBuilder;
import org.ehcache.config.builders.CacheManagerBuilder;
import redis.clients.jedis.Jedis;

public class EhcacheRedisDemo {
    public static void main(String[] args) {
        // Configure ehcache
        CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder().build();
        cacheManager.init();
        Cache<String, String> localCache = cacheManager.createCache("localCache",
                CacheConfigurationBuilder.newCacheConfigurationBuilder(String.class, String.class));

        // Configure Redis
        Jedis jedis = new Jedis("localhost", 6379);

        String key = "data_key";
        String value = "cached_value";

        // Check if data is in local cache
        String cachedValue = localCache.get(key);
        if (cachedValue != null) {
            System.out.println("Value from local cache: " + cachedValue);
        } else {
            // Retrieve data from Redis and cache it locally
            cachedValue = jedis.get(key);
            if (cachedValue != null) {
                System.out.println("Value from Redis: " + cachedValue);
                localCache.put(key, cachedValue);
            } else {
                System.out.println("Data not found.");
            }
        }

        // Closing connections
        jedis.close();
        cacheManager.close();
    }
}

限流降级

限流降级需要结合其他工具和框架来实现,比如 Sentinel、Hystrix 等。

3、缓存穿透

3.1、问题描述

缓存穿透指的是恶意或者非法的请求,其请求的数据在缓存和数据库中均不存在,由于大量的请求导致直接打到数据库,造成数据库负载过大。

3.2、解决方案

使用布隆过滤器:布隆过滤器是一种数据结构,用于快速判断一个元素是否存在于集合中。部署在Redis的前面,去拦截数据,减少对Redis的冲击,将所有可能的查询值都加入布隆过滤器,当一个查询请求到来时,先经过布隆过滤器判断是否存在于缓存中,避免不必要的数据库查询。

代码语言:javascript
复制
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import redis.clients.jedis.Jedis;

public class BloomFilterDemo {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("localhost", 6379);

        // Create and populate a Bloom Filter
        int expectedInsertions = 1000;
        double falsePositiveRate = 0.01;
        BloomFilter<String> bloomFilter = BloomFilter.create(Funnels.stringFunnel(), expectedInsertions, falsePositiveRate);

        String key1 = "data_key_1";
        String key2 = "data_key_2";
        String key3 = "data_key_3";

        bloomFilter.put(key1);
        bloomFilter.put(key2);

        // Check if a key exists in the Bloom Filter before querying the database
        String queryKey = key3;
        if (bloomFilter.mightContain(queryKey)) {
            String cachedValue = jedis.get(queryKey);
            if (cachedValue != null) {
                System.out.println("Cached Value: " + cachedValue);
            } else {
                System.out.println("Data not found in cache.");
            }
        } else {
            System.out.println("Data not found in Bloom Filter.");
        }

        // Closing the connection
        jedis.close();
    }
}

缓存空值:如果某个查询的结果在数据库中确实不存在,也将这个空结果缓存起来,但设置一个较短的过期时间,防止攻击者频繁请求同一不存在的数据。

代码语言:javascript
复制
import redis.clients.jedis.Jedis;

public class CacheEmptyValueDemo {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("localhost", 6379);

        String emptyKey = "empty_key";
        String emptyValue = "EMPTY";

        // Cache an empty value with a short expiration time
        jedis.setex(emptyKey, 10, emptyValue);

        // Check if the key exists in the cache before querying the database
        String queryKey = "nonexistent_key";
        String cachedValue = jedis.get(queryKey);
        if (cachedValue != null) {
            if (cachedValue.equals(emptyValue)) {
                System.out.println("Data does not exist in the database.");
            } else {
                System.out.println("Cached Value: " + cachedValue);
            }
        } else {
            System.out.println("Data not found in cache.");
        }

        // Closing the connection
        jedis.close();
    }
}

非法请求限制

对非法的IP或账号进行请求限制。

异常参数校验,如id=-1、参数空值。

4、缓存击穿

4.1、问题描述

缓存击穿指的是一个查询请求针对一个在数据库中存在的数据,但由于该数据在某一时刻过期失效,导致请求直接打到数据库,引发数据库负载激增。

4.2、解决方案

热点数据永不过期:和缓存雪崩类似,将热点数据设置为永不过期,避免核心数据在短时间内失效。

代码语言:javascript
复制
import redis.clients.jedis.Jedis;

public class HotDataNeverExpireDemo {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("localhost", 6379);

        String hotKey = "hot_data_key";
        String hotValue = "hot_cached_value";

        // Set the hot key with no expiration
        jedis.set(hotKey, hotValue);

        // Retrieving hot data
        String hotCachedValue = jedis.get(hotKey);
        System.out.println("Hot Cached Value: " + hotCachedValue);

        // Closing the connection
        jedis.close();
    }
}

使用互斥锁:在缓存失效时,使用互斥锁来防止多个线程同时请求数据库,只有一个线程可以去数据库查询数据,其他线程等待直至数据重新缓存。

代码语言:javascript
复制
import redis.clients.jedis.Jedis;

public class MutexLockDemo {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("localhost", 6379);

        String mutexKey = "mutex_key";
        String mutexValue = "locked";

        // Try to acquire the lock
        Long lockResult = jedis.setnx(mutexKey, mutexValue);
        if (lockResult == 1) {
            // Lock acquired, perform data regeneration here
            System.out.println("Lock acquired. Generating cache data...");

            // Simulating regeneration process
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            // Release the lock
            jedis.del(mutexKey);
            System.out.println("Lock released.");
        } else {
            System.out.println("Lock not acquired. Another thread is regenerating cache data.");
        }

        // Closing the connection
        jedis.close();
    }
}

异步更新缓存:在缓存失效之前,先异步更新缓存中的数据,保证数据在过期之前已经得到更新。

代码语言:javascript
复制
import redis.clients.jedis.Jedis;

import java.util.concurrent.CompletableFuture;

public class AsyncCacheUpdateDemo {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("localhost", 6379);

        String key = "data_key";
        String value = "cached_value";

        // Set initial cache
        jedis.setex(key, 60, value);

        // Simulate data update
        CompletableFuture<Void> updateFuture = CompletableFuture.runAsync(() -> {
            try {
                Thread.sleep(3000); // Simulate time-consuming update
                String updatedValue = "updated_value";
                jedis.setex(key, 60, updatedValue);
                System.out.println("Cache updated asynchronously.");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        // Do other work while waiting for the update
        System.out.println("Performing other work while waiting for cache update...");

        // Wait for the update to complete
        updateFuture.join();

        // Retrieve updated value
        String updatedCachedValue = jedis.get(key);
        System.out.println("Updated Cached Value: " + updatedCachedValue);

        // Closing the connection
        jedis.close();
    }
}

5、结论

在使用Redis时,缓存雪崩、缓存穿透和缓存击穿是常见的问题,但通过合理的设置缓存策略、使用数据结构和锁机制,以及采用异步更新等方法,可以有效地减少甚至避免这些问题的发生。因此,在入门Redis后,不应因为这些问题而轻易放弃,而是应当深入了解并采取相应的解决方案,以充分发挥Redis在提升应用性能方面的优势。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-08-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1、前言
  • 2、缓存雪崩
    • 2.1、问题描述
      • 2.2、解决方案
      • 3、缓存穿透
        • 3.1、问题描述
          • 3.2、解决方案
          • 4、缓存击穿
            • 4.1、问题描述
              • 4.2、解决方案
              • 5、结论
              相关产品与服务
              云数据库 Redis
              腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档