- 原文地址: Building a Reactive Architecture Around Redis
- 原文作者: Fernando Doglio
- 译文出自: 掘金翻译计划
- 本文永久链接: github.com/xitu/gold-m…
- 译者: YueYongDev
- 校对者: Usualminds、 jaredliw
Redis 是我遇到过的最强大、最通用的技术之一。遗憾的是,大多数人都只是将其作为一个优秀的缓存解决方案来使用。
为此,我们需要去改变这个现状。
我特别想通过本文告诉你,如何构建一个以 Redis 为核心的响应式架构。尤其是当你因为一些其它的需求(比如高性能的缓存)已经将 Redis 作为你整个应用基础设施的一部分时,这会是一个巨大的优势。
我在本文所描述的内容,你可以按照自己的想法采取各种手段来实现,说实话,在这一点上任何选择都是有效的。出于个人观点,我更倾向于使用 Node.js,但这也只是我自己的想法,你可以选择最适合你的方案。
首先要了解的问题是什么是响应式架构,以及为什么我们要构建一个响应式架构而不是采用其他更传统的方案?
简单来说,一个响应式架构就是让每一个逻辑都在满足所有预设条件的情况下被执行 —— 我想我应该给 “简单” 这个词加一个引号。
换个其他的说法:为了让你的逻辑在某个特定事件发生后被触发,通常会有两种实现方案:
定期检查某种标志,直到它被打开,这意味着事件发生。
停下来等待,直到某个东西通知你的服务,事件被触发。
第二个是面向对象编程中观察者模式的关键。被观察的对象让所有订阅其内部状态的人知道它更新了。
我们在这里要做的是,将这种来源于面向对象(OOP)的设计模式推导到架构级的设计中。因此,这里我所谈及的不是程序内的一些逻辑,而是架构级别的,一旦触发响应条件,就运行某项服务。
这是分配和扩展平台最有效的方式,原因在于:
你不必浪费时间和流量去轮询一个特定数据源的标志(或任何你觉得应该轮询的东西)。此外,如果你使用的基础设施是按流量付费的,不必要的轮询可能会产生额外的费用,在目标服务上增加不需要的工作,如果在你的代码等待轮询的时间里发生了多个事件,你最终可能还需要聚合这些事件。
你可以通过增加新的服务,并行工作,并以尽可能快的速度捕捉事件,来增强服务的处理能力。
平台更加稳定。通过响应式工作,你可以确保你的服务以最佳速度运行,而不必担心由于客户的数据过载而崩溃。
响应式架构本质上是异步的,所以任何试图与之交流的客户端应用,也需要适应相同的响应范式。你可以通过 HTTP 得到一个来自外部的 REST API,但是你得到的响应结果可能并不是你想要的答案。例如,你可能会得到一个 ”200 OK“ 的响应,意味着你的请求已经收到。为了让你的应用程序得到实际的结果,它必须订阅包含这种响应的特定事件。
请记住这一点,否则,你可能会花很长时间来调试为什么没有得到你想要的响应结果。
既然如此,我们需要什么来使我们的平台/架构成为一个响应式的平台/架构呢?可以肯定的是,这不是 ReactJS。我们需要一个消息代理,一个能在多个服务之间集中分配消息的东西。
对于可以充当代理的东西,我们需要确保我们的代码知道它在哪里,以及他所需要的事件类型,以此来确保订阅到某些事件。
在此之后,一个通知将被发送到我们的服务,同时触发我们的业务逻辑。
听起来是不是很容易?那是因为它本就如此!
Redis 不仅仅是一个存储在内存上的键值对存储引擎,事实上,它有三个我喜欢的特性,也正因如此,我才愿意使用 Redis 来搭建基于不同预期行为的响应式架构。
这三个特点分别是:
tail -f
命令在你的终端上的行为。如果你从来没有见过这个命令,说明这是一个*nix 命令,它向你显示一个文件的最后一行,并保持监听该文件的变化,每新增一行时,终端会立即显示。Redis 流也是同样的道理。如果使用得当,那么将会是一个强有力的工具。 你可以阅读此处了解更多。所有这些特性都使得你可以以各种方式与你的业务逻辑进行适配,根据你所期望的行为类型,解决其中的一个或全部。
让我们快速看一些例子,以便知道该怎么使用以及在什么时候使用。
最简单的例子是,每个微服务都在等待发生什么事情。要触发的事件,该事件可能来自外部,即系统的用户或客户端。
如上图所示,可以把中央的红色管看作是 Redis 的发布/订阅流程或阻塞队列,这是一个更可靠的发布/订阅模式的自定义实现。
整个过程从步骤 1 开始,由 Client App
提交请求,到步骤 9 由 Client App
得到响应通知结束。其余的呢?我不关心,客户端 App 也不需要关心。
这种模式的好处之一就是使得架构对客户端来说成为一个黑盒。一个请求可以触发数百个事件,也可能只触发一个,但是行为都是一样的:一旦准备好响应,它就会被传递给客户端。而不是让客户端知道需要多长时间或者需要多久检查一次是否准备好。这些在这里都不重要。
记住以下几点:
接下来让我们来看看,如果你的事件是基于时间触发的,会发生什么。
响应式架构的另一个常见行为是,能够在预定义的时间过后触发某些事件。例如:在发现数据问题 10 分钟后触发警报。或等待 30 分钟后触发物联网设备停止发送数据的警报。
这些行为通常与现实世界的限制有关,需要一些时间来解决,或者甚至可以通过“等待一点时间”并重新启动倒计时来解决(就像物联网设备的连接不可靠)。
对于这种情况,整体架构保持不变,唯一的区别是中央通信枢纽必须使用 来自 Redis 的键空间通知。
你看,这里就有两个关于 Redis 的主要特点,你需要了解一下:
考虑到这两点,你可以创建订阅这些特定键的服务,并在它们被删除后做出反应(即事件被触发时)。同时,生产者不断地更新键,这也重置了 TTL 计时器。因此,如果你要追踪一个设备最后一次发送心跳的时间,你可以像我上面展示的那样,为每个设备准备一个密钥,并且在每次收到新的心跳时不断更新这个密钥。一旦 TTL 过了,就意味着你在配置的时间内没有收到新的心跳。你的订阅进程将只收到密钥名称,所以如果你只需要设备的 ID,你可以像我展示的那样构造你的密钥,并解析名称以捕获所需的信息。
另一方面,如果你在该键中保存了一个复杂的结构(如果你需要这么做的话),你将不得不改变这种方法。这是因为当 TTL 过期时,键就会被删除,里面的数据也会被删除,所以你无法获取到它。这时,你可以使用一种叫做“影子键”的技术来代替。
影子键,本质上是一个用来触发事件的键,但它实际上是对包含你所需数据的实际密钥的影子。所以回到我们的例子,考虑生产者每次收到心跳时都会更新 2 个键:
在这两个 key 中,只有第一个有 TTL,第二个没有。因此,当你收到过期通知时,你将从过期的 key 中获取 ID(last_connection_time_of_device100002),并使用它来读取第二个 key 的内容。然后,如果有必要的话,你可以删除这个密钥,或者把它留在那里,这取决于你的使用情况。
这里唯一需要考虑的是,如果你把 Redis 配置为集群模式,键空间的通知不会在整个集群中广播。这意味着,你必须确保你的消费者连接到每个节点。通知如果没有人接收就会丢失,这也是这项技术的唯一缺点,但是在你花费数天时间去调试你的异步逻辑前,了解这一点对你来说会有很大帮助(我是过来人)。
正如你所看到的,这两种情况下的复杂性都被降低了 —— 我们只需要确保已经订阅了正确的事件或频道。如果你试图用一个普通的 SQL 数据库来做这件事,你必须通过代码实现非常复杂的逻辑,以便有效地确定何时有新的数据进入,或何时有一条信息被删除。
相反,刚才所提的所有复杂性都被 Redis 抽象化了,你唯一需要担心的,就是编写好你的业务逻辑。对我来说,这就是最重要的事情。
你是否将 Redis 用于缓存之外的其他场景?在评论区与其他人分享你的经验,我很想知道你们是如何使用 Redis 的。