Redis一问多答(必背篇)

看完拉勾的讲义笔记后,如果还觉得不过瘾,推荐以下毒物:

1.书籍《Redis设计与实现》,该书基于redis3.0,但学习Redis底层设计是很不错的

2.掘金小册《Redis 深度历险:核心原理与应用实践》 这本小册,适合当课外读物,有点商务化,你懂得

3.如下博客清晰明了

redis02 五种类型底层数据结构(强力推荐,完美诠释了底层数据结构的应用)

redis-08 搞定redis所有面试

Redis面试题(2020最新版)

敖丙-《我们一起进大厂》系列(空闲读物)

语雀共享文档:https://www.yuque.com/docs/share/1fea47ff-9d21-4b0b-8bfa-f3487adf6b51?# 《Redis一问多答(必背篇)》

(欢迎兄弟们评论和点赞)

目录

1.Redis怎么使用?

2.redis为什么快?redis的线程模型

3.redis常用数据结构的底层

1.string

2.list

3.Hash

4.set

5.zset

拓展:为什么SDS最大存储44字节后,编码就从embstr转为raw?

4.缓存穿透如何解决?布隆过滤器

原理:

优点:

缺点:

实现:

总结:

5.什么是大Key和热Key,会造成什么问题,如何解决?系统在某个时刻访问量剧增(热点新闻),造成数据库压力剧增甚至崩溃,怎么办?

Big Key

Hot Key

6.缓存和数据库数据是不一致时(双写不一致),会造成什么问题,如何解决?redis并发下脏读问题?

7.什么是数据并发竞争,会造成什么问题,如何解决?

第一种方案:分布式锁+时间戳

第二种方案:利用消息队列

8.什么是缓存雪崩、缓存穿透和缓存击穿,会造成什么问题,如何解决?

9.公司项目有搭建Redis集群么?怎么保证高可用?

10.redis 集群模式的工作原理能说一下么?在集群模式下,redis 的 key 是如何寻址的?分布式寻址都有哪些算法?了解一致性 hash 算法吗?gossip 协议

节点间的内部通信机制

11.dict扩容的负载因子0.75和hashmap扩容负载因子0.75

12.redis弱事务

什么是事务

Redis事务

怎么理解redis的弱事务性,redis的事务存在两种情况:

什么是弱事务性

为什么redis支持弱事务性而不完全支持事务?

13.Redis慢查询

14.redis持久化

RDB

执行过程

RDB的优缺点

AOF

AOF原理

命令传播

文件写入和保存

AOF 保存模式

AOF重写、触发方式、混合持久化

重写过程分析(整个重写操作是绝对安全的):

混合持久化

AOF文件的载入与数据还原

RDB与AOF对比

15.redis集群横向扩展?

1.根据ID分区

2.hash分区

3.client端分区

4.proxy端分区

5.官方cluster分区

去中心化

Gossip协议

slot

RedisCluster的优势

客户端路由

Smart智能客户端 JedisCluster

迁移

容灾(failover)

16.主从都崩了?怎么解决

1.本地缓存

2.副本漂移

17.redis分布式集群搭建、Redis哨兵和集群的原理及选择?

18.在多机Redis使用时,如何保证主从服务器的数据一致性?主从备份数据同步

19.一致性Hash的过程、扩容过程

普通hash

一致性hash

hash环偏移

泊松分布可看:如何通俗理解泊松分布?

20.分布式锁setnx/redssion、红锁redlock

21.如何保证 Redis 中的数据都是热点数据?Redis淘汰策略,LRU、LFU手写

22.有AOF持久化和无持久化节点的区别

23.redis 中有 1 亿 key,其中有 10w 个 key 是以某个固定已知前缀开头,如何将它们全部找出来?

24.秒杀系统设计

遗留问题


​​​​​​​

1.Redis怎么使用?

1.做DB缓存、减轻DB压力

2.session分离

3.redis分布式锁

4.做乐观锁

同步锁、数据库的行锁、表锁都是悲观锁,性能差

高性能、高响应(秒杀)可以用乐观锁,watch+incr

2.redis为什么快?redis的线程模型

1.redis是单线程,基于内存存储

2.redis自己的数据结构,如SDS、字典、压缩表、跳跃表、hash对象等

3.redis的线程模型:IO多路复用,事件处理机制?

3.redis常用数据结构的底层

1.string

sds incr(乐观锁)setnx(悲观锁)

int:数字

embstr:短字符串 redisObj和SDS在同一块内存中

raw:长字符串 redisObj和SDS在各分配一块内存

embstr和raw的界限 3.0前是39位 3.0后是44位

2.list

redis 3.2前

ziplist:列表对象所有字符串元素长度小于64字节,元素数量小于512

双向链表:不满足ziplist的情况(耗内存)

redis 3.2后

quicklist ziplist和双向链表的结合,每一个链表node都是ziplist。用于栈或队列、评论列表

这两种存储方式的优缺点

  • 双向链表linkedlist便于在表的两端进行push和pop操作,在插入节点上复杂度很低,但是它的内存开销比较大。首先,它在每个节点上除了要保存数据之外,还要额外保存两个指针;其次,双向链表的各个节点是单独的内存块,地址不连续,节点多了容易产生内存碎片。
  • ziplist存储在一段连续的内存上,所以存储效率很高。但是,它不利于修改操作,插入和删除操作需要频繁的申请和释放内存。特别是当ziplist长度很长的时候,一次realloc可能会导致大批量的数据拷贝。

3.Hash

ziplist:列表对象所有字符串元素长度小于64字节,元素数量小于512

hashtable(dict):不满足ziplist的情况

4.set

intset:所有元素都是整数,个数在512

hashtable:不满足intset条件

5.zset

ziplist:列表对象所有字符串元素长度小于64字节,元素数量小于128

skiplist+dict:

不常见的 bizmap位图类型(签到)、geo地理类型(记录地理位置、计算距离、附近的人)

5.0版本:stream类型,用于消息队列

HyperLogLog、Geo、Pub/Sub

拓展:为什么SDS最大存储44字节后,编码就从embstr转为raw?

jemalloc分配内存64字节,redisObject占用16字节,尾标识占用1个字节,所以问题出在了sdshdr,

1.3.0之前sdshdr数据结构占用8个字节,所以16+8+1=25,64-25=39

2.3.0之后sdshdr优化,只占用3个字节,所以16+3+1=20,64-20=44

malloc 是一个统称, jemalloc, tcmalloc 他们俩是具体的实现

2. embstr 和 raw 的本质区别

内存分配上:

embstr调用1次malloc, 因此redisObject和SDS内存是连续分配的

raw需要调用2次malloc, 因此redisObject和SDS内存不连续分配

使用上:

embstr 整体 64 byte, 正好和cpu cache line 64byte 一样, 可以更好的使用缓存, 效率更高

4.缓存穿透如何解决?布隆过滤器

原理:

当一个元素被加入集合时,通过K个散列函数将这个元素映射成一个位数组中的K个点,把它们置为1。检索时,我们只要看看这些点是不是都是1就(大约)知道集合中有没有它了:如果这些点有任何一个0,则被检元素一定不在;如果都是1,则被检元素很可能在

优点:

空间效率和查询时间都远远超过一般的算法

缺点:

  • 存在误判,可能要查到的元素并没有在容器中,但是hash之后得到的k个位置上值都是1。如果bloom filter中存储的是黑名单,那么可以通过建立一个白名单来存储可能会误判的元素。
  • 删除困难。一个放入容器的元素映射到bit数组的k个位置上是1,删除的时候不能简单的直接置为0,可能会影响其他元素的判断。可以采用Counting Bloom Filter

实现:

布隆过滤器有许多实现与优化,Guava中就提供了一种Bloom Filter的实现。

在使用bloom filter时,绕不过的两点是预估数据量n以及期望的误判率fpp,

在实现bloom filter时,绕不过的两点就是hash函数的选取以及bit数组的大小。

对于一个确定的场景,我们预估要存的数据量为n,期望的误判率为fpp,然后需要计算我们需要的Bit数组的大小m,以及hash函数的个数k,并选择hash函数

Big数组大小选择:根据预估数据量n以及误判率fpp,bit数组大小的m的计算方式:

哈希函数选择,由预估数据量n以及bit数组长度m,可以得到一个hash函数的个数k:

=lnfpp/ln2

要使用BloomFilter,需要引入guava包:


            com.google.guava
            guava
            23.0
 

BloomFilter一共四个create方法,不过最终都是走向第四个。看一下每个参数的含义:

funnel:数据类型(一般是调用Funnels工具类中的)

expectedInsertions:期望插入的值的个数

fpp 错误率(默认值为0.03)

strategy 哈希算法(我也不懂啥意思)Bloom Filter的应用

总结:

错误率越大,所需空间和时间越小,错误率越小,所需空间和时间越大

5.什么是大Key和热Key,会造成什么问题,如何解决?系统在某个时刻访问量剧增(热点新闻),造成数据库压力剧增甚至崩溃,怎么办?

Big Key

大key指的是存储的值(Value)非常大,常见场景:热门话题下的讨论、大V的粉丝列表、序列化后的图片、没有及时处理的垃圾数据

key的影响:大key会大量占用内存,在集群中无法均衡、Redis的性能下降,主从复制异常、在主动删除或过期删除时会操作时间过长而引起服务阻塞

如何发现大key

1、redis-cli --bigkeys命令。可以找到某个实例5种数据类型(String、hash、list、set、zset)的最大

key。但如果Redis 的key比较多,执行该命令会比较慢

2、获取生产Redis的rdb文件,通过rdbtools分析rdb生成csv文件,再导入MySQL或其他数据库中进行

分析统计,根据size_in_bytes统计bigkey

key的处理:

优化big key的原则就是string减少字符串长度,list、hash、set、zset等减少成员数。

1、string类型的big key,尽量不要存入Redis中,可以使用文档型数据库MongoDB或缓存到CDN上。

如果必须用Redis存储,最好单独存储,不要和其他的key一起存储。采用一主一从或多从。

2、单个简单的key存储的value很大,可以尝试将对象分拆成几个key-value, 使用mget获取值,这样

分拆的意义在于分拆单次操作的压力,将操作压力平摊到多次操作中,降低对redis的IO影响。

2、hash, set,zset,list 中存储过多的元素,可以将这些元素分拆。(常见)

以hash类型举例来说,对于field过多的场景,可以根据field进行hash取模,生成一个新的key,例如

原来的

hash_key:{filed1:value, filed2:value, filed3:value ...},可以hash取模后形成如下

key:value形式

hash_key:1:{filed1:value}

hash_key:2:{filed2:value}

hash_key:3:{filed3:value}

...

取模后,将原先单个key分成多个key,每个key filed个数为原先的1/N

3、删除大key时不要使用del,因为del是阻塞命令,删除时会影响性能。

4、使用 lazy delete (unlink命令)

删除指定的key(s),若key不存在则该key被跳过。但是,相比DEL会产生阻塞,该命令会在另一个线程中

回收内存,因此它是非阻塞的。 这也是该命令名字的由来:仅将keys从key空间中删除,真正的数据删

除会在后续异步操作。

redis> SET key1 "Hello"

"OK"

redis> SET key2 "World"

"OK"

redis> UNLINK key1 key2 key3

(integer) 2

拓展:DEL命令是阻塞命令?lazy delete(unlink命令)/CDN

Hot Key

当有大量的请求(几十万)访问某个Redis某个key时,由于流量集中达到网络上限,从而导致这个redis的

服务器宕机。造成缓存击穿,接下来对这个key的访问将直接访问数据库造成数据库崩溃,或者访问数

据库回填Redis再访问Redis,继续崩溃。

如何发现热key

1、预估热key,比如秒杀的商品、火爆的新闻等

2、在客户端进行统计,实现简单,加一行代码即可

3、如果是Proxy,比如Codis,可以在Proxy端收集

4、利用Redis自带的命令,monitor、hotkeys。但是执行缓慢(不要用)

5、利用基于大数据领域的流式计算技术来进行实时数据访问次数的统计,比如 Storm、Spark、Streaming、Flink,这些技术都是可以的。发现热点数据后可以写到zookeeper中

如何处理热Key

1、变分布式缓存为本地缓存

发现热key后,把缓存数据取出后,直接加载到本地缓存中。可以采用Ehcache、Guava Cache都可

以,这样系统在访问热key数据时就可以直接访问自己的缓存了。(数据不要求时时一致)

2、在每个Redis主节点上备份热key数据,这样在读取时可以采用随机读取的方式,将访问压力负载到

每个Redis上。

3、利用对热点数据访问的限流熔断保护措施

每个系统实例每秒最多请求缓存集群读操作不超过 400 次,一超过就可以熔断掉,不让请求缓存集群,

直接返回一个空白信息,然后用户稍后会自行再次重新刷新页面之类的。(首页不行,系统友好性差)

通过系统层自己直接加限流熔断保护措施,可以很好的保护后面的缓存集群。

6.缓存和数据库数据是不一致时(双写不一致),会造成什么问题,如何解决?redis并发下脏读问题?

缓存和DB的数据不一致的根源 : 数据源不一样

如何解决:强一致性很难,追求最终一致性(时间),互联网业务数据处理的特点:高吞吐量,低延迟,数据敏感性低于金融业

时序控制是否可行?先更新数据库再更新缓存或者先更新缓存再更新数据库,本质上不是一个原子操作,所以时序控制不可行,高并发情况下会产生不一致

保证数据的最终一致性(延时双删)

1、先更新数据库同时删除缓存项(key),等读的时候再填充缓存

2、2秒后再删除一次缓存项(key)

3、设置缓存过期时间 Expired Time 比如 10秒 或1小时

4、将缓存删除失败记录到日志中,利用脚本提取失败记录再次删除(缓存失效期过长 7*24)

升级方案

通过数据库的binlog来异步淘汰key,利用工具(canal)将binlog日志采集发送到MQ中,然后通过ACK机

制确认处理删除缓存。

7.什么是数据并发竞争,会造成什么问题,如何解决?

这里的并发指的是多个redis的client同时set 同一个key引起的并发问题。

多客户端(Jedis)同时并发写一个key,一个key的值是1,本来按顺序修改为2,3,4,最后是4,但是顺

序变成了4,3,2,最后变成了2。

第一种方案:分布式锁+时间戳

1.整体技术方案

这种情况,主要是准备一个分布式锁,大家去抢锁,抢到锁就做set操作。

加锁的目的实际上就是把并行读写改成串行读写的方式,从而来避免资源竞争。

Redis一问多答(必背篇)_第1张图片

2.Redis分布式锁的实现

主要用到的redis函数是setnx(),用SETNX实现分布式锁+时间戳

由于上面举的例子,要求key的操作需要顺序执行,所以需要保存一个时间戳判断set顺序。

系统A key 1 {ValueA 7:00}

系统B key 1 { ValueB 7:05}

假设系统B先抢到锁,将key1设置为{ValueB 7:05}。接下来系统A抢到锁,发现自己的key1的时间戳早

于缓存中的时间戳(7:00<7:05),那就不做set操作了。

第二种方案:利用消息队列

在并发量过大的情况下,可以通过消息中间件进行处理,把并行读写进行串行化。

把Redis的set操作放在队列中使其串行化,必须的一个一个执行。

8.什么是缓存雪崩、缓存穿透和缓存击穿,会造成什么问题,如何解决?

缓存血崩:并发下,大面积的热点key失效或者redis宕机,导致全部请求落到DB上

缓存击穿:并发下,一个热点key失效。1.数据不要求实时性可采用数据回显,采用异步形式刷到缓存 2.更新锁定,加一把分布式锁,只有一个请求能够读DB

缓存穿透:并发下,请求无效key。1.就算无效key也存储一个带过期时间的空key,2.看门狗限流 3.布隆过滤器拦截无效key

9.公司项目有搭建Redis集群么?怎么保证高可用?

项目分单机和saas,saas最低配置,由于公交公司对服务器要求有限制,所以分两种:

1.3台主机,只能做一主两从+哨兵集群

2.6台主机,三主三从,redis cluster(可横向扩展)

10.redis 集群模式的工作原理能说一下么?在集群模式下,redis 的 key 是如何寻址的?分布式寻址都有哪些算法?了解一致性 hash 算法吗?gossip 协议

  • 自动将数据进行分片,每个 master 上放一部分数据
  • 提供内置的高可用支持,部分 master 不可用时,还是可以继续工作的

在 redis cluster 架构下,每个 redis 要放开两个端口号,比如一个是 6379,另外一个就是 加1w 的端口号,比如 16379。

16379 端口号是用来进行节点间通信的,也就是 cluster bus 的东西,cluster bus 的通信,用来进行故障检测、配置更新、故障转移授权。cluster bus 用了另外一种二进制的协议,gossip 协议,用于节点间进行高效的数据交换,占用更少的网络带宽和处理时间。

节点间的内部通信机制

集群元数据的维护有两种方式:集中式、Gossip 协议。redis cluster 节点间采用 gossip 协议进行通信。

集中式是将集群元数据(节点信息、故障等等)几种存储在某个节点上。集中式元数据集中存储的一个典型代表,就是大数据领域的 storm。它是分布式的大数据实时计算引擎,是集中式的元数据存储的结构,底层基于 zookeeper(分布式协调的中间件)对所有元数据进行存储维护。

11.dict扩容的负载因子0.75和hashmap扩容负载因子0.75

取1.0时,空间利用率上去了,但是此时数组里的数据非常多,红黑树非常复杂,扩容时时间复杂度相对较高

取0.5时,空间有一半的浪费

利用二项分布概念,当进行S次插入操作,1/2作为分界,对负载因子做极限运算

Redis一问多答(必背篇)_第2张图片

所以0.75是时间空间复杂度的折中取法

12.redis弱事务

什么是事务

几个操作要么全部成功,要么全部失败的特性,我们称之为事务

Redis事务

Redis的事务是通过multi、exec、discard和watch这四个命令来完成的。Redis的单个命令都是原子性的,所以这里需要确保事务性的对象是命令集合。Redis将命令集合序列化并确保处于同一事务的命令集合连续且不被打断的执行

multi:用于标记事务块的开始,Redis会将后续的命令逐个放入队列中,然后使用exec原子化地执行这个命令队列

exec: 执 行 命 令 队 列

discard:清除命令队列

watch: 监 视 key    

unwatch:清除监视key

怎么理解redis的弱事务性,redis的事务存在两种情况:

如果在redis调用exec之前发生错误,如内存不够、命令语法错误等,会回滚事务(其实叫清楚队列命令更合适,命令未执行)

如果在redis调用exec之后发生错误,那么使用错误的命令不执行,但是队列中使用正确的命令依然执行

什么是弱事务性

从上面两种情况可以看出,redis只针对一部分情况回滚,而对于其他错误的情况不回滚的特性,称为弱事务性

为什么redis支持弱事务性而不完全支持事务?

redis官方的解释是,上述第二种情况属于编程错误。即使redis回滚了它,同样的错误依然会发生。所以这种情况更应该的是去DEBUG。另外,redis不支持这种情况的回滚有利于redis保持简单且快速

13.Redis慢查询

使用slowlog get 可以获得执行较慢的redis命令,针对该命令可以进行优化:

1、尽量使用短的key,对于value有些也可精简,能使用int就int。

2、避免使用keys *、hgetall等全量操作。

3、减少大key的存取,打散为小key 100K以上

4、将rdb改为aof模式

rdb fork 子进程 数据量过大 主进程阻塞 redis性能大幅下降

关闭持久化 , (适合于数据量较小,有固定数据源)

5、想要一次添加多条数据的时候可以使用管道

6、尽可能地使用哈希存储

7、尽量限制下redis使用的内存大小,这样可以避免redis使用swap分区或者出现OOM错误

内存与硬盘的swap

14.redis持久化

RDB

是redis默认的存储方式,RDB方式是通过快照

1. 符合自定义配置的快照规则

save 60 1 代表60s内至少有一次键更改则进行快照

2. 执行save或者bgsave命令

3. 执行flushall命令

4. 执行主从复制操作 (第一次)

执行过程

1. Redis父进程首先判断:当前是否在执行save,或bgsave/bgrewriteaof(aof文件重写命令)的子

进程,如果在执行则bgsave命令直接返回。

2. 父进程执行fork(调用OS函数复制主进程)操作创建子进程,这个复制过程中父进程是阻塞的,

Redis不能执行来自客户端的任何命令。

3. 父进程fork后,bgsave命令返回”Background saving started”信息并不再阻塞父进程,并可以响

应其他命令。

4. 子进程创建RDB文件,根据父进程内存快照生成临时快照文件,完成后对原有文件进行原子替换。

(RDB始终完整)

5. 子进程发送信号给父进程表示完成,父进程更新统计信息。

6. 父进程fork子进程后,继续工作。

RDB的优缺点

优点

RDB是二进制压缩文件,占用空间小,便于传输(传给slaver)

主进程fork子进程,可以最大化Redis性能,主进程不能太大,Redis的数据量不能太大,复制过程中主

进程阻塞

缺点

不保证数据完整性,会丢失最后一次快照以后更改的所有数据

AOF

AOF(append only fifile)是Redis的另一种持久化方式。Redis默认情况下是不开启的。开启AOF持久

化后

Redis 将所有对数据库进行过写入的命令(及其参数)(RESP)记录到 AOF 文件, 以此达到记录数据

库状态的目的,

这样当Redis重启后只要按顺序回放这些命令就会恢复到原始状态了。

AOF会记录过程,RDB只管结果

AOF原理

AOF文件中存储的是redis的命令,同步命令到 AOF 文件的整个过程可以分为三个阶段:

命令传播:Redis 将执行完的命令、命令的参数、命令的参数个数等信息发送到 AOF 程序中。

缓存追加:AOF 程序根据接收到的命令数据,将命令转换为网络通讯协议的格式,然后将协议内容追加

到服务器的 AOF 缓存中。

文件写入和保存:AOF 缓存中的内容被写入到 AOF 文件末尾,如果设定的 AOF 保存条件被满足的话,

fsync 函数或者 fdatasync 函数会被调用,将写入的内容真正地保存到磁盘中。

命令传播

当一个 Redis 客户端需要执行命令时, 它通过网络连接, 将协议文本发送给 Redis 服务器。服务器在

接到客户端的请求之后, 它会根据协议文本的内容, 选择适当的命令函数, 并将各个参数从字符串文

本转换为 Redis 字符串对象( StringObject )。每当命令函数成功执行之后, 命令参数都会被传播到

AOF 程序。

当命令被传播到 AOF 程序之后, 程序会根据命令以及命令的参数, 将命令从字符串对象转换回原来的

协议文本。协议文本生成之后, 它会被追加到 redis.h/redisServer 结构的 aof_buf 末尾。

redisServer 结构维持着 Redis 服务器的状态, aof_buf 域则保存着所有等待写入到 AOF 文件的协

议文本(RESP)。

文件写入和保存

每当服务器常规任务函数被执行、 或者事件处理器被执行时, aof.c/flflushAppendOnlyFile 函数都会被

调用, 这个函数执行以下两个工作:

WRITE:根据条件,将 aof_buf 中的缓存写入到 AOF 文件。

SAVE:根据条件,调用 fsync 或 fdatasync 函数,将 AOF 文件保存到磁盘中。

AOF 保存模式

Redis 目前支持三种 AOF 保存模式,它们分别是:

AOF_FSYNC_NO :不保存。

AOF_FSYNC_EVERYSEC :每一秒钟保存一次。(默认)

AOF_FSYNC_ALWAYS :每执行一个命令保存一次。(不推荐)

以下三个小节将分别讨论这三种保存模式。

不保存

在这种模式下, 每次调用 flflushAppendOnlyFile 函数, WRITE 都会被执行, 但 SAVE 会被略过。

在这种模式下, SAVE 只会在以下任意一种情况中被执行:

Redis 被关闭

AOF 功能被关闭

系统的写缓存被刷新(可能是缓存已经被写满,或者定期保存操作被执行)

这三种情况下的 SAVE 操作都会引起 Redis 主进程阻塞。

每一秒钟保存一次(推荐)

在这种模式中, SAVE 原则上每隔一秒钟就会执行一次, 因为 SAVE 操作是由后台子线程(fork)调用

的, 所以它不会引起服务器主进程阻塞。

每执行一个命令保存一次

在这种模式下,每次执行完一个命令之后, WRITE 和 SAVE 都会被执行。

另外,因为 SAVE 是由 Redis 主进程执行的,所以在 SAVE 执行期间,主进程会被阻塞,不能接受命令

请求。

AOF 保存模式对性能和安全性的影响

对于三种 AOF 保存模式, 它们对服务器主进程的阻塞情况如下:

Redis一问多答(必背篇)_第3张图片

AOF重写、触发方式、混合持久化

AOF记录数据的变化过程,越来越大,需要重写“瘦身”

Redis可以在 AOF体积变得过大时,自动地在后台(Fork子进程)对 AOF进行重写。重写后的新 AOF文

件包含了恢复当前数据集所需的最小命令集合。 所谓的“重写”其实是一个有歧义的词语, 实际上,

AOF 重写并不需要对原有的 AOF 文件进行任何写入和读取, 它针对的是数据库中键的当前值。

举例如下:

set s1 11

set s1 22 ------- > set s1 33

set s1 33

没有优化的:

set s1 11

set s1 22

set s1 33

优化后:

set s1 33

lpush list1 1 2 3

lpush list1 4 5 6 -------- > list1 1 2 3 4 5 6

优化后

lpush list1 1 2 3 4 5 6

Redis 不希望 AOF 重写造成服务器无法处理请求, 所以 Redis 决定将 AOF 重写程序放到(后台)子进

程里执行, 这样处理的最大好处是:

1、子进程进行 AOF 重写期间,主进程可以继续处理命令请求。

2、子进程带有主进程的数据副本,使用子进程而不是线程,可以在避免锁的情况下,保证数据的安全

性。

不过, 使用子进程也有一个问题需要解决: 因为子进程在进行 AOF 重写期间, 主进程还需要继续处理

命令, 而新的命令可能对现有的数据进行修改, 这会让当前数据库的数据和重写后的 AOF 文件中的数

据不一致。

为了解决这个问题, Redis 增加了一个 AOF 重写缓存, 这个缓存在 fork 出子进程之后开始启用,

Redis 主进程在接到新的写命令之后, 除了会将这个写命令的协议内容追加到现有的 AOF 文件之外,

还会追加到这个缓存中。

Redis一问多答(必背篇)_第4张图片

重写过程分析(整个重写操作是绝对安全的):

Redis 在创建新 AOF 文件的过程中,会继续将命令追加到现有的 AOF 文件里面,即使重写过程中发生

停机,现有的 AOF 文件也不会丢失。 而一旦新 AOF 文件创建完毕,Redis 就会从旧 AOF 文件切换到

新 AOF 文件,并开始对新 AOF 文件进行追加操作。

当子进程在执行 AOF 重写时, 主进程需要执行以下三个工作:

处理命令请求。

将写命令追加到现有的 AOF 文件中。

将写命令追加到 AOF 重写缓存中。

这样一来可以保证:

现有的 AOF 功能会继续执行,即使在 AOF 重写期间发生停机,也不会有任何数据丢失。

所有对数据库进行修改的命令都会被记录到 AOF 重写缓存中。

当子进程完成 AOF 重写之后, 它会向父进程发送一个完成信号, 父进程在接到完成信号之后, 会调用

一个信号处理函数, 并完成以下工作:

将 AOF 重写缓存中的内容全部写入到新 AOF 文件中。

对新的 AOF 文件进行改名,覆盖原有的 AOF 文件。

Redis数据库里的+AOF重写过程中的命令------->新的AOF文件---->覆盖老的

当步骤 1 执行完毕之后, 现有 AOF 文件、新 AOF 文件和数据库三者的状态就完全一致了。

当步骤 2 执行完毕之后, 程序就完成了新旧两个 AOF 文件的交替。

这个信号处理函数执行完毕之后, 主进程就可以继续像往常一样接受命令请求了。 在整个 AOF 后台重

写过程中, 只有最后的写入缓存和改名操作会造成主进程阻塞, 在其他时候, AOF 后台重写都不会对

主进程造成阻塞, 这将 AOF 重写对性能造成的影响降到了最低。

以上就是 AOF 后台重写, 也即是 BGREWRITEAOF 命令(AOF重写)的工作原理。

混合持久化

RDB和AOF各有优缺点,Redis 4.0 开始支持 rdb 和 aof 的混合持久化。如果把混合持久化打开,aof

rewrite 的时候就直接把 rdb 的内容写到 aof 文件开头。

RDB的头+AOF的身体---->appendonly.aof

开启混合持久化

aof-use-rdb-preamble yes

我们可以看到该AOF文件是rdb文件的头和aof格式的内容,在加载时,首先会识别AOF文件是否以

REDIS字符串开头,如果是就按RDB格式加载,加载完RDB后继续按AOF格式加载剩余部分。

AOF文件的载入与数据还原

因为AOF文件里面包含了重建数据库状态所需的所有写命令,所以服务器只要读入并重新执行一遍AOF 文件里面保存的写命令,就可以还原服务器关闭之前的数据库状态

Redis读取AOF文件并还原数据库状态的详细步骤如下:

1、创建一个不带网络连接的伪客户端(fake client):因为Redis的命令只能在客户端上下文中执行,而载入AOF文件时所使用的命令直接来源于AOF文件而不是网络连接,所以服 务器使用了一个没有网络连接的伪客户端来执行AOF文件保存的写命令,伪客户端执行命令 的效果和带网络连接的客户端执行命令的效果完全一样

2、从AOF文件中分析并读取出一条写命令

3、使用伪客户端执行被读出的写命令

4、一直执行步骤2和步骤3,直到AOF文件中的所有写命令都被处理完毕为止

当完成以上步骤之后,AOF文件所保存的数据库状态就会被完整地还原出来,整个过程如下图所示:

Redis一问多答(必背篇)_第5张图片

RDB与AOF对比

1、RDB存某个时刻的数据快照,采用二进制压缩存储,AOF存操作命令,采用文本存储(混合)

2、RDB性能高、AOF性能较低

3、RDB在配置触发状态会丢失最后一次快照以后更改的所有数据,AOF设置为每秒保存一次,则最多丢2秒的数据

4、Redis以主服务器模式运行,RDB不会保存过期键值对数据,Redis以从服务器模式运行,RDB会保存过期键值对,当主服务器向从服务器同步时,再清空过期键值对。

AOF写入文件时,对过期的key会追加一条del命令,当执行AOF重写时,会忽略过期key和del命令。

Redis一问多答(必背篇)_第6张图片

应用场景

内存数据库 rdb+aof 数据不容易丢

有原始数据源: 每次启动时都从原始数据源中初始化 ,则 不用开启持久化 (数据量较小)

缓存服务器 rdb 一般 性能高

在数据还原时

有rdb+aof 则还原aof,因为RDB会造成文件的丢失,AOF相对数据要完整。

只有rdb,则还原rdb

拉勾的配置策略

追求高性能:都不开 redis宕机 从数据源恢复

字典库 : 不驱逐,保证数据完整性 不开持久化

用作DB 不能主从 数据量小

做缓存 较高性能: 开rdb

Redis数据量存储过大,性能突然下降,

fork 时间过长 阻塞主进程

则只开启AOF

15.redis集群横向扩展?

1.根据ID分区

好处: 实现简单,方便迁移和扩展

缺陷: 热点数据分布不均,性能损失

非数字型key,比如uuid无法使用(可采用雪花算法替代) 分布式环境 主键 雪花算法 是数字 能排序

2.hash分区

利用简单的hash算法即可:

Redis实例=hash(key)%N

key:要进行分区的键,比如user_id

N:Redis实例个数(Redis主机)

好处:

支持任何类型的key

热点分布较均匀,性能较好

缺陷:

迁移复杂,需要重新计算,扩展较差(利用一致性hash环)

3.client端分区

对于一个给定的key,客户端直接选择正确的节点来进行读写。许多Redis客户端都实现了客户端分区

(JedisPool),也可以自行编程实现。

user_id : u001

hash(u001) : 1844213068

Redis实例=1844213068%3

余数为2,所以选择Redis3。

4.proxy端分区

在客户端和服务器端引入一个代理或代理集群,客户端将命令发送到代理上,由代理根据算法,将命令

路由到相应的服务器上。常见的代理有Codis(豌豆荚)和TwemProxy(Twitter)。

Codis由豌豆荚于2014年11月开源,基于Go和C开发,是近期涌现的、国人开发的优秀开源软件之一。

分片原理

Codis 将所有的 key 默认划分为 1024 个槽位(slot),它首先对客户端传过来的 key 进行 crc32 运算计算

哈希值,再将 hash 后的整数值对 1024 这个整数进行取模得到一个余数,这个余数就是对应 key 的槽

位。

Redis一问多答(必背篇)_第7张图片

Codis的槽位和分组的映射关系就保存在codis proxy当中。

优点

对客户端透明,与codis交互方式和redis本身交互一样

支持在线数据迁移,迁移过程对客户端透明有简单的管理和监控界面

支持高可用,无论是redis数据存储还是代理节点

自动进行数据的均衡分配

最大支持1024个redis实例,存储容量海量

高性能

缺点

采用自有的redis分支,不能与原版的redis保持同步

如果codis的proxy只有一个的情况下, redis的性能会下降20%左右

某些命令不支持

5.官方cluster分区

Redis3.0之后,Redis官方提供了完整的集群解决方案。

方案采用去中心化的方式,包括:sharding(分区)、replication(复制)、failover(故障转移)。

称为RedisCluster。

Redis5.0前采用redis-trib进行集群的创建和管理,需要ruby支持

Redis5.0可以直接使用Redis-cli进行集群的创建和管理

Redis一问多答(必背篇)_第8张图片

去中心化

RedisCluster由多个Redis节点组构成,是一个P2P无中心节点的集群架构,依靠Gossip协议传播的集

群。

Gossip协议

Gossip协议是一个通信协议,一种传播消息的方式。

起源于:病毒传播

Gossip协议基本思想就是:

一个节点周期性(每秒)随机选择一些节点,并把信息传递给这些节点。

这些收到信息的节点接下来会做同样的事情,即把这些信息传递给其他一些随机选择的节点。

信息会周期性的传递给N个目标节点。这个N被称为fanout(扇出)

gossip协议包含多种消息,包括meet、ping、pong、fail、publish等等。

命令

说明

meet

sender向receiver发出,请求receiver加入sender的集群

ping

节点检测其他节点是否在线

pong

receiver收到meet或ping后的回复信息;在failover后,新的Master也会广播pong

fail

节点A判断节点B下线后,A节点广播B的fail信息,其他收到节点会将B节点标记为下线

publish

节点A收到publish命令,节点A执行该命令,并向集群广播publish命令,收到publish 命令的节点都会执行相同的publish命令

通过gossip协议,cluster可以提供集群间状态同步更新、选举自助failover等重要的集群功能。

slot

redis-cluster把所有的物理节点映射到[0-16383]个slot上,基本上采用平均分配和连续分配的方式。

cluster 负责维护节点和slot槽的对应关系 value------>slot-------->节点

当需要在 Redis 集群中放置一个 key-value 时,redis 先对 key 使用 crc16 算法算出一个结果,然后把

结果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,redis 会根据节点数

量大致均等的将哈希槽映射到不同的节点。

比如:

set name zhaoyun

hash("name")采用crc16算法,得到值:1324203551%16384=15903

根据上表15903在13088-16383之间,所以name被存储在Redis5节点。

slot槽必须在节点上连续分配,如果出现不连续的情况,则RedisCluster不能工作,详见容错。

RedisCluster的优势

高性能

Redis Cluster 的性能与单节点部署是同级别的。

多主节点、负载均衡、读写分离

高可用

Redis Cluster 支持标准的 主从复制配置来保障高可用和高可靠。

failover

Redis Cluster 也实现了一个类似 Raft 的共识方式,来保障整个集群的可用性。

易扩展

向 Redis Cluster 中添加新节点,或者移除节点,都是透明的,不需要停机。

水平、垂直方向都非常容易扩展。

数据分区,海量数据,数据存储

原生

部署 Redis Cluster 不需要其他的代理或者工具,而且 Redis Cluster 和单机 Redis 几乎完全兼

容。

分片

不同节点分组服务于相互无交集的分片(sharding),Redis Cluster 不存在单独的proxy或配置服务

器,所以需要将客户端路由到目标的分片。

客户端路由

Redis Cluster的客户端相比单机Redis 需要具备路由语义的识别能力,且具备一定的路由缓存能力。

moved重定向

1.每个节点通过通信都会共享Redis Cluster中槽和集群中对应节点的关系

2.客户端向Redis Cluster的任意节点发送命令,接收命令的节点会根据CRC16规则进行hash运算与

16384取余,计算自己的槽和对应节点

3.如果保存数据的槽被分配给当前节点,则去槽中执行命令,并把命令执行结果返回给客户端

4.如果保存数据的槽不在当前节点的管理范围内,则向客户端返回moved重定向异常

5.客户端接收到节点返回的结果,如果是moved异常,则从moved异常中获取目标节点的信息

6.客户端向目标节点发送命令,获取命令执行结果

Redis一问多答(必背篇)_第9张图片

ask重定向

在对集群进行扩容和缩容时,需要对槽及槽中数据进行迁移

当客户端向某个节点发送命令,节点向客户端返回moved异常,告诉客户端数据对应的槽的节点信息

如果此时正在进行集群扩展或者缩空操作,当客户端向正确的节点发送命令时,槽及槽中数据已经被迁

移到别的节点了,就会返回ask,这就是ask重定向机制

1.客户端向目标节点发送命令,目标节点中的槽已经迁移支别的节点上了,此时目标节点会返回ask转

向给客户端

2.客户端向新的节点发送Asking命令给新的节点,然后再次向新节点发送命令

3.新节点执行命令,把命令执行结果返回给客户端

moved和ask的区别

1、moved:槽已确认转移

2、ask:槽还在转移过程中

Smart智能客户端 JedisCluster

JedisCluster是Jedis根据RedisCluster的特性提供的集群智能客户端

JedisCluster为每个节点创建连接池,并跟节点建立映射关系缓存(Cluster slots)

JedisCluster将每个主节点负责的槽位一一与主节点连接池建立映射缓存

JedisCluster启动时,已经知道key,slot和node之间的关系,可以找到目标节点

JedisCluster对目标节点发送命令,目标节点直接响应给JedisCluster

如果JedisCluster与目标节点连接出错,则JedisCluster会知道连接的节点是一个错误的节点

此时节点返回moved异常给JedisCluster

JedisCluster会重新初始化slot与node节点的缓存关系,然后向新的目标节点发送命令,目标命令执行

命令并向JedisCluster响应

如果命令发送次数超过5次,则抛出异常"Too many cluster redirection!"

Redis一问多答(必背篇)_第10张图片

Redis一问多答(必背篇)_第11张图片

迁移

在RedisCluster中每个slot 对应的节点在初始化后就是确定的。在某些情况下,节点和分片需要变更:

新的节点作为master加入;

某个节点分组需要下线;

负载不均衡需要调整slot 分布。

此时需要进行分片的迁移,迁移的触发和过程控制由外部系统完成。包含下面 2 种:

节点迁移状态设置:迁移前标记源/目标节点。

key迁移的原子化命令:迁移的具体步骤。

Redis一问多答(必背篇)_第12张图片

1、向节点B发送状态变更命令,将B的对应slot 状态置为importing。

2、向节点A发送状态变更命令,将A对应的slot 状态置为migrating。

3、向A 发送migrate 命令,告知A 将要迁移的slot对应的key 迁移到B。

4、当所有key 迁移完成后,cluster setslot 重新设置槽位。

扩容(增加节点分配slot)、缩容(把slot分配出去后,再删除)

容灾(failover)

故障检测

集群中的每个节点都会定期地(每秒)向集群中的其他节点发送PING消息

如果在一定时间内(cluster-node-timeout),发送ping的节点A没有收到某节点B的pong回应,则A将B

标识为pfail。

A在后续发送ping时,会带上B的pfail信息, 通知给其他节点。

如果B被标记为pfail的个数大于集群主节点个数的一半(N/2 + 1)时,B会被标记为fail,A向整个集群

广播,该节点已经下线。

其他节点收到广播,标记B为fail。

从节点选举

raft,每个从节点,都根据自己对master复制数据的offffset,来设置一个选举时间,offffset越大(复制数

据越多)的从节点,选举时间越靠前,优先进行选举。

slave 通过向其他master发送FAILVOER_AUTH_REQUEST 消息发起竞选,

master 收到后回复FAILOVER_AUTH_ACK 消息告知是否同意。

slave 发送FAILOVER_AUTH_REQUEST 前会将currentEpoch 自增,并将最新的Epoch 带入到

FAILOVER_AUTH_REQUEST 消息中,如果自己未投过票,则回复同意,否则回复拒绝。

所有的Master开始slave选举投票,给要进行选举的slave进行投票,如果大部分master node(N/2 +

1)都投票给了某个从节点,那么选举通过,那个从节点可以切换成master。

RedisCluster失效的判定:

1、集群中半数以上的主节点都宕机(无法投票)

2、宕机的主节点的从节点也宕机了(slot槽分配不连续)

变更通知

当slave 收到过半的master 同意时,会成为新的master。此时会以最新的Epoch 通过PONG 消息广播

自己成为master,让Cluster 的其他节点尽快的更新拓扑结构(node.conf)。

主从切换

自动切换 就是上面讲的从节点选举

手动切换

人工故障切换是预期的操作,而非发生了真正的故障,目的是以一种安全的方式(数据无丢失)将当前

master节点和其中一个slave节点(执行cluster-failover的节点)交换角色

1、向从节点发送cluster failover 命令(slaveof no one)

2、从节点告知其主节点要进行手动切换(CLUSTERMSG_TYPE_MFSTART)

3、主节点会阻塞所有客户端命令的执行(10s)

4、从节点从主节点的ping包中获得主节点的复制偏移量

5、从节点复制达到偏移量,发起选举、统计选票、赢得选举、升级为主节点并更新配置

6、切换完成后,原主节点向所有客户端发送moved指令重定向到新的主节点

以上是在主节点在线情况下。

如果主节点下线了,则采用cluster failover force或cluster failover takeover 进行强制切换。

副本漂移 见下方

16.主从都崩了?怎么解决

1.本地缓存

2.副本漂移

我们知道在一主一从的情况下,如果主从同时挂了,那整个集群就挂了。

为了避免这种情况我们可以做一主多从,但这样成本就增加了。

Redis提供了一种方法叫副本漂移,这种方法既能提高集群的可靠性又不用增加太多的从机。

如图:

Redis一问多答(必背篇)_第13张图片

Master1宕机,则Slaver11提升为新的Master1

集群检测到新的Master1是单点的(无从机)

集群从拥有最多的从机的节点组(Master3)中,选择节点名称字母顺序最小的从机(Slaver31)漂移

到单点的主从节点组(Master1)。

具体流程如下(以上图为例):

1、将Slaver31的从机记录从Master3中删除

2、将Slaver31的的主机改为Master1

3、在Master1中添加Slaver31为从节点

4、将Slaver31的复制源改为Master1

5、通过ping包将信息同步到集群的其他节点

17.redis分布式集群搭建、Redis哨兵和集群的原理及选择?

哨兵(sentinel)是Redis的高可用性(High Availability)的解决方案:

由一个或多个sentinel实例组成sentinel集群可以监视一个或多个主服务器和多个从服务器。

当主服务器进入下线状态时,sentinel可以将该主服务器下的某一从服务器升级为主服务器继续提供服

务,从而保证redis的高可用性。

部署方案

Redis一问多答(必背篇)_第14张图片

执行流程

启动并初始化Sentinel

Sentinel是一个特殊的Redis服务器

不会进行持久化

Sentinel实例启动后

每个Sentinel会创建2个连向主服务器的网络连接

命令连接:用于向主服务器发送命令,并接收响应;

订阅连接:用于订阅主服务器的—sentinel—:hello频道。

Redis一问多答(必背篇)_第15张图片

获取主服务器信息

Sentinel默认每10s一次,向被监控的主服务器发送info命令,获取主服务器和其下属从服务器的信息。

获取从服务器信息

当Sentinel发现主服务器有新的从服务器出现时,Sentinel还会向从服务器建立命令连接和订阅连接。

在命令连接建立之后,Sentinel还是默认10s一次,向从服务器发送info命令,并记录从服务器的信息。

Redis一问多答(必背篇)_第16张图片

向主服务器和从服务器发送消息(以订阅的方式)

默认情况下,Sentinel每2s一次,向所有被监视的主服务器和从服务器所订阅的—sentinel—:hello频道

上发送消息,消息中会携带Sentinel自身的信息和主服务器的信息。

接收来自主服务器和从服务器的频道信息

当Sentinel与主服务器或者从服务器建立起订阅连接之后,Sentinel就会通过订阅连接,向服务器发送

以下命令:

subscribe —sentinel—:hello

Sentinel彼此之间只创建命令连接,而不创建订阅连接,因为Sentinel通过订阅主服务器或从服务器,

就可以感知到新的Sentinel的加入,而一旦新Sentinel加入后,相互感知的Sentinel通过命令连接来通信

就可以了。

检测主观下线状态

Sentinel每秒一次向所有与它建立了命令连接的实例(主服务器、从服务器和其他Sentinel)发送PING命

实例在down-after-milliseconds毫秒内返回无效回复(除了+PONG、-LOADING、-MASTERDOWN外)

实例在down-after-milliseconds毫秒内无回复(超时)

Sentinel就会认为该实例主观下线(SDown)

检查客观下线状态

当一个Sentinel将一个主服务器判断为主观下线后

Sentinel会向同时监控这个主服务器的所有其他Sentinel发送查询命令

主机的

SENTINEL is-master-down-by-addr

其他Sentinel回复

< leader_runid >< leader_epoch >

判断它们是否也认为主服务器下线。如果达到Sentinel配置中的quorum数量的Sentinel实例都判断主服

务器为主观下线,则该主服务器就会被判定为客观下线(ODown)。

选举Leader Sentinel

当一个主服务器被判定为客观下线后,监视这个主服务器的所有Sentinel会通过选举算法(raft),选

出一个Leader Sentinel去执行failover(故障转移)操作。

哨兵leader选举

Raft

Raft协议是用来解决分布式系统一致性问题的协议。

Raft协议描述的节点共有三种状态:Leader, Follower, Candidate。

term:Raft协议将时间切分为一个个的Term(任期),可以认为是一种“逻辑时间”。

选举流程:

Raft采用心跳机制触发Leader选举

系统启动后,全部节点初始化为Follower,term为0。节点如果收到了RequestVote或者AppendEntries,就会保持自己的Follower身份

节点如果一段时间内没收到AppendEntries消息,在该节点的超时时间内还没发现Leader,Follower就

会转换成Candidate,自己开始竞选Leader。

一旦转化为Candidate,该节点立即开始下面几件事情:

  • 增加自己的term。
  • 启动一个新的定时器。
  • 给自己投一票。
  • 向所有其他节点发送RequestVote,并等待其他节点的回复。

如果在计时器超时前,节点收到多数节点的同意投票,就转换成Leader。同时向所有其他节点发送

AppendEntries,告知自己成为了Leader。

每个节点在一个term内只能投一票,采取先到先得的策略,Candidate前面说到已经投给了自己,

Follower会投给第一个收到RequestVote的节点。

Raft协议的定时器采取随机超时时间,这是选举Leader的关键。

在同一个term内,先转为Candidate的节点会先发起投票,从而获得多数票。

Sentinelleader选举流程

1、某Sentinel认定master客观下线后,该Sentinel会先看看自己有没有投过票,如果自己已经投过票

给其他Sentinel了,在一定时间内自己就不会成为Leader。

2、如果该Sentinel还没投过票,那么它就成为Candidate。

3、Sentinel需要完成几件事情:

  • 更新故障转移状态为start
  • 当前epoch加1,相当于进入一个新term,在Sentinel中epoch就是Raft协议中的term。
  • 向其他节点发送 is-master-down-by-addr 命令请求投票。命令会带上自己的epoch。
  • 给自己投一票(leader、leader_epoch)

4、当其它哨兵收到此命令时,可以同意或者拒绝它成为领导者;(通过判断epoch)

5、Candidate会不断的统计自己的票数,直到他发现认同他成为Leader的票数超过一半而且超过它配

置的quorum,这时它就成为了Leader。

6、其他Sentinel等待Leader从slave选出master后,检测到新的master正常工作后,就会去掉客观下

线的标识。

故障转移

当选举出Leader Sentinel后,Leader Sentinel会对下线的主服务器执行故障转移操作,主要有三个步

骤:

1. 它会将失效 Master 的其中一个 Slave 升级为新的 Master , 并让失效 Master 的其他 Slave 改为复

制新的 Master ;

2. 当客户端试图连接失效的 Master 时,集群也会向客户端返回新 Master 的地址,使得集群可以使

用现在的 Master 替换失效 Master 。

3. Master 和 Slave 服务器切换后, Master 的 redis.conf 、 Slave 的 redis.conf 和

sentinel.conf 的配置文件的内容都会发生相应的改变,即, Master 主服务器的 redis.conf

配置文件中会多一行 replicaof 的配置, sentinel.conf 的监控目标会随之调换。id范围 Redis实例

1--10000 Redis01

100001--20000 Redis02

......

90001-100000 Redis10

主服务器的选择

哨兵leader根据以下规则从客观下线的主服务器的从服务器中选择出新的主服务器。

1. 过滤掉主观下线的节点

2. 选择slave-priority最高的节点,如果由则返回没有就继续选择

3. 选择出复制偏移量最大的系节点,因为复制偏移量越大则数据复制的越完整,如果由就返回了,没

有就继续

4. 选择run_id最小的节点,因为run_id越小说明重启次数越少

18.在多机Redis使用时,如何保证主从服务器的数据一致性?主从备份数据同步

同步数据集

Redis 2.8以前使用SYNC命令同步复制

Redis 2.8之后采用PSYNC命令替代SYNC

旧版本

Redis 2.8以前

实现方式

Redis的同步功能分为同步(sync)和命令传播(command propagate)。

1)同步操作:

1. 通过从服务器发送到SYNC命令给主服务器

2. 主服务器生成RDB文件并发送给从服务器,同时发送保存所有写命令给从服务器

3. 从服务器清空之前数据并执行解释RDB文件

4. 保持数据一致(还需要命令传播过程才能保持一致)

Redis一问多答(必背篇)_第17张图片

2)命令传播操作:

同步操作完成后,主服务器执行写命令,该命令发送给从服务器并执行,使主从保存一致。

缺陷

没有全量同步和增量同步的概念,从服务器在同步时,会清空所有数据。

主从服务器断线后重复制,主服务器会重新生成RDB文件和重新记录缓冲区的所有命令,并全量同步到

从服务器上。

新版

Redis 2.8以后

实现方式

在Redis 2.8之后使用PSYNC命令,具备完整重同步和部分重同步模式。

Redis 的主从同步,分为全量同步增量同步

只有从机第一次连接上主机是全量同步

断线重连有可能触发全量同步也有可能是增量同步( master 判断 runid 是否一致)。

Redis一问多答(必背篇)_第18张图片

除此之外的情况都是增量同步

全量同步

Redis 的全量同步过程主要分三个阶段:

  • 同步快照阶段: Master 创建并发送快照RDB给 Slave , Slave 载入并解析快照。 Master 同时将

此阶段所产生的新的写命令存储到缓冲区。

  • 同步写缓冲阶段: Master 向 Slave 同步存储在缓冲区的写操作命令。
  • 同步增量阶段: Master 向 Slave 同步写操作命令。

Redis一问多答(必背篇)_第19张图片

增量同步

Redis增量同步主要指Slave完成初始化后开始正常工作时, Master 发生的写操作同步到 Slave 的

过程。

通常情况下, Master 每执行一个写命令就会向 Slave 发送相同的写命令,然后 Slave 接收并执

行。

心跳检测

在命令传播阶段,从服务器默认会以每秒一次的频率向主服务器发送命令:

replconf ack

#ack :应答

#replication_offset:从服务器当前的复制偏移量

主要作用有三个:

1. 检测主从的连接状态

检测主从服务器的网络连接状态

通过向主服务器发送INFO replication命令,可以列出从服务器列表,可以看出从最后一次向主发

送命令距离现在过了多少秒。lag的值应该在0或1之间跳动,如果超过1则说明主从之间的连接有

故障。

1. 辅助实现min-slaves

Redis可以通过配置防止主服务器在不安全的情况下执行写命令

min-slaves-to-write 3 (min-replicas-to-write 3 )

min-slaves-max-lag 10 (min-replicas-max-lag 10)

上面的配置表示:从服务器的数量少于3个,或者三个从服务器的延迟(lag)值都大于或等于10

秒时,主服务器将拒绝执行写命令。这里的延迟值就是上面INFOreplication命令的lag值。

2. 检测命令丢失

如果因为网络故障,主服务器传播给从服务器的写命令在半路丢失,那么当从服务器向主服务器发

送REPLCONF ACK命令时,主服务器将发觉从服务器当前的复制偏移量少于自己的复制偏移量,

然后主服务器就会根据从服务器提交的复制偏移量,在复制积压缓冲区里面找到从服务器缺少的数

据,并将这些数据重新发送给从服务器。(补发) 网络不断

增量同步:网断了,再次连接时

19.一致性Hash的过程、扩容过程

使用场景:分布式下client端分区时

普通hash

hash(key)%N

hash:可以采用hash算法,比如CRC32、CRC16等

N:是Redis主机个数

比如:

user_id : u001

hash(u001) : 1844213068

Redis实例=1844213068%3

余数为2,所以选择Redis3。

普通Hash的优势实现简单,热点数据分布均匀

普通Hash的缺陷

节点数固定,扩展的话需要重新计算

查询时必须用分片的key来查,一旦key改变,数据就查不出了,所以要使用不易改变的key进行分片

一致性hash

基本概念

普通hash是对主机数量取模,而一致性hash是对2^32(4 294 967 296)取模。我们把2^32想象成一

个圆,就像钟表一样,钟表的圆可以理解成由60个点组成的圆,而此处我们把这个圆想象成由2^32个

点组成的圆,示意图如下:

Redis一问多答(必背篇)_第20张图片

圆环的正上方的点代表0,0点右侧的第一个点代表1,以此类推,2、3、4、5、6……直到2^32-1,也就

是说0点左侧的第一个点代表2^32-1 。我们把这个由2的32次方个点组成的圆环称为hash环。

假设我们有3台缓存服务器,服务器A、服务器B、服务器C,那么,在生产环境中,这三台服务器肯定

有自己的IP地址,我们使用它们各自的IP地址进行哈希计算,使用哈希后的结果对2^32取模,可以使用

如下公式:

hash(服务器的IP地址) % 2^32

通过上述公式算出的结果一定是一个0到2^32-1之间的一个整数,我们就用算出的这个整数,代表服务

器A、服务器B、服务器C,既然这个整数肯定处于0到2^32-1之间,那么,上图中的hash环上必定有一

个点与这个整数对应,也就是服务器A、服务器B、服务C就可以映射到这个环上,如下图:

Redis一问多答(必背篇)_第21张图片

假设,我们需要使用Redis缓存数据,那么我们使用如下公式可以将数据映射到上图中的hash环上。

hashkey % 2^32

映射后的示意图如下,下图中的橘黄色圆形表示数据

Redis一问多答(必背篇)_第22张图片

现在服务器与数据都被映射到了hash环上,上图中的数据将会被缓存到服务器A上,因为从数据的位置

开始,沿顺时针方向遇到的第一个服务器就是A服务器,所以,上图中的数据将会被缓存到服务器A上。

如图:

Redis一问多答(必背篇)_第23张图片

将缓存服务器与被缓存对象都映射到hash环上以后,从被缓存对象的位置出发,沿顺时针方向遇到的第一个服务器,就是当前对象将要缓存于的服务器,由于被缓存对象与服务器hash后的值是固定的,所以,在服务器不变的情况下,数据必定会被缓存到固定的服务器上,那么,当下次想要访问这个数据时,只要再次使用相同的算法进行计算,即可算出这个数据被缓存在哪个服务器上,直接去对应的服务器查找对应的数据即可。多条数据存储如下:

Redis一问多答(必背篇)_第24张图片

优点

添加或移除节点时,数据只需要做部分的迁移,比如上图中把C服务器移除,则数据4迁移到服务器A中,而其他的数据保持不变。添加效果是一样的。

hash环偏移

在介绍一致性哈希的概念时,我们理想化的将3台服务器均匀的映射到了hash环上。也就是说数据的范围是2^32/N。但实际情况往往不是这样的。有可能某个服务器的数据会很多,某个服务器的数据会很少,造成服务器性能不平均。这种现象称为hash环偏移。

Redis一问多答(必背篇)_第25张图片

理论上我们可以通过增加服务器的方式来减少偏移,但这样成本较高,所以我们可以采用虚拟节点的方式,也就是虚拟服务器,如图:

Redis一问多答(必背篇)_第26张图片

"虚拟节点"是"实际节点"(实际的物理服务器)在hash环上的复制品,一个实际节点可以对应多个虚拟节

点。

从上图可以看出,A、B、C三台服务器分别虚拟出了一个虚拟节点,当然,如果你需要,也可以虚拟出更多的虚拟节点。引入虚拟节点的概念后,缓存的分布就均衡多了,上图中,1号、3号数据被缓存在服务器A中,5号、4号数据被缓存在服务器B中,6号、2号数据被缓存在服务器C中,如果你还不放心,可以虚拟出更多的虚拟节点,以便减小hash环偏斜所带来的影响,虚拟节点越多,hash环上的节点就越多,缓存被均匀分布的概率就越大。

缺点

复杂度高

客户端需要自己处理数据路由、高可用、故障转移等问题

使用分区,数据的处理会变得复杂,不得不对付多个redis数据库和AOF文件,不得在多个实例和主机之

间持久化你的数据。不易扩展

一旦节点的增或者删操作,都会导致key无法在redis中命中,必须重新根据节点计算,并手动迁移全部

或部分数据。

泊松分布可看:如何通俗理解泊松分布?

20.分布式锁setnx/redssion、红锁redlock

21.如何保证 Redis 中的数据都是热点数据?Redis淘汰策略,LRU、LFU手写

22.有AOF持久化和无持久化节点的区别

23.redis 中有 1 亿 key,其中有 10w 个 key 是以某个固定已知前缀开头,如何将它们全部找出来?

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

24.秒杀系统设计

《我们一起进大厂》系列-秒杀系统设计

遗留问题

单线程的Redis为什么这么快?

哈希算法MurmurHash

5.0stream的底层listpack紧凑列表和Rax基数树

hash对象的扩容过程,渐进式hash

字典、跳跃表、SDS、hash对象数据结构

lua脚本

有空看看GuavaCache

你可能感兴趣的:(拉勾教育Java高薪培训,redis)