Ops @ Github:有关新特性推送、MySQL优化、Hubot和Puppet的那些事儿

个人简介 Jesse Newland,GitHub资深运维工程师。在加入GitHub之前,Jesse是Rails Machine的CTO。在那儿他负责一个规模庞大的私有云集群管理工作,包括维护、扩展和调整近千个Ruby on Rails程序。现在他居住在佐治亚州的Savannah。业余时间他喜欢弹吉他和钢琴。

QCon是由InfoQ主办的全球顶级技术盛会,每年在伦敦、北京、东京、纽约、圣保罗、杭州、旧金山召开。自2007年3月份首次举办以来,已经有包括传统制造、金融、电信、互联网、航空航天等领域的近万名架构师、项目经理、团队领导者和高级开发人员参加过QCon大会。

   

1. 您好Jesse,欢迎您来到QCon北京2013。

Jesse:谢谢。我非常高兴能来到这儿。

   

2. 第一个问题是有关新功能的灰度测试。你们并行做测试吗?如果你们这样做,你们如何确保不同的功能之间彼此不受影响?你们怎么使这个过程变得更加有效?

Jesse:我们其实不太做AB测试。对于差异化的测试,我们探索了很多的方法,我可以给你举个例子我们是如何测试新功能。

与其给不同的用户搞不同的特性和不同的实现,我们测试新功能的主要策略是在公测之前先让我们所有员工去内测。 所以,你用我的账号登录时看到的github.com跟你用自己账号登陆时看到的页面完全不同。我们现在正在测试重新设计过的一些基础功能,一些工作流,等等不同的方面。我们自己每天都使用我们的产品,可以说我们自己就是Github最重度的用户,这可能是我们跟其他产品开发团队很不同的一点。

我们现在这样所有员工每时每刻都在做测试,所以也就没去做AB测试。它实际上是个挑战,因为很多时候用户会问问题,支持人员会说, “哦,就在那儿,那里有一个按钮。”但其实那儿并没有按钮,因为它是一个新的设计,用户根本看不到这个按钮。

合理地添加些新功能是一种挑战,在使用现有功能时对它们进行增量改进更是一种挑战。在这方面我们做的还不是很好,我们在这块经常会返工。

Ted:我们最早的测试都在我们称之为lab的地方做的,你可以把它理解为staging环境,不过跟一般的staging不同的是,lab是我们线上大部分的前端和小部分后端的一个精确副本。如果你做的东西实验性很强,那你可以用lab来做。我觉得这个挺有用。

Jesse:这的确很有用。在员工测试之外,lab测试可以进行一些更基础的尝试,我们用它来做一些偏基础层——可以理解为系统层的一些改动测试,比如代理之类的。

我们的梦想是可以实现一个流程,让任何人都可以直接从Github的任何一个分支中直接创建一个lab服务器。现在的方式是,我们创建一条Github分支,创建一个请求说,我要把这个Github的新特性部署到lab环境上。这个分支不在master上,但是你去lab可以看到它长得什么样。

   

3. 然后就可以把这条分支合并到员工分支上?

Jesse:这个跟员工分支是两回事。Lab用于测试单独的、较大的改动。员工分支跟主分支是一致的,我们只不过用一个条件码来判断是否去对用户展示。

Ted:特性标志(feature flags)。

Jesse:是的,是这么叫的,这个说法应该是来自Flickr。它根据用户来判断,如果你是Github的组员(team),或者指定组织(organization)的成员,你会看到不同的特性。

Ted:Github员工显示些功能,其他人看不到。

Jesse:有时候我们想让某人提早看到某个新功能,就会把他拉入一个特定的组。不过一般来说,我们员工内测的强度已经挺大了,所以把外面的人拉入内测组是比较少的做法。

   

4. 所以从代码上来说,“员工版”和“普通用户版”是一致的喽?

Jesse:是的,只是通过代码中的条件判定来显示不同的东西而已。

Ted:我们使用的Github都在同一个生产环境,完全通过成员的员工角色来判定。其他网站可能用灰度比较多,比如先推5%,然后10%,给不同的用户展示不同的特性这样。Twitter就是这样的做法。

Jesse:他们用这样的方式滚动推出一项新特性。这很有意思,他们做的实际上是个很有挑战性的工作流,就好像在资本市场上那样,有些人很想要新特性,能够比其他人先用到他们就会很兴奋,而还没用到的人则眼巴巴的想要拿到同样的东西。但是,为什么就不能把新特性一次公开给所有人呢?如果你能做到的话,有什么理由不去这样做呢?所以,我们没有选择滚动更新的方式,而是两步走:推到生产环境上,但是只有员工在使用、测试,性能测试也在这一步完成;第二步,我们把新特性推给所有用户。

前一段时间我们推出一个叫做pulse的新特性,pulse可以让你看到你的个人仓库在近期的变更历史,你可以在一个页面上看到有关你所有仓库的pull request,所有的issue报告,所有的贡献者等等。这是一个效果很棒的概要页面,不过它会要求大量的数据库查询。

所以我们的做法是,对于仓库页面接到的每一个请求,我们都让它在后台触发一个在pulse页面的请求,这个请求不用做任何事情,请求完毕后即被丢弃。我们用这样的方式给pulse做负载测试。结果是pulse的性能表现正如我们所预期的那样,在正式推广至全体用户之前我们还成功的做了一些性能上的优化。

   

5. Github的代码每天大约推送多少次?

Jesse:记不清了。也许看看pulse页面能做一个估算?总之是很多。 InfoQ:或者这样说,每次代码变更后都会往生产环境部署吗,还是有什么策略?

Jesse:首先,我们鼓励大家多做小的提交,而pull request总是较好的提交方式。Pull request会生成一封邮件,代码审查通过、合并完成之后,你也会收到邮件提醒,你知道改了什么东西。而如果大家都直接往master推送,信息的同步就不那么容易做。事实上,即使你提交代码后不需要别人审查,也应该做pull request然后合并。

Ted:合并到master之后,系统会做自动部署。当然,新的master需要先通过测试。我们对于每个仓库都是这样做的。另外,配置文件自己有单独的仓库,跟网站的仓库是分开部署的。

Jesse:这个过程很酷:有个pull request提供了某个新功能,你只要点一个按钮,这个pull request就被合并到master,系统自动进行测试然后自动部署,你就能在网站上看到变更后的页面啦。

Ted:如果测试失败的话,这个新版本是不会被部署上线的。实际上我们的系统要求master一直处于绿色状态。

Jesse:如果测试不通过,合并这个操作本身会被限制。

Ted:测试不通过的情况,原则上是不能进行合并的。你也可以选择“谨慎的合并”,但必须要告知Github的所有人。

   

6. 好的,那我们继续第二个问题。在不稳定的网络环境下,你们用怎样的算法来识别网络中的抖动?

Jesse:目前我们的确遇到一些网络能力方面的问题,早上我们刚刚就一个问题讨论了很久。网络方面有几个经典问题。首先当然是过载或者接近过载。再就是在任何一层介质上达到饱和状态。在很多情况下,这些是真正的问题。

我们的网络完全是千兆环境,对于某些服务器来说这是不够快的,比如说,我们在生产环境的主数据库处理的请求量已经超过了千兆网络能够承载的量,这很糟糕。

怎么检测到这个问题呢?这里有几种方法,我们用了两种。首先,问题的症状之一体现为丢包。网络饱和会引起丢包,在一般的丢包症状中,你能够检测到中继发送超时(retransmit timeout)。在大部分Linux终端里,中继发送超时被硬编码为200毫秒。所以如果你的程序出现了200毫秒的停滞或滞后,那么多半是遇到了网络问题。所以,当你看到了3毫秒、3毫秒、3毫秒、4毫秒、204毫秒、3毫秒、3毫秒这样的记录时,你就知道发生了一些问题。这是其一。

第二个证据在硬件交换机上:我们对内网和外网都尽量去掌控,在所有的交换机上绘制丢弃(discards)和丢包(drops)的图表——思科应该是这么描述这两个指标的。我们对每个接口进行绘制,以及对每个交换机进行绘制。今天早上绘制的图表有一个不寻常的峰值。吞吐量达到饱和固然令人不安,不过最令人不安的是不寻常的峰值。有时候你看到一个稳定在400mb每秒的图表,你觉得一切正常,但实际上这个数据是经过统计的,因为数据收集可能是每10秒才做一次。 瞬时高峰对于网络而言是沉默的危险。每个千兆接口被标注为每秒容纳千兆流量,但一秒是一段很长的时间。如果你尝试在十分之一秒内传输千兆的五分之一的量,你是会被丢包的,但是图表上看去一切正常。

如果你一直发送200mb、200mb、200mb,然后忽然发送一个瞬时高峰,你会发现其中的一部分被丢包了,而通过检测常规的数值、延时、丢弃和丢包是无法发现这个问题的。对于这个问题,你可以尝试做优化,不过归根结底的解决方案还是网络容量——减少需要传输的量,扩大管道的宽度。

Ted:对我们来说,减少传输量是很难实现的。所以我们需要更宽的管道。

Jesse:还有一种可能性是换一些缓冲区大一些的交换机,这样的交换机会允许瞬时峰值通过,而不是把它们丢弃。然而,这有点杯水车薪的感觉。终极解决方案还是把千兆网络换成万兆网络。

Ted:就目前来说,万兆网络要达到饱和还需要很长的时间,我们有充足的时间。

Jesse:这个问题在系统运维中就属于单点故障的问题,我希望把它叫做“容量的敲鼹鼠游戏”。你应该玩过这种游戏,就是盘面上有很多洞,鼹鼠头会从随机的洞里冒出来,你需要快速把它们一个一个敲回去,但不管怎么敲,总有后面的鼹鼠冒出来。你解决了一个问题,下一个问题又会冒出来。用上万兆网络之后,我相信又会有新的问题浮现,这是必然的。

   

7. 你们是用什么工具发现这个问题的?

Jesse:我们用SNMP轮询的方式获取交换数据,大部分交换机都支持这个工具,你可以用它拿到一些接口计数。另外还有SNMP查询。不管怎么说,SNMP是个好东西,是一个有年头的协议,你可以在很多网站上查询到各个OID是什么意思——有些解释是错的,有些是对的,你得根据你的机器型号来猜测。当然,这并不简单。

拿到监控指标之后,我们将吞吐量、延迟、应用占比等信息绘制成图表。我们这次发现的症状是99%的问题出现在Redis上,而Redis应该是很快的,不应该成为瓶颈。然后,我们发现了200毫秒——这是一个神奇的数字,如果你玩Linux的时候发现这个数字,你就知道多半是网络容量的问题。

之前有些人建议把这个200毫秒修改成一个比较小的数值——我们可以定制内核来实现这一点,但其实这并不好,它并没有解决问题,只不过在短期内减少了这个问题的影响,长期来看会造成更严重的拥堵问题。再说了,很多时候就算丢包率比较高,你的网络仍然能够跑起来,所以200毫秒本身不是个问题。TCP协议的设计是非常赞的。

   

8. 下一个问题是,你们在采集分析流量的过程中都使用什么算法来识别异常?

Jesse:首先我们得承认,所有已知的监控方式和图表软件主要适用于识别已知的问题——将已知的指标绘制成图表,比如每秒请求数之类的。你只能针对你认为可能会造成问题的变化模式设置警报,但你无法为你无法预测的问题设置警报。比如说上次春运前,Github接到了很多来自中国某订票插件的请求。当时已经感到了网站访问异常,就拉了请求数的图表出来,当然是看到了一个巨大的峰值。

这当然不错,但就是发现的太迟了。Twitter用三次指数平滑算法预测峰值,不过要让这个算法派上用场,你需要收集的数据量相当可观,单单收集数据也需要花费不少时间,所以我们不怎么经常使用三次指数平滑法。这个算法一般在我们遇到了陌生的问题,不知道从哪里入手的时候用。有时候就算SSH连接数忽然上升了100%,我们也不是立刻能够发现这一情况的。

算法很好,但都不够即时。可是,我们要如何及时的发现那些我们从未在寻找、从未预测过的变化呢?

Ted:不管怎么说,如果系统里有什么东西改变了,一定会体现在某个可测量的数值上。可能某一天你发现某个指标忽然掉落到0,而这以前从没发生过,那么这有两种可能性:1、你的监控系统歇菜了;2、你遇到了一个新问题。

你可能对有些指标比较熟悉,比如流量突增,然后你发现数据库连接数在突增,SSH连接数或者HTTP连接数也在突增,那可能是人们在做很多的git clone,你就会把这一条放入触发警报的条件。可能出现的问题是无穷无尽的,因此我们需要进行各种各样的测试,看看不同的操作会触发什么样的反应。

Jesse:这种实验很有意思,这也是当前服务器监控领域的热门话题。之前在波士顿的一次会议上,“如何察觉陌生的故障”成为讨论的主题之一。设置监控系统可以发现已知的故障,但如果仅仅监测已知的故障而不管未知的故障,那你的工作并没有做完整。

目前来说,机器自己无法发现陌生的故障,这项工作需要人类来完成——将陌生的故障识别、分类、解决,把它们变成已知的故障。

   

9. 你们主要关注哪些指标?

Jesse:首先是前端服务的HTTP连接数。这个图表每10秒绘制一次。我们的服务器有两种类型,一种比较强劲一些,平时晚上都有5000的HTTP连接,到了白天的高峰期大约有12000个。每个服务器都有一个HTTP连接数的图表,我们只要联网都能看到,在手机上也可以看。

Ted:然后是MySQL QPS。

Jesse:MySQL QPS在25000是比较稳固的。总之出于某种原因——可能是网络限制或者是数据库本身的限制——我们的MySQL到了这个量的QPS就到头了。

Ted:其实也是可以往上顶顶的。

Jesse:当然,我们可以换一些SSD,或者换一些网络设备,这个数字再翻番应该是没问题的。但是再怎么说,MySQL是一个神奇的软件,我们在它身上没少折腾。现在它跑的很好,这就够了。

   

10. 一般遇到问题主要是因为什么引起的?

Jesse:订票系统这次事故发生的时候是我们这边的午夜,当时我还在睡觉,也不会有什么人去服务器上变更什么东西。大部分问题都发生在部署了新代码之后,有些新特性会造成奇怪的行为。

话说回来,很多事件倒是跟外部流量有关,比如某人做了个热链(hotlink);有一批人在同一段时间内跑进来请求某个repo的数据库——我后来猜测这可能是他们在一起开某个技术大会;另外我们的API也很有意思,人们会用我们的API做一些意想不到的事情,比如在AWS上起好几百个EC2实例然后一起过来请求——我们后来找到他们,沟通出来一个更轻量级的做法。大部分峰值都不是人们有意为之,只不过是互联网混沌本质的一个副作用。把它们定义为“随意滥用”太重了,我们也并不会因为这些情况指责别人的行为是滥用。

Ted:的确如此。从长远的角度看,我们所有人都必须适应这一点——倒不是说是要适应滥用,而是要想方设法达成“优雅的失败”。Github本身是一个很特殊的网站——将SSH做成服务并非是大众网站的做法。SSH在设计时并没有考虑到成千上万个连接同时跑到一台服务器上的情况,所以我们遇到的很多问题都是别家没有的。

Jesse:可以说,我们使用软件的方式和场景都不在该软件设计师的构想之内,所以我们必须更加深入的理解这些软件,才能把它们改成我们想要的样子。

   

11. 下一个问题。你们是如何处理慢查询的?

Ted:问的真是时候,Jesse最近正巧做了很多数据库的工作。

Jesse:首先,慢查询的诊断我们就用MySQL自己的日志。MySQL自己就有一个慢查询日志,另外还有top之类的工具。有一家叫做Percona的开源公司做过不少开源工具,我们也用了其中不少,其中有一个工具叫做pt-stalk,你可以在这个工具设置数据库被认为出现异常的条件,这样一旦出现异常,这个工具就会把很多有用的数据都dump出来,包括当前写入的进程、有关当前机器的信息、内存什么的,以及关于当前执行中的请求的相关信息。

总的来说,我们跟踪数据库中所有可能出错的情况,我们收集了有关各种查询的各种信息,实际上我们发现我们的数据库还是很少出问题的——我们写的查询语句居然(令人意外的)不怎么糟糕。你也懂的,所有的互联网公司或多或少总会写出一些那样的查询语句,毕竟当你开发新特性的时候,很少会去考虑到性能优化什么的,所以我们的查询语句能达到这个水平还是挺少见的。

对于我们来说,我们的MySQL遇到的问题都比较直接:MySQL性能的关键在于你的内存——或者说缓存区——能够存多少东西,所以我们优化MySQL的主要方式是给机器加内存。当然,我们不想为了升级而去关机,所以我们在内存热插拔的实现上下了不少功夫。

Ted:说白了,如果你的内存比你的数据库更大,那它必然是很快的,你当然也就不用处理什么慢查询的问题了。你可能找寻各种方法优化你的MySQL,但归根结底的解法就是去买更多的内存。如果你的数据库比你的内存大,那就把它分片到两台机子上去。

Jesse:包括分片在内的大部分数据库优化方法最终都是为了实现这一目标。一个数据库终归能放多少数据呢?毕竟你不太可能给一台机器装上一个T的内存……

Ted:除非你不差钱。

Jesse:不差钱的话也许可以,但也未必。总之你只好找20台机器拼到一起,一台装50G内存,这样就解决了。

除了查询之外,你的表结构优化也很重要。表结构越简单就越容易优化。

   

12. 最后一个问题。你们的Hubot是如何跟Puppet交互的?Hubot跟MCollective相比有什么不同?

Jesse:正如我在演讲中演示的,目前我们用SSH让双方交互——发起几个SSH进程,通过SSH通道运行puppet,然后将运行结果返回给hubot。执行起来就是一条hubot指令,在本地执行一些脚本,这些脚本通过SSH跑到其他机器上做一些事情。

此前我们评估并试验过MCollective跟puppet的方案,也试过其他的分布式系统,但是实话说,按照我们当前的规模还犯不上去用MCollective。SSH连接可能不好,不过现在我们用起来并没有啥问题。也许几年后的某一天我们会碰到这个问题,比如服务器数量太多、太过分散、SSH已经无法稳定的提供服务,到那个时候再考虑MCollective也不迟啊。

Ted:基本上我们用的东西都是Unix子系统的工具,比如用SSH做连接,用rsync做同步。这些工具非常简单,非常有效,标准统一。我们用这些简单的东西构建我们的基础架构。

Jesse:而且,它们容易解释,容易阅读,新来的工程师们可以很快掌握。我们用rsync在不同的文件服务器之间转移repo,新来的工程师只需要了解rsync就能理解这是怎么做的。如果他看不懂,还有rsync的man手册,现成的。总之,也许我们可以做出一个比rsync更好的工具,但因此导致其他人看不懂我们在做什么就得不偿失了。

Ted:再比如数据分析。现在有很多数据分析的工具,非常复杂,但如果你的数据就10M,直接用sort就可以了。有时候数据量比较大,但你仍然可以把它们分解处理然后再用sort。比如你想查看你10万台机器的负载,可以先对这10万做一个相对排序。总之,对同一个问题,有些人写三行代码就搞定,有些人则弄出来很复杂的解法,其实没有必要。

InfoQ:非常棒,感谢两位接受我们的采访。

你可能感兴趣的:(Ops @ Github:有关新特性推送、MySQL优化、Hubot和Puppet的那些事儿)