【9.0】Redis之缓存优化
【一】缓存的收益与成本
【1】受益
- 1 加速读写
- 2 降低后端负载:后端服务器通过前端缓存降低负载,业务端使用redis降低后端mysql负载
【2】成本
- 1 数据不一致:缓存层和数据层有时间窗口不一致,和更新策略有关
- 2 代码维护成本:多了一层缓存逻辑
- 3 运维成本:比如使用了Redis Cluster
【3】使用场景
- 1 降低后端负载:对高消耗的sql,join结果集/分组统计的结果做缓存
- 2 加速请求响应:利用redis优化io响应时间
- 3 大量写合并为批量写:如计数器先redis累加再批量写入db
【二】缓存更新策略
【1】LRU/LFU/FIFO算法剔除
-
maxmemory-policy(到了最大内存,对应的应对策略)
-
LRU -Least Recently Used,没有被使用时间最长的
-
LFU -Least Frequenty User,一定时间段内使用次数最少的
-
FIFO -First In First Out,先进先出,最早放的线删除
-
LIRS (Low Inter-reference Recency Set)是一个页替换算法,相比于LRU(Least Recently Used)和很多其他的替换算法,LIRS具有较高的性能。
- 这是通过使用两次访问同一页之间的距离(本距离指中间被访问了多少非重复块)作为一种尺度去动态地将访问页排序,从而去做一个替换的选择
-
-
配置文件中设置:
(1)LRU配置
maxmemory-policy:volatile-lru
- (1)noeviction: 如果内存使用达到了maxmemory,client还要继续写入数据,那么就直接报错给客户端 - (2)allkeys-lru: 就是我们常说的LRU算法,移除掉最近最少使用的那些keys对应的数据,ps最长用的策略
- (3)volatile-lru: 也是采取LRU算法,但是仅仅针对那些设置了指定存活时间(TTL)的key才会清理掉
- (4)allkeys-random: 随机选择一些key来删除掉
- (5)volatile-random: 随机选择一些设置了TTL的key来删除掉
- (6)volatile-ttl: 移除掉部分keys,选择那些TTL时间比较短的keys
(2)LFU配置
- Redis4.0之后为maxmemory_policy淘汰策略添加了两个LFU模式:
- volatile-lfu:对有过期时间的key采用LFU淘汰算法
- allkeys-lfu:对全部key采用LFU淘汰算法
- 还有2个配置可以调整LFU算法:
- lfu-log-factor 10
- lfu-log-factor可以调整计数器counter的增长速度
- lfu-log-factor越大,counter增长的越慢。
- lfu-decay-time 1
- lfu-decay-time是一个以分钟为单位的数值,可以调整counter的减少速度
- lfu-log-factor 10
【2】超时剔除
- 例如expire,设置过期时间
【3】主动更新
- 开发控制生命周期
【4】小结
策略 | 一致性 | 维护成本 |
---|---|---|
LRU/LIRS算法剔除 | 最差 | 低 |
超时剔除 | 较差 | 低 |
主动更新 | 强 | 高 |
-
1 低一致性:最大内存和淘汰策略
-
2 高一致性:超时剔除和主动更新结合,最大内存和淘汰策略兜底
【三】缓存粒度控制
【1】从mysql获取用户信息
select * from user where id=100
【2】设置用户信息缓存
set user:100 select * from user where id=100
【3】缓存粒度
-
缓存全部属性
-
缓存部分重要属性
【4】小结
- 1 通用性:全量属性更好
- 2 占用空间:部分属性更好
- 3 代码维护:表面上全量属性更好
【四】缓存穿透
【1】描述
- 缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求
- 如发起为id为“-1”的数据或id为特别大不存在的数据。
- 这时的用户很可能是攻击者,攻击会导致数据库压力过大。
【2】解决方案
- 1 接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;
- 2 从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。
- 这样可以防止攻击用户反复用同一个id暴力攻击
- 3 通过布隆过滤器实现
- 在接口层增加校验
- 可以对用户进行鉴权校验,并在接口层对ID进行基础校验,例如拦截ID小于等于0的请求,以减少无效请求的访问量。
- 设置缓存空值
- 对于从数据库中获取不到的数据,将对应的key-value对设置为
key-null
,并将缓存有效时间设置较短,比如30秒。这样做可以防止攻击者不断用相同的ID进行攻击。
- 对于从数据库中获取不到的数据,将对应的key-value对设置为
- 使用布隆过滤器
- 布隆过滤器是一种高效的数据结构,可以判断某个元素是否存在于集合中。
- 可以利用布隆过滤器来过滤掉那些确定不存在于数据库中的请求,减轻数据库的压力。
【3】应用场景
- 假设有一个在线电商网站,用户通过输入商品ID来查询商品信息。
- 为防止缓存穿透,可以在后端接口中先对用户进行鉴权校验,确保只有合法用户才能访问。
- 同时,对于无效的商品ID(如小于等于0或特别大)的请求,可以直接拦截并返回提示信息,而不是继续向数据库发起查询请求。
【补充】布隆过滤器应用
- 布隆过滤器是一种概率型数据结构,用于判断一个元素是否可能存在于一个集合中。
- 它通过使用位数组和多个哈希函数来实现这一功能,并且具有高效的查询速度和空间效率。
- 在使用布隆过滤器时,首先需要初始化一个位数组,所有的位都被设置为0。
- 然后,对于要加入到集合中的每个元素,使用多个哈希函数对其进行计算,得到一系列的哈希值。
- 根据这些哈希值,将位数组中对应的位置设置为1。
- 判断一个元素是否存在于集合中时,同样对该元素进行多次哈希计算
- 如果所有对应的位置都是1,则可能存在于集合中;
- 如果存在任何一个位置是0,则一定不存在于集合中。
- 布隆过滤器具有一定的误判率,即会存在一定的错误判断。
- 这是因为多个元素可能哈希到了相同的位上,导致冲突。
- 但是,布隆过滤器能够在极小的空间开销下,处理大规模的数据集,因此在某些场景下是非常适用的。
- 下面是一个示例演示布隆过滤器的使用:
from bitarray import bitarray
import mmh3
class BloomFilter:
def __init__(self, size, num_hash):
self.size = size
self.num_hash = num_hash
self.bit_array = bitarray(size)
self.bit_array.setall(0)
def add(self, item):
for seed in range(self.num_hash):
index = mmh3.hash(item, seed) % self.size
self.bit_array[index] = 1
def contains(self, item):
for seed in range(self.num_hash):
index = mmh3.hash(item, seed) % self.size
if self.bit_array[index] == 0:
return False
return True
# 创建一个布隆过滤器,设置大小为10,使用3个哈希函数
bloom_filter = BloomFilter(10, 3)
# 添加元素到布隆过滤器
bloom_filter.add("apple")
bloom_filter.add("banana")
bloom_filter.add("orange")
# 判断某个元素是否存在于布隆过滤器中
print(bloom_filter.contains("apple")) # 输出 True
print(bloom_filter.contains("peach")) # 输出 False
- 在实际应用中,布隆过滤器常被用来减轻数据库的查询压力。
- 例如,在一个网站的用户登录场景中,可以使用布隆过滤器将那些非法的登录请求快速过滤掉,从而减少对数据库的查询次数。
- 另一个例子是在大规模数据集的搜索引擎中,当用户输入一个关键词进行搜索时,可以先通过布隆过滤器判断该关键词是否存在于索引中,如果不存在则可以直接返回搜索结果为空,从而节省了后续的查询操作。
【五】缓存击穿
【1】描述
- 缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力
【2】解决方案
- 设置热点数据永远不过期。
- 设置热点数据永远不过期
- 将热点数据,即高频访问的数据,不设置过期时间,保持其一直有效,以减少因缓存失效而引起的数据库压力。
【3】应用场景
- 假设有一个新闻网站,热门新闻的浏览量非常大。
- 为了避免缓存击穿,可以将热门新闻的数据存放在缓存中,并设置其不过期,这样用户访问热门新闻时,即使缓存中的数据过期了,也能保证始终能从缓存中获取最新数据,而不需要频繁访问数据库。
【六】缓存雪崩
【1】描述
- 缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。
- 和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
【2】解决方案
- 1 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
- 2 如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。
- 3 设置热点数据永远不过期。
- 设置随机的缓存过期时间
- 将缓存数据的过期时间设置为随机值,避免多个缓存同时失效导致一次性大量请求落到数据库上。
- 分布式部署缓存数据库
- 如果缓存数据库是分布式部署的,可以将热点数据均匀地分布在不同的缓存数据库中,从而减轻单个数据库的压力。
- 设置热点数据永远不过期
- 对于热点数据,可以将其设置为永远不过期,以保证即使其他数据失效,热点数据仍然能够被缓存使用。
【3】应用场景
- 假设有一个电影推荐平台,用户请求不同类型的电影列表。
- 为了应对缓存雪崩,可以设置每个类型的电影列表的缓存过期时间为随机值(比如在原有过期时间基础上加上一个随机值)。
- 同时,将不同类型的电影列表分别存放在不同的缓存数据库中,这样可以避免多个缓存同时失效而导致对数据库的大量请求。
- 对于热门的电影类型,可以设置其数据永远不过期,以保证其始终可用。
本文来自博客园,作者:Chimengmeng,转载请注明原文链接:https://www.cnblogs.com/dream-ze/p/17691103.html