java程序员进阶:Redis分布式技术问题集锦

Redis 简介

Redis 是彻底开源免费的,遵照BSD协议,是一个高性能的key-value数据库。node

Redis 与其余 key - value 缓存产品有如下三个特色:redis

  • Redis支持数据的持久化,能够将内存中的数据保存在磁盘中,重启的时候能够再次加载进行使用。
  • Redis不只仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储。
  • Redis支持数据的备份,即master-slave模式的数据备份。

Redis 优点算法

  • 性能极高 – Redis能读的速度是110000次/s,写的速度是81000次/s 。
  • 丰富的数据类型 – Redis支持二进制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操做。
  • 原子 – Redis的全部操做都是原子性的,意思就是要么成功执行要么失败彻底不执行。单个操做是原子性的。多个操做也支持事务,即原子性,经过MULTI和EXEC指令包起来。
  • 丰富的特性 – Redis还支持 publish/subscribe, 通知, key 过时等等特性。

分布式缓存

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

Redis 的主从复制是如何实现的?数组

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

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] 进行查找操做。markdown

(4)既然是哈希,那么咱们知道就会有哈希碰撞,那么当多个键哈希以后为同一个值怎么办呢?redis 采起链表的方式来存储多个哈希碰撞的键。也就是说,当根据 key 的哈希值找到该列表后,若是列表的长度大于 1,那么咱们须要遍历该链表来找到咱们所查找的 key。固然,通常状况下链表长度都为是 1,因此时间复杂度可看做 o(1)。数据结构

寻址 key 的步骤

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

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

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

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 如何实现分布式锁?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 方式。

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

RDB 快照(snapshot)

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

快照的原理

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

当 Redis 须要保存 dump.rdb 文件时, 服务器执行如下操做:

  • Redis 建立一个子进程。
  • 子进程将数据集写入到一个临时快照文件中。
  • 当子进程完成对新快照文件的写入时,Redis 用新快照文件替换原来的快照文件,并删除旧的快照文件。 这种工做方式使得 Redis 能够从写时复制(copy-on-write)机制中获益。

快照的优势

  1. 它保存了某个时间点的数据集,很是适用于数据集的备份。
  2. 很方便传送到另外一个远端数据中心或者亚马逊的 S3(可能加密),很是适用于灾难恢复。
  3. 快照在保存 RDB 文件时父进程惟一须要作的就是fork出一个子进程,接下来的工做所有由子进程来作,父进程不须要再作其余 IO 操做,因此快照持久化方式能够最大化 redis 的性能。
  4. 与 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 进行重写:重写后的新 AOF 文件包含了恢复当前数据集所需的最小命令集合。整个重写操做是绝对安全的。
  • AOF 文件有序地保存了对数据库执行的全部写入操做,这些写入操做以 Redis 协议的格式保存。所以 AOF 文件的内容很是容易被人读懂,对文件进行分析(parse)也很轻松。

AOF 的缺点

  • 对于相同的数据集来讲,AOF 文件的体积一般要大于 RDB 文件的体积。
  • 根据所使用的 fsync 策略,AOF 的速度可能会慢于快照。在通常状况下,每秒 fsync 的性能依然很是高,而关闭 fsync 可让 AOF 的速度和快照同样快,即便在高负荷之下也是如此。不过在处理巨大的写入载入时,快照能够提供更有保证的最大延迟时间(latency)。

Redis 过时策略有哪些?

  • noeviction- 当内存使用达到阈值的时候,全部引发申请内存的命令会报错。
  • allkeys-lru- 在主键空间中,优先移除最近未使用的 key。
  • allkeys-random- 在主键空间中,随机移除某个 key。
  • volatile-lru- 在设置了过时时间的键空间中,优先移除最近未使用的 key。
  • volatile-random- 在设置了过时时间的键空间中,随机移除某个 key。
  • volatile-ttl- 在设置了过时时间的键空间中,具备更早过时时间的 key 优先移除。

Redis 和 Memcached 有什么区别?

二者都是非关系型内存键值数据库。有如下主要不一样:

数据类型

  • Memcached 仅支持字符串类型;
  • 而 Redis 支持五种不一样种类的数据类型,使得它能够更灵活地解决问题。

数据持久化

  • Memcached 不支持持久化;
  • Redis 支持两种持久化策略:RDB 快照和 AOF 日志。

分布式

  • Memcached 不支持分布式,只能经过在客户端使用像一致性哈希这样的分布式算法来实现分布式存储,这种方式在存储和查询时都须要先在客户端计算一次数据所在的节点。
  • Redis Cluster 实现了分布式的支持。

内存管理机制

  • Memcached 将内存分割成特定长度的块来存储数据,以彻底解决内存碎片的问题,可是这种方式会使得内存的利用率不高,例如块的大小为 128 bytes,只存储 100 bytes 的数据,那么剩下的 28 bytes 就浪费掉了。
  • 在 Redis 中,并非全部数据都一直存储在内存中,能够将一些好久没用的 value 交换到磁盘。而 Memcached 的数据则会一直在内存中。

为何单线程的 Redis 性能反而优于多线程的 Memcached?

Redis 快速的缘由:

  • 绝大部分请求是纯粹的内存操做(很是快速)
  • 采用单线程,避免了没必要要的上下文切换和竞争条件

非阻塞 IO

  • 内部实现采用 epoll,采用了 epoll+本身实现的简单的事件框架。epoll * 中的读、写、关闭、链接都转化成了事件,而后利用 epoll 的多路复用特性,毫不在 io 上浪费一点时间。

因为篇幅缘由关于分布式服务(RPC)、分布式消息队列(MQ)将会在下次继续分享,喜欢的朋友能够关注一下,感谢您的支持

相关文章
相关标签/搜索