解决方案:微服务架构和相关的组件

优采云 发布时间: 2022-11-20 06:14

  解决方案:微服务架构和相关的组件

  来源丨本文介绍了微服务体系结构

  和相关组件、它们是什么以及为什么应该使用微服务体系结构和这些组件。本文重点简明扼要地介绍微服务体系结构的大局,因此不涉及组件使用方式等细节。

  了解微服务

  ,您必须首先了解那些不是微服务的内容。与微服务通常的对比是整体式应用程序,即将所有功能打包到单个单元中的应用程序。从单体式应用程序迁移到微服务并非一蹴而就,而是一个渐进的过程。本文将以在线超市应用程序为例来说明此过程。

  一:初始需求

  几年前,Bob和Pi一起开了一家网上超市。Bob 负责程序开发,Pi 负责其他事务。当时互联网还不发达,网上超市还是蓝海。只要实现该功能,就可以随便赚钱。所以他们的需求很简单,只需要一个网站挂在公网,用户就可以在这个网站上浏览商品、购买商品;此外,还需要管理背景来管理产品、用户和订单数据。

  让我们把功能列表放在一起:

  管理后台

  由于要求简单,鲍勃左手和右手慢动作,网站准备就绪。管理后台出于安全原因没有跟网站做,小明的右手和左手慢动作重播,管理网站也做了。整体架构图如下:

  小明摆了摆手,找了个云服务部署,网站就上线了。推出后好评如潮,受到各种肥宅的喜爱。萧明开始躺下收钱。

  二:随着业务的发展。

  好景不长,没过几天,各种网商纷纷纷纷效仿,对小明小皮造成了强烈冲击。

  在竞争的压力下,鲍勃·小皮决定实施一些营销策略:

  这些活动需要对程序开发的支持。小明拉着同学小红加入队伍。小红负责数据分析和移动开发。Bob 负责开发与促销相关的功能。

  因为开发任务比较紧迫,小明小红没有把整个系统的架构规划好,随口拍了拍脑袋,决定把推广管理和数据分析放在管理后台,微信和手机APP分开构建。几天一夜之间,新功能和应用程序几乎完成了。此时,体系*敏*感*词*如下:

  现阶段有很多不合理之处:

  尽管存在许多问题,但这一阶段的结果是不可否认的:该系统是为响应业务变化而快速构建的。然而,紧急和繁重的任务往往会导致局部的、短视的思维和妥协的决定。在这种建筑中,每个人都只关注自己的英亩和三块土地,缺乏整体的、长期的设计。从长远看,制度建设会越来越困难,甚至陷入不断推翻重建的循环。

  三:是时候改变了

  好在,晓明和小红都是有追求、有理想的好年轻人。意识到问题后,小明和小红从琐碎的业务需求中腾出一些精力,开始梳理整体架构,准备改造问题。

  要进行改造,您首先需要有足够的精力和资源。如果你的需求方(业务人员、项目经理、老板等)太专注于推进需求,以至于你无法投入额外的精力和资源,那么你可能什么都做不了。

  在编程的世界里,最重要的是抽象的能力。微服务转换的过程实际上是一个抽象的过程。小明和小红梳理了网商的经营逻辑,抽象出了常见的经营能力,做了几项公共服务:

  每个应用后台只需要从这些服务中获取所需的数据,从而去除了大量冗余代码,留下了轻薄的控制层和前端。此阶段的结构如下:

  此阶段只是分离服务,数据库保持共享,因此烟囱系统的一些缺点仍然存在:

  数据库成为性能瓶颈,并且存在单点故障的风险。

  数据管理往往是混乱的。即使一开始有良好的模块化设计,随着时间的推移,总会出现一个服务直接从数据库中的另一个服务中提取数据的现象。

  数据库表结构可能依赖于多个服务,影响整个身体,难以调整。

  如果保留通用的数据库架构,整个架构将变得越来越僵化,失去微服务架构的意义。于是,小明和小红大打出手,也把数据库也分开了。所有持久性层彼此隔离,由每个服务负责。此外,为了提高系统的实时性能,增加了消息队列机制。架构如下:

  完全拆分后,单个服务可以采用异构技术。例如,数据分析服务可以使用数据仓库作为持久层,以方便高效的统计计算;访问产品服务和促销服务的频率比较大,因此增加了缓存机制。

  抽象公共逻辑的另一种方法是使其成为一个通用的框架库。此方法减少了服务调用的性能损失。但是,此方法的管理成本非常高,并且难以保证所有应用程序版本的一致性。数据库

  拆分也存在一些问题和挑战:例如,需要跨数据库级联,通过服务查询的数据粒度厚度等。但这些问题可以通过声音设计来解决。总体而言,数据库拆分是一种利大于弊的拆分。

  微服务架构还有一个额外的技术优势,它让整个系统的分工和责任更加清晰,每个人都专注于为他人提供更好的服务。在单体式应用时代,常见的业务功能往往没有明确的归属。最后,要么做自己的事情,每个人都重新实现;要么是一个随机的人(通常是更有能力或更热情的人)在应用程序中做他负责的事情。在后一种情况下,这个人除了要对自己的申请负责外,还要另外负责把这些公共职能提供给别人——而这个职能本来是不负责任的,只是因为他更有能力/热情,就莫名其妙地责怪锅(这种情况也得到了有能力者的称赞)。结果,没有人愿意提供公共功能。随着时间的推移,团队中的人员逐渐变得孤立,不再关心整体架构设计。

  从这个角度来看,微服务架构的使用也需要对组织结构进行相应的调整。因此,做微服务转型需要管理者的支持。

  装修完成后,小明和小红分清楚了各自的花盆。两人非常满意,一切都像麦克斯韦方程组一样美丽完美。

  而。。。。

  四:没有灵丹妙药

  春天来了,一切都复活了,又是一年一度的购物狂欢节。看到日订量不断上升,小皮小明和小红笑了。可惜好景不长,音乐极度悲伤,突然系统挂断了电话。

  过去,对整体式应用程序的问题进行故障排除通常是查看日志并研究错误消息和调用堆栈。但是,微服务架构的整个应用分散在多个服务中,很难找到故障点。Bob 逐台检查日志,并一次手动调用一个服务。经过十多分钟的搜索,Bob 终于找到了故障点:由于收到大量请求,促销服务停止响应。其他服务直接或间接称为促销服务,因此它们也下降了。在微服务架构中,单个服务故障可能会产生雪崩实用程序,从而导致整个系统出现故障。事实上,在假期之前,小明和小红已经做过请求量评估。正如预期的那样,服务器资源足以支持假日的请求数,因此一定有问题。但是情况紧急,每一分一秒都白花一秒过去,所以Bob来不及排查问题,马上在云上搭建几个新的虚拟机,然后逐个部署新的提升服务节点。经过几分钟的操作,系统终于勉强恢复正常。整个失败时间估计损失了几十万的销售额,三个人的心都在滴血......

  之后,Bob 简单编写了一个日志分析工具(量太大,文本编辑器几乎打不开,肉眼看不见),统计了推广服务的访问日志,发现在失败期间,商品服务会因为代码问题,在某些场景下发起大量的推广服务请求。这个问题并不复杂,小明手指一抖,修复了这个价值数十万的bug。问题

  已解决,但不能保证不会再次发生类似的其他问题。虽然微服务架构在设计上逻辑上是完美的,但它就像一座由积木建造的华丽宫殿,经不起风。虽然微服务架构解决了老问题,但它也引入了新问题:晓

  明晓红决心要把这些问题解决好。故障的处理一般从两个方面入手,一方面尽量减少故障发生的概率,另一方面减少故障带来的影响。

  

" />

  五:监控 - 检测故障迹象

  在高并发分布式场景中,故障经常在雪崩中爆发。因此,有必要建立完善的监控系统,尽可能多地发现故障迹象。

  微服务架构中有许多组件,每个组件需要监控不同的指标。例如,Redis 缓存一般监控内存占用、网络流量、数据库监控连接、磁盘空间、业务服务监控并发、响应延迟、错误率等。因此,如果做一个庞大而全面的监控系统来监控各种组件,这是不现实的,可扩展性会很差。一般做法是每个组件提供一个接口(指标接口)来报告其当前状态,并且该接口输出的数据格式应保持一致。然后部署指标采集

器组件,定期从这些接口获取和维护组件状态,并提供查询服务。最后,您需要一个UI来从指标采集

器查询各种指标,绘制监控界面或根据阈值发出警报。

  大多数组件不需要自己开发,网络上有开源组件。Bob 下载了 RedisExporter 和 MySQL exporter,它们分别为 Redis 缓存和 MySQL 数据库提供指标接口。微服务根据每个服务的业务逻辑实现自定义指标接口。然后 Bob 使用 Prometheus 作为指标采集

器,Grafana 配置监控界面和电子邮件告警。设置了这样的微服务监控系统:

  六:定位问题——链路追踪

  在微服务体系结构中,用户的请求通常涉及多个内部服务调用。为了定位问题,您需要能够记录微服务中生成了多少服务调用以及每个用户请求时的调用关系。这称为链接跟踪。

  让我们用 Istio 文档中的链接跟踪示例来查看效果:

  图片来自:

  从图中可以看出,这是用户访问产品页面的请求。在请求过程中,产品页面服务会按顺序调用详细信息和评论服务的接口。评审服务在响应过程中调用评分接口。整个链路跟踪的记录是一棵树:

  若要实现链接跟踪,每个服务调用在 HTTP 标头中至少记录四项数据:

  此外,还需要调用日志采集

和存储组件,以及显示链接调用的 UI 组件。

  以上只是一个极简的解释,链接追踪的理论基础可以在谷歌的Dapper中找到。

  在了解了理论基础后,Bob 选择了 Zipkin,这是 Dapper 的开源实现。然后,他用手指轻弹,为HTTP请求编写了一个*敏*感*词*,生成这些数据以注入每个HTTP请求的HEADERS,同时异步将呼叫日志发送到Zipkin的日志采集

器。作为另一个观点,HTTP 请求的*敏*感*词*可以在微服务的代码中实现,也可以使用网络代理组件实现(尽管每个微服务都需要添加一层代理)。

  链路跟踪只能定位哪个服务有问题,而不能提供特定的错误信息。日志分析组件需要提供查找特定错误消息的功能。

  七:分析问题——日志分析

  在微服务兴起之前,日志分析组件应该已经广泛使用。即使使用整体式应用程序体系结构,当访问次数变大或服务器大小增加时,日志文件的大小也会膨胀到难以使用文本编辑器访问的程度,或者更糟糕的是,它们将分布在多个服务器上。要排查问题,您需要登录每台服务器获取日志文件,并逐个查找所需的日志信息(并且打开和查找速度很慢)。

  因此,当应用程序的规模变大时,我们需要一个日志的“搜索引擎”。为了准确找到所需的日志。此外,数据源端还需要一个采集

日志的组件和一个显示结果的 UI 组件

  鲍勃使用著名的 ELK 日志分析组件进行了调查。ELK是三个组件的缩写:Elasticsearch,Logstash和Kibana。

  最后,还有一个小问题,就是如何将日志发送到 Logstash。一种解决方案是在输出日志时直接调用 Logstash 接口发送日志。所以(嘿,为什么要使用“再次”)来修改代码......因此,Bob 选择了另一种解决方案:日志仍输出到文件中,并在每个服务中部署一个代理来扫描日志文件,然后输出到 Logstash。

  八:网关——权限控制、服务治理

  拆分为微服务后,会出现大量的服务和大量的接口,使整个调用关系变得混乱。往往在开发过程中,写来写去,突然记不住某个数据应该调用哪个服务。或者写得歪歪扭扭,调用不应该调用的服务,只读函数导致修改数据。

  为了应对这些情况,微服务的调用需要一些看门人,即网关。在调用方和被叫方之间添加一层网关,并在每次调用时验证权限。或者,网关可以用作提供服务接口文档的平台。使用网关

  的一个问题是决定使用多少粒度:最粗粒度的方案是整个微服务的网关,微服务通过网关外部访问微服务,微服务直接在微服务内部调用;在最好的情况下,所有调用(无论是微服务内部还是外部调用)都必须通过网关。折衷方案是按照业务域将微服务划分为若干可用区,直接在区域内调用,间隔通过网关调用。

  由于整个在线超市的服务数量不是特别大,Bob 使用了最粗粒度的解决方案:

  9. 发现中的服务注册 - 动态扩展

  上述组件旨在降低故障的可能性。但是,故障确实会发生,因此要考虑的另一件事是如何减少故障的影响。

  最粗略(也是最常用的)故障处理策略是冗余。通常,服务会部署多个实例,这可以分担提高性能的压力,其次,即使一个实例与其他实例挂起。冗余

  的一个问题是使用了多少冗余?这个问题在时间表上没有明确的答案。根据服务功能和时间段的不同,需要不同数量的实例。例如,在工作日,4 个实例可能就足够了;在升级时,流量显着增加,可能需要 40 个实例。因此,冗余的数量不是固定值,而是根据需要实时调整。

  通常,添加实例的操作为:

  部署新实例

  向负载均衡或 DNS 注册新实例

  只有两个步骤,但如果手动注册负载均衡或 DNS,那就不简单了。想想添加 40 个实例后手动输入 40 个 IP 的感觉......

  此问题的解决方案是服务自动注册和发现。首先,您需要部署一个服务发现服务,该服务为所有已注册的服务提供地址信息。DNS 也是一种服务发现服务。然后,每个应用服务在启动时会自动向服务发现服务注册自身。应用服务启动后,它会将每个应用服务的地址列表从服务发现服务实时(定期)同步到本地。服务发现服务还会定期检查应用程序服务的运行状况,并删除运行状况不佳的实例地址。这样,在添加实例时,只需要部署一个新实例,就可以在实例下线时直接关闭服务,服务发现会自动检查服务实例的增减情况。

  服务发现还与客户端负载平衡一起使用。由于应用服务已在本地同步服务地址列表,因此可以在访问微服务时决定自己的加载策略。甚至可以在注册服务时添加一些元数据(服务版本等信息),根据这些元数据通过流量控制客户端负载,实现A/B测试、蓝绿发布等功能。

  服务发现有许多组件可供选择,例如ZooKeeper,Eureka,Consul等。不过小明觉得自己水平不错,想炫耀一下自己的本事,就根据瑞迪斯自己写了一篇......

  十:断路器、服务降级、限流

  

" />

  融合

  当服务由于各种原因停止响应时,调用方通常会等待一段时间,然后超时或收到错误。如果调用链路较长,可能会导致请求堆积,整个链路占用大量资源,等待下游响应。因此,当对一个服务的多次访问失败时,应该融合它,标记该服务已停止工作,并直接返回错误。在服务恢复正常之前,不会建立连接。

  图片来自微服务设计

  服务降级

  当下游服务停止工作时,如果服务是

  不是核心业务,上游服务要降级,保证核心服务不中断。比如网上超市下单界面有推荐商品下单的功能,当推荐模块挂机时,点餐功能不能一起挂,只需要暂时关闭推荐功能。

  限流服务

  关闭后,上游服务或用户会习惯性地重试访问。这会导致服务恢复正常后立即在棺材中反复仰卧起坐,很可能是由于瞬间网络流量过多。因此,服务需要能够保护自身 - 限制。有许多限制策略,其中最简单的策略是在单位时间内的请求数太大时丢弃多余的请求。此外,还可以考虑分区限制。仅拒绝来自生成大量请求的服务的请求。例如,商品服务和订单服务都需要访问促销服务,商品服务由于代码问题发起大量请求,促销服务仅限制来自商品服务的请求,来自订单服务的请求正常响应。

  十一:测试

  在微服务架构下,测试分为三个级别:

  从上到下执行三个测试的难易程度增加,但测试的有效性降低。端到端测试是最耗时和费力的,但我们在通过测试后对系统最有信心。单元测试最容易实现,效率最高,但不能保证整个系统在测试后不会出现问题。

  由于端到端实施的困难测试

  ,一般对核心功能进行端到端测试。一旦端到端测试失败,就需要将其分解为单元测试:然后分析失败的原因,然后编写单元测试来重现问题,以便我们将来可以更快地捕获相同的错误。服务

  测试的难点在于服务通常依赖于其他一些服务。这个问题可以用模拟服务器解决:

  单元测试是每个人都熟悉的。我们通常会编写大量的单元测试(包括回归测试)来尽可能覆盖所有代码。

  十二:微服务框架

  指标接口、链路跟踪注入、日志引流、服务注册发现、路由规则等组件以及断路器和限制等功能都需要向应用程序服务添加一些互连代码。让每个应用服务自行实现是非常耗时和劳动密集型的。基于DRY的原理,Bob开发了一套微服务框架,将连接各种组件的代码和其他一些通用代码抽象到框架中,所有应用服务统一使用这个框架进行开发。

  微服务框架支持许多自定义功能。甚至可以将程序调用堆栈信息注入到链路跟踪中,以实现代码级别的链路跟踪。或者输出线程池和连接池的状态信息,实时监控服务的底层状态。

  有一个

  使用统一的微服务框架存在严重问题:更新框架的成本很高。每个框架升级都需要升级所有应用程序服务。当然,通常使用兼容性方案,为所有应用服务升级留出一段时间的并行时间。但是,如果有很多应用服务,则升级时间可能会很长。并且有一些非常稳定且几乎没有更新的应用程序服务,负责人可能会拒绝升级......因此,使用统一的微服务框架需要健全的版本管理方法和开发管理规范。

  十三:另一种方式 - 服务网格

  抽象

  公共代码的另一种方法是将其直接抽象为反向代理组件。每个服务还部署此代理组件,通过该组件处理和转发所有出站入站流量。此组件称为 Sidecar。

  挎斗不会产生额外的网络成本。挎斗部署在与微服务节点相同的主机上,并共享同一个虚拟网卡。所以挎斗和微服务节点之间的通信,其实只能通过内存拷贝来实现。

  图片来自:

  挎斗只负责网络通信。还需要有一个组件来统一管理所有挎斗配置。在服务网格中,负责网络通信的部分称为数据平面,负责配置管理的部分称为控制平面。数据平面和控制平面构成了服务网格的基本体系结构。

  图片来自:

  与微服务框架相比,Sevice Mesh 的优势在于它不会侵入代码,并且更易于升级和维护。它经常因性能问题而受到批评。即使环回网络不会生成实际的网络请求,内存副本仍会产生额外的成本。此外,还有一些集中式流量处理也会影响性能。

  十四:结束也是开始

  微服务并不是架构演进的终点。再往下,还有无服务器、FaaS 和其他方向。另一方面,也有人唱着合唱必须长时间分开,重新发现整体结构......

  无论如何,微服务架构的转型暂时结束了。萧明满意的摸了摸越来越光滑的脑袋,打算这个周末休息一下,和萧红喝杯咖啡。

  ·完·

  喜欢这篇文章,欢迎点击右上角分享文章到朋友圈~~

  建筑师

  我们都是建筑师!

  跟随架构师(家狗X)加一颗“星”

  每天获得技术干货,一起成为一名伟大的建筑师

  技术组,请添加若飞:1321113940加入建筑师组

  提交、合作、版权和其他电子邮件地址:

  解决方案:一文详解微服务架构

  本文将介绍微服务架构和相关的组件,介绍他们是什么以及为什么要使用微服务架构和这些组件。本文侧重于简明地表达微服务架构的全局图景,因此不会涉及具体如何使用组件等细节。

  要理解微服务,首先要先理解不是微服务的那些。通常跟微服务相对的是单体应用,即将所有功能都打包成在一个独立单元的应用程序。从单体应用到微服务并不是一蹴而就的,这是一个逐渐演变的过程。本文将以一个网上超市应用为例来说明这一过程。

  一:最初的需求

  几年前,小明和小皮一起创业做网上超市。小明负责程序开发,小皮负责其他事宜。当时互联网还不发达,网上超市还是蓝海。只要功能实现了就能随便赚钱。所以他们的需求很简单,只需要一个网站挂在公网,用户能够在这个网站上浏览商品、购买商品;另外还需一个管理后台,可以管理商品、用户、以及订单数据。

  我们整理一下功能清单:

  管理后台

  由于需求简单,小明左手右手一个慢动作,网站就做好了。管理后台出于安全考虑,不和网站做在一起,小明右手左手慢动作重播,管理网站也做好了。总体架构图如下:

  小明挥一挥手,找了家云服务部署上去,网站就上线了。上线后好评如潮,深受各类肥宅喜爱。小明小皮美滋滋地开始躺着收钱。

  二:随着业务发展……

  好景不长,没过几天,各类网上超市紧跟着拔地而起,对小明小皮造成了强烈的冲击。

  在竞争的压力下,小明小皮决定开展一些营销手段:

  这些活动都需要程序开发的支持。小明拉了同学小红加入团队。小红负责数据分析以及移动端相关开发。小明负责促销活动相关功能的开发。

  因为开发任务比较紧迫,小明小红没有好好规划整个系统的架构,随便拍了拍脑袋,决定把促销管理和数据分析放在管理后台里,微信和移动端APP另外搭建。通宵了几天后,新功能和新应用基本完工。这时架构图如下:

  这一阶段存在很多不合理的地方:

  尽管有着诸多问题,但也不能否认这一阶段的成果:快速地根据业务变化建设了系统。不过紧迫且繁重的任务容易使人陷入局部、短浅的思维方式,从而做出妥协式的决策。在这种架构中,每个人都只关注在自己的一亩三分地,缺乏全局的、长远的设计。长此以往,系统建设将会越来越困难,甚至陷入不断推翻、重建的循环。

  三:是时候做出改变了

  幸好小明和小红是有追求有理想的好青年。意识到问题后,小明和小红从琐碎的业务需求中腾出了一部分精力,开始梳理整体架构,针对问题准备着手改造。

  要做改造,首先你需要有足够的精力和资源。如果你的需求方(业务人员、项目经理、上司等)很强势地一心追求需求进度,以致于你无法挪出额外的精力和资源的话,那么你可能无法做任何事……

  在编程的世界中,最重要的便是抽象能力。微服务改造的过程实际上也是个抽象的过程。小明和小红整理了网上超市的业务逻辑,抽象出公用的业务能力,做成几个公共服务:

  各个应用后台只需从这些服务获取所需的数据,从而删去了大量冗余的代码,就剩个轻薄的控制层和前端。这一阶段的架构如下:

  这个阶段只是将服务分开了,数据库依然是共用的,所以一些烟囱式系统的缺点仍然存在:

  数据库成为性能瓶颈,并且有单点故障的风险。

  数据管理趋向混乱。即使一开始有良好的模块化设计,随着时间推移,总会有一个服务直接从数据库取另一个服务的数据的现象。

  数据库表结构可能被多个服务依赖,牵一发而动全身,很难调整。

  如果一直保持共用数据库的模式,则整个架构会越来越僵化,失去了微服务架构的意义。因此小明和小红一鼓作气,把数据库也拆分了。所有持久化层相互隔离,由各个服务自己负责。另外,为了提高系统的实时性,加入了消息队列机制。架构如下:

  完全拆分后各个服务可以采用异构的技术。比如数据分析服务可以使用数据仓库作为持久化层,以便于高效地做一些统计计算;商品服务和促销服务访问频率比较大,因此加入了缓存机制等。

  还有一种抽象出公共逻辑的方法是把这些公共逻辑做成公共的框架库。这种方法可以减少服务调用的性能损耗。但是这种方法的管理成本非常高昂,很难保证所有应用版本的一致性。

  数据库拆分也有一些问题和挑战:比如说跨库级联的需求,通过服务查询数据颗粒度的粗细问题等。但是这些问题可以通过合理的设计来解决。总体来说,数据库拆分是一个利大于弊的。

  微服务架构还有一个技术外的好处,它使整个系统的分工更加明确,责任更加清晰,每个人专心负责为其他人提供更好的服务。在单体应用的时代,公共的业务功能经常没有明确的归属。最后要么各做各的,每个人都重新实现了一遍;要么是随机一个人(一般是能力比较强或者比较热心的人)做到他负责的应用里面。在后者的情况下,这个人在负责自己应用之外,还要额外负责给别人提供这些公共的功能——而这个功能本来是无人负责的,仅仅因为他能力较强/比较热心,就莫名地背锅(这种情况还被美其名曰能者多劳)。结果最后大家都不愿意提供公共的功能。长此以往,团队里的人渐渐变得各自为政,不再关心全局的架构设计。

  从这个角度上看,使用微服务架构同时也需要组织结构做相应的调整。所以说做微服务改造需要管理者的支持。

  改造完成后,小明和小红分清楚各自的锅。两人十分满意,一切就像是麦克斯韦方程组一样漂亮完美。

  然而……

  四:没有银弹

  春天来了,万物复苏,又到了一年一度的购物狂欢节。眼看着日订单数量蹭蹭地上涨,小皮小明小红喜笑颜开。可惜好景不长,乐极生悲,突然嘣的一下,系统挂了。

  以往单体应用,排查问题通常是看一下日志,研究错误信息和调用堆栈。而微服务架构整个应用分散成多个服务,定位故障点非常困难。小明一个台机器一台机器地查看日志,一个服务一个服务地手工调用。经过十几分钟的查找,小明终于定位到故障点:促销服务由于接收的请求量太大而停止响应了。其他服务都直接或间接地会调用促销服务,于是也跟着宕机了。在微服务架构中,一个服务故障可能会产生雪崩效用,导致整个系统故障。其实在节前,小明和小红是有做过请求量评估的。按照预计,服务器资源是足以支持节日的请求量的,所以肯定是哪里出了问题。不过形势紧急,随着每一分每一秒流逝的都是白花花的银子,因此小明也没时间排查问题,当机立断在云上新建了几台虚拟机,然后一台一台地部署新的促销服务节点。几分钟的操作后,系统总算是勉强恢复正常了。整个故障时间内估计损失了几十万的销售额,三人的心在滴血……

  事后,小明简单写了个日志分析工具(量太大了,文本编辑器几乎打不开,打开了肉眼也看不过来),统计了促销服务的访问日志,发现在故障期间,商品服务由于代码问题,在某些场景下会对促销服务发起大量请求。这个问题并不复杂,小明手指抖一抖,修复了这个价值几十万的Bug。

  

" />

  问题是解决了,但谁也无法保证不会再发生类似的其他问题。微服务架构虽然逻辑设计上看是完美的,但就像积木搭建的华丽宫殿一样,经不起风吹草动。微服务架构虽然解决了旧问题,也引入了新的问题:

  小明小红痛定思痛,决心好好解决这些问题。对故障的处理一般从两方面入手,一方面尽量减少故障发生的概率,另一方面降低故障造成的影响。

  五:监控 - 发现故障的征兆

  在高并发分布式的场景下,故障经常是突然间就雪崩式爆发。所以必须建立完善的监控体系,尽可能发现故障的征兆。

  微服务架构中组件繁多,各个组件所需要监控的指标不同。比如Redis缓存一般监控占用内存值、网络流量,数据库监控连接数、磁盘空间,业务服务监控并发数、响应延迟、错误率等。因此如果做一个大而全的监控系统来监控各个组件是不大现实的,而且扩展性会很差。一般的做法是让各个组件提供报告自己当前状态的接口(metrics接口),这个接口输出的数据格式应该是一致的。然后部署一个指标采集器组件,定时从这些接口获取并保持组件状态,同时提供查询服务。最后还需要一个UI,从指标采集器查询各项指标,绘制监控界面或者根据阈值发出告警。

  大部分组件都不需要自己动手开发,网络上有开源组件。小明下载了RedisExporter和MySQLExporter,这两个组件分别提供了Redis缓存和MySQL数据库的指标接口。微服务则根据各个服务的业务逻辑实现自定义的指标接口。然后小明采用Prometheus作为指标采集器,Grafana配置监控界面和邮件告警。这样一套微服务监控系统就搭建起来了:

  六:定位问题 - 链路跟踪

  在微服务架构下,一个用户的请求往往涉及多个内部服务调用。为了方便定位问题,需要能够记录每个用户请求时,微服务内部产生了多少服务调用,及其调用关系。这个叫做链路跟踪。

  我们用一个Istio文档里的链路跟踪例子来看看效果:

  图片来自:

  从图中可以看到,这是一个用户访问productpage页面的请求。在请求过程中,productpage服务顺序调用了details和reviews服务的接口。而reviews服务在响应过程中又调用了ratings的接口。整个链路跟踪的记录是一棵树:

  要实现链路跟踪,每次服务调用会在HTTP的HEADERS中记录至少记录四项数据:

  另外,还需要调用日志采集

与存储的组件,以及展示链路调用的UI组件。

  以上只是一个极简的说明,关于链路跟踪的理论依据可详见Google的Dapper。

  了解了理论基础后,小明选用了Dapper的一个开源实现Zipkin。然后手指一抖,写了个HTTP请求的*敏*感*词*,在每次HTTP请求时生成这些数据注入到HEADERS,同时异步发送调用日志到Zipkin的日志采集

器中。这里额外提一下,HTTP请求的*敏*感*词*,可以在微服务的代码中实现,也可以使用一个网络代理组件来实现(不过这样子每个微服务都需要加一层代理)。

  链路跟踪只能定位到哪个服务出现问题,不能提供具体的错误信息。查找具体的错误信息的能力则需要由日志分析组件来提供。

  七:分析问题 - 日志分析

  日志分析组件应该在微服务兴起之前就被广泛使用了。即使单体应用架构,当访问数变大、或服务器规模增多时,日志文件的大小会膨胀到难以用文本编辑器进行访问,更糟的是它们分散在多台服务器上面。排查一个问题,需要登录到各台服务器去获取日志文件,一个一个地查找(而且打开、查找都很慢)想要的日志信息。

  因此,在应用规模变大时,我们需要一个日志的“搜索引擎”。以便于能准确的找到想要的日志。另外,数据源一侧还需要采集

日志的组件和展示结果的UI组件:

  小明调查了一下,使用了大名鼎鼎地ELK日志分析组件。ELK是Elasticsearch、Logstash和Kibana三个组件的缩写。

  最后还有一个小问题是如何将日志发送到Logstash。一种方案是在日志输出的时候直接调用Logstash接口将日志发送过去。这样一来又(咦,为啥要用“又”)要修改代码……于是小明选用了另一种方案:日志仍然输出到文件,每个服务里再部署个Agent扫描日志文件然后输出给Logstash。

  八:网关 - 权限控制,服务治理

  拆分成微服务后,出现大量的服务,大量的接口,使得整个调用关系乱糟糟的。经常在开发过程中,写着写着,忽然想不起某个数据应该调用哪个服务。或者写歪了,调用了不该调用的服务,本来一个只读的功能结果修改了数据……

  为了应对这些情况,微服务的调用需要一个把关的东西,也就是网关。在调用者和被调用者中间加一层网关,每次调用时进行权限校验。另外,网关也可以作为一个提供服务接口文档的平台。

  使用网关有一个问题就是要决定在多大粒度上使用:最粗粒度的方案是整个微服务一个网关,微服务外部通过网关访问微服务,微服务内部则直接调用;最细粒度则是所有调用,不管是微服务内部调用或者来自外部的调用,都必须通过网关。折中的方案是按照业务领域将微服务分成几个区,区内直接调用,区间通过网关调用。

  由于整个网上超市的服务数量还不算特别多,小明采用的最粗粒度的方案:

  九:服务注册于发现 - 动态扩容

  前面的组件,都是旨在降低故障发生的可能性。然而故障总是会发生的,所以另一个需要研究的是如何降低故障产生的影响。

  最粗暴的(也是最常用的)故障处理策略就是冗余。一般来说,一个服务都会部署多个实例,这样一来能够分担压力提高性能,二来即使一个实例挂了其他实例还能响应。

  冗余的一个问题是使用几个冗余?这个问题在时间轴上并没有一个切确的答案。根据服务功能、时间段的不同,需要不同数量的实例。比如在平日里,可能4个实例已经够用;而在促销活动时,流量大增,可能需要40个实例。因此冗余数量并不是一个固定的值,而是根据需要实时调整的。

  一般来说新增实例的操作为:

  部署新实例

  将新实例注册到负载均衡或DNS上

  

" />

  操作只有两步,但如果注册到负载均衡或DNS的操作为人工操作的话,那事情就不简单了。想想新增40个实例后,要手工输入40个IP的感觉……

  解决这个问题的方案是服务自动注册与发现。首先,需要部署一个服务发现服务,它提供所有已注册服务的地址信息的服务。DNS也算是一种服务发现服务。然后各个应用服务在启动时自动将自己注册到服务发现服务上。并且应用服务启动后会实时(定期)从服务发现服务同步各个应用服务的地址列表到本地。服务发现服务也会定期检查应用服务的健康状态,去掉不健康的实例地址。这样新增实例时只需要部署新实例,实例下线时直接关停服务即可,服务发现会自动检查服务实例的增减。

  服务发现还会跟客户端负载均衡配合使用。由于应用服务已经同步服务地址列表在本地了,所以访问微服务时,可以自己决定负载策略。甚至可以在服务注册时加入一些元数据(服务版本等信息),客户端负载则根据这些元数据进行流量控制,实现A/B测试、蓝绿发布等功能。

  服务发现有很多组件可以选择,比如说ZooKeeper 、Eureka、Consul、etcd等。不过小明觉得自己水平不错,想炫技,于是基于Redis自己写了一个……

  十:熔断、服务降级、限流

  熔断

  当一个服务因为各种原因停止响应时,调用方通常会等待一段时间,然后超时或者收到错误返回。如果调用链路比较长,可能会导致请求堆积,整条链路占用大量资源一直在等待下游响应。所以当多次访问一个服务失败时,应熔断,标记该服务已停止工作,直接返回错误。直至该服务恢复正常后再重新建立连接。

  图片来自《微服务设计》

  服务降级

  当下游服务停止工作后,如果该服务并非核心业务,则上游服务应该降级,以保证核心业务不中断。比如网上超市下单界面有一个推荐商品凑单的功能,当推荐模块挂了后,下单功能不能一起挂掉,只需要暂时关闭推荐功能即可。

  限流

  一个服务挂掉后,上游服务或者用户一般会习惯性地重试访问。这导致一旦服务恢复正常,很可能因为瞬间网络流量过大又立刻挂掉,在棺材里重复着仰卧起坐。因此服务需要能够自我保护——限流。限流策略有很多,最简单的比如当单位时间内请求数过多时,丢弃多余的请求。另外,也可以考虑分区限流。仅拒绝来自产生大量请求的服务的请求。例如商品服务和订单服务都需要访问促销服务,商品服务由于代码问题发起了大量请求,促销服务则只限制来自商品服务的请求,来自订单服务的请求则正常响应。

  十一:测试

  微服务架构下,测试分为三个层次:

  三种测试从上到下实施的容易程度递增,但是测试效果递减。端到端测试最费时费力,但是通过测试后我们对系统最有信心。单元测试最容易实施,效率也最高,但是测试后不能保证整个系统没有问题。

  由于端到端测试实施难度较大,一般只对核心功能做端到端测试。一旦端到端测试失败,则需要将其分解到单元测试:则分析失败原因,然后编写单元测试来重现这个问题,这样未来我们便可以更快地捕获同样的错误。

  服务测试的难度在于服务会经常依赖一些其他服务。这个问题可以通过Mock Server解决:

  单元测试大家都很熟悉了。我们一般会编写大量的单元测试(包括回归测试)尽量覆盖所有代码。

  十二:微服务框架

  指标接口、链路跟踪注入、日志引流、服务注册发现、路由规则等组件以及熔断、限流等功能都需要在应用服务上添加一些对接代码。如果让每个应用服务自己实现是非常耗时耗力的。基于DRY的原则,小明开发了一套微服务框架,将与各个组件对接的代码和另外一些公共代码抽离到框架中,所有的应用服务都统一使用这套框架进行开发。

  使用微服务框架可以实现很多自定义的功能。甚至可以将程序调用堆栈信息注入到链路跟踪,实现代码级别的链路跟踪。或者输出线程池、连接池的状态信息,实时监控服务底层状态。

  使用统一的微服务框架有一个比较严重的问题:框架更新成本很高。每次框架升级,都需要所有应用服务配合升级。当然,一般会使用兼容方案,留出一段并行时间等待所有应用服务升级。但是如果应用服务非常多时,升级时间可能会非常漫长。并且有一些很稳定几乎不更新的应用服务,其负责人可能会拒绝升级……因此,使用统一微服务框架需要完善的版本管理方法和开发管理规范。

  十三:另一条路 - Service Mesh

  另一种抽象公共代码的方法是直接将这些代码抽象到一个反向代理组件。每个服务都额外部署这个代理组件,所有出站入站的流量都通过该组件进行处理和转发。这个组件被称为Sidecar。

  Sidecar不会产生额外网络成本。Sidecar会和微服务节点部署在同一台主机上并且共用相同的虚拟网卡。所以Sidecar和微服务节点的通信实际上都只是通过内存拷贝实现的。

  图片来自:

  Sidecar只负责网络通信。还需要有个组件来统一管理所有sidecar的配置。在Service Mesh中,负责网络通信的部分叫数据平面(data plane),负责配置管理的部分叫控制平面(control plane)。数据平面和控制平面构成了Service Mesh的基本架构。

  图片来自:

  Sevice Mesh相比于微服务框架的优点在于它不侵入代码,升级和维护更方便。它经常被诟病的则是性能问题。即使回环网络不会产生实际的网络请求,但仍然有内存拷贝的额外成本。另外有一些集中式的流量处理也会影响性能。

  十四:结束、也是开始

  微服务不是架构演变的终点。往细走还有Serverless、FaaS等方向。另一方面也有人在唱合久必分分久必合,重新发现单体架构……

  不管怎样,微服务架构的改造暂时告一段落了。小明满足地摸了摸日益光滑的脑袋,打算这个周末休息一下约小红喝杯咖啡。

  原文链接:

  https://www.cnblogs.com/skabyy/p/11396571.html<br />

0 个评论

要回复文章请先登录注册


官方客服QQ群

微信人工客服

QQ人工客服


线