搜索 | 会员  
从0到千万级用户亿级请求微服务架构历程
来源: InforQ   作者:潘志伟  日期:2020/4/3  类别:架构设计  主题:综合  编辑:沉吟至今
单体应用因其架构简单、使用技术门槛低、研发快速上手、项目快速上线等特点是创业公司初级阶段的必然产物。随着平台用户规模的递增,产品功能的丰富以及需求迭代的频率也会加速,相对应的研发人

单体应用因其架构简单、使用技术门槛低、研发快速上手、项目快速上线等特点是创业公司初级阶段的必然产物。随着平台用户规模的递增,产品功能的丰富以及需求迭代的频率也会加速,相对应的研发人数也逐步递增,系统的性能问题、研发人员之间的协作问题、交付速度等一系列的问题就慢慢凸显,这些问题会逐步演化成阻碍项目推进的“绊脚石”。此时微服务的出现似乎是一根救命稻草,但凡遇到系统性能、项目交付质量、项目进度等问题的时候就开始准备系统重构,认为往微服务方向转型就一定能解决这些面临的问题。那么一个在企业在单体应用架构中到底如何转型微服务呢?在转型之前还需要去了解下实施微服务的一些前置条件。

微服务实施的前置条件

很多技术人员在听到企业技术架构要转型,打算从单体架构往微服务架构转型,得知消息后就异常的兴奋,认为自己马上又能学到新的技术了,开始去关注到底是选型哪种技术架构,并运行框架提供的 Demo,认为成功运行 Demo 就具备了实施微服务的条件了,等待公司一声令下,就踏上微服务之旅了。其实这是一种典型的技术人员考虑事情的思维,通过过往的经验来看,更重要的是在实施微服务之前全员统一思想、充分培训、 以及工程结构标准化。

统一思想:因为在准备实施微服务的时候,首要条件就是获得高层的认可,因为涉及到组织结构的调整以及后续人力资源的增补,另外在新架构上线后难免会出现问题,这个时候需要得到高层的支持。另外,在单体应用中其组织机构包括开发部、测试部、运维部、DBA 部,每个部门各司其职由高层统一指挥,看似很非常合理的组织结构,但是在项目或者迭代实际过程中会花费大量的时间去跨部门沟通,形成了孤岛式功能团队。

充分培训:微服务架构的开发人员具备“精”、“气”、“神”的特质,否则在后续发展阶段一定会出现各种难题。“精”是指熟悉业务,熟悉选型的开发框架,而不仅限完成 demo 运行,必须要熟悉原理,最好能熟悉源码,做到面对问题不慌,“气”是指大家对微服务架构这件事情的思想认知一致,能够在一个频道上对话,“神”是指需要了解其理论知识,比如什么是服务治理,什么是服务自治原则,明白为什么需要这样而不是那样。

工程结构标准化:所有服务交付服务,从代码风格比如类的命名,module 命名以及启动方式都是一致的,减少研发人员对于未知的未知而产生的担心。

在正式开始微服务之前,有必要了解下云原生的 12 要素,它针对微服务的一些设计思想做了充分的归纳和总结,云原生 12 要素的内容如下:

  • 基准代码:一份基准代码(Codebase),多份部署(deploy)

  • 依赖:显式声明依赖关系

  • 配置:在环境中存储配置

  • 后端服务:把后端服务 (backing services) 当作附加资源

  • 构建、发布、运行:严格分离构建和运行

  • 进程:以一个或多个无状态进程运行应用

  • 端口绑定:通过端口绑定 (Port binding) 来提供服务

  • 并发:通过进程模型进行扩展

  • 易处理(快速启动和优雅终止可最大化健壮性)

  • 环境等价:开发环境与线上环境等价

  • 日志

  • 管理进程

其中第一条需要特别注意,它要求基准代码或者软件制品只允许有一份,可以部署到多个环境,不因为环境的改变而需要重新编译或者针对不同的环境编译多个制品。记住,测试即交付的原则,即你测试的软件制品和交付到生产的软件制品是一样的。重点强调环境配置和制品库分离,如果是测试环境的配置,那么软件运行起来就是测试环境,如果是生产环境的配置,那么软件运行起来就是生产环境。发现很多程序员都喜欢把配置文件写到工程里面,例如 application-dev.properties、application-test.properties、application-prod.properties 等,然后在系统启动的时候增加 spring.profiles.active=dev 来说明环境,其实这种做法是非常的不优雅,首先违背了云原生 12 要素第一个条件,其次测试环境、生成环境的所有的配置信息都暴露在代码中,容易导致信息泄露,最后增加运维部署难度,一旦环境变量标识错误就会导致软件运行失败。

image.png

总结为三条:切实需要使用微服务来解决实际问题;组织结构思想认知一致;前期有完善系统性针对微服务的培训。

微服务实施的具体步骤

有些人认为使用 Dubbo 或者 SpringCloud 把系统内部接口调用换成 RPC 或者 Rest 调用,就完成了微服务改造了,其实这是只是微服务的冰山一角,完整的去实施微服务必须从全局考虑统一规划,包括前后端分离,服务无状态、统一认证以及运维体系的调整等。

前后端分离:是指前端和后端的代码分离,前端负责 HTML 页面的编写以及逻辑跳转,后端负责提供数据接口给前端,前后端开发人员可以并行开发。前端对跳转逻辑和 UI 交互负责,后端对接口的高可用负责。前端 html 层使用 VUE 框架,node.js 可以起到逻辑跳转的控制,前后端通信采用 rest 方式,json 数据格式通信。前后端分离后的好处总结来说包含如下:

  • 各端的专家来对各自的领域进行优化,以满足用户体验优化效果最优化;

  • 前后端交互界面更加清晰,采用接口方式通信,后端的接口简洁明了,更容易维护;

  • 前端多渠道集成场景更容易扩展,采用统一的数据和模型,可以支撑前端的 web UI\ 移动 App 等访问,后端服务不需要改动;

image.png

统一认证:统一认证与授权是开始实施服务化的最基础条件,也是最基础的一项应用。在过去的单体应用中,可以基于拦截器和 Session 实现基本的登录与鉴权。在微服务架构中,服务都被设计为无状态模式,显然拦截器和 Session 模式已经不符合架构要求了,需要由统一认证服务来完成认证鉴权以及和第三方联合登陆的要求,我们使用 token 机制来做统一认证,主要流程如下:

  • 用户输入用户名和密码提交给认证服务鉴权;

  • 认证服务验证通过后生成 token 存入分布式存储;

  • 把生成的 token 返回给调用方;

  • 调用方每次请求中携带 token,业务系统拿到 token 请求认证服务;

  • 认证服务通过后业务系统处理业务逻辑并返回最终结果。

image.png

完成前后端分离,服务无状态改造、统一认证处理后, 基本上完成了微服务轮廓的改造。接下来就需要去识别单体应用最需要改造成微服务的模块,推荐是一个模块甚至一个接口这样的进度去拆分单体应用,而不建议一次性全部重构完毕。

服务拆分理论和原理及方法

谈到微服务,议论的最多,吵架的最多的就是服务拆分问题,服务拆分是否合理直接影响到微服务架构的复杂性、稳定性以及可扩展性。然而并没有任何一本书籍或者规范来介绍如何拆分服务,那么如何正确的做服务的拆分? 目前各家做法也都是根据架构师经验以及业务形态和用户规模等因素综合考虑。在工作中曾经遇到以下二种服务拆分的模式:

  • 一个方法一个服务:视业务规模和业务场景而定;

  • 基于代码行数的划分:简单粗暴,不推荐;

有人说按方法拆分服务太过于细致,应该要按业务功能来拆。其实当业务达到一定规模的时候,按方法拆分是一种非常有效的做法,以用户服务举例,在初始阶段的时候,用户服务具备了用户的增删改查功能,当用户规模上升之后需要对增删改查功能做优先级划分。大家都知道在互联网中流量获客是最贵的,运营团队通过互联网投放广告获客,用户在广告页上填写手机号码执行注册过程,如果此时注册失败或者注册过程响应时间过长,那么这个客户就可能流失了,但是广告的点击费用产生了,无形中形成了资源的浪费。所以此时需要按方法维度来拆分服务,把用户服务拆分为用户注册服务(只有注册功能),用户基础服务(修改、查询用户信息)。

在做服务拆分的时候,每个服务的团队人数规模也是非常重要的,人数过多可能会变成单体应用,沟通决策会缓慢,人数太少工作效率又会降低,一般来说会遵循 2 个披萨原则和康威定律:

  • 2 个披萨原则:两个披萨原则最早是由亚马逊 CEO 贝索斯提出的,他认为如果两个披萨不足以喂饱一个项目团队,那么这个团队可能就显得太大了,所以一个服务的人数划分为 5-7 人比较合适。因为人数过多的项目将不利于决策的形成,而让一个小团队在一起做项目、开会讨论,则更有利于达成共识,并能够有效促进企业内部的创新。

  • 康威定律:你想要架构成为什么样,就将团队分成怎样的结构。比如前后端分离的团队,架构就是基于前后端分离。在基于微服务设计的团队里,一个很好的理念是自管理,团队内部对于自己所负责的模块高度负责,进行端对端的开发以及运维。

整个单体应用有那么多的功能,到底哪些业务功能需要拆分,哪些业务功能又不需要拆分呢?可以遵循服务拆分的方法论:当一块业务不依赖或极少依赖其它服务,有独立的业务语义,为超过 2 个或以上的其他服务或客户端提供数据,应该被拆分成一个独立的服务模,而且拆分的服务要具备高内聚低耦合。

image.png

关于服务拆分模式,使用比较多的是业务功能分解模式和数据库模式,因为容易理解而且使用起来比较简单,效果也很好。

业务功能分解模式:判断一个服务拆分的好坏,就看微服务拆分完成后是否具备服务的自治原则,如果把复杂单体应用改造成一个一个松耦合式微服务,那么按照业务功能进行分解是最简单的,只需把业务功能相似的模块聚集在一起。比如:

  • 用户管理:管理用户相关的信息,例如注册、修改、注销或查询、统计等。

  • 商品管理:管理商品的相关信息。

  • 数据库模式:在微服务架构中,每个服务分配一套单独的数据库是非常理想方案,这样就缓解了单个数据库的压力,也不会因为某个数据库的问题而导致整个系统出现问题。

微服务初始阶段服务拆分不需要太细,等到业务发展起来后可以再根据子域方式来拆分,把独立的服务再拆分成更小的服务,最后到接口级别服务。如果服务拆分的过小会导致调用链过长,以及引发没有必要的分布式事务,此时阶段性的合并非常重要。做为架构师不仅要学会拆分服务,也需要学会合并服务,需要周期性的去把拆分过小或者拆分不合理的服务要及时合并。

总得来说,在服务拆分的时候需要抓住以下重点:

  • 高内聚的拆分模式

  • 以业务为模块拆分

  • 以迭代频率和改动范围拆分

  • 阶段性合并

  • 定期复盘总结

代码结构如何做到提高研发效率

曾经有一项调查,当一个程序员到新公司或者接手项目最怕的事情是什么,超过 90% 的人的都认为最怕接手其他人的项目。从心理学角度来看,这个结果非常正常,害怕是因为对即将接手项目的未知,不清楚项目如何启动,不清楚代码是如何分层。大家试想看,当一个单体应用被划分为 N 多个服务的时候,每个服务启动方式,代码层次各不相同,如何去维护呢?所以微服务启动阶段,首先要做的事情就是工程结构标准化和自动化,让研发人员的重点精力去做业务,而不是去搭建框架。因此基于 velocity 自定义了一套微服务代码自动生成框架,研发人员设计好表结构之后,框架根据表结构自动生成服务代码,包含 API 接口,实现类,DAO 层以及 Mybatis 的配置文件,类的名称,包名、module 名称都有严格的定义。以用户服务为例,生成后的代码格式如下:

basics-userservice



basics-userservice-api


basics-userservice-business


basics-userservice-façade


basics-userservice-model


basics-userservice-service

为了让研发效率更快,架构更清晰,又从架构层面把代码再拆分为聚合服务层和原子服务层,每一层对应的功能不一样。

  • 聚合层:收到终端请求后,聚合多个原子服务数据,按接口要求把聚合后的数据返回给终端,需要注意点是聚合层不会和数据库交互;

  • 原子服务层:数据库交互,实现数据的增删改查,结合缓存和工具保障服务的高响应;要遵循单表原则,禁止 2 张以上的表做 jion 查询,如有分库分表,那么对外要屏蔽具体规则。

需要说明的是,聚合层和业务比较贴近,需要了解业务更好的服务业务,和 App 端交互非常多,重点是合理设计的前后端接口,减少 App 和后端交互次数。原子服务则是关注性能,屏蔽数据库操作,屏蔽分库分表等操作。

image.png

后还得提下系统日志,日志记录的详细程度直接关系到系统在出现问题时定位的速度, 同时也可以通过对记录日志观察和分析统计,提前发现系统可能的风险,避免线上事故的发生。对于服务端开发人员来说,线上日志的监控尤其重要,能够通过日志第一时间发现线上问题并及时解决。然而通过观察收集后的日志信息内容的时候才发现日志规范这块内容一直都没有重视过,记日志永远看心情,日志记录的内容也是凭感觉。因此在实施微服务的之前,必须要先确定日志的规范,为了便于后面的日志采集、处理和分析。例如统一约定日志格式如下:

  • 时间|事件名称|traceID|耗时时间|用户 ID|设备唯一标识|设备类型|App 版本|访问 IP|自定义参数

  • 时间:日志产生时候系统的当前时间,格式为 YYYY-MM-DD HH:MM:SS;

  • 事件名称:预先定义好的枚举值,例如 Login、Logout、search 等;

  • TraceID:当前请求的唯一标识符;

  • 耗时时间:当前事件执行完成所消耗的时间;

  • 用户 ID:当前登陆用户的唯一 ID,非登陆用户为空;

  • 设备唯一标识:当前设备的唯一标识,假如某用户登录前开始操作 App,这个时间记录下设置唯一标识后,可以通该标识关联到具体用户;

  • 设备类型:当前设备的类型,如 Android 或者 iOS;

  • App 版本:当前访问设置的 App 版本号;

  • 访问 IP:当前设备所在 IP 地址;

  • 自定义参数用:自定义参数,参数之间使用 & 分割,例如 pid=108&ptag=65

工程结构、代码框架和日志在开发过程中最容易被忽略的,但却非常的重要,前期合理的规划有助于规模化推广的时候减轻压力,在规划阶段要重点关注以下内容:

  • 代码未编工具先行;

  • 统一微服务工程结构;

  • 统一服务启动方式(jar war);

  • <p

德仔网尊重行业规范,每篇文章都注明有明确的作者和来源;德仔网的原创文章,请转载时务必注明文章作者和来源:德仔网;
头条那些事
大家在关注
我们的推荐
也许感兴趣的
干货
了解一下吧