微服务≠高逼格!一文解构微服务拆分全过程

360运维开发团队 2018-03-30 11:41:34

团队介绍

360运维开发团队,作为一支技术导向型团队,为HULK云平台在容器化、微服务、AIOPS、自动化运维等领域积累了丰富经验。更多技术好文欢迎访问团队技术博客:www.opsdev.cn

最近笔者参与了两个项目的开发,两个项目都有多组件、服务功能清晰等特点,也就是我们说的微服务。再结合之前的一些单体项目的开发经验,本文主要探讨笔者所理解的微服务和单体项目的优缺点,并浅谈微服务拆分的那些事儿。

其实这些服务的拆分与否都与很多因素有关系,比如:该项目的开发人员数目、该项目的运维敏感度、项目的紧急程度、开发人员的技术熟练程度,和微服务架构的基础储备程度等。

 

毕竟我们最终的目的是将项目快速完整地实现,而不是为了显得逼格高而做微服务,也不是因为人多就每人分点活而故意拆成微服务。总之一句话,不能为了“微服务”而微服务。

 

比如该项目总工就一个人开发,然后你重启一下服务对用户接入并没有什么敏感性,本来就是个没有并发的对内系统,那就完全没必要拆成微服务,就写个单体的,把接入层、数据处理层等通过模块化的代码方式拆开就行,没必要增加复杂度,加上一些RPC,把一个单体服务拆成四五个微服务,然后增加自己的运维成本和实现成本。

 

如果该项目是多人协作的,有一定的并发度,对用户接入比较敏感,不能随便重启,并且开发者都对微服务有一定的经验,底层RPC、连接池等初始化库都有积累,还有比较丰富的RPC多服务运维经验,那就可以拆微服务。拆完之后,服务的水平扩展一般是线性的,可以动态地根据流量扩容和缩容。

 

底层其他非接入层的服务,比如数据处理服务、session服务的重启与上线都不会影响整个项目的接入,同时还提高了该项目的容错性。整体项目的开发进度都能以服务为维度,各自负责一个服务,快速迭代与滚动更新上线。而不像单体应用,一般上线与迭代总是牵一发而动全身。
 

接下来,从以下几个维度来对比微服务和单体应用的优缺点:

 


接下来根据一个具体的项目实例,看看如何将一个单体项目拆分成微服务,这个项目是笔者前两天刚做的一个共享积分项目。

 

项目背景

 

该项目主要工作两部分:一部分是矿机上报信息给服务端,另一部分是服务端根据矿机上报信息计算分配换算成工作量,然后按工作量百分比分配相应的积分。同时该项目对接别的用户系统和boss系统等等。

 

下面是该项目的简单架构图:

 

该项目总共三人:三人都参与过微服务的开发,有基础库的储备。时间比较紧,仅一周的开发周期。

 

一开始,其实该项目的复杂度不是太高,完全可以做成一个单体应用,比如项目目录:SRC下,一个collector目录收集矿机上报,一个finance目录处理一些金融数据。再来一个API目录处理接入请求就行了,外加一些util的公共组件。

 

然后一个人开发就行了,两周的时间应该能调通。

 

但前面也提过,单体应用有诸多弊端。而且团队还剩下两个人,你一个人用两周的时间开发,那如果拆成微服务,而三个人都有相关的经验,那一个星期就可以保质保量地完成相应组件的开发与测试了。

 

因为微服务的每个组件都是一个独立的单体应用。组件之间的开发是没有关联的,都是依赖公共库。互相之间的调用都是通过RPC,所以开发是可以并行开发、互不干扰,效率肯定是非常高的。

 

现在,我们就开始简单地将这个单体应用拆成微服务。

 

第一步:根据服务职责拆分

 

其实微服务的拆分最根本是一些代码职责的拆分和抽象,这一步和我们模块化的时候思路是一样的。

 

比如该项目的矿机在不断地上报数据,然后我们通过上报的带宽给分配积分额度。细分其中的职责可以看到,我们需要一个收集上报数据的模块,只负责收集数据。这里抽象了各种数据来源,比如矿机的、其他业务接口获取的,都统一到collect模块,职责非常明确。

 

同时这些数据收集上来以后得集中运算,算完之后得通过内部分配算法,给矿机分配积分额度。这其实是一个类似经济系统的职责,该系统只负责处理金融信息,是个Finance经济系统,职责也很明确。

 

这两个模块对外暴露各种API接口。这个可以抽出来单独接入组件,负责对外统一处理所有的API请求,所以单独起一个Center组件。

 

这个项目相对功能不复杂,所以拆分完也就三个组件,职责已经比较明确了。

 

第二步:公共库的初始化

 

我们把公共的库都放在common里面,这里面包括log、config、errors等基础库,还有Redis,Mongo,MySQL等DB的连接池初始化,还有RPC的连接池初始化,这里或者用GRPC或者用户自己基于Go自带的RPC的二次封装等。此外,还有Trace等用于追踪请求方便日志查询的基本库。

 

这些基础库是我们做微服务的必备。一般在一个新项目时,在前期需求讨论完、编码前期时,我们会先把这些公共库的初始化工作都做了,比如DB的一些连接池初始化不同项目稍微有一些不同。RPC连接池代码基本都是能复用的,其余的公共库直接拖到新项目里就能开始使用。

 

第三步:组件之间接口的定义

 

在初始化完公共库之后,我们先不着急写代码,先把组件之间的接口定义好。比如该项目中的三个组件:Center、Collector和Finance。

 

Center是接入层,这里统一处理所有的API请求。http或者https,具体API接口是业务相关,这里不做描述。接入层可以做很轻量的数据处理,不宜过重,不做有状态的数据存储。它是一个无状态服务,最终的数据处理通过RPC传给后端相应的组件,基本是一个纯转发的组件。

 

此时,配置文件中已经配好了相应的组件的RPC地址,RPC公共库中二次封装了基本的请求逻辑优先级:第一同机器、第二同机房、第三跨机房。

 

Collect和Finance组件都对Center暴露了GRPC PB接口,因为请求从center进来,会通过rpc转到相应组件做单独处理。Collect系统和Finance系统之间是单向的数据流动,Finance系统需要从Collect系统获取数据去统一运算,所以Collect系统还需要给Finance系统暴露RPC接口。

 

具体的GRPC PB接口有自己的定义语言,比较简单,容易上手,在相应的目录下新建proto文件,定义完接口后让其自动生成pb.go即可。最后代码交互都是走的pb.go里面的结构体,具体的pb编写参考文档即可。

 

第四步:开始分工写自己的组件

 

现在就开始编写代码了,每个人都是相对独立的开发。因为接口定义好了,公共库也都初始化完毕,接着开发就是完全并行了。编写完代码后,自己的组件可以依靠单元测试来做一些基本的测试,再等联调即可。

以上只是一个相对比较简单的项目拆分,本文主要说的是微服务拆分的思路和具体实现微服务的基本工程流程。

 

如果项目比较复杂,可能拆分出来的组件数目就相对较多。本文也主要是聊聊笔者在拆分时的一些理解原则和具体实现细节。其实微服务还有很多很有意思的地方,比如公共库中一些DB的连接池初始化和RPC的连接池初始化、配置的集中管理、动态加载等,把它们单独抽出来研究都是挺有意义的。

活动预告