赞
踩
目录
随着架构设计的发展,微服务架构可以说是目前架构领域炙手可热的设计理念。在公司,笔者也一直在负责系统的服务化设计和开发工作。
今天就来谈谈微服务落地实践中的一些问题。希望对微服务设计无从下手的朋友,起到一些参考作用;另外也希望把自己的观点分享出来,期待与大家一起交流,能够认识到不足之处。
在落地微服务之前,我们遇到的第一个问题就是:应该如何拆分服务?
大家知道,关于如何拆分服务,并没有一个完全通用的法则。不过有以下几点要素,我们可以参考。
一般情况下,我们第一时间都会考虑到这点。将系统中的业务模块,按照职责标识出来,每个单独的模块拆分成一个独立的服务。
这样拆分之后,我们的服务要满足一个原则:高内聚,低耦合。
比如,我们把一个系统拆分为商品服务、订单服务、物流服务。一般情况下,我们修改物流服务的时候,并不会影响商品服务,这就是低耦合的体现;那么订单服务里面的功能和逻辑,都是围绕着订单这个核心业务流程的,就可以说它是高内聚的。
我们可以把系统中的业务按照稳定性进行区分。比如,用户注册、登录部分,在我司的系统中,这一块的代码只要写完,基本就不再会发生变动,那么我就将他们拆为用户服务。
同理,还有日志服务、监控服务,这些模块基本上也都是很稳定的业务,可以考虑单独拆分。
这里讲究的是,将可靠性要求高的核心服务和可靠性要求低的非核心服务拆分开来,然后重点保证核心服务的高可用。
避免由于非核心服务故障,而影响核心服务。
基于业务性能拆分,考虑的是将性能压力大的模块拆分出来。对于这一点,笔者有两点想法:
比如,在笔者参与的一个系统中,曾通过 RocketMQ
对接来自多家厂商的大量数据。
当时,就独立出来一个消息服务,专门用来消费消息。然后有的是在本地处理,有的通过 RPC
接口转发到其他服务处理,有的直接通过 WebSocket
推送到前端展示。
这样的话,即便流量激增,考虑给消息服务增加机器,提高消费能力就好了。
当了解到上面这几种拆分方式之后,我们就可以根据自己的业务范围和技术团队规模,来考虑自己系统的服务拆分了。
不过在这里,尤为值得注意的是,微服务切忌拆分的过细,一定要结合业务规模和技术团队的规模。
笔者以前就遇到一个项目,在做服务化的过程中,当时的技术负责人按照业务独立性来拆分服务,结果拆出了10来个服务;然后更狠的是,每个业务服务又把Service
层和Controller
层拆分开来,每个业务方法都需要远程调用。据当事人描述,这样做是为了方便后期提升扩展能力。
不过说实话,有些系统业务量并没有那么大,一味的迎合微服务中的微字,无疑是给自己增加难度,破坏整体系统的稳定性。所以,在重新梳理了业务流程后,笔者对这个系统进行了重构,缩减服务数量。
在这里,笔者想说明一点。Service
层和Controller
层是可以拆分成多个模块的,这个没关系。不过,它只应该是模块的分离,而不是服务的拆分。比如我们可以在开发阶段把它们拆分成多个模块,然后通过 Maven modules
聚合到一块,在部署运行阶段,它们还都是一个服务。
当完成了服务拆分之后,选用什么框架进行开发呢,应该选择 Dubbo 还是 SpringCloud ?
笔者不想单纯讨论它们的优劣之处,在这里可以就着这个问题分享下笔者的心路历程。
最初进行选型时,笔者选择了 SpringCloud
,毕竟它号称是微服务一站式解决方案。然后就搭建框架,集成各种组件,完成了一些 demo 的开发和测试。
不过,在完成这部分工作后,重新审视整个系统,看到SpringCloud
中,涉及到的Eureka、Ribbon、Feign、Hystrix、Zuul、Config
这些组件。
这时候,我会产生两个疑问:
笔者对于一站式
这个词有两层理解。第一,它简化了分布式系统基础设施的开发,易于开发;第二,简化的同时,它一定是屏蔽了复杂的配置和实现原理,不易于深入理解它的原理。
作为架构师或者团队技术负责人,我们必须对自己系统涉及到的技术点做到知根知底,或者说,最起码要懂得它们的原理。这样,即便遇到了问题,在 Baidu / Google
不出来结果时,也不会慌。
基于这样一个思路,笔者把目光又转向了Dubbo
。对于笔者来说,对Dubbo
就比较熟悉了,从它的框架本身来说,已经实现了负载均衡和集群容错,调用方式也是基于接口的。相较SpringCloud
而言,不需要再额外引入像Ribbon/Feign
这样的组件。
还有一点,Dubbo
得益于强大的SPI
机制,我们可以非常方便的扩展它。如果业务上有需要,在很多地方都可以对它进行扩展,比如 RPC 协议、集群、注册中心、序列化方式、线程池等。
不过话说回来,Dubbo
只是一个高性能的 RPC 框架,拿它和SpringCloud
相比,更多的还是比较REST和RPC
。不过关于这一点,就一般的项目而已,这点性能差异还不足以一锤定音,最多只是锦上添花而已。
至于其他的组件,比如Hystrix/Zuul/Config/zipkin
等,笔者的观点,还是看业务规模。微服务只是一种设计思想,架构理念,而不是说用到了很多分布式框架才叫微服务。这些框架的产生,只是为了解决微服务系统遇到的问题的。
需知一口吃不成个胖子,在做技术选型时,切记直接对标像阿里、京东、美团这样的大厂经验,一来,也许我们遇不到那样的业务场景;再者,一般公司也没有人家那样的人才储备。毕竟如果线上出了问题,是没人跟你分享损失的。
总的来说,还是结合自己的实际情况,以最稳妥的技术方案,完成业务上的需求。
拆分了服务,也完成了技术方案的选型,那就万事大吉,开始撸代码了吗 ? 如果单纯作为开发人员,那确实开撸就行了。如果你是一个系统的负责人,只满足于高屋建瓴,不考虑细节问题,那必然会节外生枝。
服务化之后,不同服务之间的调用就是远程调用。远程调用有个最基本的设置,即超时时间。
比如,在Dubbo
中,默认的超时时间是1秒。我们不能单纯的使用默认值或者统一设置成另外的值,为Dubbo
设置超时时间最好是有针对性的。
比如,比较简单的业务可以设置的短一些;但对于复杂业务而言,则需要适当的加长这个时间。因为,这里还涉及到一个集群容错的问题。
在Dubbo
中,集群容错的默认策略是失败重试,次数为2。假如有的业务本身就需要耗费较长的时间来执行,因为超时时间太短就会触发容错机制,来重试。大量的并发重试请求,很可能会占满Dubbo
的线程池,甚至影响后端数据库系统,导致连接被耗尽。
我们上面说,如果超时时间设置太短,有可能会导致大量请求会不断重试,而导致异常。
这里还隐瞒着另外一个细节,即读请求和写请求。如果是读请求,那么重试无所谓;如果是写请求,我们的接口是否也支持自动重试呢 ? 这就会涉及到接口幂等性问题。
如果写请求的接口,不支持幂等性,那么集群容错就得改为 failfast
,即快速失败。
笔者感觉,分布式事务在业界是一个没有彻底解决的技术难题。 没有通用的解决方案,也没有既高效又简便的手段。
虽然如此,但我们也得事先考虑到这一点,不然数据肯定会变成脏乱差。
在考虑解决方案之前,我们需要先看看自己的系统是不是真的追求强一致性;按照BASE
理论,在分布式系统中,允许不同节点在同步的过程存在延时,但是经过一段时间的修复后,能够达到数据的最终一致性。
基于这两个思路,我们才好制定自己的分布式事务方案。
对于要求强一致性的场景,或许可以考虑XA协议,通过二阶段提交或者三阶段提交来保证。
对于要求最终一致性的场景,可以考虑采用 TCC 模式,补偿模式,或者基于消息队列的模式。
比如基于消息队列模式,可以采用 RocketMQ
。它支持事务消息,那么这时候整个流程大概是这样的:
RocketMQ
发送事务消息到消息队列;RocketMQ
事务消息,提交后对消费者可见;RocketMQ
事务消息,消费者不会看到这条消息。另外,在这里安利下阿里开源的Seata
。目前最新版本是1.1.0,支持多种事务模式,比如 AT、TCC、SAGA 和 XA 事务模式。
有兴趣的朋友可以了解这篇文章。
在分布式系统架构中,为了系统间的解耦和异步处理,应对高并发和大流量,消息队列绝对是一大利器。
在使用这一利器前,我们也得考虑下有可能因为消息队列带来的烦恼。
首先需要考虑的就是可用性,如果消息队列不可用,会不会对系统本身造成大量的不可用;
然后,消息会不会丢失呢 ? 如何保证消息可靠性传输呢?比如要考虑消息队列本身的刷盘机制、同步机制;数据发送时的确认和消费后的提交;
然后就是重复消费,如果保证了消息不会丢失,多多少少都可能会有重复消息的问题,这时候就要考虑重复消费有没有问题,即消息幂等性;
还有,消息顺序性问题,你们的业务场景里,是否有消息顺序性问题,如果有这个问题,要么在设计时规避它,要么在消费时保证它的顺序。
随着微服务的拆分,日志系统也可能会演变为独立的模块。为了查看日志,我们可能需要登录到不同的服务器去一个个查看。
因此,搭建统一的日志处理平台是必然的。我们可以采用 ELK
的解决方案进行日志聚合。
在这里,还需要链路追踪问题。在微服务复杂的链式调用中,会比单体应用更难以定位和追踪问题。
对于这个问题,我们考虑引入分布式调用链,生成一个全局唯一的 TraceID
,通过它把整个调用链串联起来。结合Dubbo框架的话,我们实现自己的Filter
,用来透传这个TraceID
。
然而,在我们深入研究微服务最佳实践之前,我们应该首先谈谈微服务的一些好处和挑战,以及为什么首先要使用它们。
简而言之,微服务是一种改进的软件架构,可让你:
当然,有了这些好处,我们面临着一系列新的挑战,包括服务间通信、安全性和可扩展性。
通过对微服务的好处和挑战的概述,现在让我们深入研究一些最佳实践。这些最佳实践将帮助你创建一个强大、易于管理、可扩展且安全的微服务系统。
采用微服务架构需要遵循单一职责原则。如果单个微服务负责的业务太多,其故障或不可用将会对系统的其余部分产生多米诺骨牌效应。
微服务应该就是:微服务。保持微服务的业务域较小,只专用于一项逻辑功能。如果出现任何问题,这将降低影响。此外,较小的服务更易于维护–更容易更新和更快的开发。
连接到同一个数据库的多个微服务本质上仍然是一个单体架构。每个微服务都应该尽可能地拥有自己的数据持久层。这不仅确保了与其他微服务的隔离,而且如果特定数据集变得不可用,还可以最大限度地减少故障影响范围。
有时,不同的微服务访问同一数据库中的数据似乎是有意义的。但是,更深入的检查可能会发现一个微服务仅适用于数据库表的子集,而另一个微服务仅适用于完全不同的表子集。如果两个数据子集完全正交,这将是将数据库分离为单独服务的好方法。这样,单个服务依赖于其专用的数据存储,并且该数据存储的故障不会影响除该服务之外的任何服务。
这种数据分离带来了灵活性的增加。例如,假设我们有两个微服务,都共享相同的文件存储服务。一项微服务经常接触大量资产,但文件大小很小。另一个微服务只有几个它会定期访问的文件,但这些文件的大小有数百 GB。
对这两个微服务使用通用文件存储服务会降低优化成本的灵活性,因为你同时拥有大文件和小文件以及定期和不定期访问的混合。如果每个微服务都有自己的数据持久层,那么你就可以更灵活地找到最适合该单个微服务需求的提供者或服务。
成本优化、选项的灵活性以及对单一解决方案的更少依赖——这些都是分离不同微服务数据的原因。
微服务如何相互通信,需要深思熟虑。否则,单个不可用的服务可能会导致整个应用程序崩溃。
想象一个在线商店的微服务系统。一个微服务接受网站下的订单。另一个微服务向客户发送一条文本通知,告知其收到了他们的订单。另一个微服务通知仓库发出产品。最后,另一个微服务更新库存计数。
微服务之间有两种通信方式:同步和异步。如果我们使用同步通信来处理上述示例,Web 服务器可能会通过首先向客户通知服务发送请求来处理新订单。客户通知服务响应后,Web 服务器向仓库通知服务发送请求,然后再次等待响应。最后,Web 服务器向库存更新程序发送请求。
微服务之间的同步通信
当然,假设客户通知服务发生故障。在这种情况下,通知客户的请求可能会超时或返回错误,或者可能让 Web 服务器无限期地等待响应。仓库通知服务可能永远不会收到发货请求。
在异步通信中,服务发送请求并继续其生命周期而无需等待响应。在异步方法中,Web 服务器发送“通知客户”请求,然后完成其任务。客户通知服务负责通知客户并向仓库通知服务发送异步请求,仓库通知服务负责向库存更新服务发送请求。
微服务之间的异步通信
当然,在这个模型中,我们看到异步通信仍然会导致链依赖,单个服务的故障仍然会中断应用程序。
异步通信的一种简单但有效的方法是采用发布/订阅模式。当感兴趣的事件发生时,生产者(在本例中为微服务)将该事件的记录发布到消息队列服务。对此类事件感兴趣的任何其他微服务作为该事件的消费者订阅消息队列服务。微服务只与消息队列服务通信,而不是彼此通信。
消息队列服务促进异步通信
消息队列是一个独立的服务,与所有微服务分离。它负责接收已发布的事件并将这些事件通知订阅者。
有很多工具可以完成这种异步通信(例如,Kafka 或 RabbitMQ)。
有些情况下,微服务之间需要同步通信。大多数请求-响应交互出于必要是同步的。例如,查询数据库的 API 服务器必须等待查询响应;获取缓存数据的 Web 服务器必须等待键值存储响应。
当需要同步通信时,你可以选择使用开源Kong Gateway来确保你的通信快速可靠地路由到正确的微服务。
尽可能保持向后兼容性,这样你的使用者就不会遇到坏的 API。
然而,尽管我们作为软件工程师尽了最大的努力,但有时我们还是需要弃用过时的 API,因为我们不会一直运行它们。这时,可以使用API 网关请求转换插件,你的微服务可以通过在原始 API 响应旁边轻松注入弃用通知或附加类似于Kubernetes的“弃用标头”来提醒你的 API 使用者。
微服务的编排是成功的关键因素。从技术上讲,你可以使用 systemd 和 Docker 或 podman 之类的东西在虚拟机上运行容器,但这不能提供与容器编排平台相同级别的弹性。这会对采用微服务架构带来的正常运行时间和可用性优势产生负面影响。对于有效的微服务编排,你需要依赖经过实战考验的容器编排平台,该领域的领导者是Kubernetes。
Kubernetes 管理所有容器的构建和部署,同时处理负载均衡、缩放、高可用性副本集和网络通信问题。
你可以在本地独立部署 Kubernetes,也可以使用 Azure Kubernetes Service、Red Hat OpenShift 或 Amazon Elastic Kubernetes Service 等。Kubernetes 内置的调度、弹性甚多和网络功能使微服务编排比在传统操作系统上容易得多。
将 Kubernetes 与Kuma服务网格和Kong Ingress Controller结合使用,你就拥有了可发现、受监控和弹性的微服务——就像魔法一样。
随着你的应用程序包含越来越多的微服务,确保适当的安全性可能会变得复杂。执行安全策略对于保护你的整个应用程序免受恶意用户、入侵机器人和错误代码的侵害至关重要。无论你是在 VM 上还是在 Kubernetes 中运行,都需要注重身份验证、授权、流量控制和速率限制等。
基于微服务的架构,可能会产生成百上千个小型模块化服务。虽然这在提高速度、可用性和覆盖范围方面产生了巨大的潜力,但庞大的微服务系统需要一种全面性和系统性的监控方法。通过密切关注你的所有微服务,你将确保它们按应有的方式运行,可供你的用户使用,并正确使用资源。当这些期望中的任何一个未得到满足时,你可以通过采取适当的行动来做出调整。
幸运的是,在监控方面你不需要重新发明轮子。有多种广泛采用的监控解决方案可以无缝集成到你的基础架构中。一些解决方案使用指标导出器 SDK,可以通过在微服务中添加一两行代码来集成。其他的可以作为插件与你的 API 网关或服务网格集成,用于监控网络问题和资源使用情况。
当你的监控工具收集指标时,这些指标可以被可视化工具使用——漂亮的仪表板可以帮助你查看微服务背后的数字。上周四晚上 8:00 有多少用户在线?自我们发布新功能以来,CPU 负载增加了多少?API 的延迟是多少?
通过监控你的微服务,你可以对微服务的健康和可用性做出明智的决定。当你这样做时,你会让你的用户满意。
可扩展性:你可以分析看看每个小部分有什么要求,这将能够独立扩展应用程序的不同部分。
“对我来说,另一个最大的好处是,可以在任何虚拟机之外扩展这些容器。可以把这些容器放在我想要的任何一种配置中,因此我的应用程序完完全全具有可移植性。” - Steven McCord,ICX Media的创始人兼CTO
更容易维护:让不同的团队能够以相对独立的方式来处理不同的组件。
部署和配置最小化互相干扰:你可以在不影响其他服务的情况下部署和配置系统的微小部分。多个团队可以向生产环境提供不同的结果,而不会相互干扰和踩坑。
方便问题隔离:更容易隔离和检测问题。
更容易招聘:当你寻找开发人员或第三方供应商时,你只需要着重培训他们系统的这一小部分的相关内容。
职责清晰界定:一个团队负责一个特定的微服务。
足够深入了解:从事该工作的团队对其有深入的了解。
广泛的编程语言:可以使用不同的编程语言,这更多取决于什么最能满足微服务的目的。
更容易监督和理解:可以将你的庞大的代码库分成更多较小的项目。这允许你和你的团队更好地理解项目和它的代码。
更容易开放组件:当边界和接口被明确定义后,向新的业务部门或外部实体开放组件或现有功能就容易多了。
部署和互操作性:缺点是,部署和互操作性将会成为主要关注点。
编程语言太多:这可能会限制你的代码重用性和可维护性,而且可能会让招聘变得更加复杂。
组件工作耦合:服务的组成总是需要它们一起工作运行。但这样会带来问题,那就是改变一处端点会破坏旧版本中的其他依赖服务。
更难进行集成测试:与所有东西都放在在一个地方的单体系统相比,更难对整个系统进行集成测试。
架构必须从一开始就经过深思熟虑:如果服务之间有太多的内聚力,那么就会失去大部分甚至所有的优势。
需要在通信方面付出更多:在服务之间的通信方面,必须进行投入更多成本。很多故障也可能发生在服务的通信之间。
难以监控整个系统:拥有大量的组件,这将是运维人员的噩梦。
学习成本:需要付出更多时间来学习如何使用微服务架构。
复杂性:越来越多的微服务使得整个系统更加复杂,更难监督整个操作。
“所有这些组件都是随意储存的。如果你没有很好的工程流程,最终会有一大堆东西储存在那里,可能根本就不会被使用。” - Avi Cavale,Shippable公司的联合创始人兼CEO
“在一个基于微服务的平台上调试生产问题是一个完全不同的操作。如果没有适当的监控、日志和追踪设施,系统的复杂性便会大大增加。这就像穿过一个迷宫的过程。因此工程实践和标准化变得至关重要。” - Daniel Ben-Zvi,SimilarWeb的研发副总裁
将日志记录在一个地方是具有挑战性的:第三方日志聚合服务,如Loggly、Splunk或Heroku,是非常好的解决方案,但它们的价格非常昂贵。根据我的经验,遥测特别是集中式日志是一个最大的痛苦。你必须考虑到每项服务的粗制滥造程度。如果你不这样做,你可能最终只在日志信息方面支付50-60%的费用。(Sonu Kumar,微软的网站可靠性工程师)
开发微服务的首要挑战是将大型、复杂的应用程序分割成小型、自主、独立的可部署模块。如果微服务没有以正确的方式进行分割,将会出现紧耦合的微服务,这些微服务将具有单体架构的所有缺点,并具有分布式单体架构的所有复杂性。幸运的是,已经有一个解决方案可以在这方面提供很大的帮助。Eric Evans 当时是一名软件工程顾问,他在不同公司的业务应用程序中遇到了关于软件复杂性的反复出现的问题,于是在 2004 年出版的《领域驱动设计:处理软件核心的复杂性》一书中总结了他的宝贵见解。该书概述了三个核心概念:
软件开发团队应该与业务部门或领域专家密切合作。
架构师/开发人员和领域专家应该首先进行战略设计:找到有界的上下文和相关的核心域以及普遍存在的语言、子域、上下文映射。
然后,架构师/开发人员应该进行战术设计,将核心域分解为细粒度的构建块:实体、值对象、聚合、聚合根。
领域驱动设计的详细讨论超出了这篇文章的讨论范围,但是你应该读一下起初的 DDD 书籍 Eric Evans 的《领域驱动设计:处理复杂的软件(蓝皮书)(Domain Driven Design: Tackling Complexity in the Heart of Software (Blue Book))》,或者更现代一点儿的 DDD 书籍Vaughn Vernon的《实现领域驱动设计(红皮书)(Implementing Domain Driven Design(Red Book))》。如果将一个大型系统划分为核心域和子域,然后将核心域和子域映射到一个或多个微服务,那么我们将得到理想的松耦合微服务。
在将复杂的应用程序拆分为多个微服务模块之后,下一个挑战出现了,如何处理数据库?我们是否应该在微服务之间共享数据库?这个问题的答案是一把双刃剑。一方面,在微服务之间共享数据库将导致微服务之间的强耦合,这与微服务架构的目标正好相反。即使是数据库中的一个小更改也需要团队之间的协调同步。此外,在一个服务中管理数据库的事务和锁就已经足够具有挑战性了。而在多个分布式微服务之间管理事务和锁更是一项艰巨的任务。另一方面,如果每个微服务都有自己的数据库或私有表,那么在微服务之间交换数据就打开了挑战的潘多拉盒子。因此,许多著名的软件工程师都提倡在微服务之间共享数据库,将其作为一种实用的解决方案。然而,在我看来,微服务是关于可持续和长期的软件开发的。因此,每个微服务都应该有自己的数据库(或者私有表)。
不幸的是,大多数的后端开发人员对前端开发有一种过时的看法,认为前端开发很简单。由于大多数软件架构师都是后端开发人员,他们很少关注前端,而前端在架构设计中往往被忽视。通常在微服务项目中,后端与它们的数据库被很好地模块化,但只有一个整体前端。在最好的情况下,他们考虑用最热门的单页面应用(React、 Angular、Vue)其中之一来开发独体前端。这种方法的主要问题是,前端的单体架构和后端单体架构一样糟糕,正如我前一篇文章所述。另外,当前端因为浏览器的变化而需要更新时,它就需要一个大的更新(这就是为什么那么多公司仍然使用过时的 Angular 1 框架的原因)。网络是简单的,但非常强大,并天生提供了穿透力。开发基于单页面应用的微前端有很多方法:使用 iFrame、Web 组件或借助于(Angular/React)元素。
微服务架构的关键卖点之一是每个微服务都可以独立部署。如果你有一个系统,例如 100 个微服务,并且只需要更改一个微服务,那么你可以只更新一个微服务,而不需要修改其他 99 个微服务。但是,在没有自动化的情况下独立部署 100 个微服务(DevOps、CI/CD)是一项艰巨的任务。要充分利用微服务的这一特性,需要 CI/CD 和 DevOps。使用没有 CI/CD、DevOps 的微服务架构,自动化就像购买最新的保时捷,然后用手刹去驾驶它。难怪微服务专家 Martin Fowler 将CI/CD列为使用微服务架构的三个先决条件之一。
微服务架构的主要缺点之一是,软件开发变得简单,而牺牲了运维。使用单体架构,监视应用程序要简单得多。但是许多微服务在容器上运行,整个系统的可观察性变得非常重要和复杂。甚至日志记录也变得很复杂,要将来自许多容器/机器的日志聚集到一个中心位置。幸运的是,市场上已经有很多企业级的解决方案了。例如,ELK/Splunk 提供微服务的日志记录。Prometheus/App Dynamics 提供工业级监控。微服务世界中另一个非常重要的可观察性工具是 Tracing。通常,对一个微服务的一个 API 请求会导致对其他微服务的几个级联调用。要分析微服务系统的延迟,需要测量每个微服务的延迟。Zipkin/Jaeger 为微服务提供了出色的跟踪支持。
微服务架构告诉我们,对于一个微服务,采用最适合该微服务的编程语言和框架。这种说法不能照字面理解。有时,一个微服务可能需要一个新的技术栈,例如 CPU 密集/高性能任务,可能会选择如 c++ /Rust 之类的编程语言。如果微服务与机器学习一起工作,那么 Python 可能是更好的选择。但是在没有任何理由的情况下使用不同的编程语言/框架会导致过多的编程语言和框架而没有任何实际的好处。思考这么一个应用场景,一个微服务是使用 Spring Boot + Kotlin+ React + MySQL 开发的,另一个用的是 JakartaEE + Java + Angular + PostgreSQL,下一个是 Scala + Play Framework + VueJS + Oracle,则需要大量的工作去维持这些不同的编程语言、数据库、框架,而没有太多的收益。
微服务架构中最具挑战性的设计决策之一是服务之间如何通信和共享数据。当每个微服务都有自己的数据存储时,这一点就更为重要了。通常,一个微服务可以单独存在,但是它不能单独实现所有的业务目标。所有微服务为了实现业务目标而在一起工作,为了在一起工作,它们需要交换数据或触发其他微服务来执行任务。在微服务之间进行通信的最简单和最常见的方式是通过同步的 REST API,这是一种实用但临时的解决方案。如果服务 A 同步调用服务 B,服务 B 同步调用服务 C,服务 C 同步调用服务 D,那么延迟就会增加。此外,由于微服务主要是分布式系统,它们有可能会失败。通常,同步微服务会导致级联失败,即一个服务的失败会导致其他服务的失败。微服务之间的同步通信也导致了微服务之间的紧密耦合。对于长期解决方案,微服务应该异步通信。微服务之间的异步通信有很多方式:通过消息队列,例如 Kafka,通过异步的 REST (ATOM)或 CQRS。
许多专家认为,对于新项目,最好从松耦合的单体架构开始,因为微服务架构需要大量的初始工作来设置运维。在他们看来,一旦项目变得足够成熟,“漂亮的”设计就可以很容易地转化为微服务。然而,在我看来,这种方法在大多数情况下都会失败。实际上,实体内部的模块将是紧耦合的,这将使其很难转换成微服务。而且,一旦应用程序投入生产,在不破坏应用程序的情况下将其转换为微服务将变得更加困难。因此,我的建议是,如果最终有使用微服务架构的计划,那么就从微服务开始。
在微服务软件开发的早期,Netflix 主要使用 Java 编程来开发微服务。他们还开发了许多类库(Netflix 的 OSS 栈,包括 Hystrix、Zuul)。许多公司通过 Netflix 跟进并开始使用 Netflix OSS。后来,许多公司(包括 Netflix)发现,Java 实际上并不是开发微服务的语言,因为它体积庞大,而且冷启动问题严重。Netflix 后来转向 Polyglot 微服务范式,并决定不再进一步开发 Netflix OSS,这导致了追随者公司陷入困境。因此,与其大量投资于特定语言的类库(如基于 Java 的 Netflix OSS),不如使用框架(如服务网格、API 网关)。
大约 50 年前(1967 年),Melvin Conway 观察到公司的软件架构受到组织结构的限制(Conway 定律)。尽管这一观察发现已有 50 年历史,但麻省理工学院(MIT)和哈佛商学院(Harvard Business School)最近发现,这一法则在当今仍然有效。如果一个组织计划开发微服务架构,那么它应该使团队规模更为恰当(两个“美国”比萨团队:7±2 人)。此外,团队应该是跨职能的,最好有前端/后端开发人员、运维工程师和测试人员。微服务架构只有在高层管理者也相应地改变他们的观点和愿景的情况下才能发挥作用。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。