在这篇文章中,我将介绍我过去必须处理的一个具体的痛点:服务间通信。
微服务有什么问题吗?
您的典型的基于微服务的体系结构可能看起来像下面的图片:一些服务,其中一些是您的,另一些是由第三方(如您的数据库,您的外部API,等等)所做的,它们都相互通信,就像没有任何问题一样。
这架构看起来很美好,你在生产环境中,你的华丽的架构不应该因为流量增加而崩溃。或者不应该由于某些出问题的服务而丢失数据,您是否记得在您的comms层中添加“重试”逻辑?如果没有东西可以发送你的信息怎么办?
这些都是架构师和开发人员往往会忘记的现实场景,尤其是当他们不习惯把自己的东西放到生产环境的时候。让我在这些点上进一步扩展。
对于网站来说,更多的流量并不仅仅是意味着更多的钱
…有时,它只是意味着更多的头痛。如果您的应用程序正在处理web流量(这在当今非常常见),第一个简单的解决方法是“扩展”您的服务,并在它们前面添加一个负载平衡器。
如图所示,这在原则上是可以的,但是您突然将瓶颈从服务转移到它正在联系的其他服务。在上面的示例中,您的SearchService现在还可以,但是您的授权服务现在将会受到3倍的打击(我省略了数据库和弹性,因为我假设您正在使用的第三方服务实际上已经考虑过这个问题,并且已经准备好了)。
处理崩溃服务
我不是在讨论如何重新启动崩溃的服务,因为这是另一个话题。我说的是消费者对服务的看法。如果您的身份验证服务死亡,会发生什么情况?
让我们看一个更简单的示例:如果您有如下图所示的内容,其中MainAPI将传入的流量重定向到处理服务,处理一些数据并将其转换为存储服务,存储服务处理将数据保存到SQL数据库中。
如果你的处理服务突然崩溃,不管它是什么原因,你的主API会怎样?它处理传入的请求吗?再次崩溃吗?在这里要诚实,在将连接代码写入数据库时,您在else子句中添加了多少次日志行?是的,我这样认为。我甚至不想知道服务间通信的代码是什么样的。
主API需要知道如何处理没有数据发送的情况,所以你需要一个缓冲。
Redis可解决这些
这是我过去在处理这些类型的架构时遇到的最常见的问题中的两个(或三个,这取决于您如何计算它们)。下面是我用Redis来解它们的方法。
尽可能避免service - to - service通信
如果可以,我强烈建议您将一些服务转换为消息提供/使用的方式。通过这种方式,您可以通过让他们决定在任何给定时间可以处理多少消息来消除将请求溢出的问题。
为此,需要在服务之间推一个消息队列,并转换为pub/sub方法。您仍然保留了微服务的好处:
小型的,非常集中的进程彼此独立运行并且易于维护,
轻松的沟通,
简单的水平扩展,
能够在不影响平台其余部分的情况下工作和更改单个服务,
但是,您还可以在最需要处理过程之间获得一个缓冲区。在实践中,这意味着您已经解决了以前遇到的超载问题,因为现在,每当面对客户的服务被请求淹没时,它们就被扔进一个池子中,按照客户能够处理的速度进行处理,而不是反过来处理。
Redis如何帮助我们?
简单:Redis提供了执行缓冲区类型方法的两种方法。您可以直接使用它的发布/订阅功能。本质上,您将消息发布到队列,您的消费者将得到通知。现在,如果你有能力丢失信息,这是件好事,因为Redis' pub/sub不会在意是否有消费者在听。
另一方面,如果您需要可以依赖的东西,那么您可以使用可靠的队列模式,该模式使用Redis中的列表,通过激活keyspace通知,您可以自动通知您的消费者。这种模式需要一些额外的工作(比如锁定队列以避免并发问题),但是它们很容易处理。
上面的例子是这样的:
当新的消息到达队列时,仍然会通知使用者进程,但是它们可以决定处理它或忽略它。如果您碰巧有多个worker,那么它们可以通过在Redis上使用原子锁来决定谁在处理它(如果一个键在Redis中还不存在,那么只需设置一个键作为一个原子函数,这样您就可以确保无论哪个进程先执行它,都不会与其他进程发生冲突)。如果目前没有人能够解决这个问题,他们可以在完成任务后返回从列表中查看消息,从而成为创建缓冲区的更安全的方法。
下面是一个伪代码示例:
subscribeToQueue('new-data')
onNewMessage('new-data', function(newMsg) {
result = SETNX(newMsg.id+"_processed", 1);
if(result) {
//start processing the data
} else {
//ignore this one, someone else is working on it
}
})
不完全是JS,但你懂的。SETNX表示不存在的SET,这正是您想要的。
上述代码的另一个关键方面是选择密钥的名称。注意,我如何使用消息ID属性和字符串“_processing”创建惟一的键。除了添加简单的键-值对之外,在使用Redis时,这是一种常见的做法。人们往往会忘记一个明确定义的关键名字的力量,并错过很多机会。
当某个服务不可用,如何通知其它服务
当一个过程失败时,你怎么能告诉你的流程,这样他们就能采取相应的行动?当您必须处理相互通信的服务,并且不能使用上面的解决方案(避免直接服务到服务的通信)时,您可能至少有兴趣告诉您的服务如何意识到其中一个已经死亡。
通过这种方式,您的服务可以决定缓冲它们的通信数据,直到接收端恢复到在线状态,或者直接将它们的输出重定向到其他地方。这绝对是一种更好的方法,而不是仅仅去尝试一些不再存在的东西,然后因为它而失败。
Redis如何帮助我们?
基于keyspace通知特性(如果您了解我,您可能知道我喜欢),您可以让您的服务使用预定义的TTL更新特定于服务的密钥。您可能会说,无论什么服务在5分钟内没有提供关于其健康状况的状态更新,都被认为是死的。你可以让你的服务每次更新一个5分钟的TTL(或者每4分钟更新一次)。
如果您确保相互通信的服务订阅了它们的“聊天伙伴”的相应的“心跳键”,那么当与之交互的服务发生问题时,就会立即通知它们。
这个“心跳键”可以是1,表示他们还活着,也可以是一个包含状态信息的完整hashmap;这取决于你和你的需要。
今天就到这里吧!我希望这两个使用Redis解决跨服务通信的“技巧”对您有所帮助。