【51CTO.com快译】Hydra是一个轻量级NodeJS库,用于构建微服务之类的分布式计算应用程序。我们对轻量级的定义是:外部复杂性低、基础设施依赖项少。Hydra之所以基础设施依赖项少,是由于唯一的外部依赖项是Redis。
要是你读到本文,却从未听说过Redis(这种可能性极小),不妨先了解一下redis.io。稍后再回到此文。
Hydra使用Redis丰富的数据结构实现重要的微服务所需要的功能,比如存在、服务发现、负载均衡、消息传送和队列等功能。
Hydra也很轻巧。实际上,轻巧得足以在售价5美元的Raspberry Pi Zero上运行。想了解更多信息,请参阅:https://medium.com/flywheel-tech/embracing-microservices-11750470ba31。
除了轻巧外,Hydra还是构建微服务的最简单方法之一。虽然本文不会介绍Hydra,外面已有好多介绍它的资源,但会深入介绍Hydra如何利用Redis构建轻量级微服务。
先声明一下。首先,本文介绍的方法可能不是很适合你的项目。你可能需要较笨重的微服务。那没关系。我介绍的方法已在Flywheel Sports及另外众多公司得到了证实。我之前写过一篇文章,详细叙述了为什么我们构建Hydra、它在构建全国性直播视频流服务中起到的作用。
Hydra简介
Hydra是一个NodeJS模块,可以导入到JavaScript Node应用程序,从而为它们赋予微服务功能。Hydra通过使用Redis做到这一点。
下面我们看到三个微服务,每个都有连接到Redis的Hydra模块。在此模型中,大多数服务不直接与Redis联系,而是底层的Hydra模块充当Redis的代理。
图1
该图的另一个要点是,Hydra只是另一个导入的模块,就像图中所示的绿色框。Hydra仅在底部以蓝色显示,表明其存在和与Redis的连接。
Hydra模块公开了一个JS类接口,共有36个成员函数。
图2
上图让人大致了解我们所用抽象机制的简易性。findService比sendMessage等成员函数相当简单。
Hydra如何使用Redis?
这张幻灯片显示了许多重要的微服务问题。每个都是重要的微服务所必需的。我们会详细研究Hydra如何使用Redis实现这每项功能。
图3
请记住,此处的目的是表明如何可以执行这项任务,不是说每种方法都要你搞清楚如何在自己的服务中实现该功能。一个典型例子是,虽然可以将微服务配置数据存储在Redis中或使用Redis作为日志记录器,但这并不意味着你应该这么做。除非你确切知道自己在做什么、由此带来的缺点,否则别这么做。
另外请记住,你不需要Hydra。Redis使这每一项功能成为可能,你当然可以在自己的应用程序中这么做。
我要表明的另一个要点是,其中一些功能只有结合起来,才有可能实现。比如说,请求和消息路由依赖状态、运行状况、服务发现和负载均衡。
如你所知,可以使用各种基础设施工具来满足这每一项功能。然而,Hydra的一大目标是简化微服务的构建,同时尽量减少对外部基础设施的需求。你在构建生产就绪服务时,要决定需要哪些Hydra功能、可以从其他工具获得哪些功能。这不是非此即彼的选择,而是兼顾你想要实现什么功能、多快可以入手。
话虽如此,看看如何仅使用Redis和你青睐的编程语言实现所有这些功能还是饶有趣味。
键空间组织
想了解Hydra如何使用Redis,第一步是分析它如何组织管理Redis钥空间的使用。
Hydra使用的键由2到4个由冒号字符分隔的段标签组成。段标签名为:Prefix(前缀)、Service name(服务名)、Instance ID(实例ID)和Type(类型)。
图4
Prefix段允许过滤Hydra键与非Hydra键。所以如果你频繁使用Redis,能够过滤特定的键很重要。
Service Name段帮助过滤特定服务类型的键。比如授权、用户或图像处理等服务类型。
Instance ID段允许过滤独特服务实例的键。运行微服务时,你通常需要某个服务类型的多个实例在运行。为每个服务实例分配独特的ID,能够区别它们很有用。
最后还有Type段,用于对键的用途进行分类。并非每个键都有全部的段。比如说,一些键不需要Service Name和Instance ID。
下面这个例子显示了用户服务的键。我们看到前缀是hydra:service,后面跟着服务名(这里是“user-svcs”)。接下来,我们看到独特的实例ID。最后,我们看到这个键的类型是存在。所以,我们说存在信息存储在这个键地址中。
图5
前面一个令人困惑的地方是,键由名称组成,2到4个段标签由冒号字符分隔。然而,这里我们看到hydra:service也被冒号字符分隔。其想法是,可能还有另外的hydra:other类型,service只是其中之一。还有另一个涉及消息传送的不一致,稍后会讨论。我们打算在Hydra 2.0中解决这些问题,那将是突破性的变化,但内部更整洁。
我们可以输入redis-cli并输入Redis命令来查看各种键。我们将会在该演示文稿的其余部分介绍这方面的示例。
图6
简要说一下我们将使用的redis-cli示例:你会看到键命令的使用,Hydra内部使用Redis scan命令。
所以回顾一下,Hydra使用键是按段组织的,这使得查询来得更容易。此外,一致的组织使它们更容易扩展和维护。我们会在下面看到键在组织每个微服务功能中起到的作用。不妨先从存在说起。
存在
在微服务世界,能够发现服务,并了解服务是否健康、可以路由至关重要。那些功能取决于知道特定服务实例确实存在并可供使用。服务发现、路由和负载均衡等功能也需要这种功能。
每隔一秒,Hydra更新服务键的生存时间(TTL)。如果在3秒钟内没有更新将导致键到期失效、主机应用程序被视为不可用。
图7
我们在这里可以看到所用的Redis命令是“get”和“setex”,用于设置键和失效日期。
我们可以使用带模式匹配的“keys”命令来查询存在键。请注意有三个键。这告诉我们“asset-svcs”的三个实例在运行。
如果我们尝试检索其中一个键的内容,可以看到它包含实例ID。
而针对该键使用TTL命令可显示,它还有2秒就到期失效。
图8
所以回顾一下,可以使用自动到期失效的键来管理微服务存在。Hydra代表主机服务自动更新键。这意味着这不是开发人员做的事情。未能在3秒内更新键导致服务被视为不可用。这可能意味着服务不健康。
这引出了下一个话题......
健康
能够监控微服务的健康状况是另一项重要功能。Hydra每隔5秒就收集并写入健康信息快照。
你可以查看快照,浏览一下各个服务实例的健康状况。另外,HydraRouter仪表板之类的监控工具可以使用快照。
所以这是健康的键的样子。注意,唯一的新内容是“type”段,它识别键处于健康状况。
图9
我们查看键的内容时,看到它含有字符串化的JSON对象。在这种情况下,它代表“project-svcs”。
图10
对JSON进行非字符串化可以更容易查看存储的内容。它含有许多有用的信息。
图11
所以,可以存储每个服务实例的健康信息。它使用包含字符串化的JSON文本的字符串键来加以管理。监控应用程序可以使用该信息。
服务发现
接下来不妨考虑服务发现,这是任何微服务架构的另一项必备功能。
能够按名称发现某服务的IP和PORT位置,这大大简化了通信。其他优点包括没必要管理DNS条目或创建固定的路由规则。
服务发现信息存储在类型是“nodes”的Redis Hash中。使用Hash可以实现快速查询。我们使用Redis“hget”、“hset”和“hgetall”等命令来处理节点哈希。
图12
下列Redis操作可用于实现服务发现功能。第一个操作是查询特定的服务类型。第二个操作是查询可用实例。第三个操作让Hydra得以检索关于特定服务实例的信息。
我们可以看到有用的信息,比如服务版本、Instance ID、IP地址和端口,最后还有主机名。在这个示例中,主机名也恰好是Docker Container ID。
图13
我们可以使用Redis“hgetall”命令检索关于所有可用实例的信息。这是Hydra Router检索服务列表以便在仪表板上显示的方式。
图14
回顾一下。Hydra使用servicename键段进行查询,以便发现关于服务的各种信息。服务细节可使用Redis Hash来加以管理,Redis Hash提供了极快的服务发现。
接下来讨论路由。
路由
路由HTTP和消息(比如Web Socket或PubSub)需要路由得到验证。微服务可以向Redis发布路由。比如说,HydraRouter使用已发布的路由来实现可感知服务的动态路由。
每个服务都在类型“service:routes”的键中发布路由。这里我们看到“asset-svcs”路由的键。
图15
服务路由存储在Set结构中。这很适合,因为你不需要重复的路由条目。使用SADD命令和SMEMBERS命令。
顺便说一句,Redis丰富的数据结构集合是我与诸位得以分享的原因之一。
回到路由。我们可以使用键模式来获取路由列表。这里我们看到许多服务的路由。
图16
我们可以使用“smembers”命令来查看特定路由集的内容。顺便说一下,括号内的[get]、[post]和[put]部分代表HTTP REST端点。如果是其他消息传送技术,可忽略使用括号方法。
回顾一下。每个服务向Redis Set发布其路由。访问单个路由可显示该服务的路由条目集合。
使用Set数据结构将路由存储在Redis中,这避免了重复路由。已发布的路由可用于实现可感知服务的动态路由。接下来讨论负载均衡。
负载均衡
随着应用程序日益庞大,你需要在可用的服务实例之间对请求进行负载均衡。这通过Redis、使用上述的服务存在和路由功能来实现。在应用程序层面,使用Hydra,这跟使用“makeAPIRequest”或“sendMessage”调用一样简单。负载均衡在那些调用里面进行,因为Hydra使用路由和状态信息来选择可用的目标实例。
一个好处是,在路由期间,如果请求在特定实例上失效,Hydra能够在最终出现HTTP 503服务器不可用错误之前重试其他可用实例。
如你所见,负载均衡依赖其他功能,比如状态、服务发现和路由。
图17
回顾一下,对服务之间的请求进行负载均衡可使用我们见过的存在、服务发现和路由等功能来完成。Redis Strings、Hashes和Sets使这成为可能。整体大于各部分之和。
消息传送
分布式服务被迫通过底层网络相互联系。HTTP Rest调用可能最常见,但套接字消息传送可能高效得多。Hydra中的消息传送使用Redis Pub/Sub频道来完成,Redis通过套接字连接实现Pub/Sub。
这是一个示例键。Hydra使用Redis“subscribe”、“unsubscribe”和“publish”等命令。
图18
另外,Hydra路由工具能够通过HTTP和WebSockets接受消息,并将它们转换成pub/sub消息。
想了解这是如何工作的,不妨考虑两个服务:“asset-svcs”和“project-svcs”。每个服务都创建两个键,一个键使用服务名,另一个键使用服务名和实例ID。每个服务都监听两个频道。
图19
在大多数情况下,你不关心服务的哪个实例处理请求。这种情况下,使用的是没有特定实例ID的那个频道。
现在,你需要向特定实例发送消息时,可以使用有实例ID的那个频道。值得注意的是,进行负载均衡时,hydra将针对服务名的请求转换成带特定实例ID的请求。这确保只有一个实例处理特定的消息或请求。
我们可以使用Redis pub / sub channels命令查看频道键列表。注意,我们这里有四个键。第一个键是“asset-svcs”的名称,由资产服务的所有实例共享。接下来,我们看到另外三个有独特实例ID的键。三个服务实例各一个。
图20
继续关注消息传送。为了确保微服务之间互操作,统一共同的通信格式至关重要。通用消息格式是一种基于JSON的文档格式,它包括支持消息传送、路由和队列。这些消息以JSON字符串化文本的形式存储在Redis中。
这是UMF消息示例。
图21
“to”、“frm”和“bdy”等字段是必需的,服务可以在“bdy”对象中随意包含各自的自定义字段。
看看这实际上如何使用。
在左边,“client-svcs”向“project-svcs”发送消息。注意,这只需要UMF创建调用和发送消息调用,此处以黄色显示。
在右边,“project-svcs”监听消息,必要的话处理消息。这使用事件消息监听器来完成。
图22
注意,Hydra将服务发现、负载均衡、路由和pub/sub细节抽取出来。收发消息只涉及三个成员函数。这里有必要暂停一下。花点时间考虑使用你最喜欢的架构,这个示例将会是什么样子。
更仔细地分析一下。发送消息的机理是,解析消息中的“to”字段,以确定目标服务名。有了服务名,下一步是检查可用实例。有了目标实例,消息随后字符串化,并通过Redis“publish”命令发送出去。
图23
我们再次列出Redis中的所有Pub/Sub频道。消息可以通过这些频道发送,并由监听器检索。所以编写一点代码,我们就可以使用Redis,借助一堆井然有序的频道来路由消息。
图24
总之,值得注意的是,消息传送最终必不可少,因为服务是物理分布的。Redis使用其pub/sub功能来实现消息传送。
标准化通信让服务之间能够互操作。我们还看到了将底层的服务发现、负载均衡、路由和pub/sub等细节抽取出来后,应用程序层面的通信是多么容易。
下面讨论消息队列。
队列
作业和消息队列是许多重要应用程序的另一个重要部分。Hydra使用Redis为每种服务类型维护动态队列。
然后,服务实例可以读取其队列和进程项。
队列消息的内容是UMF消息,它遵循用于消息传送的同一种格式。互操作性同样最重要!
Hydra按照服务类型自动创建三个队列。
*“收到”的队列
*“处理中”的队列
*“未完成”的队列
因为这些是列表,我们使用Redis“lpush”、“rpush”、“rpoplpush”和“lrem”命令。
该图显示了队列之间的消息流。各项在队列之间的移动是Redis中的原子操作。因此,无论你有多少微服务,它都很安全。
图25
在左边的下一个示例中,对消息进行排队就像创建UMF消息并调用“queueMessage”来发送一样简单。右下方的代码显示图像处理服务通过调用“getQueuedMessage”使消息出列,然后处理消息完毕后调用“markQueueMessage”。这有多容易?
图26
回顾一下,有时,期待立即响应是不现实的。在这种情况下,我们只需要将以后处理的工作排入队列。Redis List数据结构可用作消息队列。像“lpush”和“rpoplpush”这些原子操作的命令让这成为可行。这里我们再次看到使用较高级的抽象实现基本的队列操作有多容易。
日志
分布式日志是任何微服务架构的另一项重要特性。然而,如果你了解Redis,可能会惊讶于将它用作分布式日志记录器的想法。你可能很担心,这很正常。然而,你可以将它用作飞行记录器。你只存储最严重的错误,并使用“lpush”和“ltrim”限制条目数量。此后,至少你可以快速检查微服务出了什么问题。
这是键的样子。注意键类型是health:log。
图27
这里我们看到health:log键类型实际上是“List”数据结构。因此,我们可以使用Redis“lrange”命令查看“imageproc-svcs”的飞行记录器日志。
图28
回顾一下:对于微服务而言,数十个乃至数百台机器上有日志是不可行的。分布式日志记录绝对是出路。使用Redis,你可以构建一个轻量级日志记录器,用作飞行记录器。使用Redis List数据结构以及方便的“lpush”和“ltrim”命令使这成为可能。
最后说说配置管理。
配置管理
管理分布式微服务的配置文件具有挑战性。然而,你甚至可以使用Redis来存储服务的配置文件。我们就是这么做的,当时看来似乎是个好主意。然而,我们开始抛弃这种做法。由于主要的缺点是在Redis中存储配置使Redis有状态(stateful),而这不太理想。不过这是可行的,所以我想告诉诸位。
不妨看看它如何工作。有一个configs键类型是个哈希值。该哈希值有一个键由服务版本以及针对该版本的配置数据所设的值组成。
图29
这是示例配置。以本文为例,我们使用一个名为“hydra-cli”的命令行工具,它让我们得以将配置文件推送到特定的服务版本。这一切是为了用键创建一个哈希值条目,而这个键由服务名和版本以及作为字符串化值的文件内容组成。记住,你还可以使用shell脚本来驱动redis cli。
我们可以使用“hget”命令和配置版本来提取特定版本。
图30
简单回顾一下,我们看到了Redis如何用于存储应用程序配置文件。Redis Hash数据结构让我们得以为每种服务类型存储配置。每个配置条目都由服务版本标签来索引,内容只是指向字符串化的JSON配置。
总结
不知不觉篇幅写了这么多。但总的来说,我在这里分享的是一种使用JavaScript和NodeJS高度依赖Redis的方法。然而,没有什么能阻止大家使用其他语言做同样的事情。又由于Hydra是开源的(https://www.npmjs.com/package/hydra),它可以充当这类工作的参考平台。
我很高兴地宣布,我们在开发一款受Hydra启发的Golang版本,我们希望尽快开源。我们还在考虑开发Java版本。
原文标题:Building Light-weight Microservices using Redis,作者:Carlos Justiniano
【51CTO译稿,合作站点转载请注明原文译者和出处为51CTO.com】