SlideShare a Scribd company logo
稳定性与代码质量
@微博平台架构
@唐福林
大纲
• 分层隔离
• SLA
分层隔离
• 分层隔离
• SLA
分层隔离
分层隔离
分层隔离
分层隔离
分层隔离
分层隔离
分层隔离
魔毯
分层隔离
• RequestID
• Http Header / Get param
• Function/Class parameter
• Thread local
请求追踪
分层隔离
• 文档
• 代码即文档
代码即文档
• 代码即注释
代码即文档
分层隔离
• 错误处理
• 统一分配错误码
统一分配错误码
分层隔离
• 向后兼容
• 明确范围
• 版本号机制:一旦破坏了向后兼容的保证,必须修改
版本号
向后兼容
总结
• 分层隔离
• RequestID
• 代码即文档
• 统一分配错误码
• 向后兼容
大纲
• 分层隔离
• SLA
SLA
• 善用异步并发
善用异步并发
SLA
• 超时控制
超时控制
超时控制
• 持续监控
• 单次调用
• 统计类信息
• 线程池
SLA
• 谨慎重试
谨慎重试
谨慎重试
• 谨慎重试
• 重试在很多时候都能极大的提高成功率
• 重试在极端的时候容易引发连锁反应
SLA
• 快速失败
快速失败
• 快速失败
SLA
• 手动开关
手动开关
• 手动开关
总结
• SLA
• 善用异步并发
• 超时控制
• 谨慎重试
• 快速失败
• 手动开关
总结
• 分层隔离
• RequestID
• 代码即文档
• 统一分配错误码
• 向后兼容
Q & A
@微博平台架构
@唐福林

More Related Content

PDF
2024 Trend Updates: What Really Works In SEO & Content Marketing
PPT
Rest与面向资源的web开发
PDF
白玉磊 Webrebuild
PPTX
Team Foundation Server
PPTX
架構設計-資料存取的選擇
PPTX
移动时代端到端的稳定性保障经验谈
PDF
物联网与微博平台探索
PDF
股市动荡下的雪球架构进化历程
2024 Trend Updates: What Really Works In SEO & Content Marketing
Rest与面向资源的web开发
白玉磊 Webrebuild
Team Foundation Server
架構設計-資料存取的選擇
移动时代端到端的稳定性保障经验谈
物联网与微博平台探索
股市动荡下的雪球架构进化历程
Ad

新浪微博平台稳定性与代码质量

Editor's Notes

  • #2: 大家下午好,我是来自微博平台的唐福林,今天我跟大家一起分享一下《微博平台稳定性与代码质量》这个话题。
  • #3: 前面道儒和庆丰已经跟大家分享了2个话题:分层隔离和SLA,为了保证微博平台的稳定,平台在这两方面都做了一些工作。这些工作大部分最终都落实到代码上。下面,我就来跟大家分享一下这些工作的具体代码实现,以及这些实现背后的故事。
  • #4: 这是前面道儒跟大家分享过的分层隔离的图,这里我们再回顾一下:域名,dns,vip,varnish这些非代码类的工作我们先略过,我们重点看下面的代码实现部分
  • #5: 首先我们简单回顾一下微博的代码实现的演进过程。在最最开始的时候,微博整体的实现大概是这个样子的:一个典型的 Lamp 网站实现
  • #6: 后来,随着微博流量的增长,有一些其它部门希望与进行业务合作,所以微博将业务逻辑实现层与页面渲染层进行了分离,另外增加了接口渲染层,对外提供了一套单独的接口实现,供别的部门调用。这里的 I 表示 interface 的意思
  • #7: 再后来,开放平台出现了。与之对应的,我们把最开始的网站称为微博主站。最初的开放平台与微博主站是完全独立的两套系统,分别有各自的业务逻辑实现,有各自的cache和db。两个系统之间通过队列进行数据交换。开放平台使用 Java 技术构建。当时的公司内部其它部门,比如无线wap站,客户端等,都面临一个严重的问题:是使用 i.t 接口,还是使用 api.t 接口?
  • #8: 再后来,微博进行了“平台化”。所谓的平台化,即在微博整体系统中,只保留一个平台进行底层业务逻辑实现,各个展示端都统一使用这个平台的接口和数据进行展示。于是,整个系统看起来就成了这个样子:下面是平台,包括底层业务逻辑实现,对外提供接口。这里的 I 是 inner 内部的意思。平台当前有 3 大客户:微博主站,微博无线(包括各种官方客户端),以及开放平台上的大量第三方应用。平台只负责实现最底层的业务逻辑,各个展示端还需要进行一些包装,可能还会有一些 cache 读写,才能得到最终的结果,展示给用户。这样,微博平台就完成了第一层的隔离:逻辑层与展示层隔离。这样我们得到的好处是:如果你发现微博主站某个功能不正常,你可以换wap站试试;如果你发现官方客户端比较慢,你可以换第三方的客户端看看会不会好一些。
  • #9: 现在我们来深入看一下平台的业务实现层。在最开始的实现中,所有的业务逻辑都跑在同一个 JVM 中。尽管我们已经在逻辑上分了多个不同的层,不同的模块,但因为跑在一个 JVM 中,异常情况下互相之间的影响还是可能存在的,最主要的影响就是对各种线程池,连接池的争用:当某个资源出现问题的时候,该资源相关的运行线程很可能会堆满了系统中的各个环节的各种池。
  • #10: 所以,我们将各个业务模块拆分成单独的服务,提供 rpc 给聚合层来调用。ps,微博 rpc 框架,code name motan(魔毯),不久的将来就会跟大家分享,敬请关注我们的官方微博上的消息。系统架构演变成现在这个样子,解决了很多的问题,但也引入了一些其它的问题。引入了哪些问题,如何解决的,我们接下来看看代码吧。
  • #11: 所以,我们将各个业务模块拆分成单独的服务,提供 rpc 给聚合层来调用。ps,微博 rpc 框架,code name motan(魔毯),不久的将来就会跟大家分享,敬请关注我们的官方微博上的消息。系统架构演变成现在这个样子,解决了很多的问题,但也引入了一些其它的问题。引入了哪些问题,如何解决的,我们接下来看看代码吧。
  • #12: 系统越来越复杂之后,遇到的第一个大问题就是:无法精确的追踪用户请求经过的路径,以及路径上每个节点当时的状态和结果。这样就给调试和线上问题追查带来了很大的麻烦。微博采用的办法是:给每个请求带上一个 request id,并将这个 id 带到尽可能的底层,所有能获得这个 id 地方的 log 里,都会带上。request id 的传递,当前我们一共使用了3种办法:在 http 接口调用的时候,走 header(为什么不直接加到 get 参数里?因为url可能会重用);代码内部,尽量加到 param 里,不方便添加的地方,及异步调用,就写到 thread local 里。
  • #13: 比如,在微博平台系统中提交异步的 runnable 到线程池的时候,我们会把它包装成这样的 TraceRunnable
  • #14: 团队越来越大,代码量越来越大,沟通成本随即越来越高。为了缓解激增的沟通成本(这个肯定是无法避免的),微博的各个团队也都写了很多的文档,画了大量的架构图。但我们也遇到了其它团队相同的问题:文档写完没几天,代码改了;架构图刚画完,又要重构了。问题最突出的是我们的接口定义文档:1. 大量的内外部用户依赖这个文档的正确性和及时性;2. 对于拥有好几百个接口的平台来说,几乎每周都会有接口文档更新的需求。怎么办?微博当前的做法是:将接口定义文档写在代码中,然后自动生成需要的格式
  • #15: 当前平台的接口定义文档例子:接口的参数说明,取值范围,返回值类型,可能的错误码,版本号等等
  • #16: 生成的最终文档是这个样子的
  • #17: 错误处理在大团队中也是一个非常头疼的问题:很难协调在每一个环节对于每一种错误都表现出相同的行为,特别是新人写代码,或在新添加的业务中,要么是表现的与原系统不一致,要么就干脆缺少细致的错误处理,只对外返回粗暴的“http 503 service unavailable”微博当前的做法是:预先在一个地方分配好所有的可能的错误码,并强制要求在所有的代码中使用这一份错误码
  • #18: 这是微博平台错误码定义的一部分。这样做还有一个好处:强制新业务的开发者去考虑所有可能的错误情况,并妥善的处理
  • #19: 因为系统设计成多个团队分别负责多个模块,“向后兼容承诺”是系统保持向前演进的必备条件:一旦某个模块放弃了向后兼容,那么它的某次上线就可能必须要所有使用方提前做准备,而这种准备工作是几乎无法按时完成的。保持向后兼容有一个前提:每个模块对外提供的服务的范围必须是明确的,比如我有一个文档上没有列出的内部方法或内部接口,不小心被设置成可访问的了,而且被使用方发现了,但使用方绝对不应该去调用它,更不应该去依赖它。微博当前的做法:给服务和接口都加上版本号,一旦不能保证兼容,则修改版本号,然后逐步推动使用方进行切换。
  • #20: 比如,微博平台化的时候,我们的用户模块接口改动较大,无法保证向后兼容,于是我们就修改了接口的版本号
  • #21: 总结一下,在分层隔离方面,平台在代码实现层面有以下的经验可以分享
  • #22: 下面我们聊聊 SLASLA,即服务水平协定,在平台看来,包含2层意思,第一,平台对外提供的服务是有水平承诺的;第二,平台依赖的外部其它服务,是有水平要求的。我们首先来看第一条,怎么保证平台对外提供的服务的水平。一般来说,平台提供的是 http 接口调用服务,对于这样的服务,服务水平承诺一般包括 2 个方面:1,可用性,即每年的可用时间保证;2,性能,即平均响应时间,99% 响应时间及99.99%响应时间。
  • #23: 由于业务不可避免的越来越复杂,需要做的步骤越来越多,为了保证性能承诺,我们需要在大多数可以做异步并发的地方去做异步并发。
  • #24: 比如我们在刷微博首页的时候,可能需要从数据库中加载微博信息,同时要加载微博对应的用户信息,及一些其它的信息,这个时候,我们就可以使用异步并发的方式去同时加载这些信息,而且,主线程不需要等待,而是继续做它能做的其它事情。但这样做也是有伴随风险的。最直观的风险:某一个资源故障的时候,可能会挤占线程池,可能会影响其它资源的获取线程。所以我们需要谨慎的选择各种异常情况下的处理策略。截图中这段代码就太粗暴了一点。
  • #25: 平台依赖的外部其它服务,虽然我们有服务水平要求,但,我们还是要做一些事情,来保证即使外部服务不够稳定,但平台整体还是可用的。正所谓“死道友不死贫道”那么我们就必须要进行超时控制。
  • #26: 平台当前的做法:一旦涉及任何的远程调用,强制添加:socket timeout,connect timeout ,及耗时区间统计。一旦涉及异步调用,也强制添加超时时间,及耗时区间统计。
  • #27: 耗时区间统计主要用来跟服务提供方计算服务水平承诺是否达到。这段代码就能够记录下每个步骤的耗时区间统计情况。平台会定期拿这个统计数据去跟各个依赖服务提供方去对服务水平。
  • #28: 服务水平承诺中,可用性承诺一旦高于 3 个 9,即 99.9%,我们就必须考虑各种灵异问题了:网络抖动,JVM 的 GC,服务重启等等。因为即使你的业务运行的再完美,这些问题也很可能会将可用性拉低到 3 个 9 以下。微博当前的做法是:根据需要,重试一次或两次。
  • #29: 微博的很多 client 代码看起来都是这个样子的。在一些必要的场合,我们还会随机 sleep一下再重试,避免对方雪崩
  • #30: 重试还有一种方式,那就是同一个业务有多套资源提供服务,如果某一套资源出现失败的返回,则重试另一套资源。比如这段代码,展示的是平台大量使用的高可用的 mc 部署模式的读写。重试的好处是消除了偶发错误,但在异常的情况下,也可能加重系统的负担,成为压垮系统的最后一根稻草。所以在进行重试之前,我们需要仔细权衡一下:重试的条件,重试的次数,重试的时机,重试的超时设置等等。
  • #31: 当我们明确知道系统快要抗不住的时候,我们最好的选择肯定不是死扛,而是快速失败。微博当前的做法:首先通过压测等各种手段明确系统中每一个模块的极限容量及安全容量阈值,然后在系统中合适的位置添加容量监控,一旦发现超过容量阈值,则迅速报警,并开始有选择的走快速失败逻辑。
  • #32: 比如在容器层 tomcat 中,我们的代码看起来是这样的。一旦发现 tomcat 中的容器线程超过阈值,则直接在接受请求的地方进行按比例的抛弃请求
  • #33: 在写代码的时候,我们知道什么是完全正常的情况,我们也知道什么是完全异常的情况(比如可以直接抛弃请求的极端情况),但是,还有很多情况是介于正常和异常之间,我们不知道确切的分界线改划在哪里。所以,我们在代码中埋入了大量的手动开关,在紧急的情况下,由人工来确认该做怎样的决策。
  • #34: 举个例子,平台的所有远程调用的 client,都进行了 switch 包装,即可以进行一键关闭
  • #35: 总结一下,在 SLA 方面,平台在代码实现层面可以分享以下的经验
  • #36: 再回顾一下前面分层隔离方面,平台分享了以下的经验
  • #37: 我今天的分享就到这里,下面是问答时间。谢谢大家!