redis
Redis
官方文档:https://redis.io/docs/latest/develop/get-started/
Redis 可作为缓存、非关系数据库、流处理引擎、消息代理等多种用途中间件
中间件:后端认为,所有的docker run 能跑起来为我们服务的软件都可以叫中间件
中间件:特指数据库[存数据的仓库];(MySQL、Redis、MongoDB、ElasticSearch、RabbitMQ、Minio)
缓存:是一种机制;加速系统数据访问;提前把数据放到离自己最近的地方
- **
MySQL 也有缓存**;- mysql为了查数据快,查过的数据可以放到内存中 buffer pool,以后再用,不用查,去内存拿
- **
Docker Build 也有缓存**;公共的镜像层,之前下载了,拿来直接用,不用重新下载- 放到磁盘文件
- **
CPU 也有缓存**;CPU 执行指令的时候,本来找内存要数据,但是太慢了,消息总线把数据提前放到CPU的缓存中,CPU直接用,不用找内存要- 跟CPU焊在一起的寄存器
快递也有缓存:我们不能去商家那里提货吧,太远了。快递员把快递小区楼下,直接去驿站取;
为什么要用Redis;参考session不一致问题
session 不一致问题
解决集群架构下的服务器session不一致问题(也就是内存中的数据不能同步);常见三种解决方案
- 内存同步方案;(配置Tomcat集群之间同步内存,这种解决不了内存容量超出的问题)
- Tomcat 集群配置
- 统一缓存方案;(Tomcat中所有Session的数据保存在缓存服务器中,而不要保存在自己内存;这种常用)
- Spring Session 框架
- iphash负载均衡方案;(只要是同一个客户端的ip,都访问到同一个服务器,这种解决不了服务器故障问题)
- Nginx 负载均衡策略设置
入门
安装
Docker 快速安装 |
compose.yaml 安装
services: |
原始安装参考;
https://redis.io/docs/latest/operate/oss_and_stack/install/install-stack/rpm/
客户端
第三方:Another Redis Desktop Manager:https://goanother.com/cn/
下载地址:https://gitee.com/qishibo/AnotherRedisDesktopManager/releases
基本概念
命令行
redis大多数的操作都可以由命令行实现。包括了数据的增删改查、服务器的安全、配置等。
每个命令都由一个唯一名称标识。相关的命令组往往遵循一致的命名规范。例如,所有处理哈希的命令都以 H 前缀开头。大多数命令接收一个或多个参数来指定要操作的数据。对于数据类型命令,第一个参数通常是标识目标数据对象的key。
发出命令后,服务器会尝试处理并返回响应。
- 更新数据(增删改)的命令通常返回状态消息(如
OK)或表示更改/更新项目数量的数字。 - 检索数据的命令则返回请求的数据。
- 执行失败的命令会返回描述问题的错误信息。
进入命令行终端 |
批量命令
虽然您可以逐个发送 Redis 命令,但将一系列相关命令批量处理成管道(pipelining)通常更为高效。管道将多个命令以单次通信形式发送至服务器,并以同样方式接收响应。有关该技术的完整说明请参阅《管道技术》章节,客户端库的管道使用示例也可参考相关文档。
Redis 2.6 之后支持 **脚本(script)**,能批量执行命令,所以管道技术就基本没用了。后面讲脚本
基本操作
Key 和 Value
存储在 Redis 数据库中的每个数据对象都有其唯一的key。
key是一个字符串,通过将其传递给 Redis 命令可以检索对应的对象或修改其数据。
与特定**key相关联的数据对象称为value,两者合称为键值对****(key-value pair)**。
key通常是数据模型中具有特定含义的文本名称。与编程语言中的变量名不同,Redis 对key的格式几乎没有限制,因此包含空格或标点符号的键大多也是有效的(例如”1st Attempt”或”% of price in $”)。
- Redis 不支持键的命名空间或其他分类机制,因此必须注意避免名称冲突。
- 不过存在一种约定俗成的做法,即使用冒号**
":"**将键划分为多个部分; - (例如**
"person:1"、"person:2"、"office:London"、"office:NewYork:1"**)。可以通过这种方式简单地将键归类组合。
尽管key通常是文本形式,但 Redis 实际上实现了**
二进制安全的键,因此您可以使用任何字节序列作为有效键**,例如 JPEG 文件或应用程序中的结构体值。空字符串在 Redis 中同样是一个有效的键。
关于键名还有几点需要注意:
- **
过长的键名并非明智之选。例如,1024 字节的键名不仅在内存方面存在问题,还因为数据集中的键查找可能需要进行多次耗时的键值比较。即使当前任务是匹配大值的存在性,对其进行哈希处理(例如使用 SHA1)是更优方案,尤其从内存和带宽**的角度考量。 - **
过短的键名通常不是个好主意。与其用”u1000flw”作为键名,不如使用”user:1000:followers”——后者更具可读性,且相比键对象和值对象本身占用的空间,额外增加的字符空间微不足道。虽然短键名确实能节省少量内存,但关键在于找到合适的平衡点**。 - 尽量遵循固定格式。例如采用**
"对象类型:ID"**的形式就不错,比如”user:1000”。多词字段通常使用点号或连字符,例如”comment:4321:reply.to”或”comment:4321:reply-to”。 - 允许的最大键大小为 512 MB
Redis中的单个字符串最多 512 MB
Keyspace 操作
exists:检查key是否存在
type:检查key存储的对应的值的类型
del:删除key
set mykey hello |
Key 过期
键过期是Redis 的一个重要特性;无论存储何种类型的值都适用;
- 键过期允许您为键设置超时时间,也称为**”time to live”或“TTL”**。
- 当生存时间到期时,键会自动被销毁。
注意:
- key的过期时间是以毫秒为单位
- Redis 会保存键的过期日期,这样即使Redis关机或者数据持久化到磁盘,也能完成自动销毁功能
设置过期时间可以使用以下命令
expire 命令
> set key some-value |
set 命令
set key 100 ex 10 |
keys:所有key
使用 keys 命令,可以获取到redis中保存的所有key
批量设置多个 kv |
Key 模式匹配
KEYS pattern:其中 pattern可以写如下规则:
h?llo匹配hello,hallo和hxlloh*llo匹配hllo和heeeelloh[ae]llo匹配hello和hallo,但不匹配hilloh[^e]llo匹配hallo,hbllo, …但不匹配helloh[a-b]llo匹配hallo和hbllo
警告:KEYS 仅用在开发环境。大型数据库执行此命令可能会严重影响性能。切勿在常规应用程序代码中使用 KEYS 。若需在键空间子集中查找键,建议改用 SCAN 或 sets。
数据类型
官网:****https://redis.io/docs/latest/develop/data-types/
Redis 是一个 key - value 存储的数据库,key都是string的,但是value可以支持很多种
- 五大基本(必会):
- String(字符串)、Hash(哈希)、List(列表)、Set(集合)、Sorted set(有序集合)
- 八大高阶(了解):
- Vector set(向量集)、Stream(流)、Bitmap(位图)、Bitfield(位属性)
- Geospatial(地理空间)、JSON(json文档)
- Probabilistic data types(概率数据类型)、Time series(时序)
string:字符串
Redis 字符串是最基础的 Redis 数据类型,表示一个字节序列。
注意:Redis的key总是字符串类型的,value也可以是字符串。但是底层要求
任何字符串不能超过512MB
Redis 字符串用于存储字节序列,包括文本、序列化对象和二进制数组。因此,字符串是可与 Redis 键关联的最简单的值类型。它们常被用于缓存,但也支持额外功能,允许您实现计数器并执行位运算。
set:保存kv
set key value |
SET bike:1 Deimos |
set nx|xx
set ****nx:如果不存在这个key,再设置这个key;nx(not exist)
**set xx**:如果存在这个key,再设置这个key;
set bike:1 bike nx |
mset:批量设置
mset key1 value1 key2 value2 ****key3 value3
mset bike:1 "Deimos" bike:2 "Ares" bike:3 "Vanth" |
counters:计数器
Redis支持原子增操作(无惧大并发,也能增正确);比i++靠谱多了
incr key:给key的值原子+1
incrby key 10:给key的值原子+10
其他类似命令:DECR 和 DECRBY; 原子减
set total_crashes 0 |
list:列表
Redis 列表是由字符串值组成的链表结构。经常用来做 栈 或 **队列**的 数据操作;
基本命令:
LPUSH向列表头部添加新元素;RPUSH向尾部添加。LPOP从列表头部移除并返回一个元素;RPOP从列表尾部移除并返回一个元素。LLEN返回列表的长度LMOVE以原子操作方式将元素从一个列表移动到另一个列表LRANGE从列表中提取一系列元素LTRIM将列表缩减至指定范围的元素
列表支持多种阻塞式命令:
BLPOP从列表头部移除并返回一个元素。如果列表为空,该命令会阻塞直到有元素可用或达到指定的超时时间。BLMOVE以原子操作方式将元素从源列表移动到目标列表。如果源列表为空,该命令将阻塞直到有新元素可用
队列(先进先出 FIFO)
列表视为队列,模拟队列操作;
队列:一端放另一端拿;在两端操作
LPUSH person zhangsan |
栈(先进后出 FILO)
列表视为栈,模拟栈操作
栈:一端放还在这端拿;在一端操作
LPUSH ball 足球 |
其他操作
LRANGE:返回指定范围数据
LRANGE key start stop: 从指定list中返回指定范围数据
- 偏移量
start和stop是从零开始的索引- 其中
0表示列表的第一个元素(列表头部),1表示下一个元素
- 其中
- 偏移量也可以是负数,表示从列表末尾开始计算的偏移。
-1表示列表的最后一个元素,-2表示倒数第二个元素,以此类推。
redis> RPUSH mylist "one" |
LMOVE:移动
原子化地从列表中弹出一个元素并推入另一个列表
LPUSH list1 p1 |
LTRIM:列表修剪
对现有列表进行修剪,使其仅包含指定的元素范围。 start 和 stop 均为从零开始的索引,其中 0 表示列表的第一个元素(头部), 1 为下一个元素,依此类推(和LRANGE规则完全相同);
例如: LTRIM foobar 0 2 将修改存储在 foobar 的列表,使得仅保留列表的前三个元素。
RPUSH mylist "one" |
set:无序集合
Redis Set 是一种无序且元素不重复(唯一)字符串集合。Redis还支持集合的**交集、并集、差集**运算
基本命令:
SADD向集合中添加一个新成员。SREM从集合中移除指定成员SISMEMBER测试字符串是否为集合成员SINTER返回两个或多个集合共有的成员集合(即交集)。SCARD返回集合的大小(即基数)
更多命令:https://redis.io/docs/latest/commands/?group=set
SADD:添加元素
SADD svip zhangsan |
SISMEMBER:判断是否是成员
SISMEMBER svip zhaoliu |
SMEMBERS:查看所有成员
大多数集合操作(包括添加、删除和检查元素是否属于集合)的时间复杂度都是 O(1),这意味着它们非常高效。但对于包含数十万甚至更多元素的大型集合,运行 SMEMBERS 命令时需谨慎,因为该命令的时间复杂度为 O(n),会一次性返回整个集合。作为替代方案,可以考虑使用 SSCAN 命令,它能以迭代方式获取集合中的所有元素。(后面讲解SCAN命令)
SMEMBERS svip |
随机返回一个成员
SRANDMEMBER svip |
SCARD:集合大小
SCARD svip |
SDIFF:差集
SDIFF svip vip |
SINTER:交集
SINTER svip vip |
SUNION:并集
SUNION svip vip |
限制
Redis 集合的最大容量为 2^32 - 1 个成员(即 4,294,967,295 个)。
sorted set:有序集合
https://redis.io/docs/latest/develop/data-types/sorted-sets
Redis 有序集合是一种由关联分数排序的唯一字符串(成员)集合。
zset常用场景:排行榜、限流器
虽然集合中的元素是无序的,但有序集合中的每个元素都与一个浮点数值相关联,这个数值称为分数。
有序集合中的元素是按顺序排列的:
- 若元素 B 与 A 的分数不同,当
A.score大于B.score时,则 A > B。 - 如果 B 和 A 的分数完全相同,那么当 A 字符串在字典序上大于 B 字符串时,A > B。
- 由于有序集合中的元素具有唯一性,B 和 A 的字符串不可能相等。
ZADD:添加元素
> ZADD racer_scores 10 "Norem" |
ZADD 支持一系列选项,这些选项需在键名之后、首个分数参数之前指定(了解)。
XX: 仅更新已存在的元素,不添加新元素。NX:仅添加新元素。不更新已存在的元素。LT:仅当新分数小于当前分数时更新现有元素。此标志不会阻止添加新元素。GT:仅当新分数大于当前分数时更新现有元素。此标志不会阻止添加新元素。CH:将返回值从新增元素数量修改为变更元素总数(CH 是 changed 的缩写)。变更元素包括新增元素和已存在但分数被更新的元素。因此命令行中指定且分数与过去相同的元素不计入。注意:通常ZADD的返回值仅计算新增元素数量。INCR:指定此选项时,ZADD的行为类似于ZINCRBY。在此模式下只能指定一个分数-元素对。
注意:GT、LT 和 NX 选项互斥。
ZRANGE:按顺序查看
> ZRANGE racer_scores 0 -1 |
扩展用法:
获取得分不超过10分的
ZRANGE racer_scores -inf 10 BYSCORE WITHSCORES |
ZRANK:排名
返回存储在 key 的有序集合中 member 的排名,分数按从低到高排序。排名(或索引)从 0 开始,这意味着分数最低的成员排名为 0 。可选的 WITHSCORE 参数会在命令返回元素时附带其分数值。
使用 ZREVRANK 获取元素在按分数从高到低排序时的排名。
ZADD myzset 1 "one" |
hash:哈希
Redis 哈希结构为**键-值对**的集合;也就是Redis的Value值也可以像是Java里面的HashMap,有KV。
基本命令
HSET:设置哈希中一个或多个字段的值。HGET:返回给定字段的值HGETALL:返回所有字段及值HMGET:返回一个或多个给定字段的值HINCRBY:将指定字段的值按提供的整数递增
> HSET bike:1 model Deimos brand Ergonom type 'Enduro bikes' price 4972 |
字段过期
Redis 开源版 7.4 新增功能:可为单个哈希字段设置过期时间或生存时间(TTL)值。该功能与键过期机制类似,并包含一系列相似命令。
使用以下命令为特定字段设置精确过期时间或 TTL 值:
HEXPIRE:设置剩余的 TTL(生存时间),单位为秒。HPEXPIRE:设置剩余的 TTL(毫秒)HEXPIREAT:将过期时间设置为指定的秒级时间戳。HPEXPIREAT: 将过期时间设置为以毫秒为单位的时间戳
使用以下命令可*获取***特定字段过期时的确切时间或剩余生存时间(TTL)**:
HEXPIRETIME: 以秒为单位的时间戳形式获取过期时间。HPEXPIRETIME:获取以毫秒为单位的过期时间戳。HTTL:获取剩余的 TTL(秒数)HPTTL: 获取剩余的 TTL(毫秒)
使用以下命令移除****特定字段的过期时间:
HPERSIST: 移除过期时间
特别注意
性能:
- 大多数 Redis 哈希命令的时间复杂度为 O(1)。
- 少数命令,如
HKEYS、HVALS、HGETALL以及大多数与过期相关的命令,其时间复杂度为 O(n),其中 n 表示字段-值对的数量。
限制:
数字、字符串:最大 512MB 容量范围
- 每个哈希最多可存储 4,294,967,295(2^32 - 1)个字段-值对。实际上,哈希的大小仅受限于托管 Redis 部署的虚拟机总内存容量。
扩展
其他操作
SCAN:大数据量下的高性能遍历
语法:scan cursor [MATCH pattern] [COUNT count] [TYPE type]
- cursor:游标值(遍历开始的索引值); 可以从 0 开始
MATCH pattern:匹配模式;参考 2.4.5 章节内容[COUNT count]:数量,默认 10;- 特别注意:
COUNT参数是一个提示,而不是一个严格的限制。这意味着 Redis 可能会返回比指定数量更多的键,特别是在键的总数较少或键的分布不均匀的情况下
- 特别注意:
[TYPE type]:限定只扫描哪种类型数据;仅适用于整个数据库的SCAN,而不适用于HSCAN或ZSCAN等操作;Type可取值如下string、list、set、zset、hash;stream、json、geo、…
实验:
先保存所有 key |
实验1:最简单scan;
注意:scan 会返回一个新游标位置,以及当前所有元素,下次接着新的位置开始scan就行
127.0.0.1:6379> scan 0 |
实验2:scan 指定 count 数量;
127.0.0.1:6379> scan 0 count 3 |
当游标为 17 时,SCAN 命令返回了四个键;这里发生了以下情况:
- 由于
COUNT参数设置为 3,您期望 Redis 返回最多 3 个键。 - 但是,由于键的总数已经接近迭代结束,或者 Redis 内部的数据结构布局导致它更容易返回这四个键,因此 Redis 返回了四个键。
- 这不是一个错误,而是
SCAN命令的预期行为之一。COUNT参数只是一个建议,Redis 可能会根据内部情况调整返回的键数。
实验3:scan 完整命令;
127.0.0.1:6379> scan 0 match * count 3 type string |
SCAN 相关命令
SCAN 命令及与之密切相关的 SSCAN 、 HSCAN 和 ZSCAN 命令用于对元素集合进行增量迭代;
SCAN遍历当前所选 Redis 数据库中的键集合SSCAN遍历 set 类型中的元素HSCAN遍历 hash 类型的字段及其关联值。ZSCAN遍历 zset 类型的元素及其关联分数
由于这些命令支持增量迭代,每次调用仅返回少量元素,因此可在生产环境中使用,而不会像 KEYS 或 SMEMBERS 这类命令那样存在弊端——当针对大型键集合或元素集合调用时,可能导致服务器长时间阻塞(甚至数秒)。虽然像 SMEMBERS 这样的阻塞命令能够提供给定时刻集合中的所有元素,但 SCAN 系列命令仅对返回元素提供有限保证,因为我们逐步迭代的集合在迭代过程中可能会发生变化
总结:SCAN命令,类似数据库分页,性能高。但是scan的过程中如果有新增元素,有可能会产生不稳定返回
实验1:**SSCAN**:set集合的scan,游标方式获取set集合中的所有元素值
添加测试数据 |
实验2:HSCAN :hash的scan,遍历hash的所有元素,可以只获取key,也可以获取key和value
添加测试数据 |
实验3:**zscan**:zset 的 scan;
添加数据 |
扩展类型
geospatial:地理空间
地图类软件用的很多。
Redis 的 Geospatial 类型允许存储地理位置信息,并可以基于这些位置执行各种地理相关的操作,如计算两点之间的距离、查找给定范围内的位置等。下面将详细介绍 Redis Geospatial 类型的使用方法。
GEOADD:添加地理位置
要使用 Redis 的 Geospatial 类型,首先需要添加地理位置信息。这可以通过 GEOADD 命令来完成
GEOADD key longitude latitude member [longitude latitude member ...] |
key是用于存储地理位置的键。longitude和latitude分别表示经度和纬度。member是与这个地理位置关联的成员名称。
GEOADD locations 13.361389 38.115556 "Palermo" 15.087269 37.502669 "Catania" |
GEOPOS:获取地理位置
postion
GEOPOS locations "Palermo" "Catania" |
GEODIST:计算距离
DIST:distance
用法 |
unit是可选参数,用于指定返回距离的单位,可以是m(米)、km(千米)、mi(英里)或ft(英尺)。
GEORADIUS:获取范围内的位置
RADIUS |
GEORADIUS 和 GEORADIUSBYMEMBER 命令用于查找指定范围内的地理位置。
127.0.0.1:6379> GEORADIUS locations 15 37 200 km WITHDIST |
返回距离 (15, 37) 200 千米范围内的所有地理位置及其距离。
JSON:json文档
替换值
redis> JSON.SET doc $ '{"a":2}' |
新增值
redis> JSON.SET doc $ '{"a":2}' |
更新多路径
JSON.SET doc $ '{"f1": {"a":1}, "f2":{"a":2}}' |
Pub/Sub
Redis 的 Pub/Sub(发布/订阅)是一种消息传递模式,允许发送者(发布者)发送消息,订阅者接收消息。这种模式常用于实现实时通知、聊天室等功能。
聊天: 点对点(p2p); 群聊(群[频道])
SUBSCRIBE 、UNSUBSCRIBE 和 PUBLISH 实现了发布/订阅消息传递范式。
- 发布的消息会被归类到频道中,而无需了解可能存在哪些(如果有的话)订阅者。
- 订阅者对一个或多个频道表示兴趣,并且只接收感兴趣的消息,而无需了解存在哪些(如果有的话)发布者。
- 这种发布者和订阅者的解耦允许更大的可扩展性和更动态的网络拓扑结构。
发布消息
使用 PUBLISH 命令可以发布消息到指定的频道。
语法:PUBLISH channel message
channel: 频道名message: 消息内容
PUBLISH news "Hello, this is a news message!" |
订阅频道
使用 SUBSCRIBE 命令可以订阅一个或多个频道;
语法:SUBSCRIBE channel [channel ...]
channel:频道名
SUBSCRIBE news |
取消订阅
使用 UNSUBSCRIBE 命令可以取消订阅一个或多个频道。
用法:UNSUBSCRIBE [channel [channel ...]]
channel:频道名;如果不指定频道,则取消订阅所有频道。
UNSUBSCRIBE news |
订阅模式
除了订阅频道外,Redis 还支持订阅模式。模式匹配允许客户端订阅符合特定模式的多个频道。
- 使用
PSUBSCRIBE命令订阅模式。 - 使用
PUNSUBSCRIBE命令取消订阅模式。
PSUBSCRIBE news.* |
客户端命令
已订阅一个或多个频道的客户端不应再发布命令,但可以向其他频道 SUBSCRIBE 和 UNSUBSCRIBE 。订阅与退订操作的回复会以消息形式发送,因此客户端只需读取连贯的消息流即可,其中首个元素会标明消息类型。在 RESP2 协议下,已订阅客户端允许执行的命令包括:
SpringBoot 整合
整合步骤
导入场景
<dependency> |
自动配置
RedisProperties:封装Redis配置属性
**RedisAutoConfiguration**:封装Redis自动配置
- **
RedisTemplate<Object, Object>**:key-value是任意对象。Redis在底层会使用RedisSerializer把对象序列化为二进制安全的字符串进行保存 - **
StringRedisTemplate**:key-value 都是字符串。自己在使用过程中需要把对象转为字符串。
// |
可用组件
StringRedisTemplate
RedisTemplate
opsFor:操作指定数据类型
opsForValue:String类型操作
opsForList:List类型操作
opsForSet:Set类型操作
opsForZSet:ZSet类型操作
opsForHash:Hash类型操作
功能测试
package com.lfy.demoredis; |
boundXxxOps:绑定某种类型操作
底层原理
内存淘汰(key驱逐策略)
https://redis.io/docs/latest/develop/reference/eviction/
什么叫内存淘汰:淘汰掉以前的数据占用的内存,为新数据腾出空间。
- Redis 保存 key value 的数据库
- Redis 把数据默认全部保存到内存中。数据一直在(利用持久化机制把内存数据搬到硬盘)
- 运维人员把Redis安装到一个机器中。redis可用的内存最多就这个机器内存这么大
思考:放着放着内存不够了怎么办?
- 不存:拒绝服务,直接说内存不够,返回错误
- 存:必须把以前的数据淘汰掉(清理掉); 到底淘汰哪些数据,就有机制;
内存淘汰策略(8种):
The following policies are available:
noeviction: Keys are not evicted but the server will return an error when you try to execute commands that cache new data. If your database uses replication then this condition only applies to the primary database. Note that commands that only read existing data still work as normal.allkeys-lru: Evict the least recently used (LRU) keys.allkeys-lfu: Evict the least frequently used (LFU) keys.allkeys-random: Evict keys at random.volatile-lru: Evict the least recently used keys that have theexpirefield set totrue.volatile-lfu: Evict the least frequently used keys that have theexpirefield set totrue.volatile-random: Evict keys at random only if they have theexpirefield set totrue.volatile-ttl: Evict keys with theexpirefield set totruethat have the shortest remaining time-to-live (TTL) value.
修改redis的配置文件
# 配置redis占用多大内存。超内存触发淘汰 |
Key 过期删除
触发时机: key 的 ttl(time to live) 到了,就要删除;
Redis如何发现了过期的key并删除?
- 主动巡检(内存采样):一直在后台运行
- redis会定期扫描某一小片区域,这次扫过以后,下次扫描另一片区域;发现过期的key直接删除
- 被动检查:等到命令过来,再一起判断
- 保存且有过期时间set(a,b,100s);redis存数据的时候,会把他什么时候过期保存起来。
- 客户端查询的时候,redis自己判断数据是否过期了,
- 过期了。直接删除,且给客户端返回 null
- 没有过期。把数据的值返回给客户端
我们想的:
- 定时任务: 每秒扫描redis中的所有key,看谁过期就删除谁(ttl key)。 导致机器卡死,接不了新请求
- 发布订阅:给删除程序通知这个key过期;
- …….
持久化机制
https://redis.io/docs/latest/operate/oss_and_stack/management/persistence/
持久化是指将数据写入持久性存储设备(如固态硬盘 SSD)的过程。
Redis 提供了一系列持久化选项,包括:
- 无持久化:您可以完全禁用持久化功能。这在缓存场景中有时会用到。
- RDB(Redis Database):RDB 持久化功能会按照指定间隔对数据集执行时间点快照。
- AOF(Append Only File):AOF 持久化会记录服务器接收到的每个写操作。这些操作可以在服务器启动时重新执行,从而重建原始数据集。命令以与 Redis 协议本身相同的格式进行记录。
- RDB + AOF:您还可以在同一实例中同时使用 AOF 和 RDB 两种持久化方式。
RDB
默认情况下,Redis 会将数据集的快照以二进制文件形式保存到磁盘,文件名为 dump.rdb 。
可以配置 Redis,使其在数据集至少有 M 次更改时,或 每 N 秒保存一次数据集;
也可以手动调用 SAVE 或 BGSAVE 命令来保存。
这种策略就是大家熟悉的 快照;
工作流程:
- 触发
BGSAVE命令; background save; 后台保存 - 子进程创建内存快照
- 原子性替换旧RDB文件(
dump.rdb)
这是一种 写时复制(copy-on-write)技术 的体现;
需要写入文件的时候,把数据复制一份出来写。
优点
- RDB 是 Redis 数据的紧凑型单文件时间点快照。RDB 文件非常适合备份场景,例如您可以每小时归档一次 RDB 文件保留最近 24 小时记录,同时每天保存一个 RDB 快照持续 30 天。这种机制让您能在灾难发生时轻松恢复不同版本的数据集。
- RDB 非常适合灾难恢复,它是一个单独的紧凑文件,可以传输到远程数据中心或亚马逊 S3(可能进行加密存储)。
- RDB 最大限度地提升了 Redis 的性能,因为父进程持久化时只需 fork 一个子进程来完成剩余工作。父进程永远不会执行磁盘 I/O 等操作。
- 与 AOF 相比,RDB 能在处理大型数据集时实现更快的重启速度。
- 在副本节点上,RDB 支持重启和故障转移后的部分重新同步。
缺点
- 可能部分数据丢失:如果需要在 Redis 停止工作时(例如断电后)尽量减少数据丢失的可能性,RDB 并不适用。您可以配置不同的保存点来生成 RDB(例如在至少五分钟内对数据集进行 100 次写入后,可以设置多个保存点)。但通常您会每隔五分钟或更长时间创建一次 RDB 快照,因此如果 Redis 因任何原因未正确关闭而停止工作,您应该做好丢失最近几分钟数据的准备。
- 大数据集导致子进程卡顿:RDB 需要频繁调用 fork()来通过子进程将数据持久化到磁盘。如果数据集很大,fork()操作可能耗时较长,当数据集非常庞大且 CPU 性能不佳时,甚至可能导致 Redis 停止服务客户端数毫秒乃至一秒。AOF 同样需要 fork()操作,但频率较低,且可以自由调整日志重写频率而不会影响持久性。
AOF
快照方式(RDB)并不十分持久。如果运行 Redis 的计算机停止工作、电源线路故障或您意外 kill -9 实例,最新写入 Redis 的数据将会丢失。虽然这对某些应用来说可能无关紧要,但有些场景需要完全持久性,仅靠 Redis 快照机制无法满足这类需求。
AOF 工作流程: Append-Only File:给文件中追加数据
- 接收写命令
- 追加到AOF缓冲区
- 根据策略刷盘到文件(
appendonly.aof)
配置:AOF 开启
AOF 是 Redis 的另一种完全持久化策略。该功能自 1.1 版本起可用。
appendonly yes |
配置:fsync 策略(刷盘策略)
Redis 可配置磁盘数据 fsync 操作的执行频率,提供三种可选方案:
appendfsync always:fsync每次有新命令追加到 AOF 文件时都会执行。速度非常非常慢,但非常安全。请注意,命令是在执行完来自多个客户端的一批命令或管道操作后才追加到 AOF 的,这意味着每次只执行一次写入和一次 fsync(在发送回复之前)。appendfsync everysec:每秒同步一次。速度足够快(自 2.4 版本起可能和快照一样快),如果发生灾难,您可能会丢失 1 秒的数据。appendfsync no:从不fsync,只是将数据交给操作系统处理。这种方法速度更快但安全性较低。通常在此配置下,Linux 每 30 秒会刷新一次数据,但这取决于内核的具体调优设置。
建议(也是默认)的策略是每秒执行一次
fsync。这种方式既快速又相对安全。always策略在实际操作中非常缓慢,但它支持组提交,因此如果有多个并行写入操作,Redis 会尝试执行一次fsync操作。
AOF重写机制:日志重写
自动触发:
- AOF 文件大小超过
auto-aof-rewrite-min-size(默认64MB) - AOF 文件大小比上次重写后增长超过
auto-aof-rewrite-percentage(默认100%)
手动触发:
BGREWRITEAOF命令
- 随着写入操作的执行,AOF 文件会变得越来越大。例如,对一个计数器递增 100 次操作后,数据集中最终只会包含一个键值对记录最终结果,但 AOF 文件中却会产生 100 条记录。其中 99 条记录对于重建当前状态来说都是不必要的。
- 重写过程是完全安全的。当 Redis 继续向旧文件追加数据时,会生成一个全新的文件,其中仅包含重建当前数据集所需的最少操作集合。一旦第二个文件准备就绪,Redis 就会切换这两个文件并开始向新文件追加数据。
- Redis 支持一项有趣的功能:它能在不中断客户端服务的情况下,在后台重建 AOF 文件。每当执行
BGREWRITEAOF命令时,Redis 会写入重建当前内存数据集所需的最短命令序列。如果您在 Redis 2.2 版本中使用 AOF,则需要定期运行BGREWRITEAOF命令。而自 Redis 2.4 起,系统已能自动触发日志重写(更多信息请参阅示例配置文件)。 - 自 Redis 7.0.0 起,当 AOF 重写被调度时,Redis 父进程会打开一个新的增量 AOF 文件继续写入。子进程执行重写逻辑并生成新的基础 AOF 文件。Redis 将使用临时清单文件来跟踪新生成的基础文件和增量文件。当它们准备就绪时,Redis 会执行原子替换操作使该临时清单文件生效。为了避免在 AOF 重写反复失败和重试时创建过多增量文件的问题,Redis 引入了 AOF 重写限制机制,确保失败的重写操作会以越来越慢的速率进行重试。
工作原理:日志重写采用了与快照相同的写时复制(COW)技术。
- Redis 会进行 fork 操作,此时我们便拥有了子进程和父进程。
- 子进程开始将新的基础 AOF 写入临时文件。
- 父进程会打开一个新的增量 AOF 文件继续写入更新。如果重写失败,旧的基文件与增量文件(如果有的话)加上这个新打开的增量文件,就代表了完整的更新后数据集,因此数据是安全的。
- 当子进程完成重写基础文件后,父进程会收到信号,并使用新打开的增量文件及子进程生成的基础文件构建临时清单文件,然后将其持久化存储。
- 大功告成!现在 Redis 会对清单文件进行原子交换,使这次 AOF 重写的结果生效。Redis 还会清理旧的基准文件和所有未使用的增量文件。
重写流程:
fork 子进程: 创建子进程执行重写,避免阻塞主进程遍历数据库: 子进程遍历当前数据库状态生成最小命令集: 为每个键生成最少的命令来重建数据
原始AOF可能有: |
写入临时文件: 生成temp-rewriteaof-bg-<pid>.aof处理重写期间的新命令: 主进程将重写期间的新命令保存到重写缓冲区原子替换: 重写完成后,将新命令追加到新文件,然后原子性替换原文件
优点
- 使用 AOF 持久化时,Redis 的可靠性显著提升:您可以选择不同的 fsync 策略——完全不执行 fsync、每秒执行一次 fsync 或每次都执行 fsync。采用默认的每秒 fsync 策略时,写入性能依然出色。fsync 操作由后台线程执行,且主线程会在没有 fsync 进行时全力处理写入请求,因此最多只会丢失一秒内的写入数据。
- AOF 日志是一种仅追加写入的日志,因此不存在寻址问题,即使发生断电也不会导致数据损坏。即便由于某些原因(如磁盘空间不足或其他情况)导致日志末尾包含未完整写入的命令,redis-check-aof 工具也能轻松修复该问题。
- 当 AOF 文件过大时,Redis 能够在后台自动重写 AOF 文件。重写过程绝对安全,因为 Redis 会继续向旧文件追加数据,同时生成一个全新的文件,该文件仅包含重建当前数据集所需的最少操作集合。当新文件准备就绪后,Redis 会切换这两个文件并开始向新文件追加数据。
- AOF 文件以易于理解和解析的格式,按顺序记录所有操作日志。您甚至可以轻松导出 AOF 文件。例如,即便您不小心使用
FLUSHALL命令清空了所有数据,只要在此期间没有执行日志重写操作,您仍然可以通过停止服务器、删除最后一条命令并重新启动 Redis 来恢复数据集。
缺点
- AOF 文件通常比相同数据集的等效 RDB 文件更大。
- 根据具体的 fsync 策略,AOF 可能比 RDB 慢。通常将 fsync 设置为每秒执行一次时,性能仍然非常高;而禁用 fsync 时,即使在高负载下,其速度也应与 RDB 完全相同。不过,即使在写入负载极大的情况下,RDB 仍能提供更可靠的最大延迟保证。
混合持久化
在 Redis 7+ 中,可以启用 aof-use-rdb-preamble yes:
- 重写时生成混合文件: AOF 重写时,文件开头是 **
RDB**格式的数据快照 - 追加 AOF 命令: RDB 数据后追加重写期间的 AOF 命令
- 恢复优化: 先快速加载 RDB 部分,再重放 AOF 命令部分
- 兼顾性能和完整性: 结合了 RDB 的快速恢复和 AOF 的数据完整性
AOF的配置
############################## APPEND ONLY MODE ############################### |
redis.conf:配置文件
https://redis.io/docs/latest/operate/oss_and_stack/management/config/
注意:
- Redis 8 以前的配置文件叫
redis.conf- Redis 8 以后的配置文件叫
redis-full.conf
配置项格式:keyword argument1 argument2 ... argumentN
redis底层使用redis.conf文件
docker run -d -p 6379:6379 --restart=always -e REDIS_PASSWORD=123456 --name redis bitnami/redis:8.2 |
配置文件位置
- redis.conf: /opt/bitnami/redis/etc/redis.conf
- 数据位置: /bitnami/redis/data
集群化
以下都使用 Docker 模拟集群;
启动一堆的机器就是集群。集群是为了消除 单点故障
主从复制集群
https://redis.io/docs/latest/operate/oss_and_stack/management/replication/
集群配置
services: |
工作原理
建立连接阶段
Slave 启动时读取配置 REDIS_MASTER_HOST=redis-primary
Slave 向 Master 发起连接请求
Master 接受连接,建立 TCP 连接
Slave 发送 AUTH 命令进行密码验证
数据同步阶段
全量同步 (Full Resynchronization)
第一次连接或数据差异过大时:
Slave 发送 PSYNC ? -1 命令
Master 执行 BGSAVE 生成 RDB 快照
Master 将 RDB 文件发送给 Slave
Slave 清空本地数据,加载 RDB 文件
Master 将缓冲区中的写命令发送给 Slave
增量同步 (Partial Resynchronization)
正常运行时的持续同步:
Master 将写操作记录到复制缓冲区
Master 异步发送写命令给 Slave
Slave 执行收到的写命令
保持数据一致性
心跳检测机制
Slave 每秒向 Master 发送 REPLCONF ACK 命令
报告自己的复制偏移量
Master 检测 Slave 是否在线
网络中断时自动重连
运行时的增量同步
Master 收到写命令时:
- 执行写命令
- 将命令写入 AOF (如果启用)
- 将命令发送给所有 Slave
- 更新复制偏移量
Slave 收到命令时:
- 执行写命令
- 更新本地复制偏移量
- 定期向 Master 报告偏移量
数据一致性保证
异步复制特性
- Master 执行写命令后立即返回客户端
- 异步将命令发送给 Slave
- 可能存在短暂的数据不一致
优势与缺点
优势:
✅ 读写分离:Master 处理写,Slave 处理读
✅ 数据备份:多个数据副本
✅ 负载分担:分散读请求压力
✅ 高可用性:Master 故障时可切换到 Slave
缺点:
❌ 异步复制:存在数据延迟
❌ 写操作瓶颈:所有写操作都在 Master
❌ 内存消耗:每个节点都存储完整数据
❌ 网络带宽:大量数据同步占用带宽
