Service Discovery(服务发现)
接下来,让我们考虑服务发现,这是任何微服务架构的另一个必备功能。
通过名称发现服务的 IP
和 PORT
位置的能力极大地简化了通信。其他好处包括不必管理 DNS
条目或创建固定的路由规则。
服务发现信息以一种 “nodes”
的形式存储在 Redis Hash 中。使用 Hash 可以实现快速的查找。我们使用 Redis 的“hget”
,“hset”
和 “hgetall”
命令来处理 nodes hash。
以下 Redis 操作可用于实现服务发现。首先是对特定服务类型的查找。第二个是查找可用实例。第三次查找,允许Hydra检索有关特定服务实例的信息。
我们可以看到有用的信息,例如服务的版本,instanceID,IP地址和端口,最后还有主机名。在此示例中,主机名也恰好是Docker 容器 ID
。
我们可以使用 Redis “hgetall”
命令检索有关所有可用实例的信息。这就是 Hydra Router
如何检索要显示在其仪表板上的服务列表。
让我们回顾一下。Hydra 使用 servicename key 段进行查询,以发现有关服务的各种信息。可以使用 Redis Hash 管理服务详细信息,该服务可提供快速的服务发现
接下来,让我们考虑路由。
Routes(路由)
同时路由 HTTP 和消息(例如 Web Socket 或 PubSub )- 要求对 routes
进行验证。微服务可以发布其 routes
到 Redis。举个例子,HydraRouter 使用发布的 routes
来实现动态的服务感知路由。
每个服务以 “service:routes” 类型的 key 发布其路由。在这里,我们看到 “asset-svcs” 路由的 key
服务路由存储在 Set
结构中。非常适合,因为您不想重复输入路由条目。使用 SADD
和 SMEMBERS
命令。
回到我们的 routes 上。我们可以使用 key
模式拉出路由列表。在这里我们可以看到许多服务的路由。
我们可以使用 “smembers” 命令查看特定路由集的内容。顺便说一下,括号中的 [get]
、[post]
和 [put]
位表示 HTTP REST
端点。对于其他消息传递传输,可以省略括号方法的使用。
让我们回顾一下。每个服务都会向一个 Redis Set
发布它的路由。访问一个单独的路由会显示该服务的路由条目集合。
路由使用 Set 数据结构存储在 Redis 中,这避免了重复的路由。发布的路由可用于实现动态的服务感知路由。接下来,让我们考虑负载平衡。
Load Balancing(负载平衡)
随着应用程序的增长,您将需要在可用的服务实例之间平衡请求。这是通过使用我们看到的服务呈现(service presence
)和路由(routing
)功能完成的。在应用程序级别,使用 Hydra,这与使用 “makeAPIRequest”
或 “sendMessage”
调用一样简单。当 Hydra 使用路由和 presence 信息在可用的目标实例中进行选择时,就会在这些调用中进行负载平衡。
一个很好的好处是,在路由过程中,如果某个请求在某个特定实例上失败,Hydra 可以在出现 HTTP 503
服务器不可用错误之前重试其他可用实例。
如您所见,负载平衡依赖于其他功能,例如 presence,服务发现和路由。
回顾一下,可以使用我们已经看到的 Presence、服务发现(Service Discovery)和路由(Routing)特性来完成服务之间的负载平衡请求。Redis Strings,Hashes 和 Sets 使这成为可能。整体大于部分之和。
Messaging(消息)
分布式服务强制通过底层网络彼此通信。HTTP Rest 调用可能是最常见的,但是 socket 消息传递可能更有效。Hydra 中的消息传递是通过 Redis 的 Pub/Sub
通道完成的,而 Redis 通过 socket 连接实现了 Pub/Sub
。
这里有一个例子。Hydra 使用 Redis 的 “subscribe”、“unsubscribe” 和 “publish” 命令。
顺便说一句,Hydra router 能够通过 HTTP 和 WebSocket 接受消息并将其转换为 pub/sub
消息。
要了解其工作原理,请考虑两个服务,即 “asset-svcs”
和 “project-svcs”
。每个服务创建两个 key,一个使用服务名(service name
),另一个使用服务名(service name
)和实例ID(instance ID
)。每个服务都监听两个 channel
。
在大多数情况下,您并不关心哪个服务实例处理请求。在这些情况下,将使用没有特定实例ID的通道。
现在,当您需要向特定实例发送消息时,可以使用具有实例ID的通道。需要特别注意的是,hydra 在负载均衡时会将请求转换为具有特定实例ID的服务名称。这样可以确保只有一个实例可以处理给定的消息或请求。
我们可以使用 Redis pub/sub channels
命令查看 channel key 列表。注意这里有四个 key。第一个 key 是 “asset-svcs”
的名称 —— 由 asset service 的所有实例共享。接下来,我们将看到三个具有惟一实例id的附加 key。三个服务实例各有一个。
继续关注消息传递。为了确保微服务之间的互操作性,必须标准化共享的通信格式。通用消息格式是已记录的基于JSON的格式,其中包括对消息传递,路由和排队的支持。这些消息作为JSON字符串文本存储在Redis中。
继续关注消息传递。为了确保微服务之间的互操作性,必须对共享的通信格式进行标准化。通用消息格式是一种文档化的 JSON-based 的格式,包括对消息传递(messaging
)、路由(routing
)和队列(queuing
)的支持。这些消息作为 JSON 字符串文本存储在 Redis 中。
下面是一个示例 UMF 消息。
“to”
,“frm”
和 “bdy”
字段是必填字段,服务可以自由地在 “body”
对象中包含自己的自定义字段。
让我们看看如何在实践中使用它。
在左边,“client-svcs”
向 “project-svcs”
发送消息。注意,这只需要一个 UMF 创建调用和一个发送消息调用,这里用黄色显示。
在右边 —— “project-svcs”
侦听消息并根据需要进行处理。这是使用事件消息侦听器完成的。
请注意,Hydra
抽象了服务发现(service discovery)、负载平衡(load balancing)、路由和发布/订阅(pub/sub)等细节。发送和接收消息只涉及三个成员函数。在这里稍作停留是值得的。花几秒钟考虑一下使用您最喜欢的堆栈时这个示例会是什么样子。
让我们仔细看看。Send message
通过解析消息中的 “to”
字段来确定目标服务名称。有了服务名,下一步是检查可用的实例。有了目标实例,消息就会被字符串序列化,并通过 Redis 的 “publish”
命令发送。
同样,我们可以列出Redis中的所有发布/订阅通道(Pub/Sub Channnel
)。消息可以通过这些通道发送,并由侦听器(listeners
)检索。因此,只需编写一些编程代码,我们就可以使用 Redis 通过组织良好的通道集合(collection of channels
)来路由消息。
总而言之,值得注意的是,由于服务是物理分布的,因此最终需要进行消息传递。Redis 使用其发布/订阅(pub/sub
)功能启用消息传递。
标准化通信可以实现服务之间的互操作性。我们还看到,当我们抽象出底层服务发现(service discover
)、负载平衡(load balancing
)、 路由(routing
)和发布/订阅(pub/sub
)细节时, 在应用程序级别上的通信是多么容易。
接下来,让我们考虑消息队列。
Queuing(队列)
作业(Job
)和消息队列(message queues
)是许多重要应用程序的另一个重要部分。Hydra 使用 Redis 为每种服务类型维护动态队列(dynamic queues
)。
然后,服务实例可以读取其队列和处理项目。
队列消息的内容是UMF消息,遵循用于消息传递的相同格式。同样,互操作性为王!
Hydra 会为每种服务类型自动创建三个队列。
- “已接收(
received
)”队列 - “进行中(
inprogress
)”队列 - 还有一个“不完整(
incomplete
)”队列。
因为这些是列表,我们使用 Redis 的 “lpush”
、“rpush”
、“rpoplpush”
和 “lrem”
命令。
下面的图表显示了队列之间的消息流。在 Redis 中,在队列之间移动项目是一个原子操作。所以不管你有多少微服务,它都是安全的。
在下一个左边的示例中, 对消息进行排队就像创建一个 UMF 消息并调用 “queueMessage”
来发送它一样简单。右下角的代码显示了图像处理服务通过调用 “getQueuedMessage”
, 然后在处理完消息后调用 “markQueueMessage”
,使消息脱离队列。很简单吧?
因此,回顾一下,有时无法期望立即做出回应。在这种情况下,我们只需要排队等待后续处理。 Redis List
数据结构可以用作消息队列。使用原子操作的 “lpush”
和 “rpoplpush”
之类的命令使此操作可行。再次在这里,我们看到了使用高级抽象进行基本排队有多么容易。
Logging(日志记录)
分布式日志记录(Distributed logging
)是任何微服务体系结构的另一个重要特征。但是,如果您知道 Redis
,可能会对将其用作分布式记录器(distributed logger
)感到震惊。您可能会理所当然地担心。但是,您可以将其用作飞行记录器(flight recorder
)。仅存储最严重的错误,并使用 “lpush”
和 “ltrim”
限制条目的数量。然后,至少您将有一种快速的方法来检查微服务可能出了什么问题。
这是 key
的样子。注意,key
类型为 health:log
。
在这里,我们可以看到 health:log
key 类型实际上是一个 “List”
数据结构。所以我们可以使用 Redis 的 “lrange”
命令来查看 “imageproc-svcs”
的飞行记录器(flight recorder)日志。
总结:使用微服务无法登录数十台或更糟的数百台计算机。分布式日志记录绝对是必经之路。使用 Redis
,您可以构建一个轻量级的记录器(light-weight logger
)以用作飞行记录器(flight recorder
)。使用 Redis List
数据结构以及方便的 “lpush”
和 “ltrim”
命令可以实现此目的。
最后,让我们考虑配置管理。
Configuration management(配置管理)
管理分布式微服务的配置文件可能具有挑战性。然而,你甚至可以使用 Redis 来存储你服务的配置文件。但这并不理想,得远离,核心缺点是在 Redis 中存储配置会使 Redis 有状态。但这是可以做的。
让我们看看它是如何工作的。configs key 类型是一个 hash。该 hash 的 key 由服务版本和设置为该版本配置数据的值组成。
下面是一个配置示例。在我们的示例中,我们使用名为 “hydra-cli”
的命令行工具, 它允许我们将配置文件推到特定的服务版本。所做的一切就是创建一个 hash 条目,其键由服务名称和版本组成, 并将文件内容字符串序列化后(stringified
)作为其值。记住,你也可以使用 shell 脚本来驱动 redis cli。
我们可以使用 “hget”
命令和配置的版本提取一个特定的版本。
让我们快速回顾一下,我们了解了 Redis
如何用于存储应用程序配置文件。 Redis Hash
数据结构允许我们存储每种服务类型的配置。每个配置条目均由服务版本标签索引,并且内容仅指向字符串化的 JSON 配置。
总结
这里分享的是一种大量地使用 JavaScript 和 NodeJS 来利用 Redis 构建分布式应用程序的工程方案。但是,你完全可以用其他你爱的语言(如:Golang)对 Redis 做同样的事情。