经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 数据库/运维 » Redis » 查看文章
Redis从入门到放弃(11):雪崩、击穿、穿透
来源:cnblogs  作者:伊力编程  时间:2023/8/29 8:48:05  对本文有异议

1、前言

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

2、缓存雪崩

2.1、问题描述

  • 在某个时间点,缓存中的大量数据同时过期失效。

  • Redis宕机。

    因以上两点导致大量请求直接打到数据库,从而引发数据库压力激增,甚至崩溃的现象。

2.2、解决方案

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

    1. import redis.clients.jedis.Jedis;
    2. public class RedisExpirationDemo {
    3. public static void main(String[] args) {
    4. Jedis jedis = new Jedis("localhost", 6379);
    5. // Key for caching
    6. String key = "my_data_key";
    7. String value = "cached_value";
    8. int randomExpiration = (int) (Math.random() * 60) + 1; // Random value between 1 and 60 seconds
    9. jedis.setex(key, randomExpiration, value);//设置过期时间
    10. jedis.set(hotKey, hotValue);//永不过期
    11. // Retrieving data
    12. String cachedValue = jedis.get(key);
    13. System.out.println("Cached Value: " + cachedValue);
    14. // Closing the connection
    15. jedis.close();
    16. }
    17. }
  2. 使用 redis 缓存集群,实现主从集群高可用

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

  3. ehcache本地缓存 + redis 缓存

    1. import org.ehcache.Cache;
    2. import org.ehcache.CacheManager;
    3. import org.ehcache.config.builders.CacheConfigurationBuilder;
    4. import org.ehcache.config.builders.CacheManagerBuilder;
    5. import redis.clients.jedis.Jedis;
    6. public class EhcacheRedisDemo {
    7. public static void main(String[] args) {
    8. // Configure ehcache
    9. CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder().build();
    10. cacheManager.init();
    11. Cache<String, String> localCache = cacheManager.createCache("localCache",
    12. CacheConfigurationBuilder.newCacheConfigurationBuilder(String.class, String.class));
    13. // Configure Redis
    14. Jedis jedis = new Jedis("localhost", 6379);
    15. String key = "data_key";
    16. String value = "cached_value";
    17. // Check if data is in local cache
    18. String cachedValue = localCache.get(key);
    19. if (cachedValue != null) {
    20. System.out.println("Value from local cache: " + cachedValue);
    21. } else {
    22. // Retrieve data from Redis and cache it locally
    23. cachedValue = jedis.get(key);
    24. if (cachedValue != null) {
    25. System.out.println("Value from Redis: " + cachedValue);
    26. localCache.put(key, cachedValue);
    27. } else {
    28. System.out.println("Data not found.");
    29. }
    30. }
    31. // Closing connections
    32. jedis.close();
    33. cacheManager.close();
    34. }
    35. }
  4. 限流降级

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

3、缓存穿透

3.1、问题描述

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

3.2、解决方案

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

    1. import com.google.common.hash.BloomFilter;
    2. import com.google.common.hash.Funnels;
    3. import redis.clients.jedis.Jedis;
    4. public class BloomFilterDemo {
    5. public static void main(String[] args) {
    6. Jedis jedis = new Jedis("localhost", 6379);
    7. // Create and populate a Bloom Filter
    8. int expectedInsertions = 1000;
    9. double falsePositiveRate = 0.01;
    10. BloomFilter<String> bloomFilter = BloomFilter.create(Funnels.stringFunnel(), expectedInsertions, falsePositiveRate);
    11. String key1 = "data_key_1";
    12. String key2 = "data_key_2";
    13. String key3 = "data_key_3";
    14. bloomFilter.put(key1);
    15. bloomFilter.put(key2);
    16. // Check if a key exists in the Bloom Filter before querying the database
    17. String queryKey = key3;
    18. if (bloomFilter.mightContain(queryKey)) {
    19. String cachedValue = jedis.get(queryKey);
    20. if (cachedValue != null) {
    21. System.out.println("Cached Value: " + cachedValue);
    22. } else {
    23. System.out.println("Data not found in cache.");
    24. }
    25. } else {
    26. System.out.println("Data not found in Bloom Filter.");
    27. }
    28. // Closing the connection
    29. jedis.close();
    30. }
    31. }
  2. 缓存空值:如果某个查询的结果在数据库中确实不存在,也将这个空结果缓存起来,但设置一个较短的过期时间,防止攻击者频繁请求同一不存在的数据。

    1. import redis.clients.jedis.Jedis;
    2. public class CacheEmptyValueDemo {
    3. public static void main(String[] args) {
    4. Jedis jedis = new Jedis("localhost", 6379);
    5. String emptyKey = "empty_key";
    6. String emptyValue = "EMPTY";
    7. // Cache an empty value with a short expiration time
    8. jedis.setex(emptyKey, 10, emptyValue);
    9. // Check if the key exists in the cache before querying the database
    10. String queryKey = "nonexistent_key";
    11. String cachedValue = jedis.get(queryKey);
    12. if (cachedValue != null) {
    13. if (cachedValue.equals(emptyValue)) {
    14. System.out.println("Data does not exist in the database.");
    15. } else {
    16. System.out.println("Cached Value: " + cachedValue);
    17. }
    18. } else {
    19. System.out.println("Data not found in cache.");
    20. }
    21. // Closing the connection
    22. jedis.close();
    23. }
    24. }
  3. 非法请求限制

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

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

4、缓存击穿

4.1、问题描述

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

4.2、解决方案

  1. 热点数据永不过期:和缓存雪崩类似,将热点数据设置为永不过期,避免核心数据在短时间内失效。
    1. import redis.clients.jedis.Jedis;
    2. public class HotDataNeverExpireDemo {
    3. public static void main(String[] args) {
    4. Jedis jedis = new Jedis("localhost", 6379);
    5. String hotKey = "hot_data_key";
    6. String hotValue = "hot_cached_value";
    7. // Set the hot key with no expiration
    8. jedis.set(hotKey, hotValue);
    9. // Retrieving hot data
    10. String hotCachedValue = jedis.get(hotKey);
    11. System.out.println("Hot Cached Value: " + hotCachedValue);
    12. // Closing the connection
    13. jedis.close();
    14. }
    15. }
  2. 使用互斥锁:在缓存失效时,使用互斥锁来防止多个线程同时请求数据库,只有一个线程可以去数据库查询数据,其他线程等待直至数据重新缓存。
    1. import redis.clients.jedis.Jedis;
    2. public class MutexLockDemo {
    3. public static void main(String[] args) {
    4. Jedis jedis = new Jedis("localhost", 6379);
    5. String mutexKey = "mutex_key";
    6. String mutexValue = "locked";
    7. // Try to acquire the lock
    8. Long lockResult = jedis.setnx(mutexKey, mutexValue);
    9. if (lockResult == 1) {
    10. // Lock acquired, perform data regeneration here
    11. System.out.println("Lock acquired. Generating cache data...");
    12. // Simulating regeneration process
    13. try {
    14. Thread.sleep(5000);
    15. } catch (InterruptedException e) {
    16. e.printStackTrace();
    17. }
    18. // Release the lock
    19. jedis.del(mutexKey);
    20. System.out.println("Lock released.");
    21. } else {
    22. System.out.println("Lock not acquired. Another thread is regenerating cache data.");
    23. }
    24. // Closing the connection
    25. jedis.close();
    26. }
    27. }
  3. 异步更新缓存:在缓存失效之前,先异步更新缓存中的数据,保证数据在过期之前已经得到更新。
    1. import redis.clients.jedis.Jedis;
    2. import java.util.concurrent.CompletableFuture;
    3. public class AsyncCacheUpdateDemo {
    4. public static void main(String[] args) {
    5. Jedis jedis = new Jedis("localhost", 6379);
    6. String key = "data_key";
    7. String value = "cached_value";
    8. // Set initial cache
    9. jedis.setex(key, 60, value);
    10. // Simulate data update
    11. CompletableFuture<Void> updateFuture = CompletableFuture.runAsync(() -> {
    12. try {
    13. Thread.sleep(3000); // Simulate time-consuming update
    14. String updatedValue = "updated_value";
    15. jedis.setex(key, 60, updatedValue);
    16. System.out.println("Cache updated asynchronously.");
    17. } catch (InterruptedException e) {
    18. e.printStackTrace();
    19. }
    20. });
    21. // Do other work while waiting for the update
    22. System.out.println("Performing other work while waiting for cache update...");
    23. // Wait for the update to complete
    24. updateFuture.join();
    25. // Retrieve updated value
    26. String updatedCachedValue = jedis.get(key);
    27. System.out.println("Updated Cached Value: " + updatedCachedValue);
    28. // Closing the connection
    29. jedis.close();
    30. }
    31. }

5、结论

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

原文链接:https://www.cnblogs.com/myshare/p/17662093.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号