Java面试必问之-Redis

Nosql:非关系型数据库

  • 分表分库 + 水平拆分 + mysql 集群:

    • 在 Memcached 的高速缓存,Mysql 主从复制、读写分离的基础上,由于 MyISAM 使用表锁,高并发 Mysql 应用开始使用 InnoDB 引擎代替 MyISAM。现如今分表分库 + 水平拆分 + mysql 集群 已经成为解决缓解写压力和数据增长的问题的热门技术。
  • NoSQL 用于超大规模数据的存储。这些类型的数据存储不需要固定的模式,无需多余操作就可以横向扩展。

  • - 代表着不仅仅是 SQL
    - 没有声明性查询语言
    - 没有预定义的模式
    - 键 - 值对存储,列存储,文档存储,图形数据库
    - 最终一致性,而非 ACID 属性
    - 非结构化和不可预知的数据

    - 高性能,高可用性和可伸缩性

    - CAP 定理

  • CAP 定理:对于一个分布式计算系统来说,不可能同时满足以下三点:

    • 一致性 (Consistency) (所有节点在同一时间具有相同的数据)
    • 可用性 (Availability) (保证每个请求不管成功或者失败都有响应)
    • 分隔容忍 (Partition tolerance) (系统中任意信息的丢失或失败不会影响系统的继续运作)

    ​ CAP 理论的核心是:一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需求,最多只能同时较好的满足两个。

    ​ 因此,根据 CAP 原理将 NoSQL 数据库分成了满足 CA 原则、满足 CP 原则和满足 AP 原则三 大类:

    • CA - 单点集群,满足一致性,可用性的系统,通常在可扩展性上不太强大。
    • CP - 满足一致性,分区容忍性的系统,通常性能不是特别高。
    • AP - 满足可用性,分区容忍性的系统,通常可能对一致性要求低一些。
  • NoSQL 数据模型:

    • 聚合模型:(主要是前两个)
      • KV 键值
      • Bson:类似 JSON,可以在 Value 中放 JSON, 可分可合
      • 列族:每个字段放在一行
      • 图形
  • NoSQL 数据库四大分类:

    • KV 键值:典型介绍:阿里百度(memcache+redis),美团(redis+tair),新浪(BerkeleyDB+redis)
    • 文档型数据库:json 格式比较多,如 MongoDB(基于分布式文件存储数据库)
    • 列存储数据库:分布式文件系统、Cassandra、HBase
    • 图关系数据库:存放关系图,如:朋友圈社交网络、广告推荐系统、社交系统、推荐系统等,专注于构建关系图谱。Neo4J,InfoGrid

Redis(Remote Dictionary Server(远程字典服务器))

  • Redis = KV + Cache + persistence
  • 3V+3 高:海量 Volume、多样 Variety、实时 Velocity、高并发、高可扩、高性能
  • CAP(与关系型数据库中相对的是 ACID)(和 ACID 都要满足四个条件不同,CAP 最多只能三选二)
    • C:Consistency 强一致性。数据强一致性,强实时性
    • A:Avaliability 可用性。可以理解为网站运行的稳定性
    • P:Partition tolerance 分区容错性。(分布式系统必须要实现)
      • CA - 单点集群,满足一致性,可用性的系统,通常在可扩展性上不太强大。如 Oracle、Mysql
      • CP - 满足一致性,分区容忍性的系统,通常性能不是特别高。如 MongoDB、HBase、Redis
      • AP - 满足可用性,分区容忍性的系统,通常可能对一致性要求低一些。大多数网站架构的选择 AP+ 弱一致性

image-20200712132748264

  • BASE(牺牲 CAP 中的 C 换取 AP)
    • 基本可用(Basically Available)
    • 软状态(Soft state)
    • 最终一致(Eventually consistent)
  • 分布式 + 集群:
    • 分布式:不同的多台服务器上部署不同的服务模块(工程),他们之间通过 RPC/Rmi 之间通信和调用,对外提供服务和组内工作
    • 集群:不同的多台服务器上面部署相同的服务模块,通过分布式调度软件进行统一的调度,对外提供服务和访问
  • Redis 支持数据持久化,数据类型包括:KV、list、set、zset、hash 等数据结构的存储。Redis 支持 master-slave 模式的数据备份
  • Linux 中 Redis 开启服务:/usr/local/bin/redis-server /opt/myRedis/redis.conf
  • 启动 Redis 客户端:redis-cli -p 6379 6379 是 Redis 默认端口 退出:exit 或 ctrl+c 关闭服务:SHUTDOWN
  • ps -ef|grep redis :查看 redis 服务的是否启动
  • lsof -i :6379 :根据 redis 的端口查看 redis 进程
  • redis 默认安装在:/usr/local/bin
    • image-20200712143609109

Redis 常用命令

  • Redis 默认在 redis.conf 中配置 16 个数据库 0-15, select 7 :表示使用 6 号数据库,

  • set 和 get 能存放和取出数据, 其中 set key val 时 key 已经存在,则覆盖

  • keys k* : 查询以 k 开头的键 FLUSH:删除

  • keys * :查询所有 key,move k 2:将 k 移动到 2 号数据库

  • exists key:判断这个 key 是否在当前数据库中存在

  • expire key 1 : 设置 key 过期时间为 1 秒

  • ttl key : 查看还有多少秒过期,-1 表示永不过期,-2 表示已经过期

  • type key : 查看 key 的类型

  • del key:删除 key

Redis 五大数据类型

  • String:与 Memcached 一样的类型,即一个 key 对应一个 value。String 是二进制安全的,意思是可以包含任何数据类型。value 最多可以是 512M
    • STRLEN key : 查看 key 的长度 INCR key:该 key 的值 +1 DECR key 则 -1
    • INCRBY key 3 和 DECRBY key 3:分别是每次 +3 和每次 -3
    • GETRANGE key 0 -1 : 获取 key 的 value 并截取索引长度,0 -1 表示截取全部
    • SETRANGE key 0 val: 从该 key 的 value 的索引位置 0 开始插入值 val
    • setex key 10 val : 新增 key-val 同时设置过期时间为 10
    • setnx key val : 如果该 key 不存在,则新增 key-val ,如果 key 存在则该命令不生效
    • mset k1 v1 k2 v2 k3 v3: 插入多个数据
    • msetnx k1 v1 k2 v2 k3 v3 :若 k1 k2 k3 中有一个已经存在,则全部新增失败
  • Hash:类似 Java 的 Map,是一个 String 类型的 field 和 value 的映射表,hash 特别适合用于存储对象
    • HSET user id 11:插入键值对 id-11
    • HGET user id:获取 value
    • HMSET user id 11 name lisi age 18:一口气插入 HMGET user id name age
    • HGETALL user:查询所有映射关系
    • HDEL user name:删除
    • HLEN user:长度
    • HEXISTS user id:查询该表的 id 这个 key 是否存在
    • HKEYS user:查询所有 key HVALS user:查询所有 value
    • HINCRBY user age 2:key 为 age 的 value,每次 +2
    • HINCRBYFLOAD user age 0.5:key 为 age 的 value,每次 +0.5
    • HSETNX user age 16: 不存在 age 这个 key 才插入有效
  • List(列表):底层是个链表,是简单的字符串列表,按照插入顺序排序,可以头插也可以尾插。类似双向循环链表 LinkedList
    • LPUSH list01 1 2 3 4 :依次从最左边插入到 ilist01,结果为 4 3 2 1 , RPUSH 同理
    • LRANGE list01 0 -1: 查询 集合 list01 所有 value,0 和 -1 表示索引位置
    • lpop list01:表示 list01 的左边作为栈顶出栈一个值,同理 rpop 表示 list01 的右边作为栈顶出栈一个值
    • LINDEX list01 3 : 查询列表 list01 索引 3 的 value
    • LLEN list01 : 列表长度
    • LREM list01 2 3 : 删除 2 个索引为 3 的元素
    • LTRAM list01 3 5 : 截取索引为 3 到 5 的元素并重新覆盖 list01
    • RPOPLPUSH:list01 list02 : 相当于 RPOP list01 的结果 LPUSH 进 list02
    • LSET list01 1 x:从左边起索引为 1 的位置上插入 string 类型的 x
    • LINSERT list01 before/after x java:在 x 的左边 / 右边插入元素 java
  • Set (集合):是 string 类型的无序集合,不允许重复,是通过 HashTable 实现的
    • sadd set01 1 1 2 2 3 3:只添加 1 2 3
    • SMEMBERS set01 0 -1:查询 set01 集合,也可以用 SMEMBERS set01
    • SISMEMBER set01 x:判断 x 是否存在
    • SCART set01:获取集合的元素个数
    • SREM set01 3:删除集合中值为 ‘3’ 的元素
    • SRANDMEMBER:set01 3 :从 set01 中随机得到 3 个元素
    • SPOP set01:随机出栈一个元素
    • SMOVE set01 set02 5:将 set01 中 5 这个元素移动到 set02
    • DEL set01:删除集合
    • SDIFF set01 set02:输出 set01 中 set02 所没有的元素,即差集
    • SINTER set01 set02:输出交集
    • SUNION set01 set02:输出并集,
  • Zset (sortedSet 有序集合):是 string 类型的有序集合且不允许重复,每个元素都会关联一个 double 类型的分数,通过分数从小到大排序。zset 成员是唯一的,但分数可以重复
    • ZADD zset01 60 v1 70 v2:60 和 70 表示分数,越小排序越前
    • ZRANGE zset01 0 -1:查询所有 ZRANGE zset01 0 -1 WITHSCOPES:查询所有且带分数
    • ZRANGEBYSCOPE zset01 60 90:查询分数 60-90 的元素 ZRANGEBYSCOPE zset01 60 (90 : 查询分数 60-90 的元素但不包含 90
    • ZRANGEBYSCOPE zset01 60 90 limit 2 3:从分数 60-90 的元素中从索引为 2 的位置截取 3 个
    • ZREM zset01 v1:删除
    • ZCARD zset01 :查询元素个数 ZCOUNT zset01 60 80:查询分数在 60-90 元素个数
    • ZRANK zset01 v2:查询元素下标 ZSCOP zset01 v2:查询元素分数
    • ZREVRANK zset01 v2:逆序获得下标值 ZREVRANGE zset01 0 -1:逆序输出
    • ZREVRANGEBYSCOPE zset01 60-90:查询分数 60-90 的元素并逆序输出

redis.conf

  • redis.conf 默认安装在:/opt/myRedis

  • Units 单位:配置大小单位,定义了一些基本的度量单位,只支持 bytes 不支持 bit,对大小写不敏感

  • INCLUDES:包含其他配置文件,作为总闸

  • GENERAL:

    • daemonize yes:启用守护进程。设置 redis 关掉终端后,redis 服务和进程还在执行。

    • port 6379:默认端口

    • tcp-backlog 511:配置 tcp 的 backlog ,是一个连接队列,backlog 队列总和 = 未完成三次握手队列 + 已经完成三次握手队列,高并发环境下需要一个高 backlog 值来避免客户端连接慢问题

    • bind IP:设置连接的 IP

    • timeout 0:设置空闲多少秒后关闭 redis 连接,0 表示一直连接不关闭

    • Tcp-Keepalive:单位为秒,如果设置为 0,则不会进行 Keepalive 检测,建议设置为 60,作用类似心跳检测、判断每过一段时间检测是否还存活、使用中

    • loglevel debug/verbose/notice/warning :设置 log 日志级别,从左到右级别依次变高

    • database 16:设置数据库数量,默认为 16

    • Syslog-enabled:是否把日志输出到 syslog 中,默认不使用

    • Syslog-facility:指定 syslog 设备,值可以是 USER 或 LOCAL0-LOCAL7

  • SNAPSHOTTING 快照(RDB)

    • dbfilename dump.rdb : 默认快照备份的文件为 dump.rdb
    • stop-writes-on-bgsave-error yes :默认 yes 表示备份出错就立即停止写数据。如果配置成 no,表示不在乎数据不一致
    • rdbcompression yes : 对于存储到磁盘中的快照,可以设置是否进行压缩存储。默认 yes 表示采用 LZF 算法进行压缩 ,会消耗 cpu,建议开启
    • rdbchecksum yes: 在存储快照后,可以使用 CRC64 算法来进行数据检验,但会增大约 10% 的性能消耗,建议开启
    • dir : 设置快照的备份文件 dump.rdb 所在目录,config get dir 可以获得目录
  • REPLICATION 复制

  • SECURITY 安全:

    • config set requirepass "123456" : 设置密码,此时若直接 ping,则会失败,需要先输入密码:auth 123456
    • config get requirepass : 获取密码
  • LIMITS 限制:

    • Maxclients:最大同时连接数,默认 10000
    • Maxmemory:单位为字节,最大内存
    • Maxmemory-policy:缓存策略。默认是 noeviction 永不过期策略,但一般不使用该策略
      • image-20200712231742607
      • volatile-lru:使用 LRU(最近最少使用) 算法移除 key,只对设置了过期时间的键
      • allkeys-lru:使用 LRU 算法移除 key
      • volatile-random:在过期集合中移除随机的 key,只对设置了过期时间的 key
      • allkeys-random:移除随机的 key
      • volatile-ttl:移除那些 TTL 值最小的 key,即那些最近要过期的 key
      • noeviction:不进行移除,针对写操作,只是返回错误信息,一般不使用该策略
    • Maxmemory-samples:设置样本数量,LRU 算法和最小 TTL 算法都不是最精确的算法,而是估算值,所以你可以设置样本大小,redis 默认会检查这么多个 key 并选择其中 LRU 的那个。默认选取 5 个样本
  • APPEND ONLY MODE 追加(AOF)

    • appendonly no : 默认关闭 AOF
    • appendfilename "appendonly.aof" :日志记录保存到该文件, 数据修改一次,追加一次日志记录到该文件
    • appendfsync:3 中 AOF 持久化策略
      • aways:同步持久化,每次发生数据变更会被立即记录到磁盘,性能较差但数据完整性较好
      • everysec:出厂默认推荐使用,异步操作,每秒记录,如果一秒内宕机,会有数据丢失。
      • no:从不同步
    • No-appendfsync-on-rewrite:重写时是否可以运用 Appendfsync,默认 no,保证数据安全性
    • auto-aof-rewrite-percentage 100:百分百情况下 AOF 文件大小是上次 rewrite 后大小的一倍
    • auto-aof-rewrite-min-size 64mb:AOF 文件大于 64M 时触发重写机制。实际公司设置 3G 起步

常见 redis.conf 配置

参数说明
redis.conf 配置项说明如下:
1. Redis默认不是以守护进程的方式运行,可以通过该配置项修改,使用yes启用守护进程
  daemonize no
2. 当Redis以守护进程方式运行时,Redis默认会把pid写入/var/run/redis.pid文件,可以通过pidfile指定
  pidfile /var/run/redis.pid
3. 指定Redis监听端口,默认端口为6379,作者在自己的一篇博文中解释了为什么选用6379作为默认端口,因为6379在手机按键上MERZ对应的号码,而MERZ取自意大利歌女Alessia Merz的名字
  port 6379
4. 绑定的主机地址
  bind 127.0.0.1
5.当 客户端闲置多长时间后关闭连接,如果指定为0,表示关闭该功能
  timeout 300
6. 指定日志记录级别,Redis总共支持四个级别:debug、verbose、notice、warning,默认为verbose
  loglevel verbose
7. 日志记录方式,默认为标准输出,如果配置Redis为守护进程方式运行,而这里又配置为日志记录方式为标准输出,则日志将会发送给/dev/null
  logfile stdout
8. 设置数据库的数量,默认数据库为0,可以使用SELECT <dbid>命令在连接上指定数据库id
  databases 16
9. 指定在多长时间内,有多少次更新操作,就将数据同步到数据文件,可以多个条件配合
  save <seconds> <changes>
  Redis默认配置文件中提供了三个条件:
  save 900 1
  save 300 10
  save 60 10000
  分别表示900秒(15分钟)内有1个更改,300秒(5分钟)内有10个更改以及60秒内有10000个更改。

10. 指定存储至本地数据库时是否压缩数据,默认为 yes,Redis 采用 LZF 压缩,如果为了节省 CPU 时间,可以关闭该选项,但会导致数据库文件变的巨大
rdbcompression yes
11. 指定本地数据库文件名,默认值为 dump.rdb
dbfilename dump.rdb
12. 指定本地数据库存放目录
dir ./
13. 设置当本机为 slav 服务时,设置 master 服务的 IP 地址及端口,在 Redis 启动时,它会自动从 master 进行数据同步
slaveof <masterip> <masterport>
14. 当 master 服务设置了密码保护时,slav 服务连接 master 的密码
masterauth <master-password>
15. 设置 Redis 连接密码,如果配置了连接密码,客户端在连接 Redis 时需要通过 AUTH <password>命令提供密码,默认关闭
requirepass foobared
16. 设置同一时间最大客户端连接数,默认无限制,Redis 可以同时打开的客户端连接数为 Redis 进程可以打开的最大文件描述符数,如果设置 maxclients 0,表示不作限制。当客户端连接数到达限制时,Redis 会关闭新的连接并向客户端返回 max number of clients reached 错误信息
maxclients 128
17. 指定 Redis 最大内存限制,Redis 在启动时会把数据加载到内存中,达到最大内存后,Redis 会先尝试清除已到期或即将到期的 Key,当此方法处理 后,仍然到达最大内存设置,将无法再进行写入操作,但仍然可以进行读取操作。Redis 新的 vm 机制,会把 Key 存放内存,Value 会存放在 swap 区
maxmemory <bytes>
18. 指定是否在每次更新操作后进行日志记录,Redis 在默认情况下是异步的把数据写入磁盘,如果不开启,可能会在断电时导致一段时间内的数据丢失。因为 redis 本身同步数据文件是按上面 save 条件来同步的,所以有的数据会在一段时间内只存在于内存中。默认为 no
appendonly no
19. 指定更新日志文件名,默认为 appendonly.aof
appendfilename appendonly.aof
20. 指定更新日志条件,共有 3 个可选值:
no:表示等操作系统进行数据缓存同步到磁盘(快)
always:表示每次更新操作后手动调用 fsync() 将数据写到磁盘(慢,安全)
everysec:表示每秒同步一次(折衷,默认值)
appendfsync everysec

21. 指定是否启用虚拟内存机制,默认值为no,简单的介绍一下,VM机制将数据分页存放,由Redis将访问量较少的页即冷数据swap到磁盘上,访问多的页面由磁盘自动换出到内存中(在后面的文章我会仔细分析Redis的VM机制)
vm-enabled no
22. 虚拟内存文件路径,默认值为/tmp/redis.swap,不可多个Redis实例共享
vm-swap-file /tmp/redis.swap
23. 将所有大于vm-max-memory的数据存入虚拟内存,无论vm-max-memory设置多小,所有索引数据都是内存存储的(Redis的索引数据 就是keys),也就是说,当vm-max-memory设置为0的时候,其实是所有value都存在于磁盘。默认值为0
vm-max-memory 0
24. Redis swap文件分成了很多的page,一个对象可以保存在多个page上面,但一个page上不能被多个对象共享,vm-page-size是要根据存储的 数据大小来设定的,作者建议如果存储很多小对象,page大小最好设置为32或者64bytes;如果存储很大大对象,则可以使用更大的page,如果不 确定,就使用默认值
vm-page-size 32
25. 设置swap文件中的page数量,由于页表(一种表示页面空闲或使用的bitmap)是在放在内存中的,,在磁盘上每8个pages将消耗1byte的内存。
vm-pages 134217728
26. 设置访问swap文件的线程数,最好不要超过机器的核数,如果设置为0,那么所有对swap文件的操作都是串行的,可能会造成比较长时间的延迟。默认值为4
vm-max-threads 4
27. 设置在向客户端应答时,是否把较小的包合并为一个包发送,默认为开启
glueoutputbuf yes
28. 指定在超过一定的数量或者最大的元素超过某一临界值时,采用一种特殊的哈希算法
hash-max-zipmap-entries 64
hash-max-zipmap-value 512
29. 指定是否激活重置哈希,默认为开启(后面在介绍Redis的哈希算法时具体介绍)
activerehashing yes
30. 指定包含其它的配置文件,可以在同一主机上多个Redis实例之间使用同一份配置文件,而同时各个实例又拥有自己的特定配置文件
include /path/to/local.conf

持久化 RDB(Redis DataBase)

  • Redis 持久化包含 RDB(Redis DataBase 快照)和 AOF(Append Only File 日志)

  • RDB:在指定时间间隔内将内存中的数据快照写入磁盘,也就是 SnapShot 快照,它恢复时是将快照文件直接读到内存里,实现持久化

  • Redis 会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。在整个过程中主线程不进行任何 IO 操作,确保了极高的性能。

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

  • RDB 的缺点是最后一次持久化后的数据可能丢失

  • Fork 的作用是复制一个与当前进程一样的子进程。新进程的所有数据(变量、环境变量、程序计数器等)数值都和原进程一致

  • RDB 保存的是dump.rdb文件,命令是:save . 默认有以下 3 种保存策略 (可以自定义) , save "" 则是禁用该功能

    • 三个策略分别为:15 分钟内改了 1 次,5 分钟内改了 10 次,1 分钟内改了一万次,会触发快照条件从而备份文件,备份文件为 dump.rdb
    • image-20200713122608467
  • 可以在任何一个目录下使用 redis,所生成的 db 文件也保存在该目录

  • 一般备份 dump.rdb 时需要拷贝文件到另外的备机上,以防物理损坏

  • SHUTDOWN 关闭 redis 的同时,默认会 commit 并备份到 dump.rdb 中。slushall 命令也会产生 dump.rdb,但空文件无意义

  • 如何恢复数据?重启服务后,默认会将启动 redis 的当前目录下的备份文件 dump.rdb 恢复到 redis 数据库中

  • 手动备份命令:save 或者 bgsave。其中 save 只管保存,其他不管全部阻塞。BGSAVE:表示 redis 会在后台异步进行快照操作,快照同时还可以响应客户端请求,可以通过 lastsave 命令获取最后一次成功执行快照的时间。

  • config get dir 可以获得快照的备份文件 dump.rdb 所在目录

  • 优势:适合大规模数据恢复,对数据完整性和一致性要求不高。

  • 劣势:fork 时内存的数据被克隆了一份,大致 2 倍的膨胀性需要考虑内存空间是否足够大。最后一次备份的数据可能丢失

  • 动态停止所有 RDB 保存规则命令:redis-cli config set save ""。不建议使用

  • 配置文件里的 SNAPSHOTTING 快照(RDB)

    • dbfilename dump.rdb : 默认快照备份的文件为 dump.rdb
    • stop-writes-on-bgsave-error yes :默认 yes 表示备份出错就立即停止写数据。如果配置成 no,表示不在乎数据不一致
    • rdbcompression yes : 对于存储到磁盘中的快照,可以设置是否进行压缩存储。默认 yes 表示采用 LZF 算法进行压缩 ,会消耗 cpu,建议开启
    • rdbchecksum yes: 在存储快照后,可以使用 CRC64 算法来进行数据检验,但会增大约 10% 的性能消耗,建议开启
    • dir : 设置快照的备份文件 dump.rdb 所在目录,config get dir 可以获得目录
  • image-20200713131151648

持久化 AOF(Append Only File)

  • AOF 是在 RDB 之后产生,是以日志的形式记录每个写操作,将 Redsi 执行过的所有写指令记录,只需追加文件但不可以改写文件,redis 启动之初会读取该文件重新构建数据。换言之,redis 重启时会根据日志文件的内容将写指令从头到尾执行一次以完成数据的恢复工作

  • AOF 也是启动 redis 后自动执行日志文件 appendonly.aof 从而恢复数据。

  • /usr/local/bin/redis-server /myredis/redis_aof.conf :启动 Redis 服务并以 aof 文件恢复数据

  • dump.rdb 和 appendonly.aof 可以同时存在,先加载 appendonly.aof,若 aof 文件中有记录是错的,开启 redis 服务会失败。此时在 redis 的 bin 目录下使用命令:redis-check-aof --fix appendonly.aof 可查看 appendonly.aof 的错误信息并消除其中的不规范指令,才能启动 redis 服务

  • appendonly.aof 因为只追加写操作记录,因此容易内存膨胀,free 命令用于查看内存使用情况,df -h 查看磁盘空间

  • Rewrite:

    • AOF 的重写机制,当 AOF 文件大小超过所设定的阈值时,会启动 AOF 文件的内容压缩,只保留可以不恢复数据的最小指令集,可以使用命令 bgrewriteaof

    • 原理:AOF 文件持续增长而过大时,会 fork 出一条新进程来将文件重写 (先写临时文件最后再 rename),遍历新进程的内存中数据,每条记录有一条 Set 语句。重写 aof 文件的操作,并没有读取旧的 aof 文件,而是将整个内存中的数据库内容用命令的方法重写了一个新 aof 文件,类似快照。

    • 触发机制:redis 会记录上次重写时的 AOF 大小,默认配置是当 AOF 文件大小是上次 rewrite 后大小的一倍且文件大于 64M 时触发

  • (优势)对应在 redis.conf 的配置:APPEND ONLY MODE 追加(AOF)

    • appendonly no : 默认关闭 AOF
    • appendfilename "appendonly.aof" :日志记录保存到该文件, 数据修改一次,追加一次日志记录到该文件
    • appendfsync:3 中 AOF 持久化策略
      • aways:同步持久化,每次发生数据变更会被立即记录到磁盘,性能较差但数据完整性较好
      • everysec:出厂默认推荐使用,异步操作,每秒记录,如果一秒内宕机,会有数据丢失。
      • no:从不同步
    • No-appendfsync-on-rewrite:重写时是否可以运用 Appendfsync,默认 no,保证数据安全性
    • auto-aof-rewrite-percentage 100:百分百情况下 AOF 文件大小是上次 rewrite 后大小的一倍
    • auto-aof-rewrite-min-size 64mb:AOF 文件大于 64M 时触发重写机制。实际公司设置 3G 起步
  • 劣势:相同数据集的数据而言 aof 文件远大于 rdb 文件,恢复速度慢于 rdb。运行效率也慢,但每秒同步策略效率较好,不同步效率和 rdb 相同

  • 如果 Enalbe AOF,好处是在最恶劣情况下也只会丢失不超过两秒数据,启动脚本较简单只 load 自己的 AOF 文件就可以了。代价一是带来了持续的 IO,二是 AOF rewrite 的最后将 rewrite 过程中产生的新数据写到新文件造成的阻塞几乎是不可避免的。只要硬盘许可,应该尽量减少 AOF rewrite 的频率,AOF 重写的基础大小默认值 64M 太小了,可以设到 5G 以上。默认超过原大小 100% 大小时重写可以改到适当的数值。

    如果不 Enable AOF ,仅靠 Master-Slave Replication 实现高可用性也可以。能省掉一大笔 IO 也减少了 rewrite 时带来的系统波动。代价是如果 Master/Slave 同时倒掉,会丢失十几分钟的数据,启动脚本也要比较两个 Master/Slave 中的 RDB 文件,载入较新的那个。新浪微博就选用了这种架构

Redis 事务

  • 可以一次执行多个命令,本质是一组命令的集合,一个事务中的所有命令都会序列化,按顺序地串行化执行而不会被其他命令插入,不许加塞

  • 能干嘛?开启事务后每个指令都会加入一个队列中,一次性、顺序性、排他性地执行一系列命令

  • 常用命令

    • DISCARD:取消事务,放弃执行事务块内的所有命令
    • EXEC:执行所有事务块的命令,执行之后会一次性显示该次事务块中所有命令的结果
    • MULTI:标记一个事务块的开始
    • UNWATCH:取消 WATCH 命令对所有key 的监视
    • WATCH key [key...] :类似乐观锁,监视一个或多个 key,如果在事务 EXEC 执行之前这些 key 被其他命令所改动,那么事务将被打断,该事务提交无效
  • 若事务块中有无效命令 (错误指令) 则全部命令不生效,若事务块中都是有效命令但有些命令无法执行成功,则只有这些命令执行失败,其他命令无影响
  • watch 监控

    • 事务提交时,如果 key 已被其他客户端改变,那么整个事务队列都不会被执行,同时返回 Nullmulti-bulk 应答以通知调用者事务执行失败
    • 乐观锁 (常用):开启 WATCH 后,若有命令要修改数据,则在要修改的该行数据加一个字段:版本号 version,每次修改完成后缓存中该行的 version+1,以后修改该行的请求所带的版本不等于缓存中 version,则会报错。此时需要重新从缓存中获取最新数据后再修改才能提交成功,保证数据一致性
    • 悲观锁 (不常用):认为每次修改数据都会觉得会有别的请求也要修改,因此锁整张表
    • 一旦执行了 exec,之前加的监控锁都会被取消掉
    • CAS(Check And Set)
  • 事务 3 阶段:MULTI 开启事务 =》命令入队,此时还未被执行 =》EXEC 执行,触发事务

  • Redis 事务 3 特性:

    • 单独的隔离操作:事务中的所有命令都会被序列化、按顺序执行,事务执行过程中不会被其他客户端的命令请求打断
    • 没有隔离级别的概念:因为事务提交前任何指令都不会被实际执行
    • 不保证原子性;同一事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚

发布订阅机制

  • 进程间的一种消息通信模式:发送者(pub)发布消息,订阅者(sub)接收消息

  • 一次性订阅多个: SUBSCRIBE c1 c2 c3 c*

  • 消息发布: PUBLISH c2 hello-redis

主从复制(Master/Slave)

  • 配从机(库)、主机(库)

  • 从库配置:slaveof 主库 IP 主库端口

  • 配置从库时需修改配置文件,需修改:端口号、进程号 pidfile、日志 logfile、RDB 快照文件 dbfilename

  • 通常主从复制有 4 招(一般都用哨兵模式)

    • 一主二从:一个主机 2 个从机
    • 薪火相传:主 =》从 =》从。。。。若中途变更转向,会清除之前的数据,重新建立拷贝最新的数据. 命令:Slaveof 新主库 IP 新主库端口
    • 反客为主: SLAVEOF no one:使当前数据库停止与其他数据库的同步,转为主数据库
    • 哨兵模式:反客为主的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库
  • info replication 可以查看当前 redis 的详细信息,也可查看是主库还是从库

  • 读写分离:默认只有主机可以写,从机只能读
  • 默认当主机宕机或关闭 Redis 服务后,其他从机还是从机,即原地待命

  • 当从机宕机或关闭 Redis 服务,重新启动 Redis 后,需要重新连接主库,除非 redis.conf 已经有配置

  • Slave 启动成功连接到 master 后会发送一个 sync 命令

哨兵模式

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

  • 使用步骤:

    • 自定义 /myRedis 目录下新建 sentinel.conf

    • 配置主机监控:在 sentinel.conf 配置:sentinel monitor host6379 127.0.0.1 6379 1

    • 最后的数字 1 表示主机挂掉后 slave 投票,得票数的多少后成为主机

    • 启动哨兵:redis-sentinel /opt/myRedis/sentinel.conf

    • 当旧主机修复后重新启动,此时哨兵监控到后会将该旧主机转换为新主库的从库
  • 一组 sentinel 能同时监控多个 master

  • 主从复制的缺点:由于写操作都是先在 Master 进行,然后同步更新到 slave 上,所以主库同步到从库有一定延迟,Slave 数量增加,延迟会更加严重

Java 使用 Redis

  • 连接使用
	public static void main(String[] args) {
		Jedis jedis = new Jedis("127.0.0.1", 6379);
		jedis.set("k1", "v1");
		System.out.println(jedis.ping());
		Set<String> keys = jedis.keys("*");
	}
  • 事务
	public static void main(String[] args) {
		Jedis jedis = new Jedis("127.0.0.1", 6379);
		Transaction transaction = jedis.multi();
		//transaction.discard(); // 事务取消
		transaction.exec();
	}
  • 事务 -watch 监控 - 加锁
public class TestTransaction {

public boolean transMethod() {
Jedis jedis = new Jedis("127.0.0.1", 6379);
int balance;// 可用余额
int debt;// 欠额
int amtToSubtract = 10;// 实刷额度
jedis.watch("balance");// watch 监控某个字段
//jedis.set("balance","5");// 此句不该出现。模拟其他程序已经修改了该条目
Thread.sleep(7000); // 延时 7 秒,模拟高并发或网络延迟,在 7 秒的过程中有其它程序改变了 balance
balance = Integer.parseInt(jedis.get("balance"));
if (balance < amtToSubtract) {
jedis.unwatch();
System.out.println("modify");
return false;
} else {
System.out.println("***********transaction");
Transaction transaction = jedis.multi();
transaction.decrBy("balance", amtToSubtract);
transaction.incrBy("debt", amtToSubtract);
transaction.exec();
balance = Integer.parseInt(jedis.get("balance"));
debt = Integer.parseInt(jedis.get("debt"));

   System.out.println(<span class="hljs-string">"*******"</span> + balance);
   System.out.println(<span class="hljs-string">"*******"</span> + debt);
   <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
 }

}

/**

  • 通俗点讲,watch 命令就是标记一个键,如果标记了一个键, 在提交事务前如果该键被别人修改过,那事务就会失败,这种情况通常可以在程序中
  • 重新再尝试一次。
  • 首先标记了键 balance,然后检查余额是否足够,不足就取消标记,并不做扣减; 足够的话,就启动事务进行更新操作,
  • 如果在此期间键balance被其它人修改, 那在提交事务(执行exec)时就会报错, 程序中通常可以捕获这类错误再重新执行一次,直到成功。
    */
    public static void main(String[] args) {
    TestTransaction test = new TestTransaction();
    boolean retValue = test.transMethod();
    System.out.println("main retValue-------:" + retValue);
    }
    }
  • 主从复制(配从不配主)
public static void main(String[] args) throws InterruptedException 
  {
     Jedis jedis_M = new Jedis("127.0.0.1",6379);
     Jedis jedis_S = new Jedis("127.0.0.1",6380);
 jedis_S.slaveof(<span class="hljs-string">"127.0.0.1"</span>,<span class="hljs-number">6379</span>);
 
 jedis_M.<span class="hljs-keyword">set</span>(<span class="hljs-string">"k6"</span>,<span class="hljs-string">"v6"</span>);
 Thread.sleep(<span class="hljs-number">500</span>);
 System.<span class="hljs-keyword">out</span>.println(jedis_S.<span class="hljs-keyword">get</span>(<span class="hljs-string">"k6"</span>));

}

JedisPool(最常用)

  • JedisPoolUtil
public class JedisPoolUtil {
  private static volatile JedisPool jedisPool = null;// 被 volatile 修饰的变量不会被本地线程缓存,对该变量的读写都是直接操作共享内存。
  private JedisPoolUtil() {}  // 单例模式,构造方法私有化,无法 new 该实例对象

public static JedisPool getJedisPoolInstance() // 对外提供获得单实例 JedisPool 的方法
{
if(null == jedisPool)
{
synchronized (JedisPoolUtil.class) // 单例模式的双重校验锁
{
if(null == jedisPool)
{
JedisPoolConfig poolConfig = new JedisPoolConfig();
// pool 可分配的 jedis 实例,pool.getResource() 获取;如果为 -1,则表示不限制;如果 pool 已经分配了 maxActive 个 jedis 实例,则此时 pool 的状态 exhausted。
poolConfig.setMaxActive(1000);
// 控制一个 pool 最多有多少个状态为 idle(空闲) 的 jedis 实例;
poolConfig.setMaxIdle(32);
// 表示当 borrow 一个 jedis 实例时,最大的等待时间,如果超过等待时间,则直接抛 JedisConnectionException;
poolConfig.setMaxWait(100*1000);
// 获得一个 jedis 实例的时候是否检查连接可用性(ping());如果为 true,则得到的 jedis 实例均是可用的;
poolConfig.setTestOnBorrow(true);
// 带上配置连接 redis
jedisPool = new JedisPool(poolConfig,"127.0.0.1");
}
}
}
return jedisPool;
}

public static void release(JedisPool jedisPool,Jedis jedis)
{
if(null != jedis)
{
jedisPool.returnResourceObject(jedis);
}
}

缓存问题

缓存穿透

​ 缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为 id 为“-1”的数据或 id 为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。

解决方案:

  1. 接口层增加校验,如用户鉴权校验,id 做基础校验,id<=0 的直接拦截;
  2. 从缓存取不到的数据,在数据库中也没有取到,这时也可以将 key-value 对写为 key-null,缓存有效时间可以设置短点,如 30 秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个 id 暴力攻击

缓存击穿

​ 缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力

解决方案:

  1. 设置热点数据永远不过期。
  2. 加互斥锁,互斥锁参考代码如下:

image-20200916132938008

缓存雪崩

​ 缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至 down 机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。

解决方案

  1. 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
  2. 如果缓存数据库是分布式部署,将热点数据均匀分布在不同的缓存数据库中。
  3. 设置热点数据永远不过期。

面试问题

热点数据需要大批量更新,怎么解决?

可以把这些热点数据写在一个内存的临时表里,因为 innoDB 会维护一个 buffer pool,如果直接把大量数据全部读进去,可能会造成 flush 操作(把脏页刷会 MySQL),从而造成线上业务的阻塞。

也可以使用 Redis 进行缓存

如果热点数据太猛,Redis 可能因为带宽被打满,访问不到,如何解决?

  • 如果要缓存的数据过多或者缓存空间不够,可以使用缓存淘汰策略

  • 也可以使用本地缓存,在不考虑高并发情况下可以使用 HashMap

    如果是在高并发情况下,可以使用 ConcurrentHashMap 作为缓存集合,再配合缓存淘汰策略。

  • 还可以使用 guava cache 组件实现本地缓存,

Redis 的持久化机制是什么?各自的优缺点?

Redis 提供两种持久化机制 RDB 和 AOF 机制:

1、RDBRedis DataBase) 持久化方式:

是指用数据集快照的方式半持久化模式 ) 记录 redis 数据库的所有键值对, 在某个时间点将数据写入一个临时文件,持久化结束后,用这个临时文件替换上次持久化的文件,达到数据恢复。

优点:

(1)只有一个文件 dump.rdb,方便持久化。

(2)容灾性好,一个文件可以保存到安全的磁盘。

(3)性能最大化,fork 子进程来完成写操作,让主进程继续处理命令,所以是 IO 最大化。使用单独子进程来进行持久化,主进程不会进行任何 IO 操作,保证了 redis 的高性能 )

(4)相对于数据集大时,比 AOF 的启动效率更高。

缺点:

数据安全性低。RDB 是间隔一段时间进行持久化,如果持久化之间 redis 发生故障,会发生数据丢失。所以这种方式更适合数据要求不严谨的时候

2、AOFAppend-only file) 持久化方式:

是指所有的命令行记录以 redis 命令请求协议的格式完全持久化存储 ) 保存为 aof 文件。

优点:

(1)数据安全,aof 持久化可以配置 appendfsync 属性,有 always,每进行一次命令操作就记录到 aof 文件中一次。

(2)通过 append 模式写文件,即使中途服务器宕机,可以通过 redis-check-aof 工具解决数据一致性问题。

(3)AOF 机制的 rewrite 模式。AOF 文件没被 rewrite 之前(文件过大时会对命令进行合并重写),可以删除其中的某些命令(比如误操作的 flushall))

缺点:

(1)AOF 文件比 RDB 文件大,且恢复速度慢。

(2)数据集大的时候,比 rdb 启动效率低。

Redis 为什么这么快?

Redis 采用的是基于内存的采用的是单进程单线程模型的 KV 数据库,用”单线程 - 多路复用 IO 模型”来实现高性能的内存数据服务的,由 C 语言编写,官方提供的数据是可以达到 100000+ 的 QPS(每秒内查询次数)。这个数据不比采用单进程多线程的同样基于内存的 KV 数据库 Memcached 差

横轴是连接数,纵轴是 QPS

image-20200916132943502

1、完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中,类似 HashMap,HashMap 的优势就是查找和操作的时间复杂度都是 O(1);
2、数据结构简单,对数据操作也简单,Redis 中的数据结构是专门进行设计的;
3、采用单线程,避免了不必要的上下文切换和竞争条件,也避免了多线程之间的的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;
4、使用多路 I/O 复用模型,非阻塞 IO;
多路 I/O 复用模型;

多路 I/O 复用模型是利用 select、poll、epoll 可以同时监察多个流的 I/O 事件的能力,在空闲的时候,会把当前线程阻塞掉,当有一个或多个流有 I/O 事件时,就从阻塞态中唤醒,于是程序就会轮询一遍所有的流(epoll 是只轮询那些真正发出了事件的流),并且只依次顺序的处理就绪的流,这种做法就避免了大量的无用操作。

这里“多路”指的是多个网络连接,“复用”指的是复用同一个线程。采用多路 I/O 复用技术可以让单个线程高效的处理多个连接请求(尽量减少网络 IO 的时间消耗),且 Redis 在内存中操作数据的速度非常快,也就是说内存内的操作不会成为影响 Redis 性能的瓶颈。

5、底层实现方式以及与客户端之间通信的应用协议构建了 VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求;
VM(virtual memory) 机制:

Redis 使用到了 VM, 在 redis.conf 设置 vm-enabled yes 即开启 VM 功能。 通过 VM 功能可以实现冷热数据分离。使热数据仍在内存中,冷数据保存到磁盘。这样就可以避免因为内存不足而造成访问速度下降的问题。Redis 并没有使用 OS 提供的 Swap,而是自己实现。

相比于 OS 的交换方式。redis 可以将被交换到磁盘的对象进行压缩, 保存到磁盘的对象可以去除指针和对象元数据信息。一般压缩后对象会比内存中对象小 10 倍。这样 redis 的 vm 会比 OS vm 能少做很多 io 操作。OS 交换的时候,是会阻塞线程的,而 Redis 可以设置让工作线程来完成,主线程仍可以继续接收 client 的请求。

当前 VM 的坏处: slow restart 重启太慢 slow saving 保存数据太慢 slow replication 上面两条导致 replication 太慢 complex code 代码过于复杂

为什么 Redis 是单线程?

因为 Redis 是基于内存的操作,CPU 不是 Redis 的瓶颈,Redis 的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且 CPU 不会成为瓶颈,那就顺理成章地采用单线程的方案了,不采用多线程可以避免很多麻烦,如:避免了不必要的上下文切换和竞争条件,也避免了多线程之间的的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;

用”单线程 - 多路复用 IO 模型”来实现高性能的内存数据服务的,这种机制避免了使用锁,但是同时这种机制在进行 sunion 之类的比较耗时的命令时会使 redis 的并发下降。因为是单一线程,所以同一时刻只有一个操作在进行,所以,耗时的命令会导致并发的下降,不只是读并发,写并发也会下降。而单一线程也只能用到一个 CPU 核心,所以可以在同一个多核的服务器中,可以启动多个实例,组成 master-master 或者 master-slave 的主从复制形式,耗时的读命令可以完全在 slave 进行。可以通过修改 redis.conf 启动多个实例

pidfile /var/run/redis/redis_6377.pid  #pidfile 要加上端口号
port 6377  #这个是必须改的
logfile /var/log/redis/redis_6377.log #logfile 的名称也加上端口号
dbfilename dump_6377.rdb  #rdbfile 也加上端口号

同时有多个子系统去 set 一个 key。这个时候要注意什么呢? 不推荐使用 redis 的事务机制。因为我们的生产环境,基本都是 redis 集群环境,做了数据分片操作。你一个事务中有涉及到多个 key 操作的时候,这多个 key 不一定都存储在同一个 redis-server 上。因此,redis 的事务机制,十分鸡肋。
(1) 如果对这个 key 操作,不要求顺序: 准备一个分布式锁,大家去抢锁,抢到锁就做 set 操作即可
(2) 如果对这个 key 操作,要求顺序: 分布式锁 + 时间戳。 假设这会系统 B 先抢到锁,将 key1 设置为 {valueB 3:05}。接下来系统 A 抢到锁,发现自己的 valueA 的时间戳早于缓存中的时间戳,那就不做 set 操作了。以此类推。
(3) 利用队列,将 set 方法变成串行访问也可以 redis 遇到高并发,如果保证读写 key 的一致性
对 redis 的操作都是具有原子性的, 是线程安全的操作, 你不用考虑并发问题,redis 内部已经帮你处理好并发的问题了。

注意点:

“我们不能任由操作系统负载均衡,因为我们自己更了解自己的程序,所以,我们可以手动地为其分配 CPU 核,而不会过多地占用 CPU,或是让我们关键进程和一堆别的进程挤在一起。”。
CPU 是一个重要的影响因素,由于是单线程模型,Redis 更喜欢大缓存快速 CPU, 而不是多核

在多核 CPU 服务器上面,Redis 的性能还依赖 NUMA 配置和处理器绑定位置。最明显的影响是 redis-benchmark 会随机使用 CPU 内核。为了获得精准的结果,需要使用固定处理器工具(在 Linux 上可以使用 taskset)。最有效的办法是将客户端和服务端分离到两个不同的 CPU 来高校使用三级缓存。

Memcache 与 Redis 的区别都有哪些?

1)、存储方式 Memecache 把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小。 Redis 有部份存在硬盘上,redis 可以持久化其数据
2)、数据支持类型 memcached 所有的值均是简单的字符串,redis 作为其替代者,支持更为丰富的数据类型 ,提供 list,set,zset,hash 等数据结构的存储
3)、使用底层模型不同 它们之间底层实现方式 以及与客户端之间通信的应用协议不一样。 Redis 直接自己构建了 VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求。
4). value 值大小不同:Redis 最大可以达到 512M;memcache 只有 1mb。
5)redis 的速度比 memcached 快很多
6)Redis 支持数据的备份,即 master-slave 模式的数据备份。

redis 的数据类型,以及每种数据类型的使用场景

回答:一共五种
(一)String
这个其实没啥好说的,最常规的 set/get 操作,value 可以是 String 也可以是数字。一般做一些复杂的计数功能的缓存。
(二)hash
这里 value 存放的是结构化的对象,比较方便的就是操作其中的某个字段。博主在做单点登录的时候,就是用这种数据结构存储用户信息,以 cookieId 作为 key,设置 30 分钟为缓存过期时间,能很好的模拟出类似 session 的效果。
(三)list
使用 List 的数据结构,可以做简单的消息队列的功能。另外还有一个就是,可以利用 lrange 命令,做基于 redis 的分页功能,性能极佳,用户体验好。本人还用一个场景,很合适—取行情信息。就也是个生产者和消费者的场景。LIST 可以很好的完成排队,先进先出的原则。
(四)set
因为 set 堆放的是一堆不重复值的集合。所以可以做全局去重的功能。为什么不用 JVM 自带的 Set 进行去重?因为我们的系统一般都是集群部署,使用 JVM 自带的 Set,比较麻烦,难道为了一个做一个全局去重,再起一个公共服务,太麻烦了。
另外,就是利用交集、并集、差集等操作,可以计算共同喜好,全部的喜好,自己独有的喜好等功能。
(五)sorted set(zset)
sorted set 多了一个权重参数 score, 集合中的元素能够按 score 进行排列。可以做排行榜应用,取 TOP N 操作。

Redis 内部结构

  • dict:是一个用于维护 key 和 value 映射关系的数据结构,与很多语言中的 Map 或 dictionary 类似。 本质上是为了解决算法中的查找问题
  • sds: 等同于 char * 它可以存储任意二进制数据,不能像 C 语言字符串那样以字符’\0’来标识字符串的结 束,因此它必然有个长度字段。
  • skiplist: (跳跃表) 跳表是一种实现起来很简单,单层多指针的链表,它查找效率很高,堪比优化过的二叉平衡树
  • quicklist:采用双向链表 sdlist 和压缩表 ziplist 来实现快速列表 quicklist 的,其中 sdlist 充当 map 中控器的作用,ziplist 充当占用连续内存空间数组的作用。quicklist 本身是一个双向无环链表,它的每一个节点都是一个 ziplist。
  • ziplist: 压缩表 ziplist 是一个编码后的列表,是由一系列特殊编码的连续内存块组成的顺序型数据结构,

redis 的过期策略和配置以及内存淘汰机制

redis 采用的是定期删除 + 惰性删除策略
为什么不用定时删除策略?
定时删除, 用一个定时器来负责监视 key, 过期则自动删除。虽然内存及时释放,但是十分消耗 CPU 资源。在大并发请求下,CPU 要将时间应用在处理请求,而不是删除 key, 因此没有采用这一策略.
定期删除 + 惰性删除是如何工作的呢?
定期删除,redis 默认每个 100ms 检查,是否有过期的 key, 有过期 key 则删除。需要说明的是,redis 不是每个 100ms 将所有的 key 检查一次,而是随机抽取进行检查 (如果每隔 100ms, 全部 key 进行检查,redis 岂不是卡死)。因此,如果只采用定期删除策略,会导致很多 key 到时间没有删除。
于是,惰性删除派上用场。也就是说在你获取某个 key 的时候,redis 会检查一下,这个 key 如果设置了过期时间判断是否过期了?如果过期了此时就会删除。
采用定期删除 + 惰性删除就没其他问题了么?
不是的,如果定期删除没删除 key。然后你也没即时去请求 key,也就是说惰性删除也没生效。这样,redis 的内存会越来越高。那么就应该采用内存淘汰机制。
在 redis.conf 中有一行配置内存淘汰策略

maxmemory-policy volatile-lru

该配置就是配内存淘汰策略的(6 种)
volatile-lru:最近最少使用,从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
volatile-ttl:淘汰即将过期,从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
volatile-random:随机淘汰,从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
no-enviction(不淘汰):禁止驱逐数据,新写入操作会报错
ps:如果没有设置 expire 的 key, 不满足先决条件 (prerequisites); 那么 volatile-lru, volatile-random 和 volatile-ttl 策略的行为, 和 noeviction(不删除) 基本上一致。

使用策略规则:

(1)如果数据呈现幂律分布,也就是一部分数据访问频率高,一部分数据访问频率低,则使用 allkeys-lru

(2)如果数据呈现平等分布,也就是所有的数据访问频率都相同,则使用 allkeys-random

Redis 集群方案应该怎么做?都有哪些方案?

1.twemproxy,大概概念是,它类似于一个代理方式, 使用时在本需要连接 redis 的地方改为连接 twemproxy, 它会以一个代理的身份接收请求并使用一致性 hash 算法,将请求转接到具体 redis,将结果再返回 twemproxy。
缺点: twemproxy 自身单端口实例的压力,使用一致性 hash 后,对 redis 节点数量改变时候的计算值的改变,数据无法自动移动到新的节点。

2.codis,目前用的最多的集群方案,基本和 twemproxy 一致的效果,但它支持在 节点数量改变情况下,旧节点数据可恢复到新 hash 节点

3.redis cluster3.0 自带的集群,特点在于他的分布式算法不是一致性 hash,而是 hash 槽的概念,以及自身支持节点设置从节点

有没有尝试进行多机 redis 的部署?如何保证数据一致的?

主从复制,读写分离
一类是主数据库(master)一类是从数据库(slave),主数据库可以进行读写操作,当发生写操作的时候自动将数据同步到从数据库,而从数据库一般是只读的,并接收主数据库同步过来的数据,一个主数据库可以有多个从数据库,而一个从数据库只能有一个主数据库。

对于大量的请求怎么样处理

redis 是一个单线程程序,也就说同一时刻它只能处理一个客户端请求;
对于大量请求,redis 是通过 IO 多路复用(select,epoll, kqueue,依据不同的平台,采取不同的实现)来处理多个客户端请求的

Redis 常见性能问题和解决方案?

(1) Master 最好不要做任何持久化工作,如 RDB 内存快照和 AOF 日志文件
(2) 如果数据比较重要,某个 Slave 开启 AOF 备份数据,策略设置为每秒同步一次
(3) 为了主从复制的速度和连接的稳定性, Master 和 Slave 最好在同一个局域网内
(4) 尽量避免在压力很大的主库上增加从库
(5)主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1<- Slave2 <- Slave3…这样的结构方便解决单点故障问题,实现 Slave 对 Master 的替换。如果 Master 挂了,可以立刻启用 Slave1 做 Master,其他不变。

Redis 实现分布式锁

Redis 为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对 Redis 的连接并不存在竞争关系 Redis 中可以使用 SETNX 命令实现分布式锁。
将 key 的值设为 value ,当且仅当 key 不存在。 若给定的 key 已经存在,则 SETNX 不做任何动作
image-20200916132948763
解锁:使用 del key 命令就能释放锁
解决死锁:
1)通过 Redis 中 expire() 给锁设定最大持有时间,如果超过,则 Redis 来帮我们释放锁。
2) 使用 setnx key “当前系统时间 + 锁持有的时间”和 getset key “当前系统时间 + 锁持有的时间”组合的命令就可以实现。

Redis 事务

Redis 事务功能是通过 MULTI、EXEC、DISCARD 和 WATCH 四个原语实现的
Redis 会将一个事务中的所有命令序列化,然后按顺序执行。
1.redis 不支持回滚“Redis 在事务失败时不进行回滚,而是继续执行余下的命令”, 所以 Redis 的内部可以保持简单且快速。
2. 如果在一个事务中的命令出现错误,那么所有的命令都不会执行;
3. 如果在一个事务中出现运行错误,那么正确的命令会被执行。
注:redis 的 discard 只是结束本次事务, 正确命令造成的影响仍然存在.

1)MULTI 命令用于开启一个事务,它总是返回 OK。 MULTI 执行之后,客户端可以继续向服务器发送任意多条命令,这些命令不会立即被执行,而是被放到一个队列中,当 EXEC 命令被调用时,所有队列中的命令才会被执行。
2)EXEC:执行所有事务块内的命令。返回事务块内所有命令的返回值,按命令执行的先后顺序排列。 当操作被打断时,返回空值 nil 。
3)通过调用 DISCARD,客户端可以清空事务队列,并放弃执行事务, 并且客户端会从事务状态中退出。
4)WATCH 命令可以为 Redis 事务提供 check-and-set (CAS)行为。 可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务就不会执行,监控一直持续到 EXEC 命令。

为什么 Redis 的操作是原子性的,怎么保证原子性的?

对于 Redis 而言,命令的原子性指的是:一个操作的不可以再分,操作要么执行,要么不执行。
Redis 的操作之所以是原子性的,是因为 Redis 是单线程的。
Redis 本身提供的所有 API 都是原子操作,Redis 中的事务其实是要保证批量操作的原子性。
多个命令在并发中也是原子性的吗?
不一定, 将 get 和 set 改成单命令操作,incr 。使用 Redis 的事务,或者使用 Redis+Lua== 的方式实现.

讲解下 Redis 线程模型

文件事件处理器包括分别是套接字、 I/O 多路复用程序、 文件事件分派器(dispatcher)、 以及事件处理器。使用 I/O 多路复用程序来同时监听多个套接字, 并根据套接字目前执行的任务来为套接字关联不同的事件处理器。当被监听的套接字准备好执行连接应答(accept)、读取(read)、写入(write)、关闭(close)等操作时, 与操作相对应的文件事件就会产生, 这时文件事件处理器就会调用套接字之前关联好的事件处理器来处理这些事件。
I/O 多路复用程序负责监听多个套接字, 并向文件事件分派器传送那些产生了事件的套接字。
工作原理:
1)I/O 多路复用程序负责监听多个套接字, 并向文件事件分派器传送那些产生了事件的套接字。
尽管多个文件事件可能会并发地出现, 但 I/O 多路复用程序总是会将所有产生事件的套接字都入队到一个队列里面, 然后通过这个队列, 以有序(sequentially)、同步(synchronously)、每次一个套接字的方式向文件事件分派器传送套接字: 当上一个套接字产生的事件被处理完毕之后(该套接字为事件所关联的事件处理器执行完毕), I/O 多路复用程序才会继续向文件事件分派器传送下一个套接字。如果一个套接字又可读又可写的话, 那么服务器将先读套接字, 后写套接字.
image-20200916132956250

使用 Redis 有哪些好处?

(1)速度快,因为数据存在内存中,类似于 HashMap,HashMap 的优势就是查找和操作的时间复杂度都是 O1)

(2)支持丰富数据类型,支持 string,list,set,Zset,hash 等

(3)支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行

(4)丰富的特性:可用于缓存,消息,按 key 设置过期时间,过期后将会自动删除

Redis 相比 Memcached 有哪些优势?

(1)Memcached 所有的值均是简单的字符串,redis 作为其替代者,支持更为丰富的数据类

(2)Redis 的速度比 Memcached 快很多

(3)Redis 可以持久化其数据

redis 过期键的删除策略?

(1)定时删除: 在设置键的过期时间的同时,创建一个定时器 timer). 让定时器在键的过期时间来临时,立即执行对键的删除操作。

(2)惰性删除: 放任键过期不管,但是每次从键空间中获取键时,都检查取得的键是否过期,如果过期的话,就删除该键; 如果没有过期,就返回该键。

(3)定期删除: 每隔一段时间程序就对数据库进行一次检查,删除里面的过期键。至于要删除多少过期键,以及要检查多少个数据库,则由算法决定。

为什么 edis 需要把所有数据放到内存中?

答 :Redis 为了达到最快的读写速度将数据都读到内存中,并通过异步的方式将数据写入磁盘。所以 redis 具有快速和数据持久化的特征。如果不将数据放在内存中,磁盘 I/O 速度会严重影响 redis 的性能。如果设置了最大使用的内存,则数据已有记录数达到内存限值后不能继续插入新值。

Redis 的同步机制了解么?

答:Redis 可以使用主从同步,从从同步。第一次同步时,主节点做一次 bgsave(另开子进程执行),并同时将后续修改操作记录到内存 buffer,待完成后将 rdb 文件全量同步到复制节点,复制节点接受完成后将 rdb 镜像加载到内存。加载完成后,再通知主节点将期间修改的操作记录同步到复制节点进行重放就完成了同步过程。

Pipeline 有什么好处,为什么要用 pipeline?

答:可以将多次 IO 往返的时间缩减为一次,前提是 pipeline 执行的指令之间没有因果相关性。使用 redis-benchmark 进行压测的时候可以发现影响 redis 的 QPS 峰值的一个重要因素是 pipeline 批次指令的数目。

是否使用过 Redis 集群,集群的原理是什么?

(1)Redis Sentinal 着眼于高可用,在 master 宕机时会自动将 slave 提升为 master,继续提供服务。

(2)Redis Cluster 着眼于扩展性,在单个 redis 内存不足时,使用 Cluster 进行分片存储。

Redis 集群方案什么情况下会导致整个集群不可用?

答:有 A,B,C 三个节点的集群, 在没有复制模型的情况下, 如果节点 B 失败了,那么整个集群就会以为缺少 5501-11000 这个范围的槽而不可用。

Redis 支持的 Java 客户端都有哪些?官方推荐用哪个?

答:Redisson、Jedis、lettuce 等等,官方推荐使用 Redisson。

Jedis 是 Redis 的 Java 实现的客户端,其 API 提供了比较全面的 Redis 命令的支持;Redisson 实现了分布式和可扩展的 Java 数据结构,和 Jedis 相比,功能较为简单,不支持字符串操作,不支持排序、事务、管道、分区等 Redis 特性。

Redisson 的宗旨是促进使用者对 Redis 的关注分离,从而让使用者能够将精力更集中地放在处理业务逻辑上。

说说 Redis 哈希槽的概念?

答:Redis 集群没有使用一致性 hash, 而是引入了哈希槽的概念,Redis 集群有 16384 个哈希槽,每个 key 通过 CRC16 校验后对 16384 取模来决定放置哪个槽,集群的每个节点负责一部分 hash 槽。

Redis 集群的主从复制模型是怎样的?

答:为了使在部分节点失败或者大部分节点无法通信的情况下集群仍然可用,所以集群使用了主从复制模型, 每个节点都会有 N-1 个复制品.

Redis 集群会有写操作丢失吗?为什么?

答 :Redis 并不能保证数据的强一致性,这意味这在实际中集群在特定的条件下可能会丢失写操作。

Redis 集群之间是如何复制的?

答:异步复制

Redis 集群最大节点个数是多少?

答:16384 个桶 (哈希槽)。

Redis 集群没有使用一致性 hash, 而是引入了哈希槽的概念,当需要在 Redis 集群中放置一个 key-value 时,根据 CRC16(key) mod 16384 的值,决定将一个 key 放到哪个桶中。

Redis key 的过期时间和永久有效分别怎么设置?

答:EXPIRE 和 PERSIST 命令。

Redis 如何做内存优化?

答:尽可能使用散列表(hashes),散列表(是说散列表里面存储的数少)使用的内存非常小,所以你应该尽可能的将你的数据模型抽象到一个散列表里面。比如你的 web 系统中有一个用户对象,不要为这个用户的名称,姓氏,邮箱,密码设置单独的 key, 而是应该把这个用户的所有信息存储到一张散列表里面。

Redis 回收进程如何工作的?

答:一个客户端运行了新的命令,添加了新的数据。Redi 检查内存使用情况,如果大于 maxmemory 的限制, 则根据设定好的策略进行回收。一个新的命令被执行,等等。所以我们不断地穿越内存限制的边界,通过不断达到边界然后不断地回收回到边界以下。如果一个命令的结果导致大量内存被使用(例如很大的集合的交集保存到一个新的键),不用多久内存限制就会被这个内存使用量超越。

都有哪些办法可以降低 Redis 的内存使用情况呢?

答:如果你使用的是 32 位的 Redis 实例,可以好好利用 Hash,list,sorted set,set 等集合类型数据,因为通常情况下很多小的 Key-Value 可以用更紧凑的方式存放到一起。

Redis 的内存用完了会发生什么?

答:如果达到设置的上限,Redis 的写命令会返回错误信息(但是读命令还可以正常返回。)或者你可以将 Redis 当缓存来使用配置淘汰机制,当 Redis 达到内存上限时会冲刷掉旧的内容。

一个 Redis 实例最多能存放多少 keys?List、Set、Sorted Set 最多能存放多少元素?

答:理论上 Redis 可以处理多达 232 的 keys,并且在实际中进行了测试,每个实例至少存放了 2 亿 5 千万的 keys。我们正在测试一些较大的值。任何 list、set、和 sorted set 都可以放 232 个元素。换句话说,Redis 的存储极限是系统中的可用内存值。

MySQL 里有 2000w 数据,redis 中只存 20w 的数据,如何保证 redis 中的数据都是热点数据?

答:Redis 内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略。

Redis 提供 6 种数据淘汰策略:

volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰

volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰

volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰

allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰

allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰

no-enviction(驱逐):禁止驱逐数据

Redis 最适合的场景?

1、会话缓存(Session Cache)

最常用的一种使用 Redis 的情景是会话缓存(session cache)。用 Redis 缓存会话比其他存储(如 Memcached)的优势在于:Redis 提供持久化。当维护一个不是严格要求一致性的缓存时,如果用户的购物车信息全部丢失,大部分人都会不高兴的,现在,他们还会这样吗? 幸运的是,随着 Redis 这些年的改进,很容易找到怎么恰当的使用 Redis 来缓存会话的文档。甚至广为人知的商业平台 Magento 也提供 Redis 的插件。

2、全页缓存(FPC)

除基本的会话 token 之外,Redis 还提供很简便的 FPC 平台。回到一致性问题,即使重启了 Redis 实例,因为有磁盘的持久化,用户也不会看到页面加载速度的下降,这是一个极大改进,类似 PHP 本地 FPC。 再次以 Magento 为例,Magento 提供一个插件来使用 Redis 作为全页缓存后端。 此外,对 WordPress 的用户来说,Pantheon 有一个非常好的插件 wp-redis,这个插件能帮助你以最快速度加载你曾浏览过的页面。

3、队列

Reids 在内存存储引擎领域的一大优点是提供 list 和 set 操作,这使得 Redis 能作为一个很好的消息队列平台来使用。Redis 作为队列使用的操作,就类似于本地程序语言(如 Python)对 list 的 push/pop 操作。 如果你快速的在 Google 中搜索“Redis queues”,你马上就能找到大量的开源项目,这些项目的目的就是利用 Redis 创建非常好的后端工具,以满足各种队列需求。例如,Celery 有一个后台就是使用 Redis 作为 broker,你可以从这里去查看。

4,排行榜 / 计数器

Redis 在内存中对数字进行递增或递减的操作实现的非常好。集合(Set)和有序集合(Sorted Set)也使得我们在执行这些操作的时候变的非常简单,Redis 只是正好提供了这两种数据结构。所以,我们要从排序集合中获取到排名最靠前的 10 个用户–我们称之为“user_scores”,我们只需要像下面一样执行即可: 当然,这是假定你是根据你用户的分数做递增的排序。如果你想返回用户及用户的分数,你需要这样执行: ZRANGE user_scores 0 10 WITHSCORES Agora Games 就是一个很好的例子,用 Ruby 实现的,它的排行榜就是使用 Redis 来存储数据的,你可以在这里看到。

5、发布 / 订阅

最后(但肯定不是最不重要的)是 Redis 的发布 / 订阅功能。发布 / 订阅的使用场景确实非常多。我已看见人们在社交网络连接中使用,还可作为基于发布 / 订阅的脚本触发器,甚至用 Redis 的发布 / 订阅功能来建立聊天系统!

假如 Redis 里面有 1 亿个 key,其中有 10w 个 key 是以某个固定的已知的前缀开头的,如果将它们全部找出来?

答:使用 keys 指令可以扫出指定模式的 key 列表。

对方接着追问:如果这个 redis 正在给线上的业务提供服务,那使用 keys 指令会有什么问题?

这个时候你要回答 redis 关键的一个特性:redis 的单线程的。keys 指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。这个时候可以使用 scan 指令,scan 指令可以无阻塞的提取出指定模式的 key 列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用 keys 指令长。

如果有大量的 key 需要设置同一时间过期,一般需要注意什么?

答:如果大量的 key 过期时间设置的过于集中,到过期的那个时间点,redis 可能会出现短暂的卡顿现象。一般需要在时间上加一个随机值,使得过期时间分散一些。

使用过 Redis 做异步队列么,你是怎么用的?

答:一般使用 list 结构作为队列,rpush 生产消息,lpop 消费消息。当 lpop 没有消息的时候,要适当 sleep 一会再重试。如果对方追问可不可以不用 sleep 呢?list 还有个指令叫 blpop,在没有消息的时候,它会阻塞住直到消息到来。如果对方追问能不能生产一次消费多次呢?使用 pub/sub 主题订阅者模式,可以实现 1:N 的消息队列。

如果对方追问 pub/sub 有什么缺点?

在消费者下线的情况下,生产的消息会丢失,得使用专业的消息队列如 RabbitMQ 等。

如果对方追问 redis 如何实现延时队列?

我估计现在你很想把面试官一棒打死如果你手上有一根棒球棍的话,怎么问的这么详细。但是你很克制,然后神态自若的回答道:使用 sortedset,拿时间戳作为 score,消息内容作为 key 调用 zadd 来生产消息,消费者用 zrangebyscore 指令获取 N 秒之前的数据轮询进行处理。到这里,面试官暗地里已经对你竖起了大拇指。但是他不知道的是此刻你却竖起了中指,在椅子背后。

使用过 Redis 分布式锁么,它是什么回事?

先拿 setnx 来争抢锁,抢到之后,再用 expire 给锁加一个过期时间防止锁忘记了释放。

这时候对方会告诉你说你回答得不错,然后接着问如果在 setnx 之后执行 expire 之前进程意外 crash 或者要重启维护了,那会怎么样?这时候你要给予惊讶的反馈:唉,是喔,这个锁就永远得不到释放了。紧接着你需要抓一抓自己得脑袋,故作思考片刻,好像接下来的结果是你主动思考出来的,然后回答:我记得 set 指令有非常复杂的参数,这个应该是可以同时把 setnx 和 expire 合成一条指令来用的!对方这时会显露笑容,心里开始默念:摁,这小子还不错。

Redis 回收进程如何工作的?

一个客户端运行了新的命令,添加了新的数据。
Redi 检查内存使用情况,如果大于 maxmemory 的限制, 则根据设定好的策略进行回收。回收使用的算法:LRU 算法

Redis 常见性能问题和解决方案?

(1) Master 最好不要做任何持久化工作,如 RDB 内存快照和 AOF 日志文件
(2) 如果数据比较重要,某个 Slave 开启 AOF 备份数据,策略设置为每秒同步一次
(3) 为了主从复制的速度和连接的稳定性,Master 和 Slave 最好在同一个局域网内
(4) 尽量避免在压力很大的主库上增加从库
(5) 主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3...
这样的结构方便解决单点故障问题,实现 Slave 对 Master 的替换。如果 Master 挂了,可以立刻启用 Slave1 做 Master,其他不变。

Redis 数据分片模型

为了解决读写分离模型的缺陷,可以将数据分片模型应用进来。
可以将每个节点看成都是独立的 master,然后通过业务实现数据分片。
结合上面两种模型,可以将每个 master 设计成由一个 master 和多个 slave 组成的模型。

Redis 读写分离模型

通过增加 Slave DB 的数量,读的性能可以线性增长。为了避免 Master DB 的单点故障,集群一般都会采用两台 Master DB 做双机热备,所以整个集群的读和写的可用性都非常高。

读写分离架构的缺陷在于,不管是 Master 还是 Slave,每个节点都必须保存完整的数据,如果在数据量很大的情况下,集群的扩展能力还是受限于单个节点的存储能力,而且对于 Write-intensive 类型的应用,读写分离架构并不适合。

Redis 集群方案应该怎么做?都有哪些方案?

1.twemproxy
2.codis,目前用的最多的集群方案,基本和 twemproxy 一致的效果,但它支持在 节点数量改变情况下,旧节点数据可恢复到新 hash 节点。
3.Redis cluster3.0 自带的集,特点在于他的分布式算法不是一致性 hash,而是 hash 槽的概念,以及自身支持节点设置从节点。

Redis 单点吞吐量

单点 TPS 达到 8 万 / 秒,QPS 达到 10 万 / 秒,补充下 TPS 和 QPS 的概念

1.QPS: 应用系统每秒钟最大能接受的用户访问量
每秒钟处理完请求的次数,注意这里是处理完,具体是指发出请求到服务器处理完成功返回结果。可以理解在 server 中有个 counter,每处理一个请求加 1,1 秒后 counter=QPS。
2.TPS: 每秒钟最大能处理的请求数
每秒钟处理完的事务次数,一个应用系统 1s 能完成多少事务处理,一个事务在分布式处理中,可能会对应多个请求,对于衡量单个接口服务的处理能力,用 QPS 比较合理。

Redis 相比 memcached 有哪些优势?

1.memcached 所有的值均是简单的字符串,Redis 作为其替代者,支持更为丰富的数据类型
2.Redis 的速度比 memcached 快很多
3.Redis 可以持久化其数据
4.Redis 支持数据的备份,即 master-slave 模式的数据备份。