图4(最小要素闭环)
(3)整体拆分示意
早期的帐号主服务包含了帐号登录、注册、凭证校验、用户资料查询/修改等流程。如果需要对服务进行拆分,我们应该首先梳理核心流程。按照上面图4的示意,我们应该先完成登录、注册、凭证校验与用户资料的拆分。用户资料主要包含昵称、头像等扩展信息,不包括帐号主体的四个要素(用户名、密码、邮箱、手机号)。
对于登录、注册、凭证校验这三个行为,随着已注册用户数量的增加,登录和凭证校验的频度远远超过注册。因此,我们进行了二次拆分,将登录和凭证校验拆分为一个服务,将注册拆分为另一个服务。拆分后的结构如下图所示(图5)。
图5
(4)业务价值变化
业务价值是动态变化的,因此我们需要根据业务的变化来适时地调整服务拆分的结构。实践案例有帐号信息服务中实名模块的拆分。早期实名信息只是用在评论场景中,因此其价值和昵称、头像等信息区别不大。但随着游戏业务深度开展以及国家防沉迷的要求,如果用户未实名认证,则无法提供相关服务。实名信息对于游戏业务的重要性等同于凭证校验。因此,我们将实名模块拆分为独立的服务,以便更好地支持业务的发展和变化。
2.1.3 拆分实施方案
在对成熟业务进行服务拆分时,稳定性是关键。必须确保对业务没有任何影响,并且用户无感知。为了降低拆分实施的难度,我们会采取先拆服务(图6),再拆数据的方案。在服务拆分时,为了进一步降低风险,可以考虑下面两点做法:
- 服务拆分阶段,只做代码迁移,不做代码重构
- 引入灰度能力,通过可控的流量进行梯度验证
图6
需要再次强调灰度的重要性,用可控的流量去验证拆分后的服务。这边介绍两种灰度实现思路:
- 在应用层中做转发,具体处理细节:为新服务申请一个内网域名,在原有服务内进行拦截实现请求转发的逻辑。
- 在架构的更加前置的环节,完成流量分配。例如:在入口网关层或反向代理层(如Nginx)进行流量转发配置。
2.2 关系治理
服务之间的依赖关系对于服务架构来说是至关重要的。为了使服务间的依赖关系清晰、明确,我们可以采用以下几个优化措施:首先服务之间的依赖关系应该是层次化的。每个服务应该处于一个特定的层次,依赖关系应该是层次化的,避免跨层级的依赖关系。其次依赖应该是单向的,要符合ADP(Acyclic Dependencies Principle)无依赖环原则。
2.2.1 ADP原则
ADP(Acyclic Dependencies Principle)无依赖环原则,下图(图7)中红色线标识出来的依赖关系都是违背了ADP原则的存在。这种关系会影响“部署独立”的目标达成。试想下A、B服务互相依赖的场景,一次需求需同时对A、B相互依赖的接口改造,发版顺序应该是被依赖的先部署,相互依赖就进入了死循环。
图7
2.2.2 关系处理
在服务架构中,服务之间的关系可以根据依赖的强度分为弱依赖和强依赖。当A服务依赖于B服务时,如果B服务异常故障时,不会影响A服务的业务流程,那么这种依赖关系被称为弱依赖;反之,如果B服务出现故障会导致A服务无法正常工作,那么这种依赖关系被称为强依赖。
(1)强依赖冗余
针对强依赖的关系,我们会采用冗余的策略,去保障核心服务流程的稳定性。在帐号系统中,“一键登录”、“实名认证”都采用了同样的方案。这种方案的实施前提是要能找到提供相同能力的多个服务,其次服务本身需要做一些适配工作,如下图(图8)增加流量分配处理模块,作用是监控依赖服务的质量,动态调整流量分配比例等。