Linux——redis
非关系型数据库 -- redis
基本特性:
- redis一共有16个库
- redis使用键值对完成数据存储
- 每一个库当中可以保存固定数量的键值对
- redis 的默认端口是6379
- redis支持的值的类型非常丰富,支持目前最为流行的json 也支持自定义数据类型
- redis数据的读写都直接的发生在内存中
- redis支持AOF 和 RDB 两种方式持久化,将内存中的数据写入指定文件以实现持久化
- redis 支持哨兵模式,对于redis 进程进行监控,一旦出现问题可以尝试恢复redis服务进程
- redis自带集群模式,自动形成redis集群,自动划分每一个库维护的键值对数量
- 丰富的开发语言支持
差别点 | mysql | redis |
数据存储 |
| 1、 数据优先写入到内存中,再通过某些方式同步到数据文件中 2、redis 可以通过两种方式将内存中数据写入系统文件 3、 redis 在进行数据存储以键值对形式存储数据,不论哪种类型的数据,redis都是通过键来完成数据处理 4、 redis 支持字符串、列表、集合等,对于编程支持较好的语言。 |
数据存储限制 | mysql 数据以数据表的格式进行存储,数据存储几乎没有上限 | redis 数据一共有16个库,每一个库独立进行数据存储,每一个库以一个数据槽对应一组键值对的方式进行数据存储,而对于单个redis实例或者整个redis集群,数据槽(slot)的数量是固定。 |
扩展功能 | 关系型数据库集群的水平扩展或者集群管理,一般需要结合额外的数据库管理服务实现。比如:mha、 | redis 自带集群管理和监控功能,可以实现自动failover 故障转移 |
数据备份和恢复 | 可以按照备份的方式分为: 冷备份 热备份 逻辑备份 物理备份 全量备份 增量备份等 | 对于redis 而言,数据的备份就是以数据持久化来实现。 |
适用场景:
- 数据缓存
- 数据复制
- sessions 管理
- 快速数据处理
使用行业:
金融服务
游戏
医保
零售
一、安装
[root@bogon ~]# dnf module list redis
Last metadata expiration check: 2:42:31 ago on Fri 30 Aug 2024 12:35:20 PM CST.
CentOS Stream 9 - AppStream
Name Stream Profiles Summary
redis 7 common [d] Redis persistent key-value database
Hint: [d]efault, [e]nabled, [x]disabled, [i]nstalled
[root@bogon ~]# dnf -y install redis
Last metadata expiration check: 2:43:18 ago on Fri 30 Aug 2024 12:35:20 PM CST.
Dependencies resolved.
==============================================================================================================================================================
Package Architecture Version Repository Size
==============================================================================================================================================================
Installing:
redis x86_64 6.2.7-1.el9 appstream 1.3 M
Transaction Summary
==============================================================================================================================================================
Install 1 Package
Total download size: 1.3 M
Installed size: 4.7 M
Downloading Packages:
redis-6.2.7-1.el9.x86_64.rpm 2.1 MB/s | 1.3 MB 00:00
--------------------------------------------------------------------------------------------------------------------------------------------------------------
Total 822 kB/s | 1.3 MB 00:01
Running transaction check
Transaction check succeeded.
Running transaction test
Transaction test succeeded.
Running transaction
Preparing : 1/1
Running scriptlet: redis-6.2.7-1.el9.x86_64 1/1
Installing : redis-6.2.7-1.el9.x86_64 1/1
Running scriptlet: redis-6.2.7-1.el9.x86_64 1/1
Verifying : redis-6.2.7-1.el9.x86_64 1/1
Installed:
redis-6.2.7-1.el9.x86_64
Complete!
[root@bogon ~]# systemctl enable --now redis
redis-sentinel.service redis.service
[root@bogon ~]# systemctl enable --now redis.service
Created symlink /etc/systemd/system/multi-user.target.wants/redis.service → /usr/lib/systemd/system/redis.service.
[root@bogon ~]# ss -anput | grep redis
tcp LISTEN 0 511 127.0.0.1:6379 0.0.0.0:* users:(("redis-server",pid=3551,fd=6))
tcp LISTEN 0 511 [::1]:6379 [::]:* users:(("redis-server",pid=3551,fd=7))
[root@bogon ~]# redis-cli ping
PONG
redis 一般可以作为a、 关系型数据的缓存加速服务部署 b、 作为应用数据库
而redis所有的数据以键值对的形式存储,下面简单了解redis数据操作语句。
redis-cli指令一般默认连接 127.0.0.1 的 6379 端口, 通过 -h 和 -p 选项可以连接其他ip和端口的redis服务端
二、指令
1.ping 不属于数据操作语句 验证数据库是否运行
root@bogon ~]# ss -anput | grep redis
tcp LISTEN 0 511 127.0.0.1:6379 0.0.0.0:* users:(("redis-server",pid=3551,fd=6))
tcp LISTEN 0 511 [::1]:6379 [::]:* users:(("redis-server",pid=3551,fd=7))
[root@bogon ~]# redis-cli ping
PONG
[root@bogon ~]# systemctl stop redis
[root@bogon ~]# redis-cli ping
Could not connect to Redis at 127.0.0.1:6379: Connection refused
2.keys 列出键
[root@bogon ~]# systemctl start redis
[root@bogon ~]# redis-cli
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> keys *
(empty array)
3.del 删除键
基于不同数据类型,对应的语句
redis社区版支持一下数据类型,企业版数据支持更加丰富
String - 字符串
Hash - 哈希值
List - 列表
Set - 集合
Sorted set - 有序集合
Stream - 数据流 一个特殊形式的字符串,只能在字符串的末尾新增,类似于日志文件
Bitmap - 位映射 一种比较特殊的字符串 ,可以对字符串进行位运算
Bitfield - 可以从bit位进行字符串数据的批量操作
Geospatial - 地理 基于经纬度实现定位
字符串, 最大不超过512m
4.set key value 键值对的生存时间
127.0.0.1:6379> set bike diamon
OK
127.0.0.1:6379> keys *
1) "bike"
127.0.0.1:6379> set bike1 value1 ex 10 // 设定键的生存时间
OK
127.0.0.1:6379> keys *
1) "bike"
2) "bike1"
127.0.0.1:6379> get bike1
"value1"
127.0.0.1:6379> keys *
1) "bike"
127.0.0.1:6379> set bike1 value1 nx // 如果键不存在,则创建新的键值对,
// 如果存在的话,不做任何事退出
OK
127.0.0.1:6379> get bike1
"value1"
127.0.0.1:6379> set bike1 value1 nx
(nil)
127.0.0.1:6379> set bike1 value2 nx
(nil)
127.0.0.1:6379> get bike1
"value1"
127.0.0.1:6379> set bike2 value2 nx
OK
127.0.0.1:6379> set bike1 value3 xx // 如果键存在,则更新值
// 如果键不存在,不会创建新的键
OK
127.0.0.1:6379> get bike1
"value3"
127.0.0.1:6379> keys *
1) "bike"
2) "bike2"
3) "bike1"
127.0.0.1:6379> set bike3 aab xx
(nil)
127.0.0.1:6379> keys *
1) "bike"
2) "bike2"
3) "bike1"
5.mset 一次性设置多个键值对 为键值对可以调整的参数与单个键值对创建的参数一致
127.0.0.1:6379> mset bike:1 "Deimos" bike:2 "Ares" bike:3 "Vanth"
OK
127.0.0.1:6379> keys *
1) "bike"
2) "bike1"
3) "bike2"
4) "bike:1"
5) "bike:3"
6) "bike:2"
6.INCR INCRBY 自增,注意此时字符串的字符一般为数字字符
127.0.0.1:6379> set counter 0
OK
127.0.0.1:6379> get counter
"0"
127.0.0.1:6379> incr counter
(integer) 1
127.0.0.1:6379> get counter
"1"
127.0.0.1:6379> incrby counter 5
(integer) 6
127.0.0.1:6379> get counter
"6"
三、列表处理
redis每一个列表最多保存 2^32 - 1 (4,294,967,295)
添加元素:lpush rpush
移除元素: rpop lpop
获取列表长度: llen
移动列表元素到新列表: lmove
获取列表元素:lrange
控制列表元素数量:ltrim
127.0.0.1:6379> LPUSH queue1 user1
(integer) 1
127.0.0.1:6379> LPUSH queue1 user2
(integer) 2
127.0.0.1:6379> LPUSH queue1 user3
(integer) 3
127.0.0.1:6379> RPOP queue1
"user1"
127.0.0.1:6379> RPOP queue1
"user2"
127.0.0.1:6379> RPOP queue1
"user3"
127.0.0.1:6379> keys queue1
(empty array)
127.0.0.1:6379> keys *
1) "bike:1"
2) "bike1"
3) "bike2"
4) "bike:2"
5) "bike:3"
6) "counter"
7) "bike"
127.0.0.1:6379> RPUSH queue2 user1
(integer) 1
127.0.0.1:6379> RPUSH queue2 user2
(integer) 2
127.0.0.1:6379> RPUSH queue2 user3
(integer) 3
127.0.0.1:6379> rpop queue2
"user3"
127.0.0.1:6379> RPUSH queue2 user3
(integer) 3
127.0.0.1:6379> lpop queue2
"user1"
127.0.0.1:6379> lpop queue2
"user2"
127.0.0.1:6379> llen queue2
(integer) 1
127.0.0.1:6379> LRANGE queue2 0 -1
1) "user3"
127.0.0.1:6379> LRANGE queue2 0 -1
1) "user3"
127.0.0.1:6379> lpush queue2 user2
(integer) 2
127.0.0.1:6379> lpush queue2 user1
(integer) 3
127.0.0.1:6379> LRANGE queue2 0 -1
1) "user1"
2) "user2"
3) "user3"
127.0.0.1:6379> del queue
(integer) 1
127.0.0.1:6379> lmove queue2 newqueue left left
"user1"
127.0.0.1:6379> lrange queue2 0 -1
1) "user2"
2) "user3"
127.0.0.1:6379> lrange newqueue 0 -1
1) "user1"
127.0.0.1:6379> lpush queue2 user1
(integer) 3
127.0.0.1:6379> lrange newqueue 0 -1
1) "user1"
127.0.0.1:6379> rpush queue2 user4
(integer) 4
127.0.0.1:6379> rpush queue2 user5
(integer) 5
127.0.0.1:6379> rpush queue2 user6
(integer) 6
127.0.0.1:6379> LRANGE queue2 0 -1
1) "user1"
2) "user2"
3) "user3"
4) "user4"
5) "user5"
6) "user6"
127.0.0.1:6379> LRANGE queue2 0 3
1) "user1"
2) "user2"
3) "user3"
4) "user4"
127.0.0.1:6379> LTRIM queue2 0 3
OK
127.0.0.1:6379> LRANGE queue2 0 -1
1) "user1"
2) "user2"
3) "user3"
4) "user4"
四、集合
集合(不包括重复元素的列表,但是集合本身是无序的【集合内元素的顺序不重要】)
127.0.0.1:6379> SADD set1 mem1 mem2 mem3 //设置集合
(integer) 3
127.0.0.1:6379> SRANDMEMBER set1 3 // 显示集合内所有元素
1) "mem3"
2) "mem2"
3) "mem1"
127.0.0.1:6379> SRANDMEMBER set1 5 // 长度大于集合本身大小 不会报错
1) "mem3"
2) "mem2"
3) "mem1"
127.0.0.1:6379> SCARD set1 // 获取集合长度
(integer) 3
127.0.0.1:6379> SRANDMEMBER set1 3 // 获取集合长度后,再查看集合内所有元素
1) "mem3"
2) "mem2"
3) "mem1"
127.0.0.1:6379> SREM set1 mem1 // 从集合中移除一个元素
(integer) 1
127.0.0.1:6379> SRANDMEMBER set1 3
1) "mem3"
2) "mem2"
127.0.0.1:6379> SISMEMBER set1 mem1 // 判断元素是否属于集合
(integer) 0
127.0.0.1:6379> SISMEMBER set1 mem3
(integer) 1
127.0.0.1:6379> SADD set2 mem3 mem1 mem6
(integer) 3
127.0.0.1:6379> SRANDMEMBER set1 3
1) "mem3"
2) "mem2"
127.0.0.1:6379> SRANDMEMBER set2 3
1) "mem3"
2) "mem6"
3) "mem1"
127.0.0.1:6379> SINTER set1 set2 // 两个集合相同元素
1) "mem3"
127.0.0.1:6379> SADD set2 mem3 mem2 // 重复添加相同元素时,集合内元素数量不增加
(integer) 1
127.0.0.1:6379> SRANDMEMBER set2 4
1) "mem3"
2) "mem2"
3) "mem6"
4) "mem1"
127.0.0.1:6379> SRANDMEMBER set1 4
1) "mem3"
2) "mem2"
127.0.0.1:6379> SDIFF set1 set2 // set1 对 set 2 求差集 结果为0
(empty array)
127.0.0.1:6379> SDIFF set2 set1 //set2 对 set1 求差集 有两个元素
1) "mem6"
2) "mem1"
有序集合
可以根据集合元素的分数设置排序的集合,注意集合内元素的顺序根据分数排序,只有分数相同的情况下,才会基于值的字典顺序排序。
适用于:
- 线上游戏,基于分数排名
- 速率限制,基于请求次数排序,限制异常高频访问
127.0.0.1:6379> ZADD sset1 60 tom 70 amy 32 ss
(integer) 3
127.0.0.1:6379> ZRANDMEMBER sset1 3
1) "amy"
2) "tom"
3) "ss"
127.0.0.1:6379> ZRANDMEMBER sset1 3 withscores
1) "amy"
2) "70"
3) "tom"
4) "60"
5) "ss"
6) "32"
127.0.0.1:6379> ZADD sset1 67 amy 60 john // 更新amy分数 同时john的分数
// 与tom相同,按照字典排序
(integer) 1
127.0.0.1:6379> ZRANDMEMBER sset1 5
1) "amy"
2) "tom"
3) "john"
4) "ss"
127.0.0.1:6379> ZRANDMEMBER sset1 5 withscores
1) "amy"
2) "67"
3) "tom"
4) "60"
5) "john"
6) "60"
7) "ss"
8) "32"
127.0.0.1:6379> ZRANGE sset1 0 -1 withscores // 升序列出所有
1) "ss"
2) "32"
3) "john"
4) "60"
5) "tom"
6) "60"
7) "amy"
8) "67"
127.0.0.1:6379> ZREVRANGE sset1 0 -1 withscores // 降序列出所有
1) "amy"
2) "67"
3) "tom"
4) "60"
5) "john"
6) "60"
7) "ss"
8) "32"
127.0.0.1:6379> ZRANGEBYSCORE sset1 30 60 withscores //分数在30-60 的元素
1) "ss"
2) "32"
3) "john"
4) "60"
5) "tom"
6) "60"
127.0.0.1:6379> ZRANGEBYSCORE sset1 50 60 withscores // 分数在50-60的元素
1) "john"
2) "60"
3) "tom"
4) "60"
127.0.0.1:6379> ZRANK sset1 amy // 指定元素 升序排名
(integer) 3
127.0.0.1:6379> ZREVRANK sset1 amy // 指定元素 降序排名
(integer) 0 // 排名从零开始
127.0.0.1:6379> ZINCRBY sset1 30 ss // zadd直接调整现有分数ZINCRBY 在现有分数上做加法
"62"
127.0.0.1:6379> ZRANGE sset1 0 -1
1) "john"
2) "tom"
3) "ss"
4) "amy"
127.0.0.1:6379> ZRANGE sset1 0 -1 withscores
1) "john"
2) "60"
3) "tom"
4) "60"
5) "ss"
6) "62"
7) "amy"
8) "67"
四、数据流
类似于只能新增的日志,redis会给数据流的每一个单独的信息设置唯一的ID,基于id对数据流信息进行处理,
使用场景:
- 事件追踪:用户操作比如说鼠标点击
- 传感器监控:读取设备信息
- 用户提醒:不同用户的提示信息,可以保存到不同的数据流中
XADD 新增数据流信息
XREAD 读取数据流信息
XRANGE 两个信息ID之间一共有多少个消息
XLEN 数据流的完整长度
每一个消息ID默认基于时间生成,所有ID值不同是正常的
127.0.0.1:6379> XADD game_event * player player1 speed 150km/h rank 1
"1725247886395-0" // 第一条消息ID
127.0.0.1:6379> XADD game_event * player player2 speed 100km/h rank 2
"1725247898840-0"
127.0.0.1:6379> XADD game_event * player player3 speed 99km/h rank 3
"1725247913980-0"
127.0.0.1:6379> XADD game_event * player player4 speed 94km/h rank 4
"1725247923881-0"
127.0.0.1:6379> XRANGE game_event 1725247886395-0 + //从第一条信息输出所有
1) 1) "1725247886395-0"
2) 1) "player"
2) "player1"
3) "speed"
4) "150km/h"
5) "rank"
6) "1"
2) 1) "1725247898840-0"
2) 1) "player"
2) "player2"
3) "speed"
4) "100km/h"
5) "rank"
6) "2"
3) 1) "1725247913980-0"
2) 1) "player"
2) "player3"
3) "speed"
4) "99km/h"
5) "rank"
6) "3"
4) 1) "1725247923881-0"
2) 1) "player"
2) "player4"
3) "speed"
4) "94km/h"
5) "rank"
6) "4"
127.0.0.1:6379> XLEN game_event //查看game_event 存储多少条记录
(integer) 4
127.0.0.1:6379> XRANGE game_event 1725247886395-0 + COUNT 2 // 从第一条消息开始,只显示两条
1) 1) "1725247886395-0"
2) 1) "player"
2) "player1"
3) "speed"
4) "150km/h"
5) "rank"
6) "1"
2) 1) "1725247898840-0"
2) 1) "player"
2) "player2"
3) "speed"
4) "100km/h"
5) "rank"
6) "2"
127.0.0.1:6379> XREAD count 10 BLOCK 300 STREAMS game_event $ // 从game_event 中读取10次,如果没有新的写入,则阻塞300ms, $ 可以替换为消息ID $ 代表的是指定数据流的最后一个信息
// 将上一条指令的$替换为player2的消息ID ,则从player2 下一条消息开始输出
127.0.0.1:6379> XREAD count 100 BLOCK 300 STREAMS game_event 1725247898840-0
1) 1) "game_event"
2) 1) 1) "1725247913980-0"
2) 1) "player"
2) "player3"
3) "speed"
4) "99km/h"
5) "rank"
6) "3"
2) 1) "1725247923881-0"
2) 1) "player"
2) "player4"
3) "speed"
4) "94km/h"
5) "rank"
6) "4"
五、地理空间
使用经纬度表示地理位置,先写经度(-180~180) 再写维度(-90~90)。
东经为正 西经为负 北纬为正 南纬为负
127.0.0.1:6379> GEOADD site1 -122.27652 37.805186 station:1 //site1 坐标1
(integer) 1
127.0.0.1:6379> GEOADD site1 -122.2674626 37.8062344 station:2 //site1 坐标2
(integer) 1
127.0.0.1:6379> GEOADD site1 -122.2469854 37.8104049 station:3 //site1 坐标3
(integer) 1
127.0.0.1:6379> GEOSEARCH site1 FROMLONLAT -122.2612767 37.7936847 BYRADIUS 5 km WITHDIST //site1 距离给定坐标5千米的点位,同时返回每个点位距离给定坐标的具体距离
1) 1) "station:1"
2) "1.8523"
2) 1) "station:2"
2) "1.4979"
3) 1) "station:3"
2) "2.2441"
六、redis 中数据的持久化配置
- rdb redis db file 类似于mysqldump的所导出的文件,实际上可以理解为某一个时刻对于redis中所保存数据的快照
rdb持久化配置 一般需要关注到时间参数的设定 redis库中的数据变化频繁、数据变更速度较快,rdb持久化持久化的策略,一般需要在10分钟左右、1个小时、3个小时、5个小时:
1、10分钟内发生数据变更
- 一个小时之内发生10次数据变更
rdb 持久化策略一般保存在redis配置文件中。
### 查看redis 配置文件,redis配置文件管理非常灵活,为了避免管理员操作失误,一般rpm包的配置文件是/etc/redis/redis.conf,但是实际上确认redis的配置文件需要结合redis服务端进程的启动参数,使用ps 命令抓取redis-server 服务端程序的运行参数,配置文件通过-C 参数指定
rdb 默认配置选项
rdbcompression yes // rdb文件是否启用压缩功能,节省磁盘空间
rdbchecksum yes // 启用rdb文件校验
dbfilename dump.rdb // rdb 文件名,注释此配置项,rdb持久化被禁用
rdb-del-sync-files no // 数据删除操作是否配置独立的文件
dir /var/lib/redis // rdb文件的保存目录
save 秒数 数据变更的次数
# save 3600 1 // 3600s= 1h 一个小时内发生一次数据变更则更新rdb快照
# save 300 100 // 300s=5m 5分钟内,发生100次数据变更
# save 60 10000 // 60s=1m 1分钟内,发生10000次数据变更
如果说发生数据变更但是没有触发rdb策略,则有可能导致数据丢失的情况出现
展示rdb的数据持久化效果
127.0.0.1:6379> keys * // 数据版本1
1) "bike1"
2) "set1"
3) "newqueue"
4) "bike:3"
5) "set2"
6) "bike"
7) "bike:1"
8) "bike2"
9) "queue2"
10) "bike:2"
11) "game_event"
12) "counter"
13) "site1"
14) "sset1"
打开另一个终端。配置rdb文件,并重启redis服务
[root@bogon ~]# ls /var/lib/redis/
dump.rdb
[root@bogon ~]# mv /var/lib/redis/dump.rdb /root/ //版本一快照文件
[root@bogon ~]# ls /var/lib/redis/
[root@bogon ~]# systemctl restart redis
[root@bogon ~]# ls /var/lib/redis/
dump.rdb
[root@bogon ~]# ll /var/lib/redis/dump.rdb
-rw-r--r--. 1 redis redis 630 Sep 2 17:12 /var/lib/redis/dump.rdb
切换到redis-cli的交互输入终端
127.0.0.1:6379> keys * // 因为连接终端,出现一次报错
Error: Broken pipe
127.0.0.1:6379> keys * // 停止redis服务前,redis先进行一次数据快照
// 所以 重启时从快照文件读数据 数据不会丢失
1) "bike"
2) "newqueue"
3) "bike1"
4) "bike:3"
5) "counter"
6) "sset1"
7) "bike:1"
8) "site1"
9) "bike:2"
10) "set2"
11) "queue2"
12) "game_event"
13) "set1"
14) "bike2"
127.0.0.1:6379> FLUSHALL // 清楚所有键值
OK
127.0.0.1:6379> keys *
(empty array)
在另一个终端:
[root@bogon ~]# systemctl restart redis
[root@bogon ~]# ll -h /var/lib/redis/dump.rdb
-rw-r--r--. 1 redis redis 92 Sep 2 17:14 /var/lib/redis/dump.rdb
切换到redis-cli
127.0.0.1:6379> keys *
Error: Server closed the connection
127.0.0.1:6379> keys * // 重启时,是一个空库,所以重启后快照也是空的
(empty array)
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379> set test adbsd
OK
127.0.0.1:6379> mset a v e c s v d g e d g s
OK
127.0.0.1:6379> get keys*
(nil)
127.0.0.1:6379> keys * //版本二数据库
1) "test"
2) "e"
3) "d"
4) "a"
5) "g"
6) "s"
[root@bogon ~]# systemctl stop redis.service
[root@bogon ~]# ll -h /var/lib/redis/dump.rdb
-rw-r--r--. 1 redis redis 134 Sep 2 17:19 /var/lib/redis/dump.rdb //版本二数据库快照文件
[root@bogon ~]# systemctl start redis.service
127.0.0.1:6379> keys *
Error: Server closed the connection
127.0.0.1:6379> keys *
1) "g"
2) "test"
3) "a"
4) "d"
5) "e"
6) "s"
127.0.0.1:6379> FLUSHALL // 清空数据库
OK
127.0.0.1:6379> keys *
(empty array)
[root@bogon ~]# systemctl stop redis.service
[root@bogon ~]# cp /root/dump.rdb /var/lib/redis/dump.rdb //将版本一文件复制到快照文件夹下,重启时从此文件恢复数据
cp: overwrite '/var/lib/redis/dump.rdb'? y
[root@bogon ~]# systemctl start redis.service
[root@bogon ~]# redis-cli
127.0.0.1:6379> keys *
1) "game_event"
2) "counter"
3) "bike:3"
4) "set1"
5) "set2"
6) "site1"
7) "bike2"
8) "bike:1"
9) "sset1"
10) "queue2"
11) "bike"
12) "bike:2"
13) "newqueue"
14) "bike1"
127.0.0.1:6379> exit
[root@bogon ~]# vim /etc/redis/redis.conf
[root@bogon ~]# systemctl stop redis.service
[root@bogon ~]# systemctl start redis.service
[root@bogon ~]# redis-cli //禁用了rdb快照,所以重启后是一个空的数据库
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379> exit
[root@bogon ~]# vim /etc/redis/redis.conf
[root@bogon ~]# systemctl stop redis.service
[root@bogon ~]# ll /var/lib/redis/dump.rdb
-rw-r--r--. 1 redis redis 630 Sep 2 17:25 /var/lib/redis/dump.rdb
[root@bogon ~]# systemctl start redis.service
[root@bogon ~]# redis-cli // 使用版本1快照文件启动redis
127.0.0.1:6379> keys *
1) "bike:2"
2) "set2"
3) "newqueue"
4) "queue2"
5) "bike2"
6) "bike1"
7) "set1"
8) "bike"
9) "site1"
10) "sset1"
11) "bike:1"
12) "bike:3"
13) "game_event"
14) "counter"
redis-cli 原生客户端指令
save // 立刻执行一次redis快照
config get 配置选项 // 查看某一个配置指令的值
config set 配置选项 值 // 修改目前redis服务端配置
127.0.0.1:6379> CONFIG get save
1) "save"
2) "3600 1 300 100 60 10000"
127.0.0.1:6379> config set save "600 20"
OK
127.0.0.1:6379> CONFIG get save
1) "save"
2) "600 20"
127.0.0.1:6379> config set dbfilename "redis1.rdb"
OK
127.0.0.1:6379> exit
[root@bogon ~]# ll /var/lib/redis/
total 4
-rw-r--r--. 1 redis redis 630 Sep 3 08:43 dump.rdb
[root@bogon ~]# redis-cli save
OK
[root@bogon ~]# ll /var/lib/redis/
total 8
-rw-r--r--. 1 redis redis 630 Sep 3 08:43 dump.rdb
-rw-r--r--. 1 redis redis 630 Sep 3 08:49 redis1.rdb
rdb 可能失效的场景:
- 服务器意外断电,那么redis进程意外中断,不更新快照文件,比较容易发生数据丢失
- rdb 是一个redis数据快照,因此,实际不能通过rdb倒推redis库中可能发生的数据变更
七、aof
-
- aof // append only file 仅支持追加内容的文件
- aof本质是一个redis数据变更的日志文件
- aof持久化文件的优先级高于rdb快照文件 也是如果有aof 就不使用rdb快照
- 默认不启用aof 持久化,下面介绍aof 持久化的配置选项
appendonly no // 是否启用aof 持久化,值为no代表禁用 值为yes代表启用
appendfilename "appendonly.aof" // aof持久化文件名,在未写明目录的情况下,默认保存到/var/lib/redis,也是dir的默认值
appendfsync everysec //AOF 持久化调用fsync() 函数完成持久化,这个配置选项fsync的函数调用的时机,简单点说就是指aof追加数据的时机, 值是
no 代表系统默认,时间间隔较久 执行速度快 但是不能保证数据不会丢失
always // 每一个指令都写入AOF文件 最安全 数据丢失可能性最小
everysec // 每秒记录一次数据写入指令 没有就不执行 效率和安全性之间平衡
no-appendfsync-on-rewrite no // 在执行数据同步命令时,不会调用fsync
aof-use-rdb-preamble yes // rdb + aof结合,在AOF持久化文件中留下标记,标记将指向一个rdb文件,在基于AOF恢复redis数据时,先加载rdb文件,然后从标记的位置开始从aof文件中恢复数据
auto-aof-rewrite-percentage 100 // aof文件大小比起上次重写时的大小,增长100%(配置可以大于100%)时,触发重写
auto-aof-rewrite-min-size 64mb // aof文件大小超过64MB*2时,触发重写,为何要乘以2,因为auto-aof-rewrite-percentage 100 是翻倍即100%,达到翻倍时才重写
127.0.0.1:6379> mset key1 a key2 b key3 c key4 d
OK
127.0.0.1:6379> keys *
1) "key4"
2) "key2"
3) "counter"
4) "game_event"
5) "sset1"
6) "key3"
7) "bike"
8) "bike:3"
9) "bike:1"
10) "set1"
11) "bike1"
12) "newqueue"
13) "bike2"
14) "key1"
15) "set2"
16) "queue2"
17) "site1"
18) "bike:2"
127.0.0.1:6379> save
OK
127.0.0.1:6379> exit
[root@bogon ~]# vim /etc/redis/redis.conf // 仅修改截图中的内容
[root@bogon ~]# systemctl restart redis
[root@bogon ~]# ls /var/lib/redis/
appendonly.aof dump.rdb redis1.rdb
[root@bogon ~]# ll /var/lib/redis/
total 8
-rw-r--r--. 1 redis redis 0 Sep 3 09:23 appendonly.aof // 一个空的aof文件
// 其实此时如果需要保存之前的数据,只需要在重启前手动执行一个bgrewriteaof指令,就可以,因为aof和rdb 混合模式下,实际还是从aof文件中恢复,之前重写AOF文件指令会基于rdb格式生成数据快照在AOF文件开头,所以恢复时先读aof文件的前面的rdb快照数据,再读取aof记录的指令,完成数据恢复,因为没有提前执行bgrewriteaof指令,所以只恢复了aof中记录的数据,而rdb中指令则未加载
// 在redis运行一段时间后启用AOF 持久化的正常指令顺序为先执行bgrewriteaof ,修改redis配置文件,启用AOF ,然后重启redis服务。
-rw-r--r--. 1 redis redis 630 Sep 3 08:43 dump.rdb
-rw-r--r--. 1 redis redis 662 Sep 3 09:23 redis1.rdb
[root@bogon ~]# redis-cli
127.0.0.1:6379> mset aaa vvv bbb xxx ccc xxx
OK
127.0.0.1:6379> keys *
1) "ccc"
2) "bbb"
3) "aaa"
127.0.0.1:6379> exit
[root@bogon ~]# ll /var/lib/redis/
total 12
-rw-r--r--. 1 redis redis 91 Sep 3 09:24 appendonly.aof
-rw-r--r--. 1 redis redis 630 Sep 3 08:43 dump.rdb
-rw-r--r--. 1 redis redis 662 Sep 3 09:23 redis1.rdb
[root@bogon ~]# systemctl restart redis
[root@bogon ~]# redis-cli
127.0.0.1:6379> keys *
1) "bbb"
2) "aaa"
3) "ccc"
127.0.0.1:6379> save
OK
127.0.0.1:6379> exit
aof文件中内容如下:
实际上AOF文件就是一个记录了数据写入修改指令的文件,在重启redis期间,读写这些指令并执行。
如果发生了演示中的错误,可以尝试将rdb文件的内容导入到aof文件开头
八、AOF+RDB 混合模式
先启用了rdb redis数据库中保存部分数据的情况下,再启用aof,可能因为操作失误导致部分数据丢失,正确的操作顺序如下:
实验重点为:
- 在启用aof之前一定要生成对应的aof持久化文件,如果没有生成的话可能导致数据丢失;
- aof文件本身支持rdb格式,所以如果没有通过指令生成aof文件,可以直接使用rdb文件内容来手动生成AOF文件
[root@bogon ~]# redis-cli
127.0.0.1:6379> keys * //此时未启用aof持久化。redis中保存数据
1) "sset1"
2) "key3"
3) "set1"
4) "bike1"
5) "set2"
6) "bike2"
7) "site1"
8) "bike:2"
9) "counter"
10) "newqueue"
11) "bike"
12) "bike:3"
13) "queue2"
14) "game_event"
15) "key1"
16) "key2"
17) "bike:1"
18) "key4"
127.0.0.1:6379> BGREWRITEAOF // 先生成 一个aof文件
Background append only file rewriting started
127.0.0.1:6379> exit
[root@bogon ~]# ls /var/lib/redis/ -l
total 12
-rw-r--r--. 1 redis redis 662 Sep 3 10:12 appendonly.aof // 生成的aof文件
-rw-r--r--. 1 redis redis 662 Sep 3 10:12 dump.rdb // rdb快照文件
-rw-r--r--. 1 redis redis 662 Sep 3 09:23 redis1.rdb // 之前实验演示遗留 和本实验无关
[root@bogon ~]# vim /etc/redis/redis.conf
appendonly yes //配置文件中启用aof
[root@bogon ~]# systemctl restart redis
[root@bogon ~]# redis-cli
127.0.0.1:6379> keys * // 原来数据还在,依然是18个键
1) "bike:3"
2) "site1"
3) "key1"
4) "key4"
5) "set2"
6) "bike1"
7) "set1"
8) "newqueue"
9) "sset1"
10) "queue2"
11) "bike"
12) "bike:1"
13) "key2"
14) "key3"
15) "counter"
16) "bike2"
17) "bike:2"
18) "game_event"
127.0.0.1:6379> mset a 1111 b 2222 c 3333 // 写入新的数据
OK
127.0.0.1:6379> keys *
1) "bike:3"
2) "site1"
3) "a"
4) "key1"
5) "key4"
6) "set2"
7) "bike1"
8) "set1"
9) "newqueue"
10) "sset1"
11) "c"
12) "queue2"
13) "bike"
14) "bike:1"
15) "key2"
16) "key3"
17) "b"
18) "counter"
19) "bike2"
20) "bike:2"
21) "game_event"
127.0.0.1:6379> exit
[root@bogon ~]# ls /var/lib/redis/ -l
total 12
-rw-r--r--. 1 redis redis 750 Sep 3 10:13 appendonly.aof // aof文件变大,说明新的写入同步到aof文件
-rw-r--r--. 1 redis redis 662 Sep 3 10:13 dump.rdb
-rw-r--r--. 1 redis redis 662 Sep 3 09:23 redis1.rdb
[root@bogon ~]# systemctl restart redis
[root@bogon ~]# ls /var/lib/redis/ -l
total 12
-rw-r--r--. 1 redis redis 750 Sep 3 10:13 appendonly.aof
-rw-r--r--. 1 redis redis 680 Sep 3 10:19 dump.rdb // rdb快照因为重启更新一次
-rw-r--r--. 1 redis redis 662 Sep 3 09:23 redis1.rdb
[root@bogon ~]# redis-cli
127.0.0.1:6379> keys * //21个键 在原本的18个基础上新增a b c 三个键
1) "key3"
2) "bike:3"
3) "game_event"
4) "queue2"
5) "key1"
6) "bike1"
7) "set1"
8) "bike:1"
9) "b" // 在初始数据库的基础上新增的
10) "bike2"
11) "newqueue"
12) "counter"
13) "site1"
14) "set2"
15) "c" // 在初始数据库的基础上新增的
16) "a" // 在初始数据库的基础上新增的
17) "key2"
18) "key4"
19) "bike:2"
20) "sset1"
21) "bike"
如果在启用aof之前忘记通过生成aof文件可以通过rdb快照导入aof文件的方式来恢复。
比如说下面通过指令模拟忘记提前生成aof文件的情况。
127.0.0.1:6379> mset d 4444 e 5555 // 此时aof启用所以键的在新增和删除都在aof文件中记录
OK
127.0.0.1:6379> del d
(integer) 1
127.0.0.1:6379> del e
(integer) 1
127.0.0.1:6379> config set appendonly no //从这里开始禁用aof 观察aof文件则不会写入后面新增的键 ddd eee
OK
127.0.0.1:6379> mset ddd 4444 eee 5555
OK
127.0.0.1:6379> exit
此时配置文件中是启用aof的,所以重启时,依旧从aof中恢复数据,aof文件中中仅记录21个键, rdb中记录23个键,因为只从aof文件中恢复数据那么重启后,redis库中仅有2个键。
[root@bogon ~]# systemctl restart redis
[root@bogon ~]# ls /var/lib/redis/ -l
total 12
-rw-r--r--. 1 redis redis 861 Sep 3 10:24 appendonly.aof
-rw-r--r--. 1 redis redis 696 Sep 3 10:28 dump.rdb
+
-rw-r--r--. 1 redis redis 662 Sep 3 09:23 redis1.rdb
[root@bogon ~]# redis-cli
127.0.0.1:6379> keys *
1) "set2"
2) "bike2"
3) "key3"
4) "c"
5) "sset1"
6) "bike:2"
7) "newqueue"
8) "set1"
9) "queue2"
10) "game_event"
11) "b"
12) "key2"
13) "bike1"
14) "bike:1"
15) "counter"
16) "key1"
17) "bike:3"
18) "a"
19) "key4"
20) "bike"
21) "site1"
那么此时如果想恢复手动禁用aof后创建的ddd 和 eee 就可以尝试将rdb文件的内容直接复制到aof文件中,因为aof 和rdb文件格式略有不同,最好的方式不是cp指令,而是通过重定向将rdb文件内容导入aof文件,然后启动redis服务。
通过aof文件检查命令,目前aof文件中包括21个键
rdb文件检查命令显示rdb文件中23个键
将rdb文件内容导入aof文件
[root@bogon ~]# cat /var/lib/redis/dump.rdb > /var/lib/redis/appendonly.aof
[root@bogon ~]# redis-check-aof /var/lib/redis/appendonly.aof // 检查AOF文件
The AOF appears to start with an RDB preamble.
Checking the RDB preamble to start:
[offset 0] Checking RDB file /var/lib/redis/appendonly.aof
[offset 26] AUX FIELD redis-ver = '6.2.7'
[offset 40] AUX FIELD redis-bits = '64'
[offset 52] AUX FIELD ctime = '1725331033'
[offset 67] AUX FIELD used-mem = '876616'
[offset 83] AUX FIELD aof-preamble = '0'
[offset 85] Selecting DB ID 0
[offset 696] Checksum OK
[offset 696] \o/ RDB looks OK! \o/
[info] 23 keys read // 23 个键
[info] 0 expires
[info] 0 already expired
RDB preamble is OK, proceeding with AOF tail...
AOF analyzed: size=696, ok_up_to=696, ok_up_to_line=1, diff=0
AOF is valid
AOF is valid
启动redis服务,查看有23个键
[root@bogon ~]# systemctl restart redis
[root@bogon ~]# redis-cli
127.0.0.1:6379> keys *
1) "bike:3"
2) "set1"
3) "ddd"
4) "eee"
5) "bike2"
6) "set2"
7) "site1"
8) "key1"
9) "b"
10) "bike:2"
11) "a"
12) "bike1"
13) "sset1"
14) "newqueue"
15) "key4"
16) "bike"
17) "queue2"
18) "key3"
19) "c"
20) "game_event"
21) "bike:1"
22) "key2"
23) "counter"
redis aof 目前配置如下:
appendonly yes
appendfilename "appendonly.aof"
appendfsync everysec
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb // 以上几个配置含义见 9月3日 第一篇文档
aof-load-truncated yes // 允许加载缩减后的aof文件,用于aof文件出现格式问题无法加载,通过相同指令恢复aof文件时生效
aof-use-rdb-preamble yes // 启用aof + rdb 混合,就是支持aof文件中由rdb格式+aof组成 rdb在前 aof在后 顺序不能反
在aof和rdb混合使用的情况下,为了保证数据的完整性,启用aof 以aof文件为准,同时,每隔一定时间更新rdb快照,作为数据备份。
# 手动更新rdb快照,从bash命令行执行
redis-cli save
结合crontab 周期任务,每隔一定时间自动执行更新rdb快照的命令。
aof从理论上说,是比rdb更加稳定的数据持久化策略,
演示:删除aof文件,使用rdb文件恢复aof文件
[root@bogon ~]# redis-cli
127.0.0.1:6379> keys *
1) "bike:3"
2) "set1"
3) "ddd"
4) "eee"
5) "bike2"
6) "set2"
7) "site1"
8) "key1"
9) "b"
10) "bike:2"
11) "a"
12) "bike1"
13) "sset1"
14) "newqueue"
15) "key4"
16) "bike"
17) "queue2"
18) "key3"
19) "c"
20) "game_event"
21) "bike:1"
22) "key2"
23) "counter"
127.0.0.1:6379> exit
[root@bogon ~]# systemctl stop redis.service
[root@bogon ~]# rm -f /var/lib/redis/appendonly.aof
[root@bogon ~]# redis-check-rdb /var/lib/redis/dump.rdb
[offset 0] Checking RDB file /var/lib/redis/dump.rdb
[offset 26] AUX FIELD redis-ver = '6.2.7'
[offset 40] AUX FIELD redis-bits = '64'
[offset 52] AUX FIELD ctime = '1725332814'
[offset 67] AUX FIELD used-mem = '876616'
[offset 83] AUX FIELD aof-preamble = '0'
[offset 85] Selecting DB ID 0
[offset 696] Checksum OK
[offset 696] \o/ RDB looks OK! \o/
[info] 23 keys read
[info] 0 expires
[info] 0 already expired
[root@bogon ~]# cat /var/lib/redis/dump.rdb > /var/lib/redis/appendonly.aof
[root@bogon ~]# redis-check-aof /var/lib/redis/appendonly.aof
The AOF appears to start with an RDB preamble.
Checking the RDB preamble to start:
[offset 0] Checking RDB file /var/lib/redis/appendonly.aof
[offset 26] AUX FIELD redis-ver = '6.2.7'
[offset 40] AUX FIELD redis-bits = '64'
[offset 52] AUX FIELD ctime = '1725332814'
[offset 67] AUX FIELD used-mem = '876616'
[offset 83] AUX FIELD aof-preamble = '0'
[offset 85] Selecting DB ID 0
[offset 696] Checksum OK
[offset 696] \o/ RDB looks OK! \o/
[info] 23 keys read
[info] 0 expires
[info] 0 already expired
RDB preamble is OK, proceeding with AOF tail...
AOF analyzed: size=696, ok_up_to=696, ok_up_to_line=1, diff=0
AOF is valid
[root@bogon ~]# systemctl start redis
Job for redis.service failed because the control process exited with error code.
See "systemctl status redis.service" and "journalctl -xeu redis.service" for details.
[root@bogon ~]# chown redis.redis /var/lib/redis/appendonly.aof
[root@bogon ~]# systemctl start redis
[root@bogon ~]# redis-cli
127.0.0.1:6379> keys *
1) "game_event"
2) "bike1"
3) "key4"
4) "key1"
5) "bike:3"
6) "queue2"
7) "eee"
8) "ddd"
9) "bike:2"
10) "counter"
11) "bike"
12) "bike2"
13) "bike:1"
14) "newqueue"
15) "set1"
16) "a"
17) "key2"
18) "site1"
19) "key3"
20) "set2"
21) "c"
22) "sset1"
23) "b"