关于从 Redis 迁移到 Redis 集群

关于从 Redis 迁移到 Redis 集群

对于一款已经发展了十多年的产品来说,发现其中的技术已经过时也就不足为奇了。但是,如果六个月内您必须保持高 10 倍的负载,并且跌倒的成本将增加数百倍怎么办?在这种情况下,您需要一位出色的高负载工程师。但在没有女佣的情况下,他们就委托我来解决这个问题。在文章的第一部分中,我将告诉您我们如何从 Redis 迁移到 Redis-cluster,在第二部分中,我将给出有关如何开始使用集群以及使用集群时需要注意的事项的建议。

技术选择

有那么糟糕吗? 单独的Redis (独立redis)在1个主服务器和N个从服务器的配置中? 为什么我称其为过时的技术?

不,Redis 并没有那么糟糕……但是,也有一些不容忽视的缺点。

  • 首先,Redis不支持master故障后的灾难恢复机制。为了解决这个问题,我们使用了一种配置,可以自动将 VIP 转移到新的主服务器,更改其中一个从服务器的角色并切换其余的服务器。这种机制确实有效,但还不能称为可靠的解决方案。首先会出现误报,其次是一次性的,使用后需要手动对弹簧储能。

  • 其次,只有一个master导致了分片的问题。 我们必须创建几个独立的集群“1 个主服务器和 N 个从服务器”,然后在这些机器之间手动分配数据库,并希望明天其中一个数据库不会膨胀太多,以至于必须将其移动到单独的实例。

有哪些选择?

  • 最昂贵、最丰富的解决方案是 Redis-Enterprise。 这是一个盒装解决方案,提供全面的技术支持。 尽管从技术角度来看它看起来很理想,但出于意识形态原因它并不适合我们。
  • Redis 集群。开箱即用地支持主故障转移和分片。界面与普通版本几乎没有区别。看起来很有希望,我们稍后会讨论其中的陷阱。
  • Tarantool、Memcache、Aerospike 等。所有这些工具都做几乎相同的事情。但每个都有其自身的缺点。我们决定不把所有鸡蛋放在同一个篮子里。我们使用 Memcache 和 Tarantool 来完成其他任务,展望未来,我会说在我们的实践中它们存在更多问题。

使用细节

让我们看一下历史上我们使用 Redis 解决了哪些问题以及使用了哪些功能:

  • 在向 2GIS 等远程服务发出请求之前进行缓存 | 戈兰

    获取设置 MGET MSET“选择数据库”

  • MYSQL之前的缓存 | PHP

    获取设置 MGET MSET 扫描“按模式键入”“选择数据库”

  • 会话和驱动程序坐标工作服务的主要存储 | 戈兰

    获取设置 MGET MSET“选择数据库”“添加地理密钥”“获取地理密钥”扫描

正如你所看到的,没有高等数学。那么有什么困难呢?让我们分别看看每个方法。

方法
使用说明
Redis集群的特点

获取设置
写/读键

MGET MSET
写入/读取多个密钥
密钥将位于不同的节点上。现成的库只能在一个节点内执行多种操作
将 MGET 替换为包含 N 个 GET 操作的管道

选择数据库
选择我们将合作的基地
不支持多数据库
将所有内容放入一个数据库中。 为键添加前缀

SCAN
遍历数据库中的所有键
由于我们只有一个数据库,因此遍历集群中的所有键成本太高
维护一个键内的不变量并对该键执行 HSCAN。 或者完全拒绝

GEO
使用 geokey 进行操作
geokey 未分片

按模式键入
按模式搜索键
由于我们只有一个数据库,因此我们将搜索集群中的所有键。太贵了
拒绝或维持不变式,如 SCAN 的情况

Redis 与 Redis 集群

切换到集群时我们会失去什么,又会得到什么?

  • 缺点:我们失去了一些数据库的功能。
    • 如果我们想将逻辑上不相关的数据存储在一个集群中,我们就必须以前缀的形式做拐杖。
    • 我们丢失了所有“基本”操作,例如 SCAN、DBSIZE、CLEAR DB 等。
    • 多操作变得更加难以实现,因为它可能需要访问多个节点。
  • 优点:
    • 以主故障转移形式的容错。
    • Redis 端的分片。
    • 以原子方式在节点之间传输数据,无需停机。
    • 添加并重新分配容量和负载,无需停机。

我的结论是,如果您不需要提供高水平的容错能力,那么迁移到集群就不值得,因为这可能是一项不平凡的任务。 但是,如果您最初在独立版本和集群版本之间进行选择,那么您应该选择集群,因为它并不更糟,而且还能减轻您的一些麻烦

准备搬家

我们先来说说搬家的要求:

  • 它应该是无缝的。 完全停止服务 5 分钟不适合我们。
  • 它应该尽可能安全和渐进。 我想对局势有所控制。 我们不想立即转储所有内容并祈祷回滚按钮。
  • 移动时数据丢失最少。 我们知道原子移动非常困难,因此我们允许常规 Redis 和集群 Redis 中的数据之间存在一定程度的不同步。

集群维护

在搬家之前,我们应该考虑是否可以支持集群:

  • 图表。我们使用 Prometheus 和 Grafana 来绘制 CPU 负载、内存使用情况、客户端数量、GET、SET、AUTH 操作数量等图表。
  • 专业知识。 想象一下,明天您将负责一个巨大的集群。 如果它坏了,除了你之外没有人可以修复它。 如果他开始放慢速度,每个人都会跑向你。 如果您需要添加资源或重新分配负载,请回来找您。 为了不在 25 岁时变灰,建议考虑这些情况并提前检查该技术在某些操作下的表现。 让我们在“专业知识”部分更详细地讨论这一点。
  • 监控和警报。 当集群发生故障时,您希望成为第一个知道的人。 在这里,我们仅限于通知所有节点返回有关集群状态的相同信息(是的,它的发生方式不同)。 而其他问题可以通过 Redis 客户端服务的警报更快地发现。

路口

我们将如何移动:

  • 首先,您需要准备一个与集群一起使用的库。 我们以 go-redis 作为 Go 版本的基础,并对其进行了一些更改以适合我们自己。 我们通过管道实现了多方法,并且还稍微修正了重复请求的规则。 PHP版本有更多问题,但我们最终选择了php-redis。 他们最近引入了集群支持,我们认为它看起来不错。
  • 接下来您需要部署集群本身。 这实际上是根据配置文件通过两个命令完成的。 我们将在下面更详细地讨论该设置。
  • 对于逐渐移动,我们使用干模式。由于我们有两个版本的库具有相同的接口(一个用于常规版本,另一个用于集群),因此创建一个包装器无需花费任何成本,该包装器将与单独的版本一起使用并并行地将所有请求复制到集群,比较响应并在日志中写入差异(在我们的例子中是 NewRelic)。这样,即使集群版本在上线过程中出现故障,我们的生产也不会受到影响。
  • 在干模式下推出集群后,我们可以冷静地查看响应差异图。 如果错误率缓慢但稳定地向某个小常数移动,那么一切都很好。 为什么仍然存在差异? 由于单独版本中的记录比集群中的记录发生得稍早,并且由于微滞后,数据可能会出现偏差。 剩下的就是查看差异日志,如果它们都可以通过记录的非原子性来解释,那么我们就可以继续。
  • 现在您可以向相反方向切换干燥模式。 我们将从集群中写入和读取,并将其复制到一个单独的版本中。 为了什么? 在接下来的一周里,我想观察集群的工作。 如果突然发现峰值负载出现问题,或者我们没有考虑到某些问题,那么借助干模式,我们总是可以紧急回滚到旧代码和当前数据。
  • 剩下的就是禁用干模式并拆除单独的版本。

专长

首先简单介绍一下集群设计。

首先,Redis 是一个键值存储。 任意字符串用作键。 数字、字符串和整个结构都可以用作值。 后者有很多,但对于理解一般结构来说,这对我们来说并不重要。
键之后的下一个抽象级别是槽(SLOTS)。 每个密钥属于 16 个插槽之一。 每个槽内可以有任意数量的钥匙。 因此,所有键都被分为 383 个不相交的集合。
关于从 Redis 迁移到 Redis 集群

接下来,集群中必须有N个主节点。 每个节点都可以被视为一个单独的 Redis 实例,它了解集群中其他节点的所有信息。 每个主节点包含多个插槽。 每个槽位只属于一个主节点。 所有时隙都需要在节点之间分配。 如果某些插槽未分配,则存储在其中的密钥将无法访问。 在单独的逻辑或物理机器上运行每个主节点是有意义的。 还值得记住的是,每个节点仅在一个核心上运行,如果您想在同一台逻辑机器上运行多个 Redis 实例,请确保它们在不同的核心上运行(我们还没有尝试过这一点,但理论上它应该可以工作) 。 本质上,主节点提供定期分片,更多主节点允许扩展写入和读取请求。

当所有密钥都分布在槽之间,并且槽分散在主节点之间之后,每个主节点可以添加任意数量的从节点。 在每个这样的主从链接中,正常复制将起作用。 需要从属设备来扩展读取请求并在主设备发生故障时进行故障转移。
关于从 Redis 迁移到 Redis 集群

现在我们来谈谈最好能做的操作。

我们将通过 Redis-CLI 访问系统。 由于 Redis 没有单一入口点,因此您可以在任何节点上执行以下操作。 在每一点上,我都分别提请注意在负载下执行操作的可能性。

  • 我们首先需要做的也是最重要的事情是集群节点的操作。 它返回集群的状态,显示节点列表、它们的角色、槽分布等。 可以使用集群信息和集群槽获取更多信息。
  • 如果能够添加和删除节点就太好了。 为此,存在集群相遇和集群遗忘操作。 请注意,集群遗忘必须应用于每个节点,包括主节点和副本节点。 而cluster meet只需要在一个节点上调用即可。 这种差异可能会令人不安,因此最好在使用集群之前了解它。 添加节点是在战斗中安全完成的,不会以任何方式影响集群的运行(这是合乎逻辑的)。 如果要从集群中删除节点,则应确保该节点上没有剩余插槽(否则您可能会失去对该节点上所有密钥的访问权限)。 另外,不要删除有从属设备的主设备,否则将对新主设备进行不必要的投票。 如果节点不再有槽,那么这是一个小问题,但是如果我们可以先删除从节点,为什么我们需要额外的选择呢?
  • 如果需要强制交换主从位置,那么集群故障转移命令就可以了。 在战斗中调用它时,您需要了解主人在操作过程中将无法使用。 通常,切换发生的时间不到一秒,但不是原子的。 您可以预期,在此期间,向 master 发出的某些请求将会失败。
  • 从集群中删除节点之前,该节点上不应留下任何插槽。最好使用 cluster reshard 命令重新分配它们。插槽将从一个主机转移到另一个主机。整个操作可能需要几分钟,这取决于传输的数据量,但传输过程是安全的,不会以任何方式影响集群的运行。因此,所有数据都可以在负载下直接从一个节点传输到另一个节点,而无需担心其可用性。然而,也有一些微妙之处。首先,数据传输与接收方和发送方节点上的一定负载相关。如果接收节点的处理器负载已经很重,那么您不应该通过接收新数据来加载它。其次,一旦发送主设备上没有剩余的时隙,其所有从设备将立即转到这些时隙被传输到的主设备。问题是所有这些从站都希望立即同步数据。如果是部分同步而不是完全同步,你会很幸运。考虑到这一点,并结合传输插槽和禁用/传输从属设备的操作。或者希望你有足够的安全边际。
  • 如果在转账过程中,您发现自己的名额丢失了,该怎么办? 我希望这个问题不会影响您,但如果影响了,则有一个集群修复操作。 至少,她会以随机顺序将插槽分散在节点上。 我建议首先从集群中删除具有分布式插槽的节点来检查其操作。 由于未分配槽中的数据已经不可用,因此担心这些槽的可用性问题为时已晚。 反过来,该操作不会影响分布式槽。
  • 另一个有用的操作是监视。 它允许您实时查看发送到节点的请求的完整列表。 此外,您可以grep它并查看是否有必要的流量。

还值得一提的是主故障转移过程。 简而言之,它存在,而且在我看来,它效果很好。 但是,不要以为在有主节点的机器上拔掉电源线,Redis会立即切换,客户端不会注意到这种情况。 在我的实践中,切换发生在几秒钟内。 在此期间,部分数据将不可用:检测到主站不可用,节点投票选出新主站,切换从站,同步数据。 确保该计划有效的最佳方法是进行本地练习。 在笔记本电脑上启动集群,为其提供最小负载,模拟崩溃(例如,通过阻止端口),并评估切换速度。 我认为,只有这样玩了一两天,你才能对技术的运行有信心。 好吧,或者希望互联网上一半人使用的软件可能有效。

布局

通常,配置是您开始使用该工具所需的第一件事。当一切正常时,您甚至不想触及配置。 强迫自己回到设置并仔细检查它们需要花费一些努力。 在我的记忆中,由于配置不注意,我们至少发生过两次严重的故障。 特别注意以下几点:

  • 超时0
    非活动连接关闭之前的时间(以秒为单位)。 0 - 不关闭
    并非我们的每个库都能够正确关闭连接。通过禁用此设置,我们可能会遇到客户端数量限制的风险。另一方面,如果存在这样的问题,那么丢失连接的自动终止会掩盖它,而我们可能不会注意到。此外,在使用持久连接时不应启用此设置。
  • 保存 xy 并附加 是
    保存 RDB 快照。
    我们将在下面详细讨论 RDB/AOF 问题。
  • stop-writes-on-bgsave-error 否 & Slave-serve-stale-data 是
    如果启用,如果 RDB 快照损坏,master 将停止接受更改请求。 如果与主站的连接丢失,从站可以继续响应请求(是)。 或者将停止响应(否)
    我们对 Redis 变成南瓜的情况并不满意。
  • repl-ping-从属周期 5
    这段时间过后,我们会开始担心master已经坏掉了,是时候执行故障转移程序了。
    您必须手动在误报和触发故障转移之间找到平衡。 在我们的实践中,该时间为 5 秒。
  • repl-backlog-size 1024mb & epl-backlog-ttl 0
    我们可以将这么多数据存储在失败副本的缓冲区中。 如果缓冲区用完,您将必须完全同步。
    实践表明,设置一个较高的值更好。副本可能开始滞后的原因有很多。如果它滞后,那么很可能你的主人已经在努力应对,而完全同步将是最后一根稻草。
  • 最大客户数 10000
    一次性客户的最大数量。
    根据我们的经验,最好设置一个更高的值。 Redis 可以很好地处理 10k 连接。 只需确保系统上有足够的套接字即可。
  • 最大内存策略 易失性 ttl
    达到可用内存限制时删除键的规则。
    这里重要的不是规则本身,而是理解这将如何发生。 Redis 值得称赞的是,它在达到内存限制时仍能正常工作。

RDB 和 AOF 问题

虽然Redis本身将所有信息存储在RAM中,但也有一种将数据保存到磁盘的机制。 更准确地说,三种机制:

  • RDB-snapshot - 所有数据的完整快照。使用 SAVE X Y 配置进行设置,并读取“如果至少 Y 个键已更改,则每 X 秒保存所有数据的完整快照”。
  • 仅附加文件 - 按执行顺序排列的操作列表。 每 X 秒或每 Y 操作向文件添加新的传入操作。
  • RDB和AOF是前两者的组合。

所有方法都有其优点和缺点,我不会全部列出,我只会提请注意我认为不明显的点。

首先,保存RDB快照需要调用FORK。 如果数据量很大,这可能会导致所有 Redis 挂起几毫秒到一秒的时间。 此外,系统需要为这样的快照分配内存,这导致需要在逻辑机上保留双倍的 RAM:如果为 Redis 分配 8 GB,则虚拟机上应有 16 GB 可用它。

其次,存在部分同步的问题。 AOF模式下,当slave重新连接时,可以进行全同步,而不是部分同步。为什么会发生这种情况,我无法理解。但值得记住这一点。

这两点已经让我们思考,如果所有内容都已被从属设备复制,我们是否真的需要磁盘上的这些数据。 只有当所有从站都发生故障时,数据才会丢失,这是“DC 火灾”级别的问题。 作为折衷方案,您可以建议仅在从属设备上保存数据,但在这种情况下,您需要确保这些从属设备在灾难恢复期间永远不会成为主设备(为此,在其配置中存在从属设备优先级设置)。 对于我们自己来说,在每种具体情况下,我们都会考虑是否有必要将数据保存到磁盘,大多数情况下答案是“否”。

结论

总而言之,我希望能够让那些根本没有听说过 redis-cluster 的人大致了解它是如何工作的,同时也让那些已经使用过它的人注意一些不明显的点许久。
感谢您抽出宝贵的时间,一如既往,欢迎就该主题发表评论。

来源: habr.com

添加评论