首页 > 数据库 >Redis从入门到放弃(11):雪崩、击穿、穿透

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

时间:2023-08-28 13:55:57浏览次数:40  
标签:11 缓存 String Redis jedis 雪崩 key Jedis out

1、前言

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

2、缓存雪崩

2.1、问题描述

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

  • Redis宕机。

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

2.2、解决方案

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

    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();
        }
    }
    
  2. 使用 redis 缓存集群,实现主从集群高可用

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

  3. ehcache本地缓存 + redis 缓存

    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();
        }
    }
    
  4. 限流降级

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

3、缓存穿透

3.1、问题描述

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

3.2、解决方案

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

    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();
        }
    }
    
  2. 缓存空值:如果某个查询的结果在数据库中确实不存在,也将这个空结果缓存起来,但设置一个较短的过期时间,防止攻击者频繁请求同一不存在的数据。

    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();
        }
    }
    
  3. 非法请求限制

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

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

4、缓存击穿

4.1、问题描述

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

4.2、解决方案

  1. 热点数据永不过期:和缓存雪崩类似,将热点数据设置为永不过期,避免核心数据在短时间内失效。
    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();
        }
    }
    
  2. 使用互斥锁:在缓存失效时,使用互斥锁来防止多个线程同时请求数据库,只有一个线程可以去数据库查询数据,其他线程等待直至数据重新缓存。
    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();
        }
    }
    
  3. 异步更新缓存:在缓存失效之前,先异步更新缓存中的数据,保证数据在过期之前已经得到更新。
    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在提升应用性能方面的优势。

标签:11,缓存,String,Redis,jedis,雪崩,key,Jedis,out
From: https://www.cnblogs.com/myshare/p/17662093.html

相关文章

  • docker 启动redis 启动配置文件可以去网上查找一篇
    dockerrun--privileged=true-d--nameredis-p6379:6379-v/Users/zs/docker/redis/conf/redis.conf:/etc/redis/redis.conf-v/Users/zs/docker/redis/data:/dataredisredis-server/etc/redis/redis.conf   configurl参考如下 https://blog.csdn.net/xuyan......
  • Redisson幂等校验例子
    在添加接口增加幂等校验,防止用户在短时间内重复调用添加接口importorg.apache.commons.lang3.ArrayUtils;importorg.aspectj.lang.JoinPoint;importorg.aspectj.lang.annotation.Aspect;importorg.aspectj.lang.annotation.Before;importorg.aspectj.lang.annotation.Point......
  • 面试官:如何遍历 Redis 中的海量数据?
    来源:https://www.toutiao.com/article/6697540366528152077/前言有时候我们需要知道线上的redis的使用情况,尤其需要知道一些前缀的key值,让我们怎么去查看呢?今天给大家分享一个小知识点!事故产生因为我们的用户token缓存是采用了【user_token:userid】格式的key,保存用户的token......
  • 服务启动连接redis报错问题
    报错截图如下1,该报错为redis认证问题,也就是需要redis登录密码,需要在配置文件redis配置中,需填写密码2,对于redis的修改有一下操作 2.1,关闭redis的保护模式:打开redis的配置文件,redis.conf文件,找到protected-mode,改成no, 2.2,设置redis密码,requirepass***  到redis的......
  • 优雅的对旋转编码器消抖(EC11,正交)
    环境:STM32SDK:Arduino(烧录了Arduino的bootloader)旋转编码器:EC11此处只处理正交编码器的A,B。在网上随意找的一个截图事宜,观看此图后,默认各位了解EC11的工作逻辑。  消抖的核心思路:A脚设置为上升下降沿均会进中断,下降上升一个变换周期,判断这个周期的A脚,B脚的始末状......
  • Win11开发嵌入式Linux与交叉编译的一些轮子
    由于我不愿意直接使用ubuntu环境来开发Linux,所以在实践中我摸索出一套能够在最新的win11下调试Linux开发板的方法。wsl2准备首先我们需要安装wsl2。安装教程使用USBIP读写SD卡我们需要在linux环境下对开发板使用的TF卡进行读写。由于wsl2不支持直接挂载宿主机的usb设备,并且其......
  • 20230711 java.lang.ClassLoader
    介绍java.lang.ClassLoaderpublicabstractclassClassLoader类加载器APIstaticClassLoadergetPlatformClassLoadergetSystemClassLoader获取系统类加载器,即用于加载第一个应用类的类加载器SystemResourcegetSystemResourcegetSystemResourceAsStreamgetSys......
  • Redis中文乱码解决方案
    问题描述刚开始学Redis,当我存入中文并想要读取时发现控制台上显示的是乱码......
  • Redis集群
     Redis集群一、Redis集群Redis集群是一种使用分布式技术将数据分散存储在多个节点上的解决方案。它可以提供高可用性、扩展性和性能的优势。Redis集群通过分片(Sharding)来存储数据。数据被平均分配到多个节点上,每个节点负责存储一部分数据。这样可以将负载分散到多个节点上,......
  • Docker安装redis、redis设置密码、挂载配置文件并以配置文件方式启动(避坑)
    1.拉取redis镜像:拉取最新版:dockerpullredis拉取指定版本,以6.2.7为例:dockerpullredis:6.2.72.在Linux环境中创建需要挂载的目录mkdir/usr/local/software/redis/6379/confmkdir/usr/local/software/redis/6379/datamkdir/usr/local/software/redis/6379/log3......