摘要:Redis是一种使用非常广泛的非关系型数据库,记录下Redis的入门知识。
1. 什么是NoSQL
NoSQL(Not Only SQL)是一种非关系型数据库管理系统,与传统的关系型数据库不同,它不使用结构化查询语言(SQL)进行数据操作。NoSQL数据库可以处理比传统关系型数据库更大、更快速和更多样的数据类型,例如文档、键值、图形和列族等。常见的NoSQL数据库包括MongoDB、Cassandra、Redis、Elasticsearch等
NoSQL数据库通常具有更高的可扩展性和可用性,适用于需要处理大量数据和高并发读写的场景,如互联网应用、物联网和大数据分析等。
NoSQL在处理下面场景时,相对于关系型数据库优势明显
- High performance -高并发读写
- Huge Storage - 海量数据的高效率存储和访问
- High scalability && High availability -高扩展和高可用性
NoSQL数据库分类
- 键值(key-value)存储:例如Redis,快速查询,但存储数据缺少结构化
- 列存储:Hbase 查找数据快,但功能局限
- 文档数据库:monogDB,
- 图形数据库
1.1 Redis概述
高性能键值对数据库,支持的键值数据类型:
- 字符串类型
- 列表类型
- 有序集合类型
- 散列类型
- 集合类型
Redis的应用场景:
- 缓存
- 任务队列
- 网站访问统计
- 数据过期处理(精确到毫秒)
- 应用排行榜
- 分布式集群架构中的session分离
1.2 Redis的安装
MacOS安装Redis
-
终端解压和移动命令:
tar -zxvf 压缩包名称 例如:tar -zxvf redis-7.0.13.tar.gz
sudo mv 文件夹 目标路径 例如:sudo mv redis-7.0.13 /Users/czh12/Env
-
进入上述Redis安装目录
cd /Users/czh12/Env/redis-7.0.13
-
进行编译测试
sudo make test 显示All tests passed without errors!则编译成功
-
执行安装Redis命令
sudo make install
-
进入src目录,通过redis-server启动Redis服务器
19537:C 19 Sep 2023 16:19:49.915 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
19537:C 19 Sep 2023 16:19:49.915 # Redis version=7.0.10, bits=64, commit=00000000, modified=0, pid=19537, just started
19537:C 19 Sep 2023 16:19:49.915 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf
19537:M 19 Sep 2023 16:19:49.915 * Increased maximum number of open files to 10032 (it was originally set to 2560).
19537:M 19 Sep 2023 16:19:49.915 * monotonic clock: POSIX clock_gettime
.
.-__ ''-._ _.-
.
. ''-._ Redis 7.0.10 (00000000/0) 64 bit
.-.-```. ```\/ _.,_ ''-._ ( ' , .-` | `, ) Running in standalone mode |`-._`-...-` __...-.
-.|'_.-'| Port: 6379 |
-.._ / _.-' | PID: 19537
-._-._
-./ .-' .-'
|-._
-.-.__.-' _.-'_.-'| |
-.-._ _.-'_.-' | https://redis.io
-._-._
-..-'.-' .-'
|-._
-.-.__.-' _.-'_.-'| |
-.-._ _.-'_.-' |
-._-._
-..-'_.-' _.-'
-._
-..-' _.-'
-._ _.-'
-..-'19537:M 19 Sep 2023 16:19:49.917 # WARNING: The TCP backlog setting of 511 cannot be enforced because kern.ipc.somaxconn is set to the lower value of 128.
19537:M 19 Sep 2023 16:19:49.917 # Server initialized
19537:M 19 Sep 2023 16:19:49.917 * Ready to accept connections -
新建bin、etc、db三分目录,将下面5个文件拷贝到bin目录下
chenzh12@chenzh12deiMac src % cp mkreleasehdr.sh /Users/czh12/Env/redis-7.0.13/bin
chenzh12@chenzh12deiMac src % cp redis-benchmark /Users/czh12/Env/redis-7.0.13/bin
chenzh12@chenzh12deiMac src % cp redis-check-rdb /Users/czh12/Env/redis-7.0.13/bin
chenzh12@chenzh12deiMac src % cp redis-cli /Users/czh12/Env/redis-7.0.13/bin
chenzh12@chenzh12deiMac src % cp redis-server /Users/czh12/Env/redis-7.0.13/bin -
配置redis.conf文件 例如: vim /Users/chenzh12/Env/redis-7.0.13/redis.conf
修改下述配置内容:
daemonize yes #后台启动
protected-mode no #关闭保护模式,开启的话,只有本机才可以访问Redis
#bind 127.0.0.1 -::1 #bind绑定的是自己机器网卡的IP,如果有多个网卡可以配置多个IP,代表允许客户端
使用机器的那些网卡IP去访问,内网一般不配置bind,注释掉即可
appendonly yes #打开AOF功能
多次打开redis.config,未正确关闭,会导致终端报错,可用这个指令覆盖 vim -r /Users/chenzh12/Env/redis-7.0.13/redis.conf
-
想要改成后端启动,需要加载redis配置文件redis.conf,例如:
./bin/redis-server ./redis.conf
ps -ef | grep -i redis 此指令验证Redis启动成功
czh12@czh12deiMac redis-7.0.13 % ps -ef | grep -i redis 501 63479 1 0 9:46上午 ?? 0:05.44 ./bin/redis-server *:6379 501 73403 59754 0 10:01上午 ttys000 0:00.00 grep -i redis
-
进入bin目录下,运行Redis终端 例如:./bin/redis-cli
czh12@czh12deiMac redis-7.0.13 % ./bin/redis-cli
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> set chenzh 12
OK
127.0.0.1:6379> get chenzh
"12"
127.0.0.1:6379> del chenzh # keys * 查看当前数据库所有的key
(integer) 1
127.0.0.1:6379> shutdown #退出
not connected> exit -
关闭Redis服务
正确停止Redis是通过shutdown指令 例如:redis-cli shutdown
chenzh12@chenzh12deiMac redis-7.0.13 % ./bin/redis-cli shutdown
chenzh12@chenzh12deiMac redis-7.0.13 % ps -ef | grep -i redis
501 88072 59754 0 10:23上午 ttys000 0:00.00 grep -i redis
强行终止Redis 例如:sudo pkill redis-server
2.Redis数据类型
|------------|-----------------------------------------|-----------------------|
| 数据类型 | 存储的值 | 读写能力 |
| String | 可以是字符串、整数或浮点,统称为元素 | 对字符串操作、对整数类型加减 |
| List | 一个序列集合且每个节点都包含了一个元素 | 序列两端推入、弹出、修剪、查找或者移除元素 |
| Set | 各不相同的元素 | 从集合中插入或者删除元素 |
| Hash | 由key-value组成的散裂组,其中key为字符串,value是元素 | 按照key进行增加删除 |
| Sort Set | 带分数的score-value有序集合,其中score为浮点,value为元素 | 集合插入,按照分数范围查找 |
key定义的注意点:
- 不要过长:不仅消耗内存,还降低查找效率
- 不要过短:降低可读性
- 统一的命名规范
2.1 存储String
二进制安全的,存入和获取的数据相同,Value最多可以容纳的数据长度是512M
chenzh12@chenzh12deiMac redis-7.0.13 % ./bin/redis-cli
127.0.0.1:6379> get chenzh #获取:get key名
"12"
127.0.0.1:6379> del chenzh #删除:del key名
(integer) 1
127.0.0.1:6379> get chenzh
(nil)
127.0.0.1:6379> keys * #列出所有key
(empty array)
127.0.0.1:6379> set company imooc #赋值: set key名 value值
OK
127.0.0.1:6379> get company
"imooc"
127.0.0.1:6379> getset company baidu #先获取key的旧值,再赋值:getset key名 value值
"imooc"
127.0.0.1:6379> get company
"baidu"
127.0.0.1:6379> incr num #自增:incr key名
(integer) 1
127.0.0.1:6379> get num
"1"
#key不存在时,会先创建赋值0再递增;value不能转换为整型,会操作失败并返回错误信息
127.0.0.1:6379> incr company
(error) ERR value is not an integer or out of range
127.0.0.1:6379> decr num #自减:decr key名 (递减1,逻辑同自增)
(integer) 0
127.0.0.1:6379> incrby num 2023 #指定key增加某值:incrby key名 value值;原值加value值
(integer) 2023
127.0.0.1:6379> decrby num 6 #指定key的增减去某值
(integer) 2017
127.0.0.1:6379> append num 5 #拼接字符串,原值后拼接新值,并返回字符串长度
(integer) 5
127.0.0.1:6379> append number 234 #原值不存在,创建后执行拼接
(integer) 3
127.0.0.1:6379> get number
"234"
2.2 存储Hash
String key和String Value 的map容器,每个Hash可以存储42994967295个键值对
127.0.0.1:6379> hset myhash username jack #存值:hset key名 key-value键值对
(integer) 1
127.0.0.1:6379> hset myhash age 18
(integer) 1
#存多个值:hmset key名 key-value键值对多个
127.0.0.1:6379> hmset myhash2 username rose age 21
OK
127.0.0.1:6379> hget myhash username #取值:hget key名 属性键值对
"jack"
127.0.0.1:6379> hmget myhash username age #获取多个属性值:hget key名 属性键名多个
1) "jack"
2) "18"
127.0.0.1:6379> hgetall myhash #同时获取属性和值:hgetall key
1) "username"
2) "jack"
3) "age"
4) "18"
127.0.0.1:6379> hdel myhash2 username age #删除一个或多个属性:hdel key 属性名一个或多个
(integer) 2
127.0.0.1:6379> hgetall myhash2
(empty array)
127.0.0.1:6379> hdel myhash2 username #删除不存在的字段,0表示失败
(integer) 0
127.0.0.1:6379> hmset myhash2 username rose age 21
OK
127.0.0.1:6379> del myhash2 #删除整个集合:del key名
(integer) 1
127.0.0.1:6379> hget myhash2 username
(nil)
127.0.0.1:6379> hincrby myhash age 5 #增加指定值:hincrby key名 属性名 值
(integer) 23
127.0.0.1:6379> hget myhash age
"23"
127.0.0.1:6379> hexists myhash username #判断指定key中属性是否存在:hexists key名 属性名
(integer) 1 #返回1表示存在
127.0.0.1:6379> hexists myhash mypassword
(integer) 0
127.0.0.1:6379> hlen myhash #属性数量: hlen key名
(integer) 2
127.0.0.1:6379> hkeys myhash #获取全部属性: hkeys key名
1) "username"
2) "age"
127.0.0.1:6379> hvals myhash #获取全部属性值: hvals key名
1) "jack"
2) "23"
2.3 存储List
ArrayList使用数组方式
LinkedList使用双向链接方式
双向链表中增加数据
双向链表中删除数据
#链表两端添加:lpush key element [element ...]
127.0.0.1:6379> lpush mylist a b c #左侧添加,链表不存在则会先创建再添加
(integer) 3
127.0.0.1:6379> lpush mylist 1 2 3
(integer) 6
127.0.0.1:6379> rpush mylist2 a b c
(integer) 3
127.0.0.1:6379> rpush mylist2 1 2 3
(integer) 6
#链表查看:lrange key start stop
127.0.0.1:6379> lrange mylist 0 5
1) "3"
2) "2"
3) "1"
4) "c"
5) "b"
6) "a"
127.0.0.1:6379> lrange mylist2 0 -1
1) "a"
2) "b"
3) "c"
4) "1"
5) "2"
6) "3"
#链表两端弹出:lpop key [count] 存在则返回弹出值,不存在则返回nil
127.0.0.1:6379> lpop mylist
"3"
127.0.0.1:6379> lrange mylist 0 -1
1) "2"
2) "1"
3) "c"
4) "b"
5) "a"
127.0.0.1:6379> rpop mylist2
"3"
127.0.0.1:6379> lrange mylist2 0 -1
1) "a"
2) "b"
3) "c"
4) "1"
5) "2"
#获取列表中元素个数:llen key
127.0.0.1:6379> llen mylist
(integer) 5
127.0.0.1:6379> llen mylist3 #没有的链表则返回0
(integer) 0
#指定的key存在,则在头部插入指定值:lpushx key element [element ...];不存在则啥也不做
127.0.0.1:6379> lpushx mylist x
(integer) 6
127.0.0.1:6379> lrange mylist 0 -1
1) "x"
2) "2"
3) "1"
4) "c"
5) "b"
6) "a"
127.0.0.1:6379> rpushx mylist2 y
(integer) 6
127.0.0.1:6379> lrange mylist2 0 -1
1) "a"
2) "b"
3) "c"
4) "1"
5) "2"
6) "y"
#删除count个值为value(element)的元素:lrem key count element
#count>0,则从头向尾部遍历;count<0,则从后往前遍历;count=0,则删除所有值为value的元素
127.0.0.1:6379> lpush mylist3 1 2 3 1 2 3 1 2 3
(integer) 9
127.0.0.1:6379> lrange mylist3 0 -1
1) "3"
2) "2"
3) "1"
4) "3"
5) "2"
6) "1"
7) "3"
8) "2"
9) "1"
127.0.0.1:6379> lrem mylist3 2 3
(integer) 2
127.0.0.1:6379> lrange mylist3 0 -1
1) "2"
2) "1"
3) "2"
4) "1"
5) "3"
6) "2"
7) "1"
127.0.0.1:6379> lrem mylist3 -2 1
(integer) 2
127.0.0.1:6379> lrange mylist3 0 -1
1) "2"
2) "1"
3) "2"
4) "3"
5) "2"
127.0.0.1:6379> lrem mylist3 0 2
(integer) 3
127.0.0.1:6379> lrange mylist3 0 -1
1) "1"
2) "3"
#设置key中对应索引index的值:lset key index element
127.0.0.1:6379> lset mylist 3 mmm
OK
127.0.0.1:6379> lrange mylist 0 -1
1) "x"
2) "2"
3) "1"
4) "mmm"
5) "b"
6) "a"
#指定值之前插入值:linsert key BEFORE|AFTER pivot element
127.0.0.1:6379> lpush mylist4 a b c a b c #初始化数据
(integer) 6
127.0.0.1:6379> lrange mylist4 0 -1
1) "c"
2) "b"
3) "a"
4) "c"
5) "b"
6) "a"
127.0.0.1:6379> linsert mylist4 before b 11
(integer) 7
127.0.0.1:6379> lrange mylist4 0 -1
1) "c"
2) "11"
3) "b"
4) "a"
5) "c"
6) "b"
7) "a"
#将source中右侧的元素弹出,压如destination左侧:rpoplpush source destination
127.0.0.1:6379> lpush mylist5 1 2 3 #初始化数据
(integer) 3
127.0.0.1:6379> lpush mylist6 a b c
(integer) 3
127.0.0.1:6379> rpoplpush mylist5 mylist6
"1"
127.0.0.1:6379> lrange mylist6 0 -1
1) "1"
2) "c"
3) "b"
4) "a"
2.4 存储set
和List类型不同的是,Set集合中不允许出现重复的数据
Set可包含的最大元素数量是4294967295
#set中添加值:sadd key menber [member ...]
127.0.0.1:6379> sadd myset a b c 1 2 3
(integer) 6
127.0.0.1:6379> sadd myset a #值不能重复
(integer) 0
#set中删除值:srem key menber [member ...]
127.0.0.1:6379> srem myset 1 2
(integer) 2
#set中元素查看:smembers key
127.0.0.1:6379> smembers myset
1) "b"
2) "a"
3) "c"
4) "3"
#set中判断一个值是否存在:sismember key members
127.0.0.1:6379> sismember myset a #1表示存在;0表示不存在
(integer) 1
127.0.0.1:6379> sismember myset x
(integer) 0
#set的差集计算:sdiff key [key ...]
交集计算:sinter key [key ...]
并集计算:sunion key [key ...] 会去掉重复值
上述计算与key的顺序是有关系的
127.0.0.1:6379> sadd mya1 a b c
(integer) 3
127.0.0.1:6379> sadd myb1 a c 1 2
(integer) 4
127.0.0.1:6379> sdiff mya1 myb1
1) "b"
127.0.0.1:6379> smembers mya1
1) "b"
2) "a"
3) "c"
127.0.0.1:6379> smembers myb1
1) "2"
2) "1"
3) "a"
4) "c"
#set中值得数量:scard key
127.0.0.1:6379> scard myset
(integer) 4
#随机返回set中成员:srandmember key [count]
127.0.0.1:6379> srandmember myset
"b"
127.0.0.1:6379> srandmember myset
"a"
#存储差集计算的结果:sdiffstore destination key [key ...]
127.0.0.1:6379> sdiffstore my1 mya1 myb1
(integer) 1
127.0.0.1:6379> smembers my1
1) "b"
127.0.0.1:6379> sinterstore my2 mya1 myb1
(integer) 2
127.0.0.1:6379> smembers my2
1) "a"
2) "c"
127.0.0.1:6379> sunionstore my2 mya1 myb1
(integer) 5
127.0.0.1:6379> smembers my2
1) "c"
2) "b"
3) "2"
4) "1"
5) "a"
2.5 存储sorted-Set
有序集合(Sorted Set)中的成员是有序的,它们根据存储时指定的分值(score)进行排序
#添加元素:zadd key [NX|XX] [GT|LT] [CH] [INCR] score member [score member ...]
127.0.0.1:6379> zadd mysort 70 zs 80 ls 90 ww
(integer) 3 #返回添加元素的个数
127.0.0.1:6379> zadd mysort 100 zs #已存在的元素,会替换分数部分
(integer) 0
127.0.0.1:6379> zadd mysort 60 tom #添加新值
(integer) 1
#获得值对应的分数:zscore key member
127.0.0.1:6379> zscore mysort zs
"100"
#获得key中具体成员的数量:zcard key
127.0.0.1:6379> zcard mysort
(integer) 4
#删除元素:zrem key member [member ...]
127.0.0.1:6379> zrem mysort tom ww
(integer) 2
127.0.0.1:6379> zcard mysort
(integer) 2
#范围内查找元素:zrange key start stop [BYSCORE|BYLEX] [REV] [LIMIT offset count] [WITHSCORES]
127.0.0.1:6379> zadd mysort 85 jack 95 rose
(integer) 2
127.0.0.1:6379> zrange mysort 0 -1
1) "ls"
2) "jack"
3) "rose"
4) "zs"
127.0.0.1:6379> zrange mysort 0 -1 withscores #默认由小到大排序
1) "ls"
2) "80"
3) "jack"
4) "85"
5) "rose"
6) "95"
7) "zs"
8) "100"
127.0.0.1:6379> zrevrange mysort 0 -1 withscores #由大到小排序
1) "zs"
2) "100"
3) "rose"
4) "95"
5) "jack"
6) "85"
7) "ls"
8) "80"
#按照范围删除:zremrangebyrank key start stop
127.0.0.1:6379> zremrangebyrank mysort 0 4
(integer) 4
127.0.0.1:6379> zcard mysort
(integer) 0
#按照分数范围删除:zremrangebyscore key min max
127.0.0.1:6379> zadd mysort 80 zs 90 ls 100 ws
(integer) 3
127.0.0.1:6379> zremrangebyscore mysort 80 100
(integer) 3
127.0.0.1:6379> zrange mysort 0 -1
(empty array)
127.0.0.1:6379> zadd mysort 70 zs 80 ls 90 ww
(integer) 3
127.0.0.1:6379> zrangebyscore mysort 0 100 withscores
1) "zs"
2) "70"
3) "ls"
4) "80"
5) "ww"
6) "90"
#按分数排序后,限制显示个数:zrangebyscore key min max [WITHSCORES] [LIMIT offset count]
127.0.0.1:6379> zrangebyscore mysort 0 100 withscores limit 0 2
1) "zs"
2) "70"
3) "ls"
4) "80"
#设置指定成员增加:zincrby key increment member
127.0.0.1:6379> zincrby mysort 3 ls
"83"
127.0.0.1:6379> zscore mysort ls #查询指定成员值
"83"
#获取区间中成员个数:zcount key min max
127.0.0.1:6379> zcount mysort 80 90 #区间用分数表示
(integer) 2
3. Redis Keys通用操作
#所有Keys查看: keys *
127.0.0.1:6379> keys *
1) "mysort"
#依据Keys开头部分字符筛选keys:keys pattern
127.0.0.1:6379> keys my???? #问号数目需要与字符数保持一致
1) "mysort"
#删除指定的key是:del key [key ...] #返回操作的key的数量
#判断指定的Keys是否存在:exists key [key ...] #返回1表示存在;返回0表示不存在
127.0.0.1:6379> exists my1
(integer) 0
127.0.0.1:6379> exists mysort
(integer) 1
#keys重新命名:rename key newkey
127.0.0.1:6379> rename mysort mysort1
OK
127.0.0.1:6379> get mysort
(nil)
#设置过期时间: expire key seconds [NX|XX|GT|LT]
127.0.0.1:6379> expire mysort1 1000
(integer) 1
#查询过期前的剩余时间:ttl key #没设置过期时间默认返回-1
127.0.0.1:6379> ttl mysort1
(integer) 962
#获取指定key的类型:type key
127.0.0.1:6379> type mysort1
zset
4. Redis 的特性
4.1 多数据库
Redis可以创建多数据库,客户端链接是可以指定某个Redis实例的某个数据库;一个Redis实例可以创建16个数据库(索引0-15),默认链接的是第0号数据库;
# 选择指定的数据库:select index
127.0.0.1:6379> select 1
OK
127.0.0.1:6379[1]> keys * #默认选择0号数据库,1号数据库为空
(empty array)
127.0.0.1:6379[1]> select 0
OK
127.0.0.1:6379> keys 0
(empty array)
127.0.0.1:6379> keys *
1) "mysort1"
# 移动key到指定数据库:move key db
127.0.0.1:6379> move mysort1 1
(integer) 0 # 移动失败,数据丢失了?
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379> select 1
OK
127.0.0.1:6379[1]> keys *
(empty array)
4.2 Redis事务机制
事务是一个不可分割的工作逻辑单位。Redis的事务提供了一种将多个命令请求打包,然后一次性、按顺序性地执行多个命令的机制。在事务执行期间,服务器不会中断事务而去执行其它客户端的命令请求,它会将事务中的所有命令执行完毕,然后才去处理其它客户端的命令请求。
Redis实现事务的关键命令包括:
- MULTI 开启事务,总是返回OK
- EXEC 提交事务
- DISCARD 放弃事务(即放弃提交执行)
- WATCH 监控
- QUEUED 命令加入执行的队列,没操作一个动作的时候,都先加入Queue
Redis 事务的执行过程包含三个步骤:
-
开启事务:MULTI
-
命令入队:QUEUE
-
执行事务或丢弃:EXEC 或者 DISCARD
127.0.0.1:6379> set num 1
OK
127.0.0.1:6379> get num
"1"
#开启事务
#Client 通过 MULTI 命令显式开启一个事务,随后执行的操作将会暂时缓存在Queue中,实际并没有立即执行。
127.0.0.1:6379> multi
OK
#命令入列
#Client 端 把事务中的要执行的一系列操作指令发送到Service 端。 Redis服务端 实例接收到指令之后,
#并不是马上执行,而是暂存在命令队列中。
127.0.0.1:6379(TX)> incr num
QUEUED
127.0.0.1:6379(TX)> incr num
QUEUED
#执行事务
#当Client端向Service端发送的命令都Ready之后,可以发送提交执行或者丢弃事务的命令,如果是执行则操作队列中
#的具体指令,如果是丢弃则是清空队列命令。
127.0.0.1:6379(TX)> exec- (integer) 3
- (integer) 4
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set user jerry
QUEUED
#丢弃事务
127.0.0.1:6379(TX)> discard
OK
5.Redis的持久化
Redis是一种内存数据库,当某些情况导致Redis不可用,例如断电,会导致数据的丢失。为保证数据的可靠性,在系统宕机等情况下,能更快就行故障恢复,引入了数据持久化方案。(Why?)
Redis持久化又称为钝化,是指将内存中数据库的状态描述信息保存到磁盘中。不同的持久化技术,对数据的状态描述信息是不同的,生成的持久化文件也是不同的。(What?)
Redis中持久化功能的实现是通过快照(RDB)或操作日志(AOF)的形式将数据持久化到磁盘。(How?)
5.1 RDB持久化
实现类似照片记录效果的方式,将某一时刻的数据和状态以文件的形式。RDB方式的持久化是默认开启的,其规则配置再redis.conf文件中,也可以按照自己的规则修改:
# 这里表示每隔60s,如果有超过1000个key发生了变更,那么就生成一个新的dump.rdb文件,就是当前redis内存中
# 完整的数据快照,这个操作也被称之为snapshotting(快照)。
save 60 1000
# 持久化 rdb文件遇到问题时,主进程是否接受写入,yes 表示停止写入,如果是no 表示redis继续提供服务。
stop-writes-on-bgsave-error yes
# 在进行快照镜像时,是否进行压缩。yes:压缩,但是需要一些cpu的消耗。no:不压缩,需要更多的磁盘空间。
rdbcompression yes
# 一个CRC64的校验就被放在了文件末尾,当存储或者加载rbd文件的时候会有一个10%左右的性能下降,为了达到性能
# 的最大化,你可以关掉这个配置项。
rdbchecksum yes
# 快照的文件名
dbfilename dump.rdb
# 存放快照的目录
dir ./ #当前目录下
-----------------------------------
引用:redis 断电moved redis断电后数据会丢失吗
https://blog.51cto.com/u_16099350/6471903
RDB操作实践:
-
Redis中添加数据后通过shutdown关闭redis,再重启Redis:
shutdown作为一种安全退出模式,Redis执行shutdown时,会将内存中的数据立刻生成一份完整的RDB快照。故数据正常保存
chenzh12@chenzh12deiMac redis-7.0.13 % ./bin/redis-server ./redis.conf
chenzh12@chenzh12deiMac redis-7.0.13 % ./bin/redis-cli
127.0.0.1:6379> set myredis 123
OK
127.0.0.1:6379> get myredis
"123"
127.0.0.1:6379> shutdown
not connected> exit
chenzh12@chenzh12deiMac redis-7.0.13 % ps -ef | grep redis
501 18697 16369 0 2:03下午 ttys000 0:00.00 grep redis
chenzh12@chenzh12deiMac redis-7.0.13 % ./bin/redis-server ./redis.conf
chenzh12@chenzh12deiMac redis-7.0.13 % ./bin/redis-cli
127.0.0.1:6379> get myredis
"123" -
使用kill -9杀死Redis进程,模拟Redis故障异常退出
此处,查看博客中的结论数据应该丢失。实际实验发现没丢失,应该是由于redis.conf中将AOF打开
终端A:
chenzh12@chenzh12deiMac redis-7.0.13 % ./bin/redis-cli
127.0.0.1:6379> get myredis
"123"
127.0.0.1:6379> flushall # 清除Redis内存中数据
OK
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379> exit
chenzh12@chenzh12deiMac redis-7.0.13 % ls
00-RELEASENOTES INSTALL TLS.md dump.rdb runtest-moduleapi ...
chenzh12@chenzh12deiMac redis-7.0.13 % rm -f dump.rdb # 删除dump.rdb
chenzh12@chenzh12deiMac redis-7.0.13 % ls
00-RELEASENOTES INSTALL TLS.md etc runtest-sentinel ...终端B:
chenzh12@chenzh12deiMac redis-7.0.13 % ./bin/redis-cli
127.0.0.1:6379> set myredis mybatis
OK
127.0.0.1:6379> keys * #存入数据- "myredis"
终端A:
chenzh12@chenzh12deiMac redis-7.0.13 % ps -ef | grep redis
501 18722 1 0 2:03下午 ?? 0:05.76 ./bin/redis-server *:6379
501 31081 16369 0 2:22下午 ttys000 0:00.00 grep redis
501 30319 13011 0 2:21下午 ttys001 0:00.03 ./bin/redis-cli
chenzh12@chenzh12deiMac redis-7.0.13 % kill -9 18722 #杀掉Redis进程
chenzh12@chenzh12deiMac redis-7.0.13 % ./bin/redis-cli
Could not connect to Redis at 127.0.0.1:6379: Connection refused
not connected> exit
chenzh12@chenzh12deiMac redis-7.0.13 % ./bin/redis-server ./redis.conf
chenzh12@chenzh12deiMac redis-7.0.13 % ./bin/redis-cli
127.0.0.1:6379> keys *- "myredis" #此处实验失败,通过kill -9杀掉进程数据依然被保存
-
体验save阻塞式持久化和bgsave异步方式持久化
save 命令执行一个同步保存 操作,将当前 Redis 实例的所有数据快照(snapshot)以 RDB文件的形式保存到硬盘。会阻塞redis-server的进程,期间不能处理读写请求(线上禁止使用)。
bgsave命令执行之后立即返回 OK ,然后 Redis fork 出一个新子进程,原来的 Redis进程(父进程)继续处理客户端请求,而子进程则负责将数据保存到磁盘,子进程保存的过程不会阻塞读写的处理。
chenzh12@chenzh12deiMac redis-7.0.13 % ./bin/redis-cli
127.0.0.1:6379> set spring boot
OK
127.0.0.1:6379> keys *- "spring"
127.0.0.1:6379> save #阻塞式持久化
OK
127.0.0.1:6379> set mybatis 123
OK
127.0.0.1:6379> bgsave #异步方式持久化
Background saving started
127.0.0.1:6379> keys * - "mybatis"
- "spring"
127.0.0.1:6379> lastsave #通过lastsave可以查看最近一次持久化的时间戳
(integer) 1695796787
- "spring"
RDB 持久化的优点:
- RDB会生成多个数据文件,每个数据文件都代表了某个时刻中的Redis数据,这种多个数据文件的方式,非常适合做冷备,可以将这种完整的数据文件发送到远程云服务器上,例如阿里云的ODPS分布式存储上,以预订好的备份策略定期备份Redis中的数据;
- Redis对外提供的读写服务,受RDB的影响非常小;主进程fork出子进程来执行磁盘IO操作,来进行RDB持久化即可,Redis的高性能得以保持;
- 相对于AOF持久化机制,直接基于RDB数据文件来重启和恢复Redis进程速度更快;
- 适合大规模数据恢复,也可按照业务定时备份。
RDB 持久化的缺点:
- RDB生成数据文件会有一定时间的间隔,这期间发生故障会导致最经操作的数据丢失。
- RDB通过fork方式分出的子进程来完成数据持久化工作,当数据集非常大会严重影响服务性能。
RDB快照的触发条件
- 配置文件中默认的快照配置
- 手动save、bgsave命令
- 实行flushall/flushdb命令也会产生dump.rdb文件
- 执行shutdown且咩有设置开启AOF持久化、主从复制时,朱节点自动触发
写时复刻技术Copy On Write: Redis中执行BGSAVE命令生成RDB文件时,本质上就是调用Linux中的fork()命令,Linux下的fork()系统的调用实现了copy-on-write写时复刻。在fork出子进程后,与父进程共享内存空间,两者只是虚拟空间不同,但其对应的物理空间是同一个(减少物理内存的消耗);只有在父进程发生写操作修改内存数据时,才会真正的分配内存,并复制内存数据,而且也只是复制被修改的内存页的数据,并不是全部内存数据。
注释:fork是类Unix操作系统上创建进程的主要方法,fork用于创建子进程(等同于当前进程的副本)
5.2 AOF持久化
AOF方式是通过记录写操作日志的方式,记录Redis数据的一种持久化方式,Redis启动之初会读取该文件重新构建数据。此机制默认是关闭的,其在redis.conf中配置如下:
# 是否开启AOF,默认关闭
appendonly yes
# 指定 AOF 文件名(写入日志的文件)
appendfilename appendonly.aof
# Redis支持三种刷写模式:
# appendfsync always #每次收到写命令就立即强制写入磁盘,类似MySQL的sync_binlog=1,是最安全的。但该
# 模式下速度也是最慢的,一般不推荐使用。
appendfsync everysec #先写入AOF文件内存缓冲区,每秒钟强制写入磁盘一次,在性能和持久化方面做平衡,推荐
# appendfsync no #先写入AOF缓冲区,完全依赖OS的写入,一般为30秒左右一次,性能最好但是持久化最没有保证,
#不推荐。
#在日志重写时,不进行命令追加操作,而只是将其放在缓冲区里,避免与命令的追加造成DISK IO上的冲突。
#设置为yes表示rewrite期间对新写操作不fsync,暂时存在内存中,等rewrite完成后再写入,默认为no,建议yes
no-appendfsync-on-rewrite yes
#当前AOF文件大小是上次日志重写得到AOF文件大小的二倍时,自动启动新的日志重写过程。
auto-aof-rewrite-percentage 100
#当前AOF文件启动新的日志重写过程的最小值,避免刚刚启动Reids时由于文件尺寸较小导致频繁的重写。
auto-aof-rewrite-min-size 64mb
AOF操作实践:
操作Redis数据库查看appendonlydir文件夹下appendonly.aof.1.incr.aof文件,部分操作记录如下:
flushall
*3
$3
set
$7
myredis
$7
mybatis
*2
$6
SELECT
$1
0
*1
$8
kill -9杀掉redis进程,重新启动redis进程,发现数据被恢复回来了,就是从AOF文件中恢复回来的,redis进程启动的时候,直接就会从appendonly.aof中加载所有的日志,把内存中的数据恢复回来。
rewrite操作
Redis中的数据会有多重过期情况,例如用户删除、缓存清除的算法清理,这些数据被淘汰掉之后,Redis内存中只保留了常用的数据;但是对应的写日志仍然保存在AOF日志文件中,并且会不断膨胀。此时,就需要通过rewrite操作,定期基于内存中当前数据构建一套新的日志,覆盖原有的AOF日志文件。
AOF持久化的优点
- 通常后台线程每秒执行一次fsync操作,最多丢失一秒数据,可更好保存数据不丢失;
- AOF日志文件以append-only模式写入,没有磁盘寻址开销,写入性能高且文件不易破损,及时文件尾部破损也容易修复;
- AOF日志文件即使过大的时候,出现后台重写操作,也不会影响客户端的读写。因为在rewrite log的时候,会对其中的指导进行压缩,创建出一份需要恢复数据的最小日志出来。再创建新日志文件的时候,老的日志文件还是照常写入。当新的merge后的日志文件ready的时候,再交换新老日志文件即可。
- AOF日志文件的命令通过易读的方式进行记录,这个特性非常适合做灾难性的误删除的紧急恢复。比如某人不小心用flushall命令清空了所有数据,只要这个时候后台rewrite还没有发生,那么就可以立即拷贝AOF文件,将最后一条flushall命令给删了,然后再将该AOF文件放回去,就可以通过恢复机制,自动恢复所有数据.
AOF持久化的缺点
- 对于同一份数据来说,AOF日志文件通常比RDB数据快照文件更大。
- AOF开启后,支持的写QPS会比RDB支持的写QPS低,因为AOF一般会配置成每秒fsync一次日志文件,当然,每秒一次fsync,性能也还是很高的。
- AOF这种基于命令日志方式,比基于RDB每次持久化一份完整的数据快照文件的方式,更加脆弱一些,容易有bug。不过AOF为了避免rewrite过程导致的bug,因此每次rewrite并不是基于旧的指令日志进行merge的,而是基于当时内存中的数据进行指令的重新构建,这样健壮性会好很多。
AOF触发机制
手动触发,客户端向服务端发送bgrewriteaof命令
自动触发,满足配置文件选项后,Redis会记录上次重写的AOF大小,默认当AOF文件大小是上次rewrite后大小的一倍且大于64M。
5.3 RDB和AOF混合持久化
两种持久化方式同时开启,Redis重启的时候会优先加载AOF文件来恢复原始数据,因为通常情况下AOF文件保存的数据集要比RDB文件保存的数据集完整。
Redis4.0为解决当开启了混合持久化时,在 AOF 重写日志时,fork 出来的重写子进程会先将与主线程共享的内存数据以 RDB 方式写入到 AOF 文件,然后主线程处理的操作命令会被记录在重写缓冲区里,重写缓冲区里的增量命令会以 AOF 方式写入到 AOF 文件,写入完成后通知主进程将新的含有 RDB 格式和 AOF 格式的 AOF 文件替换旧的的 AOF 文件。
也就是说,使用了混合持久化,AOF 文件的前半部分是 RDB 格式的全量数据,后半部分是 AOF 格式的增量数据(RDB镜像做全量持久化,AOF做增量持久化)。
Redis数据库查看并不是十分方便,推荐一款可视化管理工具AnotherRedisDesktopManager:AnotherRedisDesktopManager 发行版 - Gitee.comhttps://gitee.com/qishibo/AnotherRedisDesktopManager/releases