我们来谈谈通信工具和模式。随着 Redis 推出 Streams,在 Redis Pub/Sub 和其他工具(如 Kafka 和 RabbitMQ)之外,我们现在有了另一种通信模式可供选择。在本文中,我将指导您了解各种通信模式的定义特征,并简要介绍用于实现每种模式的最常用工具。最后,我会做一个小结,希望能帮助你更好更快的找到解决方案。
同步通信
在这种情况下,同步意味着所有各方需要同时处于活动状态才能进行通信。最简单的形式是服务 A 和服务 B 通过从服务 A 调用服务 B 的 HTTP REST 端点来执行直接远程过程调用(RPC)。如果服务 B 脱机,则服务 A 将无法与 B 通信,因此 A 需要实施内部故障恢复过程,大多数情况下该过程意味着进行适度降级。例如,如果推荐服务无法访问,Netflix 的“观看下一个”部分可能会显示随机的节目样本。
这些服务只会优雅地降级,对于更敏感的场景(例如,要求订单服务开始处理付费订单的支付服务),下面将要介绍的异步机制更适用。虽然 RPC 范例适用于一对一通信,但您偶尔需要支持一对多或多对多。此时,您有两个主要选项:无代理或代理工具。
无代理(Brokerless)
无代理意味着参与者仍然直接连接,但可以选择使用与 RPC 不同的模式。在这个类别中,我们有 ZeroMQ 等库和更新的 nanoMsg。它们被正确地描述为“打了鸡血的 TCP 套接字。”实际上,您将库导入到代码中并使用它来实例化可以使用各种内置消息路由机制的连接,例如 Pub / Sub,Push / Pull,Dealer/Router 等。
有代理(Brokered)
Brokered 意味着参与者连接到相同的服务,顾名思义,该服务作为实现整个消息路由机制的中央代理。虽然这种体系结构通常被描述为星形,而代理是星形的中心,但代理本身可以(通常是)集群系统。
在这个类别中,据我所知,RedisPub / Sub 是独立的。您仍然可以使用具有 NATS 或 RabbitMQ 等持久性的工具来处理此场景,因为它们允许您关闭持久性,但我所知道的唯一纯同步消息传递代理是 Redis。不同之处不仅仅在于持久性,而在于可靠交付(即应用程序级别)的概念与“即发即忘”。RabbitMQ 默认使用以前的行为,而 Redis Pub / Sub 则专注于为即发消失做最少量的工作。你可以想象,这对性能有影响(毕竟没有免费的午餐),但可靠的交付确实适用于更广泛的用例。
由于 Streams 在 Redis 版本 5 之前不可用,因此有些人选择在他们更喜欢更好的交付保证的情况下使用 Pub / Sub,并且现在正在进行切换。因此,如果您正在构建新应用程序或对当前使用 Pub / Sub 的应用程序不满意,如果您需要的是“Pub / Sub,但能够在断开连接时恢复而不会丢失消息”,请考虑 Redis Streams。
优点和缺点
无中断工具是您能想到的最快的通信方法,甚至比 Redis Pub / Sub 更快。遗憾的是,它们无法抽象出所有复杂性 - 例如每个参与者需要知道所有其他参与者的位置以便连接到它们,或者您通常不需要在代理系统中处理的复杂故障情景(例如,参与者中途退出的情况)。
在这种情况下,使用 Redis Pub / Sub 的好处在于不必放弃过多的吞吐量,并获得具有小集成面的简单和无处不在的基础设施。您只需要一个 Redis 客户端用于您的语言,并且可以使用 PUBLISH 和 (P)SUBSCRIBE 来移动消息。
异步通信
当然,异步意味着即使并非所有参与者同时出现,仍然可以进行通信。要启用此模式,必须保留消息,否则无法保证在发生故障时进行传递。此类别中的工具主要包括基于队列或基于流的解决方案。
基于队列的异步通信
这是进行异步通信的“传统”方式,也是大多数面向服务架构(SOA)的基础。这个想法是,当一个服务需要与另一个服务进行通信时,它会在中央系统中留下一条消息,以便其他服务稍后再接收。实际上,这些消息收件箱就像任务队列。
对这些系统的另一个期望是任务彼此独立。这意味着它们可以(并且几乎总是)由多个相同的消费者(通常称为工人)并行处理。此属性还可以启用独立故障,这是许多工作负载的一个很好的功能。例如,无法处理来自一个用户的付款(可能是因为缺少个人资料信息或其他琐碎的问题)不会停止所有用户的整个支付处理流程。
这个类别中最有名的工具是 RabbitMQ,其次是大量其他工具和云服务,主要是 AMQP(Rabbit 的本机协议)或 MQTT(类似的开放标准)。通常的做法是通过框架使用 RabbitMQ,这种框架提供了一种简单的方法来实现各种重试策略(例如,指数退避和死信)以及一个加糖的接口,使得在特定客户端生态系统中处理消息更加惯用。其中一些框架是“简陋”的任务队列,如 Sidekiq(Ruby),Celery(Python),Dramatiq(Python)等。其他是“更重型”的企业服务总线(ESB),如 NServiceBus(C#),MassTransit( C#),Apache Synapse(Java)或 Mulesoft(Java)。
此模式的更简单版本(任务队列)也可以直接使用 Redis 列表实现。Redis 具有阻塞和原子操作,使构建定制解决方案变得非常容易。特别值得一提的是 Kue,它在 JavaScript 的任务队列的漂亮实现中使用了 Redis。
基于流的异步通信
首先,值得注意的是,使用流的最简单方法就是一种存储形式。Streams 是一个不可变的,仅附加的系列时间导向条目,许多类型的数据自然地适合该格式。例如,传感器读数或日志包含的值本质上是按创建时间索引的,并且仅附加(您不能更改过去)。它们也具有相当规则的结构(因为它们倾向于保持相同的字段集),流可以利用的属性可以提高空间效率。这种类型的数据非常适合流,因为访问数据的最直接方式是检索给定的时间范围,这些流可以以非常有效的方式进行。
回到我们的通信场景,所有流实现还允许客户端尾随流,在添加新条目时接收实时更新。这有时称为观察或订阅流。使用 Streams 作为通信工具的最简单方法是推送到您将通过 Pub / Sub 发布的流,基本上创建一个可恢复的 Pub / Sub。每个订阅者只需记住它处理的最后一个条目 ID,因此如果发生崩溃或断开连接,它可以轻松恢复。
通过流实现服务到服务通信也是可能的 - 有时是优选的 - 进入流体系结构领域。这里的主要概念是我们之前描述的任务/消息,现在将成为一个事件。使用基于队列的设计,任务会被另一个希望它执行某项操作的服务推送到服务队列,但在流式架构中,反过来会发生:每个服务都会将状态更新推送到自己的流中,而这反过来又由其他服务。
这种设计变化有许多微妙的含义。例如,您可以稍后添加新服务,并让它们浏览整个流历史记录。在队列中,这是不可能的,因为任务一旦完成就被删除,并且通常在这些系统中表达通信的方式现在允许这样做(认为命令性与功能性)。
流具有双重性质:数据结构和通信模式。一些数据自然地适合于此(例如,日志),并且服务之间的通信不一定必须基于任务队列。完全接受这种双重性质的做法称为事件采购。
通过事件源,您可以将业务模型定义为无穷无尽的事件流,并让业务逻辑和其他服务对其做出反应。这种翻译并不总是那么容易,但是当处理诸如“Y 时刻物体 X 的状态是什么?”这样的难题时,这些好处可能会很大,否则如果没有适当的审核,这将很难回答或完全不可能日志记录。
也许您的普通移动应用程序不需要事件采购,但对于必须处理客户个人数据,运输和其他“混乱”域的企业软件,它可能会有很大帮助。
要实现这些类型的模式,您可以使用大量工具。当然,我们应该从房间里的大象开始:Apache Kafka,以及像 Apache Pulsar(来自雅虎)的替代品,以及其他语言的 Kafka 的重新实现,以及一些 SaaS 产品。最后,还有一个新人:Redis Streams。
Apache Kafka 与 Redis Streams
首先,请注意 Redis 称之为“流”,Kafka 称之为“主题分区”,而在 Kafka 中,流是一个完全不同的概念,围绕处理 Kafka 主题的内容。
也就是说,就表现力而言,两个系统都是等效的:您可以在不对数据建模方式进行任何实质性更改的情况下实现相同的应用程序。一旦你深入了解实际的细节,差异就会开始,而且它们很多而且很重要。
Kafka 已经存在了很长时间,人们已经成功构建了可靠的流媒体架构,它是真正的单一来源。但是,如果您愿意尝试新技术,在开发和运营中简化价值,并且需要亚毫秒级延迟,那么 Redis Streams 可以填补您的架构中非常相似的位置。
我说简单是因为真的简单。如果您从未尝试过 Redis Streams,即使您计划在生产环境使用 Kafka,我建议您尝试使用 Redis Streams 对您的应用程序进行原型设计,因为它需要几分钟才能在您的笔记本电脑上启动和运行。
每种模式的场景
让我们考虑几个例子,看看每种模式最能解决哪些问题。
Brokerless 同步通信
如果你试图让一些客户端设备(例如,电话,Arduino)在局域网中相互通话或者在计算机上运行的程序,那么到工作解决方案的最短路径可能就是“打了鸡血的 TCP 连接 “。
Brokered 同步通信
IRC 风格的聊天应用程序(即没有历史记录),或用于易失性日志/事件的即插即用实时处理管道,与代理方法配合良好。Benjamin Sergeant 谈到了旧金山 RedisConf19 的最后一个用例 (幻灯片)。
基于队列的异步通信
Web 爬虫通常依赖于此模式,以及许多 Web 服务,其操作无法立即完成以响应请求。例如,考虑一下 YouTube 中的视频编码。
基于流的异步通信
除了 Slack 风格的聊天应用程序(即历史记录)之外,此技术最适用于日志处理,物联网(IoT)设备和微服务。
Redis 对抗全世界
在结束之前,我想留下最后一个思考。Redis 真正的超强大功能在于它不仅仅是 Pub / Sub 消息系统,队列,也不是流服务。它也不仅仅是一个通用数据库。实际上,只要有足够的毅力,你就可以在关系型数据库之上实现上面描述的每一种模式,但是有一些现实的原因可以解释为什么这会是一个坏主意。
Redis 提供真正的 Pub/ Sub 即发即弃系统,以及真正的 Stream 数据类型。此外,使用 Redis 模块,Redis 还支持许多不同数据类型的实际实现。我们可以通过持久化和非持久化的聊天应用程序示例来验证这些观点。
IRC 风格的聊天应用程序
通过之前的介绍,我们可以看出 Pub / Sub 将是正确的选择,因为这种类型的聊天应用程序只需要向连接的客户端发送消息。但是,要实现此应用程序的简单版本,您仍需要考虑每个通道的全局通道列表和用户状态列表。将状态存储在何处?如何使它保持最新,特别是当服务实例意外死亡时?在 Redis 中,答案很简单:排序集,过期键和原子操作。如果您使用 RabbitMQ,则需要 DBMS。
Slack 风格的聊天应用程序
对话可以非常自然地表达为消息流。我们甚至不需要将事件源加入到混合中,因为它已经是此数据类型的本机结构。那么为什么 Redis 会在这个例子中超过 Kafka 呢?和以前一样,您的聊天系统不仅仅是一个消息流。有频道和其他使用其他方式更好表达的状态。还有“用户 X 正在输入…”功能:该信息不稳定,您希望将其发送给所有参与者,但仅限于他们已连接时。如果您使用的是 Kafka,则无论如何都需要启动 Pub / Sub 系统。
小结
在分布式系统中,当您需要协调时,通常需要共享状态,反之亦然。忽视这一事实往往会导致过于复杂的解决方案。Redis 非常了解这一点,这也是其独特设计背后的原因之一。如果你接受这个原则,你会发现在给定正确的原语的情况下,偶尔可以用少量命令解决难题,这正是 Redis 给你的。例如,这是您可以在事务上将条目附加到流,将任务推送到队列(的开头)以及发布到 Pub / Sub 的方式:
我希望这能让您了解分布式系统常用的主要通信模式。下次需要将两个服务连接在一起时,这应该可以帮助您导航选项。
Redis Streams 数据类型是 Redis 的一个很棒的功能,它将成为许多应用程序的构建块,特别是现在 Redis 拥有一个模块池,可以为时间序列,图形和搜索添加新的全功能。要了解有关 Redis Streams 的更多信息,请查看 Antirez 的这篇介绍性博客文章以及官方文档。但是不要忘记,流不是每个工作的正确工具:有时你需要 Pub /Sub,或者简单地在 Redis 列表上使用简单的阻塞操作(或者排序集,Redis 也有这个)。
本文转载自公众号中间件小哥(ID:huawei_kevin)。
原文链接:
https://mp.weixin.qq.com/s/bUx6lcknmWK2vjFjVSNGUA
评论