在大型多线程并发的程序中,logging是非常重要的,从Developer的角度看,我个人觉得什么东西都没有logging来的有用。这里要谈的是:
需要log些什么东西,以及怎么利用这些logging。
Pattern: Failure Detection
Randy在这里介绍的是一个利用logging进行Failure Detection的例子。
- Application servers log all requests
- Detailed logging of all application activity, particularly database and other external resources.
- Log request, application-generated information, and exceptions.
Log几乎记录所有发生的事情,特别是数据库操作,对于Search的app server,特别要记录发往搜索引擎的Query。所有资源的require和release都要记录,注意在记录中必须包括,发生的时间,发生的内容,发生的错误。我们还可以加入错误级别…当系统的复杂程度达到一定程度后,时间点非常重要,因为有些问题只在一定的时间内发生,我们如果把那段时间内涉及的所有的组件上出现的错误联系起来看,就比较能够在全局上对这个错误认识更加深刻。
但是到这一步,还谈不上Detection,只能说是当我们手动去进行trouble shooting的时候,我们可以查看‘犯罪’现场到底发生了些什么了。下一步就是要建立monitor机制,能够自动发现甚至预报错误。
- Messages broadcast on multicast message bus
这点是比较特别的,logging信息会广播出去,写到这里,估计大家也就知道大概的做法了,既然广播,那一定在别的地方部署有listener来监听这些logging,并且做相应的检查,一旦发现问题,马上报警。
- Listeners automate failure detection and notification
- Real-time application state monitoring: exceptions and operational alerts.
- Historical reports by application server pool,URL, database, etc.
这里还提到一点,我们保存了历史logging,这些是分析系统性能的很好的资料。这些数据是海量的,每天eBay记录的log超过1.5TB。我们还可以开发很多工具来方便的处理这些log,方便快速定位。
我自己还有一点理解,一般我们自己开发比较小的系统可能没有这么完善的系统,但是对于多线程,或者死锁之类的问题,log真的是非常有用,当然什么东西都不是免费的,log会对系统速度,磁盘都造成压力。所以写一个高效的log layer也不是容易的事情。还有就是学习一点perl对于分析log非常有好处。我把perl比喻成文本处理的瑞士军刀,短小精悍,方便快捷。
Everything Fails[Code Rollout/Rollback]
其核心思想非常明了:就是要支持Undo!
Pattern: Rollback
Absolutely no changes to the site which cannot be undone(!)
- Entire site rolled every 2 weeks: 16,000 application servers in 220 pools.
- Many deployed features have dependencies between pools.
- Rollout plan contains explicit set (transitive closure) of all rollout dependencies.
- Automated tool executes staged rollout, with built-in checkpoints and immediate
rollback if necessary. - Automated tool optimizes rollback, including full rollback of dependent pools.
简单的说就是因为系统太大了,首先要制定roll out plan,什么先roll,什么后roll,在roll out的时候当然是有工具的。其中需要牢记的一点就是一定要制定roll back plan。万一代码上线出问题,能够迅速的恢复到以前的状态。
Rollback非常重要,如果出现site issue。Operation Team最关心什么,他们最关心的就是能不能尽快的恢复正常,Operation并不关心到底是哪行代码出了问题,那是developer关心的问题。一般情况下,Operation会看一下,最好能马上解决,如果不行,那就先"保留现场",然后roll back,如果roll back以后系统正常了,那Operation会松一口气,然后再和developer一起去调查什么地方出了问题。
可见,如果没有roll back的机制,就没有退路了。整个site的availability就会下降。不单单是Code上面,每次我们做DB Schema的修改,都要写verification plan和roll back plan,就是这个DB的改动一但完成,如何来验证你所做的改动正得生效了,如果出了问题,如何回退。
我有一次碰到一个问题,假设当前的代码版本是100,运行了一段时间,我们发现有个BUG需要紧急Fix,然后我们将带有BUG FIXING的代码(版本101) roll out,但是我们一 roll out,就发现系统非常不稳定,查了一下,原来那个BUG FIXING带来了一个更为严重的BUG,怎么办?FIX那个新的BUG需要时间,但是如果Roll Back那以前的那个BUG就会再出现。需要马上做决策!最后还是决定,先roll back,毕竟site的稳定最重要,同时抓紧时间fix bug。对于以前的那个bug,在新的bug没有被fix之前,采用别的办法避免其出现。
如何避免其出现呢,这里倒是正好引出了关于roll back pattern的第二个例子: Feature Wire-on/Wire-off,并不是所有的时候都严重到一定要code roll back的。
Everything Failes[Feature Wire-on/Wire-off]
Pattern: Rollback
就模式来说,Feature Wire-on/Wire-off还是采用了roll-back的思想。我们看两种情况:
- 如果现在在新的版本的应用程序中新增了2个Feature,A和B,上线后发现其中有一个Feature工作不正常,或者由于Operation和Business的原因,希望禁用其中的Feature A,如果采用code roll-back的方法,A和B就会同时失效,如何禁用A,而保留B。
- 如果Pool A和Pool B同时有个新Feature要上线,但是Pool A和Pool B在新版本中的Feature互相依赖,如果完整的roll-out一个Pool需要1天,那无论是先roll pool A还是先roll pool B就不能解决依赖问题。
解决提出的两个问题是非常典型的两个问题,解决的思路就是:将代码部署和Feature部署脱离!
- Decouples code deployment from feature deployment
在具体实现上,我们为每一个Feature设定一个"软"开关,然后统一在一个地方设置所有Feature的开关状态。
- Every feature has on / off state driven by central configuration
- Allows feature to be immediately turned off for operational or business reasons.
- Allows features to be deployed “wired-off” to unroll dependencies.
如此一来,对于情况1,只需要简单的把开关关掉,那个Feature就wire-off了。对于情况2,我们可以把两个Pool的代码都roll 完,但是两个Feature都是 Wire-off的。然后等到要让feature生效的那一天那一秒,同时把两个feature wire-on。
最后要提到的一点是:
- Applications check for feature “availability” in the same way as they check for
resource availability
Randy举了一个例子,假设在页面上要显示一个widget,这个widget依赖于数据库A,和Feature B。必须两个资源都Available的时候,这个widget才显示出来。从应用程序检测资源是否可用这个意义上来讲,数据库资源A和Feature B是等价的。
对于如何检查资源是否有效,我自己有一点猜想,但是不肯定他们真的是这么做的。比如Feature,我们可以检查开关状态。对于如何检查数据库资源是否有效,会不会是采用Failure Detection的方法?还是说就是根据数据库返回的错误代码进行Error Handling?Randy在下一讲给出了答案 : Everything Failes — Resource Mark Down 。
Everything Fails[Resource Markdown]
Pattern:Failure Detection
前面已经提到过Failure Detection,那次给出的例子是如何利用Central Logging进行检测Application的问题。其实这里的基本思路是一样的,如何检测资源的Availability,比如数据库资源。
- Application detects when database or other backend resource is unavailable or
distressed- “Resource slow” is often far more challenging than “resource down” (!)
这里用数据库资源作为例子,应用程序发起数据库操作,失败了,但是一次失败很有可能是有偶然因素造成的。于是应用程序试图重试,如果重试一定次数以后还是返回同样的错误,我们有理由怀疑该数据库资源已经unavailable了,于是将该数据库资源标记为"不可用",同时报警。应用程序不会向标记为不可用的数据库发送数据库操作。
Randy特地提到了,资源使用速度非常慢要比资源彻底不可用难处理的多!所以在写应用程序的时候要注意这一点!
[注:]我这里倒是有一个问题:资源标记为不可用以后,由谁负责再将资源标记为可用呢,还有就是在资源标记为不可用以后,应用程序的行为是怎样的?
Pattern:Graceful Degradation
其核心思想是我们不希望"部分"失效导致"整体"失效。
- Application “marks down” the resource
- Stops making calls to it and sends alert.
- Non-critical functionality is removed or ignored
- Critical functionality is retried or deferred
- Failover to alternate resource.
- Defer processing to async event.
- Explicit “markup”
- Allows resource to be restored and brought online in a controlled way .
前3点写的已经很清楚了,我就不多说了。主要说一下第4点。我们有Automatic Markdown,为什么没有Automatic Markup。这是有教训的:
如果数据库服务器A因为Load太高而崩溃了,被Markdown,或者是因为A被Markdown后不再Take Traffic而缓了过来了,或者是因为我们采取了措施使得A 又Back to alive了。如果Application Tier使用了Automatic Mark up,那最坏的情况是什么呢?是eBay的16000台app server在很短的时间内将大量的Traffic又送到那台刚缓过劲来的数据库上,于是那台数据库在很短的时间内由于Load过高而再次极速死去,再次被Mark down,以此往复…down-up-down-uo-down-up…
所以,Automatic Markup是很有风险的,比较好的办法是当资源back up后,控制Application Tier中markup的速度和比例,使系统的恢复在有序,受控的情况下进行。
eBay Architecture–Racap
至此,Randy的关于eBay Architectural Principles的介绍都"翻译"完了,我从上个周末开始的,每天写一点,还是花了不少时间的。在重新看这些Slides的时候自己也很有收获,网上的Slides一共25张,除了最后一张Q&A以外,我一共写了24篇BLOG,是严格得按照Randy的PPT写的,一一对应。我应该没有透露不该透露的东西,也希望没有误导大众。最后,Recap,一共就是4句话,整个presentation都是围绕这4句话在谈:
- Strategy 1: Partition Everything
- Strategy 2: Async Everywhere
- Strategy 3: Automate Everything
- Strategy 4: Remember Everything Fails
不废话了,整个关于eBay Architecture的介绍到此暂告结束。
Thanks Randy~~