手记

踢开绊脚石:微服务难点之服务调用的解决方案

微服务已经成为越来越多企业IT部门研究的对象,逐步趋向于火热,数人云之前给大家分享过:《实录|微服务企业级落地将会带来哪些转变?》深入地阐述了微服务能够带来的利益,但是作为一项新兴的技术,总难免会遇到这样或那样的难题,今天就来一起看看微服务的难点之一:服务调用的解决方案。

微服务有很多难点,比如分布式系统:

Markdown

本文作者与红帽的顶级战略客户紧密合作,帮助其成功地驾驭这些困难的部分,实现服务框架以保持竞争力,根据创新去创造商业价值,作者也非常接近于开源社区中技术的快速发展。

当继续在服务架构的这些概念上进行探讨时,不可避免的会对一些难题进行讨论,作者希望在探索并实现微服务时,已经看到并消化了分布式计算的错误,同时作者也推荐了Jeff Hodges的《Notes on Distributed Systems for Young Bloods 》。

大部分一直在用过时的方法去解决这些问题,并一直在寻找“如何让开发者编写能够交付价值并尝试抽象分布式系统的业务逻辑”的解决方案。所做的事情是为了让服务调用看起来如本地调用通过抽象的网络与本地接口(CORBA、DCOM、ejb等等),但后来发现这并不是一个好方法,然后切换到WSDL/SOAP/代码生成类似的东西,以摆脱这些其他协议的脆弱性,但仍然使用相同的实践(SOAP客户机代码生成),有些人确实让这些方法奏效了,但也有很多不足之处,下面来看看当简单地调用服务时会遇到的一些问题:

  • 错误或延迟

  • 重试

  • 路由

  • 服务发现

  • 可观察性

Markdown

错误或延迟

当我们向服务发送消息时会发生什么?出于这个讨论的目的,这个请求被分成小块并通过网络路由。

因为这个“网络”,我们处理分布式计算错误想法,应用程序通过异步网络进行通信,这意味着对时间没有单一且统一的理解,服务以自己对“时间的含义”理解去工作,这可能与其他服务不同,更重要的是,这些异步网络路由数据包根据可用性的路径、拥堵、硬件鼓掌等,没有保证消息在有限的时间内将其接受者(注意,这个相同的现象发生在“同步”网络没有一个统一的理解时间)。

这其实很糟糕,现在不可能确定错误或只是缓慢,如果客户要求在售票网站上搜索演唱会,可不想等到天荒地老,希望能得到响应,在某个时候,请求失败,所以需要增加服务的超时时间,但也不仅限于增加服务时间。

在处理下游请求时,不能因为Down-Stream网络交互速度而慢下来,要有一些时间去考虑设置:

  • 建立到下游服务的链接需要多长时间,这样才能去发送请求

  • 是否收到回复

补充说明:构建系统作为服务架构的巨大优势是速度,包括对系统进行更改的速度,重视自治和系统的频繁部署,但当去这样做的时候会很快发现,在一切奇怪的情况下,超时工作,并不能很好地进行。

考虑到客户机应用设置了3个超时,从推荐引擎中得到相应,但推荐引擎也咨询相关引擎,因此,它发出了一个设置为2S的超时调用,这个应该没有问题,因为尚有服务调用将等待最多3个,但如果关联引擎必须与促销服务进行对话该怎么办呢?如果超时设置为5S呢?我们的测试(单元、本地、集成)的关联引擎似乎通过了所有的测试,甚至在潜在的操作下,因为超时设置为5S,推广服务也不会花那么长的时间,或者在超时结束后,关联引擎适当地结束了调用。

Markdown

最终得到的是一个繁琐的,很难调试很多电话进来时的状态(这可能发生在任何时间,因为网络是“异步”的),超时是很重要的一个条件。

重试

由于确实没有在分布式系统中有任何弹性的时间保证,所以需要在任务花费太长时间时超时,现在的情况是“超时后要做什么?”是不是向来电者抛出一个糟糕的HTTP 5XX?是否接受一些建议,让微服务变得有弹性,承诺理论和回调?还是我们重试?

若要重试,如果调用了更改下游服务的数据会怎样?作者在其他文章提到过,微服务最难的部分之一也是数据方面。

但更有意思的是,如果下游服务开始失败,最终会重新尝试所有的请求呢?如果有10个或100个实例的推荐引擎调用关联引擎,但关联引擎超时了怎么办?最后得到了Thundering Herd Problem的一个变种,当试图补救,并慢慢地恢复受影响的服务时,这就结束了DDoS的服务,即使我们试图补救和缓慢地返回受影响的服务。

Markdown

需要注意重试策略。指数重试的回退会有所帮助,但可能仍然会遇到同样的问题。

路由

因为服务是在考虑弹性的情况下部署的,所以在理想的情况下,在不同的容错区域中运行多个实例,这样一些区域在不降低服务的可用性情况下就会失败,但当事件开始失败时,需要一种方法去绕过这些失败,但在容错区域执剑,可能有其他的服务路由选择原因。也许某些“区域”被实现作为备份的服务的地理部署;也许从延迟的角度去看,让我们的流量在正常运行时访问备份实例太贵了。

Markdown

也许想要像这样路由客户端流量,但服务间通信呢?为了满足客户端请求,必须在服务之间进行反向对话,那么路由又如何选择呢?

容错性区域执剑的路由变化可能是路由和负载均衡请求,而这些请求是在可能出现周期性的异常服务的情况下进行的,希望将路由调整到能够与服务调用保持一致的服务,将流量发送到无法跟上的服务上是没有什么意义的。

当讨论如何部署服务的新版本时,应该去考虑更棘手的问题,正如前面所说,希望在服务之间保持某种程度上的自治,这样就可以快速迭代和推动更改,不希望破坏依赖服务,因此,如果可以将某些流量路由到新版本,并将其绑定到构建和发布策略(即蓝/绿,A/B测试,Canary发行版),这样很快就变得复杂了,如何确定路线?也许正在做的是在一个特殊的“登台环境”中进行测试,也许只是在做一个未经宣布的Dark Launch,也许是A/B测试,但如果有某种状态,需要考虑数据模式演化、多版本数据库实现等。

Markdown

服务发现

除了前面讨论的一些弹性考虑之外,在Expect失败的环境中,如何发现协作服务?怎么知道它们在哪里,如何和它们进行通信?在惨痛的静态拓扑中,应用将被配置为需要讨论服务的URL/IPs,还将构建这些依赖服务,以“用永不失败”但不可避免的是,它们会以一些不能接受的方式失败(或部分失败),但在一个弹性的环境中,下游的服务可以自动伸缩,对不同的容错区域进行改造,或简单地被一些自动化系统去除和重新启动,这些服务的客户端需要能够在运行时发现它们,并且不管拓扑是什么样子都可以使用它们。

Markdown

这不是一个要新解决的问题,而在弹性环境中变得困难,如DNS这样的东西是起不到作用的(除非在Kubernetes中运行)。

信任自己的服务架构

本文将最重要的考虑/问题留到最后,微服务可以快速改变系统架构,如果你不能信任自己的系统,那么你就会犹豫是否要对它进行改造,这样就会放慢发布速度,如果部署变慢,将意味着会需要更长的周期时间,导致了可能尝试部署更大的更改,团队之间需要更多的协调,在扼杀企业IT能力之前,你将会觉得“这就是我们的一贯态度”,听起来是不是很熟悉?

需要对自己的系统有信心,去信任它,可能有些基础设施的复杂组合(物理/虚拟/私有云/公有云/混合云/容器),有大量的中间件、服务、框架、语言和每个服务的部署,如果一个请求是缓慢的,那么要从哪里开始呢?

Markdown

在系统中,需要很强的“观察性”,需要有用且有效的日志记录、度量和跟踪,如果快速地迭代和发布服务,需要数据驱动,理解更改的影响,回滚的意义,或者快速发布新的版本去处理负面的影响,系统的所有部分都需要能够提供可靠的可观察性(日志/指标/监控),越是相信这些数据,越是能信任自己的服务架构,也就越有信心去根据情况进行改进。

回顾

回顾一下,当服务调用其他服务时,需要去解决的问题:

  • 服务发现

  • 自动适应路由/客户端负载均衡

  • 自动重试

  • 超时控制

  • 速度限制

  • 指标/统计数据收集

  • 监控

  • A/B测试

  • 服务重构/请求跟踪

  • 跨服务调用的服务期限/超时执行

  • 安全服务

  • 边缘网关/路由器

  • 强制服务隔离/离群检测

  • 内部版本/暗启动

解决方案

接下来看看那些巨头公司是如何做的,如果你看看Google/Twitter / Amazon / Netflix的人解决了这个问题,你会看到它们其实很暴力的就解决了这个问题,它们基本上会说:我们将使用Java/c++/Python,兵投入数不清的强大工程力量去构建库,以帮助开发人员去解决这个问题。”因此Google创建了Stubby,Twitter创建了Finagle,Netflix创建和开源了Netflix OSS等等,其他的人也都是这样做的。

但对于其他人来说,这种方式其实存在一个重大的问题:

你并不是这些巨头公司。

投入大量的人力资本资源去解决这些问题是不切实际的,一些对微服务感兴趣的公司其实感兴趣的方向是对价值和创新的速度,但它们并没有专业领域的知识。

也许可以重新使用它们的解决方案?但也又引出了另外一个问题:在最后,将会得到一个非常复杂、特别、只能实现部分的解决方案。

举例说明,作者与许多客户交互的是Java商店,它们考虑解决Java服务的这个问题,很自然地,会被Netflix的OSS或Spring Cloud所吸引,但是NodeJS服务呢?Python服务呢?遗留应用呢?Perl脚本呢?

每种语言都有自己对于这些问题的实现,每一种都被执行到不同的质量,它不像抓取开源库并将其填充到应用中那样简单,必须测试并且验证每个实现,需要负责是不是这些库,而是服务体系结构,可能实现扩散/语言/版本很快就会成为一个不可逾越的复杂性。

作者正在应用层实现较低级别的网络功能,比较喜欢Oliver Gould在提到这些问题时(如路由、重试、限速、断路等),这些都是5层考虑的问题观点:

Markdown

那么为什么要把这些东西复杂化?一直在试图解决这些问题的应用通过创建库(服务发现,跟踪,不同的数据集合,做更复杂的路由等)和干扰到的应用空间(依赖关系、传递依赖库调用等等),如果服务开发者忘记添加该实现的一部分(例如跟踪),会发生什么情况?因此,每个开发者都有责任去实现这些功能,引入到正确的库,在代码中编写它们等等。

更不用说在Java环境中使用注释的配置去缓解这些情况的一些框架,信任、可观察性、调试性等的目标被这样的方法所忽略。

作者更偏爱一种能够以更优雅的方式去实现这一点:

IMHO这是服务代理/Sidecar模式可以提供帮助的地方,如果能解出这些约束条件:

  • 如果有的话,将任何应用级别的感知减少到琐碎的库

  • 在一个地方实现所有的这些特性,而不是散布依赖项的倾卸区

  • 使可观察性成为一流的设计目标

  • 使它对包括遗留服务在内的服务透明

  • 非常低的开销/资源影响

  • 为任何或所有的语言/框架工作

  • 将这些考虑因素推到较低的堆栈级别(见上文)

在最近的一些微服务的前沿公司,作者看到了一些有趣的技术,实施服务代理的Sidecar模式,包括:

https://buoyant.io Linkerd

https://containo.us Traeffik

以上是小数今天给大家分享的文章,借用数人云了哥的观点,微服务其实非常复杂,并不是所有的人、企业都可以搞定,入坑之前一定要进行完整且慎重的考虑。

原文作者:Software
原文链接:http://www.tuicool.com/articles/VnIvyyF



作者:优云数智
链接:https://www.jianshu.com/p/acdbf1751710


0人推荐
随时随地看视频
慕课网APP