共计 12235 个字符,预计需要花费 31 分钟才能阅读完成。
本文目录:
- Redis 是什么?
- Redis 优缺点?
- Redis 为什么这么快?
- Redis 为何抉择单线程
- Redis6.0 为何引入多线程?
- Redis 利用场景有哪些?
- Memcached 和 Redis 的区别?
- Redis 数据类型有哪些?
- keys 命令存在的问题?
- SortedSet 和 List 异同点?
- Redis 事务
-
长久化机制
- RDB 形式
- AOF 形式
- RDB 和 AOF 如何抉择?
- Redis 常见的部署形式有哪些?
- 主从复制
- 哨兵 Sentinel
-
Redis cluster
- 哈希分区算法有哪些?
- 过期键的删除策略?
- 内存淘汰策略有哪些?
- 如何保障缓存与数据库双写时的数据一致性?
- 缓存穿透
- 缓存雪崩
- 缓存击穿
- Redis 怎么实现音讯队列?
- pipeline 的作用?
- LUA 脚本
- 什么是 RedLock?
Redis 是什么?
Redis(Remote Dictionary Server
)是一个应用 C 语言编写的,高性能非关系型的键值对数据库。与传统数据库不同的是,Redis 的数据是存在内存中的,所以读写速度十分快,被广泛应用于缓存方向。Redis 能够将数据写入磁盘中,保障了数据的平安不失落,而且 Redis 的操作是原子性的。
Redis 优缺点?
长处:
- 基于内存操作,内存读写速度快。
- Redis 是 单线程 的,防止线程切换开销及多线程的竞争问题。单线程是指网络申请应用一个线程来解决,即一个线程解决所有网络申请,Redis 运行时不止有一个线程,比方数据长久化的过程会另起线程。
- 反对多种数据类型,包含 String、Hash、List、Set、ZSet 等。
- 反对长久化。Redis 反对 RDB 和 AOF 两种长久化机制,长久化性能能够无效地防止数据失落问题。
- 反对事务。Redis 的所有操作都是原子性的,同时 Redis 还反对对几个操作合并后的原子性执行。
- 反对主从复制。主节点会主动将数据同步到从节点,能够进行读写拆散。
毛病:
- 对结构化查问的反对比拟差。
- 数据库容量受到物理内存的限度,不适宜用作海量数据的高性能读写,因而 Redis 适宜的场景次要局限在较小数据量的操作。
- Redis 较难反对在线扩容,在集群容量达到下限时在线扩容会变得很简单。
Redis 为什么这么快?
- 基于内存:Redis 是应用内存存储,没有磁盘 IO 上的开销。数据存在内存中,读写速度快。
- 单线程实现(Redis 6.0 以前):Redis 应用单个线程解决申请,防止了多个线程之间线程切换和锁资源争用的开销。
- IO 多路复用模型:Redis 采纳 IO 多路复用技术。Redis 应用单线程来轮询描述符,将数据库的操作都转换成了事件,不在网络 I / O 上节约过多的工夫。
- 高效的数据结构:Redis 每种数据类型底层都做了优化,目标就是为了谋求更快的速度。
Redis 为何抉择单线程
- 防止过多的 上下文切换开销。程序始终运行在过程中单个线程内,没有多线程切换的场景。
- 防止同步机制的开销:如果 Redis 抉择多线程模型,须要思考数据同步的问题,则必然会引入某些同步机制,会导致在操作数据过程中带来更多的开销,减少程序复杂度的同时还会升高性能。
- 实现简略,不便保护:如果 Redis 应用多线程模式,那么所有的底层数据结构的设计都必须思考线程平安问题,那么 Redis 的实现将会变得更加简单。
Redis6.0 为何引入多线程?
Redis 反对多线程次要有两个起因:
- 能够充分利用服务器 CPU 资源,单线程模型的主线程只能利用一个 cpu;
- 多线程工作能够摊派 Redis 同步 IO 读写的负荷。
Redis 利用场景有哪些?
- 缓存热点数据,缓解数据库的压力。
- 利用 Redis 原子性的自增操作,能够实现 计数器 的性能,比方统计用户点赞数、用户拜访数等。
- 简略的音讯队列,能够应用 Redis 本身的公布 / 订阅模式或者 List 来实现简略的音讯队列,实现异步操作。
- 限速器,可用于限度某个用户拜访某个接口的频率,比方秒杀场景用于避免用户疾速点击带来不必要的压力。
- 好友关系,利用汇合的一些命令,比方交加、并集、差集等,实现独特好友、共同爱好之类的性能。
Memcached 和 Redis 的区别?
- Redis 只应用 单核,而 Memcached 能够应用多核。
- MemCached 数据结构繁多,仅用来缓存数据,而 Redis 反对多种数据类型。
- MemCached 不反对数据长久化,重启后数据会隐没。Redis 反对数据长久化。
- Redis 提供主从同步机制和 cluster 集群部署能力,可能提供高可用服务。Memcached 没有提供原生的集群模式,须要依附客户端实现往集群中分片写入数据。
- Redis 的速度比 Memcached 快很多。
- Redis 应用 单线程的多路 IO 复用模型,Memcached 应用多线程的非阻塞 IO 模型。
Redis 数据类型有哪些?
根本数据类型:
1、String:最罕用的一种数据类型,String 类型的值能够是字符串、数字或者二进制,但值最大不能超过 512MB。
2、Hash:Hash 是一个键值对汇合。
3、Set:无序去重的汇合。Set 提供了交加、并集等办法,对于实现独特好友、独特关注等性能特地不便。
4、List:有序可反复的汇合,底层是依赖双向链表实现的。
5、SortedSet:有序 Set。外部保护了一个 score
的参数来实现。实用于排行榜和带权重的音讯队列等场景。
非凡的数据类型:
1、Bitmap:位图,能够认为是一个以位为单位数组,数组中的每个单元只能存 0 或者 1,数组的下标在 Bitmap 中叫做偏移量。Bitmap 的长度与汇合中元素个数无关,而是与基数的下限无关。
2、Hyperloglog。HyperLogLog 是用来做基数统计的算法,其长处是,在输出元素的数量或者体积十分十分大时,计算基数所需的空间总是固定的、并且是很小的。典型的应用场景是统计独立访客。
3、Geospatial:次要用于存储地理位置信息,并对存储的信息进行操作,实用场景如定位、左近的人等。
keys 命令存在的问题?
redis 的单线程的。keys 指令会导致线程阻塞一段时间,直到执行结束,服务能力复原。scan 采纳渐进式遍历的形式来解决 keys 命令可能带来的阻塞问题,每次 scan 命令的工夫复杂度是O(1)
,然而要真正实现 keys 的性能,须要执行屡次 scan。
scan 的毛病:在 scan 的过程中如果有键的变动(减少、删除、批改),遍历过程可能会有以下问题:新增的键可能没有遍历到,遍历出了反复的键等状况,也就是说 scan 并不能保障残缺的遍历进去所有的键。
SortedSet 和 List 异同点?
相同点:
- 都是有序的;
- 都能够取得某个范畴内的元素。
不同点:
- 列表基于链表实现,获取两端元素速度快,拜访两头元素速度慢;
- 有序汇合基于散列表和跳跃表实现,拜访两头元素工夫复杂度是 OlogN;
- 列表不能简略的调整某个元素的地位,有序列表能够(更改元素的分数);
- 有序汇合更耗内存。
Redis 事务
事务的原理是将一个事务范畴内的若干命令发送给 Redis,而后再让 Redis 顺次执行这些命令。
事务的生命周期:
- 应用 MULTI 开启一个事务
- 在开启事务的时候,每次操作的命令将会被插入到一个队列中,同时这个命令并不会被真的执行
- EXEC 命令进行提交事务
一个事务范畴内某个命令出错不会影响其余命令的执行,不保障原子性:
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set a 1
QUEUED
127.0.0.1:6379> set b 1 2
QUEUED
127.0.0.1:6379> set c 3
QUEUED
127.0.0.1:6379> exec
1) OK
2) (error) ERR syntax error
3) OK
WATCH 命令
WATCH
命令能够监控一个或多个键,一旦其中有一个键被批改,之后的事务就不会执行(相似于乐观锁)。执行 EXEC
命令之后,就会主动勾销监控。
127.0.0.1:6379> watch name
OK
127.0.0.1:6379> set name 1
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set name 2
QUEUED
127.0.0.1:6379> set gender 1
QUEUED
127.0.0.1:6379> exec
(nil)
127.0.0.1:6379> get gender
(nil)
比方下面的代码中:
watch name
开启了对name
这个key
的监控- 批改
name
的值 - 开启事务 a
- 在事务 a 中设置了
name
和gender
的值 - 应用
EXEC
命令进提交事务 - 应用命令
get gender
发现不存在,即事务 a 没有执行
应用 UNWATCH
能够勾销 WATCH
命令对 key
的监控,所有监控锁将会被勾销。
长久化机制
长久化就是把 内存的数据写到磁盘中,避免服务宕机导致内存数据失落。
Redis 反对两种形式的长久化,一种是 RDB
的形式,一种是 AOF
的形式。前者会依据指定的规定定时将内存中的数据存储在硬盘上 ,而 后者在每次执行完命令后将命令记录下来。个别将两者联合应用。
RDB 形式
RDB
是 Redis 默认的长久化计划。RDB 长久化时会将内存中的数据写入到磁盘中,在指定目录下生成一个 dump.rdb
文件。Redis 重启会加载 dump.rdb
文件复原数据。
bgsave
是支流的触发 RDB 长久化的形式,执行过程如下:
- 执行
BGSAVE
命令 - Redis 父过程判断以后 是否存在正在执行的子过程 ,如果存在,
BGSAVE
命令间接返回。 - 父过程执行
fork
操作 创立子过程,fork 操作过程中父过程会阻塞。 - 父过程
fork
实现后,父过程持续接管并解决客户端的申请 ,而 子过程开始将内存中的数据写进硬盘的临时文件; - 当子过程写完所有数据后会 用该临时文件替换旧的 RDB 文件。
Redis 启动时会读取 RDB 快照文件,将数据从硬盘载入内存。通过 RDB 形式的长久化,一旦 Redis 异样退出,就会失落最近一次长久化当前更改的数据。
触发 RDB 长久化的形式:
- 手动触发 :用户执行
SAVE
或BGSAVE
命令。SAVE
命令执行快照的过程会阻塞所有客户端的申请,应防止在生产环境应用此命令。BGSAVE
命令能够在后盾异步进行快照操作,快照的同时服务器还能够持续响应客户端的申请,因而须要手动执行快照时举荐应用BGSAVE
命令。 -
被动触发:
- 依据配置规定进行主动快照,如
SAVE 100 10
,100 秒内至多有 10 个键被批改则进行快照。 - 如果从节点执行全量复制操作,主节点会主动执行
BGSAVE
生成 RDB 文件并发送给从节点。 - 默认状况下执行
shutdown
命令时,如果没有开启 AOF 长久化性能则主动执行·BGSAVE·。
- 依据配置规定进行主动快照,如
长处:
- Redis 加载 RDB 复原数据远远快于 AOF 的形式。
- 应用独自子过程来进行长久化,主过程不会进行任何 IO 操作,保障了 Redis 的高性能。
毛病:
- RDB 形式数据无奈做到实时长久化 。因为
BGSAVE
每次运行都要执行fork
操作创立子过程,属于重量级操作,频繁执行老本比拟高。 - RDB 文件应用特定二进制格局保留,Redis 版本升级过程中有多个格局的 RDB 版本,存在老版本 Redis 无奈兼容新版 RDB 格局的问题。
AOF 形式
AOF(append only file)长久化:以独立日志的形式记录每次写命令,Redis 重启时会从新执行 AOF 文件中的命令达到复原数据的目标。AOF 的次要作用是 解决了数据长久化的实时性,AOF 是 Redis 长久化的支流形式。
默认状况下 Redis 没有开启 AOF 形式的长久化,能够通过 appendonly
参数启用:appendonly yes
。开启 AOF 形式长久化后每执行一条写命令,Redis 就会将该命令写进 aof_buf
缓冲区,AOF 缓冲区依据对应的策略向硬盘做同步操作。
默认状况下零碎 每 30 秒 会执行一次同步操作。为了避免缓冲区数据失落,能够在 Redis 写入 AOF 文件后被动要求零碎将缓冲区数据同步到硬盘上。能够通过 appendfsync
参数设置同步的机会。
appendfsync always // 每次写入 aof 文件都会执行同步,最平安最慢,不倡议配置
appendfsync everysec // 既保证性能也保障平安,倡议配置
appendfsync no // 由操作系统决定何时进行同步操作
接下来看一下 AOF 长久化执行流程:
- 所有的写入命令会追加到 AOP 缓冲区中。
- AOF 缓冲区依据对应的策略向硬盘同步。
- 随着 AOF 文件越来越大,须要定期对 AOF 文件进行重写,达到压缩文件体积的目标。AOF 文件重写是把 Redis 过程内的数据转化为写命令同步到新 AOF 文件的过程。
- 当 Redis 服务器重启时,能够加载 AOF 文件进行数据恢复。
长处:
- AOF 能够更好的爱护数据不失落,能够配置 AOF 每秒执行一次
fsync
操作,如果 Redis 过程挂掉,最多失落 1 秒的数据。 - AOF 以
append-only
的模式写入,所以没有磁盘寻址的开销,写入性能十分高。
毛病:
- 对于同一份文件 AOF 文件比 RDB 数据快照要大。
- 数据恢复比较慢。
RDB 和 AOF 如何抉择?
通常来说,应该同时应用两种长久化计划,以保障数据安全。
- 如果数据不敏感,且能够从其余中央从新生成,能够敞开长久化。
- 如果数据比拟重要,且可能接受几分钟的数据失落,比方缓存等,只须要应用 RDB 即可。
- 如果是用做内存数据,要应用 Redis 的长久化,倡议是 RDB 和 AOF 都开启。
- 如果只用 AOF,优先应用 everysec 的配置抉择,因为它在可靠性和性能之间取了一个均衡。
当 RDB 与 AOF 两种形式都开启时,Redis 会优先应用 AOF 复原数据,因为 AOF 保留的文件比 RDB 文件更残缺。
Redis 常见的部署形式有哪些?
Redis 的几种常见应用形式包含:
- 单机版
- Redis 主从
- Redis Sentinel(哨兵)
- Redis Cluster
应用场景:
单机版:很少应用。存在的问题:1、内存容量无限 2、解决能力无限 3、无奈高可用。
主从模式:master 节点挂掉后,须要手动指定新的 master,可用性不高,根本不必。
哨兵模式:master 节点挂掉后,哨兵过程会被动选举新的 master,可用性高,然而每个节点存储的数据是一样的,节约内存空间。数据量不是很多,集群规模不是很大,须要主动容错容灾的时候应用。
Redis cluster:次要是针对海量数据 + 高并发 + 高可用的场景,如果是海量数据,如果你的数据量很大,那么倡议就用 Redis cluster,所有主节点的容量总和就是 Redis cluster 可缓存的数据容量。
主从复制
Redis 的复制性能是反对多个数据库之间的数据同步。主数据库能够进行读写操作,当主数据库的数据发生变化时会主动将数据同步到从数据库。从数据库个别是只读的,它会接管主数据库同步过去的数据。一个主数据库能够有多个从数据库,而一个从数据库只能有一个主数据库。
redis-server // 启动 Redis 实例作为主数据库
redis-server --port 6380 --slaveof 127.0.0.1 6379 // 启动另一个实例作为从数据库
slaveof 127.0.0.1 6379
SLAVEOF NO ONE // 进行接管其余数据库的同步并转化为主数据库。
主从复制的原理?
- 当启动一个从节点时,它会发送一个
PSYNC
命令给主节点; - 如果是从节点首次连贯到主节点,那么会触发一次全量复制。此时主节点会启动一个后盾线程,开始生成一份
RDB
快照文件; - 同时还会将从客户端 client 新收到的所有写命令缓存在内存中。
RDB
文件生成结束后,主节点会将RDB
文件发送给从节点,从节点会先将RDB
文件 写入本地磁盘,而后再从本地磁盘加载到内存中; - 接着主节点会将内存中缓存的写命令发送到从节点,从节点同步这些数据;
- 如果从节点跟主节点之间网络呈现故障,连贯断开了,会主动重连,连贯之后主节点仅会将局部缺失的数据同步给从节点。
哨兵 Sentinel
主从复制存在不能主动故障转移、达不到高可用的问题。哨兵模式解决了这些问题。通过哨兵机制能够主动切换主从节点。
客户端连贯 Redis 的时候,先连贯哨兵,哨兵会通知客户端 Redis 主节点的地址,而后客户端连贯上 Redis 并进行后续的操作。当主节点宕机的时候,哨兵监测到主节点宕机,会从新推选出某个体现良好的从节点成为新的主节点,而后通过公布订阅模式告诉其余的从服务器,让它们切换主机。
工作原理
- 每个
Sentinel
以每秒钟一次的频率向它所晓得的Master
,Slave
以及其余Sentinel
实例发送一个PING
命令。 - 如果一个实例间隔最初一次无效回复
PING
命令的工夫超过指定值,则这个实例会被Sentine
标记为主观下线。 - 如果一个
Master
被标记为主观下线,则正在监督这个Master
的所有Sentinel
要以每秒一次的频率确认Master
是否真正进入主观下线状态。 - 当有足够数量的
Sentinel
(大于等于配置文件指定值)在指定的工夫范畴内确认Master
确实进入了主观下线状态,则Master
会被标记为主观下线。若没有足够数量的Sentinel
批准Master
曾经下线,Master
的主观下线状态就会被解除。若Master
从新向Sentinel
的PING
命令返回无效回复,Master
的主观下线状态就会被移除。 - 哨兵节点会选举出哨兵 leader,负责故障转移的工作。
- 哨兵 leader 会推选出某个体现良好的从节点成为新的主节点,而后告诉其余从节点更新主节点信息。
Redis cluster
哨兵模式解决了主从复制不能主动故障转移、达不到高可用的问题,但还是存在主节点的写能力、容量受限于单机配置的问题。而 cluster 模式实现了 Redis 的分布式存储,每个节点存储不同的内容,解决主节点的写能力、容量受限于单机配置的问题。
Redis cluster 集群节点最小配置 6 个节点以上(3 主 3 从),其中主节点提供读写操作,从节点作为备用节点,不提供申请,只作为故障转移应用。
Redis cluster 采纳 虚构槽分区,所有的键依据哈希函数映射到 0~16383 个整数槽内,每个节点负责保护一部分槽以及槽所映射的键值数据。
哈希槽是如何映射到 Redis 实例上的?
- 对键值对的
key
应用crc16
算法计算一个后果 - 将后果对 16384 取余,失去的值示意
key
对应的哈希槽 - 依据该槽信息定位到对应的实例
长处:
- 无核心架构,反对动静扩 容;
- 数据依照
slot
存储散布在多个节点,节点间数据共享,可动静调整数据分布; - 高可用性 。局部节点不可用时,集群仍可用。集群模式可能实现主动故障转移(failover),节点之间通过
gossip
协定替换状态信息,用投票机制实现Slave
到Master
的角色转换。
毛病:
- 不反对批量操作(pipeline)。
- 数据通过异步复制,不保证数据的强一致性。
- 事务操作反对无限 ,只反对多
key
在同一节点上的事务操作,当多个key
散布于不同的节点上时无奈应用事务性能。 key
作为数据分区的最小粒度,不能将一个很大的键值对象如hash
、list
等映射到不同的节点。- 不反对多数据库空间,单机下的 Redis 能够反对到 16 个数据库,集群模式下只能应用 1 个数据库空间。
哈希分区算法有哪些?
节点取余分区。应用特定的数据,如 Redis 的键或用户 ID,对节点数量 N 取余:hash(key)%N 计算出哈希值,用来决定数据映射到哪一个节点上。
长处是简略性。扩容时通常采纳翻倍扩容,防止数据映射全副被打乱导致全量迁徙的状况。
一致性哈希分区。为零碎中每个节点调配一个 token,范畴个别在 0~232,这些 token 形成一个哈希环。数据读写执行节点查找操作时,先依据 key 计算 hash 值,而后顺时针找到第一个大于等于该哈希值的 token 节点。
这种形式相比节点取余最大的益处在于退出和删除节点只影响哈希环中相邻的节点,对其余节点无影响。
虚构槽分区,所有的键依据哈希函数映射到 0~16383 整数槽内,计算公式:slot=CRC16(key)&16383。每一个节点负责保护一部分槽以及槽所映射的键值数据。Redis Cluser 采纳虚构槽分区算法。
过期键的删除策略?
1、被动删除。在拜访 key 时,如果发现 key 曾经过期,那么会将 key 删除。
2、被动删除。定时清理 key,每次清理会顺次遍历所有 DB,从 db 随机取出 20 个 key,如果过期就删除,如果其中有 5 个 key 过期,那么就持续对这个 db 进行清理,否则开始清理下一个 db。
3、内存不够时清理。Redis 有最大内存的限度,通过 maxmemory 参数能够设置最大内存,当应用的内存超过了设置的最大内存,就要进行内存开释,在进行内存开释的时候,会依照配置的淘汰策略清理内存。
内存淘汰策略有哪些?
当 Redis 的内存超过最大容许的内存之后,Redis 会触发内存淘汰策略,删除一些不罕用的数据,以保障 Redis 服务器失常运行。
Redisv4.0 前提供 6 种数据淘汰策略:
- volatile-lru:LRU(
Least Recently Used
),最近应用。利用 LRU 算法移除设置了过期工夫的 key - allkeys-lru:当内存不足以包容新写入数据时,从数据集中移除最近起码应用的 key
- volatile-ttl:从已设置过期工夫的数据集中筛选将要过期的数据淘汰
- volatile-random:从已设置过期工夫的数据集中任意抉择数据淘汰
- allkeys-random:从数据集中任意抉择数据淘汰
- no-eviction:禁止删除数据,当内存不足以包容新写入数据时,新写入操作会报错
Redisv4.0 后减少以下两种:
- volatile-lfu:LFU,Least Frequently Used,起码应用,从已设置过期工夫的数据集中筛选最不常常应用的数据淘汰。
- allkeys-lfu:当内存不足以包容新写入数据时,从数据集中移除最不常常应用的 key。
内存淘汰策略能够通过配置文件来批改,相应的配置项是maxmemory-policy
,默认配置是noeviction
。
如何保障缓存与数据库双写时的数据一致性?
1、先删除缓存再更新数据库
进行更新操作时,先删除缓存,而后更新数据库,后续的申请再次读取时,会从数据库读取后再将新数据更新到缓存。
存在的问题:删除缓存数据之后,更新数据库实现之前,这个时间段内如果有新的读申请过去,就会从数据库读取旧数据从新写到缓存中,再次造成不统一,并且后续读的都是旧数据。
2、先更新数据库再删除缓存
进行更新操作时,先更新 MySQL,胜利之后,删除缓存,后续读取申请时再将新数据回写缓存。
存在的问题:更新 MySQL 和删除缓存这段时间内,申请读取的还是缓存的旧数据,不过等数据库更新实现,就会复原统一,影响绝对比拟小。
3、异步更新缓存
数据库的更新操作实现后不间接操作缓存,而是把这个操作命令封装成音讯扔到音讯队列中,而后由 Redis 本人去生产更新数据,音讯队列能够保证数据操作程序一致性,确保缓存零碎的数据失常。
缓存穿透
缓存穿透是指查问一个 不存在的数据,因为缓存是不命中时被动写的,如果从 DB 查不到数据则不写入缓存,这将导致这个不存在的数据每次申请都要到 DB 去查问,失去了缓存的意义。在流量大时,可能 DB 就挂掉了。
- 缓存空值,不会查数据库。
- 采纳 布隆过滤器 ,将所有可能存在的数据哈希到一个足够大的
bitmap
中,查问不存在的数据会被这个bitmap
拦挡掉,从而防止了对DB
的查问压力。
布隆过滤器的原理:当一个元素被退出汇合时,通过 K 个散列函数将这个元素映射成一个位数组中的 K 个点,把它们置为 1。查问时,将元素通过散列函数映射之后会失去 k 个点,如果这些点有任何一个 0,则被检元素肯定不在,间接返回;如果都是 1,则查问元素很可能存在,就会去查问 Redis 和数据库。
缓存雪崩
缓存雪崩是指在咱们设置缓存时采纳了雷同的过期工夫,导致缓存在某一时刻同时生效,申请全副转发到 DB,DB 刹时压力过重挂掉。
解决办法:在原有的生效工夫根底上 减少一个随机值,使得过期工夫扩散一些。
缓存击穿
缓存击穿:大量的申请同时查问一个 key 时,此时这个 key 正好生效了,就会导致大量的申请都落到数据库。缓存击穿是查问缓存中生效的 key,而缓存穿透是查问不存在的 key。
解决办法:加分布式锁,第一个申请的线程能够拿到锁,拿到锁的线程查问到了数据之后设置缓存,其余的线程获取锁失败会期待 50ms 而后从新到缓存取数据,这样便能够防止大量的申请落到数据库。
public String get(String key) {String value = redis.get(key);
if (value == null) { // 缓存值过期
String unique_key = systemId + ":" + key;
// 设置 30s 的超时
if (redis.set(unique_key, 1, 'NX', 'PX', 30000) == 1) { // 设置胜利
value = db.get(key);
redis.set(key, value, expire_secs);
redis.del(unique_key);
} else { // 其余线程曾经到数据库取值并回写到缓存了,能够重试获取缓存值
sleep(50);
get(key); // 重试
}
} else {return value;}
}
Redis 怎么实现音讯队列?
应用一个列表,让生产者将工作应用 LPUSH 命令放进列表,消费者一直用 RPOP 从列表取出工作。
BRPOP 和 RPOP 命令类似,惟一的区别就是当列表没有元素时 BRPOP 命令会始终阻塞连贯,直到有新元素退出。
BRPOP queue 0 // 0 示意不限度等待时间
优先级队列
如果多个键都有元素,则依照从左到右的程序取元素。
BLPOP queue:1 queue:2 queue:3 0
公布 / 订阅模式
PSUBSCRIBE channel?*
依照规定订阅。PUNSUBSCRIBE channel?*
退订通过 PSUBSCRIBE 命令依照某种规定订阅的频道。其中订阅规定要进行严格的字符串匹配,PUNSUBSCRIBE *
无奈退订 channel?*
规定。
PUBLISH channel1 hi
SUBSCRIBE channel1
UNSUBSCRIBE channel1 // 退订通过 SUBSCRIBE 命令订阅的频道。
毛病:在消费者下线的状况下,生产的音讯会失落。
延时队列
应用 sortedset,拿工夫戳作为 score,音讯内容作为 key,调用 zadd 来生产音讯,消费者用 zrangebyscore
指令获取 N 秒之前的数据轮询进行解决。
pipeline 的作用?
redis 客户端执行一条命令分 4 个过程:发送命令、命令排队、命令执行、返回后果。应用 pipeline
能够批量申请,批量返回后果,执行速度比逐条执行要快。
应用 pipeline
组装的命令个数不能太多,不然数据量过大,减少客户端的等待时间,还可能造成网络阻塞,能够将大量命令的拆分多个小的 pipeline
命令实现。
原生批命令(mset 和 mget)与 pipeline
比照:
- 原生批命令是原子性,
pipeline
是 非原子性 。pipeline 命令中途异样退出,之前执行胜利的命令 不会回滚。 - 原生批命令只有一个命令,但
pipeline
反对多命令。
LUA 脚本
Redis 通过 LUA 脚本创立具备原子性的命令:当 lua 脚本命令正在运行的时候,不会有其余脚本或 Redis 命令被执行,实现组合命令的原子操作。
在 Redis 中执行 Lua 脚本有两种办法:eval
和 evalsha
。eval
命令应用内置的 Lua 解释器,对 Lua 脚本进行求值。
// 第一个参数是 lua 脚本,第二个参数是键名参数个数,剩下的是键名参数和附加参数
> eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 first second
1) "key1"
2) "key2"
3) "first"
4) "second"
lua 脚本作用
1、Lua 脚本在 Redis 中是原子执行的,执行过程两头不会插入其余命令。
2、Lua 脚本能够将多条命令一次性打包,无效地缩小网络开销。
利用场景
举例:限度接口拜访频率。
在 Redis 保护一个接口拜访次数的键值对,key
是接口名称,value
是拜访次数。每次拜访接口时,会执行以下操作:
- 通过
aop
拦挡接口的申请,对接口申请进行计数,每次进来一个申请,相应的接口拜访次数count
加 1,存入 redis。 - 如果是第一次申请,则会设置
count=1
,并设置过期工夫。因为这里set()
和expire()
组合操作不是原子操作,所以引入lua
脚本,实现原子操作,防止并发拜访问题。 - 如果给定工夫范畴内超过最大拜访次数,则会抛出异样。
private String buildLuaScript() {
return "local c" +
"\nc = redis.call('get',KEYS[1])" +
"\nif c and tonumber(c) > tonumber(ARGV[1]) then" +
"\nreturn c;" +
"\nend" +
"\nc = redis.call('incr',KEYS[1])" +
"\nif tonumber(c) == 1 then" +
"\nredis.call('expire',KEYS[1],ARGV[2])" +
"\nend" +
"\nreturn c;";
}
String luaScript = buildLuaScript();
RedisScript<Number> redisScript = new DefaultRedisScript<>(luaScript, Number.class);
Number count = redisTemplate.execute(redisScript, keys, limit.count(), limit.period());
PS:这种接口限流的实现形式比较简单,问题也比拟多,个别不会应用,接口限流用的比拟多的是令牌桶算法和漏桶算法。
什么是 RedLock?
Redis 官网站提出了一种权威的基于 Redis 实现分布式锁的形式名叫 Redlock,此种形式比原先的单节点的办法更平安。它能够保障以下个性:
- 平安个性:互斥拜访,即永远只有一个 client 能拿到锁
- 防止死锁:最终 client 都可能拿到锁,不会呈现死锁的状况,即便本来锁住某资源的 client 挂掉了
- 容错性:只有大部分 Redis 节点存活就能够失常提供服务
文章对你有用的话,点个赞,反对一下~
我是大彬,非科班转码,校招拿了多家互联网中大厂 offer,专一分享 Java 技术干货,欢送关注~