全站最帅😎
发布于 2022-07-05 / 883 阅读
0
0

Redis 开发注意事项

1. 缓存穿透

查询一个根本不存在的数据,缓存层查询为空,则查询存储层。如果存储层的查询结果也为空,则不会写入数据到存储层。

缓存穿透将导致不存在的数据每次请求都要到存储层去查询,失去了缓存保护后端存储的意义
  • 缓存穿透的基本原因有两点:
  1. 不严格的参数校验:
    我们一般设计表 id 字段基本 > 0,但是一直用一个 < 0 的参数去请求,而我们又没有做严格的参数校验,所以请求不会被拦截,且每次都能绕开 Redis 直接打到数据库。
  2. 一些恶意攻击、爬虫等造成大量的空命中。
  • 解决缓存穿透
  1. 缓存空对象
    如果存储层仍然没有查询结果,则将空对象保留到缓存中,后续请求再次访问则直接返回缓存中的空对象。
    但是缓存空对象会引发两个问题:
    第一,对空值做缓存,意味着缓存层中存了更多的键,需要更多的内存空间(如果是大量的攻击请求,问题会更严重),一个比较有效的方法是针对这类空数据设置一个较短的过期时间,让其自动剔除。
    第二,如果存储层进行添加数据操作,那么应该和更新操作一样进行缓存的删除,不然那此段时间就会出现缓存层和存储层数据的不一致。
  2. 布隆过滤器拦截
    这种方法适用于数据命中不高、数据相对固定、实时性低(通常是数据集较大)的应用场景,代码维护较为复杂,但是缓存空间占用少,存在一定的误判率。

2. 缓存击穿

缓存击穿是指一个热点 Key,大量请求集中对这个 key 进行访问,当这个 Key 在失效的瞬间,大量的请求就会穿破缓存,直接请求数据库,可能将数据库打死。
解决缓存击穿:

  1. 使用分布式互斥锁
    查询缓存结果为空时,不是立刻去查询 DB,而是先针对这个 key 上分布式锁,上锁成功后,才能查询 DB 并设置缓存。伪代码如下
public String get(key) { 
    String value = redis.get(key); 
    // 代表缓存值过期 
    if (value == null) { 
        // 设置 1min 的超时
        boolean res = lock.tryLock(60)
        // 代表设置成功 
        if (res) {
	        // 这个时候其他线程可能已经成功设置缓存了
	        String value = redis.get(key);
            value = db.get(key); 
            redis.set(key, value, expire_secs); 
            redis.del(key_mutex); 
        } else { 
    	    // 重试
            sleep(50); 
            get(key); 
        } 
    } else {
      return value; 
    } 
}
  1. 使热点 key “永不过期”
    这里的 “永不过期” 包含两层意思:
    (1) 从 redis 上看,确实没有设置过期时间,这就保证了不会出现热点 key 过期问题,也就是 “物理” 不过期。
    (2) 从功能上看,我们把过期时间存在 key 对应的 value 里,如果发现要过期了,通过一个后台的异步线程再进行缓存的构建,也就是 “逻辑” 过期。从项目反馈上来看,这种方法对于性能非常友好,唯一不足的就是构建缓存时候,其余线程(非构建缓存的线程)可能访问的是旧数据,但是对于一般的互联网功能来说这个是可以忍受。

3. 缓存雪崩

由于缓存层承载着大量请求,有效地保护了存储层。但是如果缓存层由于某些原因不能提供服务,比如同一时间缓存数据大面积失效,那一瞬间所有的请求都会达到存储层,存储层的调用量会暴增,造成存储层也会级联宕机的情况。
最常用的手段就是将缓存的失效时间分散开,避免缓存的集体失效。

4. 小结

缓存是提升性能的利器,所以保证缓存的高可用是十分有必要的。
Sentinel 和 Redis Cluster都实现了高可用。目前公有云厂商提供的缓存服务可用性非常高,对比自建的缓存服务性能和稳定性都要高上不少。当然啦,缺点就是贵😝😝😝
其次很重要的一点,我们一定要对后端 DB 的查询进行限流,防止数据库被冲垮。


评论