编者的话
微服务是否真的适合小团队这里不多做争辩。但是透过现象看本质,随着产品版本的不断迭代、业务复杂度的提高最终都会导致单体应用越来越庞大,总会超过单体架构的负荷。那么使用微服务分而治之就成为一个不得不面对的问题。所以这么庞大的单体应用拆分出多个小应用也更符合这种分治的思想。虽然这些不是小团队能够考虑到的事情,但是如果能在产品的初期阶段能够规划好产品的架构体系那么在慢慢演变的过程中会越来越顺手,团队的战斗力也打造的越来越强。
一、背景
公司的背景是提供SaaS服务,初期是对于客户的定制开发以及私有化部署。经过两年多的发展,公司产品受到客户的欢迎并且慢慢蜕变到平台化的产品,技术架构也从单体架构到微服务架构迁移。
1、单体应用
起步的时候开发人员只有2、3人,并没有考虑微服务之类的(多余)。但是整体架构采用了SPA式的前后端分离法,单纯的从物理层做区分(认为只要是客户端的就是前端,服务器端的就是后端),这种分法不能满足前后端分离的需求,认为从技术职责上划分才能满足目前我们的使用场景。
由于团队资源有限后端人员往往担任前端的一些开发任务,所以采用了如下职责划分:
前端:负责View和Controller层。
后端:只负责Model层,业务处理/数据等。
优点:可以做url design,我们可以根据场景决定在服务端同步渲染,还是根据view层数据输出json数据,我们还可以根据表现层需求很容易的做Bigpipe,Comet,Socket等等,完全是需求决定使用方式。
缺点:需要前端来写Controller,以Java语言开发为例,需要前端学会Java开发,这样在处理复杂的业务逻辑的产品里双方都有Java 代码方面的重叠。
服务器部署上,采用Nginx代理前端HTML资源,在接收请求时根据路径反向代理到服务端口达到实现业务目的,如下图所示:
采用RESTful Api和Json搭建前后台交互
备注:后台提供一组设计原则和约束条件,接口通过swagger生成文档进行接口测试和联调。
2、开发过程遇到的问题
2.1 半持续集成
由于团队成员之间没有共事过的经历,对于开发模式、质量管控和开发流程都需要重新开始制定。针对这种情况开发初期没有进行引入非常完整的集成测试体系,主要是针对接口进行单元测试(Unit Test)、编写测试用例和人工Review。
2.2 引进Jenkins持续集成工具
Jenkins功能包括:
1、持续的软件版本发布/测试项目。
2、监控外部调用执行的工作。
两种启动方式
第一种:切换到jenkins.war存放的目录,输入如下命令:$ java -jar jenkins.war如果需要修改端口可以使用如下命令:$ java -jar jenkins.jar--httpPort=8081然后在浏览器中输入localhost:8081,localhost可以是本机的ip,也可以是计算机名。就可以打开jenkins。第二种:解压tomcat到某个目录,如/usr/local,进入tomcat下的/bin目录,启动tomcat将jenkins.war文件放入tomcat下的webapps目录下,启动tomcat时,会自动在webapps目录下建立jenkins目录,在地址栏上需要输入localhost:8080/jenkins。
持续集成
执行步骤:
- 开发人员提交代码进入gerrit中;
- Jenkins被触发开始编译代码并执行集成测试;
- 完成后生成测试报告;
- 测试通过再由reviewer进行代码Review;
在单体应用时代这样的CI架构已经足够好用,由于有集成测试的覆盖,在保持API兼容性的前提下进行代码重构都将变得更有信心。
三、微服务时代
1 服务拆分原则
“独立,独立,再独立?”先忘记这句话吧!想象是美好的,显示却很骨感。以下拆分方案可以给大家一点参考,有好的经验也可以留言分享。
1.1 公共库的初始化拆分
我们把公共的库都放在common里面,这里面包括了log,config,errors等基础库,还有redis,mysql,mongodb等db的连接池初始化,还有rpc的连接池初始化,这里或者用grpc或者用户自己基于go自带的rpc的二次封装等。还有trace等用于追踪请求方便日志查询的基本库。
这些基础库是我们做微服务的必备,在搭建一个新项目的时候,前期需求讨论评审完成后,编码前期,我们会先把这些公共库的初始化工作都做了,比如db的一些连接池初始化不同项目稍微有一些不同。rpc等连接池代码基本都是能复用的。其他的公共库,直接拖到新项目里就能开搞,大大的规范了代码质量和减少开发周期。
1.2 根据业务职责拆分
其实微服务的拆分最根本是一些代码职责的拆分和抽象,这一步和我们模块化的时候思路是一样的。我们将按照要开发的业务功能拆分为不同的项目,而负责不同功能的研发人员就可以在自己的代码项目上进行开发,从而解决了大家无法在开发阶段并行开发的苦恼。
如上图所示,可以拆分为用户中心、产品中心和订单中心等,支撑整体业务的基础服务业可以相应的进行拆分。
1.3 各组件之间接口的定义(重点讲)
公共库和各个业务职责搞清楚后,接下来还不要着急写代码,我们先把各个组件之间接口定义清楚。不然各自写各自的,最后还是一团乱麻。
先做到以下几点:
1.3.1 接口协议选型。
- 现在微服务流行采用的http协议restful接口(语言无关)。
- RMI远程接口调用(Java语言支持)。
- 大数据传递采用文件离线下载的方式(FTP)。
- 状态数据(如果进度条)放在Redis中共享缓存。
- 数据库共享。一般来说微服务的数据库是隔离的,不同微服务不允许直接访问彼此的数据库。如涉及大数据有性能问题时可特殊考虑。
备注:如果为防止数据被人抓包分析,需要有相对应的加解密处理方案。
1.3.2 定义接口内容。
接口内容包含接口名称(url)、输入参数、返回值、错误码。一个典型的restful接口内容定义如下:
备注:code:100代表成功,message:描述说明,result:返回详细信息可以一般是json格式
1.3.3 明确接口性能。
接口性能包括:接口单次响应时间、单次查询返回记录条数和每秒支持调用次数等。数据量比较大的查询接口一把都会设计成分页查询,需要定义好单次查询返回的最大记录条数。
微服务接口的支撑能力是有限的,必须定义好单位时间内允许的最大请求次数(超过请求次数就不响应或者返回错误码),否则海量的请求一下涌过来,服务就挂了。对于特殊的服务,还会根据实际情况设定一天的请求次数上限等。
1.3.4 做好接口管理。
上面说了要做那么多事情那么能不能用好并且是持续的用好,接口管理是非常重要的。
接口管理包括:接口版本管理、接口权限管理、接口管控等;
为什么要做接口版本管理?
大家都知道同一个接口随着产品的不断迭代、需求的不断变更都可能面临接口升级(不管是新增还是兼容)。但是不管怎么升级都需要兼容旧的业务使用,为了管理好就需要通过版本号来解决。例如:在URL中带上不同的版本号,需要使用新特性的可以调用新版本的接口;不使用新特性的可以仍然沿用旧版本接口。
为什么要做接口权限管理?
对外发布的接口都需要做权限控制,未授权的服务是不允许访问的。可以采用在接口的header参数中加上加密的token作为权限认证。
后续当整个系统越来越庞大复杂后,各微服务发布的接口需要做可视化管理,包含服务的注册、发布、调用、下线都需要在统一的运维平台上操作。
为什么要做接口管控?
前面提到的接口版本管理对接口不同版本的兼容处理是不可能不限支持下去的。发布旧版本接口的下线公告到期后,如果在监控平台上发现旧版本接口已无人调用就可以下线了,如果还有人调用则可以通知他进行限期整改。
1.4 开始分工写自己的组件
这样就可以分配任务编写代码啦。每个开发人员都是相对独立的开发,并且接口也定义好了,公共库也都已经初始化完毕,然后开发就是完全并行了。编写完之后,自己的组件可以依靠单元测试,做一些基本的测试。然后联调即可。
2 技术框架选择
来看一下经典的微服务架构图:
主要包含11大核心组件,分别是:
核心支撑组件
- 服务网关Zuul
- 服务注册发现Eureka+Ribbon
- 服务配置中心Apollo
- 认证授权中心Spring Security OAuth
- 服务框架Spring MVC/Boot
监控反馈组件
- 数据总线Kafka
- 日志监控ELK
- 调用链监控CAT
- Metrics监控KairosDB
- 健康检查和告警ZMon
- 限流熔断和流聚合Hystrix/Turbine
看到这里是不是想说:“尼玛,这是几个人小团队能玩起来的?确定不是让我们996 变 007 吗?”,我也想说我们也不是用的这个,一般是给别人吹牛用的,哈哈哈哈!
我们来看下面这张技术架构图,大家会好理解许多。
这张图大部分业务都通用,根据团队自身情况去选择技术来满足业务需求。
四、总结
还是那句话,技术栈没有好坏之分,只有适合一说。本文推荐的技术栈主要基于我个人的实践和总结,但是未必适合所有场景,毕竟每个企业的上下文各不相同。作为架构师你可以参考我推荐的技术栈,但不可拘泥照搬,你必须在深入理解分布系统原理的基础上,再结合企业实际场景灵活应用。
在整个互联网基础技术平台体系中,还有消息,任务,数据访问层,发布系统,容器云平台,分布式事务,分布式一致性,测试,CI/CD等其它重要主题,请大家持续关注“IT实战联盟”公众号。
最后送上整理的业务架构图,希望对大家有所帮助!
贡献者
- IT实战联盟-Line