一:缓存介绍:

  • 缓存的优点:减小数据库的访问压力, 提高并发能力。

1:缓存架构:

  • 1: 对于查询来说,可以增加缓存。用户发送请求,现在缓存中查询,查询到直接返回数据,查询不到,查询Mysql数据库,然后回填缓存,返回数据。
  • 2:多级缓存:
  • 2.1: 前端也可以搭建缓存,使用浏览器,cache,sqlite3,搭建前端缓存。
  • 2.2:后端一级缓存:使用代码中的大字典,或者全局变量,速度非常快。
  • 2.3:二级缓存,使用内存型数据库Redis等,一般存储所有的数据,每一个小时清除一次缓存。
  • 2.4:三级缓存,使用内存型数据库Redis等,一般存储热点数据,每5小时清除一次缓存。
    缓存设计_数据库
    缓存设计_redis_02

2:缓存粒度:

1: 缓存某个数值:

应用场景:验证码,使用字符串存储。根据key获取值就可以了。

2:缓存数据对象:

应用场景:用户,文章的数据,使用哈希类型存储。

3: 缓存数据集合:

应用场景:数据库的结果集,文章/关注列表。
如果需要排序,比如文章/关注列表,需要对文章/用户列表排序,可以使用zset存储。
如果需要去重可以使用set存储,如果只是存储,可以使用list存储。

注意:使用集合存储数据对象的形式,被称为redis二级索引。原因:因为我适应set/zset存储的只是用户的user_id,通过这个user_id,在缓存中还可以找到用户的其他信息,所以称为二级缓存。

4:缓存视图响应:

如果一个视图经常被使用,可以将url地址作为key,返回的响应作为内容。

三:缓存的过期和淘汰:

1: 缓存过期:

  • 1:设置有效期的优点:节约空间,做到弱一致性,有效期失效以后,保证数据一致。
  • 2:定时过期:给每个过期的key都设置一个定时器,到了过期时间就立即清除。缺点:占用大量的CPU资源计时和处理过期数据,影响缓存的响应时间和吞吐量。
  • 3:惰性过期:只有当这个值使用的时候才判断是否过期,过期清除。节省了CPU但是消耗大量的内存空间。
  • 4: 定期过期:每个一定的时间,扫描一部分键值对是否过期,如果过期则清除。是个折中的方案。

注意: redis中同时使用惰性过期和定时过期两种策略。

1: 默认每100秒检测一次。
2:读或写一个key时,触发惰性过期。

2:缓存淘汰:

缓存设计_数据库_03
缓存设计_redis_04
缓存设计_数据_05
缓存设计_redis_06

面试题:

  • 1: 预估20w数据在redis数据库中的内存用量,然后设置redis内存最大上限值=内存用量。
  • 2:开启内存淘汰策略LFU,将2000w数据读取到mysql中,当redis超过20w数据量后,开启淘汰策略,最后留下的就是热点数据。
    缓存设计_redis_07
  • 1:使用dbsize查看当前数据库中有多少个键值对。
  • 2:使用info Memory 查看Redis分配的内存总量。
  • 3:内存总量/键的个数 * 20w,粗略估计出20w数据占用的内存总量。
  • 4:修改配置文件。

$ sudo vi redis.conf
maxmemory 1048576 # 最大使用内存数量, 以字节为单位 如服务器内存10G, 最多给redis分配9G
maxmemory-policy volatile-lfu # 淘汰策略

方案二:使用第三方工具精确计算:

# 安装工具
git clone https://github.com/sripathikrishnan/redis-rdb-tools
cd redis-rdb-tools
sudo python3 setup.py install

# 将Redis持久化的数据导出,  其中 /path/dump.rdb 为Redis持久化的数据文件路径
$ rdb -c memory /path/dump.rdb > ~/redis_memory_report.csv
$ cat ~/Desktop/redis_memory_report.csv

# 库号, 类型, 键名, 占用空间, 编码方式, 元素个数, 最大元素占用的空间, 过期时间
database,type,key,size_in_bytes,encoding,num_elements,len_largest_element,expiry  
0,sortedset,user:all:art_count,89,ziplist,3,8,
0,sortedset,list2,63,ziplist,1,8,

四:缓存问题:

1:缓存更新

  • 1: 解决方案一:设计使用分布式锁(redis setnx)使用消息队列顺序执行。
  • 1.1:
  • 2:解决方案二:更新数据到mysql数据库时,先删除缓存。
  • 2.1:如果使用缓存,缓存的是用户对象,字典,或者哈希,可以删除缓存。
  • 2.2:如果使用缓存,缓存的是集合,比如粉丝列表,考虑更新缓存,而不是删除缓存。删除缓存成本太高了。
    缓存设计_缓存_08

2:缓存穿透:

  • 缓存只是为了缓解数据库压力而添加的一层保护层,当从缓存中查询不到我们需要的数据就要去数据库中查询了。如果被黑客利用,频繁去访问缓存中没有的数据,那么缓存就失去了存在的意义,瞬间所有请求的压力都落在了数据库上,这样会导致数据库连接异常。
  • 解决方案一:对于数据库不存在的值,也在缓存中设置Null。
  • 解决方案二:设置布隆过滤器。

缓存设计_redis_09

描述:当第一次请求数据库中没有这个值,则在redis中设置一个值为空的数据,下次再次请求这个数据的时候,redis中直接返回一个NULL类型的数据,就不会访问MYSQL数据库了,防止了黑客的频繁攻击数据库。一般来说,为了不占用redis数据库资源,设置的时间比较短。当缓存过期了,数据库存在了这个数据,用户再次访问,则mydsl数据,直接回填缓存即可。

布隆过滤器的设置:

pip install pybloomfiltermmap3
缓存设计_数据库_10

缓存设计_解决方案_11

3: 缓存雪崩:

  • 如果大量缓存数据都在同一个时间过期, 那么很可能出现 缓存集体失效, 会导致所有的请求都直接访问数据库, 导致数据库压力过大。
  • 方案1: 设置过期时间时添加随机值, 让过期时间进行一定程度分散,避免同一时间集体失效。

比如以前是设置10分钟的超时时间,那每个Key都可以随机8-13分钟过期,尽量让不同Key的过期时间不同。

  • 方案2: 采用多级缓存,不同级别缓存设置的超时时间不同,即使某个级别缓存都过期,也有其他级别缓存兜底。

缓存设计_redis_12

五:缓存模式:

缓存设计_缓存_13
缓存设计_数据库_14