Redis的典型应用

一.缓存

1.缓存的含义:

把⼀些常⽤的数据放到触⼿可及(访问速度更快)的地⽅, ⽅便随时读取

对于计算机硬件来说,往往访问速度越快的设备,成本越高,存储空间越小

缓存是更快, 但是空间上往往是不⾜的. 因此⼤部分的时候, 缓存只放⼀些 热点数据 (访问频繁的数据),就⾮常有⽤了.

2.使用Redis作为缓存:

在⼀个⽹站中, 我们经常会使⽤关系型数据库 (⽐如 MySQL) 来存储数据.

关系型数据库虽然功能强⼤, 但是有⼀个很⼤的缺陷, 就是性能不⾼. (换⽽⾔之, 进⾏⼀次查询操作消耗的系统资源较多)

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

如何让数据库承担更大的并发量,核心思路有两个:

  • 开源:引⼊更多的机器, 部署更多的数据库实例, 构成数据库集群. (主从复制, 分库分表等...)
  • **节流:**引⼊缓存, 使⽤其他的⽅式保存经常访问的热点数据, 从⽽降低直接访问数据库的请求数量

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

Redis访问速度比MySQL快很多,或者说处理同一个访问请求,Redis消耗的系统资源比MySQl少很多,因此Redis能支持的并发量更大

  • Redis的数据存储在内存中,访问内存比硬盘快得多
  • Redis只是支持简单的key-value存储,不涉及复杂查询那么多限制规则

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

提⾼性能.

3.缓存的更新策略

(1)定期生成:

每隔一定的周期,对于数据的访问频次进行统计,挑选出访问频次最高的前N%

(2)实时生成:

先给缓存容量设置上限,接下来把用户的每次查询:

  • 如果在Redis查到了,就直接返回
  • 如果Redis没查到,就去MySQL数据库查询,MySQL返回数据的同时将数据放进Redis中

如果缓存已经满了(达到上限), 就触发缓存淘汰策略, 把⼀些 "相对不那么热⻔" 的数据淘汰掉.

按照上述过程, 持续⼀段时间之后 Redis 内部的数据⾃然就是 "热⻔数据" 了

FIFO:先进先出,把缓存中存在时间最长(最先来的数据)淘汰掉

LRU:最近最少使用,当前时间减去最后一次访问时间,这个值越大则淘汰优先级越高

LFU:最少频次使用,统计每个key的访问频率,值越小的越先淘汰

Random:随即淘汰,从所有key中挑选幸运儿淘汰

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中随机淘汰数据
  • noeviction:默认策略,当内存不⾜以容纳新写⼊数据时,新写⼊操作会报错

4.缓存穿透

含义:查一个不存在的数据,Redis和MySQL都查不到,也不会写入缓存,就会导致每次请求都在访问数据库,给数据库带来巨大压力

产生原因:

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

解决方法:

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

5.缓存击穿

含义:给某个key设置了过期时间,当key过期了,恰好这个时间点对这个key有大量的并发请求过来,这些并发请求可能瞬间把DB压垮

解决方法:

  • 基于统计的方式设置热点key,并设置永不过期(高可用,性能优)
  • 进行必要的服务升级,添加互斥锁,例如访问数据库的时候使⽤分布式锁, 限制同时请求数据库的并发数(强一致,性能差)

6.缓存雪崩

含义:在同一时间段大量的缓存key同时失效或redis服务宕机,导致大量的请求到达数据库,带来巨大的压力

解决方法:

  • 给不同的key的TTL设置随机值
  • 利用redis的集群提高服务的可用性(哨兵,集群)
  • 给缓存业务添加降级限流策略
  • 给业务添加多级缓存

二.分布式锁

1.含义:

在⼀个分布式的系统中, 也会涉及到多个节点访问同⼀个公共资源的情况. 此时就需要通过 锁 来做互斥控制, 避免出现类似于 "线程安全" 的问题

2.分布式锁的基础实现:

本质上就是通过**⼀个键值对**来标识锁的状态

举个例⼦: 考虑买票的场景, 现在⻋站提供了若⼲个⻋次, 每个⻋次的票数都是固定的.

现在存在多个服务器节点, 都可能需要处理这个买票的逻辑: 先查询指定⻋次的余票, 如果余票 > 0, 则设置余票值 -= 1,如下图所示:

很显然,上诉过程是存在"线程安全"问题的,所以就需要锁来控制

此时, 如果 买票服务器1 尝试买票, 就需要先访问 Redis, 在 Redis 上设置⼀个键值对. ⽐如 key 就是⻋次, value 随便设置个值

如果这个操作设置成功, 就视为当前没有节点对该 001 ⻋次加锁, 就可以进⾏数据库的读写操作. 操作完成之后, 再把 Redis 上刚才的这个键值对给删除掉.

如果在 买票服务器1 操作数据库的过程中, 买票服务器2 也想买票, 也会尝试给 Redis 上写⼀个键值对,key 同样是⻋次. 但是此时设置的时候发现该⻋次的 key 已经存在了, 则认为已经有其他服务器正在持有锁, 此时 服务器2 就需要等待或者暂时放弃

注:Redis 中提供了 setnx 操作, 正好适合这个场景. 即: key 不存在就设置, 存在则直接失败.

3.引入过期时间:

当 服务器1 加锁之后, 开始处理买票的过程中, 如果 服务器1 意外宕机了, 就会导致解锁操作 (删除该key) 不能执⾏. 就可能引起其他服务器始终⽆法获取到锁的情况.

为了解决这个问题, 可以在设置 key 的同时引⼊过期时间. 即这个锁最多持有多久, 就应该被释放.

注:可以使⽤ set ex nx 的⽅式, 在设置锁的同时把过期时间设置进去

4.引入校验id:

对于 Redis 中写⼊的加锁键值对, 其他的节点也是可以删除的

⽐如可以把设置的键值对的值, 不再是简单的设为⼀个 1, ⽽是设成服务器的编号. 形如 "001": "服务器1".这样就可以在删除 key (解锁)的时候, 先校验当前删除 key 的服务器是否是当初加锁的服务器, 如果是,才能真正删除; 不是, 则不能删除

5.引入lua:

为了使解锁操作原⼦, 可以使⽤ Redis 的 Lua 脚本功能

Lua 的语法类似于 JS, 是⼀个动态弱类型的语⾔. Lua 的解释器⼀般使⽤ C 语⾔实现. Lua 语法

简单精炼, 执⾏速度快, 解释器也⽐较轻量(Lua 解释器的可执⾏程序体积只有 200KB 左右).

因此 Lua 经常作为其他程序内部嵌⼊的脚本语⾔. Redis 本⾝就⽀持 Lua 作为内嵌脚本.

6.引入看门狗(Watch dog):

上述⽅案仍然存在⼀个重要问题. 当我们设置了 key 过期时间之后 (⽐如 10s), 仍然存在⼀定的可能性,当任务还没执⾏完, key 就先过期了. 这就导致锁提前失效

所以看门狗的作用就是对锁的过期时间进行"续约"

举个例子:

初始情况下设置过期时间为 10s. 同时设定看⻔狗线程每隔 3s 检测⼀次.

那么当 3s 时间到的时候, 看⻔狗就会判定当前任务是否完成

  • 如果任务已经完成, 则直接通过 lua 脚本的⽅式, 释放锁(删除 key)
  • 如果任务未完成, 则把过期时间重写设置为 10s. (即 "续约")

这样就不担⼼锁提前失效的问题了. ⽽且另⼀⽅⾯, 如果该服务器挂了, 看⻔狗线程也就随之挂了, 此时⽆⼈续约, 这个 key ⾃然就可以迅速过期, 让其他服务器能够获取到锁了

注:看门狗是业务服务器上的线程,不是Redis服务器上的线程

7.引入Redlock算法:

引⼊⼀组 Redis 节点. 其中每⼀组 Redis 节点都包含⼀个主节点和若⼲从节点. 并且组和组之间存

储的数据都是⼀致的, 相互之间是 "备份" 关系(⽽并⾮是数据集合的⼀部分, 这点有别于 Redis cluster).

加锁的时候, 按照⼀定的顺序, 写多个 master 节点. 在写锁的时候需要设定操作的 "超时时间". ⽐如

50ms. 即如果 setnx 操作超过了 50ms 还没有成功, 就视为加锁失败

如果给某个节点加锁失败, 就⽴即再尝试下⼀个节点.

当加锁成功的节点数超过总节点数的⼀半, 才视为加锁成功

(如上图,一共五个节点,三个加锁成功,两个失败,视为加锁成功)

这样的话, 即使有某些节点挂了, 也不影响锁的正确性

同理, 释放锁的时候, 也需要把所有节点都进⾏解锁操作. (即使是之前超时的节点, 也要尝试解锁, 尽量保证逻辑严密)

Redlock 算法的核⼼就是, 加锁操作不能只写给⼀个 Redis 节点, ⽽要写给多个!!

分布式系统中任何⼀个节点都是不可靠的. 最终的加锁成功结论是 "少数服从多数的 ".

由于⼀个分布式系统不⾄于⼤部分节点都同时出现故障, 因此这样的可靠性要⽐单个节点来说靠谱不少.

相关推荐
hadage2331 小时前
--- redis 常见问题 ---
数据库·redis·mybatis
O***P5711 小时前
redis批量删除namespace下的数据
数据库·redis·缓存
5***26222 小时前
SQL Server导出和导入可选的数据库表和数据,以sql脚本形式
数据库·sql
JSUITDLWXL2 小时前
Oracle记录被锁的查询与强制删除方法
数据库·oracle
雨中飘荡的记忆2 小时前
SpringAI_Redis向量库实战
数据库·redis·缓存
姓蔡小朋友2 小时前
Redis网络I/O模型
网络·数据库·redis
数据库学啊3 小时前
专业的国产时序数据库哪个好
数据库·时序数据库
爱吃面条的猿3 小时前
MySQL 随机日期/时间生成
数据库·mysql
2501_939909053 小时前
Mysql 主从复制、读写分离
数据库·mysql