面试碰到分布式技术面试题该怎么解答?

1. 分布式缓存

1.1. Redis 有什么数据类型?分别用于什么场景?

数据类型可以存储的值操作STRING字符串、整数或者浮点数对整个字符串或者字符串的其中一部分执行操作

对整数和浮点数执行自增或者自减操作LIST列表从两端压入或者弹出元素

读取单个或者多个元素

进行修剪,只保留一个范围内的元素SET无序集合添加、获取、移除单个元素

检查一个元素是否存在于集合中

计算交集、并集、差集

从集合里面随机获取元素HASH包含键值对的无序散列表添加、获取、移除单个键值对

获取所有键值对

检查某个键是否存在ZSET有序集合添加、获取、删除元素

根据分值范围或者成员来获取元素

计算一个键的排名

What Redis data structures look like

1.2. Redis 的主从复制是如何实现的?

  1. 从服务器连接主服务器,发送 SYNC 命令;
  2. 主服务器接收到 SYNC 命名后,开始执行 BGSAVE 命令生成 RDB 文件并使用缓冲区记录此后执行的所有写命令;
  3. 主服务器 BGSAVE 执行完后,向所有从服务器发送快照文件,并在发送期间继续记录被执行的写命令;
  4. 从服务器收到快照文件后丢弃所有旧数据,载入收到的快照;
  5. 主服务器快照发送完毕后开始向从服务器发送缓冲区中的写命令;
  6. 从服务器完成对快照的载入,开始接收命令请求,并执行来自主服务器缓冲区的写命令;

1.3. Redis 的 key 是如何寻址的?

背景

(1)redis 中的每一个数据库,都由一个 redisDb 的结构存储。其中:

  • redisDb.id 存储着 redis 数据库以整数表示的号码。
  • redisDb.dict 存储着该库所有的键值对数据。
  • redisDb.expires 保存着每一个键的过期时间。

(2)当 redis 服务器初始化时,会预先分配 16 个数据库(该数量可以通过配置文件配置),所有数据库保存到结构 redisServer 的一个成员 redisServer.db 数组中。当我们选择数据库 select number 时,程序直接通过 redisServer.db[number] 来切换数据库。有时候当程序需要知道自己是在哪个数据库时,直接读取 redisDb.id 即可。

(3)redis 的字典使用哈希表作为其底层实现。dict 类型使用的两个指向哈希表的指针,其中 0 号哈希表(ht[0])主要用于存储数据库的所有键值,而 1 号哈希表主要用于程序对 0 号哈希表进行 rehash 时使用,rehash 一般是在添加新值时会触发,这里不做过多的赘述。所以 redis 中查找一个 key,其实就是对进行该 dict 结构中的 ht[0] 进行查找操作。

(4)既然是哈希,那么我们知道就会有哈希碰撞,那么当多个键哈希之后为同一个值怎么办呢?redis 采取链表的方式来存储多个哈希碰撞的键。也就是说,当根据 key 的哈希值找到该列表后,如果列表的长度大于 1,那么我们需要遍历该链表来找到我们所查找的 key。当然,一般情况下链表长度都为是 1,所以时间复杂度可看作 o(1)。

寻址 key 的步骤

  1. 当拿到一个 key 后,redis 先判断当前库的 0 号哈希表是否为空,即:if (dict->ht[0].size == 0)。如果为 true 直接返回 NULL。
  2. 判断该 0 号哈希表是否需要 rehash,因为如果在进行 rehash,那么两个表中者有可能存储该 key。如果正在进行 rehash,将调用一次_dictRehashStep 方法,_dictRehashStep 用于对数据库字典、以及哈希键的字典进行被动 rehash,这里不作赘述。
  3. 计算哈希表,根据当前字典与 key 进行哈希值的计算。
  4. 根据哈希值与当前字典计算哈希表的索引值。
  5. 根据索引值在哈希表中取出链表,遍历该链表找到 key 的位置。一般情况,该链表长度为 1。
  6. 当 ht[0] 查找完了之后,再进行了次 rehash 判断,如果未在 rehashing,则直接结束,否则对 ht[1]重复 345 步骤。

1.4. Redis 的集群模式是如何实现的?

Redis Cluster 是 Redis 的分布式解决方案,在 Redis 3.0 版本正式推出的。

Redis Cluster 去中心化,每个节点保存数据和整个集群状态,每个节点都和其他所有节点连接。

Redis Cluster 节点分配

Redis Cluster 特点:

  1. 所有的 redis 节点彼此互联(PING-PONG 机制),内部使用二进制协议优化传输速度和带宽。
  2. 节点的 fail 是通过集群中超过半数的节点检测失效时才生效。
  3. 客户端与 redis 节点直连,不需要中间 proxy 层。客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可。
  4. redis-cluster 把所有的物理节点映射到[0-16383] 哈希槽 (hash slot)上(不一定是平均分配),cluster 负责维护 node、slot、value。
  5. Redis 集群预分好 16384 个桶,当需要在 Redis 集群中放置一个 key-value 时,根据 CRC16(key) mod 16384 的值,决定将一个 key 放到哪个桶中。

Redis Cluster 主从模式

Redis Cluster 为了保证数据的高可用性,加入了主从模式。

一个主节点对应一个或多个从节点,主节点提供数据存取,从节点则是从主节点拉取数据备份。当这个主节点挂掉后,就会有这个从节点选取一个来充当主节点,从而保证集群不会挂掉。所以,在集群建立的时候,一定要为每个主节点都添加了从节点。

Redis Sentinel

Redis Sentinel 用于管理多个 Redis 服务器,它有三个功能:

  • 监控(Monitoring) - Sentinel 会不断地检查你的主服务器和从服务器是否运作正常。
  • 提醒(Notification) - 当被监控的某个 Redis 服务器出现问题时, Sentinel 可以通过 API 向管理员或者其他应用程序发送通知。
  • 自动故障迁移(Automatic failover) - 当一个主服务器不能正常工作时, Sentinel 会开始一次自动故障迁移操作, 它会将失效主服务器的其中一个从服务器升级为新的主服务器, 并让失效主服务器的其他从服务器改为复制新的主服务器; 当客户端试图连接失效的主服务器时, 集群也会向客户端返回新主服务器的地址, 使得集群可以使用新主服务器代替失效服务器。

Redis 集群中应该有奇数个节点,所以至少有三个节点。

哨兵监控集群中的主服务器出现故障时,需要根据 quorum 选举出一个哨兵来执行故障转移。选举需要 majority,即大多数哨兵是运行的(2 个哨兵的 majority=2,3 个哨兵的 majority=2,5 个哨兵的 majority=3,4 个哨兵的 majority=2)。

假设集群仅仅部署 2 个节点

+----+ +----+
| M1 |---------| R1 |
| S1 | | S2 |
+----+ +----+

如果 M1 和 S1 所在服务器宕机,则哨兵只有 1 个,无法满足 majority 来进行选举,就不能执行故障转移。

1.5. Redis 如何实现分布式锁?ZooKeeper 如何实现分布式锁?比较二者优劣?

分布式锁的三种实现:

  • 基于数据库实现分布式锁;
  • 基于缓存(Redis 等)实现分布式锁;
  • 基于 Zookeeper 实现分布式锁;

数据库实现

Redis 实现

  1. 获取锁的时候,使用 setnx 加锁,并使用 expire 命令为锁添加一个超时时间,超过该时间则自动释放锁,锁的 value 值为一个随机生成的 UUID,通过此在释放锁的时候进行判断。
  2. 获取锁的时候还设置一个获取的超时时间,若超过这个时间则放弃获取锁。
  3. 释放锁的时候,通过 UUID 判断是不是该锁,若是该锁,则执行 delete 进行锁释放。

ZooKeeper 实现

  1. 创建一个目录 mylock;
  2. 线程 A 想获取锁就在 mylock 目录下创建临时顺序节点;
  3. 获取 mylock 目录下所有的子节点,然后获取比自己小的兄弟节点,如果不存在,则说明当前线程顺序号最小,获得锁;
  4. 线程 B 获取所有节点,判断自己不是最小节点,设置监听比自己次小的节点;
  5. 线程 A 处理完,删除自己的节点,线程 B 监听到变更事件,判断自己是不是最小的节点,如果是则获得锁。

实现对比

ZooKeeper 具备高可用、可重入、阻塞锁特性,可解决失效死锁问题。 但 ZooKeeper 因为需要频繁的创建和删除节点,性能上不如 Redis 方式。

1.6. Redis 的持久化方式?有什么优缺点?持久化实现原理?

RDB 快照(snapshot)

将存在于某一时刻的所有数据都写入到硬盘中。

快照的原理

在默认情况下,Redis 将数据库快照保存在名字为 dump.rdb 的二进制文件中。你可以对 Redis 进行设置, 让它在“N 秒内数据集至少有 M 个改动”这一条件被满足时, 自动保存一次数据集。你也可以通过调用 SAVE 或者 BGSAVE,手动让 Redis 进行数据集保存操作。这种持久化方式被称为快照。

当 Redis 需要保存 dump.rdb 文件时, 服务器执行以下操作:

  • Redis 创建一个子进程。
  • 子进程将数据集写入到一个临时快照文件中。
  • 当子进程完成对新快照文件的写入时,Redis 用新快照文件替换原来的快照文件,并删除旧的快照文件。

这种工作方式使得 Redis 可以从写时复制(copy-on-write)机制中获益。

快照的优点

  • 它保存了某个时间点的数据集,非常适用于数据集的备份。
  • 很方便传送到另一个远端数据中心或者亚马逊的 S3(可能加密),非常适用于灾难恢复。
  • 快照在保存 RDB 文件时父进程唯一需要做的就是 fork 出一个子进程,接下来的工作全部由子进程来做,父进程不需要再做其他 IO 操作,所以快照持久化方式可以最大化 redis 的性能。
  • 与 AOF 相比,在恢复大的数据集的时候,DB 方式会更快一些。

快照的缺点

  • 如果你希望在 redis 意外停止工作(例如电源中断)的情况下丢失的数据最少的话,那么快照不适合你。
  • 快照需要经常 fork 子进程来保存数据集到硬盘上。当数据集比较大的时候,fork 的过程是非常耗时的,可能会导致 Redis 在一些毫秒级内不能响应客户端的请求。

AOF

AOF 持久化方式记录每次对服务器执行的写操作。当服务器重启的时候会重新执行这些命令来恢复原始的数据。

AOF 的原理

  • Redis 创建一个子进程。
  • 子进程开始将新 AOF 文件的内容写入到临时文件。
  • 对于所有新执行的写入命令,父进程一边将它们累积到一个内存缓存中,一边将这些改动追加到现有 AOF 文件的末尾,这样样即使在重写的中途发生停机,现有的 AOF 文件也还是安全的。
  • 当子进程完成重写工作时,它给父进程发送一个信号,父进程在接收到信号之后,将内存缓存中的所有数据追加到新 AOF 文件的末尾。
  • 搞定!现在 Redis 原子地用新文件替换旧文件,之后所有命令都会直接追加到新 AOF 文件的末尾。

AOF 的优点

  • 使用默认的每秒 fsync 策略,Redis 的性能依然很好(fsync 是由后台线程进行处理的,主线程会尽力处理客户端请求),一旦出现故障,使用 AOF ,你最多丢失 1 秒的数据。
  • AOF 文件是一个只进行追加的日志文件,所以不需要写入 seek,即使由于某些原因(磁盘空间已满,写的过程中宕机等等)未执行完整的写入命令,你也也可使用 redis-check-aof 工具修复这些问题。
  • Redis 可以在 AOF 文件体积变得过大时,自动地在后台对 AOF 进行重写࿱
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值