Redis缓存

Redis最主要的用途

  • 存储数据(内存数据库)
  • 缓存(redis最常用的场景)
  • 消息队列

什么是缓存

缓存 (cache) 是计算机中的⼀个经典的概念. 在很多场景中都会涉及到. 核⼼思路就是把⼀些常⽤的数据放到触⼿可及(访问速度更快)的地⽅, ⽅便随时读取

举个例⼦: ⽐如我需要去⾼铁站坐⾼铁. 我们知道坐⾼铁是需要反复刷⾝份证的 (进⼊⾼铁站, 检票, 上⻋, 乘⻋过程中, 出站....)

正常来说, 我的⾝份证是放在⽪箱⾥的(⽪箱的存储空间⼤, ⾜够能装). 但是每次刷⾝份证都需 要开⼀次⽪箱找⾝份证, 就⾮常不⽅便

因此我就可以把⾝份证先放到⾐服⼝袋⾥. ⼝袋虽然空间⼩, 但是访问速度⽐⽪箱快很多

这样的话每次刷⾝份证我只需要从⼝袋⾥掏⾝份证就⾏了, 就不必开⽪箱了

此时 "⼝袋" 就是 "⽪箱" 的缓存. 使⽤缓存能够⼤⼤提⾼访问效率.

这⾥所说的 "触⼿可及" 是个相对的概念

我们知道, 对于硬件的访问速度来说, 通常情况下:

CPU 寄存器 > 内存 > 硬盘 > ⽹络

那么硬盘相对于⽹络是 "触⼿可及的", 就可以使⽤硬盘作为⽹络的缓存

内存相对于硬盘是 "触⼿可及的", 就可以使⽤内存作为硬盘的缓存

CPU 寄存器相对于内存是 "触⼿可及的", 就可以使⽤ CPU 寄存器作为内存的缓存

  • 速度快的设备,可以作为速度慢的设备的缓存
  • 最常见的是内存作为硬盘的缓存(redis定位)
  • 硬盘也可以作为网络的缓存,浏览器的缓存
  • 浏览器通过http/https从服务器上获取到数据(html,css,js,照片,视频,音频,字体)并进行展示,像这样体积大,又不太改变的数据,就可以保存到浏览器本地(浏览器所在主机的硬盘上),后续再打开这个页面,就不必重新从网络获取上述数据了

"⼆⼋定律"

20%的热点数据, 能够应对 80% 的访问场景

因此只需要把这少量的热点数据缓存起来, 就可以应对⼤多数场景, 从⽽在整体上有明显的性 能提升.

使⽤ Redis 作为数据库(MySQL)缓存

Question:为什么说关系型数据库性能不⾼?


Answer:

  • 数据库把数据存储在硬盘上, 硬盘的 IO 速度并不快. 尤其是随机访问
  • 如果查询不能命中索引, 就需要进⾏表的遍历, 这就会⼤⼤增加硬盘 IO 次数
  • 关系型数据库对于 SQL 的执⾏会做⼀系列的解析, 校验, 优化⼯作
  • 如果是⼀些复杂查询, ⽐如联合查询, 需要进⾏笛卡尔积操作, 效率更是降低很多
  • ......

因此, 如果访问数据库的并发量⽐较⾼, 对于数据库的压⼒是很⼤的, 很容易就会使数据库服务器宕机

Question:为什么并发量⾼了就会宕机?


Answer:

  1. 服务器每次处理⼀个请求, 都是需要消耗⼀定的硬件资源的。所谓的硬件资源包括不限于 CPU, 内存, 硬盘, ⽹络带宽...... ⼀个服务器的硬件资源本⾝是有限的
  2. ⼀个请求消耗⼀份资源, 请求多了, ⾃然把资源就耗尽 了
  3. 后续的请求没有资源可⽤, ⾃然就⽆法正确处理. 更严重的还会导致服务器程序的代码出现 崩溃.

Question:

那么如何解决这个问题(提高MySQL能承担的并发量)?


Answer:

开源:引入更多的机器,构成数据库集群

节流:引入缓存,把一些频繁读取的热点数据,保存到缓存上,后续在查询数据的时候,如果缓存中已经存在了,就不再访问MySQL了

Redis 就是⼀个⽤来作为数据库缓存的常⻅⽅案

  • Redis 访问速度⽐ MySQL 快很多. 或者说处理同⼀个访问请求, Redis 消耗的系统资源⽐ MySQL 少很多. 因此 Redis 能⽀持的并发量更⼤
  • Redis 数据在内存中, 访问内存⽐硬盘快很多
  • Redis 只是⽀持简单的 key-value 存储, 不涉及复杂查询的那么多限制规则

缓存是⽤来加快 "读操作" 的速度的. 如果是 "写操作", 还是要⽼⽼实实写数据库, 缓存并不能 提⾼性能.

缓存更新策略

  • 如何知道redis中应该存储哪些数据?
  • 如何知道哪些数据是热点数据呢?

定期生成

会把访问的数据,给以日志的形式记录下来;通过日志,把使用到了哪些词,给记录下来,就可以针对这些日志进行统计了,统计每个词出现的频率,再根据频率降序排序,再取出前20%的词,就可以把这些词认为是”热点词“,数据量大,可以使用HDFS,或者hadoop的map-reduce来写代码进行统计/也可以使用基于HDFS的HBASE这样的数据库来写sql统计

此处的数据,就可以根据当前这里统计的维度,来定期更新,按照天级别统计,就每天更新一次;按照月级别统计,就每个月更新一次

写一套离线的流程(shell/python写脚本代码)可以通过定时任务来触发

  • 完成统计热词的过程
  • 根据热刺,找到搜索结果的数据
  • 把得到缓存数据同步到缓存服务器上
  • 控制这些缓存服务器自动重启

  • 优点:实现起来比较简单,过程可控(缓存中有啥是比较固定的,方便排查问题)
  • 缺点:实时性不够,如果出现一些突发性事件,有一些本来不是热词的内容,成了热词了,新的热词可能会给后面的数据库啥的带来较大的压力

实时生成

先给缓存设定容量上限(可以通过 Redis 配置⽂件的 maxmemory 参数设定)

接下来把⽤⼾每次查询

  • 如果在 Redis 查到了, 就直接返回
  • 如果 Redis 中不存在, 就从数据库查, 把查到的结果同时也写⼊ Redis,不断写redis,就会使redis的内存占用越来越多,逐渐达到内存上限(redis上限可以配置使用多少内存->maxmemory

此时如果继续往里插入数据,就会触发问题

为了解决上述问题,redis就引入了”内存淘汰策略“

  • FIFO (First In First Out) 先进先出 把缓存中存在时间最久的 (也就是先来的数据) 淘汰掉
  • LRU (Least Recently Used) 淘汰最久未使⽤的 记录每个 key 的最近访问时间. 把最近访问时间最⽼的 key 淘汰掉
  • LFU (Least Frequently Used) 淘汰访问次数最少的 记录每个 key 最近⼀段时间的访问次数. 把访问次数最少的淘汰掉
  • Random 随机淘汰 从所有的 key 中抽取幸运⼉被随机淘汰掉

理解上述⼏种淘汰策略:

想象你是个皇帝, 有后宫佳丽三千. 虽然你是 "真⻰天⼦", 但是经常宠幸的妃⼦也就那么寥寥数 ⼈(精⼒有限)

后宫佳丽三千, 相当于数据库中的全量数据. 经常宠幸的妃⼦相当于热点数据, 是放在缓存中 的

今年选秀的⼀批新的⼩主, 其中有⼀个被你看上了. 宠信新⼈, ⾃然就需要有旧⼈被冷落. 到底 谁是要被冷落的⼈呢?


  • FIFO: 皇后是最先受宠的. 现在已经年⽼⾊衰了. 皇后失宠
  • LRU: 统计最近宠幸时间. 皇后(⼀周前), 熹妃(昨天), 安答应(两周前), 华妃(⼀个⽉前). 华妃 失宠
  • LFU: 统计最近⼀个⽉的宠幸次数, 皇后(3次), 熹妃(15次), 安答应(1次), 华妃(10次). 安答应 失宠
  • Random: 随机挑⼀个妃⼦失宠.

这⾥的淘汰策略, 我们可以⾃⼰实现

当然 Redis 也提供了内置的淘汰策略, 也可以供我们直接使⽤. Redis 内置的淘汰策略如下

  • volatile-lru 当内存不⾜以容纳新写⼊数据时,从设置了过期时间(设置了过期的时间就算,包括过期时间还没到的)的key中使⽤LRU(最近最少使⽤)算法进⾏淘汰
  • allkeys-lru 当内存不⾜以容纳新写⼊数据时,从所有key中使⽤LRU(最近最少使⽤)算法进 ⾏淘汰
  • volatile-lfu 4.0版本新增,当内存不⾜以容纳新写⼊数据时,在过期的key中,使⽤LFU算法 进⾏删除key
  • allkeys-lfu 4.0版本新增,当内存不⾜以容纳新写⼊数据时,从所有key中使⽤LFU算法进⾏ 淘汰
  • volatile-random 当内存不⾜以容纳新写⼊数据时,从设置了过期时间的key中,随机淘汰数 据
  • allkeys-random 当内存不⾜以容纳新写⼊数据时,从所有key中随机淘汰数据
  • volatile-ttl 在设置了过期时间的key中,根据过期时间进⾏淘汰,越早过期的优先被淘汰. (相当于 FIFO, 只不过是局限于过期的 key),对于其他没有设置过期时间的,很可能是没有保存设置时间的
  • noeviction 默认策略,当内存不⾜以容纳新写⼊数据时,新写⼊操作会报错

整体来说 Redis 提供的策略和我们上述介绍的通⽤策略是基本⼀致的. 只不过 Redis 这⾥会针对 "过期 key" 和 "全部 key" 做分别处理.

缓存使用注意事项

关于缓存预热 (Cache preheating)

缓存中的数据,定期生成(不涉及预热),实时生成涉及到预热:

redis服务器首次接入之后服务器里是没有数据的(此时,所有的请求都会打给MySQL,随着时间的推移,redis上的数据越积累越多,MySQL承担的压力就逐渐减少了):客户端先查询redis,如果没查到,就再查一次MySQL,查到了之后,会把数据也写入到redis中

缓存预热就是来解决上述问题的,把定期生成和实时生成结合一下,先通过离线的方式,通过一些统计的途径,先把热点数据找到一批,导入到redis中,此时导入的这批热点数据,就能帮MySQL承担很大的压力了,随着时间的推移,逐渐就使用新的热点数据淘汰掉旧的数据

关于缓存穿透 (Cache penetration)

查询的某个key,在redis中没有,MySQL中也没有,这个key肯定也不会被更新到redis中,这次查询,没有,下次查,仍然没有;想这样的数据,存在很多,并且还反复查询,一样也会给MySQL带来很大的压力

为何产⽣? 原因可能有⼏种

  • 业务设计不合理. ⽐如缺少必要的参数校验环节, 导致⾮法的 key 也被进⾏查询了
  • 开发/运维误操作. 不⼩⼼把部分数据从数据库上误删了
  • 黑客恶意攻击

如何解决?

  • 针对要查询的参数进⾏严格的合法性校验. ⽐如要查询的 key 是⽤⼾的⼿机号, 那么就需要校验当前 key 是否满⾜⼀个合法的⼿机号的格式
  • 针对数据库上也不存在的 key , 也存储到 Redis 中, ⽐如 value 就随便设成⼀个 "". 避免后续频繁访问数据库-降低问题的严重性
  • 使⽤布隆过滤器先判定 key 是否存在, 再真正查询.

关于缓存雪崩 (Cache avalanche)

由于在短时间内,redis上大规模的key失效,导致缓存命中率陡然下降,并且MySQL的压力迅速上升,甚至直接宕机

  1. redis直接挂了,redis宕机/redis集群模式下大量节点宕机
  2. redis还好,但是可能之前短时间内设置很多key给redis,并且设置的过期时间是相同的,给redis里设置key作为缓存的时候,有的时候为了考虑缓存的时效性,就会设置过期时间(和redis内存淘汰机制,是配合使用的)

解决方案

  • 加强监控报警,加强redis集群可用性的保证
  • 不给key设置过期时间/设置过期时间的时候添加随机的因子(避免同一时刻过期)

关于缓存击穿 (Cache breakdown)-瘫痪!

可以说是缓存雪崩的特殊情况-针对热点 key , 突然过期了,导致⼤量的请求直接访问到数据库上, 甚⾄引起数据库宕机

如何解决?

  • 基于统计的⽅式发现热点 key, 并设置永不过期(往往需要服务器结构做出比较大的调整)
  • 进⾏必要的服务降级(本身服务器的功能有10个,但是在特定情况下,适当的关闭一些不重要的功能,只保留核心功能),例如访问数据库的时候使⽤分布式锁, 限制同时请求数据库的并发数,类似于省电模式

小结

  • 缓存基本概念
  • 如何使用redis作为缓存
  • 缓存更新策略-redis内存淘汰机制
  • 缓存使用的注意事项
  1. 缓存预热
  2. 缓存穿透
  3. 缓存雪崩
  4. 缓存击穿
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值