来源:程序猿技术大咖
毫无疑问,微服务架构的设计原则和核心话题是本文要讨论的重点,也是打算从零基础开始构建微服务架构需要事先考虑、规划的。一个好的产品、应用能否稳定运行,持续开发,满足业务需求,能否经得起现实的考验,就需要在设计阶段考虑很多、很多,以确保它的健壮性。
![2404736e112b0485670f4efa6850e640.png](https://i-blog.csdnimg.cn/blog_migrate/be6fac99d9db1528be87f4507feb7346.jpeg)
当我们从单体架构的应用走向基于微服务的架构时,首先会面临一个很棘手的问题是如何进行服务的拆分,服务的拆分粒度应该如何衡量,怎样拆分的服务才算是“微”。接着将又会面临,这么多的服务又如何关联起来呢?如何有效的相互间通信呢?如何高效的部署呢……
本文我将从微服务架构的设计原则、核心话题两大方面展开讨论,希望能够对你构建一个微服务架构的应用有所帮助。
微服务架构的设计原则
软件架构的设计原则、方法论,在很大程度上能够指导、提醒我们应该遵循什么的原则、规范,能让软件架构更加健壮、稳固,并易于开发、扩展、维护等。
拆分足够微
在解决复杂的问题时,我们倾向于将问题划分成若干个小问题来解决,所谓“大事化小,小事化了”。单体架构的应用,随着时间的推移,会变得越来越臃肿,越来越难以维护,适当的做“减法”,可以解决单体架构存在的这些问题。
将单体架构的应用拆分为微服务架构的应用时,服务的拆分粒度问题,成为了重中考虑的问题。粒度太大,拆分的不够充分,便和单体架构没有太大的区别,更不能发挥出微服务的优势。如果拆分的太细,又将会面临着服务数量太多而引发的服务管理、服务间调用的问题。对于如何“微”才算是足够的“微”,是没有标准的衡量计算方法的。
![c0769030d5e04c32e9d4c4bdc92f0686.png](https://i-blog.csdnimg.cn/blog_migrate/53dd2bb2a6b542bae7d40b6da691665e.jpeg)
微服务不是说越小越好。服务越小,微服务架构的优点和缺点也就会越来越明显。服务越小,微服务的独立性就越高,但同时微服务的数量也会增加,管理就会存在很大的问题,成为一个新的挑战,这也就是常常所被提到的“这么多的服务,该服务管理啊?”问题。
服务的拆分足够微,可以按照某种方式、规则拆分,通常可以按照业务模块、业务场景等进行拆分,尽量避免服务间的相互依赖,做到高内聚低耦合。紧密关联的处理,放在一个服务内,但避免在服务与服务之间共享数据。
![a4155b79a6a9d27c3686f215fc8144db.png](https://i-blog.csdnimg.cn/blog_migrate/556ecd3068fd70f7c89633c80db5bba3.jpeg)
轻量级通信
在单体架构的应用中,可直接通过简单的方法调用就能进行通信,但在微服务架构中,由于服务都是跨域进程,甚至是跨主机的,组件只能通过REST、Web服务或RPC类似的机制在网络上进行通信。
因为服务划分的已经足够小了,服务间的通信可能会比较频繁,考虑到性能、响应时间等方面,则服务间通信应采用轻量级的通信协议,如:同步的REST,异步的AMQP、STOMP、MQTT等。在实时性要求不高的场景下,采用REST通信是不错的选择,REST是基于HTTP协议,可方便进行跨域访问或跨防火墙的设置,并且消息格式可以统一为XML或JSON格式,方便开发人员阅读和理解。如果服务间通信比较频繁,有比较高的要求,可采用消息通信的方式,如:Kafka、MQ等类似的消息中间件。如果不考虑对外提供访问的话,可采用gRPC的通信方式,因为gRPC是基于Netty的,通信效率更高。
单一职责原则
当服务粒度过粗时,服务内部很容易产生耦合。如果多人开发同一个服务,很容易因为耦合过大造成代码修改重合,不利于后期维护。确保每个服务职责单一,这也是用来确定服务拆分边界的一个原则,遵循“高内聚、低耦合”。
必须要对自己的产品和业务的了解,才能更准确的确定服务边界,让各个服务满足单一的业务职责,避免职责交叉。
领域驱动原则
领域驱动设计(Domain Driven Design),是一套综合软件系统分析和设计的面向对象建模方法。一个微服务,就应该能够反映出某个业务的领域模型,使用领域驱动设计,不但可以降低微服务环境中通用语言的复杂度,而且可以帮助团队搞清楚领域的边界,理清上下文边界。
建议将每个微服务都设计成一个DDD限界上下文,为微服务提供了一个逻辑边界。每个独立的团队负责一个逻辑上定义好的系统切片,负责与一个领域或业务功能相关的全部开发,最终团队开发出的代码更加易于理解和维护。
微服务架构的核心话题
基于微服务架构的应用,将面临着许多选择、争议等讨论的核心话题,这些核心话题将会在你接下来的微服务架构生涯里不断出现,并成为讨论的焦点。在此,我觉得有必要进行汇总整理,让你觉得它存在的必要性,能为你之所用。
服务拆分
服务拆分首先关注的就是服务的颗粒度,可遵循设计原则——拆分足够微,通过DDD(领域驱动设计)的指导,将某个领域的功能进行聚合成为一个服务。
对于一个大型复杂的单体应用而言,选择先拆分哪个模块,是一个问题。一般考虑先从容易、简单被拆分的模块开始,在拆分简单模块过程中,不断积累微服务的经验,逐步拆分掉复杂、繁重业务的核心模块。同时,寻找那些和其他功能业务重合度低、耦合度低,且自身变化较为缓慢的基础服务,将它们拆分为微服务。
决定了拆分哪些模块,要拆分成多个微服务后,接下来就要划清服务拆分的边界,将服务边界和接口顺理清楚。确定哪些应该包含进来,哪些不应该包含进来,哪些接口需要重新设计,哪些可以重复利用。
如下图所示,展示了一个单体应用拆分为多个微服务的过程。一旦拆分完后,各个服务就可以独立开发、部署和扩展。
![d1964b2df6a5f4b704635e9e714a43ac.png](https://i-blog.csdnimg.cn/blog_migrate/eb1de16ed0652672642093fa4c1c2caa.jpeg)
服务的拆分,不单单指功能的拆分,如上图所示,还得考虑数据库的拆分 ,确保降低功能逻辑层、数据访问层的耦合度。
服务注册与发现
微服务架构的特点是服务的数量众多,这些众多的服务需要一个统一的服务注册平台来进行服务的管理。每个微服务实例在启动后,将自己的实例信息注册到服务注册表或服务注册中心。服务的调用方若想获取可用服务实例的列表,则需要从服务注册表中去获取相关的信息。
![4e34e05f590de717c3f9e90845aaf863.png](https://i-blog.csdnimg.cn/blog_migrate/ceb5f625fa7bdbe8bbc15cbe6af574f7.jpeg)
当服务实例失效或down掉以后,服务实例的信息就要从服务注册表中移除,即:服务注销。服务注册表是用于维护所有可用的服务实例的地方,服务注册表一方面要接收微服务实例的接入,另一方面当服务实例不可用时,需要及时将服务实例从注册表中清楚。下图展示了服务注册与服务实例的关系。
![05f3df8c2e5c0605aca36e2b5db78442.png](https://i-blog.csdnimg.cn/blog_migrate/f22f3f7ba87808d5d13688043bee7fd0.jpeg)
服务注册与发现组件或框架,有很多,如:Eureka、Consul、etcd等,都提供了服务注册表的功能,可供大家进行选择。
负载均衡
在微服务架构中,负载均衡是必须使用的技术,通过它来实现系统的高可用、集群扩容等功能。负载均衡通常分为两种:服务端负载均衡和客户端负载均衡。通常所说的负载均衡均指服务器端的负载均衡,可通过软件或硬件设备来实现,软件如:Nginx、LVS等,硬件如:F5、A10等,硬件负载均衡设备成本较高,大部分采用的是软件方式。架构图如下:
![2ef23a7c276b6c626cd7df72fc8f5326.png](https://i-blog.csdnimg.cn/blog_migrate/e107ad0678001cb6e19b3ac753424388.jpeg)
通过软件或硬件实现负载均衡都会维护一个服务端清单,利用心跳检测等手段进行清单维护,保证清单中都是可以正常访问的服务节点。当用户发送请求时,会先到达负载均衡器(一般作为一个服务),负载均衡器根据负载均衡算法(轮训、随机、加权轮训)从可用的服务端列表中取出一台服务端的地址,接着进行转发,降低系统的压力。
API网关
考虑到微服务架构中服务的数量很多,为了便于服务对外统一的管理,API网关的引入是必不可少的。API网关旨在提供统一的API入口点,来管理多个服务内部API,可方便实现对平台众多服务接口进行管控,如对访问服务的身份认证、业务鉴权、流量并发控制、API调用的计量或计费等。
API网关常用于以下场景:
- 黑白名单:实现例如通过IP地址来禁止访问某些服务的某些功能。
- 日志:实现日志访问的记录,用于分析访问、处理性能指标,并将分析结果提供给其他模块使用,如:运维平台的统计功能。
- 协议适配:实现通信协议校验、适配转换的功能。
- 身份认证:负责外部系统的访问身份认证。
- 计流限流:实现微服务访问流量计算,基于流量计算分析进行限流等。
- 路由:API网关的核心功能,实现请求的转发。
API网关的引入为微服务架构应用带来诸多的好处,如下:
- 避免将内部信息/接口泄露给外部: 能够将对外发布的API与微服务内部的API区分开来,使得各个微服务在添加或变更时,能有明确的安全边界,避免多过的对外暴露。
- 为微服务添加额外的安全层:能够提供一套额外的保护层,用以应对SQL注入、Dos攻击等,其中系统的权限控制可以再这一层来实施。
- 支持多种混合通信协议:考虑到微服务架构中,各个微服务的平台与语言的多样性,通常将对外提供基于HTTP或REST的API接口,而内部微服务将根据自身服务情况采用不同的通信协议(如:ProtoBuf、RPC等)。API网关则跨域这些内部不同协议的微服务,提供一个基于REST的统一外部API。
- 减低构建微服务的复杂性:基于微服务架构应用的复杂性,如API令牌、访问控制、限速限流等,每一项功能的添加,对会额外对各个服务带来影响,从而影响微服务的开发周期。这些功能如果在API网关上统一处理,则会从代码层面进行了有效的隔离,使得不会影响其他微服务,这样更有利于其他微服务只需关注于实际的业务开发。
常见的API网关实现方式很多,如:Nginx、Kong、Spring Cloud Zuul、Træfɪk等。
服务部署与发布
单体应用被拆分为微服务后,随着微服务的数量增多,部署就成了问题,使得部署的复杂性提高了不少。所以,微服务的部署更加倾向于使用具有相互之间隔离的主机/虚拟机来实现服务的部署,使得服务能够独立的部署、测试、发布、升级。
目前比较好的服务部署方式就是把各个微服务打包成Docker镜像,这样就保障避免了不同主机环境对部署产生的影响。使用Docker部署,并结合Jenkins进行CI/CD,使得构建、发布、启动变得更加快捷。
下图就是服务部署、发布流程。
![d995d6526167130bf78ecaf480edc698.png](https://i-blog.csdnimg.cn/blog_migrate/668eff9920a9dc51f5106719baee2f96.jpeg)
总结
一个微服务架构的应用,从最初的设计到逐步成型,是需要经过不断的迭代开发、摸索来完善的,上述只是列举出了我个人认为需要重点关注的点,以供大家参考。