redis原理快速入门知识点总结
1、 项目中缓存是如何使用的?为什么要用缓存?缓存使用不当会造成什么后果?
-
为什么用缓存?
- 1、高性能: 一些需要复杂操作耗时查出来的结果,且确定后面不怎么变化,但是有很多读请求,那么直接将查询出来的结果放在缓存中,后面直接读缓存就好 。直接读缓存就会快很多。
- 2、高并发: mysql 是重型数据库(基于磁盘的),不能承受高并发的。mysql 单机支撑到
2000QPS
是个极限。 缓存是走内存的,内存天然就支撑高并发, 缓存功能简单,就是key-value
式操作,单机支撑的并发量轻松一秒几万十几万,单机承载并发量是 mysql 单机的几十倍 。
2、 redis 和 memcached 有什么区别?redis 的线程模型是什么?为什么 redis 单线程却能支撑高并发?
-
1、Redis和memcached的区别:
- 1、redis 支持复杂的数据结构: redis 相比 memcached 来说,拥有更多的数据结构,能支持更丰富的数据操作。如果需要缓存能够支持更复杂的结构和操作, redis 会是不错的选择。
- 2、redis 原生支持集群模式: 在 redis3.x 版本中,便能支持 cluster 模式,而 memcached 没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据。
-
2、redis的单线程模型:
-
基本原理: redis 内部使用文件事件处理器
file event handler
,这个文件事件处理器是单线程的,所以 redis 才叫做单线程的模型。它采用 IO 多路复用机制同时监听多个 socket,将产生事件的 socket 压入内存队列中,事件分派器根据 socket 上的事件类型来选择对应的事件处理器( 连接应答处理器、命令请求处理器、命令回复处理器 )进行处理 。 -
具体流程:
- 1、redis 服务端进程初始化的时候,会将 server socket 的
AE_READABLE
事件与连接应答处理器关联 。 - 2、 客户端 socket01 向 redis 进程的 server socket 请求建立连接,此时 server socket 会产生一个
AE_READABLE
事件,IO 多路复用程序监听到 server socket 产生的事件后,将该 socket 压入队列中。 - 3、文件事件分派器从队列中获取 socket,交给连接应答处理器。连接应答处理器会创建一个能与客户端通信的 socket01,并将该 socket01 的
AE_READABLE
事件与命令请求处理器关联 。 - 4、 假设此时客户端发送了一个
set key value
请求,此时 redis 中的 socket01 会产生AE_READABLE
事件,IO 多路复用程序将 socket01 压入队列,此时事件分派器从队列中获取到 socket01 产生的AE_READABLE
事件。 - 5、由于前面 socket01 的
AE_READABLE
事件已经与命令请求处理器关联,因此事件分派器将事件交给命令请求处理器来处理。命令请求处理器读取 socket01 的key value
并在自己内存中完成key value
的设置。操作完成后,它会将 socket01 的AE_WRITABLE
事件与命令回复处理器关联。 - 6、如果此时客户端准备好接收返回结果了,那么 redis 中的 socket01 会产生一个
AE_WRITABLE
事件,同样压入队列中,事件分派器找到相关联的命令回复处理器,由命令回复处理器对 socket01 输入本次操作的一个结果,比如ok
,之后解除 socket01 的AE_WRITABLE
事件与命令回复处理器的关联 。
- 1、redis 服务端进程初始化的时候,会将 server socket 的
-
-
3、为啥 redis 单线程模型也能效率这么高
- 1、核心是基于非阻塞的 IO 多路复用机制
- 就是socket和IO多路复用程序相连接,他们两个本身不去处理问题,而是将问题请求传入队列中,由事件处理器进行统一处理。不会造成阻塞:也就是非得第一个socket的请求完成之后,才会接受读取第二个socket的请求并处理请求。
- 2、纯内存操作
- 3、 单线程反而避免了多线程的频繁上下文切换问题,预防了多线程可能产生的竞争问题
- 线程从消耗完时间片保存当前执行的状态再到获取下一次时间片加载这个状态的过程,我们称之为上下文切换,这种切换会影响多线程的执行速度
3、redis 都有哪些数据类型?分别在哪些场景下使用比较合适?
-
String:这是最简单的类型,就是普通的 set 和 get,做简单的 KV 缓存
-
Hash: 这个是类似 map 的一种结构,这个一般就是可以将结构化的数据,比如一个对象(前提是这个对象没嵌套其他的对象)给缓存在 redis 里,然后每次读写缓存的时候,可以就操作 hash 里的某个字段。
hset person name bingo hset person age 20 hset person id 1 hget person name person = { "name": "bingo", "age": 20, "id": 1 }
-
List:
-
list 是有序列表,这个可以玩儿出很多花样。
-
比如可以通过 list 存储一些列表型的数据结构,类似粉丝列表、文章的评论列表之类的东西。
-
比如可以通过 lrange 命令,读取某个闭区间内的元素,可以基于 list 实现分页查询,这个是很棒的一个功能,基于 redis 实现简单的高性能分页,可以做类似微博那种下拉不断分页的东西,性能高,就一页一页走。
-
比如可以搞个简单的消息队列,从 list 头怼进去,从 list 尾巴那里弄出来
lpush mylist 1 lpush mylist 2 lpush mylist 3 4 5 # 1 rpop mylist
-
-
set:
-
set 是无序集合,自动去重。
-
直接基于 set 将系统里需要去重的数据扔进去,自动就给去重了,如果你需要对一些数据进行快速的全局去重,你当然也可以基于 jvm 内存里的 HashSet 进行去重,但是如果你的某个系统部署在多台机器上呢?得基于 redis 进行全局的 set 去重。
-
可以基于 set 玩儿交集、并集、差集的操作,**比如交集吧,可以把两个人的粉丝列表整一个交集,看看俩人的共同好友是谁?**对吧。
-
把两个大 V 的粉丝都放在两个 set 中,对两个 set 做交集
#-------操作一个set------- # 添加元素 sadd mySet 1 # 查看全部元素 smembers mySet # 判断是否包含某个值 sismember mySet 3 # 删除某个/些元素 srem mySet 1 srem mySet 2 4 # 查看元素个数 scard mySet # 随机删除一个元素 spop mySet #-------操作多个set------- # 将一个set的元素移动到另外一个set smove yourSet mySet 2 # 求两set的交集 sinter yourSet mySet # 求两set的并集 sunion yourSet mySet # 求在yourSet中而不在mySet中的元素 sdiff yourSet mySet
-
-
sorted set:
- sorted set 是排序的 set,去重但可以排序,写进去的时候给一个分数,自动根据分数排序
4、 redis 的过期策略都有哪些?内存淘汰机制都有哪些?手写一下 LRU 代码实现?
-
1、redis的过期策略:定期删除+惰性删除
- 定期删除:redis 默认是每隔 100ms 就**随机抽取一些(并不是全局扫描redis缓存,这样不现实,太过消耗cpu算力)**设置了过期时间的 key,检查其是否过期,如果过期就删除 。
- 惰性删除: 这就是说,在你获取某个 key 的时候,redis 会检查一下 ,这个 key 如果设置了过期时间那么是否过期了?如果过期了此时就会删除,不会给你返回任何东西。
- 躲过定期删除和惰性删除的数据:内存淘汰机制
- noeviction: 当内存不足以容纳新写入数据时,新写入操作会报错,这个一般没人用吧,实在是太恶心了。
- allkeys-lru:当内存不足以容纳新写入数据时,在键空间中**,移除最近最少使用的 key**(这个是最常用的)。
- allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个 key,这个一般没人用吧,为啥要随机,肯定是把最近最少使用的 key 给干掉啊。
- volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的 key(这个一般不太合适)。
- volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个 key。
- volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的 key 优先移除。
-
2、手写LRU算法
class LRUCache<K, V> extends LinkedHashMap<K, V> { private final int CACHE_SIZE; /** * 传递进来最多能缓存多少数据 * * @param cacheSize 缓存大小 */ public LRUCache(int cacheSize) { // true 表示让 linkedHashMap 按照访问顺序来进行排序,最近访问的放在头部,最老访问的放在尾部。 super((int) Math.ceil(cacheSize / 0.75) + 1, 0.75f, true); CACHE_SIZE = cacheSize; } @Override protected boolean removeEldestEntry(Map.Entry<K, V> eldest) { // 当 map中的数据量大于指定的缓存个数的时候,就自动删除最老的数据。 return size() > CACHE_SIZE; } }
5、 如何保证 redis 的高并发和高可用?redis 的主从复制原理能介绍一下么?redis 的哨兵原理能介绍一下么?
-
1、高并发: redis 实现高并发主要依靠主从架构,一主多从,一般来说,很多项目其实就足够了,单主用来写入数据,单机几万 QPS,多从用来查询数据,多个从实例可以提供每秒 10w 的 QPS 。
-
1.1、redis的replication原理:
- 核心机制:
- 1、redis 采用异步方式复制数据到 slave 节点,不过 redis2.8 开始,slave node 会周期性地确认自己每次复制的数据量;
- 2、一个 master node 是可以配置多个 slave node 的;
- 3、slave node 也可以连接其他的 slave node;
- 4、slave node 做复制的时候,不会 block master node 的正常工作;
- 5、slave node 在做复制的时候,也不会 block 对自己的查询操作,它会用旧的数据集来提供服务;但是复制完成的时候,需要删除旧数据集,加载新数据集,这个时候就会暂停对外服务了;
- 6、slave node 主要用来进行横向扩容,做读写分离,扩容的 slave node 可以提高读的吞吐量。
- 注意点: 如果采用了主从架构,那么建议必须开启 master node 的持久化,不建议用 slave node 作为 master node 的数据热备,因为那样的话,如果你关掉 master 的持久化,可能在 master 宕机重启的时候数据是空的,然后可能一经过复制, slave node 的数据也丢了。
- master的备份: 万一本地的所有文件丢失了,从备份中挑选一份 rdb 去恢复 master,这样才能确保启动的时候,是有数据的,即使采用了后续讲解的高可用机制,slave node 可以自动接管 master node,但也可能 sentinel 还没检测到 master failure,master node 就自动重启了,还是可能导致上面所有的 slave node 数据被清空 。
-
1.2、redis的replication具体原理流程:
-
1、slave node 启动时,会在自己本地保存 master node 的信息,包括 master node 的
host
和ip
,但是复制流程没开始 。 -
2、 slave node 内部有个定时任务,每秒检查是否有新的 master node 要连接和复制,如果发现,就跟 master node 建立 socket 网络连接。
-
3、然后 slave node 发送
ping
命令给 master node。如果 master 设置了 requirepass,那么 slave node 必须发送 masterauth 的口令过去进行认证。master node 第一次执行全量复制,将所有数据发给 slave node。而在后续,master node 持续将写命令,异步复制给 slave node 。
-
master和slave都会有一个offset
- master和slave为了保持两者的数据的统一性,会各自维护offset,并且,slave的offset会发送给master,master也就会保存每个slave的offset数据。
-
backlog
- master node有一个backlog(默认1M)
-
master run id
- host+Ip定位master node不靠谱,master node一旦重启或者变化,slave应该根据不同的run id区分,run id不同就做全量复制。
- redis-cli debug reload不更改run id区分master node。
-
psync
- 从节点使用psync去master node进行复制,psync run id offset
- master根据runid变化与否,触发FULLRESYNC runid offset触发全量复制,也可以是CONTINUE出发增量复制。
-
1.2.1、全量复制:
- 1、master 执行 bgsave ,在本地生成一份 rdb 快照文件。
- 2、master node 将 rdb 快照文件发送给 slave node,如果 rdb 复制时间超过 60秒(repl-timeout),那么 slave node 就会认为复制失败,可以适当调大这个参数(对于千兆网卡的机器,一般每秒传输 100MB,6G 文件,很可能超过 60s)
- 3、master node 在生成 rdb 时,会将所有新的写命令缓存在内存中,在 slave node 保存了 rdb 之后,再将新的写命令复制给 slave node。
- 4、如果在复制期间,内存缓冲区持续消耗超过 64MB,或者一次性超过256MB,那么停止复制,复制失败
- 5、 slave node 接收到 rdb 之后,清空自己的旧数据,然后重新加载 rdb 到自己的内存中,同时基于旧的数据版本对外提供服务
- 6、 如果 slave node 开启了 AOF,那么会立即执行 BGREWRITEAOF,重写 AOF
-
1.2.2、增量复制:
- 如果全量复制过程中,master-slave 网络连接断掉,那么 slave 重新连接 master 时,会触发增量复制。
- master 直接从自己的 backlog 中获取部分丢失的数据,发送给 slave node,默认 backlog 就是 1MB。
- master 就是根据 slave 发送的 psync 中的 offset 来从 backlog 中获取数据的
-
1.2.3、heartbeat
- 主从节点互相都会发送 heartbeat 信息。
- master 默认每隔 10秒 发送一次 heartbeat,slave node 每隔 1秒 发送一个 heartbeat
-
1.2.4、异步复制
- master每次接收到写命令之后,先在内部写入数据,然后异步发送给slave node。
-
-
2、高可用:
-
2.1、基本原理:redis 的高可用架构,叫做
failover
故障转移,也可以叫做主备切换。master node 在故障时,自动检测,并且将某个 slave node 自动切换为 master node 的过程,叫做主备切换。这个过程,实现了 redis 的主从架构下的高可用。 -
2.2、哨兵机制:
- 集群监控:负责监控 redis master 和 slave 进程是否正常工作。
- 消息通知:如果某个 redis 实例有故障,那么哨兵负责发送消息作为报警通知给管理员。
- 故障转移:如果 master node 挂掉了,会自动转移到 slave node 上。
- 配置中心:如果故障转移发生了,通知 client 客户端新的 master 地址。
-
2.3、哨兵的集群的2 or 3 ?:
-
哨兵至少需要 3 个实例,来保证自己的健壮性。
-
哨兵 + redis 主从的部署架构,是不保证数据零丢失的,只能保证 redis 集群的高可用性。
-
对于哨兵 + redis 主从这种复杂的部署架构,尽量在测试环境和生产环境,都进行充足的测试和演练。
-
哨兵集群为2:哨兵集群必须部署 2 个以上节点,如果哨兵集群仅仅部署了 2 个哨兵实例,quorum = 1;
-
故障点: 配置
quorum=1
,如果 master 宕机, s1 和 s2 中只要有 1 个哨兵认为 master 宕机了,就可以进行切换,同时 s1 和 s2 会选举出一个哨兵来执行故障转移。但是同时这个时候,需要 majority,也就是大多数哨兵都是运行的。2 个哨兵,majority=2 3 个哨兵,majority=2 4 个哨兵,majority=2 5 个哨兵,majority=3 ...
-
故障点:如果此时仅仅是redis M1 进程宕机了,哨兵 s1 正常运行,那么故障转移是 OK 的。但是如果是整个 M1 和 S1 运行的机器宕机了,那么哨兵只有 1 个,此时就没有 majority 来允许执行故障转移,虽然另外一台机器上还有一个 R1,但是故障转移不会执行 。
-
-
哨兵集群为3(必须>3)!!: 配置
quorum=2
,如果 M1 所在机器宕机了,那么三个哨兵还剩下 2 个,S2 和 S3 可以一致认为 master 宕机了,然后选举出一个来执行故障转移,同时 3 个哨兵的 majority 是 2,所以还剩下的 2 个哨兵运行着,就可以允许执行故障转移。
-
-
2.4、redis 哨兵主备切换的数据丢失问题
-
2.4.1、主备切换过程中可能会导致数据丢失: 因为 master->slave 的复制是异步的,所以可能有部分数据还没复制到 slave,master 就宕机了,此时这部分数据就丢失了。
-
2.4.2、集群脑裂导致的数据消失:
-
基本原理:master 所在机器突然脱离了正常的网络,跟其他 slave 机器不能连接,但是实际上 master 还运行着。此时哨兵可能就会认为 master 宕机了,然后开启选举,将其他 slave 切换成了 master。这个时候,集群里就会有两个 master ,也就是所谓的脑裂 。
-
数据丢失情况: client 还没来得及切换到新的 master,还继续向旧 master 写数据。因此旧 master 再次恢复的时候,会被作为一个 slave 挂到新的 master 上去,自己的数据会清空,重新从新的 master 复制数据。而新的 master 并没有后来 client 写入的数据,因此,这部分数据也就丢失了 。
-
-
-
2.5、数据丢失的解决方案
-
min-slaves-to-write 1 min-slaves-max-lag 10
表示:要求至少有 1 个 slave,数据复制和同步的延迟不能超过 10 秒。
如果说一旦所有的 slave,数据复制和同步的延迟都超过了 10 秒钟,那么这个时候,master 就不会再接收任何请求了。
-
2.5.1、减少异步复制数据的丢失: 有了
min-slaves-max-lag
这个配置,就可以确保说,一旦 slave 复制数据和 ack 延时太长,就认为可能 master 宕机后损失的数据太多了,那么就拒绝写请求,这样可以把 master 宕机时由于部分数据未同步到 slave 导致的数据丢失降低的可控范围内 。
-
2.5.2、减少脑裂数据的丢失:
- master 出现了脑裂,跟其他 slave 丢了连接,那么上面两个配置可以确保说,如果不能继续给指定数量的 slave 发送数据,而且 slave 超过 10 秒没有给自己 ack 消息,那么就直接拒绝客户端的写请求。因此在脑裂场景下,最多就丢失 10 秒的数据
- master 出现了脑裂,跟其他 slave 丢了连接,那么上面两个配置可以确保说,如果不能继续给指定数量的 slave 发送数据,而且 slave 超过 10 秒没有给自己 ack 消息,那么就直接拒绝客户端的写请求。因此在脑裂场景下,最多就丢失 10 秒的数据
-
-
2.6、sdown 和 odown 转换机制
- sdown 是主观宕机,就一个哨兵如果自己觉得一个 master 宕机了,那么就是主观宕机
- sdown 达成的条件很简单,如果一个哨兵 ping 一个 master,超过了
is-master-down-after-milliseconds
指定的毫秒数之后,就主观认为 master 宕机了。
- sdown 达成的条件很简单,如果一个哨兵 ping 一个 master,超过了
- odown 是客观宕机,如果 quorum 数量的哨兵都觉得一个 master 宕机了,那么就是客观宕机。
- 如果一个哨兵在指定时间内,收到了 quorum 数量的其它哨兵也认为那个 master 是 sdown 的,那么就认为是 odown 了。
- sdown 是主观宕机,就一个哨兵如果自己觉得一个 master 宕机了,那么就是主观宕机
-
2.7、哨兵的集群的自动发现机制:
- 哨兵互相之间的发现,是通过 redis 的
pub/sub
系统实现的,每个哨兵都会往__sentinel__:hello
这个 channel 里发送一个消息,这时候所有其他哨兵都可以消费到这个消息,并感知到其他的哨兵的存在。 - 每隔两秒钟,每个哨兵都会往自己监控的某个 master+slaves 对应的
__sentinel__:hello
channel 里发送一个消息,内容是自己的 host、ip 和 runid 还有对这个 master 的监控配置。 - 每个哨兵也会去监听自己监控的每个 master+slaves 对应的
__sentinel__:hello
channel,然后去感知到同样在监听这个 master+slaves 的其他哨兵的存在。 - 每个哨兵还会跟其他哨兵交换对
master
的监控配置,互相进行监控配置的同步
- 哨兵互相之间的发现,是通过 redis 的
-
2.8、slave配置的自动纠正:
- 哨兵会负责自动纠正 slave 的一些配置,比如 slave 如果要成为潜在的 master 候选人,哨兵会确保 slave 复制现有 master 的数据;如果 slave 连接到了一个错误的 master 上,比如故障转移之后,那么哨兵会确保它们连接到正确的 master 上 。
-
2.9、slave-master选举算法:
-
如果一个 master 被认为 odown 了,而且 majority 数量的哨兵都允许主备切换,那么某个哨兵就会执行主备切换操作,此时首先要选举一个 slave 来,会考虑 slave 的一些信息:
- 跟 master 断开连接的时长
- slave 优先级
- 复制 offset
- run id
2.9.1、slave-master选举:如果一个 slave 跟 master 断开连接的时间已经超过了
down-after-milliseconds
的 10 倍,外加 master 宕机的时长,那么 slave 就被认为不适合选举为 master。(down-after-milliseconds * 10) + milliseconds_since_master_is_in_SDOWN_state
接下来会对 slave 进行排序:
- 按照 slave 优先级进行排序,slave priority 越低,优先级就越高。
- 如果 slave priority 相同,那么看 replica offset,哪个 slave 复制了越多的数据,offset 越靠后,优先级就越高。
- 如果上面两个条件都相同,那么选择一个 run id 比较小的那个 slave。
-
-
2.10、quorum和majority
- 1、每次一个哨兵要做主备切换,首先需要 quorum 数量的哨兵认为 odown,然后选举出一个哨兵来做切换,这个哨兵还需要得到 majority 哨兵的授权,才能正式执行切换。
- 2、如果 quorum < majority,比如 5 个哨兵,majority 就是 3,quorum 设置为 2,那么就 3 个哨兵授权就可以执行切换。
- 3、但是如果 quorum >= majority,那么必须 quorum 数量的哨兵都授权,比如 5 个哨兵,quorum 是 5,那么必须 5 个哨兵都同意授权,才能执行切换。
-
2.11、configuration epoch
- 哨兵会对一套 redis master+slaves 进行监控,有相应的监控的配置。
- 执行切换的那个哨兵,会从要切换到的新 master(salve->master)那里得到一个 configuration epoch,这就是一个 version 号,每次切换的 version 号都必须是唯一的。
- 如果第一个选举出的哨兵切换失败了,那么其他哨兵,会等待 failover-timeout 时间,然后接替继续执行切换,此时会重新获取一个新的 configuration epoch,作为新的 version 号。
-
2.12、configuration传播:
- 哨兵完成切换之后,会在自己本地更新生成最新的 master 配置,然后同步给其他的哨兵,就是通过之前说的
pub/sub
消息机制。 - 这里之前的 version 号就很重要了,因为各种消息都是通过一个 channel 去发布和监听的,所以一个哨兵完成一次新的切换之后,新的 master 配置是跟着新的 version 号的。其他的哨兵都是根据版本号的大小来更新自己的 master 配置的。
- 哨兵完成切换之后,会在自己本地更新生成最新的 master 配置,然后同步给其他的哨兵,就是通过之前说的
-
6、 redis 的持久化有哪几种方式?不同的持久化机制都有什么优缺点?持久化机制具体底层是如何实现的?
-
1、redis持久化的两种方式:
- RDB:RDB 持久化机制,是对 redis 中的数据执行周期性的持久化(就是一份数据文件)
- AOF:AOF 机制对每条写入命令作为日志,以
append-only
的模式写入一个日志文件中,在 redis 重启的时候,可以通过回放 AOF 日志中的写入指令来重新构建整个数据集 - RDB 和 AOF 两种持久化机制,那么在 redis 重启的时候,会使用 AOF 来重新构建数据,因为 AOF 中的数据更加完整
- AOF的rewrite
-
2、RDB 优缺点:
-
2.1、优点:
-
RDB 会生成多个数据文件,每个数据文件都代表了某一个时刻中 redis 的数据,这种多个数据文件的方式,非常适合做冷备,可以将这种完整的数据文件发送到一些远程的安全存储上去,以预定好的备份策略来定期备份 redis 中的数据。
-
RDB 对 redis 对外提供的读写服务,影响非常小,可以让 redis 保持高性能,因为 redis 主进程只需要 fork 一个子进程,让子进程执行磁盘 IO 操作来进行 RDB 持久化即可。
-
相对于 AOF 持久化机制来说,直接基于 RDB 数据文件来重启和恢复 redis 进程,更加快速。
-
2.2、缺点:
-
如果想要在 redis 故障时,尽可能少的丢失数据,那么 RDB 没有 AOF 好。一般来说,RDB 数据快照文件,都是每隔 5 分钟,或者更长时间生成一次,这个时候就得接受一旦 redis 进程宕机,那么会丢失最近 5 分钟的数据。
-
RDB 每次在 fork 子进程来执行 RDB 快照数据文件生成的时候,如果数据文件特别大,可能会导致对客户端提供的服务暂停数毫秒,或者甚至数秒。
-
-
3、AOF优缺点:
-
优点:
-
1、AOF 可以更好的保护数据不丢失,一般 AOF 会每隔 1 秒,通过一个后台线程执行一次
fsync
(从OS cache写入磁盘的操作)操作,最多丢失 1 秒钟的数据。 -
2、AOF 日志文件以
append-only
模式写入,所以没有任何磁盘寻址的开销,写入性能非常高,而且文件不容易破损,即使文件尾部破损,也很容易修复。 -
AOF 日志文件即使过大的时候,出现后台重写操作,也不会影响客户端的读写。因为在
rewrite
log 的时候,会对其中的指令进行压缩,创建出一份需要恢复数据的最小日志出来。在创建新日志文件的时候,老的日志文件还是照常写入。当新的 merge 后的日志文件 ready 的时候,再交换新老日志文件即可。 -
缺点:
-
1、对于同一份数据来说,AOF 日志文件通常比 RDB 数据快照文件更大。
-
2、AOF 开启后,支持的写 QPS 会比 RDB 支持的写 QPS 低,因为 AOF 一般会配置成每秒
fsync
一次日志文件,当然,每秒一次fsync
,性能也还是很高的。(如果实时写入,那么 QPS 会大降,redis 性能会大大降低)
-
-
4、RDB 和 AOF 到底该如何选择
- redis 支持同时开启开启两种持久化方式,我们可以综合使用 AOF 和 RDB 两种持久化机制,用 AOF 来保证数据不丢失,作为数据恢复的第一选择; 用 RDB 来做不同程度的冷备,在 AOF 文件都丢失或损坏不可用的时候,还可以使用 RDB 来进行快速的数据恢复。
7、 redis 集群模式的工作原理能说一下么?在集群模式下,redis 的 key 是如何寻址的?分布式寻址都有哪些算法?了解一致性 hash 算法吗
-
简单介绍: redis cluster,主要是针对海量数据+高并发+高可用的场景。redis cluster 支撑 N 个 redis master node,每个 master node 都可以挂载多个 slave node。这样整个 redis 就可以横向扩容了。如果你要支撑更大数据量的缓存,那就横向扩容更多的 master 节点,每个 master 节点就能存放更多的数据了。
-
1、redis cluster介绍:
-
1、自动将数据进行分片,每个 master 上放一部分数据
-
2、提供内置的高可用支持,部分 master 不可用时,还是可以继续工作的。
-
3、注意:
- 在 redis cluster 架构下,每个 redis 要放开两个端口号,比如一个是 6379,另外一个就是 加1w 的端口号,比如 16379。
- 16379 端口号是用来进行节点间通信的,也就是 cluster bus 的东西,cluster bus 的通信,用来进行故障检测、配置更新、故障转移授权。cluster bus 用了另外一种二进制的协议,
gossip
协议,用于节点间进行高效的数据交换,占用更少的网络带宽和处理时间 。
-
2、分布式寻址算法:
- 2.1、hash算法: 来了一个 key,首先计算 hash 值,然后对节点数取模。然后打在不同的 master 节点上。一旦某一个 master 节点宕机,所有请求过来,都会基于最新的剩余 master 节点数去取模,尝试去取数据。这会导致大部分的请求过来,全部无法拿到有效的缓存,导致大量的流量涌入数据库 。
-
2.2、一致性hash算法:
- 一致性 hash 算法将整个 hash 值空间组织成一个虚拟的圆环,整个空间按顺时针方向组织,下一步将各个 master 节点(使用服务器的 ip 或主机名)进行 hash。这样就能确定每个节点在其哈希环上的位置。
- 来了一个 key,首先计算 hash 值,并确定此数据在环上的位置,从此位置沿环顺时针“行走”,遇到的第一个 master 节点就是 key 所在位置。
- 在一致性哈希算法中,如果一个节点挂了,受影响的数据仅仅是此节点到环空间前一个节点(沿着逆时针方向行走遇到的第一个节点)之间的数据,其它不受影响。增加一个节点也同理。
- 一致性哈希算法在节点太少时,容易因为节点分布不均匀而造成缓存热点的问题。为了解决这种热点问题,一致性 hash 算法引入了虚拟节点机制,即对每一个节点计算多个 hash,每个计算结果位置都放置一个虚拟节点。这样就实现了数据的均匀分布,负载均衡。
-
2.3、hash slot算法:
- redis cluster 有固定的
16384
个 hash slot,对每个key
计算CRC16
值,然后对16384
取模,可以获取 key 对应的 hash slot。 - redis cluster 中每个 master 都会持有部分 slot,比如有 3 个 master,那么可能每个 master 持有 5000 多个 hash slot。hash slot 让 node 的增加和移除很简单,增加一个 master,就将其他 master 的 hash slot 移动部分过去,减少一个 master,就将它的 hash slot 移动到其他 master 上去。移动 hash slot 的成本是非常低的。客户端的 api,可以对指定的数据,让他们走同一个 hash slot,通过
hash tag
来实现。 - 任何一台机器宕机,另外两个节点,不影响的。因为 key 找的是 hash slot,不是机器。
- redis cluster 有固定的
-
3、节点内部通信机制
- 集群元数据的维护有两种方式:集中式、Gossip 协议。redis cluster 节点间采用 gossip 协议进行通信
- 3.1、集中式: 是将集群元数据(节点信息、故障等等)几种存储在某个节点上。集中式元数据集中存储的一个典型代表,就是大数据领域的
storm
。它是分布式的大数据实时计算引擎,是集中式的元数据存储的结构,底层基于 zookeeper(分布式协调的中间件)对所有元数据进行存储维护 。
- 3.1.1、集中式的好处: 集中式的好处在于,元数据的读取和更新,时效性非常好,一旦元数据出现了变更,就立即更新到集中式的存储中,其它节点读取的时候就可以感知到;
- 3.1.2、缺点: 所有的元数据的更新压力全部集中在一个地方,可能会导致元数据的存储有压力
- 3.2、Gossip: redis 维护集群元数据采用另一个方式,
gossip
协议,所有节点都持有一份元数据,不同的节点如果出现了元数据的变更,就不断将元数据发送给其它的节点,让其它节点也进行元数据的变更 。- 3.2.1、好处: 元数据的更新比较分散,不是集中在一个地方,更新请求会陆陆续续打到所有节点上去更新,降低了压力
- 3.2.2、缺点: 元数据的更新有延时,可能导致集群中的一些操作会有一些滞后
- 3.3、10000端口
- 每个节点都有一个专门用于节点间通信的端口,就是自己提供服务的端口号+10000,比如 7001,那么用于节点间通信的就是 17001 端口。每个节点每隔一段时间都会往另外几个节点发送
ping
消息,同时其它几个节点接收到ping
之后返回pong
。(信息包括故障信息、节点的增加和删除、hash slot等)。
- 每个节点都有一个专门用于节点间通信的端口,就是自己提供服务的端口号+10000,比如 7001,那么用于节点间通信的就是 17001 端口。每个节点每隔一段时间都会往另外几个节点发送
-
4、Gossip协议
- gossip 协议包含多种消息,包含
ping
,pong
,meet
,fail
等等。 - meet: 某个节点发送 meet 给新加入的节点,让新节点加入集群中,然后新节点就会开始与其它节点进行通信 ( 发送了一个 gossip meet 消息给新加入的节点,通知那个节点去加入我们的集群 )
- ping: 每个节点都会频繁给其它节点发送 ping,其中包含自己的状态还有自己维护的集群元数据,互相通过 ping 交换元数据
- pong: 返回 ping 和 meeet,包含自己的状态和其它信息,也用于信息广播和更新。
- fail: 某个节点判断另一个节点 fail 之后,就发送 fail 给其它节点,通知其它节点说,某个节点宕机啦。
- gossip 协议包含多种消息,包含
-
5、ping的探入:
- 每个节点每秒会执行 10 次 ping,每次会选择 5 个最久没有通信的其它节点。当然如果发现某个节点通信延时达到了
cluster_node_timeout / 2
,那么立即发送 ping,避免数据交换延时过长,落后的时间太长了。 - 比如说,两个节点之间都 10 分钟没有交换数据了,那么整个集群处于严重的元数据不一致的情况,就会有问题。所以
cluster_node_timeout
可以调节,如果调得比较大,那么会降低 ping 的频率。 - 每次 ping,会带上自己节点的信息,还有就是带上 1/10 其它节点的信息,发送出去,进行交换。至少包含
3
个其它节点的信息,最多包含总节点数减 2
个其它节点的信息
- 每个节点每秒会执行 10 次 ping,每次会选择 5 个最久没有通信的其它节点。当然如果发现某个节点通信延时达到了
-
6、redis cluster的高可用和主从切换:
- 6.1、判断节点宕机:
- 如果一个节点认为另外一个节点宕机,那么就是
pfail
,主观宕机。如果多个节点都认为另外一个节点宕机了,那么就是fail
,客观宕机,跟哨兵的原理几乎一样,sdown,odown - 在
cluster-node-timeout
内,某个节点一直没有返回pong
,那么就被认为pfail
。 - 如果一个节点认为某个节点
pfail
了,那么会在gossip ping
消息中,ping
给其他节点,如果超过半数的节点都认为pfail
了,那么就会变成fail
。
- 如果一个节点认为另外一个节点宕机,那么就是
- 6.2、从节点过滤:
- 对宕机的 master node,从其所有的 slave node 中,选择一个切换成 master node。
- 检查每个 slave node 与 master node 断开连接的时间,如果超过了
cluster-node-timeout * cluster-slave-validity-factor
,那么就没有资格切换成master
- 6.3、从节点中选举:
- 每个从节点,都根据自己对 master 复制数据的 offset,来设置一个选举时间,offset 越大(复制数据越多)的从节点,选举时间越靠前,优先进行选举。
- 所有的 master node 开始 slave 选举投票,给要进行选举的 slave 进行投票,如果大部分 **master node
(N/2 + 1)
**都投票给了某个从节点,那么选举通过,那个从节点可以切换成 master。 - 从节点执行主备切换,从节点切换为主节点
- 6.1、判断节点宕机:
7、 了解什么是 redis 的雪崩、穿透和击穿?redis 崩溃之后会怎么样?系统该如何应对这种情况?如何处理 redis 的穿透?
-
1、缓存雪崩(解决方案):缓存全盘宕机,数据库背数据流量打死。
- 事前:redis 高可用,主从+哨兵,redis cluster,避免全盘崩溃。
- 事中:本地 ehcache 缓存 + hystrix 限流&降级,避免 MySQL 被打死。
- 事后:redis 持久化,一旦重启,自动从磁盘上加载数据,快速恢复缓存数据。
- 好处:
- 数据库绝对不会死,限流组件确保了每秒只有多少个请求能通过。
- 只要数据库不死,就是说,对用户来说,2/5 的请求都是可以被处理的。
- 只要有 2/5 的请求可以被处理,就意味着你的系统没死,对用户来说,可能就是点击几次刷不出来页面,但是多点几次,就可以刷出来一次。
-
2、缓存穿透: 请求每次都“视缓存于无物”,直接查询数据库。这种恶意攻击场景的缓存穿透就会直接把数据库给打死
- 解决方案: 系统 A 从数据库中只要没查到,就写一个空值到缓存里去,比如
set -999 UNKNOWN
。然后设置一个过期时间,这样的话,下次有相同的 key 来访问的时候,在缓存失效之前,都可以直接从缓存中取数据 。
- 解决方案: 系统 A 从数据库中只要没查到,就写一个空值到缓存里去,比如
-
3、缓存击穿: 某个 key 非常热点,访问非常频繁,处于集中式高并发访问的情况,当这个 key 在失效的瞬间,大量的请求就击穿了缓存,直接请求数据库,就像是在一道屏障上凿开了一个洞
- 解决方案:
- 若缓存的数据是基本不会发生更新的,则可尝试将该热点数据设置为永不过期。
- 若缓存的数据更新不频繁,且缓存刷新的整个流程耗时较少的情况下,则可以采用基于 redis、zookeeper 等分布式中间件的分布式互斥锁,或者本地互斥锁以保证仅少量的请求能请求数据库并重新构建缓存,其余线程则在锁释放后能访问到新缓存。
- 若缓存的数据更新频繁或者缓存刷新的流程耗时较长的情况下,可以利用定时线程在缓存过期前主动的重新构建缓存或者延后缓存的过期时间,以保证所有的请求能一直访问到对应的缓存
- 解决方案:
8、 如何保证缓存与数据库的双写一致性?
-
1、Cache Aside Pattern
- 读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。
- 更新的时候,先更新数据库,然后再删除缓存
-
2、为什么是删除缓存而不是更新缓存?
-
更新缓存的代价高
-
缓存中有大量的冷数据
-
用到缓存才去计算缓存
-
-
3、最初级的缓存不一致性及其解决方案:
- 出现原因:先更新数据库,再删除缓存。如果删除缓存失败了,那么会导致数据库中是新数据,缓存中是旧数据,数据就出现了不一致
- 解决方案: 先删除缓存,再更新数据库。如果数据库更新失败了,那么数据库中是旧数据,缓存中是空的,那么数据不会不一致。因为读的时候缓存没有,所以去读了数据库中的旧数据,然后更新到缓存中 。
-
4、比较复杂的数据不一致性
- 数据发生了变更,先删除了缓存,然后要去修改数据库,此时还没修改。一个请求过来,去读缓存,发现缓存空了,去查询数据库,查到了修改前的旧数据,放到了缓存中。随后数据变更的程序完成了数据库的修改。完了,数据库和缓存中的数据不一样了。
-
5、解决方案:读写串行化
-
1、更新数据的时候,根据数据的唯一标识,将操作路由之后,发送到一个 jvm 内部队列中。读取数据的时候,如果发现数据不在缓存中,那么将重新执行“读取数据+更新缓存”的操作,根据唯一标识路由之后,也发送到同一个 jvm 内部队列中 。
-
2、一个队列对应一个工作线程,每个工作线程串行拿到对应的操作,然后一条一条的执行。
-
3、这样的话,一个数据变更的操作,先删除缓存,然后再去更新数据库,但是还没完成更新。此时如果一个读请求过来,没有读到缓存,那么可以先将读的请求发送到队列中,此时会在队列中积压,然后同步等待缓存更新完成。
-
4、优化:这里有一个优化点,一个队列中,在上一个请求的读+更新缓存没有完成时,其实多个读+更新缓存请求串在一起是没意义的,因此可以做过滤,如果发现队列中已经有一个更新缓存的请求了,那么就不用再放个更新请求操作进去了,直接等待前面的更新操作请求完成即可。
-
5、 如果后续很多的读请求还在等待时间范围内,不断轮询缓存发现可以取到值了,那么就直接返回;如果请求等待的时间超过一定时长,那么这一次直接从数据库中读取当前的值。
-
-
6、解决方案的问题:读请求长时间阻塞
- 问题: 可能数据更新很频繁,导致队列中积压了大量更新操作在里面,然后读请求会发生大量的超时,最后导致大量的请求直接走数据库
- 解决: 如果一个内存队列中可能积压的更新操作特别多,那么你就要加机器 。
- 多服务实例部署的请求路由:可能这个服务部署了多个实例,那么必须保证说,执行数据更新操作,以及执行缓存更新操作的请求,都通过 Nginx 服务器路由到相同的服务实例上 (对同一个商品的读写请求,全部路由到同一台机器上)
9、 redis 的并发竞争问题是什么?如何解决这个问题?了解 redis 事务的 CAS 方案吗?
-
问题描述: 多客户端同时并发写一个 key,可能本来应该先到的数据,但是后到了,导致数据版本错了;或者是多客户端同时获取一个 key,修改值之后再写回去,只要顺序错了,数据就错了 。
-
解决方案: 可以基于 zookeeper 实现分布式锁。每个系统通过 zookeeper 获取分布式锁,确保同一时间,只能有一个系统实例在操作某个 key,别人都不允许读和写 。
- 写入的机制:
- 你要写入缓存的数据,都是从 mysql 里查出来的,都得写入 mysql 中,写入 mysql 中的时候必须保存一个时间戳,从 mysql 查出来的时候,时间戳也查出来。
- 每次要写之前,先判断一下当前这个 value 的时间戳是否比缓存里的 value 的时间戳要新。如果是的话,那么可以写,否则,就不能用旧的数据覆盖新的数据。
- 写入的机制:
10、 生产环境中的 redis 是怎么部署的?
- 1、redis cluster,10 台机器,5 台机器部署了 redis 主实例,另外 5 台机器部署了 redis 的从实例,每个主实例挂了一个从实例,5 个节点对外提供读写服务,每个节点的读写高峰qps可能可以达到每秒 5 万,5 台机器最多是 25 万读写请求/s。
- 2、机器是什么配置?32G 内存+ 8 核 CPU + 1T 磁盘,但是分配给 redis 进程的是10g内存,一般线上生产环境,redis 的内存尽量不要超过 10g,超过 10g 可能会有问题。
- 3、5 台机器对外提供读写,一共有 50g 内存。因为每个主实例都挂了一个从实例,所以是高可用的,任何一个主实例宕机,都会自动故障迁移,redis 从实例会自动变成主实例继续提供读写服务。
- 4、你往内存里写的是什么数据?每条数据的大小是多少?商品数据,每条数据是 10kb。100 条数据是 1mb,10 万条数据是 1g。常驻内存的是 200 万条商品数据,占用内存是 20g,仅仅不到总内存的 50%。目前高峰期每秒就是 3500 左右的请求量。
不能用旧的数据覆盖新的数据*。
[外链图片转存中…(img-mDGCgLZK-1584788530819)]
10、 生产环境中的 redis 是怎么部署的?
- 1、redis cluster,10 台机器,5 台机器部署了 redis 主实例,另外 5 台机器部署了 redis 的从实例,每个主实例挂了一个从实例,5 个节点对外提供读写服务,每个节点的读写高峰qps可能可以达到每秒 5 万,5 台机器最多是 25 万读写请求/s。
- 2、机器是什么配置?32G 内存+ 8 核 CPU + 1T 磁盘,但是分配给 redis 进程的是10g内存,一般线上生产环境,redis 的内存尽量不要超过 10g,超过 10g 可能会有问题。
- 3、5 台机器对外提供读写,一共有 50g 内存。因为每个主实例都挂了一个从实例,所以是高可用的,任何一个主实例宕机,都会自动故障迁移,redis 从实例会自动变成主实例继续提供读写服务。
- 4、你往内存里写的是什么数据?每条数据的大小是多少?商品数据,每条数据是 10kb。100 条数据是 1mb,10 万条数据是 1g。常驻内存的是 200 万条商品数据,占用内存是 20g,仅仅不到总内存的 50%。目前高峰期每秒就是 3500 左右的请求量。