目录
redis缓存预热
Redis 缓存预热是指在系统启动或者缓存失效后,提前将部分数据加载到 Redis 缓存中,以便用户在访问时能够快速获取数据,提高系统性能和响应速度。
-  
全量加载: 在系统启动时,将所有需要缓存的数据从数据库或其他数据源加载到 Redis 中,确保缓存中包含了所有可能被访问的数据。这种方式适用于数据量较小、变动频率低的场景。
 -  
按需加载: 根据访问模式和数据访问频率,预先加载部分热门数据到 Redis 缓存中,以及时满足用户请求。这种方式适用于数据量较大、热点数据集中的场景。
 -  
定时加载: 设置定时任务,在系统空闲时段或低峰期,定期从数据库加载数据到 Redis 缓存中,以确保缓存数据的及时性和完整性。
 -  
异步加载: 在系统启动后,通过异步任务的方式逐步将数据加载到 Redis 缓存中,避免系统启动时的压力过大。
 
redis缓存雪崩
缓存雪崩是指在某个时间段,缓存中大量的数据同时失效或者因为某种原因都不可用,导致大量请求直接落到了后端数据库上,压力过大而导致数据库或其他服务崩溃,最终影响整个系统的稳定性和可用性。
缓存雪崩一般发生在以下情况:
- 缓存服务器宕机或故障
 - 所有缓存数据在同一时间失效
 - 缓存数据没有设置过期时间或者过期时间过短
 - 大量请求访问热门数据,导致缓存击穿,无法承受巨大的并发请求
 
避免缓存雪崩,可以采取以下措施:
-  
分布式缓存: 将缓存分散到多个节点上,避免所有节点同时失效导致缓存雪崩。比如使用 Redis Cluster、Memcached Cluster 等分布式缓存方案。
 -  
缓存预热: 在系统启动时,预先将热门数据加载到缓存中,避免缓存全部失效时,大量请求落到数据库上。
 -  
设置过期时间: 设置合理的缓存过期时间,避免所有缓存同时失效。
 -  
限流降级: 当请求量超过系统承受范围时,通过限流或者降级等措施,分流请求,保证系统的稳定性和可用性。
 -  
多级缓存: 使用多级缓存架构,将热门数据放到内存中的缓存,避免频繁读取数据库和外部接口。
 
redis缓存击穿
缓存击穿是指当某个热点数据的缓存过期或不存在时,大量请求同时访问该数据,导致请求直接落到了后端数据库上,造成数据库压力过大,影响系统性能和可用性。
缓存击穿一般发生在以下情况:
- 热点数据缓存失效:由于缓存的过期时间到达或被手动删除等原因,导致热点数据不再存在于缓存中。
 - 并发请求访问热点数据:大量并发请求同时访问热点数据,此时缓存未命中,导致请求直接访问数据库。
 
为了避免缓存击穿,可以采取以下措施:
-  
加锁机制: 当检测到缓存失效时,先进行加锁操作,只允许一个请求去查询数据库,其他请求等待结果。查询完毕后,更新缓存并释放锁,其他请求再从缓存获取数据。
 -  
设置短暂的空值缓存: 在缓存失效时,先将空值(null)或者占位数据设置到缓存中,并给予较短的过期时间,避免大量请求同时访问数据库。同时,在缓存中设置一个较长的过期时间,以防止后续请求再次击穿。
 -  
异步更新缓存: 当检测到缓存失效后,异步更新缓存,而不是在请求时即时更新缓存。这样可以避免大量请求同时访问数据库,在缓存更新完成后,后续请求再从缓存获取数据。
 -  
使用互斥锁或分布式锁: 在多服务器环境下,使用互斥锁或者分布式锁来保证只有一个请求去查询数据库,其他请求等待结果,避免大量请求同时访问数据库。
 
redis缓存穿透
缓存穿透是指恶意请求或者不存在的数据被请求,导致缓存无法命中,每次请求都直接访问数据库或其他数据源,造成数据库压力过大。
缓存穿透一般发生在以下情况:
- 恶意请求:恶意用户发送的请求,例如使用遍历查询参数、非法字符等,导致缓存无法命中,直接访问数据库。
 - 数据不存在:请求查询的数据本身就不存在于数据库或其他数据源中,无论怎样的请求都无法在缓存中命中。
 
为了避免缓存穿透,可以采取以下措施:
-  
布隆过滤器(Bloom Filter): 使用布隆过滤器来预先过滤掉不存在的数据,当请求到来时,首先经过布隆过滤器的检查,如果不在布隆过滤器中,则直接拒绝请求,避免直接访问数据库。
 -  
空对象缓存: 当查询数据库为空时,在缓存中设置一个空对象(如null),并设置较短的过期时间,避免下次请求再次穿透到数据库。
 -  
合法性检查: 对于用户发送的请求参数进行合法性检查,避免恶意请求导致缓存穿透,可以使用验证 token、接口鉴权等方式进行有效性验证。
 -  
限流和监控: 对请求进行限流,避免大量的恶意请求进入系统,同时建立监控系统,及时发现异常请求并进行处理。
 
 
空对象缓存解决缓存穿透
    public Customer findCustomerById(Integer customerId) {
        Customer customer = null;
        // 缓存redis的key名称
        String key = CACHE_KEY_CUSTOMER + customerId;
        // 1.去redis上查询
        customer = (Customer) redisTemplate.opsForValue().get(key);
        // 2. 如果redis有,直接返回  如果redis没有,在mysql上查询
        if (customer == null) {
            // 3.对于高QPS的优化,进来就先加锁,保证一个请求操作,让外面的redis等待一下,避免击穿mysql(大公司的操作 )
            synchronized (CustomerService.class) {
                // 3.1 第二次查询redis,加锁后
                customer = (Customer) redisTemplate.opsForValue().get(key);
                // 4.再去查询我们的mysql
                customer = customerMapper.selectByPrimaryKey(customerId);
                // 5.mysql有,redis无
                if (customer != null) {
                    // 6.把mysql查询到的数据会写到到redis, 保持双写一致性  7天过期
                    redisTemplate.opsForValue().set(key, customer, 7L, TimeUnit.DAYS);
                }else {
                    // defaultNull 规定为redis查询为空、MySQL查询也没有,缓存一个defaultNull标识为空,以防缓存穿透
                    redisTemplate.opsForValue().set(key, "defaultNull", 7L, TimeUnit.DAYS);
                }
            }
        }
        return customer;
    }
 
Google布隆过滤器Guava解决缓存穿透
添加pom文件
        <!--guava Google 开源的 Guava 中自带的布隆过滤器-->
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>23.0</version>
        </dependency>
 
业务类
- GUavaBloomFilterController
 
import com.xfcy.service.GuavaBloomFilterService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@Api(tags = "gogle工具Guava处理布隆过滤器")
@RestController
@Slf4j
public class GuavaBloomFilterController {
    @Resource
    private GuavaBloomFilterService guavaBloomFilterService;
    @ApiOperation("guava布隆过滤器插入100万样本数据,额外10w(110w)测试是否存在")
    @RequestMapping(value = "/guavafilter", method = RequestMethod.GET)
    public void guavaBloomFilter() {
        guavaBloomFilterService.guavaBloomFilter();
    }
}
 
- GUavaBloomFilterService
 
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
@Slf4j
@Service
public class GuavaBloomFilterService {
    // 1.定义一个常量
    public static final int _1W = 10000;
    // 2.定义我们guava布隆过滤器,初始容量
    public static final int SIZE = 100 * _1W;
    // 3.误判率,它越小误判的个数也越少(思考:是否可以无限小? 没有误判岂不是更好)
    public static double fpp = 0.01;  // 这个数越小所用的hash函数越多,bitmap占用的位越多  默认的就是0.03,5个hash函数   0.01,7个函数
    // 4.创建guava布隆过滤器
    private static BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), SIZE, fpp);
    public void guavaBloomFilter() {
        // 1.先让bloomFilter加入100w白名单数据
        for (int i = 0; i < SIZE; i++) {
            bloomFilter.put(i);
        }
        // 2.故意取10w个不在合法范围内的数据,来进行误判率的演示
        ArrayList<Integer> list = new ArrayList<>(10 * _1W);
        // 3.验证
        for (int i = SIZE + 1; i < SIZE + (10 * _1W); i++){
            if (bloomFilter.mightContain(i)){
                log.info("被误判了:{}", i);
                list.add(i);
            }
        }
        log.info("误判总数量:{}", list.size());
    }
}
 
 
缓存问题总结
| 缓存问题 | 产生原因 | 解决方案 | 
| 缓存更新不一致 | 数据变更、缓存时效性 | 同步更新、失效更新、异步更新、定时更新 | 
| 缓存不一致 | 同步更新失败、异步更新 | 增加重试、补偿任务、最终一致 | 
| 缓存穿透 | 恶意攻击 | 空对象缓存、bloomFilter 过滤器 | 
| 缓存击穿 | 热点key失效 | 互斥更新、随即退避、差异失效时间 | 
| 缓存雪崩 | 缓存挂掉 | 快速失败熔断、主从模式、集群模式 | 










