初识Redis

摘要: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数据库分类

  1. 键值(key-value)存储:例如Redis,快速查询,但存储数据缺少结构化
  2. 列存储:Hbase 查找数据快,但功能局限
  3. 文档数据库:monogDB,
  4. 图形数据库

1.1 Redis概述

高性能键值对数据库,支持的键值数据类型

  • 字符串类型
  • 列表类型
  • 有序集合类型
  • 散列类型
  • 集合类型

Redis的应用场景:

  • 缓存
  • 任务队列
  • 网站访问统计
  • 数据过期处理(精确到毫秒)
  • 应用排行榜
  • 分布式集群架构中的session分离
1.2 Redis的安装

MacOS安装Redis

        首先要下载安装包:Download | RedisRedisYou can download the last Redis source files here. For additional options, see the Redis downloads section below.Stable (7.2)Redis 7.2 …icon-default.png?t=N7T8https://redis.io/download/#redis-downloads

  • 终端解压和移动命令:

    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
1) (integer) 3
2) (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 *    #存入数据
1) "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 *
1) "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 *
1) "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 *
1) "mybatis"
2) "spring"
127.0.0.1:6379> **lastsave   #通过lastsave可以查看最近一次持久化的时间戳**
(integer) 1695796787

RDB 持久化的优点:

  1. RDB会生成多个数据文件,每个数据文件都代表了某个时刻中的Redis数据,这种多个数据文件的方式,非常适合做冷备,可以将这种完整的数据文件发送到远程云服务器上,例如阿里云的ODPS分布式存储上,以预订好的备份策略定期备份Redis中的数据;
  2. Redis对外提供的读写服务,受RDB的影响非常小;主进程fork出子进程来执行磁盘IO操作,来进行RDB持久化即可,Redis的高性能得以保持;
  3. 相对于AOF持久化机制,直接基于RDB数据文件来重启和恢复Redis进程速度更快;
  4. 适合大规模数据恢复,也可按照业务定时备份。

RDB 持久化的缺点:

  1. RDB生成数据文件会有一定时间的间隔,这期间发生故障会导致最经操作的数据丢失。
  2. 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持久化的优点

  1. 通常后台线程每秒执行一次fsync操作,最多丢失一秒数据,可更好保存数据不丢失;
  2. AOF日志文件以append-only模式写入,没有磁盘寻址开销,写入性能高且文件不易破损,及时文件尾部破损也容易修复;
  3. AOF日志文件即使过大的时候,出现后台重写操作,也不会影响客户端的读写。因为在rewrite log的时候,会对其中的指导进行压缩,创建出一份需要恢复数据的最小日志出来。再创建新日志文件的时候,老的日志文件还是照常写入。当新的merge后的日志文件ready的时候,再交换新老日志文件即可。
  4. AOF日志文件的命令通过易读的方式进行记录,这个特性非常适合做灾难性的误删除的紧急恢复。比如某人不小心用flushall命令清空了所有数据,只要这个时候后台rewrite还没有发生,那么就可以立即拷贝AOF文件,将最后一条flushall命令给删了,然后再将该AOF文件放回去,就可以通过恢复机制,自动恢复所有数据.

AOF持久化的缺点

  1. 对于同一份数据来说,AOF日志文件通常比RDB数据快照文件更大。
  2. AOF开启后,支持的写QPS会比RDB支持的写QPS低,因为AOF一般会配置成每秒fsync一次日志文件,当然,每秒一次fsync,性能也还是很高的。
  3. 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.comicon-default.png?t=N7T8https://gitee.com/qishibo/AnotherRedisDesktopManager/releases

  • 50
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值