Klook是总部位于香港的旅游科技公司,成立四年,在亚洲旅游创业领域成为一匹黑马,为全球上千万用户提供折扣门票、旅游特色活动的预订服务。Klook在技术的不断探索和创新,是这家公司国际化、规模型业务的实现根基。今天由Klook的后端技术总监熊传亮,为大家分享Klook在Go语言的应用实践。
目
录
1.Go 在Klook的应用情况
2.如何用Go实施新架构
3.架构面临的挑战和技术的演进
4.一点探索和思考
1.Go 在Klook的应用情况
Klook用Go很早,2015年就开始尝试用Go做项目开发,我们面向全球的:网站、App、Open API等平台都是Go在后面支撑,所有的内部系统,风控,智能推荐,微服务工具链等等也是用Go,可以说Klook的后端用Go在做几乎所有的事情,后端技术栈都是基于Go。
当然,Klook最初也不是用Go,Klook最初的架构是这样子
这是一个非常经典的后端架构,Klook最初的业务服务都是基于这个经典架构的Java单体应用,这套架构的问题在于,依Klook当时的发展速度,在可以预计的时间范围内,这套系统很快会不足以支撑公司业务的增长,很快会面临一个技术架构的升级问题。但启用一个新的技术架构,就需要思考一个问题,怎么在保证业务发展的情况下,去升级和落地新的架构?
常见的顾虑
我认为你要升级一套架构或者落地到新架构,有几个点你可能要考虑清楚:
如何证明新架构或新语言更有优势?
对新架构或新语言,团队是否有足够强的技术问题解决能力?
如何解决旧系统的迁移问题?
业务需求与技术改造的冲突怎么解决?
招不到人怎么办?其它语言开发者转Go之类快么?
如果没有想清楚,升级架构我认为大家可以再斟酌一下。
2.如何落地新架构?
2.1 你需要一些成功案例
具体怎么开始一套新的架构,从哪里着手?从Klook的经验来说,你要开始一套新的架构,你可能需要一些成功案例做背书,Klook最开始做了两个服务
1.香港WeChat钱包,客路产品后台服务 (证明新语言是可靠的)
首先是微信钱包的服务,这是当时和腾讯的微信团队合作的一个项目。如果大家打开香港的微信钱包,微信钱包里面购买门票或者旅游类产品,就是Klook提供的服务,而这个服务是用Go写的。
2.Go定时任务服务替换QuartZ(证明能解决旧系统迁移问题)
另外一个项目是定时任务迁移项目,这个项目的背景是这样子的,当时准备多实例部署Tomcat,定时任务会面临重复触发的问题。我们的CTO Bernie建议,可不可以趁这个机会,一次性的把定时任务从Java中抽出来,就花了一点点时间做了这样的服务。这套服务的架构其实非常简单。
2.2 一次定时任务的触发过程
首先把所有的定时任务封装成一个一个独立接口,然后通过Go写的定时任务调度服务,根据定时任务配置定时触发接口,触发成功会有触发成功的响应给调度服务。当这个接口相关业务逻辑处理完毕以后,会把业务逻辑的处理结果再反馈给定时任务的调度服务。
另外,还有一个WEB界面管理和监控定时任务。这套东西从技术的角度并没有很复杂,编码也只花了一点点时间。我认为它做得比较好的地方是它把定时任务变成了接口,任务和调度分离得很干净,也更灵活,符合单一原则。通过这个服务,解决了定时任务迁移问题。 后续,我们用Go做了很多的微服务,增加了很多的定时任务,但是这套代码从写完一直用到了现在都没怎么动过。
有了这两个项目做背书以后,其实这个时候Klook才开始真正的认真思考怎么用Go做微服务。我们知道,Go这几年比较火,如果大家有想要在公司推广Go,可以借鉴一下这个思路,让大家建立起对Go语言的信心。
要上一个新的架构用Go做微服务,可能要做好下面几点
1. 做好日志采集与报警
2. 基础库积累
3. 业务边界划分
4. 统一的技术规范
5. 代码评审很关键
前面分享有强调过做好日志采集与报警的重要性,大家考虑是一样的,日志采集与报警是很重要的,对后端来说,当出现产线问题时,在某些情况下,日志查询甚至是唯一的手段。
另外,做好内部架构需要有一个好的,不断在更新的基础库。另外一个就是业务边界的划分,这个非常重要,因为你的业务边界不在早期划分清楚,随着公司的发展,公司业务和微服务会越来越多,如果不划分清楚,之后就会变成一团乱麻。而你在早期就把业务边界划分清楚,可能后续的代价就不会那么大。我认为再怎么发展,它的大的业务分类是不会有变化的。
剩下的是统一的技术规范和代码评审,这两个东西,我认为对开发来说最大的好处是,它是非常好的代码最佳实践的推广渠道。而好的、最佳的代码实践,它无形之中可以帮开发避免掉很多的坑。这对于服务的肯定性和代码的稳定性是非常有好处的,如果大家的团队里面还没有代码评审机制,我觉得大家真的可以在团队内部尝试一下,它对你的代码质量和服务稳定性提高都是非常有帮助的。
2.3 服务稳定三件套
过载保护 + Go:recover() + 进程守护(supervisord)
如果一个服务要稳定,推荐大家注意这几点:
1、服务的过载保护,如果没有做服务过载保护,当它要处理的量超过服务最大承受能力的时候,它有可能把服务冲垮,后面的服务也会跟着有可能冲垮,甚至你的DB都有可能被冲垮。所以服务的性能测试很重要,你要知道你服务的处理极限在什么地方,再设一个相对比较保守的值。这样你的服务就不会雪崩,连累到其他的兄弟服务。
2、大家知道,GO里面有一个很好用的东西是指针,有指针就有可能会出现空指针,有空指针就有可能会Panic。当Panic了,服务就危险了。所以说你写代码的时候,一定要有recover的机制兜底。这样,就算出现了异常,你的服务也不会挂掉。当然,万一如果你兜底失败,你的服务仍然挂掉了,这时,你需要一个服务守护的工具,服务挂掉,把你的服务自动拉起,保证你的服务是可靠的,可用的。
3、有过载保护保证服务不会冲垮,有recover机制兜底防止服务挂掉,另外还有一个进程守护,保证就算服务挂了,仍会自动拉起。有了这三样东西做保证,我认为,服务的基本稳定性还是有保障的。当然,如果业务逻辑错了就不在这个范围内了。
2.4后端微服务化
Klook的架构微服务化后,当时的架构是这样的:
根据当时的业务边界划分情况,拆分成一个个的服务组,然后,这些服务的服务注册和服务发现,配置中心等,都是基于Consul,fabio做负载均衡路由。然后我们有自己的监控中心,链路追踪,及我们自己的服务治理平台。 当然,在这个架构下面也还有其他很多东西在支撑。
我认为这些应当都是大家非常熟悉的开源项目,至少大家都认识这些东西。这些构成了整个Klook微服务的基石。
随着微服务改造的深入,出现了很多、很多的,微服务治理相关的痛点。 比如,因为引入了这么多的组件或者第三方服务,你会发现,当要做相关事情的时候,经常要在几个窗口或者几个服务间来回切换,经常会有屏幕老是不够用的感觉。另外你的服务越来越多,服务的部署和快速回滚会是很大的麻烦,你服务越多,比如APP发一个大版本,那可能是十几个到几百个服务实例,你很辛苦的一个个部署完了,结果,当测试进行产线回归后,测试告诉你说,很不幸命中了不知在哪里的一个bug,需要紧急回滚,这时候会很惨,要手忙脚乱的回滚,这会是一个问题。
另外就是配置参数的问题,在微服务有很多的参数,假如说批量更改一个参数,比如帐号或者密码,你首先要知道产线那么多服务,有多少服务用到这个参数,怎么去批量更改,你是一个、一个服务更改它吗?这也是一个问题。
总之,服务化后越到后面越发现维护是个很大的负担。对于这些服务治理上存在的的问题,Klook给出的解决方案是这样子的:
我们自己做了一个微服务的治理平台,大家可以看到这个界面上有产线服务的运行情况是怎么样的,它也可以单独针对某个实例进行操作。然后可以在这里面进行批量部署和快速回滚,可以在这个系统上做参数的查询和批量的整理,对一些WAF、流控或者其他的规则,也可以在这个页面进行快速的设置和分发。在这个界面上,基本上已经把微服务相关的痛点在上面解决掉很大部份。至少有了这个界面,我们只要借助鼠标就能完成相关的操作,不用在各种组件中来回切换。
反正我个人感觉,有这个界面以后,很大的提高了工作效率,然后减少了很多的心智负担,我不需要再记那么多的命令和那么多的脚本参数,还有另一个好处是大大减少 了忙中出错的机率,因为有时候往往产线出现紧急情况也是很慌张,一慌张就容易出错。这个比你手动换参数要稳定一点。
当然,这个平台的背后需要对Klook的基础架构做很多的改造,并且需要有很强的技术整合能力才能做这个平台。另外,用一个相对简单的界面去解决这么复杂的微服务治理问题,我个人认为这是符合Go的少即是多的理念的。当Klook的微服务治理平台,特别是上面的界面比较稳定以后,我当时认为Klook的微服务改造进入相对成熟的时期。
但其实并不是,挑战是无处不在的。
3. 新挑战与架构演进
1.全球化扩张对服务的可靠性、安全性和可用性均提出更高的要求
2.系统复杂度在不断的提升,而产品质量要求却越来越高
3.现有业务迭代频率越来越快,同时还在不断出现新业务
4.招人速度赶不上业务发展速度
公司一直在高速发展,会面临各种各样的挑战,其中,最大的挑战是来自全球化,因为公司一直在拓展新的国家、新的目的地,这会面临很多的困难与要求。我不知道大家对全球化的理解是怎么样的,我个人对全球化的理解是这样的,大家可以想象这样一个场景,我想出去玩,说走就走,直接去了香港迪士尼,或者日本的大阪环球影城,或者到了迪拜的某个景点,直接掏出手机通过Klook的APP购买凭证,不用排队,直接扫码入场。不管身处哪个国家,在哪个景点,用户体验是一致的,这就是我所认为的全球化。
而要达到这种用户体验,要面对的情况是,他们可能处于不同的时区、不同的语言、不同的货币、不同的景点、不同的支付方式等等,全球化是件很有挑战和非常有意思的一件事情。回到服务的本身,全球化对服务的可靠性、稳定性都提出了很高的要求,如果服务不稳定,客户就没法进去,只能排队买票,这种用户体验是很差的,所以说全球化的扩张对服务的可靠性越来越高,这是个很重要的问题。
同时,因为公司一直在扩张,不可避免的面对的场景和业务变得越来越复杂,业务压力很大。而同时,有一个很大的问题是人手非常紧张,后端非常缺人,不止是后端,整个技术团队都缺人,甚至整个Klook的所有部门感觉都在缺人,这可能是一个处于高速发展期的公司都会碰的问题,人才的需求。
3.1后端面临的其它问题
1.太多重武器,既消耗机器资源,部署和运维也相当复杂
2.一些开源组件的Bug,与频繁更新的跟进令人头痛
与此同时,后端还有一些其他的问题。大家可以看到前面我们引入了非常、非常多的开源组件或者第三方的服务。我们有那么多的服务,这些组件和服务其实是需要人来配置和维护的。而且搭建这些开源组建的东西是需要资源的,这方面需要花很多的人力、物力。另外就是bug的问题,当然不只是开源软件,其他商业的也会有,对于很多的bug。
当命中后,你去官方查解决方案,往往给的结果是,新的版本已经解决这个问题,或者下一个版本会解决这个问题......其它还要面对很多组件版本的更新频繁且兼容性很差等问题。
这两个都是很热门的项目,特别是Consul,可以看到有很多人参与,有很多人去让它变得更好,这是很好的事情。但大家可能也要注意一下,它已经解决了1900多个issue,还有300多个在处理中。这300多个里面就可能说的是某些bug,我们就命中过其中一些bug。当时命中bug,去查解决方案,里面告诉我说新的版本已经修复了bug,问题是当时产线已经命中了bug,怎么可能没有经过完整测试就直接升级产线?整个微服务的基石就是它,所有的服务注册、服务发现,配置中心都是基于它。
所以我们重新对整个架构的底层进行梳理和思考,引入了那么多的开源组件,是不是真的有这个需要?它的成本到底有多高,给我们带来的好处有多高,对服务稳定性的伤害有多大?对资源的占用是否合理? 最后,我们的结论是需要云化,我们要多使用云服务。云服务在大部份情况下,能提供更好的保障。
3.2微服务云化
微服务化架构
在新的架构里,大家可以看到很多的开源组件或者第三方服务不见了,简洁了很多。
这个架构你可以看到分三个部分,云服务、开源组件加第三方服务以及自研的工具链,就是一些自研的如源代码生成工具,自动化平台和命令行工具之类。这三块构成了目前的后台架构。那为什么中间还剩下一些开源组件之类?首先是有一些功能是云服务商不提供的,另外就是有些项目的质量要远远超过云服务商提供的服务,云服务商提供的服务不是每个都那么好的,这个东西就看你怎么权衡。
大家可以想一想,这种把后端服务全面拥抱亚马逊或者Google云或者其他云服务商以后,会带来什么样的好处或者思路上的转变?
3.3日志处理与报警-ELK Stack
这里我有准备一个例子,一个经典的ELK Stack,相信在场很多人都非常熟悉。日志上报、采集都可以用它来解决,非常好用,我们的开发也非常喜欢用它,Klook早期都一直用它。它本身没有什么问题,有问题的是公司的日志增长速度太快,最初是一台ELK的服务器,很快开发就说查询太慢,查不到资料,然后很快变成两台、变成四台,为什么不一次到位变成四台或者八台?作为创业公司还是要有些成本考量的。
所以,我就开始反思,这个做法可能有问题。在扩ELK这个事情上,花费了太多的精力,但是它的效果并不好,往往你升级以后能稳定一段时间,但是很快又不够用。我们又没有太多的人手在这上面做性能优化,我们都是直接升级。整个评估下来,这个效果真的不是很好。
如果,我们基于云服务的思路去看这个解决方案,我们最后的结论是这样的。
3.4日志与归档数据处理方案
这是我们梳理过后的日志解决方案(PPT),大家可以先看看蓝色的线条。目前的方案是,服务的日志直接用protobuf协议发到nats队列,然后数据上报服务将其上报到aws s3,及对异常触发报警。当我们的开发或业务需要查询相关日志的时候,可以通过我们的数据查询平台拿出数据。
这样带来的好处是显而易见的,首先4台、8台的ELK集群和挂载的日志存储盘都可以扔掉,不再需要人维护。S3是只要你有数据就能存得下,Athena不会因为你的日志量太大影响查询速度,它的查询速度是稳定的。另外现在服务的日志不需要落盘,而以前ELK是日志写到本地磁盘,然后Filebeat通过配置,监控指定日志目录进行上报,这意味着你的日志要在本地写磁盘,有爆盘的风险,且存在延迟。另外一个问题是我们服务用的日志包是glog,它为了提高性能,会缓存30秒再批量写入日志,而我们的报警依赖于日志,报警就会有30秒的延迟。因为PB协议和Nats的速度很快,日志报警会变得更及时。
同时,这样的做法可以带来更多的可能性,首先上报服务,直接完成了日志的归档。 另外还有其他类似的东西也可以采用这种方案上报到AWS S3。 对于数据查询平台,还可以在上面做很多事情,比如说以前有开发反映,一个Request日志只能一个个服务查,但是做微服务,一个请求后面有很多服务,你查问题的时候需要进行聚合,而现在这个可以根据你的相关日志查询的集合快速定位问题,另外,以前有些数据查询因为数据量或查询条件问题会查得比较慢的,在这个界面上可以做相关的限制或者优化。它还有一个好处是支持SQL语句,通过SQL去组合查询日志。
总的来说,就是你把架构的思路换一下以后,可以带来更开阔的思路,用更少的人力资源或者用
更少的精力或者更少的代价,得到一个相对稳定的服务,我觉得这个代价很值得,同时也会带来更多的可能性。像这个方案很简单,代码并没有写太多,但是解决了很多的问题。Klook目前就是在用这种思路在做相关的架构。
4.一点探索和思考
4.1 主动变化,在不同阶段,找到最适合的那个架构
我认为每个公司所处的每个阶段都不一样,你需要找到最适合公司当前的那个架构,当然,最好是在现有架构还没有恶化到一定程度的时候,提前做架构方面的升级或者改造,不要等到系统快顶不住的时候再临时抱佛脚。
4.2 要善于“组合”
另外就是一个组合的能力,怎么根据你的业务需求或者业务场景或者公司的发展,怎么把第三方开源组件、云服务,自已造的轮子等等,根据业务场景或者业务需求变成一个完整的微服务生态链,整合起来,这是很考验人的。
4.3 服务稳定最重要
对后端开发而言,服务的稳定性永远是最重要的,一定要记得。
谢谢大家!
【提问环节】
提问:我看到你有用Neo4j,你们怎么用的?我之前有研究过图数据库。
熊传亮:这是应用在我们的风控系统上,在做全球化的过程中,会有一些很有挑战性的风控问题,我们通过这个东西做,当然,这只是风控系统中的一部分,但是是很重要的一部分。
提问:你们的微服务没有谈到容器化,我想了解一下。
熊传亮:对。很多人会关心容器化的问题,目前来说我们的Docker只用在测试环境,但我们产线为什么不用容器?是有原因的,容器,我个人认为对公司现有的Go服务来说带来的好处主要有两点:一是服务的隔离,二是弹性扩容。首先服务的隔离,我认为在你的代码质量有保障的情况下,同时注意对服务进行合理分配部署,不会有太大的问题。另外就是弹性扩容,弹性扩容的前提条件是有足够大的资源池,才能在池子里面进行弹性扩容。我们也有和相关的人交流,包括也有请AWS的架构师交流,我们仔细评估了一下,假如说以现有服务的数量和规模做这方面的方案,可能公司的服务器数量要增长很多倍,它的收益比其实没有那么大,没太多的吸引力,因为对我们来说,哪些服务是热点,其实我们是清楚的,我们可以通过其他的途径达到这个目的,并不一定要很大规模的容器化。并且,Klook的微服务治理平台在我们16年的时候就做得相对比较成熟了,所以说也还好,不是很迫切。
提问:国际化的话有没有多个数据中心?
熊传亮:有,一定要有多个数据中心,每个国家和地区对数据安全和合规都有不同的要求。
提问:目前是已经有?
熊传亮:对。
提问:你们公司开始是使用Java,微服务用Go我不理解,Java微服务也很好,为什么要转去Go拥抱微服务?
熊传亮:那个是有原因的,我本人对Java和Go还是有些了解的,然后在了解以后经过评估,依Go的性能还有可靠性或者易上手之前来说,我个人倾向于用Go。用Java做微服务,以当时的团队来说,我觉得我们团队其他人和我都hold不住。你做新架构的时候要评估团队的能力。我之前PPT也讲过这一点,出问题的时候,你的团队和人是不是能hold住? 我认为我能hold住Go的技术架构,但是Java的那套我可能不是很hold住。
提问:微服务的时候,它有很多服务,每个微服务用的数据库不一样,你们这些数据库是跟服务走的还是搞一个数据库的数据中心,然后这些服务调数据中心的东西?
熊传亮:我们目前的做法,数据库根据业务边界的划分,跟着你的业务走,业务有很多的服务,这些服务可能调的是同一个数据库,要访问其他业务线的数据库,你要通过接口的方式调。
提问:我看到您有提到用了很多开源组件,对于这些开源组件他们相当于一个个服务了,使用过程中对它有高可用性要求吗?如果有的话,你怎么要求?服务多,面临的维护成本就上升了,您怎么处理这样的事情?
熊传亮:一个开源组件是否值得做高可用的中心就取决于服务的重要性,服务相对次要,我不会考虑它的多集群可用性的问题,有时候挂了就挂了,是可以接受的。大家可以看到最开始架构会有很多的开源组件,而我们的团队到目前都没有运维,我们的业务需求又非常多,实际上我们不会想花太多精力在开源组件的搭建和运维上,毕竟,你用它的目的只是为了使用其功能,而不是为了搭建和运维它。
提问:你们在做微服务化的时候,你们是怎么解决数据聚合和数据一致性的问题?
熊传亮:数据的一致性很重要,数据一致性取决于你服务的划分,这个东西是因为业务场景,在什么情况下用强制一致性的方案,什么时候用最终一致性的业务方案取决于业务场景。
提问:数据聚合和一致性,比如一个用户,单个用户中心,你各个微服务都会用到数据中心的数据,你怎么把这些数据聚合给前端用?
熊传亮:如何快速把数据展示给前端要转成服务化,首先请求会到对外的业务服务,然后会调用户中心服务,它是个基础服务,其他的服务都调用户中心这个基础服务来获取用户相关数据的,另外,如果它是很常见的页面接口,一般会直接调缓存服务,通过缓存服务来做这些事情。
2018年的 Gopher Meetup 将在杭州开启巡回第二站,这一次邀请了很多新的讲师给大家一起交流分享Go的使用经验〜
点击阅读原文报名参加