王晓波 / 同程旅游首席架构师
专注于高并发互联网架构设计、分布式电子商务交易平台设计、大数据分析平台设计、高可用性系统设计,基础云相关技术研究,对 Docker 等容器有深入的实践。
前言
随着公司体系的不断庞大,各种服务越来越多,程序员往往需要操心很多代码以外的事情如资源申请、环境配置、性能安全等,导致了开发效率低下。在ECUG 10周年的大会上,同程旅游的首席架构师王晓波分享了同程旅游从传统架构转变为微服务架构,再从微服务架构转变为Serverless架构这个过程中的一些经验,旨在让程序员的开发能更加纯粹快乐,专注于代码。
很多人说OTA这个行业不是互联网,其实确实很难把旅游这事完全的互联网化。如果你去买一个充电宝和买一个帽子,不论是在淘宝还是京东,肯定是一套系统帮你服务的。但是如果在同程上面,你去买一个酒店或者买一张门票,可能有五六套系统在为你服务。这是因为数据、关注的点、用户的行为,完全不一样。导致公司里几千号程序员,每天好像都在干重复的事情。怎么改变这个事情呢?让编程更快乐一些,更纯粹一些。
同程实践Serverless的背景
从一条SQL到一个服务的距离到底有多远?
很多在做业务级开发的同学,往往每天应对的是从数据库或其它数据存储的地方,用一条SQL把数据搬出来,或者写进去,并且把它作为一个服务提供出去。那这样一件事情到底离一个服务有多远?其实真的很远。换句话说就是,当你想开发一个功能的时候,这个功能什么时候能够上线?当我们去做一条SQL时,首先要知道DB在哪,要知道它的性能怎么样,然后得去申请一个工程来放代码。申请了工程之后,得去构建开发环境;构建了开发环境后,得申请线上的资源;申请了资源之后,要去申请部署;部署完之后,要去申请运维。这一切都好了以后,还得想到哪一天要是压力扛不住了,或者哪一天流量大了以后怎么去扩容。当看到这一系列带来的问题之后,一条SQL到一个服务的距离到底有多远?非常远。
在同程,很多的产品经理充满想法。可能早上在公交车上被人撞了一下,就想到一个想法,然后到公司让程序员把这个实现了,下午就要上线。程序员说这个下午上不了线,做各种的解释,说需要申请服务器,或者其它的各种资源。这一系列的事情,其实都来自这个问题:一条SQL到一个服务的距离到底有多远。如果这个时间只需要1小时的话,一天8个小时允许产品经理想8个问题了。晚上下班以后加班4个小时,还能够再想4个问题。产品经理开心了,这样程序员的开发一定是更加快乐一点的。
哪些因素影响着程序员不快乐
环境、框架、依赖
首先是环境、框架、依赖,这些问题非常的难搞。
第一个是指环境,从开发到上线这个过程中,环境就已经非常难搞了。可能在开发同学的本地是好的,但到了测试手上或者线上的时候,就出现了问题。闹了半天可能是怪我们的运维同学太弱了,配的环境跟本地开发环境不对,所以不行。其实这里有很大的问题,这个运维同学是背锅的。环境的一致性我觉得本身就是程序员应该做的一个事情。可是并不是所有的程序员都是好的程序员,并不都是全栈工程师。他可能会写代码,但不懂运维,不懂得如何让代码在环境中更好地跑起来。再反过来说中国有多少的全栈工程师?一个全栈工程师的培养又需要多久?这里困难就来了。
还有一个困难就是依赖关系,这也是开发过程当中非常头疼的问题。我自己是个Java程序员,有时候自己写个一两千行代码,只有一两个文件,打一个包,60几兆出来了,一堆依赖关系。当然现在用Go好一些了,但这样的问题能不能解决?比如说同程很多的同学,我们在苏州,北京也有一些开发,在苏州我们有近千名程序员。当有这么多程序员的时候,如何让这些程序员的效率更好地提升?当然你可以用管理,用开发模式去做。但是用技术去解决依赖关系,让没有依赖关系产生不是更好?即使有依赖,也是用接口的形式去调,这样就舒服多了。
部署、运维、扩容
还有一块就是部署、运维、扩容。刚才说过可能开发的同学不是太懂运维,运维的同学又不是太懂开发,所以导致了问题。同程之前也在推DevOps,现在我们每个开发同学也可以拥有自己的运维工作台,去操作他部署的每一台机器,这个是没有问题的。但是这件事情真的好吗?其实同程在做这个的时候发现,真正让一帮开发的同学去做运维并做不好,天天线上故障,因为做运维和做开发是两个思路。所以后来我们在做DevOps的时候,先把所有原始的运维同学进行转岗,变成运维的产品经理和项目经理,让一帮开发同学去做运维平台,把运维平台做出来之后再给开发同学去用,这样DevOps才推起来了。听起来这样真的解决了这个问题吗?其实还是没有解决,因为对于开发的同学来说他们还是得知道运维的工作有哪些,但有些开发同学可能并不想知道。比如某天早上产品经理想出来个卖票时搭售个盒饭,这样一个功能可能50行代码就写完了,那么这个开发同学为什么要去懂运维呢,这样的同学其实有很多。
当公司大了之后一定会受到成本结算。OTA的缺点是一个公司看似一个公司,但它其实是一个特种部队,里面集成了各种事业部,互相独立,都很牛逼,但是收他们钱的时候就都没钱。比如在春节这种流量高峰时部了很多服务器,但是春节过后,这些服务器就产生了资源浪费。所以就有了问题,如何让我的代码做到没有流量的时候不要算资源,当流量大的时候又可以自己扩容。这个要求很高,但是对于商业战场来说为什么不可以呢?
调试,性能,安全,太复杂
这块可能是最大的痛苦。在同程我们有专门的性能测试团队,也有性能测试的平台,也会做全链路的性能测试。比如说每年国庆节和春节前期都是两次旅游的高峰点,所以我们都会做全链路的压测。但这有个问题是你如何保障每一个程序员写出的任何一条垃圾代码都可以高并发。
还有一个问题是什么呢?假如之后产品经理又出了个难题,说抢购不好玩,抢了就没有了,为什么不能在抢购的时候实时显示排在第几位。这就得要保存每个人的排名,还不能造假,那这个时候的性能如何去做。可能有人说找个高端的程序员做这个事情不就好了吗,但是我们有这么多的程序员,产品经理可能随便拉了个刚毕业的新人来做。当一个一年左右的程序员写了一个产品也是坑爹想法的系统的时候,最后上线真的造成了损失,那么谁该负这个责任呢?一路追究上去,事业部技术不行,公司技术不行,公司架构师技术不行,那这个事情就很坑爹了,我作为架构师甚至都不知道这个系统。所以我们就想着能不能去做一个平台,让这个事情变得更加简单。
Serverless与传统架构比较
这是同程最老的一个架构,这个架构基本上是同程创业的时候那几个老板自己写的。10年前看这个系统的时候感觉还可以,做这样的架构没有问题。但如果是17年的时候还用这样的架构,大家会觉得OTA的技术果然比较弱,还在用这样的系统。但其实今天在同程的系统里确实还有这样的架构出现,而且还有新做的系统也长这个样。为什么有这样的问题呢?比如说我们某个业务同学发现在澳大利亚的某个沙漠深处很好玩,产品经理感觉也不错,要成立个项目干这个事。然后就给他三程序员,你让他们写个微服务的系统出来,不可能。能写个上面这样的架构就不错了,说不定连服务都没有,直接都堆到应用里了。这样的业务在整个旅游场景里面非常多,我们把它叫做打样业务或者创新业务。一个事业部会孵化这样的项目N多个,可能一个成了,就会慢慢发展成大项目的。
但这样的架构容易碰到问题,流量上来就容易死机,死个一天也是有可能的,所以需要改造。
同程在2年前开始做微服务,这一套微服务架构跟别人的不一样,除了服务的基础容器以外还多了一个中间的Gateway,专门去屏蔽非微服务的服务。这是因为在同程中还有很多传统架构的那些创新业务,这些创新业务不代表不会相互去调用,那么这些非微服务的服务如何被微服务调用呢,我们当时特别想到了一个模式,叫GNE模式。大家可以思考一下微服务是不是一个伪命题?我们实践下来觉得是伪命题,因为并没有谁能够非常明确地用数字告诉你微服务应该多微才称之为一个微服务。而且服务长时间跑下来,一定会变大。举个例子来说,你今天有一个下单接口做成一个微服务发布上去,下单可以算是核心的一个服务了。假如你的产品是在不断迭代的,那么一个月或者两个月之后一定会有第二个服务出现,叫“新下单服务”。三个月之后,一定还会有新的服务出现,叫”新新下单服务“。半年之后,最老的那个服务会被打上标注“不能再用的下单接口,但是不能下线”。所以会发现一个问题,在所有的公司里面,系统上线是很容易的,但是如果想让一个系统下线,需要联系各个部门,烧各种香,最终才能下线。
微服务产生的结果,就是这个服务不停长大。原来我们微服务的目的是业务单一化,做得更小更好更稳定,独立地部署,可以去做编排。但当你三五个月或一年半以后再去看时,会发现这玩意就是个吃胖的杨贵妃,不再是那个窈窕淑女。编排也排不起来了,因为里面包含了太多的东西。那这就是同程在做微服务时遇到的麻烦。我们的解决方案是再做个Gateway,把长胖的胖子关进去,对外还是用多个微服务基本接口,只是有不同的路由策略去选择使用哪个版本的接口。这做完之后,其实带来的是维护成本和维护代价非常大。
同程现在几乎所有的应用都是部署在Docker里面的,包括数据库。但服务的数量越来越多后,其实对运维和每个服务的回收带来很大的问题,服务不能这样无限地增长下去。而且发现30%的服务随着时间的流逝调用量几乎变成没有了,有时候一天调一次或者一个月调一次。这些就是已经可以下线但是又没法完全下线的服务,还有微量的流量进来。这样的服务越积越多,大量地消耗你服务器的资源,浪费就极大了。
一个代码脚本能不能就是一个微服务
我们来看一个对比。开发同学写代码时,有的情况下是叫伪面向对象,写出来的代码和过程代码没什么区别,而且往往非常累赘,代码一坨一坨的。所以开发同学经常会说要重构一下代码,因为他的代码是错综复杂的。反过来想,现在去看运维的同学,运维同学也写代码,很多都是脚本化的。对于运维同学来说,脚本不存在重构一说,不要就扔了再写一个,相对来说是快速的一次性的东西。
在前面的系统中我们说了很多快速变现的内容,那么这些快速变现的内容可不可能去变成一个脚本,这一个脚本可不可能变成一个微服务?比如说今天要从数据库一个表里面查出来数据,对外提供服务,可能就一句SQL的事。如果不允许它变成一个脚本,而是做一个微服务,微服务是有一整套自己的流程要走,怎么办?他肯定不会走这个流程,他可能就会找一个其他服务的实例,加一个这个接口,结果这个服务就被他污染了。所以我们就想在已经做微服务的场景下,把那些变化快的轻的东西变成脚本去走。
同程的Serverless实现了什么
同程大概是2016年的4、5月份开始做Serverless。Serverless架构是什么,估计今天也没有人能说清楚。我这里截了一句话:“如果你的PaaS可以将以前半秒启动的应用在20ms内启动,就叫它Serverless”。反正我们做这个的时候并没有想到多少毫秒能把它启动起来。我们本着的一个原则是,那些轻的服务能让它一个脚本的,就去变成一个微服务,并且它的调用部署能够做成动态的扩容。当它没有流量的时候最好不要再消耗大部分的资源,而且能够轻易的上下线。我们做Serverless其实要解决的问题就是,有大量的很轻的服务需要快速地上下线,并且又不想耗费巨大的资源成本。
这是我们做的一个架构图,我们主要分为三个层次,调度层、计算层和基础层。调度这一层看似没有什么问题,但其实我们做了很多的事情。可以想象一下如何快速地在1秒之内把下面扩容之后的服务挂载上来,并且扩容出去。所以动态的热加载的负载均衡是非常重要的,而且是每个阶段都会有,因为有可能发布在这上面的是个网页。如果是个对外的高并发的东西,比如产品经理说今天要做一个大流量的引流,需要做一个落地页,假如程序员不小心做到这里面,那并发就来了,很可能就挂了。而且不仅要考虑前端的外部流量进来,也要考虑内部流量。比如在内部是被多个系统依赖之后,如何在断掉之后有个熔断机制。因为如果用微服务架构去 做的话,都会做熔断的机制,但如果是这样的简单脚本,它很少会去考虑熔断的时候怎么办。
第二层是计算层,主要是做我们的调度工作,如何快速地开出资源。在Serverless做的时候有两种做法,比如我们直接用Docker容器放,每次扩容都是一个容器,靠一个个容器堆上去。但我觉得这种不够美,而且直接去扩容器太粗暴,毕竟容器本身还在占资源。而且还有个问题是,这里面部署的东西,我们是定义为一个脚本或者一两个脚本去做的事情,这个代码非常少,如果分配一个容器这么重的东西,那么资源还是挺浪费的。所以我们开始入手的时候比较简单,用动态语言去做这件事。我们先去实现Lua,Node.js这类动态语言,它的好处是运行时像个虚拟机,可以把它隔出来。隔掉之后,当每个脚本在执行的时候,它就是一个独立的可运行的容器,然后再把它放到一个Docker容器中。目前我们只支持动态语言,我们也在尝试Go,但比较难还没成功。上面这两图展开的是我们每个脚本之间的互调关系。
这是我们Serverless架构的资源利用图。基本是在物理机的基础上去布Docker容器,Docker容器里再去开我们一个个实例去部署出来,然后做个隔离。我们做了实验,最小的部署量能在4台物理机上部署10万个应用。当然,这在生产环境是做不到的,10万个脚本服务在生产环境都有流量的话,光这些流量就把它打死了。使用这样的Serverless之后,很多轻的应用是可以随时下线的,就解决了那些流量很少但是必须活着的服务的问题。
但这里又产生一个问题,就这样的话程序员编程快乐了吗?我想还是不快乐的。我比较讨厌的是想写行代码从数据库里面取一个数据,结果需要我找什么驱动,问我DB地址、密码,以及打各种各样的类库,烦都烦死了。所以我们也开始做了个SDK,让代码能更好地去用它。如下图所示的从数据库拉数据的例子,当你编程时不需要关心服务器在哪儿,专注于代码就可以了。这样一个Server.DB的库在整个平台会有一个配置中心进行管理,所有DB可以人工地配进去,配进去之后它就生成了这个对象,对于开发同学来说他只需要敲就行了。
在这个平台除了操作DB以外,还可以调其它的微服务。比如做了一个落地页,想知道会员的等级,那肯定要调会员的服务。会员服务是个很重的服务,不会在这个平台上,肯定会是一个微服务部署的独立服务,那这个平台一样可以去靠到它,通过调度去直接调用它。
做了这么多事情以后,其实我们发现还不够Serverless。我刚才说了还有个很大的问题,本地是好的,但线上或测试环境不行,这个问题还是没有解决。这就还是在关注环境,还是不能够仅关注代码。所以我们做了一个Web IDE,开发同学不用再关心本地的开发环境,也不再需要配置本地开发环境的依赖关系。因为当微服务化以后,特别是大量大团队去合作的时候,往往它的互相依赖关系在本地很难模拟出来。我们提供Web IDE,让每个同学打开一个Web网页就能写代码,这样代码在写的时候,一切就在我的控制之下。
这个Web IDE还蛮强大的,可以做一些版本合并。可以直接在上面新建工程,或者进入一个工程,工程中还带了很多的脚手架,还会有很多的角色。同时还有商店的功能,程序员可以写一些公用的东西给别人去用。比如刚才说的反爬,反爬的兄弟把反爬系统做好了,如果你写了个脚本是对外提供网页服务的,需要反爬功能,那只需要在配置这里打个勾就可以支持反爬了。比如说要做个排队抢购的,只需要在配置里打个勾,写上并发数量就可以了,因为有别的系统会为你做这事。
Serverless在同程的情况
开发、发布和运维的效率
我们发现做了这样的一个事情以后,对于我们开发的初期,任何一个项目,任何一个独立部署的功能开始放置时,一定会要申请一系列的东西,在这些事情上我们省了90%的时间。再接下来是找数据,DB在哪看起来是小事,但其实在开发看来是很烦的,测试环境的DB在哪,开发环境的DB在哪,预发环境的DB在哪,搞错一个就完了。所以做了Serverless之后,在这些事情上省了80%的时间。对于写代码来说,其实节省的时间不多,毕竟很多代码还是要人写的。在这个环节我们提供的很多SDK起了不少作用,节省了40%的时间。还有90%的时间是运维这边省下来的,当应用作为一个小脚本来部署的话,整个平台化,连开发工具都没有了,那么这样对于运维来说更简单,一个监控系统就可以全自动化地做掉。
Web应用
现在同程所有的站点所有的网页都是放在刚才的平台里面,因为有了这个平台,同程内部在16年末兴起了前端革命。以前前端工程师做完页面需要找后端提供接口,现在做了这个平台以后,由于是Node.js,前端工程师自己就撸完了,各种数据库都可以直接找到。真的是早上提需求,晚上就能上线,所以所有的前端都喜欢用这个平台。而且这个平台不仅是个网页平台,还有很多的API提供,很多同学就开始自己造工具,比如拿Atom对接平台的API,拿Atom也能写这些代码。
轻型服务
还有就是一些简单业务的快速服务,上图就是一个实际的代码,真的就是一个脚本就完成了。
配套功能集成
然后是价格计算的实时服务。很多人说OTA没有什么漂亮的技术,其实还挺多的。举个例子,最传统的业务,酒店。酒店这样的业务,非常的传统,但其实它非常难做,非常的复杂。可以想象一下在阿里或者京东上面买东西的时候,它的价格是什么样子的?死的,上午的价格至少到中午是不会变的。但是酒店的价格是不一样的,为什么?酒店连住三天,或者你住的日期里面有周末,或者提前三天,或者提前一个星期,每种搜索条件下出来的酒店价格是不一样的。酒店住一个礼拜价格是低的,可能300块钱的酒店变280,如果你提前两周订可能又更便宜。但如果你订明天或今天晚上的,那就贵了,或者住到周末要涨价30%,所以这个价格是根据每次搜索的条件去变的。目前在酒店搜索的时候很多是死数据,可能5分钟变一次,或者半小时变一次。这时候就要实时的计算,这个实时的计算其实就是扩容的问题,因为算的时间太长了。当然现在搜三亚很快就能够搜索到,因为它有个实时的扩容机制。像我们同程的酒店,日常是有480个节点去计算它,可以扩容到1000个节点,也可以缩回来。
下一步在Serverless上要做什么
当然未来我们还有很多事情要做,Serverless只是才刚刚开始,做的对不对也不一定,总之这个Serverless是能够帮助我们去做掉一些事情的。
Q&A
Q1:如果放代码的服务器或者其他的服务器出现问题怎么办?
王晓波:其实国内开发都有这个问题,前两天跟他们facebook的交流了下,我们中国要求当天代码当天交,根本没有人看的,耽误公司的话就挂了,哪怕你有本地代码,我们这个可能还会更好一些。
Q2: 比如说在网页上编代码,编一半突然断网?
王晓波:这个不会。我觉得你们那个方式比国内那个方式好的。
Q3:我最近也在做公司网关的东西,关于网关这一块的经验介绍一下。
王晓波:稍微简单一点,我们分很多种的网关,比如说前端过来的,我们把它也放在网关,你说的网关是微服务的,同程微服务的话我们做成负责服务的分发,对非微服务和微服务,我们只把这个叫网关,别的都没有了,这个网关是要牺牲一些功能,我们做一些微服务,通过网关这些东西都没有了,做的非常粗暴,我们也不融合的,给你返回到一个特定的地方,剩下就是自己服务了。