Spring Cloud Hystrix 在我们的微服务治理中扮演了重要角色,我们对它做了二次开发,提供更灵活的故障隔离、降级和熔断策略,满足 API 网关等服务的特殊业务需求。进程内的故障隔离仅是服务治理的一方面,另一方面,在一个应用混部的主机上,应用间应该互相隔离,避免进程间互抢资源,影响业务 SLA。比如绝对要避免一个离线应用失控占用了大量 CPU,使得同主机的在线应用受影响。我们通过 K8S 限制了容器运行时的资源配额(以 CPU 和内存限制为主),实现了进程间的故障和异常隔离。K8S 提供的集群容错、高可用、进程隔离,配合 Spring Cloud Hystrix 提供的故障隔离和熔断,能够很好地实践 “Design for Failure” 设计哲学。
服务拆分的好坏直接影响了实施微服务架构的收益大小。服务拆分的难点往往在于业务边界不清晰、历史遗留系统改造难、数据一致性问题、康威定律等。从我们经验来看,对于前两个问题解决思路是一样的:1)只拆有确定边界能独立的业务。2)服务拆分本质上是数据模型的拆分,上层应用经得起倒腾,底层数据模型经不起倒腾。对于边界模糊的业务,即使要拆,只拆应用不拆数据库。
以下是我们从主工程里平滑拆出用户服务的示例步骤:
将用户相关的 UserService、UserDAO 分离出主工程,加上 UserController、UserDTO 等,形成用户服务,对外暴露 HTTP RESTful API。
将主工程用户相关的 UserService 类替换成 UserFaçade 类,采用 Spring Cloud Feign 的注解,调用用户服务 API。
主工程所有依赖 UserServce 接口的地方,改为依赖 UserFaçade 接口,平滑过渡。
经过以上三个步骤, 用户服务独立成一个微服务,而整个系统代码的复杂性几乎没有增加。
数据一致性问题在分布式系统中普遍存在,微服务架构下会将问题放大,这也从另一个角度说明合理拆分业务的重要性。我们碰到的大部分数据一致性场景都是可以接受最终一致的。“定时任务重试+幂等” 是解决这类问题的一把瑞士军刀,为此我们开发了一套独立于具体业务的 “分布式定时任务+可靠事件” 处理框架,将任何需保证数据最终一致的操作定义为一种事件,比如用户初始化、实例重建、资源回收、日志索引等业务场景。以用户初始化为例,注册一个用户后,必须对其进行初始化,初始化过程是一个耗时的异步操作,包含租户初始化、网络初始化、配额初始化等等,这需要协调不同的系统来完成。我们将初始化定义为一种 initTenant 事件,将 initTenant 事件及上下文存入可靠事件表,由分布式定时任务触发事件执行,执行成功后,清除该事件记录;如果执行失败,则定时任务系统会再次触发执行。对于某些实时性要求较高的场景,则可以先触发一次事件处理,再将事件存入可靠事件表。对于每个事件处理器来说,要在实现上确保支持幂等执行,实现幂等执行有多种方式,我们有用到布尔型状态位,有用到 UUID 做去重处理,也有用到基于版本号做 CAS。这里不展开说了。
从我们的实践经验来看,当业务边界与组织架构冲突时,宁愿选择更加符合组织架构的服务拆分边界。这也是一种符合康威定律的做法。康威定律说,系统架构等同于组织的沟通结构。组织架构会在潜移默化中约束软件系统架构的形态。违背康威定律,非常容易出现系统设计盲区,出现 “两不管” 互相推脱的局面,我们在团队间、团队内都碰到过这种情况。
本文是《网易容器云平台的微服务化实践》系列文章的第一篇,介绍了容器技术和微服务架构的关系,我们做容器云平台的目的,以及简单介绍了网易云容器服务基于 Kubernetes 和 Spring Cloud 的微服务化实践经验。限于篇幅,有些微服务架构要点并未展开,比如服务通信、服务发现和治理、配置管理等话题;有些未提及,比如分布式调用跟踪、CI/CD、微服务测试等话题,这些方面的实践经验会在后续的系列文章中再做分享。实践微服务架构的方式有千万种,我们探索并实践了其中的一种可能性,希望可以给大家一个参考。