【Redis十二】Redis的典型应用(缓存和分布式锁)

目录

Redis作为缓存

1.什么是缓存?

2.缓存的更新策略

3.缓存预热,缓存穿透,缓存雪崩和缓存击穿

Redis作为分布式锁

1.什么是分布式锁?

2.分布式锁的实现过程


Redis是目前后端开发中非常热门的组件之一,本篇文章主要介绍它在作为缓存以及分布式领域的作用。

Redis作为缓存

1.什么是缓存?

缓存 (cache) 是计算机中的⼀个经典的概念. 在很多场景中都会涉及到。
核⼼思路就是把⼀些常⽤的数据放到触⼿可及(访问速度更快)的地⽅, ⽅便随时读取。
对于计算机硬件来说, 往往访问速度越快的设备, 成本越⾼, 存储空间越⼩.
缓存是更快, 但是空间上往往是不⾜的. 因此⼤部分的时候, 缓存只放⼀些 热点数据 (访问频繁的数据),就⾮常有⽤了。
众所周知,咱们日常使用的关系型数据库比如MySQL是非常"娇嫩"的,操作他比起操作缓存是非常耗时的,而且很容易在一瞬间遭受到大量数据操作时,因忙不过来而宕机,数据库很多时候直接成为了整个程序的性能瓶颈。为了给MySQL减少压力,提高程序的效率和稳定性,业内通常使用Redis作为MySQL的缓存,这样就能大大减少MySQL的压力,提高程序性能和稳定性。

之所以缓存只存热点数据就能大大降低数据库的访问压力,是因为这个世界存在普遍的"二八定律",即 20% 的热点数据, 能够应对 80% 的访问场景。 因此只需要把这少量的热点数据缓存起来, 就可以应对⼤多数场景, 从⽽在整体上有明显的性 能提升.

2.缓存的更新策略

接下来还有⼀个重要的问题, 到底哪些数据才是 "热点数据" 呢?
对于热点数据的挑选一般有两种方式:

  1. 定期生成
  2. 实时生成

定期生成:
每隔⼀定的周期(⽐如⼀天/⼀周/⼀个⽉), 对于访问的数据频次进⾏统计. 挑选出访问频次最⾼的前 N%的数据。

以搜索引擎为例:
搜索引擎的服务器会把哪个⽤⼾什么时间搜了啥词, 都通过⽇志的⽅式记录的明明⽩ . 然后
每隔⼀段时间对这期间的搜索结果进⾏统计 (⽇志的数量可能⾮常巨⼤, 这个统计的过程可能
需要使用 hadoop等大数据挖掘技术完成). 从⽽就可以得到 "⾼频词表" 。
实时生成:
先给缓存设定容量上限(可以通过 Redis 配置⽂件的 maxmemory 参数设定).
接下来把⽤⼾每次查询:

  • 如果在 Redis 查到了, 就直接返回.
  • 如果 Redis 中不存在, 就从数据库查, 把查到的结果同时也写⼊ Redis.

如果缓存已经满了(达到上限), 就触发缓存淘汰策略, 把⼀些 "相对不那么热⻔" 的数据淘汰掉.
按照上述过程, 持续⼀段时间之后 Redis内部的数据⾃然就是 "热⻔数据" 了。
常用的缓存淘汰策略主要有以下几种:

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

3.缓存预热,缓存穿透,缓存雪崩和缓存击穿

缓存预热
什么是缓存预热?

使⽤ Redis 作为 MySQL 的缓存的时候, 当 Redis 刚刚启动, 或者 Redis ⼤批 key 失效之后, 此时由于Redis ⾃⾝相当于是空着的, 没啥缓存数据, 那么 MySQL 就可能直接被访问到, 从⽽造成较⼤的压⼒.因此就需要提前把热点数据准备好, 直接写⼊到 Redis 中. 使 Redis 可以尽快为 MySQL 撑起保护伞.热点数据可以基于之前介绍的统计的⽅式⽣成即可. 这份热点数据不⼀定⾮得那么 "准确", 只要能帮助MySQL 抵挡⼤部分请求即可. 随着程序运⾏的推移, 缓存的热点数据会逐渐⾃动调整, 来更适应当前情况。

缓存穿透
什么是缓存穿透?

访问的 key 在 Redis 和 数据库中都不存在. 此时这样的 key 不会被放到缓存上, 后续如果仍然在访问该key, 依然会访问到数据库. 这就会导致数据库承担的请求太多, 压⼒很⼤. 这种情况称为 缓存穿透.

因为什么产生?

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

如何解决?

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

缓存雪崩
什么是缓存雪崩?
短时间内⼤量的 key 在缓存上失效, 导致数据库压⼒骤增, 甚⾄直接宕机。
为何产⽣?

⼤规模 key 失效, 可能性主要有两种:

  • Redis 挂了.
  • Redis 上的⼤量的 key 同时过期.

如何解决?

  • 部署⾼可⽤的 Redis 集群, 并且完善监控报警体系.
  • 不给 key 设置过期时间 或者 设置过期时间的时候添加随机时间因⼦.

缓存击穿
什么是缓存击穿?

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

如何解决?

  • 基于统计的⽅式发现热点 key, 并设置永不过期.
  • 进⾏必要的服务降级. 例如访问数据库的时候使⽤分布式锁, 限制同时请求数据库的并发数.

Redis作为分布式锁

1.什么是分布式锁?

在⼀个分布式的系统中, 也会涉及到多个节点访问同⼀个公共资源的情况. 此时就需要通过 锁 来做互斥控制, 避免出现类似于 "线程安全" 的问题.
⽽ java 的 synchronized 或者 C++ 的 std::mutex, 这样的锁都是只能在当前进程中⽣效, 在分布式的这种多个进程多个主机的场景下就⽆能为力了。

分布式锁本质上就是使⽤⼀个公共的服务器, 来记录 加锁状态。这个公共的服务器可以是 Redis, 也可以是其他组件(⽐如 MySQL 或者 ZooKeeper 等), 还可以是我们⾃⼰写的⼀个服务。

2.分布式锁的实现过程

1.首先我们可以使用Redis的键值对功能进行简单的加锁功能。

如果服务器1 尝试买票操作, 就需要先访问 Redis, 在 Redis 上设置⼀个键值对. ⽐如 key 就是⻋ 次, value 随便设置个值 (比如 1).如果这个操作设置成功, 就视为当前没有节点对该 001 ⻋次加锁, 就可以进⾏数据库的读写操作. 操作完成之后, 再把 Redis 上刚才的这个键值对给删除掉. 如果在 买票服务器1 操作数据库的过程中, 买票服务器2 也想买票, 也会尝试给 Redis 上写⼀个键值对, key 同样是⻋次. 但是此时设置的时候发现该⻋次的 key 已经存在了, 则认为已经有其他服务器正在持有锁, 此时服务器2就需要等待或者暂时放弃.(可以使用setnx操作)。

2.在上述加锁过程中我们可以引入过期时间。

当服务器1 加锁之后, 开始处理买票的过程中, 如果 服务器1 意外宕机了, 就会导致解锁操作 (删除该key) 不能执⾏. 就可能引起其他服务器始终⽆法获取到锁的情况. 为了解决这个问题, 可以在设置 key 的同时引⼊过期时间. 即这个锁最多持有多久, 就应该被释放。

  1. 对于Redis写入的加锁键值对,其他节点也是可以删除的,此时我们需要引入校验id。

⽐如 服务器1 写⼊⼀个 "001": 1 这样的键值对, 服务器2 是完全可以把 "001" 给删除掉的. 当然, 服务器2 不会进⾏这样的 "恶意删除" 操作, 不过不能保证因为⼀些 bug 导致 服务器2 把锁误删除。
为了解决上述问题, 我们可以引⼊⼀个校验 id.
⽐如可以把设置的键值对的值, 不再是简单的设为⼀个 1, ⽽是设成服务器的编号. 形如 "001": "服务器 1"。这样就可以在删除 key (解锁)的时候, 先校验当前删除 key 的服务器是否是当初加锁的服务器, 如果是,才能真正删除; 不是, 则不能删除。

4.引入Redis的Lua脚本功能是锁操作具有原子性。

lua脚本是一个以.lua为后缀的文件,使用lua语法编辑,它可以由redis-cli或jedis等客户端加载,并发送给Redis服务器,由Redis服务器来执行这段逻辑。一个lua脚本会被Redis以原子的方式来执行

5.引入watch dog(看门狗)使过期时间更准确。

上述⽅案仍然存在⼀个重要问题. 当我们设置了 key 过期时间之后 (⽐如 10s), 仍然存在⼀定的可能性, 当任务还没执⾏完, key 就先过期了. 这就导致锁提前失效。所谓 watch dog, 本质上是加锁的服务器上的⼀个单独的线程, 通过这个线程来对锁过期时间进⾏ "续约"。这样就不担⼼锁提前失效的问题了. ⽽且另⼀⽅⾯, 如果该服务器挂了, 看⻔狗线程也就随之挂了, 此时无人续约, 这个 key ⾃然就可以迅速过期, 让其他服务器能够获取到锁了.

6.引入Redlock算法,使用Redis集群参与分布式锁。

实践中的 Redis ⼀般是以集群的⽅式部署的 (⾄少是主从的形式, ⽽不是单机). 那么就可能出现以下⽐较极端的⼤冤种情况:
服务器1 向 master 节点进⾏加锁操作. 这个写⼊ key 的过程刚刚完成, master 挂了; slave 节点升级成了新的 master 节点. 但是由于刚才写⼊的这个 key 尚未来得及同步给 slave 呢, 此时就相当于 服务器1 的加锁操作形同虚设了, 服务器2 仍然可以进⾏加锁 (即给新的 master 写⼊ key. 因为新的 master 不包含刚才的 key).
为了解决这个问题, Redis 的作者提出了 Redlock 算法.
我们引⼊⼀组 Redis 节点. 其中每⼀组 Redis 节点都包含⼀个主节点和若⼲从节点. 并且组和组之间存储的数据都是⼀致的, 相互之间是 "备份" 关系(⽽并⾮是数据集合的⼀部分, 这点有别于 Redis cluster).加锁的时候, 按照⼀定的顺序, 写多个 master 节点. 在写锁的时候需要设定操作的 "超时时间". ⽐如50ms. 即如果 setnx 操作超过了 50ms 还没有成功, 就视为加锁失败.如果给某个节点加锁失败, 就⽴即再尝试下⼀个节点. 当加锁成功的节点数超过总节点数的⼀半, 才视为加锁成功. 这样的话, 即使有某些节点挂了, 也不影响锁的正确性.同理, 释放锁的时候, 也需要把所有节点都进⾏解锁操作. (即使是之前超时的节点, 也要尝试解锁, 尽量保证逻辑严密)。

Redlock 算法的核⼼就是, 加锁操作不能只写给⼀个 Redis 节点, ⽽要写个多个!! 分布式系统中任何⼀个节点都是不可靠的. 最终的加锁成功结论是 "少数服从多数的"。
由于⼀个分布式系统不⾄于⼤部分节点都同时出现故障, 因此这样的可靠性要⽐单个节点来说靠谱不少.

❤️😍😍😍😍😍😍😍😍😍😍😍😍😍😍😍😍😍

🍔我是小皮侠,谢谢大家都能看到这里!!

🦚主页已更新Java基础内容,数据结构基础,数据库,算法,Redis相关内容。

🚕未来会更新Java项目,SpringBoot,docker,mq,微服务以及各种Java路线会用到的技术。

🎃求点赞!求收藏!求评论!求关注!

🤷‍♀️谢谢大家!!!!!!!!

相关推荐
正在努力Coding1 分钟前
kafka(windows)
分布式·kafka
让我上个超影吧2 小时前
黑马点评【基于redis实现共享session登录】
java·redis
懒羊羊大王呀5 小时前
Ubuntu20.04中 Redis 的安装和配置
linux·redis
禺垣6 小时前
区块链技术概述
大数据·人工智能·分布式·物联网·去中心化·区块链
John Song7 小时前
Redis 集群批量删除key报错 CROSSSLOT Keys in request don‘t hash to the same slot
数据库·redis·哈希算法
zhuhit9 小时前
FASTDDS的安全设计
分布式·机器人·嵌入式
暗影八度9 小时前
Spark流水线+Gravitino+Marquez数据血缘采集
大数据·分布式·spark
q5673152310 小时前
IBM官网新闻爬虫代码示例
开发语言·分布式·爬虫
不爱学英文的码字机器10 小时前
数据网格的革命:从集中式到分布式的数据管理新范式
分布式
优秀的颜13 小时前
计算机基础知识(第五篇)
java·开发语言·分布式