什么是Redis?

什么是Redis

什么是Redis

Redis是一个高性能的key-value数据库,是由 Salvatore Sanfilippo 用C语言开发的一款开源的、高性能的键值对存储数据库,它采用 BSD 协议,为了适应不同场景下的存储需求,提供了多种键值数据类型。

支持的键值数据类型有字符串、列表、有序集合、散列及集合等。正是因为它有如此丰富的数据类型的支持,才会有庞大的用户群体。

内置复制、Lua 脚本、LRU 收回、事务及不同级别磁盘持久化功能,同时通过 Redis Sentinel 实现高可用,通过 Redis Cluster 提供自动分区等相关功能。

一、特性

Redis 是一款功能强大、支持多语言多种数据类型的数据库,它具有许多优秀的特性,可以实现消息订阅发布、Lua 脚本、数据库事务、Pipeline(管道,即当指令达到一定数量后,客户端才会执行)。同时 Redis 是单线程的,它不依赖外部库,它的所有操作都是原子性的,使用简单,

具体如下:

1. 支持多种数据结构

哈希、集合、位图(多用于活跃用户数等的统计)、HyperLogLog(超小内存唯一值计数,由于只有 12KB,因而是有一定误差范围的)、GEO(地理信息定位)。

2. 读/写速度快,性能高。

官方给出的数据是,Redis 能读的速度是 110 000次/s,写的速度是 81 000次/s。之所以有这么快的读/写速度,是因为这些数据都存储在内存中。

3. 支持持久化。

Redis 的持久化也就是备份数据,它每隔一段时间就将内存中的数据保存在磁盘中,在重启的时候会再次加载到内存中,从而实现数据持久化。Redis 的持久化方式是 RDB 和 AOF。

通常来看,数据放在内存中是不安全,一旦发生断电或者故障,重要的数据可能会丢失。因此Redis提供了两种持久化方式:RDB和AOF,这两种方式可以将数据保存到硬盘中,这样就保证了数据的持久化。

4. 实现高可用主从复制,主节点做数据副本。

Redis提供了复制功能,实现多个相同数据的Redis副本。

复制功能是分布式Redis的基础。

5. 实现分布式集群和高可用。

2.8版本正式提供了高可用实现Redis Sentinel ,它能保证Redis节点的故障发现和故障自动转移。

3.0版本正式提供了分布式实现Redis Cluster ,它是是Redis真正的分布式实现,提供了高可用,读写和容量的扩展性。

二、基本数据类型

string(字符串)

它师最基本的类型,可以理解为Memcached一模一样的类型,一个key对应一个value

常用命令:set、get、decr、incr、mget等

一个键最大能存储 512MB

java 复制代码
// 批量设置
> mset key1 value1 key2 value2
// 批量获取
> mget key1 key2
// 获取长度
> strlen key  
//  字符串追加内容
> append key xxx
// 获取指定区间的字符
> getrange key 0 5
// 整数值递增 (递增指定的值)
> incr intkey (incrby intkey 10)
// 当key 存在时将覆盖
> SETEX key seconds value
// 将 key 的值设为 value ,当且仅当 key 不存在。
> SETNX key value

list(双向链表)

Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)

常用命令:lpush、rpush、lpop、rpop、lrange等

列表最多可存储 232 - 1 元素 (4294967295, 每个列表可存储40多亿)

java 复制代码
// 将一个或多个值 value 插入到列表 key 的表头
> LPUSH key value [value ...]
// 将一个或多个值 value 插入到列表 key 的表尾(最右边)。
> RPUSH key value [value ...]
// 移除并返回列表 key 的头元素。
> LPOP key
// 移除并返回列表 key 的尾元素。
> RPOP key
// BLPOP 是列表的阻塞式(blocking)弹出原语。
> BLPOP key [key ...] timeout
// BRPOP 是列表的阻塞式(blocking)弹出原语。
> BRPOP key [key ...] timeout
// 获取指点位置元素
> LINDEX key index

set(集合)

Redis 的 Set 是 string 类型的无序集合

集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)

常用命令:sadd、spop、smembers、sunion等

Set可包含的最大元素数量是4294967295

java 复制代码
// 将一个或多个 member 元素加入到集合 key 当中,已经存在于集合的 member 元素将被忽略。
> SADD key member [member ...]
// 返回集合 key 中的所有成员。
> SMEMBERS key
// 返回集合 key 的基数(集合中元素的数量)。
> SCARD key
// 如果命令执行时,只提供了 key 参数,那么返回集合中的一个随机元素。
> SRANDMEMBER key [count]
// 移除并返回集合中的一个随机元素。
> SPOP key
// 移除集合 key 中的一个或多个 member 元素,不存在的 member 元素会被忽略。
> SREM key member [member ...]
// 判断 member 元素是否集合 key 的成员。
> SISMEMBER key member
// 获取前一个集合有而后面1个集合没有的
> sdiff huihuiset huihuiset1
// 获取交集
> sinter huihuiset huihuiset1
// 获取并集
> sunion huihuiset huihuiset1

zset(排序set)

Redis zset 和 set 一样也是string类型元素的集合,且不允许重复的成员

不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序

常用命令:zadd、zrange、zrem、zcard等

zset的成员是唯一的,但分数(score)却可以重复

java 复制代码
//将一个或多个 member 元素及其 score 值加入到有序集 key 当中。
> ZADD key score member [[score member] [score member] ...]
// 返回有序集 key 中,指定区间内的成员。其中成员的位置按 score 值递增(从小到大)来排序
> ZRANGE key start stop [WITHSCORES]
// 返回有序集 key 中,指定区间内的成员。其中成员的位置按 score 值递减(从大到小)来排列。
> ZREVRANGE key start stop [WITHSCORES]
// 返回有序集 key 中,所有 score 值介于 min 和 max 之间(包括等于 min 或 max )的成员。有序集成员按 score 值递增(从小到大)次序排列。
>  ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
// 移除有序集 key 中的一个或多个成员,不存在的成员将被忽略。
> ZREM key member [member ...]
// 返回有序集 key 的基数。
> ZCARD key
// 为有序集 key 的成员 member 的 score 值加上增量 increment 。
> ZINCRBY key increment member
// 返回有序集 key 中, score 值在 min 和 max 之间(默认包括 score 值等于 min 或 max )的成员的数量。
> ZCOUNT key min max
// 返回有序集 key 中成员 member 的排名。其中有序集成员按 score 值递增(从小到大)顺序排列。
> ZRANK key member

Hash(hash表)

Redis hash 是一个键名对集合

Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象

常用命令:hget、hset、hgetall等

每个 hash 可以存储 232 -1 键值对(40多亿)

java 复制代码
// 将哈希表 key 中的域 field 的值设为 value 。
> HSET key field value
// 返回哈希表 key 中给定域 field 的值。
>  HGET key field
// 返回哈希表 key 中的所有域。
> HKEYS key
// 返回哈希表 key 中所有域的值。
>  HVALS key
// 为哈希表 key 中的域 field 的值加上增量 increment 。
> HINCRBY key field increment
// 查看哈希表 key 中,给定域 field 是否存在。
> HEXISTS key field

三、特殊数据类型

geospatial(地理位置)

1.geospatial将指定的地理空间位置(纬度、经度、名称)添加到指定的key中。

这些数据将会存储到sorted set这样的目的是为了方便使用GEORADIUS或者GEORADIUSBYMEMBER命令对数据进行半径查询等操作。

2.sorted set使用一种称为Geohash的技术进行填充。经度和纬度的位是交错的,以形成一个独特的52位整数。

sorted set的double score可以代表一个52位的整数,而不会失去精度。(有兴趣的同学可以学习一下Geohash技术,使用二分法构建唯一的二进制串)

3.有效的经度是-180度到180度

有效的纬度是-85.05112878度到85.05112878度

命令 功能 描述
geoadd 添加地理位置 往key中添加地理位置的坐标(经度、纬度、位置名称)
geopos 查询经纬度 返回指定成员的经纬度参数
geodist 查询距离 返回指定2个成员之间的直线距离(默认单位:米)
georadius 附近的人 指定经纬度为中心,返回该中心指定半径内的成员
georadiusbymember 附近的人 等同georadius,上述是指定经纬度,这个是指定成员附近的人
geohash 地理位置坐标 返回Redis GEO 的地理位置的坐标

m 表示单位为米[默认值]

km 表示单位为千米

mi 表示单位为英里

ft 表示单位为英尺

  1. geoadd 向key中添加指定的地理位置:经度、纬度、位置名称 (添加位置)

geoadd key longitude latitude member [longitude ...]

geoadd {key} {longitude} {latitude} {member} [longitude latitude member...]

java 复制代码
// 添加北京(经纬度)、上海(经纬度)的位置到名为mycity的key中
127.0.0.1:6379> geoadd mycity 116.40 39.9 beijing 121.47 31.23 shanghai
(integer) 2
 
// 底层是zset存储方式,可以直接用zrange查询索引区间的所有成员
127.0.0.1:6379> zrange mycity 0 -1
1) "beijing"
2) "tianjin"
  1. geopos 返回指定成员的经纬度参数 (查询位置)

geopos key member1 member2...

geopos {key} {member} [member...]

java 复制代码
// 底层是zset存储方式,可以直接用zrange查询索引区间的所有成员
127.0.0.1:6379> zrange mycity 0 -1
1) "beijing"
2) "shanghai"
 
// 查询上海、北京、成都的经度纬度
127.0.0.1:6379> geopos mycity beijing shanghai tianjin
1) 1) "116.39999896287918091"
   2) "39.90000009167092543"
2) 1) "121.47000163793563843"
   2) "31.22999903975783553"
3) (nil)  # 不存在mycity中的城市位置,则返回nil
  1. geodist 返回指定2个成员之间的距离 (查询位置)

geodist key member1 member2... [m|km|ft|mi 单位可选]

geodist {key} {member1} {member2} [m|km|ft|mi ]

java 复制代码
// 指定单位的参数 unit 必须是以下单位的其中一个
// 返回上海到北京的直线距离,单位为:km千米
127.0.0.1:6379> geodist mycity beijing shanghai km
"1067.3788" 
 
// 返回北京到天津的直线距离,成都在mycity中不存在,返回nil
127.0.0.1:6379> geodist mycity beijing tianjin
(nil)
  1. georadius 以给定的经纬度为中心,返回指定半径内的成员 (查询附近的人)

georadius key longitude latitude radius [m|km|ft|mi 单位可选]

georadius {key} {longitude} {latitude} radius m|km|ft|mi

java 复制代码
# 返回经度120,纬度30 的周边500千米内的成员
127.0.0.1:6379> georadius mycity 120 30 500 km
1) "nanchang"
2) "hangzhou"
3) "shanghai"
4) "nanjing"
 
# 100千米内的成员
127.0.0.1:6379> georadius mycity 120 30 100 km
1) "hangzhou"
 
# 200千米内的成员
127.0.0.1:6379> georadius mycity 120 30 200 km
1) "hangzhou"
2) "shanghai"
 
# 200千米内的成员名称、120,30与该成员位置的直线距离数、经纬度参数
127.0.0.1:6379> georadius mycity 120 30 200 km withdist withcoord
1) 1) "hangzhou"
   2) "30.8146"  # 直线距离
   3) 1) "120.1600000262260437"  
      2) "30.2400003229490224"
2) 1) "shanghai"
   2) "196.2512"
   3) 1) "121.47000163793563843"
      2) "31.22999903975783553"
 
# 返回1个 200千米内的成员名称 (count num 指定显示num个)
127.0.0.1:6379> georadius mycity 120 30 200 km count 1
1) "hangzhou"
  1. georadiusbymember 指定某成员为中心,返回指定半径内的成员 (查询附近的人)

georadiusbymember key longitude latitude radius [m|km|ft|mi]

java 复制代码
# 返回上海的周边300千米内的成员名称
127.0.0.1:6379> georadiusbymember mycity shanghai 300 km
1) "hangzhou"
2) "shanghai"
3) "nanjing"
 
# 200千米内的成员名称
127.0.0.1:6379> georadiusbymember mycity shanghai 200 km
1) "hangzhou"
2) "shanghai"
 
# 返回上海的周边200千米内的成员名称、经纬度参数
127.0.0.1:6379> georadiusbymember mycity shanghai 200 km withcoord
1) 1) "hangzhou"
   2) 1) "120.1600000262260437"
      2) "30.2400003229490224"
2) 1) "shanghai"
   2) 1) "121.47000163793563843"
      2) "31.22999903975783553"
  1. geohash 获取一个或多个地理位置的hash值 (返回坐标hash值)

geohash key member1 member2...

java 复制代码
# 返回上海、北京、杭州的坐标hash
127.0.0.1:6379> geohash mycity shanghai beijing hangzhou
1) "wtw3sj5zbj0"
2) "wx4fbxxfke0"
3) "wtmkn31bfb0"

Hyperloglog(基数)

hyperloglog 是用来做基数统计的,其优点是:输入的提及无论多么大,hyperloglog使用的空间总是固定的12KB ,利用12KB,它可以计算2^64个不同元素的基数!非常节省空间!但缺点是估算的值,可能存在误差

这个结构可以非常省内存的去统计各种计数,比如注册 IP 数、每日访问 IP 数的页面实时UV、在线用户数,共同好友数等。

Redis HyperLogLog 是一种基数算法,可以用于估计一个集合中的元素数量。Redis HyperLog 的特点如:

Redis HyperLogLog 是一种基数算法,可以用于估计一个集合中的元素数量。

Redis HyperLogLog 的误差率很小,通常在 0.81% 左右。

Redis HyperLogLog 的空间占用很小,通常只需要几 KB 的空间。

java 复制代码
# 添加指定元素到 HyperLogLog 中
# PFADD key [element [element ...]]
127.0.0.1:6379> PFADD mykey1 a b c d e f g h i j
(integer) 1

# 返回给定 HyperLogLog 的基数估算值。
# PFCOUNT key [key ...]
127.0.0.1:6379> PFCOUNT mykey1
(integer) 10

# 将多个 HyperLogLog 合并为一个 HyperLogLog
# PFMERGE destkey sourcekey [sourcekey ...]
127.0.0.1:6379> PFADD mykey2 i j z x c v b n m
(integer) 1
127.0.0.1:6379> PFCOUNT mykey2
(integer) 9
127.0.0.1:6379> PFMERGE mykey3 mykey1 mykey2
OK
127.0.0.1:6379> PFCOUNT mykey3
(integer) 15

Bitmaps(位存储)

Redis提供的Bitmaps这个"数据结构"可以实现对位的操作。Bitmaps本身不是一种数据结构,实际上就是字符串,但是它可以对字符串的位进行操作。

Bitmap 即位图数据结构,都是操作二进制位来进行记录,只有0 和 1 两个状态。

比如:统计用户信息,活跃,不活跃! 登录,未登录! 打卡,不打卡! 两个状态的,都可以使用

可以把Bitmaps想象成一个以位为单位数组,数组中的每个单元只能存0或者1,数组的下标在bitmaps中叫做偏移量。单个bitmaps的最大长度是512MB,即2^32个比特位。

1.设置Bitmaps中某个偏移量的值(0或1)

java 复制代码
setbit key offset value

2.获取Bitmaps中某个偏移量的值

java 复制代码
getbit key offset

3.统计字符串从start字节到end字节比特值为1的数量

java 复制代码
bitcount key [start end]

4.对多个Bitmaps的and(交集)、or(并集)、not(非)、xor(异或)操作并将结果保存在destkey中

java 复制代码
bitop and(or/not/xor) destkey [key1 key2 ...]
java 复制代码
127.0.0.1:6379> SETBIT sign:Bob:202201 1 1 # 设置 这里的 1|0 是二进制
(integer) 0
127.0.0.1:6379> SETBIT sign:Bob:202201 2 1
(integer) 0
127.0.0.1:6379> SETBIT sign:Bob:202201 3 0
(integer) 0
127.0.0.1:6379> SETBIT sign:Bob:202201 4 1
(integer) 0
127.0.0.1:6379> SETBIT sign:Bob:202201 5 1
(integer) 0
127.0.0.1:6379> SETBIT sign:Bob:202201 6 1
(integer) 0
127.0.0.1:6379> SETBIT sign:Bob:202201 7 1
(integer) 0
127.0.0.1:6379> GETBIT sign:Bob:202201 2 # 获取某天状态
(integer) 1
127.0.0.1:6379> BITCOUNT sign:Bob:202201 # 统计 位 1 的数量
(integer) 6

对指定key按位进行交、并、非、异或操作,并把结果保存到destKey中

java 复制代码
127.0.0.1:6379> SETBIT sign:Bob:202201 1 1 # 设置 这里的 1|0 是二进制
(integer) 0
127.0.0.1:6379> SETBIT sign:Bob:202201 2 1
(integer) 0
127.0.0.1:6379> SETBIT sign:Bob:202201 3 0
(integer) 0
127.0.0.1:6379> SETBIT sign:Bob:202201 4 1
(integer) 0
127.0.0.1:6379> SETBIT sign:Bob:202201 5 1
(integer) 0
127.0.0.1:6379> SETBIT sign:Bob:202201 6 1
(integer) 0
127.0.0.1:6379> SETBIT sign:Bob:202201 7 1
(integer) 0
127.0.0.1:6379> GETBIT sign:Bob:202201 2 # 获取某天状态
(integer) 1
127.0.0.1:6379> BITCOUNT sign:Bob:202201 # 统计 位 1 的数量
(integer) 6

事务

java 复制代码
// 1. multi命令开启事务,exec命令提交事务
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> exec
1) OK
2) OK
// 2. 组队的过程中可以通过discard来放弃组队。
 127.0.0.1:6379> multi
OK
 127.0.0.1:6379(TX)> set k3 v3
QUEUED
 127.0.0.1:6379(TX)> set k4 v4
QUEUED
 127.0.0.1:6379(TX)> discard
OK

四、击穿

缓存已过期了,请求直奔数据库。

出现原因

  1. 热点Key过期

解决方案

查DB后同步Redis、Canal同步

  1. 设置热点key永不过期,但是非常占用空间对内存消耗也是极大。个人并不建议使用该方法,应该根据具体业务逻辑来操作。
  2. 加互斥锁:通过synchronized+双重检查机制 当发生reids穿透的时候,这时海量请求发送到数据库。保证了只有一个线程能够进行持久层数据查询,其它线程保持阻塞状态(可以让它们sleep几秒)。当这个进入数据库的线程查询出key对应的value时,我们再将其同步至redis的缓存当中,其它线程睡醒以后再重新去redis里边请求数据。
  3. 使用Timetask做一个定时任务 使用Timetask做定时,每隔一段时间对一些热点key进行数据库查询,将查询出的结果更新至redis中。前条件是不会给数据库过大的压力。

五、雪崩

大量的热点Key过期(Redis 宕机),请求全部打到DB,导致数据库极大压力飙升甚至宕机。

出现原因

  1. 大量Key同时失效
  2. Redis宕机

解决方案

过期时间添加随机值、接口限流

  1. 设置缓存时,随机初始化其失效时间
  2. 将不同的热点key放置到不同的节点上去
  3. 使用Timetask做一个定时任务,在失效之前重新刷redis缓存
  4. 时效设置成永不过期
  5. Redis高可用,搭建Redis集群(异地多活)
  6. 限流降级:在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
  7. 数据预热,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀 。

六、穿透

查询不存在的数据(一般为恶意请求),请求直奔数据库,缓存系统形同虚设,对数据库产生很大压力从而影响正常服务。

出现原因

  1. 特殊请求在查询一个不存在的数据,即Redis不存在且数据库也不存在。

解决方案

布隆过滤器、设置缺省值

  1. 布隆过滤器,可以理解成一个白名单或者黑名单,它的作用就是判断一个元素是否存在于这个过滤器。
    白名单: 过滤器里有数据库中所有的合法的参数key,请求经过布隆过滤器,布隆过滤器判断这个请求的key在不在过滤器,在就放行让请求进入redis,不在就直接return空数据。
    在数据写入数据库的同时将这个 ID 同步到到布隆过滤器中,当请求的 id 不存在布隆过滤器中则说明该请求查询的数据一定没有在数据库中保存,就不要去数据库查询了。
  2. DB即使空值也讲其在Redis设置一个缺省值(比如:None)并设置一个过期时间,再访问这个数据将会从Redis中访问,保护了持久层的数据库
    a. 很多多余的空值
    b. 缓存与DB不一致,请求Redis导致查询结果不正确(不是真实DB数据)
  3. 拉黑其IP
  4. 对请求的参数进行合法性校验,在判断其不合法的前提下直接return跳出

七、集群同步

1. 主从复制

主从复制是高可用Redis的基础,哨兵和cluster都是在主从复制基础上实现高可用的。主从复制主要实现了数据的多机备份,以及对于读操作的负载均衡和简单的故障恢复。

优势:

  1. 读写分离:如果只有一台服务器,读和写操作都在一台服务器上进行,这台服务器的压力就会很大。而使用主从复制可以达到读写分离效果,写操作都在master主服务器进行,写操作进行完成之后,把内容复制到它的从服务器去;读操作都在slave从服务器进行。
  2. 容灾快速恢复:如果主服务器中进行写操作之后,复制到了从服务器(从服务器一般有多台),如果第一台从服务器在读的过程中突然挂掉,就会切换到另外一台从服务器进行读操作,此即为容灾快速恢复。

缺陷:故障恢复无法自动化,写操作无法负载均衡,存储能力受到单机的限制。

主从复制模式就是,部署多台redis节点,其中只有一台节点是主节点(master),其他的节点都是从节点(slave),也叫备份节点(replica)。只有master节点提供数据的事务性操作(增删改),slave节点只提供读操作。所有slave节点的数据都是从master节点同步过来的。

2. 哨兵(sentinel)

哨兵模式即为反客为主的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库。

缺陷:故障恢复无法自动化,写操作无法负载均衡,存储能力受到单机的限制。

  1. 集群监控:负责监控 Redis master 和 slave 进程是否正常工作
  2. 消息通知:如果某个 Redis 实例出现故障,那么哨兵负责发送消息作为报警通知给管理员
  3. 故障转移:如果 master node 挂掉了,会自动转移到 slave node 上
  4. 配置中心:如果故障转移发生了,通知 client 客户端习新的 master 地址

3. 集群(Cluster)

通过集群,Redis解决了写操作无法负载均衡,以及存储能力受到单机限制的问题,实现了较为完善的高可用方案

主节点负责读写请求和集群信息的维护,从节点只进行主节点数据和状态信息的复制

  1. 数据分区

数据分区(或称数据分片)是集群最核心的功能(分布式)

集群将数据分散到多个节点,一方面突破了 Redis 单机内存大小的限制,存储容量大大增加,另一方面每个主节点都可以对外提供读服务和写服务,大大提高了集群的响应能力

Redis 单机内存大小受限问题,例如,如果单机内存太大,bgsave 和 bgrewriteaof 的 fork 操作可能导致主进程阻塞,主从环境下主机切换时可能导致从节点长时间无法提供服务,全量复制阶段主节点的复制缓冲区可能溢出

  1. 高可用

集群支持主从复制(模式)和主节点的自动故障转移(与哨兵类似),当任意节点发送故障时,集群仍然可以对外提供服务

  1. 数据分片

Redis 集群引入了哈希槽的概念,有 16384 个哈希槽(编号 0~16383)

集群的每个节点负责一部分哈希槽,每个 Key 通过 CRC16 校验后对 16384 取余来决定放置哪个哈希槽,通过这个值,去找到对应的插槽所对应的节点,然后直接自动跳转到这个对应的节点上进行存取操作

八、持久化

Redis是一种内存型数据库,一旦服务器进程退出,数据库的数据就会丢失,为了解决这个问题Redis供了两种持久化的方案,将内存中的数据保存到磁盘中,避免数据的丢失

两种持久化方式:快照(RDB文件)和追加式文件(AOF文件),下面分别为大家介绍两种方式的原理。

RDB(快照)

Redis中的RDB持久化方式,采用了写时复制技术(copy on write)和fork子进程。

RDB是Redis用来进行持久化的一种方式,是把当前内存中的数据集快照写入磁盘,也就是 Snapshot 快照(数据库中所有键值对数据),它可以手动执行,也可以在redis.conf配置文件中配置,定时执行,恢复时是将快照文件直接读到内存里。

Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,在用这个临时文件替换上次持久化好的文件.

整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能

如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效.

何时触发

自动触发

1. save

save:这里是用来配置触发 Redis的 RDB 持久化条件,也就是什么时候将内存中的数据保存到硬盘。比如"save m n"。表示m秒内数据集存在n次修改时,自动触发bgsave(这个命令下面会介绍,手动触发RDB持久化的命令)

当然如果你只是用Redis的缓存功能,不需要持久化,那么你可以注释掉所有的 save 行来停用保存功能。可以直接一个空字符串来实现停用:save ""

java 复制代码
save 900 1 900s检查一次,至少有1个key被修改就触发
save 300 10 300s检查一次,至少有10个key被修改就触发
save 60 10000 60s检查一次,至少有10000个key被修改
2. stop-writes-on-bgsave-error

默认值为yes。当启用了RDB且最后一次后台保存数据失败,Redis是否停止接收数据。这会让用户意识到数据没有正确持久化到磁盘上,否则没有人会注意到灾难(disaster)发生了。如果Redis重启了,那么又可以重新开始接收数据了

3. rdbcompression

默认值是yes。对于存储到磁盘中的快照,可以设置是否进行压缩存储。如果是的话,redis会采用LZF算法进行压缩。如果你不想消耗CPU来进行压缩的话,可以设置为关闭此功能,但是存储在磁盘上的快照会比较大。

4. rdbchecksum

默认值是yes。在存储快照后,我们还可以让redis使用CRC64算法来进行数据校验,但是这样做会增加大约10%的性能消耗,如果希望获取到最大的性能提升,可以关闭此功能。

5. dbfilename

设置快照的文件名,默认是 dump.rdb

6. dir

设置快照文件的存放路径,这个配置项一定是个目录,而不能是文件名。默认是和当前配置文件保存在同一目录。

也就是说通过在配置文件中配置的 save 方式,当实际操作满足该配置形式时就会进行 RDB 持久化,将当前的内存快照保存在 dir 配置的目录中,文件名由配置的 dbfilename 决定。

手动触发

1. save

该命令会阻塞当前Redis服务器,执行save命令期间,Redis不能处理其他命令,直到RDB过程完成为止。

显然该命令对于内存比较大的实例会造成长时间阻塞,这是致命的缺陷,为了解决此问题,Redis提供了第二种方式。

2. bgsave

执行该命令时,Redis会在后台异步进行快照操作,快照同时还可以响应客户端请求。具体操作是Redis进程执行fork操作创建子进程,RDB持久化过程由子进程负责,完成后自动结束。阻塞只发生在fork阶段,一般时间很短。

基本上 Redis 内部所有的RDB操作都是采用 bgsave 命令。

ps:执行执行 flushall 命令,也会产生dump.rdb文件,但里面是空的,无意义

a. shutdown正常关闭

任何组件在正常关闭的时候,都会去完成应该完成的事。比如Mysql 中的Redolog持久化,正常关闭的时候也会去持久化。

b. flushall指令触发

数据清空指令会触发RDB操作,并且是触发一个空的RDB文件,所以, 如果在没有开启其他的持久化的时候,flushall是可以删库跑路的,在生产环境慎用。

恢复数据

将备份文件 (dump.rdb) 移动到 redis 安装目录并启动服务即可,redis就会自动加载文件数据至内存了。Redis 服务器在载入 RDB 文件期间,会一直处于阻塞状态,直到载入工作完成为止。

获取 redis 的安装目录可以使用 config get dir 命令

停止 RDB 持久化

有些情况下,我们只想利用Redis的缓存功能,并不像使用 Redis 的持久化功能,那么这时候我们最好停掉 RDB 持久化。可以通过上面讲的在配置文件 redis.conf 中,可以注释掉所有的 save 行来停用保存功能或者直接一个空字符串来实现停用:save ""

也可以通过命令:

redis-cli config set save " "

优势

1.RDB是一个非常紧凑(compact)的文件,它保存了redis 在某个时间点上的数据集。这种文件非常适合用于进行备份和灾难恢复。

2.生成RDB文件的时候,redis主进程会fork()一个子进程来处理所有保存工作,主进程不需要进行任何磁盘IO操作。

3.RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。

劣势

  1. 如果你想保证数据的高可用性,即最大限度的避免数据丢失,那么RDB将不是一个很好的选择。因为数据完整性和一致性不高,因为RDB可能在最后一次备份时宕机,此前没有来得及写入磁盘的数据都将丢失,可能会丢失最后一次快照后的修改
  2. fork的时候,内存中数据被克隆了一份,需要考虑内存的使用,fork过程比较耗时,可能响应客户端的时间不能达到毫秒级,经常fork子进程,所以比较耗CPU,对CPU不是很友好
  3. 由于RDB是通过fork子进程来协助完成数据持久化工作的,因此,如果当数据集较大时,可能会导致整个服务器停止服务几百毫秒,甚至是1秒钟。
  4. DB方式数据没办法做到实时持久化/秒级持久化。因为bgsave每次运行都要执行fork操作创建子进程,属于重量级操作(内存中的数据被克隆了一份,大致2倍的膨胀性需要考虑),频繁执行成本过高(影响性能)
  5. RDB文件使用特定二进制格式保存,Redis版本演进过程中有多个格式的RDB版本,存在老版本Redis服务无法兼容新版RDB格式的问题(版本不兼容)

AOF

以日志的形式来记录每个写的操作,将Redis执行过的所有指令记录下来(读操作不记录),只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。

Always,同步写回:每个写命令执行完,立马同步地将日志写回磁盘;

Everysec,每秒写回:每个写命令执行完,只是先把日志写到 AOF 文件的内存缓冲区,每隔一秒把缓冲区中的内容写入磁盘;

No,操作系统控制的写回:每个写命令执行完,只是先把日志写到 AOF 文件的内存缓冲区,由操作系统决定何时将缓冲区内容写回磁盘。

配置项 写回时机 优点 缺点
Always 同步写回 可靠性高,数据基本不会丢失 每个写命令都要落盘,性能影响较大
Everysec 每秒写回 性能适中 宕机时丢失1秒内的数据
No 操作系统控制的写回 性能好 宕机时丢失数据较多

base:表示基础AOF,一般由子进程重写产生,该文件最多只有一个。

incr:表示增量AOF,一般会在AOFRW开始执行时被创建,该文件可有多个。

history:表示历史AOF,由base和incr变化而来,每次AOFRW成功完成时,本次AOFRW之前对应的base和incr都会变成history,history类型的AOF会被redis自动删除

优势

AOF相对RDB更加安全,一般不会有数据的丢失或者很少,官方推荐同时开启AOF和RDB。

如果系统是愿意牺牲一些性能,换取更高的缓存一致性(AOF)

或者是愿意写操作频繁的时候,不启用备份来换取更高的性能,待手动运行save的时候,再做备份(RDB)。

Redis允许同时开启AOF和RDB,既保证了数据安全又使得进行备份等操作十分容易。此时重新启动Redis后Redis会使用AOF文件来恢复数据,因为AOF方式的持久化可能丢失的数据更少。

  1. 更好的保护数据不丢失:使用默认每秒写回策略也最多只会丢失一秒钟的写入
  2. 性能高:AOF仅是一个附加日志,不会出现寻道问题,也不会在断电时出现损坏问题,并且多数问题redis-check-aof工具能轻松修复它
  3. 可做紧急恢复:例如最后一条命令是flushall,只需要打开增量文件把flushall删除,再重启服务,数据就恢复到flushall之前了

劣势

  1. AOF文件通常比相同数据集的等效RDB文件大
  2. AOF运行效率要慢于RDB,每秒同步策略效率较好,不同步效率和RDB相同

触发

AOF也有不同的触发方案,这里简要描述以下三种触发方案:

always:每次发生数据修改就会立即记录到磁盘文件中,这种方案的完整性好但是IO开销很大,性能较差;

everysec:在每一秒中进行同步,速度有所提升。但是如果在一秒内宕机的话可能失去这一秒内的数据;

no:默认配置,即不使用 AOF 持久化方案。

可以在redis.config中进行配置,appendonly no改换为yes,再通过注释或解注释appendfsync配置需要的方案

RDB与AOF混合持久化

  1. 数据恢复顺序和加载流程:
  2. 混合持久化中RDB持久化的必要性
    在混合持久化情况下,当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整。
    RDB的数据不实时,同时使用两者时服务器重启也只会找AOF文件。但是RDB更适合用于备份数据库(AOF在不断变化不好备份),留着rdb作为一个万一的手段。
  3. 混合持久化结合了RDB和AOF的优点,既能快速加载又能避免丢失过多的数据
    RDB镜像做全量持久化,AOF做增量持久化。
    先使用RDB进行快照存储,然后使用AOF持久化记录所有的写操作,当重写策略满足或手动触发重写的时候,将最新的数据存储为新的RDB记录。这样的话,重启服务的时候会从RDB和AOF两部分恢复数据,既保证了数据完整性,又提高了恢复数据的性能。
    简单来说:混合持久化方式产生的文件一部分是RDB格式,一部分是AOF格式。----> AOF包括了RDB头部+AOF混写

应用场景:做纯粹的高并发高性能缓存服务器时

关闭RDB:save ""

关闭AOF:appendonly no

同时关闭RDB+AOF,但在RDB+AOF都禁用的情况下,仍然可以使用save/bgsave命令生成rdb文件,仍可以使用bgrewriteaof命令生成aof文件

相关推荐
建投数据6 分钟前
建投数据与腾讯云数据库TDSQL完成产品兼容性互认证
数据库·腾讯云
Hacker_LaoYi1 小时前
【渗透技术总结】SQL手工注入总结
数据库·sql
岁月变迁呀1 小时前
Redis梳理
数据库·redis·缓存
独行soc1 小时前
#渗透测试#漏洞挖掘#红蓝攻防#护网#sql注入介绍06-基于子查询的SQL注入(Subquery-Based SQL Injection)
数据库·sql·安全·web安全·漏洞挖掘·hw
你的微笑,乱了夏天2 小时前
linux centos 7 安装 mongodb7
数据库·mongodb
工业甲酰苯胺2 小时前
分布式系统架构:服务容错
数据库·架构
独行soc3 小时前
#渗透测试#漏洞挖掘#红蓝攻防#护网#sql注入介绍08-基于时间延迟的SQL注入(Time-Based SQL Injection)
数据库·sql·安全·渗透测试·漏洞挖掘
White_Mountain3 小时前
在Ubuntu中配置mysql,并允许外部访问数据库
数据库·mysql·ubuntu
Code apprenticeship3 小时前
怎么利用Redis实现延时队列?
数据库·redis·缓存
百度智能云技术站3 小时前
广告投放系统成本降低 70%+,基于 Redis 容量型数据库 PegaDB 的方案设计和业务实践
数据库·redis·oracle