一切尽在掌握

长兄于病视神,未有形而除之,故名不出于家。中兄治病,其在毫毛,故名不出于闾。若扁鹊者,镵血脉,投毒药,副肌肤,闲而名出闻于诸侯。———— 扁鹊

对于一个7*24小时对外服务的架构,一定要对服务的各个方面极致的掌控,做到“一切尽在掌握”,面对服务心中不是模糊的、可能的,而是确定的。下面通过各个方面来说明如何做到一切尽在掌握。

下面从一个设计一个服务的各个方面来简单介绍一下如何做到尽在掌握,开发一个服务,分为几个阶段,每个阶段都有自身着重需要关注的点,这里从 设计之初->开发过程->发布过程->上线运营几个方面来介绍,其实开发阶段能够掌控的非常有限,大多数的问题和要点都需要在设计之初关注,而且可以发现,这篇文章所介绍的要点大都是防御性方法,可能会让人觉得谨小慎微,但对重要的服务而言,正需要开发者怀着敬畏之心。

性能

对于一个Server,很多人都会觉得性能是一个服务最重要的方面,有时甚至会认为写一个服务就是尽可能提升性能,性能越好,服务就被评价为写的越好。但其实不然,首先在当前的硬件水平和操作系统提供的优化的基础上,想要实现一个不错的性能的服务,是轻而易举的,而且目前开源框架非常成熟,选择一个适合自身服务的也非常容易;其次,出现性能问题往往在于架构的设计上,而不是单机所能处理的性能,在互联网时代,基本上不存在只有单机的服务;再者,如果想提升一倍的性能需要很多人力很长时间,但是如果服务设计能够水平扩展,想提升一倍性能只要增加一倍机器即可;最后,虽然每个人都希望服务的性能尽可能好,但真正需要很高性能的服务却并不多,并不一定性能越高越好,适合业务需要的性能才是最好的设计,设计一个服务要做到低成本高可用。

但是性能是不是不重要呢?当然不是,尤其是面对海量用户的服务,比如任何一个模块都上百台机器,增加一倍的性能成本消耗是非常可观的成本,大多数开发者都会写Demo进行性能验证,但其实在设计之初,就应该对服务的性能情况了如指掌。

对性能时常有个误解,通过测试数据去优化,但是性能测试往往滞后,其实要在设计之初做好规划,要对系统、组件、依赖的性能都了如指掌,未必要了解期代码,但必须了解其实现。

比如性能调优之函数调用硬件性能参数两张表,一些常用的操作或者逻辑性能,需要了然于胸,这样在设计服务的时候,可以快速的在脑海中定位到性能瓶颈,并预知到服务所能处理的请求量。比如锁操作,每秒只可以执行10000000次,远大于所能处理的请求量,真正应该担心的不是锁造成的开销,而是某一资源的竞态。

设计一个服务,在设计阶段需要预先计算以下指标:

  • 延迟:每个请求预计的耗时,这里主要计算网络耗时和磁盘耗时
  • 每秒处理能力:需要考虑服务的性能瓶颈,预算处理能力,这个指标也是衡量一个服务的最重要指标
  • 容量:一台机器能够存储多少用户数据,总共需要多少存储,能够存储几天,增长曲线如何等
  • 吞吐量

架构设计

缓存

缓存是服务性能的加速器,一个好的缓存方案能够极大的提升服务性能,设计缓存需要考虑几个方面:

  • 缓存的命中率:对某些运算结果进行缓存或者某些数据缓存,在设计的时候要预估命中率
  • 缓存的更新:缓存的数据是否能够得到及时更新,更新时延是否能够满足业务需求
  • 缓存重启:当大面积缓存机器重启时,是否会对后端服务造成很大压力导致雪崩

缓存具体的设计方案与业务息息相关,需要针对不同的情况缓存合理的数据。

伸缩性

服务提供方也许经常会由于做活动,或者某些爆发事件导致请求量急剧增长,面对这类服务,要考虑很好的伸缩性,在需要扩容的时候,扩容的时延级别是什么样的,分钟级、小时级、还是天级。

对于无状态服务,扩容往往很简单,增加机器并部署即可,区别在于这个过程是否需要手工干预,但对于有状态服务,扩容时需要把数据进行分片复制,除非是做分布式存储的业务,否则建议尽量使用适合业务模型的可以动态扩容的分布式存储。对于业务方,更需要考虑缓存的动态扩容,比如使用一致性Hash,尽量减少增删机器时缓存的失效比率。

另外根据业务的不同,有些业务可能会瞬时的请求量暴增,比如QQ业务,在除夕夜会造成请求量突增,但由于原本体量过大,不会有很多倍的暴涨。但对于微博则不同,预留几倍的性能空间是非常必要的,又比如秒杀业务,瞬时请求又会高的惊人。事先考虑伸缩性以及预留足够的扩容时延空间,可以很好的应对请求量和用户量迅速上涨的情况。

重试/过载保护

在调用一个服务接口时,会出现三种情况,成功、失败、超时,其中成功和失败相对容易处理,但对于超时,可能对方已经操作成功,只是还未来得及回包,或者处理失败,对于调用者来说无法知道是哪种情况,而且为了服务质量,提升成功率,必须要做重试处理,当然,有了重试就需要去重,服务提供方需要有机制确保同样的请求调用多次不会出现问题。

当然,如果服务提供方由于请求量暴涨,或者内部处理异常的原因导致无法处理当前的请求量,比如服务一秒钟只能处理8k请求量,当请求量达到1w时,表面上看起来有2k请求量无法处理,不过由于请求方重试,导致下一秒请求量增长到1w2,而服务还在处理上一秒的2k请求,这样会导致处理方永远处理的是已经超时的请求,而且随着请求方重试,又会导致请求量不断上涨,造成“雪崩”,所以在设计服务的时候,需要考虑雪崩情况下的过载保护,由于处理能力已经跟不上请求量,造成2k/s的请求失败是必然的,要果断抛弃不能正常处理的请求,起码能够服务百分之80的用户。

冷热分离

有些情况下为了提升性能,又需要把冷数据和热的数据分离,根据需要可以对最近的经常使用的数据保存在内存或者SSD磁盘,对很久之前的访问量较少的数据保存在磁盘,当然相关的策略都需要根据业务进行决策。当然设计冷热数据交互沉淀的系统复杂度较高,没有十分的必要,尽量使用缓存的方式。

自动测试

自动化测试对于开发来讲并不是必须的,但如果有自动测试的服务,稳定性会的到质的提升,关于自动测试可以参考文章单元测试的思考

灰度染色

所谓灰度,就是指介于黑和白之间,比如新功能发布过程中,先找一部分灰度用户试用,根据反馈情况再决定是进一步扩大还是做一些调整,在发布过程中也可以使用灰度策略,先发布部分机器或者用户,可以及早的在小范围内发现问题,及时修复。

灰度的粒度可大可小,往小了说可以只对某些人生效,比如开发人员和产品人员;再扩大可以为全公司内部人员;或者可以灰度某个号段或者活跃/非活跃用户。

灰度可以用在开发、发布、运营的不同阶段,下面逐一举例:

功能灰度

产品上开发了某个新功能,但不知道功能的效果如何,比如优化了某种算法,分别对不同的用户使用不同的算法,对比其反馈效果,这也就是常说的AB Test,可以通过试错的方式快速迭代产品。另外从开发的角度,可能重构了部分模块代码,或者新功能尚不稳定,可以先开放给内部用户使用,通过数据上报或者人工反馈的方式,在开放给外部用户之前,预先通过实践检验代码是否有问题。

实现上讲,需要通过一个机制可以快速判断一个用户是否灰度用户,可以通过本地Hash+远程定时同步的方式来做,另外是否灰度,灰度号码,或是按照号段灰度,或是全量。

发布灰度

从发布角度,因为90%以上的事故都是发布引起的,即使遵循严格发布规范,仍然可能存在隐藏的代码BUG,出现BUG或者事故不可避免,但是需要通过一系列机制保证事故的影响范围尽可能小,其中一项就是灰度发布,如果模块存在大量的机器,需要预先发布其中一台,根据风险程度进行不同时长的观察,确认无误后再逐渐扩大范围。

染色运营

线上的大多数问题,都需要日志来确定原因,但是线上环境如果打印过于详细的日志,会导致性能的急剧下降,如果打印很精简又不容易查找问题,面对两难情况,一个解决办法就是使用染色号码,并不针对所有用户都打印详细的日志,只对某一些染色的号码打印详细日志,如果有用户出现问题,可以对这些号码染色,一旦复现问题,便可以轻而易举定位,同时,也不会造成系统性能下降。当然,对于一些重要服务和重要节点,全量的日志也不可避免。

当然这一切都需要快速的染色号码查找和同步机制,以及灵活的配置系统所支撑,构建相应的基础设置是非常值得的。

日志

日志的重要性不必多说,多数线上问题,以及一些对账机制,都依赖完善的日志系统,多数的日志组件,都会打印到本地,但是面对几百台机器,想检索到某个具体用户的日志流水,是比较困难的,这时就需要远程日志组件。在打印日志的同时,把某些级别或者某些用户的日志打印到远程机器,通过Web页面搜索日志,再统一某种格式,即使非模块的开发者,也可以通过阅读日志了解用户发生了什么。

监控

监控可能多数服务都会配备,从最基本的讲,需要监控进程是否存在,机器的当前内存占用、磁盘占用、CPU占用率,这也是大多数情况需要了解的,但对于业务的监控,也是重中之重,你是否能够了解所负责服务过去一段时间每分钟所处理的最大业务包量、每天什么时间请求量最高、不同的处理接口调用比率、处理延迟数据,当依赖接口的失败率上涨、或者未知原因请求量暴涨,你是否能够第一时间感知?如果一时说不上来,这就需要在监控上多下功夫了,在业务开发过程中,需要针对业务的关键点,进行数据上报,由单独的模块汇总,绘制曲线,一定要可视化处理,运营过程中,需要根据情况调整告警阈值,设置最大值、最小值、波动、比率等告警,一旦出现异常,可以第一时间感知并处理,主动发现问题。

另外在每天都需扫一遍核心数据,检查数据是否有异常,同比如何,对自己的服务做到心中有数,掌握每一个变化。

实现上小米的开源监控组件Open-Falcon是一个值得尝试的方案。

柔性可用

柔性可用也很容易被人忽略,虽然理想情况下要做到所有服务的可用性,但是在极端情况下,比如用户量超出预期的暴增、某个机房的网络被切断、大面积断电停机等,设计服务架构时,需要深刻理解用户的核心价值,在不能满足用户的所有诉求的时候,尽量满足最核心的诉求,在异常极端情况下,在不能满足所有功能的情况下,优先保证核心功能的可用性。

举个例子,对于QQ的APNs推送服务,推送的消息需要用户的群名片、备注等信息,但是遇到异常没有额外的性能拉取信息,可以选择忽略这些信息,对用户来说,真正的核心诉求是收到消息的提醒,能够提供其他额外信息增强用户体验甚好,如果实在难以达到,也尽量保证推送的触答。

这需要架构师对业务的核心价值有着很清晰的理解,敢于设计柔性系统。

Built with Hugo
主题 StackJimmy 设计