同程微服务从1到1w的旅程



内容来源:2017 年 9 月 9 日,前同程艺龙架构师谢康在“ArchData技术大会上海站”进行《同程微服务从1到1w的旅程》演讲分享。IT 大咖说(微信id:itdakashuo)作为独家视频合作方,经主办方和讲者审阅授权发布。

阅读字数:4418 | 12分钟阅读

嘉宾演讲视频回放及PPTt.cn/RgbsbVv

摘要

本次演讲主要介绍同程微服务的演化进程,如何通过各方面的升级让微服务架构趋于完善,以及在大规模微服务化过程中总结的一些经验教训。

同程微服务起源

在12年到14年之间因与其他同行的激烈竞争,导致我们的系统变的臃肿不堪,因此迫切需要一个新的服务化平台。这个平台需要具备这几点要求,首先要简洁、高效、可靠,其实能够跨语言、跨平台,最后还要能够快速实现,对开发者友好。

为了实现这些要求,我们开始研究关于微服务的相关方案,最终实现了上图这样的服务系统。整个架构简单来说就是一套协议,两个容器,三个服务

因为微服务比较推崇轻量级的通信协议,所以我们就选择了HTTP2,基于该协议提供两个平台。由于时间和人力上的关系,要想做所有的平台还是不太现实,因此我们决定先搞定最核心的两个平台,分别是基于.Net和Java的轻量级容器。在此之上就是微服务核心的几个部分,服务注册,服务发现和服务状态。最后通过服务将数据上报到zookeeper,通过Zookeeper作为配置和调度中心将整个系统作为分布式集群给管理起来。

从单体架构到微服务

2015年12月份我们正式上线了一个服务Demo,正当要松一口气的时候,问题出现了。最大的问题在于团队人员对微服务不了解,面对这种情况,我们在半年内组织了40多场关于微服务的分享培训,不仅仅是让大家了解微服务,更多的是统一对它的认识,只有认知达成一致才能真正的去应用。

虽然做了这么多的努力,但是等到2016年7月整个平台系统上也只有600个服务在运行。这明显不符合预期,相对于SOA治理我们提供了很多的工具,让开发变的便捷简单,只需要将服务代码提交到git后续的步骤都会自动化完成,那么问题到底出在哪呢?后来发现还是因为前面提到的原因,大家对这套新的东西并不是很放心。

运维之痛

抛开研发层面,运维也存在着问题。为了前面提到的600个服务,运维方面已经是疲于应对了。首先是Docker,虚拟机分配任务翻倍,这是由于平台中服务耗尽的时候会触发一系列的机制申请新的资源,而这些资源都需要运维人员check之后才能分配。其次是发布量的问题,原来的平台中很多项目都是一周或两周发布一次,现在微服务之后甚至可能是每天多次,发布的时候还要保证平滑,让前面的业务无感。另外故障排除的时间也延迟了一倍,原先只有一个服务的情况下排查还相对简单,微服务之一次调用被拆成了多个服务,很难去判断哪个节点出现了问题。这些不协调的因素集合在一起之后,报警量当然也会随之增多,运维人员处理起来力不从心,疲于奔命。

运维系统的全面升级

针对上述情况我们仔细分析了一下,发现了几个问题。首先是运维体系跟不上,以前那种手动写脚本的方式已经无法应对当下的场景。其次因为微服务的发布频率很高,所以测试的自动化也要跟上。还有包括以前的持续集成和底层服务编排也不再适用。

所以在推行微服务的过程中,运维系统也要进行全面的升级。首先在源头上要有一个一站式的工单系统,通过这样的一站式运维平台进行整合然后交给运维处理。由于服务发布频率非常高,所以我们联合了自动化测试小组开发了一个集成测试化环境——天镜,将所有的case和单元测试集成起来,在上线之前就能够生成一份测试报告,判断是否能够发布。一旦判断为可以发布就先做持续集成开始发布,最后到底层的PaaS平台分配资源。

整个流程看起来和微服务关系不大,但是微服务对底层的PaaS平台有很强的依赖,如果没有这一系列的步骤就很难去实施微服务。所以这里给大家一个建议,在底层的基础设施还不够完善的时候,不要贸然去推微服务

兼容并处

经过一系列的升级,到2017年2月的时候平台中的服务终于达到了5400多个。这时运维是没什么问题了,不过研发方面又出了新的难题。一方面有大量的新老员工对微服务开始感兴趣,过来咨询如何进行迁移。另一方面随着服务量的增多,出现了各种罕见的故障。简单说个大家不常碰到的问题,因为有着跨平台的需求,所以我们经常要从windows向linux发包,有一段时间出现过发送的包全部丢失的情况,最后经过与运维人员的配合才发现是linux内核中的参数出现了问题。

服务化作为整个业务体系中最核心部分,一旦业务研发人员发现问题首先想到的就是向我们反馈求助。这种情况的出现导致我们的咨询量暴增,且很难对所有的问题作出应对。

DevOps工具

我们的整个升级方案中,前一阶段解决的只是DevOps中的运维部分,Dev还没有一个很完善的解决方式。在反思了这一问题之后,我们开始着手完善DevOps的整个流程问题。最终实现了在开发人员写第一行代码之前就已开始介入,无论是Java还是.Net或者Go我们都能提供一致的项目模板或脚手架,安装完成之后就能享受所带来的便利。同时考虑到多语言多平台的现状,我们还做了一个通用平台,这样开发者在不同语言之间进行迁移的时候也能获得一致的体验。

到了测试联调阶段,我们也进行了相应的改进。使得代码在编译的时候只要有一个良好风格的注释,剩下的就可以交由组件处理。比如将所有的注释和接口抽取出来做数据契约和接口契约生成wiki文档,在调试的时候该文档就会生成,它给联调带来了极大的便利,发布之后的回归测试也能够用到。

DevOps作为一个庞大的平台,一定要有一整套的OpenAPI机制,保留足够的透明化,让用户能够知道系统运行的时候到底发生了什么。我们有一个完善的监控系统让每一步调用,每个接口的响应时间都能通过OpenAPI机制让用户获取到。必要的时候,还会开放一部分的运维权限给开发,因为只有开发人员才会知道系统出现的问题该如何维护。

以上就是我们目前所提供的DevOps工具,相对来说还是有所不足,因为当出现新的工具的时候,研发人员总会期望有更好的工具,所以我们还是会进行不断的演化升级。

关于服务粒度

前面分享的都是同程两年的微服务历程,在这个过程中我们也总结了很多经验。比如大家最关系的服务粒度问题,微服务如何拆分,分到多少粒度才合适。


在谈具体的拆分方式之前,先来了解下康维定律和领域建模。康维定律简单来说就是系统设计(产品结构)等同组织形式,每个设计系统的组织,其产生的设计等同于组织之间的沟通结构。同程就是一种典型的树形结构,底层会有一些高频的互访,最终我们的微服务架构也是类似于这种结构。

上面这两个概念相对来说比较宽泛,这里说下我们的具体方案。

第一是按照团队组织结构切分,明确服务的归属,切勿出现跨组的服务,这样物理上的切分能够让服务获得更好的内聚性。

第二个是按服务的发布升级频率分,我们刚开始做的时候,经常会出现一个有多个接口微服务中,仅有一两个接口是高频的,剩下90%只有少量的访问,但是每次发布时这些接口都需要全部带上,测试的时候也都需要再跑一遍,造成很大的资源浪费。因此我们推荐将哪些特别高频的服务接口单独剥离出来。

第三是按服务调用数量级分,一般使用Restful风格设计的接口,update和inset应该是放在一起的,但是update和inset之间如果有一个访问频率远远高出的情况下,就有必要考虑将它分离出来。

最后就是按照数据的读写分离划分,之前我们的很多设计都是基于增删改查,但是这种设计大部分情况下查询都会高出几个数量级。这时候我们推荐采用CQLRS模式,将幂等操作和会导致数据变更的操作分开,也就是常说的读写分离。

关于服务版本

在一开始只有600多个服务的时候版本问题还不明显,当达到5000多个时就给我们造成了很大的困扰。之前刚开始用服务的时候大家都没有定义版本,一直的都是1.0,导致服务内的接口不断增多。这主要是由于服务的开发人员不敢随意的对代码做修改,怕服务下线后引起服务的宕机,所以只能不断的增加接口。

为了解决版本问题,我们开始使用标准的语义化版本。版本号有4位,前两位是大版本号,后面是小版本号。在修改或删除原先的接口后,需要升级大版本号显示的告知调用者该服务做了不兼容的升级。如果只是优化了逻辑或者新增接口的情况下,则要升级小版本号。

这种方案在刚开始推行的时候也出现了很多问题,有的团队是不知道如何写,有的则是一味的升级小版本,毕竟这样安全些。这个问题也是微服务治理的一个重要方面,就是微服务平台要能够评判服务的质量。在我们的平台中有一整套的中台机制,在服务注册的时候不光会上传服务的节点、IP、端口,同时还会上传所有的接口和数据契约。也就是说它会解析包的原数据,将对方需要知道的数据都抽取出来,甚至包括注释,它的好处在于服务发布的时候能够知道是否有不兼容的升级,调用方也能知道服务升级具体做了那些事情。

我们对版本号的机制做了多次迭代。第一代版本号是固定的,调用方调用的时候也要通过该版本号,只要有了服务名和版本号就能帮开发者进行服务查询和调用。这一代的问题在于每天都要更新多次版本,非常麻烦,为此提出的妥协方案是直接让开发者将版本号写在配置中。到了第二代就有了统一的向前/后兼容的策略,和明确的不兼容策略。只要接口没有发生改变造成不兼容,我们都能够找到,即使接口所在的项目包发生了多次升级。

关于过渡到微服务

最后也是最难的一步就是从如何单体架构过渡到微服务,过渡的时候不仅对业务研发有要求,对整个的微服务平台也有着要求。微服务框架要有良好的兼容性,让研发人员能够平滑的进行过渡。我们刚开始的方案是让开发者直接将代码打包接到平台中,然后通过服务治理系统进行拆分。

接下来就要讲到绞杀者模式和监狱模式。绞杀者模式很常见,就是将单体架构向微服务演化的时候,首先要保证单体架构不会继续增大,对于新的功能和模块要独立开来,原来的项目直接冻结,再把原先有的功能慢慢剥离出来。如果有些功能很难进行剥离的话,那么就要用到监狱模式。先做一个微代理的微服务项目,接入真正项目打包后的接口,什么时候接入的项目能够转换成微服务,就将微代理给去除掉。

虽然上面讲到了很多解决方案,但是还有一点需要提醒大家,“微服务不是银弹,不要过度微服务”。因为多一层调用势必会影响一些性能,整体的复杂度也会增加,运维和测试成本也会上升。因此那些对性能特别敏感或有高频运算的项目就不适合去做微服务。


猜你喜欢

转载自juejin.im/post/5b4d5ab36fb9a04fc226a626