出发点首先,我们要有一个“服务”。根据定义,我们可以将每个服务实例视为一个黑盒子。该盒子具有明确定义的输入和输出点,并且(理想情况下)仅通过这些输入和输出点与外界通信。每个服务实例都会有一个专用的网络地址、独立的计算资源和独立的部署。客户端通过访问服务实例的地址来调用服务API。不同的服务也可以互相调用。ConfigurationManager:统一管理配置在微服务体系中,每个服务都是独立部署和运行的,团队可以根据需要选择增减计算资源。一个服务可能运行多个实例,每个服务实例都需要配置。为了方便统一调整配置,我们可以集中配置,每个服务实例去配置管理器(ConfigurationManager)获取配置。当配置更新时,我们也可以请求服务实例获取新的配置。服务名册:主机地址解耦这也导致了一个问题:网络地址(如IP)很容易因扩容和维护而改变,调用者很难实时知道可用地址。鉴于此,我们可以将网络地址抽象成一个不易改变的概念,比如给每个服务一个固定的名称。互联网使用DNS来解决这个问题,对应微服务基础设施中的ServiceRegistry。在运行过程中,每个服务实例都会以心跳的形式向服务名册发送注册信息,包括服务ID、访问地址、健康状态等。这样,当需要访问服务时,客户端可以先向服务名册查询可用的实例地址,然后访问实例调用服务。服务名册除了更好定位实例地址外,还可以在一些实例下线、维护或升级时,将一些实例暂时从名册中移除,使服务不至于下线。服务之间的调用也是如此,先找花名册获取网络地址,再调用。API网关:入口和路由寻找花名册的地址,然后调用服务API。这些是每个客户都会做的琐碎的事情。我们可以把这些东西抽象和集中起来,把服务API集成到一个大的中心点。然后封装地址和调用服务API等细节,所有客户端只和这个中心点对话,不再直接访问单个服务。从结构的角度来看,这个中心点将整个架构分为内部和外部两个部分。内部是所有的服务,客户端是外部的,中心点站在中间。作为内外唯一的通道,它在逻辑上被命名为“API网关”(APIGateway),有时也被称为“边缘服务”。API网关作为唯一的入口和出口,占据了最有利的位置,所以它有时会承载其他公共功能,比如我们很快就会提到的身份验证。认证服务:身份和权限问题继续沿着这个架构发展,我们会遇到新的问题:认证不方便。认证(Auth)包括两部分:身份认证(Authentication)和权限验证(Authorization)。身份认证关注的是“你是谁”,权限认证关注的是“你能不能做某事”。身份和权限都是高度集中的概念。对于一个系统来说,用户的身份必须是统一的。不能说这个用户做这件事的时候是张三,做那件事的时候是李四。另外,用户的认证状态也应该是统一的。不能说用户在访问这个服务的时候已经登录认证,访问另一个服务的时候没有登录。因此,只能有一个认证方。权限有点复杂。与身份不同,权限通常分为两类:功能权限和数据权限。这种划分对应现实世界中常见的权限模型:你的角色决定你的功能,功能的范围通常受到附加条件的限制。比如你是法官,有权审理案件,但你是A区的法官,只能审理A区的案件。再比如快餐店的经理,有权查看详细的员工信息,但只能查看自己门店的员工信息。两种类型的权力都是由全球规则决定的,而不是在行政部门。例如,谁来决定案件取决于法律,而不是法院。谁可以查看谁的数据,不是由数据存储部门决定的,而是由规章制度决定的。在实际情况下,组织可能有专门的审计部门来验证权限,但对于那些不是特别敏感的权限,公司会让每个部门自己验证。但是,无论是谁进行验证,都必须持有相同的规章制度,不能互相议论。该制度必须由中央组织制定和维护。也就是说,权限的管理也应该集中化。明确了认证的中心化之后,我们就可以开发一个公共的认证服务来进行身份认证和权限验证。下一个问题是:谁来发起认证?所有的服务调用都需要调用者明确身份,自然身份认证越高越好。API网关作为入口和出口,自然是发起身份认证的最佳选择。权限验证稍微复杂一些,值得单独写一篇文章详细讲解。这里我们暂且假设授权验证也是由API网关发起的。消息中介:异步和通知开发继续,一切风平浪静,暂时没有技术问题。但是,有一个业务问题需要解决。比如我们是一个网店,在订单创建成功的那一刻,仓库就必须开始盘点和发货的流程。问题是订单和仓储是两个服务,不同的团队负责。重点来说,订单服务并不关心仓储相关的问题,所以订单服务不可能在创建订单时主动通知仓储服务。仓储服务只能定期轮询订单服务,看是否有新订单。这样不仅麻烦,而且不够实时。我们仔细想想就会发现,这种需求很普遍,信息的产生者并不知道(也不关心)谁会对这些信息感兴趣。比如我们可能有一个监控服务需要实时展示产品的销售情况,一个BI服务需要获取客户购买的产品信息进行分析等等。既然这是一个普遍的需求,我们不妨对其进行建模,形成一种机制:信息产生者发出通知,接收通知的人决定是否需要采取行动。这意味着我们需要引入另一个中心化的公共服务:MessageBroker。当事件发生时(例如用户激活成功、订单创建成功),服务可以向消息队列发送消息。其他服务可以订阅这些消息并对它们做出反应。比如仓库服务可以订阅订单创建成功的消息。这样,订单创建成功后,订单服务发送消息给消息中介,消息中介通知仓储服务。仓储服务查看后,向订单服务询问新的订单信息,最后启动出库流程。除了广播事件,消息代理还可以进行异步调用。将同步调用转换为异步回调。对于耗时较长且不需要实时结果的调用,可以提高性能,改善体验。前后端:优化前端开发到这里,但是系统已经比较完善了。现在的问题是微服务基础架构如何与研发团队的通用架构更好的匹配。这就需要我们从康威定律的角度来看待整个基础设施的设计。在围绕用户和价值的软件开发过程中,我们经常使用用户旅程和用户故事来捕捉和跟踪价值的实现。用户故事通常包含具有明确边界、明确验收标准和明确价值的业务步骤。问题是支撑一个故事的研发工作有两端,两者并不同步。前端由业务流程和设计驱动,预计按顺序生产;后端由业务资源和建模驱动,希望通过模块来生产。比如前端经常因为设计原因调整自己需要的字段,但是后端从建模的角度来说没有这个需求,也没有动力去频繁的跟着前端的调整,使得前端不得不在网络不稳定的情况下传输冗余信息,占用宝贵的网络带宽。另外,在前端呈现某个业务步骤时,有两种信息目前不是必要的信息,但往往需要和必要的信息一起展示。一类是状态信息,比如当前登录状态和用户名,短信条数等。一种是垂直相关的信息,比如在展示文章的同时展示相关的文章。这就需要前端在调用主服务的同时调用多个不同的服务。且不说这些服务可能会出现调用超时和错误,光是多出一堆异步请求就足以让前端的效率大大降低。在微服务体系下,这些问题更加严重,因为现在不仅前后端有区别,而且不同的团队负责不同的服务。这些团队的诉求和时间安排各不相同,很难达到前端要求的快速响应。这些问题和麻烦可能会导致一个“缓冲区”,比如从后端指派专人满足前端的需求,或者从前端派一个人到后端讨论需求。根据康威定律,这种通信系统很容易随着时间的推移以软件的形式沉淀下来,形成专属的中间层。调和前后端的异步是不可能的,这个中间层是可以保留的天然解决方案。新的问题是,它的责任是什么?应该放在哪里?谁应该维护它?经过分析,有两个职责。诀窍是将前后端的工作解耦,减少相互影响。前端需要的东西可以写在中间层,经常改也没关系。如果后端没有准备好,前端也可以在这一层模拟假数据,以免被屏蔽。二是提高前端的运行效率。前端可以将需要的多个服务统一收集起来,一次性全部获取,免得发送多个请求。放置在API网关内,使其能够享受API网关的好处和保护。***这是维护问题。按照“谁主张,谁证明”的原则,既然有这个中间层,利益都是前端获得的,所以理论上应该由前端来维护。这样就定义了一个主要服务于前端的中间层。不同类型的前端(桌面、移动)可能有不同的需求。为了避免中间层的碎片化,我们可以让每个中间层与特定的前端类型紧密耦合,例如桌面特定的和移动特定的。这样一来,每个中间层就好像是某类前端的专用后端,因此“Backend-for-Frontend”(简称BFF)也因此而得名。LoopFuse:提高容错能力现在调试很容易,我们继续开发。一开始没有问题,但是部署到预生产环境的时候,又出现了一个问题:整个系统的容错性很低。一个小错误很容易被层层传递和放大,导致整个系统的崩溃。我们都知道编程中最头疼的就是远程调用了。本地调用多数情况下是“成功”或“失败”,但远程调用很可能是“无响应”。“没有回应”可能是正常的,对方可能会晚一点给你结果,也可能是对方已经死了,无法给你回应。最坏的结果就是门口挤满了人,大家都在等你给结果,你也在等别人给结果,所有的资源都被占用等待,什么也做不了。但是,无法避免远程调用。在微服务系统中,这个问题被进一步放大。这是因为微服务的模块化是以服务为基础的,每个服务都是独立部署和维护的,服务之间的调用是家常便饭。在这种严峻的形势下,我们必须尽量从架构上提高整个服务体系的容错能力,让个别服务出现问题影响全局。具体做法是在远程调用中加入熔断阈值检查。当调用超时次数超过阈值后,将不再调用,直接返回错误。一段时间后,恢复阈值,尝试继续调用,重复前面的过程。这个机构就是断路器,这个工具就是断路器(CircuitBreaker)。除了隔离发生故障的服务实例外,熔断器的另一个重要功能是提供备份解决方案。虽然我们把所有的业务都拆分成了服务,但是服务有高有低。一些服务是关键服务。一旦出现问题,整个流程就无法继续下去。一些服务是分支服务。即使他们错了,也不影响大局。比如在购买一个产品的时候,往往会根据用户的习惯,当前购买的是什么,做出一些推荐。如果推荐的服务有问题,一律不予推荐,不应影响用户正常的购买流程。同理,如果在线点餐的地址定位服务出现问题,我们也应该允许用户手动选择餐厅点餐。虽然体验不好,但至少正常流程还是可以完成的。基于这样的考虑,熔断器应该为非必要的服务调用提供备份方案,尽量保证核心流程的顺畅。有了循环熔断器,一定程度上缓解了远程调用出错的问题。结合循环熔断器和监控熔断阈值的变化,开发人员可以更轻松地发现问题并及时采取行动。LoadBalancer:提高服务弹性要想正式上线,还要做好LoadBalancing(简称LB),提高整个服务的弹性。理论上,有两种负载均衡方式:客户端负载均衡(Client-SideLB):由客户端决定如何分发请求。中间层负载均衡(Mid-TierLB):由DNS和网关等中间方决定如何分发请求。现在,服务名册中已经有了一个服务列表和它们对应的实例地址,所以最简单的客户端负载均衡的方法是拉下地址,然后顺序或随机选择可用的地址。中间方的负载均衡有更多的选择,从最外层的DNS到网关都可以根据需要做不同程度的。扩展基础架构现在,微服务基础架构已基本完成。如有必要,我们可以扩展此基础设施。架构师在做扩展的时候要注意区分哪些东西应该集中,哪些东西应该由服务自己决定。比如本文提到的基础设施中,(几乎)强制性的全中心化模块有:配置管理服务名册消息队列其中,配置管理和服务名册是所有服务都需要的基础设施,必须统一。消息队列和日志采集都是为了跨服务操作和跟踪,也必须是中心化的。半中心化模块包括:路由鉴权路由和鉴权必须统一,我们前面讲过。但是,微服务可能会对外暴露多套“自用”、“客户用”等公共API(比如快递公司内部使用的物流API和对第三方开放的物流API),所以可能有两种API网关,对应会有两套API目录和两套认证体系,所以是“半中心化”的。这些是集中式、半集中式选择范例。每一个中心化的选择都可能使整个架构变得死板和不灵活。因此,我们在设计和扩建基础设施时要特别注意这个问题。除了中心化的选择,架构开发的另一个重点是保持业务“黑匣子”。我们抽取了各个服务之间的关联,也抽取了权限的定义和验证。每个服务都变得简单纯粹,成为“纯业务服务”,相当于只包含业务规则的服务。黑盒子。这样无论有多少服务和模块,都不会受到影响。业务的复用性也很高。总而言之,设置好微服务的必要设施后,剩下的就需要根据实际情况和项目经验进行调整了。例如,我们可能会选择将很多功能合并到一层,以避免过度分层造成不必要的性能损失,或者微调整个基础架构。只要把控好“中心-自理”和“业-非业”的关系,这个基础设施就能健康发展。微服务基础设施总结总结一下这篇文章,微服务的基础设施应该包括以下组件(按照在请求流中出现的顺序):配置管理:配置的集中管理。API网关:对外API目录;API依赖;启动身份验证。服务注册:服务的注册和发现。认证服务:提供认证服务:认证身份,验证功能权限。前后端:根据前端的要求对请求进行拆解,调用服务,对结果进行汇总转换。消息中介:全局通知机制;异步调用机制。LoopBreaking:隔离故障服务,等待恢复;提供后备。负载均衡:避免服务过载。需要注意的是,这些组件的组合形式,具体拆分形式,是否需要,需要根据实际项目和团队情况进行调整。本文权属抛砖引玉,请读者知晓。【本文为专栏作者“ThoughtWorks”原创稿件,微信公众号:Thinkworker,转载请联系原作者】点此查看该作者更多好文
