Redis从入门到精通,至少要看看这篇!
常用的 SQL 数据库的数据都是存在磁盘中的,虽然在数据库底层也做了对应的缓存来减少数据库的 IO 压力。
图片来自 Pexels
由于数据库的缓存一般是针对查询的内容,而且粒度也比较小,一般只有表中的数据没有发生变动的时候,数据库的缓存才会产生作用。
但这并不能减少业务逻辑对数据库的增删改操作的 IO 压力,因此缓存技术应运而生,该技术实现了对热点数据的高速缓存,可以大大缓解后端数据库的压力。
主流应用架构
客户端在对数据库发起请求时,先到缓存层查看是否有所需的数据,如果缓存层存有客户端所需的数据,则直接从缓存层返回,否则进行穿透查询,对数据库进行查询。
如果在数据库中查询到该数据,则将该数据回写到缓存层,以便下次客户端再次查询能够直接从缓存层获取数据。
缓存中间件 Memcache 和 Redis 的区别
Memcache 的代码层类似 Hash,特点如下:
支持简单数据类型
不支持数据持久化存储
不支持主从
不支持分片
Redis 特点如下:
数据类型丰富
支持数据磁盘持久化存储
支持主从
支持分片
为什么 Redis 能这么快
Redis 完全基于内存,绝大部分请求是纯粹的内存操作,执行效率高。
Redis 使用单进程单线程模型的(K,V)数据库,将数据存储在内存中,存取均不会受到硬盘 IO 的限制,因此其执行速度极快。
另外单线程也能处理高并发请求,还可以避免频繁上下文切换和锁的竞争,如果想要多核运行也可以启动多个实例。
数据结构简单,对数据操作也简单,Redis 不使用表,不会强制用户对各个关系进行关联,不会有复杂的关系限制,其存储结构就是键值对,类似于 HashMap,HashMap 最大的优点就是存取的时间复杂度为 O(1)。
Redis 使用多路 I/O 复用模型,为非阻塞 IO。
因地制宜,优先选择时间复杂度为 O(1) 的 I/O 多路复用函数作为底层实现。
由于 Select 要遍历每一个 IO,所以其时间复杂度为 O(n),通常被作为保底方案。
基于 React 设计模式监听 I/O 事件。
Redis 的数据类型
String
最基本的数据类型,其值最大可存储 512M,二进制安全(Redis 的 String 可以包含任何二进制数据,包含 jpg 对象等)。
注:如果重复写入 key 相同的键值对,后写入的会将之前写入的覆盖。
Hash
String 元素组成的字典,适用于存储对象。
List
列表,按照 String 元素插入顺序排序。其顺序为后进先出。由于其具有栈的特性,所以可以实现如“最新消息排行榜”这类的功能。
Set
String 元素组成的无序集合,通过哈希表实现(增删改查时间复杂度为 O(1)),不允许重复。
Sorted Set
通过分数来为集合中的成员进行从小到大的排序。
更高级的 Redis 类型
从海量 Key 里查询出某一个固定前缀的 Key
假设 Redis 中有十亿条 Key,如何从这么多 Key 中找到固定前缀的 Key?
keys test* //返回所有以test为前缀的key
注:
cursor:游标
MATCH pattern:查询 Key 的条件
Count:返回的条数
例:
SCAN 0 MATCH test* COUNT 10 //每次返回10条以test为前缀的key
如何通过 Redis 实现分布式锁
分布式锁
互斥性:任意时刻只有一个客户端获取到锁,不能有两个客户端同时获取到锁。
安全性:锁只能被持有该锁的客户端删除,不能由其他客户端删除。
死锁:获取锁的客户端因为某些原因而宕机继而无法释放锁,其他客户端再也无法获取锁而导致死锁,此时需要有特殊机制来避免死锁。
容错:当各个节点,如某个 Redis 节点宕机的时候,客户端仍然能够获取锁或释放锁。
如何使用 Redis 实现分布式锁
使用 SETNX 实现,SETNX key value:如果 Key 不存在,则创建并赋值。
该命令时间复杂度为 O(1),如果设置成功,则返回 1,否则返回 0。
用法:
EXPIRE key seconds
程序:
RedisService redisService = SpringUtils.getBean(RedisService.class);
long status = redisService.setnx(key,"1");
if(status == 1){
redisService.expire(key,expire);
doOcuppiedWork();
}
EX second:设置键的过期时间为 Second 秒。
PX millisecond:设置键的过期时间为 MilliSecond 毫秒。
NX:只在键不存在时,才对键进行设置操作。
XX:只在键已经存在时,才对键进行设置操作。
SET KEY value [EX seconds] [PX milliseconds] [NX|XX]
有了 SET 我们就可以在程序中使用类似下面的代码实现分布式锁了:
RedisService redisService = SpringUtils.getBean(RedisService.class);
String result = redisService.set(lockKey,requestId,SET_IF_NOT_EXIST,SET_WITH_EXPIRE_TIME,expireTime);
if("OK.equals(result)"){
doOcuppiredWork();
}
如何实现异步队列
①使用 Redis 中的 List 作为队列
使用上文所说的 Redis 的数据结构中的 List 作为队列 Rpush 生产消息,LPOP 消费消息。
②使用 BLPOP key [key…] timeout
BLPOP key [key …] timeout:阻塞直到队列有消息或者超时。
③Pub/Sub:主题订阅者模式
发送者(Pub)发送消息,订阅者(Sub)接收消息。订阅者可以订阅任意数量的频道。
Redis 持久化
什么是持久化
Redis 如何做持久化
RDB(快照)持久化
RDB持久化会在某个特定的间隔保存那个时间点的全量数据的快照。
RDB 配置文件,redis.conf:
save 900 1 #在900s内如果有1条数据被写入,则产生一次快照。
save 300 10 #在300s内如果有10条数据被写入,则产生一次快照
save 60 10000 #在60s内如果有10000条数据被写入,则产生一次快照
stop-writes-on-bgsave-error yes
#stop-writes-on-bgsave-error :
如果为yes则表示,当备份进程出错的时候,
主进程就停止进行接受新的写入操作,这样是为了保护持久化的数据一致性的问题。
①RDB 的创建与载入
②自动化触发 RDB 持久化的方式
自动化触发RDB持久化的方式如下:
根据 redis.conf 配置里的 SAVE m n 定时触发(实际上使用的是 BGSAVE)。
主从复制时,主节点自动触发。
执行 Debug Reload。
执行 Shutdown 且没有开启 AOF 持久化。
③BGSAVE 的原理
检查是否存在子进程正在执行 AOF 或者 RDB 的持久化任务。如果有则返回 false。
调用 Redis 源码中的 rdbSaveBackground 方法,方法中执行 fork() 产生子进程执行 RDB 操作。
关于 fork() 中的 Copy-On-Write。
④RDB 持久化方式的缺点
RDB 持久化方式的缺点如下:
内存数据全量同步,数据量大的状况下,会由于 I/O 而严重影响性能。
可能会因为 Redis 宕机而丢失从当前至最近一次快照期间的数据。
AOF 持久化:保存写状态
AOF 记录除了查询以外的所有变更数据库状态的指令。
以增量的形式追加保存到 AOF 文件中。
开启 AOF 持久化
一般来说,操作系统考虑效率问题,会等待缓冲区被填满再将缓冲区数据写入 AOF 文件中。
appendonly yes
#appendsync always
appendfsync everysec
# appendfsync no
日志重写解决 AOF 文件不断增大
调用 fork(),创建一个子进程。
子进程把新的 AOF 写到一个临时文件里,不依赖原来的 AOF 文件。
主进程持续将新的变动同时写到内存和原来的 AOF 里。
主进程获取子进程重写 AOF 的完成信号,往新 AOF 同步增量变动。
使用新的 AOF 文件替换掉旧的 AOF 文件。
AOF 和 RDB 的优缺点
AOF 和 RDB 的优缺点如下:
RDB 优点:全量数据快照,文件小,恢复快。
RDB 缺点:无法保存最近一次快照之后的数据。
AOF 优点:可读性高,适合保存增量数据,数据不易丢失。
AOF 缺点:文件体积大,恢复时间长。
RDB-AOF 混合持久化方式
Redis 数据的恢复
RDB 和 AOF 文件共存情况下的恢复流程如下图:
Pineline
Redis 的同步机制
主从同步原理
另外,Master 和 Slave 的数据不是一定要即时同步的,但是在一段时间后 Master 和 Slave 的数据是趋于同步的,这就是最终一致性。
全同步过程如下:
Slave 发送 Sync 命令到 Master。
Master 启动一个后台进程,将 Redis 中的数据快照保存到文件中。
Master 将保存数据快照期间接收到的写命令缓存起来。
Master 完成写文件操作后,将该文件发送给 Slave。
使用新的 AOF 文件替换掉旧的 AOF 文件。
Master 将这期间收集的增量写命令发送给 Slave 端。
增量同步过程如下:
Master 接收到用户的操作指令,判断是否需要传播到 Slave。
将操作记录追加到 AOF 文件。
将操作传播到其他 Slave:对齐主从库;往响应缓存写入指令。
将缓存中的数据发送给 Slave。
Redis Sentinel(哨兵)
监控:检查主从服务器是否运行正常。
提醒:通过 API 向管理员或者其它应用程序发送故障通知。
自动故障迁移:主从切换(在 Master 宕机后,将其中一个 Slave 转为 Master,其他的 Slave 从该节点同步数据)。
Redis 集群
如何从海量数据里快速找到所需?
①分片
②一致性 Hash 算法
如果定位到的地方没有 Redis 服务器实例,则继续顺时针寻找,找到的第一台服务器即该数据最终的服务器位置。
③Hash 环的数据倾斜问题
Hash 环在服务器节点很少的时候,容易遇到服务器节点不均匀的问题,这会造成数据倾斜,数据倾斜指的是被缓存的对象大部分集中在 Redis 集群的其中一台或几台服务器上。
针对这一问题,可以引入虚拟节点解决。简单地说,就是为每一个服务器节点计算多个 Hash,每个计算结果位置都放置一个此服务器节点,称为虚拟节点,可以在服务器 IP 或者主机名后放置一个编号实现。
结语
作者:ObjectSpace
编辑:陶家龙、孙淑娟
出处:juejin.im/post/5d809a89e51d456206115ab3
精彩文章推荐: