Redis命令与持久化

基础命令

redis根据操作对象的不同,命令可以分为三大类:对redis进行基础性操作的命令;对key的操作命令;对value的操作命令。

针对redis基础的操作命令

ping 心跳命令

登录redis后,输入ping,如果返回pong,说明目前客户端与redis之间的连接是正常的。

[root@test2 ~]# redis-cli -a 123
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> 

**select **切换数据库

登录redis默认进入的是0号数据库,使用select命令可以切换至其他数据库。

#切换至4号数据库
127.0.0.1:6379> select 4
OK
127.0.0.1:6379[4]> 

dbsize 查看key的总数

#查看当前数据库key的总数(当前没有插入数据,所以显示为0)
127.0.0.1:6379> dbsize
(integer) 0
127.0.0.1:6379> 

set 插入数据

#插入数据格式为set key value
127.0.0.1:6379> set name tom
OK
127.0.0.1:6379> set age 20
OK
127.0.0.1:6379> 

get 查询数据

#查询的格式为get key
127.0.0.1:6379> get name 
"tom"
127.0.0.1:6379> get age
"20"
127.0.0.1:6379> 

flushdb 删除当前数据库数据

注意,此命令为高危命令,一般会在配置文件中禁用,如需执行,请谨慎操作。

#0和1号数据库中都有数据,切换到1号数据库执行flushdb命令,发现当前数据库数据已经被删除,切换到0号数据库发现数据依然存在。
127.0.0.1:6379> dbsize
(integer) 2
127.0.0.1:6379> select 1
OK
127.0.0.1:6379[1]> dbsize
(integer) 1
127.0.0.1:6379[1]> flushdb
OK
127.0.0.1:6379[1]> dbsize
(integer) 0
127.0.0.1:6379[1]> select 0
OK
127.0.0.1:6379> dbsize
(integer) 2
127.0.0.1:6379> 

flushall 删除所有数据库的所有数据

高危!删除redis中所有数据库的数据,此命令一般会在配置文件中禁用

#在0号数据库和1号数据库中创建一些数据,再通过flushall命令删除,可以发现0和1数据库已经完全清空。
127.0.0.1:6379> dbsize 
(integer) 2
127.0.0.1:6379> select 1
OK
127.0.0.1:6379[1]> set name jerry
OK
127.0.0.1:6379[1]> set age 14
OK
127.0.0.1:6379[1]> dbsize
(integer) 2
127.0.0.1:6379[1]> flushall
OK
127.0.0.1:6379[1]> dbsize
(integer) 0
127.0.0.1:6379[1]> select 0
OK
127.0.0.1:6379> dbsize
(integer) 0
127.0.0.1:6379> 

针对key的操作命令

redis中存储的数据整体是一个键值对(key-value),其中key为string类型,而value则可以是string、hash表、list、set等类型。

keys 查找符合指定模式的key,keys命令支持正则表达式,但是需要注意的是,生产环境中,很少使用keys命令进行检索,因为他会将线程暂时变为阻塞状态,这对服务来说是不好的,大部分情况下用scan命令代替。

#查询当前数据库中所有的值
127.0.0.1:6379> keys *
1) "hard"
2) "hellow"
3) "head"
127.0.0.1:6379> 

#查询以he开头的值
127.0.0.1:6379> keys he*
1) "hellow"
2) "head"
127.0.0.1:6379> 

exists 判断key是否存在

#存在返回1,不存在返回0
127.0.0.1:6379> exists he
(integer) 0
127.0.0.1:6379> exists hellow
(integer) 1
127.0.0.1:6379> 

del 删除一个或多个key

127.0.0.1:6379> keys *
1) "hard"
2) "hellow"
3) "head"
127.0.0.1:6379> del hard head
(integer) 2
127.0.0.1:6379> keys *
1) "hellow"
127.0.0.1:6379> 

rename 重命名

127.0.0.1:6379> keys *
1) "hellow"
127.0.0.1:6379> rename hellow world
OK
127.0.0.1:6379> keys *
1) "world"
127.0.0.1:6379> 

move 移动key到别的数据库

127.0.0.1:6379> keys *
1) "world"
127.0.0.1:6379> move world 1
(integer) 1
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379> select 1
OK
127.0.0.1:6379[1]> keys *
1) "world"
127.0.0.1:6379[1]> 

type 查看key类型

127.0.0.1:6379[1]> type world
string
127.0.0.1:6379[1]> 

expire、pexpire 生存时间/过期时间

为key设置生存时间,单位默认为秒,到达生存时间后该key会自动消失,在redis中,像这种带有生存时间的key被称为“易失的”(volatile)。

expire的单位为秒,pexpire的单位为毫秒。

127.0.0.1:6379[1]> keys *
1) "world"
127.0.0.1:6379[1]> expire world 5
(integer) 1
#等待5秒后查看
127.0.0.1:6379[1]> keys *
(empty array)
127.0.0.1:6379[1]> 

ttl、pttl 查看剩余生存时间/过期时间

ttl的单位为秒,pttl的单位为毫秒

127.0.0.1:6379[1]> keys *
1) "name"
127.0.0.1:6379[1]> expire name 100
(integer) 1
127.0.0.1:6379[1]> ttl name
(integer) 95
127.0.0.1:6379[1]> ttl name
(integer) 94
127.0.0.1:6379[1]> pttl name
(integer) 91281
127.0.0.1:6379[1]> pttl name
(integer) 89929
127.0.0.1:6379[1]> 

persist 设置持久化

将设置有过期时间的key,变为不过期

127.0.0.1:6379[1]> expire name 1000
(integer) 1
127.0.0.1:6379[1]> ttl name
(integer) 997
127.0.0.1:6379[1]> persist name
(integer) 1
#ttl返回为-1表示此key没有设置过期时间。
127.0.0.1:6379[1]> ttl name
(integer) -1
127.0.0.1:6379[1]> keys *
1) "name"
127.0.0.1:6379[1]> 

randomkey 随机返回key

随机返回一个key,主要用于测试当前数据库是否为空

127.0.0.1:6379[1]> keys *
1) "home"
2) "age"
3) "name"
127.0.0.1:6379[1]> randomkey 
"age"
127.0.0.1:6379[1]> randomkey 
"home"
127.0.0.1:6379[1]>
#数据库为空时返回null
127.0.0.1:6379[1]> select 3
OK
127.0.0.1:6379[3]> keys *
(empty array)
127.0.0.1:6379[3]> randomkey 
(nil)
127.0.0.1:6379[3]> 

scan 查询

127.0.0.1:6379> scan 0 #以“0”为游标,开始新的迭代
1) "0"  #第一次迭代返回的游标
2)  1) "i"
    2) "j"
    3) "d"
    4) "f"
    5) "k"
    6) "g"
    7) "a"
    8) "b"
    9) "e"
   10) "c"
   11) "h"
127.0.0.1:6379> scan 2 match a* #scan支持正则,在match后可以跟匹配条件
1) "15"
2) 1) "aa"
   2) "a"
127.0.0.1:6379> scan 0 count 5 #count 指定key值的数量
1) "20"
2) 1) "i"
   2) "k"
   3) "h"
   4) "gg"
   5) "bb"
127.0.0.1:6379> scan 0 type string #查询指定类型的key
1) "22"
2)  1) "i"
    2) "k"
    3) "h"
    4) "gg"
    5) "bb"
    6) "e"
    7) "cc"
    8) "aa"
    9) "hh"
   10) "d"
127.0.0.1:6379> 

string型针对value的操作命令

redis存储数据的value可以是一个string类型数据。string类型的value类型是redis中最基本最常见的类型。string类型的value中可以存放任意数据,包括数值型,甚至二进制的图片、音频、视频、序列化对象等。一个string类型的value最大是512M。

set

#插入数据
127.0.0.1:6379[1]> set name jerry #插入key/value
OK
127.0.0.1:6379[1]> set age 10 ex 200 #ex 设置过期时间,默认单位秒
OK
127.0.0.1:6379[1]> ttl age 
(integer) 192
127.0.0.1:6379[1]> set sex man nx #nx 添加新的key,如果当前添加的key已经存在,会返回错误
OK
127.0.0.1:6379[1]> set sex woman nx 
(nil)
127.0.0.1:6379[1]> set sex woman xx #xx 用于更新key,如果key不存在,则返回错误
OK 
127.0.0.1:6379[1]> set hi "nice to meet you" #如果添加的value不是连续的字符,可以用单/双引号引用
OK
127.0.0.1:6379[1]> 

getset

127.0.0.1:6379[1]> getset name tom #getset先获取当前value,再重新设置
"jerry"
127.0.0.1:6379[1]> get name
"tom"
127.0.0.1:6379[1]> 

mset

127.0.0.1:6379[1]> mset k1 v1 k2 v2 #一次设置多个键值对
OK
127.0.0.1:6379[1]> 
#msetnx,在mset的基础上加了nx,设置的多个键值对必须是不存在的,否则会添加不成功

mget

127.0.0.1:6379[1]> mget k1 k2 #查询多个键值对
1) "v1"
2) "v2"
127.0.0.1:6379[1]> 

append

#为已存在的字符串追加内容
127.0.0.1:6379[1]> set address hubei
OK
127.0.0.1:6379[1]> get address
"hubei"
127.0.0.1:6379[1]> append address "-wuhan" 
(integer) 11
127.0.0.1:6379[1]> get address
"hubei-wuhan"
127.0.0.1:6379[1]> 

incr/decr

127.0.0.1:6379[1]> incr age #自增长,每次增长为1
(integer) 21
127.0.0.1:6379[1]> get age 
"21"
127.0.0.1:6379[1]> decr age #自删减,每次删减为1
(integer) 20
127.0.0.1:6379[1]> get age 
"20"
127.0.0.1:6379[1]> 

incrby/decrby

#用法与incr/decr一样,不过incrby/decrby可以设置每次增加删减的步长
127.0.0.1:6379[1]> incrby age 5
(integer) 25
127.0.0.1:6379[1]> get age 
"25"
127.0.0.1:6379[1]> decrby age 5
(integer) 20
127.0.0.1:6379[1]> get age 
"20"
127.0.0.1:6379[1]> 

incrbyfloat

#incr/decr和incrby/decrby都是针对与整型数据,浮点型数据需要用incrbyfloat
127.0.0.1:6379[1]> incrbyfloat age 2.5
"22.5"
127.0.0.1:6379[1]> get age 
"22.5"
127.0.0.1:6379[1]> incrbyfloat age -2.5
"20"
127.0.0.1:6379[1]> get age 
"20"
127.0.0.1:6379[1]> 

getrange

#获取子串
127.0.0.1:6379[1]> get hi
"nice to meet you"
127.0.0.1:6379[1]> getrange hi 0 4 #去除0到4的值,从0开始算有5个,第5个是空格,也会被取出
"nice "
127.0.0.1:6379[1]> getrange hi 0 20 #当结束值超出子串总长度后,会返回整个值
"nice to meet you"
#除了正向查询,也可以反向查询
127.0.0.1:6379[1]> getrange hi -3 -1 #-1就是最后一位
"you"
127.0.0.1:6379[1]> 

setrange

#替换子串
127.0.0.1:6379[1]> set nihao hello
OK
127.0.0.1:6379[1]> get nihao
"hello"
127.0.0.1:6379[1]> setrange nihao 5 " hellow" #原本子串长度为4,从第五位开始添加内容
(integer) 12
127.0.0.1:6379[1]> get nihao
"hello hellow"
127.0.0.1:6379[1]> setrange nihao 15 "hellow" 
(integer) 21
127.0.0.1:6379[1]> get nihao #如果指定的长度位置超过了子串自身的长度,会用"\x00"来代表空格占位
"hello hellow\x00\x00\x00hellow"
127.0.0.1:6379[1]>
#如果setrange指定的key是不存在的,会先创建,再替换

hash型针对value的操作命令

hash类型存放是不单单可以是值,也可以存放键值对,hash类型的命令都是以h开头。hash型value适合存放存储对象数据。

hset/hget

#hash类型可以存放一个或多个键值对,结构为:key field value
127.0.0.1:6379[2]> hset employee name tom age 22
(integer) 2
127.0.0.1:6379[2]> hget employee name
"tom"
127.0.0.1:6379[2]> hget employee age 
"22"
#hset还可以用来继续插入数据,如果插入已存在的value,会更新为当前输入的value
127.0.0.1:6379[2]> hset employee sex man
(integer) 1
127.0.0.1:6379[2]> 

hmset/hmget

#hmset与hset的效果一样
#hmget可以一次查询多个值
127.0.0.1:6379[2]> hmget employee name age sex
1) "tom"
2) "22"
3) "man"
127.0.0.1:6379[2]> 

hgetall

#查询当前key中所有的field和value
127.0.0.1:6379[2]> hgetall employee
1) "name"
2) "tom"
3) "age"
4) "22"
5) "sex"
6) "man"
127.0.0.1:6379[2]> 

hkeys/hvals

#获取指定key中所有的field
127.0.0.1:6379[2]> hkeys employee
1) "name"
2) "age"
3) "sex"
#获取指定key中所有的value
127.0.0.1:6379[2]> hvals employee
1) "tom"
2) "22"
3) "man"
127.0.0.1:6379[2]>

hdel

#删除field
127.0.0.1:6379[2]> hdel employee sex
(integer) 1
127.0.0.1:6379[2]> hkeys employee
1) "name"
2) "age"
127.0.0.1:6379[2]> 

hexists

#判断field是否存在,查询值存在时返回1,不存在返回0
127.0.0.1:6379[2]> hkeys employee
1) "name"
2) "age"
127.0.0.1:6379[2]> hexists employee name
(integer) 1
127.0.0.1:6379[2]> hexists employee sex
(integer) 0
127.0.0.1:6379[2]> 

hincrby/hincrbyfloat

#与incrby用法相同
127.0.0.1:6379[2]> hget employee age 
"22"
127.0.0.1:6379[2]> hincrby employee age 2
(integer) 24
127.0.0.1:6379[2]> hget employee age 
"24"
127.0.0.1:6379[2]> hincrby employee age -4
(integer) 20
127.0.0.1:6379[2]> hget employee age 
"20"
127.0.0.1:6379[2]> 

#hincrby不支持浮点型,需要用到hincrbyfloat
127.0.0.1:6379[2]> hget employee age 
"20"
127.0.0.1:6379[2]> hincrbyfloat employee age 0.5
"20.5"
127.0.0.1:6379[2]> hget employee age 
"20.5"
127.0.0.1:6379[2]> hincrbyfloat employee age -2.5
"18"
127.0.0.1:6379[2]> hget employee age 
"18"
127.0.0.1:6379[2]> 

hstrlen

#查询value长度
127.0.0.1:6379[2]> hstrlen employee name 
(integer) 5
127.0.0.1:6379[2]> 

list型针对value的操作命令

list类型是列表,有头有尾,且每个元素都为string类型。列表中的数据会按照插入顺序进行排序。左为头,右为尾。

lpush/rpush/rpushx

#将一个或多个值value插入到列表key的表头/表尾(表头在左,表尾在右)
#lpush插入元素的顺序:zs是第一个元素,序号为0,插入第二个元素ls时,ls会变为第一个元素序号0,zs变为第二个元素序号1,以此类推。
127.0.0.1:6379[3]> lpush names zs ls ww 
(integer) 3
#lrange查看
127.0.0.1:6379[3]> lrange names 0 -1
1) "ww"
2) "ls"
3) "zs"
127.0.0.1:6379[3]> 
#rpush的顺序与lpush相反,会按照输入元素的顺序,进行排列
127.0.0.1:6379[3]> rpush names zs ls ww
(integer) 3
127.0.0.1:6379[3]> lrange names 0 -1
1) "zs"
2) "ls"
3) "ww"
127.0.0.1:6379[3]> 
#rpushx插入数据,加上x表示key必须存在才能插入成功
127.0.0.1:6379[3]> lrange names 0 -1
1) "zs"
2) "ls"
3) "zl"
#当key不存在时,会返回0,表示插入失败
127.0.0.1:6379[3]> rpushx namename tom
(integer) 0
127.0.0.1:6379[3]> rpushx names tom jerry
(integer) 5
127.0.0.1:6379[3]> lrange names 0 -1
1) "zs"
2) "ls"
3) "zl"
4) "tom"
5) "jerry"
127.0.0.1:6379[3]> 

#小结:lpush与rpush用法一样,区别是插入数据的方向;rpush与rpushx区别在于,rpushx只能针对已经存在的key进行操作。

lrange/llen

#查看key中的具体元素,0表示第一个元素,-1表示最后一个元素
127.0.0.1:6379[3]> lrange names 0 -1
1) "ww"
2) "ls"
3) "zs"
127.0.0.1:6379[3]> 
#输出key中元素数量
127.0.0.1:6379[3]> llen names
(integer) 3
127.0.0.1:6379[3]> 

lindex

#查看指定的元素
127.0.0.1:6379[3]> lindex names 2
"ww"
127.0.0.1:6379[3]> 
#如果指定的序号不存在,会返回空
127.0.0.1:6379[3]> lindex names 1111
(nil)
127.0.0.1:6379[3]> 

lset

#替换指定元素
127.0.0.1:6379[3]> lindex names 2
"ww"
#将序号2的“ww”替换为“zl”
127.0.0.1:6379[3]> lset names 2 zl
OK
127.0.0.1:6379[3]> lindex names 2
"zl"
127.0.0.1:6379[3]> 

lpop/rpop

#删除表中的元素
127.0.0.1:6379[3]> lrange names 0 -1
1) "zs"
2) "ls"
3) "ww"
4) "tom"
5) "jerry"
#lpop key名称 后面不指定删除几个元素时,默认从表头删除一个
127.0.0.1:6379[3]> lpop names 
"zs"
127.0.0.1:6379[3]> lrange names 0 -1
1) "ls"
2) "ww"
3) "tom"
4) "jerry"
127.0.0.1:6379[3]>

#rpop 是从表尾删除
127.0.0.1:6379[3]> lrange names 0 -1
1) "ls"
2) "ww1"
3) "ww"
4) "ww2"
5) "tom"
6) "jerry"
127.0.0.1:6379[3]> rpop names 2
1) "jerry"
2) "tom"
127.0.0.1:6379[3]> lrange names 0 -1
1) "ls"
2) "ww1"
3) "ww"
4) "ww2"
127.0.0.1:6379[3]> 

linsert

#插入元素,linsert key名称 before/after 元素 需要添加的元素
#before表示在元素的前面插入,after表示在元素的后面插入
127.0.0.1:6379[3]> linsert names before ww ww1
(integer) 5
127.0.0.1:6379[3]> lrange names 0 -1
1) "ls"
2) "ww1"
3) "ww"
4) "tom"
5) "jerry"
127.0.0.1:6379[3]> 

127.0.0.1:6379[3]> linsert names after ww ww2
(integer) 6
127.0.0.1:6379[3]> lrange names 0 -1
1) "ls"
2) "ww1"
3) "ww"
4) "ww2"
5) "tom"
6) "jerry"
127.0.0.1:6379[3]> 

lrem

#rem=remove删除
#lrem key名称 count value名称
127.0.0.1:6379[3]> lrange number 0 -1
 1) "5"
 2) "4"
 3) "3"
 4) "2"
 5) "1"
 6) "5"
 7) "4"
 8) "3"
 9) "2"
10) "1"
11) "5"
12) "4"
13) "3"
14) "2"
15) "1"
#从表头开始删除3个含“1”的元素
#当count为正数时,从表头开始检索删除,为负数时从表尾开始
127.0.0.1:6379[3]> lrem number 3 1
(integer) 3
127.0.0.1:6379[3]> lrange number 0 -1
 1) "5"
 2) "4"
 3) "3"
 4) "2"
 5) "5"
 6) "4"
 7) "3"
 8) "2"
 9) "5"
10) "4"
11) "3"
12) "2"
127.0.0.1:6379[3]> 

set型针对value的操作命令

set型表示无序队列,与list不同的是set型里的元素是唯一的,不允许重复

zset型针对value的操作命令

set是无序队列,不允许重复,zset是有序队列,但是有序并不是根据字母或者数字大小来排序,zset中,每个元素都会分配一个score值,根据score值来排序。元素不能重复,score值允许重复,当score值相同时,才会根据字母进行排列。

benchmark测试工具

简介

在redis安装完成后会自动安装一个redis-benchmark测试工具,是一个压力测试工具,用于测试redis的性能。

[root@test2 ~]# ll /usr/local/bin | grep redis-benchmark
-rwxr-xr-x 1 root root  6988104 Oct 24 16:20 redis-benchmark
[root@test2 ~]# 

通过redis-benchmark --help 可以查看用法以及示例

[root@test2 ~]# redis-benchmark --help
Usage: redis-benchmark [OPTIONS] [COMMAND ARGS...]

Options:
 -h <hostname>      Server hostname (default 127.0.0.1)
 -p <port>          Server port (default 6379)
 -s <socket>        Server socket (overrides host and port)
 
.....................

 On user specified command lines __rand_int__ is replaced with a random integer
 with a range of values selected by the -r option.
[root@test2 ~]# 

常用参数

-h:redis服务端IP(测试本机时可以省略)
-p:redis服务端端口号(默认6379)
-c:客户端的并发量(默认50)
-n:客户端请求数量(默认10w)
-d:set/get、value数据的字节大小(默认3字节)
-k:是否使用keepalive,1使用,0不使用,默认为1(开启后会判断客户端是否一直处于连接状态,如果中途有断连,会显示0,一直处于连接状态显示1)
-t:指定测试命令(如果不指定,会测试全部命令)
-q:静默测试,只显示QPS的值,不显示过程信息。
#redis-benchmark测试实际上是将redis的命令依次执行,每个命令执行结束后都会返回一份报告。

[root@test2 ~]# redis-benchmark -h 127.0.0.1 -p 6379 -c 100 -n 200000

.................

====== SET ====== #测试环境报告                                                   
  200000 requests completed in 2.26 seconds #20w次数据请求
  100 parallel clients #100台客户端
  3 bytes payload #负载为3字节
  keep alive: 1 #存活检测结果为1
  host configuration "save": 3600 1 300 100 60 10000 
  host configuration "appendonly": no
  multi-thread: no #多线程,默认关闭

Latency by percentile distribution: #按百分比分布的延迟(每次统计剩余的50%)
0.000% <= 0.343 milliseconds (cumulative count 12) 
50.000% <= 0.735 milliseconds (cumulative count 102183)
...................................................
100.000% <= 13.023 milliseconds (cumulative count 200000)
100.000% <= 13.023 milliseconds (cumulative count 200000)

Cumulative distribution of latencies: #延迟累计分布(每次统计0.1毫秒)
0.000% <= 0.103 milliseconds (cumulative count 0)
1.849% <= 0.407 milliseconds (cumulative count 3698)
...................................................
99.981% <= 10.103 milliseconds (cumulative count 199961)
100.000% <= 13.103 milliseconds (cumulative count 200000)

Summary: #测试总结
  throughput summary: 88339.23 requests per second #redis每秒可以处理8.8万次请求
  latency summary (msec): #延迟总结(单位毫秒)
          avg     min       p50       p95       p99       max
        0.835     0.336     0.735     1.543     2.887    13.023

...................

生产环境中一般会指定测试命令

[root@test2 ~]# redis-benchmark -t set,push,sadd -c 100
SET: 100300.91 requests per second, p50=0.647 msec                    
SADD: 111234.70 requests per second, p50=0.631 msec    

redis持久化

redis是一个内存数据库,所以其运行效率非常高,但也存在一个问题:内存中的数据是不持久的,如果主机宕机或者redis关机重启,则内存中的数据全部丢失,这种情况是不允许的,redis具有持久化的功能,其会按照设置快照或操作日志的形式将数据持久化到磁盘。

根据持久化使用技术的不同,redis的持久化分为RDB和AOF。

持久化基本原理

redis持久化也称为“钝化”,是指将内存中数据库的状态描述信息保存到磁盘中,不过是不同的持久化技术,对数据的状态描述信息是不同的,生成的持久化文件也是不同的。但它们的作用都是为了避免数据丢失。

通过手动、定时或自动条件触发方式,将内存中数据库的状态描述信息写入到指定的持久化文件中。当系统重新启动时,自动加载持久化文件,并根据文件中数据库状态描述信息将数据恢复到内存中,这个数据恢复过程也称为“激活”。

钝化”与“激活”的过程就是redis持久化的基本原理。在redis单机状态下,无论是手动、定时或条件触发方式,都存在数据丢失问题,在尚未 “手动/自动” 保存时发生了宕机状况,那么从上次保存到宕机期间产生的数据就会丢失。不同的持久化方式,其数据丢失率也是不同的。

RDB是默认的持久化方式,但redis允许RDB与AOF两种持久化技术同时开启。同时开启后,系统会使用AOF方式做持久化,即AOF持久化技术的优先级高于RDB。如果系统启动时,同时存在两种持久化文件,会优先加载AOF持久化文件。在这里插入图片描述
在这里插入图片描述

RDB持久化

RDB(redis database),是指将内存中某一时刻的数据快照全量写入到指定rdb文件的持久化技术。RDB默认是开启的,当redis启动时,会自动读取RDB快照文件,将数据从硬盘载入到内存,以此来恢复redis关机前的状态。

持久化的执行

RDB持久化的执行有三种方式:手动save命令、手动bgsave命令、自动条件触发。

手动save

127.0.0.1:6379> save
OK
127.0.0.1:6379> 

但是需要注意的是,执行save后,redis-cli会进入阻塞状态,在钝化完成之前,都不会处理任何读写请求,无法对外提供服务。生产环境中不要使用

手动bgsave

127.0.0.1:6379> bgsave
Background saving started
127.0.0.1:6379> 

redis-cli客户端中执行bgsave命令可立即进行一次持久化保存。与save不同的是bgsave会后台允许,并令redis-server生成一个子进程,由子进程负责完成保存过程,而在子进程保存的过程中,不会阻塞redis-server进程对客户端读写请求的处理。

自动条件触发

自动条件触发的本质仍是bgsave命令的执行,只不过是用户通过在配置问价中做了相应 的设置,redis会根据设置信息自动调用bgsave命令执行。

查看上一次执行持久化的时间

#返回的是时间戳
127.0.0.1:6379> lastsave
(integer) 1701763998
127.0.0.1:6379> 

RDB优化配置

持久化的过程中,对redis的性能影响很大,所以需要进行优化配置

# 在redis配置文件中找到“Snapshotting”
[root@test2 ~]# vim /usr/local/redis/redis.conf 
# 自动触发条件,是一种递进的关系,从后往前看,如果60秒内有10000次写操作就会执行bgsave,没有达到10000次就不会执行;如果300秒内有100次写操作就会执行bgsave;如果3600秒内有1次写操作就执行,如果三个条件都不满足,就不执行。
save 3600 1 300 100 60 10000 #默认是注释的

# 当此选项设置为yes时,redis会检测上一次bgsave是否成功,如果上一次的持久化失败,会暂时禁止写操作,以此来达到提醒用户持久化失败。
stop-writes-on-bgsave-error yes

# RDB持久化时会使用LFZ压缩到.rdb数据文件中,压缩是默认开启的,但是压缩会消耗cpu资源,如果选择关闭,没有经过压缩的文件会变得庞大。
rdbcompression yes

# 从RDB5版本开始,CRC64校验和被设置到了文件的末尾,使得格式更能抵抗损坏,但是也会在加载RDB文件或执行save命令时消耗更多的性能。在禁用后,校验和变为0,在读取加载RDB文件时,就会跳过检查。
rdbchecksum yes

# 当加载RDB或RESTORE负载时,启用或禁用对ziplist和listpack等进行完整的清理检查。这减少了以后处理命令时断言或崩溃的可能性。
sanitize-dump-payload no #默认是注释的,有三个选项,yes:总是检测  no:总是关闭  client:当客户端连接时开启

# 持久化文件名
dbfilename dump.rdb

# 删除未启用持久化实例中的RDB文件。默认情况下是禁用,例如在主从集群中,由master下发RDB文件给slave,但是有的salve未开启持久化,根据安全需要进行删除RDB文件。
rdb-del-sync-files no

# .rdb文件存放路径,只能指定路径,不能指定文件名。此路径不光用于存放RDB文件,也用于存放AOF文件。
dir ./

RDB文件结构

RDB持久化文件dump.rdb整体有五部分组成:SOF、rdb_version、EOF、check_sum、databases。

1.SOF

SOF是个常量,一个字符串“REDIS”,只包含这五个字符,其长度为5。用于标识RDB文件的开始,以便在加载RDB文件时可以迅速的判断本文件是否是RDB文件。

2.rdb_version

整数,长度为4字符,表示RDB的版本号。

3.EOF

常量,占1个字节,用于标识RDB数据的结束,校验和的开始。

4.check_sum

校验和check_sum用于判断RDB文件中的内容是否出现数据异常。其采用的是CRC校验算法。

CRC校验算法:

在持久化时,先将SOF、rdb_version及内存数据库中的数据快照这三者的二进制数据拼接起来,形成一个二进制数,再用这个二进制数除以check_sum,得一个余数,再将余数拼接到二进制数的后面,形成databases。

加载时,首先会使用check_sum对RDB文件进行数据损坏验证,过程需要将RDB文件中除了EOF和check_sum以外的数据相加并除以check_sum,余数不为0,说明数据发生了损坏。

需要注意的是,这种算法只是“数据损坏校验”,并不是“数据完整校验”。也就是说,如果余数不为0,那么数据一定损坏了;如果除数为0,也不能代表数据百分百完整。

5.databases

databases部分是RDB文件中最重要的数据部分,其可以包含任意多个非空数据库。而每个database又是由三部分构成:

​ (1)SODB:是一个常量,占1字节,用于标识一个数据库的开始。

​ (2)db_number:数据库编号。

​ (3)key_value_pairs:当前数据库中的键值对数据。

​ 每个key_value_pairs又由很多个描述键值对的数据构成。

​ ((1)) value_type:是一个常量,占1个字节,用于标识键值对中value的类型。

​ ((2)) EXPIRETIME_UNIT:是一个常量,占1个字节,用于标识过期时间的单位是秒还是毫秒。

​ ((3)) time:当前key_value的过期时间。

RDB持久化过程

对于redis默认的RDB持久化,在进行bgsave持久化时,redis-serve进程会fork出一个bgsave子进程,由该子进程以异步方式负责完成持久化,在持久化的过程中,redis–server进程不会阻塞,其会继续接受并处理用户的读写请求。

bgsave子进程的详细工作原理如下:

由于子进程可以继承父进程的所有资源,且父进程不能拒绝子进程的继承权。所以bgsave子进程有权读取到redis-server进程写入到内存中的用户数据,使得将内存数据持久化到dump.rdb成为可能。

bgsave子进程在持久化时,首先会将内存中的全量数据copy到磁盘中的一个RDB临时文件,copy结束后,再将该文件rename为“dump.rdb”,并替换掉原来的同名文件。

不过在持久化过程中,如果redis-server进程接收到了用户写请求,则系统会将内存中发生数据修改的物理块copy出一个副本。等内存中的全量数据copy结束后,会再将副本中的数据copy到RDB临时文件中。这个副本是由Linux系统的“写时复制(copy-on-write)”实现的。

AOF持久化

RDB足以支持大多数应用场景,但如果在高并发的时候redis服务器突然断电或宕机,再次启动时只能恢复断电30秒之前的内容,在高并发的场景下,30秒其实也是不能接受的,如果是使用的AOF持久化,可以将数据丢失情况控制在上一次写操作。

AOF持久化是指将redis每次的写操作都已日志的方式记录到AOF文件中,当需要恢复数据时,将这些写操作再重新执行一遍,就可以恢复到之前的数据状态。

AOF基础配置

1、开启AOF持久化

#在redis配置文件中,开启appendonly
[root@test2 ~]# vim /usr/local/redis/redis.conf
appendonly no #默认是no,开启改为yes
#但是这种修改方式需要重启,一般会进入到redis中,使用命令修改
[root@test2 ~]# cat /usr/local/redis/redis.conf | grep "appendonly no" 
appendonly no #修改前配置文件中默认为no
[root@test2 ~]# redis-cli 
127.0.0.1:6379> config set appendonly yes #修改appendonly no为yes
OK
127.0.0.1:6379> config get appendonly #查看修改结果
1) "appendonly"
2) "yes"
127.0.0.1:6379> config rewrite #将修改内容更新到配置文件中,如果不执行此命令,只会修改缓存中的命令,配置文件中还是no
OK
127.0.0.1:6379> exit
[root@test2 ~]# cat /usr/local/redis/redis.conf | grep "appendonly" 
appendonly yes

2、文件名配置

在redis7版本之前,AOF的文件只有一个“appendonly.aof”,在7版本后,由单个文件变为了一组三类文件,虽然文件个数变多了,但是关联起来变得更方便了。这三类文件都有共同的前缀(默认前缀为appendonly.aof),中间的数字用于区分不同的文件,后缀为各个文件的类型:

(1)appendonly.aof.1.base.rdb:基本文件,它是在文件创建时表示数据集完整状态的快照。可以是RDB也可以是AOF类型。

(2)appendonly.aof.1.incr.aof:增量文件,以日志的形式记录的写入操作,一般有多个。

(3)appendonly.aof.manifest:清单文件,用于跟踪文件及其创建和应用的顺序。

[root@test2 ~]# vim /usr/local/redis/redis.conf
#此为前缀,定义所有的append文件的前缀。
appendfilename "appendonly.aof" 

#存放三种文件的目录名称,此目录默认存放在redis的安装目录,在redis开启AOF持久化时自动创建
appenddirname "appendonlydir"

#基本文件默认的是RDB格式,官方建议如果不是因为兼容性问题,就使用RDB即可,如果需要改为AOF,将此选项改为no即可
aof-use-rdb-preamble yes

rewrite机制

随着使用时间的推移,AOF文件会越来越大。为了防止AOF文件太大而占用大量的磁盘空间,导致性能下降,redis引入了rewrite机制来对AOF文件进行压缩。

1、什么是rewrite

所谓rewrite其实是对AOF文件进行重写整理。当rewrite开启后,主进程redis-server创建出子进程bgrewriteaof,由该子进程完成rewrite过程。其首先对现有aof文件进行rewrite计算,将计算结果写入到临时文件中,写入完毕后再rename该文件为原aof文件名,覆盖原文件。

2、rewrite计算

rewrite计算也成为rewrite策略。rewrite计算会遵循以下策略:

– 读操作命令不写入文件

– 无效命令不写入文件

– 过期数据不写入文件

– 多条命令合并写入文件

3、手动开启rewrite

进入redis执行命令开启

127.0.0.1:6379> bgrewriteaof
Background append only file rewriting started
127.0.0.1:6379> 

4、自动开启rewrite

自动rewrite会根据指定大小的百分比来判断是否执行。当执行一次rewrite时,会记录当前rewrite的大小,如果没有做过rewrite则会使用当前AOF文件的大小,作为参数,如果这个参数超过了指定数值的百分比,就会执行rewrite。例如默认的策略是:当前rewrite的大小超过了64MB的100%,就会执行rewrite。

[root@test2 ~]# vim /usr/local/redis/redis.conf
auto-aof-rewrite-percentage 100 #设置百分比,当百分比超过数值时会执行rewrite。设置为0时表示禁用rewrite。
auto-aof-rewrite-min-size 64mb #指定数值大小

AOF优化配置

1、appendsync

当客户端提交写操作命令后,该命令会同步数据到AOF文件中,有三种不同的同步策略:

[root@test2 ~]# vim /usr/local/redis/redis.conf
# appendfsync always #always,表示每次执行一次写操作,就会同步一次,这样是最安全的,但是也最耗时
appendfsync everysec #redis默认开启的是everysec,每秒同步一次,比always节约资源。
# appendfsync no #no表示不额外开启同步策略,由Linux操作系统负责,每30秒同步一次

2、no-appendfsync-on-rewrite

当appendsync设置为"always"或者"eversec"时,redis主进程会分出一个子进程去执行bgsave,只有当设置为no时才不会去执行,那么在子进程执行bgsave时,会不允许主进程调用save。

简单来说,当appendsync设置为非NO选项时,主进程都会分出一个子进程来调用执行save,如果这时又有写操作需要主进程执行,那么子进程的save就会阻塞,当数据量很大的情况下,就会出现保存的数据和写入的数据存在延迟,将no-appendfsync-on-rewrite设置为yes,表示不允许主进程调用save,设置为no表示允许。一般在数据量较大的情况下会设置为yes,其余设置为no。

[root@test2 ~]# vim /usr/local/redis/redis.conf
#默认为no
no-appendfsync-on-rewrite no 

3、aof-rewrite-incremental-fsync

当bgrewriteaof在执行过程中是先将rewrite计算结果写入到aof_rewrite_buf缓存中,然后当缓存中数据达到一定大小后,再调用fsync进行数据同步。此选项用于控制每次fsync同步的数据大小,避免因为单次同步数据过大而引发长时间的阻塞。

[root@test2 ~]# vim /usr/local/redis/redis.conf
aof-rewrite-incremental-fsync yes #针对aof设置
rdb-save-incremental-fsync yes #针对rdb设置

4、aof-load-truncated

在redis启动过程结束,AOF数据被加载回内存时,可能会出现AOF文件被截断,如果设置为yes,redis会继续读取数据并且以日志的形式提示用户,如果设置为no,则直接返回错误并拒绝启动。文件被截断的情况可能发生在redis为崩溃,但Linux系统崩溃的情况下。

当redis启动后,AOF数据被加载到内存时,可能会出现AOF文件被截断的情况,如果被截断的地方是文件的末尾,那么当aof-load-truncated设置为no时,不会启动redis;当设置为yes时,会删除末尾有错误的命令继续启动redis。如果文件是从中间被截断,需要使用redis-check-aof工具进行修复。

[root@test2 ~]# vim /usr/local/redis/redis.conf
aof-load-truncated yes #默认设置为yes

5、aof-timestamp-enabeld

#设置为yes会开启在AOF文件中增加时间戳的显示功能,可方便按照时间对数据进行恢复。但该方式可能会与AOF解析器不兼容,所以默认为no。
[root@test2 ~]# vim /usr/local/redis/redis.conf
aof-timestamp-enabled no

RDB和AOF持久化总结

RDB优势与不足

一、优势

1、RDB文件较小。RDB文件记录的是数据库当前的快照,redis是内存数据库,数据量不会很大,所以RDB文件也较小。

2、数据恢复较快。同样得益于RDB文件是数据库的当前快照。

二、不足

1、数据安全性较差。

2、"写时复制"会降低性能。

3、文件可读性较差。

AOF的优势与不足

一、优势

1、数据安全性高。配置完成后,数据丢失可以降低为一秒或只丢失上一次的写操作

2、文件可读性强

二、不足

1、文件较大。由于AOF记录的是操作日志,即使有rewrite机制,也避免不了文件较大。

2、写操作会影响性能。

3、数据恢复慢。

两种持久化如何抉择?

官方推荐使用RDB与AOF的混合式

如果对数据的安全性要求不高,则推荐使用纯RDB持久化方式。

不推荐使用纯AOF持久化。

若redis只是作为缓存,那么不需要持久化。

阻塞。

[root@test2 ~]# vim /usr/local/redis/redis.conf
aof-rewrite-incremental-fsync yes #针对aof设置
rdb-save-incremental-fsync yes #针对rdb设置

4、aof-load-truncated

在redis启动过程结束,AOF数据被加载回内存时,可能会出现AOF文件被截断,如果设置为yes,redis会继续读取数据并且以日志的形式提示用户,如果设置为no,则直接返回错误并拒绝启动。文件被截断的情况可能发生在redis为崩溃,但Linux系统崩溃的情况下。

当redis启动后,AOF数据被加载到内存时,可能会出现AOF文件被截断的情况,如果被截断的地方是文件的末尾,那么当aof-load-truncated设置为no时,不会启动redis;当设置为yes时,会删除末尾有错误的命令继续启动redis。如果文件是从中间被截断,需要使用redis-check-aof工具进行修复。

[root@test2 ~]# vim /usr/local/redis/redis.conf
aof-load-truncated yes #默认设置为yes

5、aof-timestamp-enabeld

#设置为yes会开启在AOF文件中增加时间戳的显示功能,可方便按照时间对数据进行恢复。但该方式可能会与AOF解析器不兼容,所以默认为no。
[root@test2 ~]# vim /usr/local/redis/redis.conf
aof-timestamp-enabled no

RDB和AOF持久化总结

RDB优势与不足

一、优势

1、RDB文件较小。RDB文件记录的是数据库当前的快照,redis是内存数据库,数据量不会很大,所以RDB文件也较小。

2、数据恢复较快。同样得益于RDB文件是数据库的当前快照。

二、不足

1、数据安全性较差。

2、"写时复制"会降低性能。

3、文件可读性较差。

AOF的优势与不足

一、优势

1、数据安全性高。配置完成后,数据丢失可以降低为一秒或只丢失上一次的写操作

2、文件可读性强

二、不足

1、文件较大。由于AOF记录的是操作日志,即使有rewrite机制,也避免不了文件较大。

2、写操作会影响性能。

3、数据恢复慢。

两种持久化如何抉择?

官方推荐使用RDB与AOF的混合式

如果对数据的安全性要求不高,则推荐使用纯RDB持久化方式。

不推荐使用纯AOF持久化。

若redis只是作为缓存,那么不需要持久化。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值