初识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 ...https://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 *
    2. "mybatis"
    3. "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.comhttps://gitee.com/qishibo/AnotherRedisDesktopManager/releases

相关推荐
行走的山峰3 分钟前
etcd三节点,其中一个坏掉了的恢复办法
数据库·etcd
ImomoTo3 小时前
HarmonyOS学习(十三)——数据管理(二) 关系型数据库
数据库·学习·harmonyos·arkts·鸿蒙
机器视觉知识推荐、就业指导5 小时前
Qt/C++事件过滤器与控件响应重写的使用、场景的不同
开发语言·数据库·c++·qt
jnrjian6 小时前
export rman 备份会占用buff/cache 导致内存压力
数据库·oracle
isNotNullX6 小时前
一文解读OLAP的工具和应用软件
大数据·数据库·etl
小诸葛的博客8 小时前
pg入门1——使用容器启动一个pg
数据库
大熊程序猿9 小时前
python 读取excel数据存储到mysql
数据库·python·mysql
落落落sss9 小时前
sharding-jdbc分库分表
android·java·开发语言·数据库·servlet·oracle
jnrjian9 小时前
Oracle 启动动态采样 自适应执行计划
数据库·oracle