初识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

相关推荐
木卫二号Coding几秒前
docker-开源nocodb,使用已有数据库
数据库·docker·开源
StarRocks_labs11 分钟前
StarRocks 存算分离在得物的降本增效实践
数据库·数据仓库·湖仓
敲代码敲到头发茂密1 小时前
基于 LangChain 实现数据库问答机器人
数据库·人工智能·语言模型·langchain·机器人
虾球xz1 小时前
游戏引擎学习第64天
redis·学习·游戏引擎
一入程序无退路1 小时前
c语言传参数路径太长,导致无法获取参数
linux·c语言·数据库
陌夏微秋2 小时前
STM32单片机芯片与内部47 STM32 CAN内部架构 介绍
数据库·stm32·单片机·嵌入式硬件·架构·信息与通信
计算机学无涯3 小时前
Spring事务回滚
数据库·sql·spring
web130933203983 小时前
flume对kafka中数据的导入导出、datax对mysql数据库数据的抽取
数据库·kafka·flume
张声录13 小时前
【ETCD】【实操篇(二十)】浅谈etcd集群管理的艺术:从两阶段配置到灾难恢复的设计原则
数据库·etcd
qq_254674413 小时前
数据仓库和数据湖 数据仓库和数据库
数据库·数据仓库