写点什么

QCon 演讲速递:异步处理在分布式系统中的优化作用

  • 2015-04-23
  • 本文字数:3642 字

    阅读完需:约 12 分钟

本文根据阿里巴巴技术保障研究员赵海平在 2015 年 QCon 全球软件开发大会(北京站)主题演讲整理而成。

赵海平在 Facebook 工作 8 年期间,主要针对后端进行性能优化的工作,包括 PHP 的优化,memcache 的优化,等等后端组件。偶然有机会跟阿里的朋友沟通他们遇到的问题,聊得比较深入,就发现虽然阿里是用 Java 的,但在大的系统优化方面遇到的问题,跟 Facebook 是很类似的,因此回国加入阿里,希望帮助阿里把整个系统优化得更好。计划第一步是先做整体的 profiling 系统,以找到性能的局部优化点;之后再进行一些大的架构优化,以及深入到 JVM 层面的优化。

回国跟很多人沟通,感觉现在到了 2015 年,国内的朋友们基本上也都对分布式系统的架构相当了解了。今天的演讲就一个主题,就是分布式系统中异步处理的优化。

单机时代的数据请求

十五年前写软件是很简单的,一个 Client 对应一个 DB Server,或者多个 Client 对应一个 DB Server,每一个 Client 执行各自的服务。当时的讨论很多是说,这个东西要写在 Client 端还是写在 DB Server 端,流行的思路有两种:

  1. 把 DB Server 写得很复杂,比如 Oracle 数据库,而 Client 端则写得很简单,只有调用返回
  2. DB 很简单,只有简单的表,而 Client 写得复杂。很多创业公司会这样做,因为他们对 SQL 不是很熟悉,但是很熟悉 PHP。早期 Facebook 就是典型的代表

大数据时代的数据请求

单机时代随着两个趋势而逐渐成为历史。一个趋势是随着互联网的流行,越来越多的人开始上网使用 Web 服务,而且很多时候用户增长速度是非常快的,结果造成一台 DB Server 无法储存下所有用户的数据。第二个趋势是计算机能力越来越强,网络服务针对每一个用户要做的事情也变多了,比如 Facebook 不仅要保存一个用户的个人信息,还有他的关系链信息,他的使用习惯、点击习惯等,就造成一个用户的数据量也大大增加,仅仅访问一个 DB Server 就准备好一个页面变成了不可能的事情。

这就带来了一个问题:针对多个 DB Server 的程序应该怎么写?

针对这个问题也有两个思路:

  1. 串行同步。先 query DB1,返回 res1,再使用 res1 做另一个 DB 的 query,返回 res2。这是在第二个 Query 依赖第一个 Query 结果的情况
  2. 并行同步。针对 DB1 的 query 跟针对 DB2 的 query 同步进行。这是两个 Query 之间没有依赖关系的情况。Facebook 早期专门写了一个并行处理的函数,用法是 ExecParallelQuery(conn1,Query1,conn2,Query2)

这个时候的代码就比以前的代码更加复杂了,不过还是能实现需要实现的需求。但这时候带来了一个新的问题,就是等待。一个页面的加载可能需要调用不同的函数,而不同的函数可能是由不同的团队写的。比如获取朋友关系的函数 getFriends 把自己需要的数据用同步的方式获取了,但如果一个第三方开发者过来,则不仅要调用这个函数,还需要调用其他函数,这样其他函数的执行就需要等待前面这个 getFriends 函数返回了结果之后才能开始执行,就很慢了。

要如何做到并行处理在代码层面很直观,在机器上的执行效率又好呢?

异步的处理思路就是这么来的。

所谓异步就是,我这个函数知道这里需要访问哪几个 DB Server,但我先不着急去访问,而是先记录一下,等等看其他函数是不是也要访问这个 DB,如果有的话,待会儿再一起去访问。异步处理的指令比如说是 conn.asyncExec(Query) ,这个可以立刻返回一个 Future 对象,意思就是“待会儿再去执行”。如果每个函数都返回这种 Future 对象,那么就可以根据这些 Future 对象来判断哪些请求没有依赖可以并行处理,哪些请求有依赖需要串行处理了。如此,不同的团队写出来的函数就不用一个等一个,而是可以在更高层面上互相合作。

然而这又带来了一个问题,那就是异步处理的写法是具有传染性的。如果一个服务中有的函数写的异步,有的函数没写异步,就会造成有的函数返回了 Future Object,有的函数返回了数值,导致无法执行。要实现异步,需要关联的所有函数都用异步的写法返回 Future Object 才可以。

所以 Facebook 在转向异步处理的过程是非常痛苦的,一开始做了局部修改,再修改调用了局部修改过的函数的函数,所有调用的调用都要修改,最后全部改成了异步,只要有调用远程服务 IO 的操作都要改。每一个 DB Query 都拆分成两步,一个 set request,一个 receive response。这里的工作量很大,所以如果创业团队的话,最好是第一天就用正确的写法,就不会这么痛苦。

所有函数改写后,每一个函数执行都会返回 Future Object。那么异步处理的第一步,就是将这些 Future Object 形成一棵依赖树的结构,好像这样:

这里每个节点都是一个 Future 对象,每一个 Future 对象有两种状态,一个是等待执行,一个是完成执行。同级的节点是没有依赖关系的,可以并行执行;上下节点是有依赖关系的,需要串行执行,先执行下层再执行上层。

树结构形成后,从下到上执行,直到最上面的 top parent 节点被执行进入完成执行的状态,就是完成,比如一个页面加载完毕。

所以异步处理之后有一个很有意思的情况,那就是 PHP 这个语言已经跟以前不同了,不再是一上来就是执行,而是一上来先 lazy 一下,看清楚所有的 Query 之后再执行。

异步处理还需要解决的问题

到目前为止,这样做异步处理似乎已经是足够好的优化,但实际上还有问题。看看下面这个例子。

比如我们现在有两个查询需求。一个是查询你在淘宝上买过东西的朋友,另一个是查询你在淘宝上买过保时捷的朋友。常理来说,我们会先想到查询你在淘宝上的朋友,再进行另一个条件的查询,比如这样:

复制代码
IdList friends = waitFor(getFriends(myId));
yield return getTaoBaoBuyers(friends);

但是对于保时捷这个查询而言,这是不对的,因为淘宝上买保时捷的人是很少的,可能就一两个,而淘宝上的好友数可能有上百。因此保时捷的查询应该是这个次序比较优化:

复制代码
IdList buyers = waitFor(getPorscheBuyer());
yield return getFriends(buyers);

这个次序应该如何决定?实际上不应该在写程序的时候决定,因为写程序的时候是无法避免有先后顺序的——编辑器只能一行一行的写代码,但是机器执行却无需管这个。所以更好的方法应该是在执行代码之前再加入一个 phase。

其实传统数据库的 cardinality(基数)功能已经解决了这个问题。你在 DB query 里面使用 INNER JOIN 这个指令,其实 DB 已经能够预判哪一个表给出的 row 会比较少,从而以更优化的次序去执行。但现在我们用的编程语言,无论是 PHP,Java,Python 还是 C/C++,并没有考虑这个问题。有人会开很多线程来解决这个问题,但这不是最佳方案,因为在 Linux 系统里,你的线程数要是上了 200-300,就会有很大的 overhead。

代码执行的次序,这是一个。另外最近几年还有一个流行的优化思路,就是上 memcache。我们有时候会看到程序员把他自己的函数放进了 memcache,相当于是依赖树的中间的一个节点,我就问他为什么要把他这个 Class 放入 memcache,他可能会说,他觉得这个节点和这个节点的 child 被调用的次数多。我觉得这可能不是特别理想的。你今天觉得这个 Class 被调用的多,可以放进 memcache,但明天是不是会有更重要的 Class 会更值得放进 memcache,于是你又要把 memcache 的资源让给这个新的 Class?如果你放入 memcache 的 Class 并不是最重要的,这就相当于真正优化的可能性被拿走了。

如何让异步执行的更好?

哪个 query 先执行,哪个 query 后执行,不应该是在编码阶段来做的。哪个 Class 该进 memcache,哪个 Class 该出 memcache,也不应该在编码阶段来做。应该有一个中间的阶段,专门进行这种调度工作,然而到目前为止,还没有公司能够做到,因为没有合适的语言。

异步处理在分布式系统中怎样做有更好的优化作用,我们需要更多的思考。希望大家能够把计算机当作科学去思考,而不仅仅是工程应用。我们现在看十几年前,对单机是非常了解了,那么未来过了五年十年再回来看,可能对分布式系统也会了解的比现在更多很多,可能给分布式系统写程序也会变得跟给单机写程序一样简单。当然这就需要更合适的工具语言去给大家提供这种异步的便利。是不是会有 Haskell 那样 lazy 的方式从系统层面解决这个问题?希望跟大家一起思考探讨。

讲师简介

赵海平,阿里巴巴技术保障研究员,从小酷爱编程,多次获得中学生计算机竞赛的各种奖项,1987 年以河北省高考状元的优异成绩进入北京大学生物系,又在美国纽约大学获得分子生物学硕士,其后放弃博士学位,进入普林斯顿获得计算机科学硕士,曾就职于微软公司。2007 年加入只有不到 50 个软件工程师的 Facebook,致力于软件性能和架构分析,在此期间创建了 HipHop 项目,重新编写和实现 PHP 语言,使其速度提高 5 到 6 倍,为公司节约数十亿美元。HipHop 项目之后,致力于“用异步处理来优化分布式系统”的设计理念中,并为此做了多项分布式数据库的优化研究,在 PHP 语言中加入了 yield 和 generator 的新功能,来帮助日趋复杂的 Facebook 网页设计。2015 年 3 月回国,加入阿里巴巴技术保障部,将重点攻克阿里在软件性能以及 Java 使用过程中遇到的技术问题。

演讲 slides 下载地址

2015-04-23 07:389062

评论 1 条评论

发布
用户头像
2018-11-15 16:22
回复
没有更多了
发现更多内容

二次元界福音:MakeGirlsMoe创建动漫人物

不脱发的程序猿

GitHub 开源 4月日更 二次元 MakeGirlsMoe

如何美化 GitHub 个人主页?

彭宏豪95

GitHub 写作 markdown IT 4月日更

数字货币,已成为理解现代经济不可排斥的一个因素

CECBC

数字经济

入职字节跳动那一天,我哭了(蘑菇街被裁,奋战7个月拿下offer)

Java 编程 程序员 架构 面试

单片机异常复位后如何保存变量数据

不脱发的程序猿

嵌入式 单片机 4月日更 硬件研发 单片机异常复位

融云X-Meetup南京站 探讨实时通信架构的高质量设计

融云 RongCloud

软件测试分类体系,系统学习

程序员阿沐

软件测试 测试工程师 黑盒测试 白盒测试 测试类型

将AI部署到现实?或许你该读读这本书!

澳鹏Appen

人工智能 大数据 AI 伦理

为什么拥有云原生数据平台对电信公司很重要?

VoltDB

云原生 5G VoltDB 电信

货运物流移动端解决方案:为货运物流行业打造高性能、高粘性的“双端”触点

蚂蚁集团移动开发平台 mPaaS

移动开发 mPaaS 移动端 智慧物流

已拿到8个Offer!阿里巴巴Java面试参考指南(泰山版)

钟奕礼

Java 编程 程序员 架构 面试

一体化智能安全防御 京东云星盾安全加速正式发布

京东科技开发者

互联网 网络安全

荷小鱼 x mPaaS | 借助 H5 容器改善 App 白屏、浏览器兼容等问题

蚂蚁集团移动开发平台 mPaaS

html5 mPaaS 离线包 教育科技

Github上堪称最全的面试题库(Java岗)到底有多香

钟奕礼

Java 编程 程序员 架构 面试

Github连夜下架!阿里新产Java全栈面试突击小册太香了

Java架构之路

Java 程序员 架构 面试 编程语言

如何抓住新社交风口下的音视频通讯大潮?

融云 RongCloud

flink流计算可视化web平台

无情

sql 流计算 flin

Java开发8年,40W年薪被别人叫垃圾?请你们不要口嗨了,好好去刷题吧!

Java架构追梦

Java 架构 面试 金三银四 年薪40W

阿凡提EGGNETWORK恒价通证+加密社交催生新玩法 EFTalk

币圈那点事

嘉楠科技发布勘智K210-MicroPython 无需寄存器手册就能上手开发

区块链版「滴滴+Uber」,让出行带来收益

CECBC

移动互联网

盘点几代会声会影图标

奈奈的杂社

构建用户安全评级,UGC智能化审核应用实践

爱奇艺技术产品团队

人工智能

激光雷达(LiDAR)技术

澳鹏Appen

人工智能 大数据 智能驾驶 激光雷达 点云标注

Android AAC音频硬编解码你知道多少?

Engineer-Jsp

HBase底层读写过程

五分钟学大数据

HBase 4月日更

用DeBug的方式,带你掌握HBase文件在Snapshot的各种变化

华为云开发者联盟

HBase 元数据 数据迁移 数据备份 Snapshot

融云推出超值套餐包,音视频20万分钟免费享

融云 RongCloud

Java程序员都要懂得知识点:原始数据类型

华为云开发者联盟

Java 字符串 StringBuffer 原始数据类型 布尔类型

公安重点人员管控系统搭建,智慧派出所系统

阿里云 RTC QoS 弱网对抗之变分辨率编码

阿里云CloudImagine

阿里云 WebRTC 分辨率 视频编解码 视频云

QCon演讲速递:异步处理在分布式系统中的优化作用_Java_sai_InfoQ精选文章