前言
随着架构设计的发展,微服务架构可以说是目前架构领域炙手可热的设计理念。在公司,笔者也一直在负责系统的服务化设计和开发工作。
今天就来谈谈微服务落地实践中的一些问题。希望对微服务设计无从下手的朋友,起到一些参考作用;另外也希望把自己的观点分享出来,期待与大家一起交流,能够认识到不足之处。
一、服务拆分
在落地微服务之前,我们遇到的第一个问题就是:应该如何拆分服务?
大家知道,关于如何拆分服务,并没有一个完全通用的法则。不过有以下几点要素,我们可以参考。
1、业务独立性
一般情况下,我们第一时间都会考虑到这点。将系统中的业务模块,按照职责标识出来,每个单独的模块拆分成一个独立的服务。
这样拆分之后,我们的服务要满足一个原则:高内聚,低耦合。
比如,我们把一个系统拆分为商品服务、订单服务、物流服务。一般情况下,我们修改物流服务的时候,并不会影响商品服务,这就是低耦合的体现;那么订单服务里面的功能和逻辑,都是围绕着订单这个核心业务流程的,就可以说它是高内聚的。
2、业务稳定性
我们可以把系统中的业务按照稳定性进行区分。比如,用户注册、登录部分,在我司的系统中,这一块的代码只要写完,基本就不再会发生变动,那么我就将他们拆为用户服务。
同理,还有日志服务、监控服务,这些模块基本上也都是很稳定的业务,可以考虑单独拆分。
3、业务可靠性
这里讲究的是,将可靠性要求高的核心服务和可靠性要求低的非核心服务拆分开来,然后重点保证核心服务的高可用。
避免由于非核心服务故障,而影响核心服务。
4、业务性能
基于业务性能拆分,考虑的是将性能压力大的模块拆分出来。对于这一点,笔者有两点想法:
- 避免性能压力大的服务影响其他服务。
- 将高流量的业务独立出来,扛不住的情况下,方便水平扩展。
比如,在笔者参与的一个系统中,曾通过 RocketMQ
对接来自多家厂商的大量数据。
当时,就独立出来一个消息服务,专门用来消费消息。然后有的是在本地处理,有的通过 RPC
接口转发到其他服务处理,有的直接通过 WebSocket
推送到前端展示。
这样的话,即便流量激增,考虑给消息服务增加机器,提高消费能力就好了。
当了解到上面这几种拆分方式之后,我们就可以根据自己的业务范围和技术团队规模,来考虑自己系统的服务拆分了。
不过在这里,尤为值得注意的是,微服务切忌拆分的过细,一定要结合业务规模和技术团队的规模。
笔者以前就遇到一个项目,在做服务化的过程中,当时的技术负责人按照业务独立性来拆分服务,结果拆出了10来个服务;然后更狠的是,每个业务服务又把Service
层和Controller
层拆分开来,每个业务方法都需要远程调用。据当事人描述,这样做是为了方便后期提升扩展能力。
不过说实话,有些系统业务量并没有那么大,一味的迎合微服务中的微字,无疑是给自己增加难度,破坏整体系统的稳定性。所以,在重新梳理了业务流程后,笔者对这个系统进行了重构,缩减服务数量。
在这里,笔者想说明一点。Service
层和Controller
层是可以拆分成多个模块的,这个没关系。不过,它只应该是模块的分离,而不是服务的拆分。比如我们可以在开发阶段把它们拆分成多个模块,然后通过 Maven modules
聚合到一块,在部署运行阶段,它们还都是一个服务。
二、技术选型
当完成了服务拆分之后,选用什么框架进行开发呢,应该选择 Dubbo 还是 SpringCloud ?
笔者不想单纯讨论它们的优劣之处,在这里可以就着这个问题分享下笔者的心路历程。
最初进行选型时,笔者选择了 SpringCloud
,毕竟它号称是微服务一站式解决方案。然后就搭建框架,集成各种组件,完成了一些 demo 的开发和测试。
不过,在完成这部分工作后,重新审视整个系统,看到SpringCloud
中,涉及到的Eureka、Ribbon、Feign、Hystrix、Zuul、Config
这些组件。
这时候,我会产生两个疑问:
- 这些组件是不是非用不可 ? 有没有更轻便的系统方案 ?
- 这么多东西,任一环节出了问题,我们是否能hold的住?
笔者对于一站式
这个词有两层理解。第一,它简化了分布式系统基础设施的开发,易于开发;第二,简化的同时,它一定是屏蔽了复杂的配置和实现原理,不易于深入理解它的原理。
作为架构师或者团队技术负责人,我们必须对自己系统涉及到的技术点做到知根知底,或者说,最起码要懂得它们的原理。这样,即便遇到了问题,在 Baidu / Google
不出来结果时,也不会慌。
基于这样一个思路,笔者把目光又转向了Dubbo
。对于笔者来说,对Dubbo
就比较熟悉了,从它的框架本身来说,已经实现了负载均衡和集群容错,调用方式也是基于接口的。相较SpringCloud
而言,不需要再额外引入像Ribbon/Feign
这样的组件。
还有一点,Dubbo
得益于强大的SPI
机制,我们可以非常方便的扩展它。如果业务上有需要,在很多地方都可以对它进行扩展,比如 RPC 协议、集群、注册中心、序列化方式、线程池等。
不过话说回来,Dubbo
只是一个高性能的 RPC 框架,拿它和SpringCloud
相比,更多的还是比较REST和RPC
。不过关于这一点,就一般的项目而已,这点性能差异还不足以一锤定音,最多只是锦上添花而已。
至于其他的组件,比如Hystrix/Zuul/Config/zipkin
等,笔者的观点,还是看业务规模。微服务只是一种设计思想,架构理念,而不是说用到了很多分布式框架才叫微服务。这些框架的产生,只是为了解决微服务系统遇到的问题的。
需知一口吃不成个胖子,在做技术选型时,切记直接对标像阿里、京东、美团这样的大厂经验,一来,也许我们遇不到那样的业务场景;再者,一般公司也没有人家那样的人才储备。毕竟如果线上出了问题,是没人跟你分享损失的。
总的来说,还是结合自己的实际情况,以最稳妥的技术方案,完成业务上的需求。
三、节外生枝
拆分了服务,也完成了技术方案的选型,那就万事大吉,开始撸代码了吗 ? 如果单纯作为开发人员,那确实开撸就行了。如果你是一个系统的负责人,只满足于高屋建瓴,不考虑细节问题,那必然会节外生枝。
1、超时和容错
服务化之后,不同服务之间的调用就是远程调用。远程调用有个最基本的设置,即超时时间。
比如,在Dubbo
中,默认的超时时间是1秒。我们不能单纯的使用默认值或者统一设置成另外的值,为Dubbo
设置超时时间最好是有针对性的。
比如,比较简单的业务可以设置的短一些;但对于复杂业务而言,则需要适当的加长这个时间。因为,这里还涉及到一个集群容错的问题。
在Dubbo
中,集群容错的默认策略是失败重试,次数为2。假如有的业务本身就需要耗费较长的时间来执行,因为超时时间太短就会触发容错机制,来重试。大量的并发重试请求,很可能会占满Dubbo
的线程池,甚至影响后端数据库系统,导致连接被耗尽。
2、容错和幂等性
我们上面说,如果超时时间设置太短,有可能会导致大量请求会不断重试,而导致异常。
这里还隐瞒着另外一个细节,即读请求和写请求。如果是读请求,那么重试无所谓;如果是写请求,我们的接口是否也支持自动重试呢 ? 这就会涉及到接口幂等性问题。
如果写请求的接口,不支持幂等性,那么集群容错就得改为 failfast
,即快速失败。
3、分布式事务
笔者感觉,分布式事务在业界是一个没有彻底解决的技术难题。 没有通用的解决方案,也没有既高效又简便的手段。
虽然如此,但我们也得事先考虑到这一点,不然数据肯定会变成脏乱差。
在考虑解决方案之前,我们需要先看看自己的系统是不是真的追求强一致性;按照BASE
理论,在分布式系统中,允许不同节点在同步的过程存在延时,但是经过一段时间的修复后,能够达到数据的最终一致性。
基于这两个思路,我们才好制定自己的分布式事务方案。
对于要求强一致性的场景,或许可以考虑XA协议,通过二阶段提交或者三阶段提交来保证。
对于要求最终一致性的场景,可以考虑采用 TCC 模式,补偿模式,或者基于消息队列的模式。
比如基于消息队列模式,可以采用 RocketMQ
。它支持事务消息,那么这时候整个流程大概是这样的:
- 通过
RocketMQ
发送事务消息到消息队列; - 消息发送成功,则执行本地事务;
- 如果本地事务执行成功,则提交
RocketMQ
事务消息,提交后对消费者可见; - 如果本地事务执行失败,则删除
RocketMQ
事务消息,消费者不会看到这条消息。
另外,在这里安利下阿里开源的Seata
。目前最新版本是1.1.0,支持多种事务模式,比如 AT、TCC、SAGA 和 XA 事务模式。
笔者有篇文章是基于 Seata 0.7
版本的写的,有兴趣的朋友可以了解下。
SpringBoot+Dubbo+Seata分布式事务实战
4、消息队列
在分布式系统架构中,为了系统间的解耦和异步处理,应对高并发和大流量,消息队列绝对是一大利器。
在使用这一利器前,我们也得考虑下有可能因为消息队列带来的烦恼。
首先需要考虑的就是可用性,如果消息队列不可用,会不会对系统本身造成大量的不可用;
然后,消息会不会丢失呢 ? 如何保证消息可靠性传输呢?比如要考虑消息队列本身的刷盘机制、同步机制;数据发送时的确认和消费后的提交;
然后就是重复消费,如果保证了消息不会丢失,多多少少都可能会有重复消息的问题,这时候就要考虑重复消费有没有问题,即消息幂等性;
还有,消息顺序性问题,你们的业务场景里,是否有消息顺序性问题,如果有这个问题,要么在设计时规避它,要么在消费时保证它的顺序。
5、统一日志
随着微服务的拆分,日志系统也可能会演变为独立的模块。为了查看日志,我们可能需要登录到不同的服务器去一个个查看。
因此,搭建统一的日志处理平台是必然的。我们可以采用 ELK
的解决方案进行日志聚合。
在这里,还需要链路追踪问题。在微服务复杂的链式调用中,会比单体应用更难以定位和追踪问题。
对于这个问题,我们考虑引入分布式调用链,生成一个全局唯一的 TraceID
,通过它把整个调用链串联起来。结合Dubbo框架的话,我们实现自己的Filter
,用来透传这个TraceID
。
具体思路可以参考:SpringBoot+Dubbo集成ELK实战
当然,我们也可以选用一些成熟的开源框架来解决。
总结
本文简单总结了微服务设计和开发过程中,可能会涉及到的一些问题。
以上观点只是一家之言,仅仅是笔者在过去时间里的经验总结。
如果对您有帮助,请点赞鼓励~ 如果您有不同观点,请积极发言,共同交流~