前言:这是我最近在公司内部分享的一篇文章,大家反响比较强烈,所以也分享到博客里来。

一转眼,来公司已经三年多了。

这三年里,所属部门在变,地理位置在变,技术也日新月异,但是有很多设计原则却是一直不曾改变的,而这次就是我用自身的实践来谈谈我对其中的一个的理解—有损服务。

记得当年qwang用一个很形象的比喻来解释有损(原话记不太清楚了):

比如一个人在沙漠里迷失了寻找水源,那么在他还能走的时候,就尽量走;实在走不动了,用爬的;最后爬也爬不了了,起码要保证自己活着。

所以我们从这个比喻中起码可以获得如下几个信息:

  1. 问题时,优先保证关键功能
  2. 非关键功能不可以影响关键功能
  3. 在条件允许的情况下,损失越少越好

接下来就从自己印象比较深刻的有损服务项目讲起吧。


一、空间应用列表有损服务优化

想当年,苍井空还是处女,玛利亚还姓圣母。好吧,扯远了,想当年第一款国民级应用《QQ农场》横空出世,其空前的火爆导致空间个人中心应用列表的农场图标变得如此重要。

然而由于各种网络等各种原因,这个列表的展现总是会有一定的失败率,而且只要稍微失败就会招来大批用户的投诉。

我们分析一下这个模块的功能:

  • 正常功能:正常展示用户已经安装的应用列表
  • 关键功能:正常展示用户最关心的基础应用(如日志)、火爆游戏(农场)等的应用列表

于是优化开始了……


Step1. 应用信息本地cache

由于应用列表第一个要获取的就是应用自身的信息(包括URL,名称等),而这部分数据本身又是较为固定的,所以就直接cache在了webserver本地。一旦当网络拉取应用信息失败时,就会使用本地的存储。

这样做了之后,其实严格来说体验上并没有任何损失,但这才是第一步。


Step2. 忽略过滤已安装应用列表失败

接着我们发现过滤已安装应用列表接口也存在一定的失败,之前采取了简单而粗暴的方式,一旦报错则直接返回。

然而后来我们考虑到,如果这个时候我们返回一个默认应用列表,同时在这个默认列表中加入那些平台基础应用和火爆应用(农场),是不是体验会更好呢?

再然后我们尝试了,确实证明了这样带来的效果更好。对比一下:一个是想玩农场找不到入口;一个是没装过农场,但是看到了农场的应用图标。高下立分。

这是体验的第一次降级,我们尝到了甜头。


Step3. 前台协助

但是我们很快发现,只有上面的方案是不行的。

  1. CGI在调用后端接口时,如果接口超时,很可能会导致CGI超时,而前台JS此时很可能还没有等到CGI的默认应用列表返回就已经向用户报错了。
  2. 由于网络问题、webserver异常等原因,CGI没有接收到请求,也会导致默认应用列表获取失败。

所以我们马上联系前台同学优化了两个逻辑:

  1. JS调用CGI的等待超时,与CGI调用后台接口的超时对应
  2. 一旦CGI超时返回,则在前台也会存储有一份默认的应用列表,直接展示给用户

在这里,我们把有损的设计从后台延伸到了前台,并再次证明好的设计一定是前后台共同实现的~再次感谢当年鼎力相助的晓晓同学~


Step4. 闭环

然而,有损服务毕竟是对体验有影响的,此时如果不对用户做好提示和限制,就会导致用户使用很多功能报错,反而还会增加投诉的数量。

比如用户如果在有损的情况下去编辑应用列表,或者添加应用都会报错,如果没有限制又没有合适的提示,用户很可能会认为自己数据丢失,招来投诉。

所以我们又做了几件事:

  1. 有损时优化对用户的提示,告知数据可能不准确
  2. 限制写操作,如编辑,添加应用,都被禁止,并明确提示原因

OK,到了这里,应用列表的整个有损服务优化就基本告一段落了。虽然后来我们优化了server,提高了成功率,但这里的有损逻辑却被永远的保存了下来,毕竟,什么样的系统能完全没有错误呢?

也正因为有这个项目的铺垫,为我后来做OpenAPI设计时的有损奠定了根基。


二、OpenAPI有损服务优化

OpenAPI是平台与应用之间沟通的桥梁,因此对可用性的要求极高,因此当部分功能出现问题时,保证有损服务,也就是必然的事情。

先来简单看一下OpenAPI的架构图吧(这个之前在QCon已经分享,所以不算泄密~):

1

当请求进入OpenAPI接口机的时候,接口机会根据参数、URL分别将请求转发到对应的业务CGI,之后再经由接口机返回给调用方。

所以我们再次开始有损优化之旅:


Step1. 业务之间互相屏蔽

从描述可以看出,由于不同业务的CGI都挂载在接口机上,所以一旦某一个业务出现问题时,势必会影响到其他业务。

所以我们对每个业务都分配了单独的L5 ID,当失败量或者超时量太高时,webserver的IP分配就会失败,从而保证业务之间不会受到相互影响。


Step2. CGI运行最长超时设置

刚才提到OpenAPI对性能的要求极高,所以要求CGI都能尽快的返回,否则就会被Step1里面的技术打击到。

但是具体怎么做呢?

我们将有损服务具体化为一个CGI设计原则:

在能容忍的最长时间内,将最重要的事做完

比如下图:

1

当我们执行到3的时候,发现CGI的运行时间已经太长了(比如超过1秒),那么为了避免其他请求被堵死,我们就直接直接返回给调用方了。

这个时候虽然数据不是完整的(丢了4的数据),但是我们在数据完整和快速响应之间做了一个平衡。

这样就保证了在服务出现问题的时候,大部分的应用还是可以正常使用,只是体验上稍微差一点。


Step3. 智能调整最长超时时间-EMA算法

但是我们很快发现,仅仅做到这里还是不够的,我们刚才提到了能容忍的最长响应时间,但是这个最长响应时间的值怎么指定呢?

如果指定的很长,比如1秒,那么一旦出现问题的时候,相当于每个进程每秒钟只能处理一个请求,根本没有达到我们预期的容灾的效果。

但如果指定的很短,比如20毫秒,那么一旦出现一次偶然的网络波动,即使很快会恢复也会导致我们的OpenAPI大面积失败。

这两种设置方法都不完美,那么还有什么办法呢?

那就是EMA算法,公司之前将预测股票走势的EMA算法引入来预测CGI运行时间的变化,而EMA的一个核心原则就是:

当CGI运行时间越短的时候,给CGI设置的最长超时时间越长;当CGI运行时间越长的时候,给CGI设置的最长超时时间越短。

如下图所示:

1

可以看出平均响应时间和动态超时时间基本是沿响应时间上限 对称的关系,很直观的描述了这两者之间的关系。

所以到此为止,有损服务才能真正的发挥作用。

以上就是我在腾讯这三年里对有损服务的一些理解和实践,希望能对大家有所帮助,也更希望是抛砖引玉,和大家碰撞出更多更好的火花。

关于柔性服务的一些实践和思考

最近花了大力气在做openapi的优化,使其尽量柔性可用,借此也有些想法想和大家分享一下。 柔性服务,google一下,在网上并没有这样一个标准的概念,所以应该...

阅读全文

15则回应给“有损服务-不完美主义者的胜利”

  1. spock说道:

    既然是公司内部的文章,你将其公开在自己的博客上不会违反公司的规章制度吗?

    [回复]

    Dante 回复:

    里面的内容并不涉及到公司机密……
    如果这也算泄密的话,那只能无语了。。

    [回复]

  2. 广东省企业信息中心(http://www.8sme.com/)
    PR=4,诚交贵站友情链接。
    可以链接请回复下,谢谢。
    QQ:24387481
    E-mail:24387481@qq.com 谢谢。

    [回复]

    Dante 回复:

    链接已经添加~ 谢谢

    [回复]

    友情链接 回复:

    你好,贵站的也已经添加,合作愉快。

    [回复]

  3. Heiher说道:

    贵博的文章太好了!

    [回复]

    Dante 回复:

    多谢多谢~~

    [回复]

  4. Heiher说道:

    为什么要大量使用CGI做前端呢?

    [回复]

    Dante 回复:

    嗯?是我没理解你的意思吗?互联网服务都是用webserver做前端吧?

    [回复]

    Heiher 回复:

    哦,是这样的,我的意思就是像PHP、Python等等这些做前端和编写CGI程序,后者更有优势吗?

    [回复]

    Dante 回复:

    用C、C++的话,性能会稍微高一点。。但是开发能力却会下降非常多,所以用C、C++写CGI纯属历史原因。。。

    [回复]

    Heiher 回复:

    有道理,软件开发毕竟是一次性的,硬件也是很大的成本。

    [回复]

  5. moper说道:

    这篇文章很好啊,不过还是喜欢完完整整的,呵呵~

    [回复]

  6. meteoric_cry说道:

    写的很棒,不知道是否可以引用或是转载那句形象比喻的话。

    [回复]

    Dante 回复:

    哈哈,过奖啦~~
    随意随意哈~~

    [回复]

发表评论