设计软件有两种方法: 一种是简单到极致而明显没有缺陷; 另一种是复杂到极致以至于没有明显的缺陷。前者要难得多!
—— C.A.R.Hoare
本篇开始!
灰度发布 和 A/B testing
很多朋友应该对Facebook首页和人人网首页改版的例子印象深刻。这里Facebook使用的一大法宝就是灰度发布和A/B testing。这一利器像宙斯盾一样(想不出好的比喻),多次将Facebook从出错的悬崖上拉回来。就好像上一篇里面所说的那样:
即使像 Facebook 这样的航母,在创业的大海里还是犹如“盲人”一样,很多产品的改动没人真正知道方向到底在哪儿。所以这里采用的方式就是 “Everything must be tested”。在灰度发布后,data dashboard + A/B testing 就犹如航母上的雷达或者声纳一样,对于方向和航线起到验证作用。
所以来重点介绍一下Facebook的这台雷达系统。
Facebook早在2007-2008年就在网页服务端(PHP)上开发了这套 发布和测试系统,代号叫 GateKeeper (最早在Boz的文章中提到),本质上它就是一个开关,可以在一个admin page上定义一个个的开关,然后控制某些开关到底是开还是关。这些开关的属性预先都缓存在内存中,所以读取开关的操作不重。示例代码如下:
主要的逻辑就在 if 中,判断这个开关是否对相应的用户开启,如果是则跑实验代码,否则跑老代码。非常直接和简单对吧!后来,Facebook又陆陆续续对它进行了各种加强,让其可以更加精细地分割和控制用户,比如说 对于US的1%用户开放,或者对于 日本的年龄25岁以下的男性用户开放,等等。可以从时间,国家,加入日期,好友数,是否为FB员工,性别,年龄等等各种维度进行控制。
这极大方便了我们对于用户分批进行 A/B testing。Gatekeeper(简称GK)对于Facebook的整个internal tools组来说一个很重要的基础设施。随着Facebook用户量的增大,每个GK每日的被访问量也大大增加;同时Facebook自己的功能和相应的GK项也不断增加, 这对于整个GK的规模能力后来也提出了很大的挑战。
到了移动时代后,iOS和Android的 core team 也相应地推出了比较强大的移动灰度发布和 A/B testing 工具,代号叫做 Airlock ,其中我们的一位中国工程师也参与它的开发。当然,类似的工具还有不少,比如 Twitter 开源的 Clutch IO 。移动上的 Airlock 系统稍微复杂一点:
首先,用户在手机上登录或者打开Facebook App后,airlock会从FB server取得所有的GK值;然后在本地缓存起来;
然后在 iOS 或者 Android 代码里写相同 if 判断逻辑,来检查当前用户是否已经开启这个属性。是的话,跑试验代码;否则跑老代码;
然后app每隔一段时间去和server同步一次(FB用的是一个小时的间隔);当然app随时也可以强制去取server上的最新值。
最关键的是:移动端的logging会把当前用户的每个GK的值记录在logging中,这样当这些logs被上传到server后, server可以根据这些logs来统计用户的GK值和相应的动作。
回顾来看,移动上的灰度发布和 A/B testing 本质就是要在本地代码加入一个库,来负责和server同步所有开关的值,以及在logs里记录好相应的这些开关,便于后来分析用户的行为,来了解此用户是暴露在怎样的开关组合中。
案例一:Facebook iOS app的演化
下面来说一下iOS下面的Facebook app界面演化过程。众所周知,Zuck 和 Steve Jobs 的私交一直不错,Zuck 也把 乔帮主 当做自己的模样,私下里经常去乔老爷家里共进晚餐和请教 run company 的窍门。所以,用 iPhone 第一版SDK开始,Facebook就有iOS原生应用开始入驻App Store:
可以看到iOS还是拟物化的风格,Facebook用的是当年红极一时的九宫格首页。消息提示在首页的最下面,当时Facebook还没有 Like button,只可以comment。此版本的Facebook app为最初一版,由一大牛独自完成。此大牛把后来的常用组件开源成 Three20 库。
后来Facebook经过一次大的UI改变:
最大的变化就是九宫格改为了左侧抽屉式的导航栏,左上角出现著名的”Hanburger”按钮。
于是来到2013年,Facebook app准备进行新一轮的大改版,由左侧抽屉式改为 Tab bar 格式:
这一版的改动,在意义上主要是让用户可以更加方便切换到news feed以外的其他功能区;可是却引发了另外一个问题:到底下面的tab bar放几个按钮?每一个位置上应该放什么?
此时已经是2013年,前一年在Facebook在WWW首页的改版失败依然影响着公司的engineering team,于是在iOS app决定更偏好保守和务实。Airlock在这次的改版上起到巨大的作用。Facebook iOS core team的人写好了tab bar的代码后,并没有马上发布给所有用户,而是开始了长达4个月的灰度发布和A/B testing;测试了下面 tab bar 各种可能的情况:比如 5个tab项 或者 4个?
第二个放 Requests or Messages? Notifications 或者 Groups 暴露在外面? 同时对于右上角的按钮的功能和样式也进行了测试,比如是放 通讯录 还是 Messages? 是放一个 图标 还是 直接写字 等等。一度因为出现新的测试组合,以及对于好几个组合的测试结果在数据上的比较模棱两可而把发布时间一拖再拖。整个iOS app界面重组的项目由 Mick Johnson 主导,他是我见过执行力最强的Facebook的几个PM之一。
他认真审视了各种组合的数据后,结合Facebook当时要大力推行Messenger的大背景,最后确定上图的组合顺序。这个组合在各种测试中数据的综合表现最好,能够有效地让用户查看news feed,增加用户的好友数(好友数是从Facebook data组里试验出来的一个非常重要的指标,这在文章最后可以和大家介绍),方便地收发消息,以及查看新消息,但是 groups,events,还有其他一些辅助功能被藏入了 “more” 之中。
案例二:Voice message 的发布过程
下面拿我之前负责的 voice message(语音消息)功能在Facebook messenger中的发布来分解一下整个Facebook 灰度发布和 A/B testing 的过程:
同理,在 iOS messenger 中,用户一登录后(以及以后的每一个小时),iOS messenger client都会和server通信一次,拿到所有 gatekeeper(控制属性)的值,然后缓存在本地。Messenger上重要的新功能在发布之前都要放在一个GK(gatekeeper)后面,根据Server端的设置来控制每个功能在 网页,iOS和Android端 的打开或者关闭,然后通过控制GK开启的范围(用户的百分比)来实现功能逐步开放给所有的用户。
整个功能的发布过程分解如下:
1.准备阶段:写好之后代码都已经在App里,且提交给app store审核通过,但是GK为关闭状态。等到上线后,先看拥有版本X的代码的App在关闭情况下的表现;这时程序的这块功能逻辑应该和上一个版本保持一致,并且没严重的不稳定性。
员工测试阶段:先将其GK开启到 Employee 20%~50% (对于普通用户仍然关闭),看在员工群体里新功能的表现情况。一般这个过程是几天,甚至是几周。我们把功能开启的员工群叫做 实验组,功能仍然关闭的叫做 参照组。对比两组群体的核心数据表现,比如Facebook App的话一般是看用户的session length(App使用时长),news feed engagement(like或者comment数量),广告显示时长和收入指标 等;而Messenger则是看 平均发消息数,消息收发的耗时;另外对于性能方面相关的功能还会看:App启动时间,核心功能打开时间和App耗电量等数值。一般说来,对于重要功能,会在发布前就会对这些数据的变化进行一个预测,对于不符合预期的变化会重点排查。
小范围发布阶段:等到在员工群体里,新功能被证明是有效,稳定,无害的时候,Facebook会将GK开启到1%~5%的用户范围。这里主要是两点考虑:
a) 对系统进行压力测试,对于一些新的后台功能(比如:语音消息,VoIP电话,视频聊天,或者 转账功能),看服务器是否能承受地住这么大的用户流量。一般来说:5%,10%,30%,50% 是常见的几个压力测试坎,过了50%基本上没有问题;
b) 看用户和媒体对于这个功能的评价。一般来说,PM会收集TechCrunch,VentureBeat,Wired上面的媒体评测,发到内部群里给大家分享;也有在Twitter上面搜索用户对此功能的讨论。
全部发布阶段:等到服务器确认能抗住所有流量,这时会将GK开到95~98%的用户,同时依然保留2%的参照组作为对比用。这个阶段最重要的是看 data dashboard 上的各种监控数据,看是否和其他预期的一样,至少一些关键的指标不能出现下滑。
收尾阶段:技术人员开始修改程序代码,把相应的GK和旧功能代码删除,这样下一个或者下下个新版本将拥有纯粹的新功能X代码。这一步,一般会经常1个月或者几个月的时间,而且最终纯粹的X代码发布会很谨慎,因为一旦上线给用户,功能X的出现会不受Facebook server的控制;假设突然出现App端的恶性bug,Facebook除了马上发布一个新版App,等待App Store审核,同时拜托用户马上升级之外没有任何办法。
综合来看,灰度发布 (shadow release) 和 A/B testing 的特点和注意事项:
前端代码预先发布;且有可能同时多套不同版本的代码存在;
移动端或者前端MVC的代码需要按照一定频率获取来自服务器的控制信息,从而展示相应的形态;
后台可以对发布进行精密的控制。比如说:发布给多少比例的用户,用户所在区域等;并且即使在开放给100%用户后,只要 GK检查 没有清楚,则server仍然可以远程控制(或回滚)试验中的功能。在某些严重bug出现的情况下,可以完全回滚某一功能。
注意:由于有A/B testing的原因,不同用户A和B坐在一起,都刚刚下载和安装了最新的Facebook App,但是打开app后所看到的功能(甚至UI布局)却可能不一样;并且对于同一用户,今天看到的app里展示的功能可能和明天展示的也不一样,即使他并没有更新自己的app。
其实,微信早在几年前就已经借助此方式来进行测试和数据采集(微信团队的主力在2012年时来Facebook访问,当年我们FB的一些工程师和他们的一些工程师+张小龙有对于产品和技术上的深度讨论)。比如说微信的一大特点是右上角的快捷菜单:
上面的选项和顺序就是在server端进行控制,如果它想,随时就可以改变。而且也有可能不同人的不一样(比如我的微信是在北美下载和注册的,我的列表进行少一些,另外我的发现里面的京东购物也比中国区的朋友晚出现了将近一年;另外我从来没看到过任何朋友圈广告)。
Apple官网其实不支持这种server远端控制app client的逻辑,因为它对于App store的审核有很大的阻碍。但是Apple一直对此一直睁一只眼闭一只眼。
iOS程序员都知道,Objective C是纯动态语言,所有的函数都是虚函数,也就是说任何函数调用都可以在runtime时改变(特别有变态的 method swizzling 机制)。这就导致了当用户下载了你的app之后,可以本地hack你的GK开关。之前就有很多次,Facebook发布了一个新版本后,那些iPhone界的破解高手就开始研究Facebook app,然后找到好些GK值,人为地把它们打开,然后尝鲜未开放的新功能;有时还会把一些功能的使用体验写到博客里,或者发给 TechCrunch 用与报道。后来使得Facebook内部把比较重要有战略意义的功能,直接用 compile flag 将其从 product build 中剥离。
最后 ——Don’t reinvent the role
最后也是最重要的一点:从上面可以看出,对于整个灰度发布还有A/B testing,不管是产品本身还是做灰度发布的系统,都有着不小的开发量。也就是说:灰度发布+ A/B test一方面 会减慢产品迭代速度,另一方面会大大提高迭代稳定性的。这对于特别是初创公司(成立半年以内,用户量不大的)要慎用(或者明确说来:禁用)。
然而对于发展得不错的创业公司,特别是在同质化竞争比较严重(中国特色)的时候,要尽快地开始使用 A/B test。比如:Nice和In,去哪儿和携程(合并之前) 这种。在做 A/B test 方面,我觉得国内做得很不错的创业公司公司是 AppAdhoc;创始人王晔有着很强的技术背景,之前一直在 Google mountainview 效力。公司网站:吆喝科技 AppAdhoc。 我之前去看过他们产品的界面和使用,和FB的inhouse系统相似度很大;个人还觉得他们的UI更加接近于大家习惯的 bootstrap 风格,而不是 FB 自己的蓝色风:吆喝科技 AppAdhoc(感兴趣的公司,可以我直接帮你们推荐 :D)
— END —
本文作者:覃超,公众号: qc_empire
– Do have the faith in what you love