架构
Q1:Redis 有什么特点?
基于键值对的数据结构服务器
Redis 中的值不仅可以是字符串,还可以是具体的数据结构,这样不仅能应用于多种场景开发,也可以提高开发效率。它主要提供五种数据结构:字符串、哈希、列表、集合、有序集合,同时在字符串的基础上演变出了 Bitmaps 和 HyperLogLog 两种数据结构,Redis 3.2 还加入了有关 GEO 地理信息定位的功能。
丰富的功能
① 提供了键过期功能,可以实现缓存。② 提供了发布订阅功能,可以实现消息系统。③ 支持 Lua 脚本,可以创造新的 Redis 命令。④ 提供了简单的事务功能,能在一定程度上保证事务特性。⑤ 提供了流水线功能,客户端能将一批命令一次性传到 Redis,减少网络开销。
简单稳定
Redis 的简单主要体现在三个方面:① 源码很少,早期只有 2 万行左右,在 3.0 版本由于添加了集群特性,增加到了 5 万行左右,相对于很多 NoSQL 数据库来说代码量要少很多。② 采用单线程模型,使得服务端处理模型更简单,也使客户端开发更简单。③ 不依赖底层操作系统的类库,自己实现了事件处理的相关功能。虽然 Redis 比较简单,但也很稳定。
客户端语言多
Redis 提供了简单的 TCP 通信协议,很多编程语言可以方便地接入 Redis,例如 Java、PHP、Python、C、C++ 等。
持久化
通常来说数据放在内存中是不安全的,一旦发生断电或故障数据就可能丢失,因此 Redis 提供了两种持久化方式 RDB 和 AOF 将内存的数据保存到硬盘中。
高性能
Redis 使用了单线程架构和 IO 多路复用模型来实现高性能的内存数据库服务。
每次客户端调用都经历了发送命令、执行命令、返回结果三个过程,因为 Redis 是单线程处理命令的,所以一条命令从客户端到达服务器不会立即执行,所有命令都会进入一个队列中,然后逐个被执行。客户端的执行顺序可能不确定,但是可以确定不会有两条命令被同时执行,不存在并发问题。
通常来说单线程处理能力要比多线程差,Redis 快的原因:① 纯内存访问,Redis 将所有数据放在内存中。② 非阻塞 IO,Redis 使用 epoll 作为 IO 多路复用技术的实现,再加上 Redis 本身的事件处理模型将 epoll 中的连接、读写、关闭都转换为时间,不在网络 IO 上浪费过多的时间。③ 单线程避免了线程切换和竞争产生的消耗。单线程的一个问题是对于每个命令的执行时间是有要求的,如果某个命令执行时间过长会造成其他命令的阻塞,对于 Redis 这种高性能服务来说是致命的,因此 Redis 是面向快速执行场景的数据库。
Q2:Redis 的数据结构有哪些?
可以使用 type 命令查看当前键的数据类型结构,它们分别是:string、hash、list、set、zset,但这些只是 Redis 对外的数据结构。实际上每种数据结构都有自己底层的内部编码实现,这样 Redis 会在合适的场景选择合适的内部编码,string 包括了 raw、int 和 embstr,hash 包括了 hashtable 和 ziplist,list 包括了 linkedlist 和 ziplist,set 包括了 hashtable 和 intset,zset 包括了 skiplist 和 ziplist。可以使用 object encoding
查看内部编码。
Q3:Redis 为什么要使用内部编码?
① 可以改进内部编码,而对外的数据结构和命令没有影响。
② 多种内部编码实现可以在不同场景下发挥各自的优势,例如 ziplist 比较节省内存,但在列表元素较多的情况下性能有所下降,这时 Redis 会根据配置选项将列表类型的内部实现转换为 linkedlist。
string
Q1:简单说一说 string 类型
字符串类型是 Redis 最基础的数据结构,键都是字符串类型,而且其他几种数据结构都是在字符串类型的基础上构建的。字符串类型的值可以实际可以是字符串(简单的字符串、复杂的字符串如 JSON、XML)、数字(整形、浮点数)、甚至二进制(图片、音频、视频),但是值最大不能超过 512 MB。
Q2:你知道哪些 string 的命令?
设置值
set key value [ex seconds] [px millseconds] [nx|xx]
- ex seconds:为键设置秒级过期时间,跟 setex 效果一样
- px millseconds:为键设置毫秒级过期时间
- nx:键必须不存在才可以设置成功,用于添加,跟 setnx 效果一样。由于 Redis 的单线程命令处理机制,如果多个客户端同时执行,则只有一个客户端能设置成功,可以用作分布式锁的一种实现。
- xx:键必须存在才可以设置成功,用于更新
获取值
get key
,如果不存在返回 nil
批量设置值
mset key value [key value...]
批量获取值
mget key [key...]
批量操作命令可以有效提高开发效率,假如没有 mget,执行 n 次 get 命令需要 n 次网络时间 + n 次命令时间,使用 mget 只需要 1 次网络时间 + n 次命令时间。Redis 可以支持每秒数万的读写操作,但这指的是 Redis 服务端的处理能力,对于客户端来说一次命令处理命令时间还有网络时间。因为 Redis 的处理能力已足够高,对于开发者来说,网络可能会成为性能瓶颈。
计数
incr key
incr 命令用于对值做自增操作,返回结果分为三种:① 值不是整数返回错误。② 值是整数,返回自增后的结果。③ 值不存在,按照值为 0 自增,返回结果 1。除了 incr 命令,还有自减 decr、自增指定数字 incrby、自减指定数组 decrby、自增浮点数 incrbyfloat。
Q3:string 的内部编码是什么?
- int:8 个字节的长整形
- embstr:小于等于 39 个字节的字符串
- raw:大于 39 个字节的字符串
Q4:string 的应用场景有什么?
缓存功能
Redis 作为缓存层,MySQL 作为存储层,首先从 Redis 获取数据,如果失败就从 MySQL 获取并将结果写回 Redis 并添加过期时间。
计数
Redis 可以实现快速计数功能,例如视频每播放一次就用 incy 把播放数加 1。
共享 Session
一个分布式 Web 服务将用户的 Session 信息保存在各自服务器,但会造成一个问题,出于负载均衡的考虑,分布式服务会将用户的访问负载到不同服务器上,用户刷新一次可能会发现需要重新登陆。为解决该问题,可以使用 Redis 将用户的 Session 进行集中管理,在这种模式下只要保证 Redis 是高可用和扩展性的,每次用户更新或查询登录信息都直接从 Redis 集中获取。
限速
例如为了短信接口不被频繁访问会限制用户每分钟获取验证码的次数或者网站限制一个 IP 地址不能在一秒内访问超过 n 次。可以使用键过期策略和自增计数实现。
hash
Q1:简单说一说 hash 类型
哈希类型指键值本身又是一个键值对结构,哈希类型中的映射关系叫 field-value,这里的 value 是指 field 对于的值而不是键对于的值。
Q2:你知道哪些 hash 的命令?
设置值
hset key field value
,如果设置成功会返回 1,反之会返回 0,此外还提供了 hsetnx 命令,作用和 setnx 类似,只是作用于由键变为 field。
获取值
hget key field
,如果不存在会返回 nil。
删除 field
hdel key field [field...]
,会删除一个或多个 field,返回结果为删除成功 field 的个数。
计算 field 个数
hlen key
批量设置或获取 field-value
hmget key field [field...]``hmset key field value [field value...]
判断 field 是否存在
hexists key field
,存在返回 1,否则返回 0。
获取所有的 field
hkeys key
,返回指定哈希键的所有 field。
获取所有 value
hvals key
,获取指定键的所有 value。
获取所有的 field-value
hgetall key
,获取指定键的所有 field-value。
Q3:hash 的内部编码是什么?
ziplist 压缩列表:当哈希类型元素个数和值小于配置值(默认 512 个和 64 字节)时会使用 ziplist 作为内部实现,使用更紧凑的结构实现多个元素的连续存储,在节省内存方面比 hashtable 更优秀。
hashtable 哈希表:当哈希类型无法满足 ziplist 的条件时会使用 hashtable 作为哈希的内部实现,因为此时 ziplist 的读写效率会下降,而 hashtable 的读写时间复杂度都为 O(1)。
Q4:hash 的应用场景有什么?
缓存用户信息,每个用户属性使用一对 field-value,但只用一个键保存。
优点:简单直观,如果合理使用可以减少内存空间使用。
缺点:要控制哈希在 ziplist 和 hashtable 两种内部编码的转换,hashtable 会消耗更多内存。
list
Q1:简单说一说 list 类型
list 是用来存储多个有序的字符串,列表中的每个字符串称为元素,一个列表最多可以存储 232-1 个元素。可以对列表两端插入(push)和弹出(pop),还可以获取指定范围的元素列表、获取指定索引下标的元素等。列表是一种比较灵活的数据结构,它可以充当栈和队列的角色,在实际开发中有很多应用场景。
list 有两个特点:① 列表中的元素是有序的,可以通过索引下标获取某个元素或者某个范围内的元素列表。② 列表中的元素可以重复。
Q2:你知道哪些 list 的命令?
添加
从右边插入元素:rpush key value [value...]
从左到右获取列表的所有元素:lrange 0 -1
从左边插入元素:lpush key value [value...]
向某个元素前或者后插入元素:linsert key before|after pivot value
,会在列表中找到等于 pivot 的元素,在其前或后插入一个新的元素 value。
查找
获取指定范围内的元素列表:lrange key start end
,索引从左到右的范围是 0N-1,从右到左是 -1-N,lrange 中的 end 包含了自身。
获取列表指定索引下标的元素:lindex key index
,获取最后一个元素可以使用 lindex key -1
。
获取列表长度:llen key
删除
从列表左侧弹出元素:lpop key
从列表右侧弹出元素:rpop key
删除指定元素:lrem key count value
,如果 count 大于 0,从左到右删除最多 count 个元素,如果 count 小于 0,从右到左删除最多个 count 绝对值个元素,如果 count 等于 0,删除所有。
按照索引范围修剪列表:ltrim key start end
,只会保留 start ~ end 范围的元素。
修改
修改指定索引下标的元素:lset key index newValue
。
阻塞操作
阻塞式弹出:blpop/brpop key [key...] timeout
,timeout 表示阻塞时间。
当列表为空时,如果 timeout = 0,客户端会一直阻塞,如果在此期间添加了元素,客户端会立即返回。
如果是多个键,那么brpop会从左至右遍历键,一旦有一个键能弹出元素,客户端立即返回。
如果多个客户端对同一个键执行 brpop,那么最先执行该命令的客户端可以获取弹出的值。
Q3:list 的内部编码是什么?
ziplist 压缩列表:跟哈希的 zipilist 相同,元素个数和大小小于配置值(默认 512 个和 64 字节)时使用。
linkedlist 链表:当列表类型无法满足 ziplist 的条件时会使用linkedlist。
Redis 3.2 提供了 quicklist 内部编码,它是以一个 ziplist 为节点的 linkedlist,它结合了两者的优势,为列表类提供了一种更为优秀的内部编码实现。
Q4:list 的应用场景有什么?
消息队列
Redis 的 lpush + brpop 即可实现阻塞队列,生产者客户端使用 lpush 从列表左侧插入元素,多个消费者客户端使用 brpop 命令阻塞式地抢列表尾部的元素,多个客户端保证了消费的负载均衡和高可用性。
文章列表
每个用户有属于自己的文章列表,现在需要分页展示文章列表,就可以考虑使用列表。因为列表不但有序,同时支持按照索引范围获取元素。每篇文章使用哈希结构存储。
lpush + lpop = 栈、lpush + rpop = 队列、lpush + ltrim = 优先集合、lpush + brpop = 消息队列。
set
Q1:简单说一说 set 类型
集合类型也是用来保存多个字符串元素,和列表不同的是集合不允许有重复元素,并且集合中的元素是无序的,不能通过索引下标获取元素。一个集合最多可以存储 232-1 个元素。Redis 除了支持集合内的增删改查,还支持多个集合取交集、并集、差集。
Q2:你知道哪些 set 的命令?
添加元素
sadd key element [element...]
,返回结果为添加成功的元素个数。
删除元素
srem key element [element...]
,返回结果为成功删除的元素个数。
计算元素个数
scard key
,时间复杂度为 O(1),会直接使用 Redis 内部的遍历。
判断元素是否在集合中
sismember key element
,如果存在返回 1,否则返回 0。
随机从集合返回指定个数个元素
srandmember key [count]
,如果不指定 count 默认为 1。
从集合随机弹出元素
spop key
,可以从集合中随机弹出一个元素。
获取所有元素
smembers key
求多个集合的交集/并集/差集
sinter key [key...]
sunion key [key...]
sdiff key [key...]
保存交集、并集、差集的结果
sinterstore/sunionstore/sdiffstore destination key [key...]
集合间运算在元素较多情况下比较耗时,Redis 提供这三个指令将集合间交集、并集、差集的结果保存在 destination key 中。
Q3:set 的内部编码是什么?
intset 整数集合:当集合中的元素个数小于配置值(默认 512 个时),使用 intset。
hashtable 哈希表:当集合类型无法满足 intset 条件时使用 hashtable。当某个元素不为整数时,也会使用 hashtable。
Q4:set 的应用场景有什么?
set 比较典型的使用场景是标签,例如一个用户可能与娱乐、体育比较感兴趣,另一个用户可能对例时、新闻比较感兴趣,这些兴趣点就是标签。这些数据对于用户体验以及增强用户黏度比较重要。
sadd = 标签、spop/srandmember = 生成随机数,比如抽奖、sadd + sinter = 社交需求。
zset
Q1:简单说一说 zset 类型
有序集合保留了集合不能有重复成员的特性,不同的是可以排序。但是它和列表使用索引下标作为排序依据不同的是,他给每个元素设置一个分数(score)作为排序的依据。有序集合提供了获取指定分数和元素查询范围、计算成员排名等功能。
Q2:你知道哪些 zset 的命令?
添加成员
zadd key score member [score member...]
,返回结果是成功添加成员的个数
Redis 3.2 为 zadd 命令添加了 nx、xx、ch、incr 四个选项:
- nx:member 必须不存在才可以设置成功,用于添加。
- xx:member 必须存在才能设置成功,用于更新。
- ch:返回此次操作后,有序集合元素和分数变化的个数。
- incr:对 score 做增加,相当于 zincrby。
zadd 的时间复杂度为 O(logn),sadd 的时间复杂度为 O(1)。
计算成员个数
zcard key
,时间复杂度为 O(1)。
计算某个成员的分数
zscore key member
,如果不存在则返回 nil。
计算成员排名
zrank key member
,从低到高返回排名。
zrevrank key member
,从高到低返回排名。
删除成员
zrem key member [member...]
,返回结果是成功删除的个数。
增加成员的分数
zincrby key increment member
返回指定排名范围的成员
zrange key start end [withscores]
,从低到高返回
zrevrange key start end [withscores]
, 从高到底返回
返回指定分数范围的成员
zrangebyscore key min max [withscores] [limit offset count]
,从低到高返回
zrevrangebyscore key min max [withscores] [limit offset count]
, 从高到底返回
返回指定分数范围成员个数
zcount key min max
删除指定分数范围内的成员
zremrangebyscore key min max
交集和并集
zinterstore/zunionstore destination numkeys key [key...] [weights weight [weight...]] [aggregate sum|min|max]
destination
:交集结果保存到这个键numkeys
:要做交集计算键的个数key
:需要做交集计算的键weight
:每个键的权重,默认 1aggregate sum|min|max
:计算交集后,分值可以按和、最小值、最大值汇总,默认 sum。
Q3:zset 的内部编码是什么?
ziplist 压缩列表:当有序集合元素个数和值小于配置值(默认128 个和 64 字节)时会使用 ziplist 作为内部实现。
skiplist 跳跃表:当 ziplist 不满足条件时使用,因为此时 ziplist 的读写效率会下降。
Q4:zset 的应用场景有什么?
有序集合的典型使用场景就是排行榜系统,例如用户上传了一个视频并获得了赞,可以使用 zadd 和 zincrby。如果需要将用户从榜单删除,可以使用 zrem。如果要展示获取赞数最多的十个用户,可以使用 zrange。
Q5:zset数据结构的跳表怎么实现的?
通过在结点中存储其他层级结点的指针, 达到可以对有序链表二分查找的目的
插入结点时, 通过random方法保证插入结点每升一级都有1/2的可能, 从而保证每一层级都有下一层的1/2
详情参考文章:
http://zrkworld.com/archives/tiao-biao-de-ji-ben-gai-nian-yi-ji-dai-ma-shi-xian-xiang-jie
键和数据库管理
Q1:如何对键重命名?
rename key newkey
如果 rename 前键已经存在,那么它的值也会被覆盖。为了防止强行覆盖,Redis 提供了 renamenx 命令,确保只有 newkey 不存在时才被覆盖。由于重命名键期间会执行 del 命令删除旧的键,如果键对应值比较大会存在阻塞的可能。
Q2:如何设置键过期?
expire key seconds
:键在 seconds 秒后过期。
如果过期时间为负值,键会被立即删除,和 del 命令一样。persist 命令可以将键的过期时间清除。
对于字符串类型键,执行 set 命令会去掉过期时间,set 命令对应的函数 setKey 最后执行了 removeExpire 函数去掉了过期时间。setex 命令作为 set + expire 的组合,不单是原子执行并且减少了一次网络通信的时间。
Q3:如何进行键迁移?
-
move
move 命令用于在 Redis 内部进行数据迁移,
move key db
把指定的键从源数据库移动到目标数据库中。 -
dump + restore
可以实现在不同的 Redis 实例之间进行数据迁移,分为两步:
①
dump key
,在源 Redis 上,dump 命令会将键值序列化,格式采用 RDB 格式。②
restore key ttl value
,在目标 Redis 上,restore 命令将序列化的值进行复原,ttl 代表过期时间, ttl = 0 则没有过期时间。整个迁移并非原子性的,而是通过客户端分步完成,并且需要两个客户端。
-
migrate
实际上 migrate 命令就是将 dump、restore、del 三个命令进行组合,从而简化操作流程。migrate 具有原子性,支持多个键的迁移,有效提高了迁移效率。实现过程和 dump + restore 类似,有三点不同:
① 整个过程是原子执行,不需要在多个 Redis 实例开启客户端。
② 数据传输直接在源 Redis 和目标 Redis 完成。
③ 目标 Redis 完成 restore 后会发送 OK 给源 Redis,源 Redis 接收后根据 migrate 对应选项来决定是否在源 Redis 上删除对应键。
Q4:如何切换数据库?
select dbIndex
,Redis 中默认配置有 16 个数据库,例如 select 0 将切换到第一个数据库,数据库之间的数据是隔离的。
Q5:如何清除数据库?
用于清除数据库,flushdb 只清除当前数据库,flushall 会清除所有数据库。如果当前数据库键值数量比较多,flushdb/flushall 存在阻塞 Redis 的可能性。
持久化
Q1:RDB 持久化的原理?
RDB 持久化是把当前进程数据生成快照保存到硬盘的过程,触发 RDB 持久化过程分为手动触发和自动触发。
手动触发分别对应 save 和 bgsave 命令:
- save:阻塞当前 Redis 服务器,直到 RDB 过程完成为止,对于内存比较大的实例会造成长时间阻塞,线上环境不建议使用。
- bgasve:Redis 进程执行 fork 操作创建子进程,RDB 持久化过程由子进程负责,完成后自动结束。阻塞只发生在 fork 阶段,一般时间很短。bgsave 是针对 save 阻塞问题做的优化,因此 Redis 内部所有涉及 RDB 的操作都采用 bgsave 的方式,而 save 方式已经废弃。
除了手动触发外,Redis 内部还存在自动触发 RDB 的持久化机制,例如:
- 使用 save 相关配置,如 save m n,表示 m 秒内数据集存在 n 次修改时,自动触发 bgsave。
- 如果从节点执行全量复制操作,主节点自动执行 bgsave 生成 RDB 文件并发送给从节点。
- 执行 debug reload 命令重新加载 Redis 时也会自动触发 save 操作。
- 默认情况下执行 shutdown 命令时,如果没有开启 AOF 持久化功能则自动执行 bgsave。
Q2:bgsave 的原理?
① 执行 bgsave 命令,Redis 父进程判断当前是否存在正在执行的子进程,如 RDB/AOF 子进程,如果存在 bgsave 命令直接返回。
② 父进程执行 fork 操作创建子进程,fork 操作过程中父进程会阻塞。
③ 父进程 fork 完成后,bgsave 命令返回并不再阻塞父进程,可以继续响应其他命令。
④ 子进程创建 RDB 文件,根据父进程内存生成临时快照文件,完成后对原有文件进行原子替换。
⑤ 进程发送信号给父进程表示完成,父进程更新统计信息。
Q3:RDB 持久化的优点?
RDB 是一个紧凑压缩的二进制文件,代表 Redis 在某个时间点上的数据快照。非常适合于备份,全量复制等场景。例如每 6 个消时执行 bgsave 备份,并把 RDB 文件拷贝到远程机器或者文件系统中,用于灾难恢复。
Redis 加载 RDB 恢复数据远远快于 AOF 的方式。
Q4:RDB 持久化的缺点?
RDB 方式数据无法做到实时持久化/秒级持久化,因为 bgsave 每次运行都要执行 fork 操作创建子进程,属于重量级操作,频繁执行成本过高。针对 RDB 不适合实时持久化的问题,Redis 提供了 AOF 持久化方式。
RDB 文件使用特定二进制格式保存,Redis 版本演进过程中有多个格式的 RDB 版本,存在老版本 Redis 服务无法兼容新版 RDB 格式的问题。
Q5:AOF 持久化的原理?
AOF 持久化以独立日志的方式记录每次写命令,重启时再重新执行 AOF 文件中的命令达到恢复数据的目的。AOF 的主要作用是解决了数据持久化的实时性,目前是 Redis 持久化的主流方式。
开启 AOF 功能需要设置:appendonly yes
,默认不开启。保存路径同 RDB 方式一致,通过 dir 配置指定。
AOF 的工作流程操作:命令写入 append、文件同步 sync、文件重写 rewrite、重启加载 load:
- 所有的写入命令会追加到 aof_buf 缓冲区中。
- AOF 缓冲区根据对应的策略向硬盘做同步操作。
- 随着 AOF 文件越来越大,需要定期对 AOF 文件进行重写,达到压缩的目的。
- 当服务器重启时,可以加载 AOF 文件进行数据恢复。
Q6:AOF 命令写入的原理?
AOF 命令写入的内容直接是文本协议格式,采用文本协议格式的原因:
- 文本协议具有很好的兼容性。
- 开启 AOF 后所有写入命令都包含追加操作,直接采用协议格式避免了二次处理开销。
- 文本协议具有可读性,方便直接修改和处理。
AOF 把命令追加到缓冲区的原因:
Redis 使用单线程响应命令,如果每次写 AOF 文件命令都直接追加到硬盘,那么性能完全取决于当前硬盘负载。先写入缓冲区中还有另一个好处,Redis 可以提供多种缓冲区同步硬盘策略,在性能和安全性方面做出平衡。
Q7:AOF 文件同步的原理?
Redis 提供了多种 AOF 缓冲区文件同步策略,由参数 appendfsync
控制,不同值的含义如下:
- always:命令写入缓冲区后调用系统 fsync 操作同步到 AOF 文件,fsync 完成后线程返回。每次写入都要同步 AOF,性能较低,不建议配置。
- everysec:命令写入缓冲区后调用系统 write 操作,write 完成后线程返回。fsync 同步文件操作由专门线程每秒调用一次。是建议的策略,也是默认配置,兼顾性能和数据安全。
- no:命令写入缓冲区后调用系统 write 操作,不对 AOF 文件做 fsync 同步,同步硬盘操作由操作系统负责,周期通常最长 30 秒。由于操作系统每次同步 AOF 文件的周期不可控,而且会加大每次同步硬盘的数据量,虽然提升了性能,但安全性无法保证。
Q8:AOF 文件重写的原理?
文件重写是把 Redis 进程内的数据转化为写命令同步到新 AOF 文件的过程,可以降低文件占用空间,更小的文件可以更快地被加载。
重写后 AOF 文件变小的原因:
- 进程内已经超时的数据不再写入文件。
- 旧的 AOF 文件含有无效命令,重写使用进程内数据直接生成,这样新的 AOF 文件只保留最终数据写入命令。
- 多条写命令可以合并为一个,为了防止单条命令过大造成客户端缓冲区溢出,对于 list、set、hash、zset 等类型操作,以 64 个元素为界拆分为多条。
AOF 重写分为手动触发和自动触发,手动触发直接调用 bgrewriteaof 命令,自动触发根据 auto-aof-rewrite-min-size
和 auto-aof-rewrite-percentage
参数确定自动触发时机。
重写流程:
① 执行 AOF 重写请求,如果当前进程正在执行 AOF 重写,请求不执行并返回,如果当前进程正在执行 bgsave 操作,重写命令延迟到 bgsave 完成之后再执行。
② 父进程执行 fork 创建子进程,开销等同于 bgsave 过程。
③ 父进程 fork 操作完成后继续响应其他命令,所有修改命令依然写入 AOF 缓冲区并同步到硬盘,保证原有 AOF 机制正确性。
④ 子进程根据内存快照,按命令合并规则写入到新的 AOF 文件。每次批量写入数据量默认为 32 MB,防止单次刷盘数据过多造成阻塞。
⑤ 新 AOF 文件写入完成后,子进程发送信号给父进程,父进程更新统计信息。
⑥ 父进程把 AOF 重写缓冲区的数据写入到新的 AOF 文件并替换旧文件,完成重写。
Q9:AOF 重启加载的原理?
AOF 和 RDB 文件都可以用于服务器重启时的数据恢复。Redis 持久化文件的加载流程:
① AOF 持久化开启且存在 AOF 文件时,优先加载 AOF 文件。
② AOF 关闭时且存在 RDB 文件时,记载 RDB 文件。
③ 加载 AOF/RDB 文件成功后,Redis 启动成功。
④ AOF/RDB 文件存在错误导致加载失败时,Redis 启动失败并打印错误信息。
Q10:Redis底层数据结构分别用的什么?说一下它们的实现原理
http://zrkworld.com/archives/tu-jie-redis-wu-zhong-shu-ju-jie-gou-di-ceng-shi-xian
集群
Q1:什么是分布式锁?有什么性质?Redis怎么实现分布式锁?
分布式锁的性质:
可靠性。首先,为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件:
互斥性。在任意时刻,只有一个客户端能持有锁。
不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
具有容错性。只要大部分的Redis节点正常运行,客户端就可以加锁和解锁。
解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。
加锁代码
public class RedisTool {
private static final String LOCK_SUCCESS = "OK";
private static final String SET_IF_NOT_EXIST = "NX";
private static final String SET_WITH_EXPIRE_TIME = "PX";
/**
\* 尝试获取分布式锁
\* @param jedis Redis客户端
\* @param lockKey 锁
\* @param requestId 请求标识
\* @param expireTime 超期时间
\* @return 是否获取成功
*/
public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {
String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
if (LOCK_SUCCESS.equals(result)) {
return true;
}
return false;
}
}
可以看到,我们加锁就一行代码:jedis.set(String key, String value, String nxxx, String expx, int time),这个set()方法一共有五个形参:
第一个为key,我们使用key来当锁,因为key是唯一的。
第二个为value,我们传的是requestId,很多童鞋可能不明白,有key作为锁不就够了吗,为什么还要用到value?原因就是我们在上面讲到可靠性时,分布式锁要满足第四个条件解铃还须系铃人,通过给value赋值为requestId,我们就知道这把锁是哪个请求加的了,在解锁的时候就可以有依据。requestId可以使用UUID.randomUUID().toString()方法生成。
第三个为nxxx,这个参数我们填的是NX,意思是SET IF NOT EXIST,即当key不存在时,我们进行set操作;若key已经存在,则不做任何操作;
第四个为expx,这个参数我们传的是PX,意思是我们要给这个key加一个过期的设置,具体时间由第五个参数决定。
第五个为time,与第四个参数相呼应,代表key的过期时间。
总的来说,执行上面的set()方法就只会导致两种结果:1. 当前没有锁(key不存在),那么就进行加锁操作,并对锁设置个有效期,同时value表示加锁的客户端。2. 已有锁存在,不做任何操作。
我们的加锁代码满足我们可靠性里描述的三个条件。首先,set()加入了NX参数,可以保证如果已有key存在,则函数不会调用成功,也就是只有一个客户端能持有锁,满足互斥性。其次,由于我们对锁设置了过期时间,即使锁的持有者后续发生崩溃而没有解锁,锁也会因为到了过期时间而自动解锁(即key被删除),不会发生死锁。最后,因为我们将value赋值为requestId,代表加锁的客户端请求标识,那么在客户端在解锁的时候就可以进行校验是否是同一个客户端。由于我们只考虑Redis单机部署的场景,所以容错性我们暂不考虑。
Q2:什么是一致性哈希?
在散列表扩容或者缩容后, key值保持一致性. 制定一个模数, 无论扩容或缩容, 模数不变, 计算的hashcode没有实际桶位则向后找到一个实际桶位放置. 最后一个桶位连接第一个桶位做成一个循环链表. 当新增桶位后, 只需同步后一个桶位的属于新增桶位的key. 使用跳表实现查找复杂度O(Logn).
详情参考文章:
http://zrkworld.com/archives/yi-zhi-xing-ha-xi-tu-jie
Q3:介绍一下Redis分布式集群解决方案
详情参考文章:
http://zrkworld.com/archives/fen-bu-shi-redis-jie-jue-fang-an--gai-shu-ban-
Q4:Redis集群故障后发生了什么?
详情参考文章:
http://zrkworld.com/archives/rediscluster-gu-zhang-zhuan-yi-ji-zhi-xiang-jie
Q5:主从复制怎么确保一致性?
https://blog.csdn.net/weixin_39653311/article/details/111615523