点击此处开始使用 Redis Enterprise。Redis Enterprise 允许您以任何规模、在任何地方处理任何实时数据。
根据我的经验,某些应用程序在分解成更小、松散耦合、自包含的逻辑业务服务块并协同工作时,更易于构建和维护。这些服务中的每一个(又称微服务)都管理着自己的技术栈,该技术栈易于独立于其他服务进行开发和部署。这种架构设计有很多经过充分记录的好处,其他人已经对此进行了详细的阐述。也就是说,这种设计有一个方面我一直非常注意,因为当我没有注意时,会导致一些有趣的挑战。
虽然构建松散耦合的微服务是一个非常轻量级且快速开发的过程,但这些服务之间共享状态、事件和数据的服务间通信模型并不那么简单。我使用过的最简单的通信模型是直接服务间通信。但是,正如 Fernando Dogio 在 文章 中精辟地解释的那样,它在规模上会失败——导致服务崩溃、重试逻辑和负载增加时的严重头痛——应该不惜一切代价避免。其他通信模型从通用发布/订阅到复杂的 Kafka 事件流,但我最近一直在使用 Redis 进行微服务之间的通信。
微服务在网络边界上分配状态。为了跟踪这种状态,事件应该存储在,比如,一个事件存储中。由于这些事件通常是异步写入操作(又称事务日志)的不可变记录流,因此适用以下属性
使用 Redis,我一直很容易实现 发布/订阅 模式。但现在新的 Stream 数据类型可与 Redis 5.0 一起使用,我们可以以更抽象的方式对日志数据结构进行建模——使其成为时间序列数据(如带有至多一次或至少一次传递语义的事务日志)的理想用例。再加上主动-主动功能、简单易用的部署和内存中超快的处理能力,Redis Streams 是大规模管理微服务通信必不可少的工具。
基本模式称为命令查询责任隔离 (CQRS)。它将命令和查询的执行方式分开。在这种情况下,命令通过 HTTP 执行,查询通过 RESP(Redis 序列化协议)执行。
让我们使用一个示例来演示如何使用 Redis 创建事件存储。
我为一个简单但常见的电子商务用例创建了一个应用程序。当创建/删除客户、库存商品或订单时,应该使用 RESP 将事件异步通知 CRM 服务,以管理 OrderShop 与现有客户和潜在客户的互动。与许多常见的应用程序需求一样,CRM 服务可以在运行时启动和停止,而不会影响其他微服务。这需要在 CRM 服务停机期间发送给它的所有消息都必须被捕获以供处理。
下图显示了九个解耦微服务的互连性,它们使用使用 Redis Streams 构建的事件存储进行服务间通信。它们通过监听事件存储中特定事件流上新创建的任何事件来做到这一点,即 Redis 实例。
图 1:OrderShop 架构
我们 OrderShop 应用程序的域模型包含以下五个实体
通过监听域事件并将实体缓存更新到最新状态,事件存储的聚合函数只需要调用一次或在回复时调用。
图 2:OrderShop 域模型
要自己试用
以下是 client.py 中的一些示例测试用例,以及相应的 Redis 数据类型和键。
测试用例 | 描述 | 类型 | 键 |
test_1_create_customers | 创建 10 个随机客户 | 集合
流 散列 | customer_ids
events:customer_created customer_entity:customer_id |
test_2_create_products | 创建 10 个随机产品名称 | 集合
流 散列 | product_ids
events:product_created product_entity:product_id |
test_3_create_inventory | 为所有产品创建 100 个库存 | 集合
流 散列 | inventory_ids
events:inventory_created inventory_entity:inventory_id |
test_4_create_orders | 为所有客户创建 10 个订单 | 集合
流 散列 | order_ids
events:order_created order_product_ids:<> |
test_5_update_second_order | 更新第二个订单 | 流 | events:order_updated |
test_6_delete_third_order | 删除第三个订单 | 流 | events:order_deleted |
test_7_delete_third_customer | 删除第三个客户 | 流 | events:customer_deleted |
test_8_perform_billing | 执行第一个订单的结算 | 集合
流 散列 | billing_ids
events:billing_created billing_entity:billing_id |
test_9_get_unbilled_orders | 获取未结算的订单 | 集合
散列 | billing_ids, order_ids
billing_entity:billing_id, order_entity:order_id |
我选择 Streams 数据类型来保存这些事件,因为它们背后的抽象数据类型是事务日志,这完美地符合我们对连续事件流的用例。我选择不同的键来分配分区,并决定为每个流生成自己的条目 ID,它由以秒为单位的时间戳“-”微秒组成(以确保唯一性并保留跨键/分区的事件顺序)。
127.0.0.1:6379> XINFO STREAM events:order_created
1) “length”
2) (integer) 10
3) “radix-tree-keys”
4) (integer) 1
5) “radix-tree-nodes”
6) (integer) 2
7) “groups”
8) (integer) 0
9) “last-generated-id”
10) “1548699679211-658”
11) “first-entry”
12) 1) “1548699678802-91”
2) 1) “event_id”
2) “fdd528d9-d469-42c1-be95-8ce2b2edbd63”
3) “entity”
4) “{\”id\”: \”b7663295-b973-42dc-b7bf-8e488e829d10\”, \”product_ids\”:
[\”7380449c-d4ed-41b8-9b6d-73805b944939\”, \”d3c32e76-c175-4037-ade3-ec6b76c8045d\”,
\”7380449c-d4ed-41b8-9b6d-73805b944939\”, \”93be6597-19d2-464e-882a-e4920154ba0e\”,
\”2093893d-53e9-4d97-bbf8-8a943ba5afde\”, \”7380449c-d4ed-41b8-9b6d-73805b944939\”],
\”customer_id\”: \”63a95f27-42c5-4aa8-9e40-1b59b0626756\”}”
13) “last-entry”
14) 1) “1548699679211-658”
2) 1) “event_id”
2) “164f9f4e-bfd7-4aaf-8717-70fc0c7b3647”
3) “entity”
4) “{\”id\”: \”1ea7f394-e9e9-4b02-8c29-547f8bcd2dde\”, \”product_ids\”:
[\”2093893d-53e9-4d97-bbf8-8a943ba5afde\”], \”customer_id\”:
\”8e8471c7-2f48-4e45-87ac-3c840cb63e60\”}”
我选择 Sets 来存储 ID(UUID)以及 Lists 和 Hashes 来对数据进行建模,因为它反映了它们的结构,并且实体缓存只是域模型的简单投影。
127.0.0.1:6379> TYPE customer_ids
set
127.0.0.1:6379> SMEMBERS customer_ids
1) “3b1c09fa-2feb-4c73-9e85-06131ec2548f”
2) “47c33e78-5e50-4f0f-8048-dd33efff777e”
3) “8bedc5f3-98f0-4623-8aba-4a477c1dd1d2”
4) “5f12bda4-be4d-48d4-bc42-e9d9d37881ed”
5) “aceb5838-e21b-4cc3-b59c-aefae5389335”
6) “63a95f27-42c5-4aa8-9e40-1b59b0626756”
7) “8e8471c7-2f48-4e45-87ac-3c840cb63e60”
8) “fe897703-826b-49ba-b000-27ba5da20505”
9) “67ded96e-a4b4-404e-ace6-3b8f4dea4038”
127.0.0.1:6379> TYPE customer_entity:67ded96e-a4b4-404e-ace6-3b8f4dea4038
hash
127.0.0.1:6379> HVALS customer_entity:67ded96e-a4b4-404e-ace6-3b8f4dea4038
1) “67ded96e-a4b4-404e-ace6-3b8f4dea4038”
2) “Ximnezmdmb”
3) “ximnezmdmb@server.com”
Redis 提供的各种数据结构——包括 Sets、Sorted Sets、Hashes、Lists、Strings、Bit Arrays、HyperLogLogs、Geospatial Indexes 和现在的 Streams——可以轻松地适应任何数据模型。Streams 具有不止一个字符串的元素,而是由字段和值组成的对象。范围查询速度很快,并且流中的每个条目都有一个 ID,它是一个逻辑偏移量。Streams 为时间序列等用例提供了解决方案,以及为其他用例(如替换需要比即发即忘更可靠的通用发布/订阅应用程序以及完全新的用例)提供流式消息。
由于您可以通过分片(通过将多个实例集群)扩展 Redis 实例并提供用于灾难恢复的持久性选项,因此 Redis 是一个企业级选择。
如有任何问题或反馈意见,请随时与我联系。
再见