最近几年一直参与大数据产品的研发,同时大数据产品在海量数据场景下其处理性能又是其主要的卖点和突破,所以个人在这几年经常忙于如何对大数据产品进行性能上面的优化,并且想通过本文和大家聊聊具体的几种比较常见大数据性能优化技术。
常见的大数据性能优化技术一般分为两部分,其一是硬件和系统层面的观测,从而来发现具体的瓶颈,并进行硬件或者系统级的调整;其二是主要通过对软件具体使用方法的调整来实现优化。
硬件方面的监测
图 1. Windows7 性能指数
关于硬件性能本身,个人觉得最好对性能的诠释就像图 1 大家比较熟悉的 Windows7 操作系统性能指数所展示的一样,性能本身并在于其所长,而是在于其所短,就像图 1 里面那个 5.4 分主硬盘托了整体的后腿一样,只要有短板存在,其他地方再强也可能收效甚微,所以需要硬件的性能检测就是找出短板在那里,并且尽可能地找到应对的方法。
在硬件观测角度方面,主要通过以下四个维度来判断到底哪里是瓶颈,它们分别是 CPU、内存、硬盘还有网络。
CPU 利用率
首先,在讲检测 CPU 性能之前,我们可以通过这个“cat /proc/cpuinfo |grep “processor”|wc -l”命令来获取本机的核数(如果开了超线程,一个核可以被看作两个核),这样可以知道 CPU 利用率的上限是多少。
最常用 CPU 监测工具是 TOP,当然 TOP 输出是一个瞬间值,如果想获取精确的数据,需要持续关注一段时间。
图 2 TOP 示例
TOP 的使用主要看两个值,其一是总体使用值,其最大值是 100%,就是图 2 第三行 Cpu(s), 前面两个 0.2% 分别是用户态和内核态的利用率,而 99.7% 是 CPU 空闲率,从这个可以看出,本机的 CPU 部分基本是空闲的;其二可以看相关进程,看它的“%CPU”使用率,比如,Xorg 这个 GUI 进程的占用率是 0.3%,但是这里面的 100% 不是本机所有 CPU 的 100%,而是单个核的 100%。所以它的上限会是本机核数 *100%。
图 3 uptime 示例
因为 TOP 主要关注的是瞬时的值,如果要看一段时间的均值,这个时候可以用 uptime 这个命令,见图 3,它除了可以显示当前总运行时,当前在线用户,更重要的是可以显示 1 分钟、5 分钟、15 分钟的整机 CPU 的平均负载情况。
假设在平时监测的时候,如果经常碰到用满 80% 以上 CPU 资源的话,可以理解为 CPU 利用率高,在这种场景下大多数只能靠优化执行逻辑,才能提升效率。
内存的监测
图 4 free -m 的示例
关于内存的监测,常用的命令是 free -m,通过这个命令可以查看系统内存的具体使用情况。其中 total,used 和 free 都很好理解,通过这三列可以看出此时系统总内存,已经使用内存和没有被使用的内存,而 cached 这列则表示有多少内存已经被 Page Cache 占用,但当系统内存吃紧的时候,Page Cache 会立即被回收并分配给请求内存的应用程序,所以 Page Cache 也可以被视为处于 free 状态的内存。
还有下面的 Swap 分区,如果 used 数值比较高,说明内存非常紧张,系统已经动用交换区,同时 IO 开销也会增长非常明显。当发现内存不够用的情况,可以考虑重启或者关闭那些占用很多内存的进程。
在这里稍微扩展一下 Page Cache 这个内存机制,因为这个机制对大数据挺重要的。一般在 Linux 系统上,利用默认系统 I/O 接口写入的文件块,会先在 Page Cache 上面有一个缓存,之后再写入到 I/O 设备上面,那么假设系统内存没有被占有满的话,在这种情况下,这个缓存会长时间保留,并不会被洗出内存,这样等下次程序访问到这些文件块的时候,肯定会访问 Page Cache 上面的那个版本,也就是直接访问内存,所以性能方面是内存级别的。
I/O 性能的监测
图 5 iostat –xz 1 示例
关于 I/O 性能,可以通过 iostat 这个命令来观察 I/O 的性能,具体见图 5(sda 是主硬盘),虽然参数比较多,但可以主要关注这两个参数:
其一是 await,它代表了 IO 操作的平均等待时间,单位是毫秒,这也是应用和磁盘之间操作所要消耗的时间,包括等待和实际的操作,如果这个数值大,说明 I/O 资源非常忙或者有故障;
其二是 %util,也就是设备利用率,数值如果超过 60,所以利用率很高,并会影响 I/O 平均等待时间,如果到 100,那就说明设备已饱和了,只能添加更多 I/O 资源。
网络方面的监测
图 6 sar –n DEV 1 示例
在网络方面,使用的比较多的 sar(System Activity Reporter)命令,如图 6。这个命令可以查看网络设备的吞吐率, 并在这个基础上,将吞吐量和硬件上限做对比,来判断网络设备是否已经饱和,假设以单张千兆网卡为例,如果“rxkB/s”和“txkB/s”两种相加超过 100MB 的话,说明网络已经接近饱和了。还有除了这个通过命令行来获取网络数据之外,还可以通过开源的 nload 的工具来进行监测,具体见下图:
图 7:nload 示例
VMSTAT
图 8 vmstat 1 示例
其实除了上面这些工具外,还有一个 vmstat 这个全能的命令,能监控硬件的方方面面,比如,如图 8 所示,Procs 的“r”列,这个列显示正在等待 CPU 资源的进程数,这个数据比之前看的 top 和 uptime 更加能够体现 CPU 负载情况,并且这个数据不包含等待 IO 的进程。如果这个数值大于机器 CPU 核数,那么机器的 CPU 资源已经饱和。
Memory 部分的“free”,“buff”和“cache”列的作用和上面 free 作用类似,而“si”和“so”说明使用 Swap 的次数,如果这个数据不为 0,说明 Swap 交换区已经在使用,也意味着物理内存已经不足。
Cpu 部分也大体和 TOP 上面显示类似,但可以关注“wa”这列,其代表的是 IO 等待时间,如果数值大于 0 的话,可以判断 I/O 资源有争抢。
如果通过上面硬件方面的监测,发现了瓶颈,或者发现了有很多余量,可以通过下半部分的软件方面的优化来进行调整,如果软件方面也无能为力的话,那么只能通过购买和安装更多的硬件。
软件方面的优化
这个方面因为各个大数据产品的实现方式不同,并且需要优化点也不同,操作方式更是不同,所以在这里,主要提供一些方针供大家参考。
写入优化
因为常见大数据产品的写入和传统关系型数据库是不同,传统关系数据库的写入是一行一行的写入,而常见大数据产品的写入是批量的写入,并且每次批量写入之后,都会生成新的数据文件,并且这个数据文件是不会被修改的。所以导入数据粒度小的话会导致很多细小文件产生,这样会导致更多的 I/O 操作,所以在使用大数据产品的时候,导入数据规模是越大越好,常见的规模在 100MB 以上为佳。
尽可能地并行
假设通过前面的硬件方面的测试方面,发现无论是 CPU,内存,I/O 还是网络,都没有遇到瓶颈,并且至少有 20% 潜力可挖,这个时候可以考虑尽可能地通过并行来提升性能,主要有两个方式:其一是每台机器上面部署更多的进程来压榨硬件资源;其二是提升单个进程的多线程数,这种方式比第一种更简单,风险也更低。总体而言,尽量使每台机器所使用到的线程数可以达到系统自身线程数的 80%。
尽可能使用压缩和列存
对于一些新入门的工程师,也包括那些有很多传统关系数据库使用经验的专业 DBA 数据管理员而言,大家都对列存比较一知半解,从而不敢使用。
列存和传统行存相比,主要有两个比较大的区别:
其一是数据不是按照行来存储,而且是将很多行的数据按列归属在一起,并存储 ,具体可以看图 9;
其二是一般行存的写入是一行行,而且列存是比较批量的,所以写入的数据库块会比较大,一般大于行存常见的 8KB。基于我个人这几年的经验,列存在极大多数分析场景下,都能提升 3 倍以上的性能,除了那些需要遍历一个表半数以上列的场景。因为通过列存不仅能够通过避免那些不要列的导入,这样能减少硬盘的 I/O 总量。并且由于列存本身数据是一个大块一个大块的存在,所以是硬盘 I/O 读取操作的次数也会减伤,这个对于硬盘 I/O 非常有利,因为本身硬盘 I/O 单次随机读取操作的成本非常高,和 SSD 相比。但是批量连续成本却非常优秀,当然如果使用 SSD 的话,性能会更优。
在这个基础上,由于连续数据都归属于一列都比较类似,比如,性别,所以对其压缩的效果非常不错,一般在 1 比 5 左右,并且通过压缩节省的 I/O 远大于压缩和解压缩所带来 CPU 的损耗。这也导致就算所有数据全都在硬盘上,其性能的损失和所有数据在内存上面缓存相比,一般慢 4 到 5 倍左右,其他也不会特别亏。
图 9 列存和行存的对比
善加利用 Page Cache
在上半部分已经提到了, 利用好 Page Cache 可以达到最基础级别的内存计算的效果,当然和真正意义上的内存计算还是很大的距离。在性能测试的时候,这个优化是比较常见的。一般作法是,先通过命令“sync; echo 3 > /proc/sys/vm/drop_caches”来清空 page cache,之后跑一下比较简单,但又能加载所有相关数据的语句,比如,对每一列进行求总,这种做法的坏处是没有机会应对真实可能存在性能瓶颈,这对今后的实际运行会产生很多不可控的因素,因为真实业务场景肯定会比所预想到的场景更复杂。
利用好分区特性
众所周知,最快 SQL 就是什么都不做的 SQL,比如,“select 1”;当然在实际的操作过程中,肯定不会有类似“select 1”这样没有意义的操作。所以对于传统关系数据库而言,为了减少读取不必要的数据,一般会使用索引。但是对于大数据这样分析操作而言,索引这种机制太昂贵,而且收效甚微。
分析大数据应用常用的过滤数据的方式是分区,特别是按照时间来分区,因为一般时间是最合适分割大数据的维度,比如,数据按照月分区,这样如果查询只需要涉及到某月数据,那么其余十一个月数据可以立刻忽略,当然如果按日来分区的,效果可能会更好,但尽量避免因为粒度太小,导致写入文件过于碎片化的情况。
Join 的优化
对于大数据的分析应用而言,Join 操作是非常常见的,并且 Join 操作本身对硬件的短板也更敏感,特别是网络,因为大多数的分布式操作,每个数据节点可以独立地完成,但 Join 经常需要来自其他节点数据才能完成本节点的执行,并且这个量可能很大,有的时候,一个节点执行所需要的数据远超本节点自带的数据,类似场景还有 unique 这样的去重操作,所以在调优方面消耗的功夫也最多。
常见 Join 方式,主要有三种:
其一是 Broadcast 广播,常用于大小表之间的 Join,Join 发起方会将小表的相关数据完整地分发到每个数据节点,之后当每个数据节点收到小表之后,会找其本地的大表数据来完成 Join 的,如图 10,pages 是小表,visits 是大表,发起方将 Pages 这张小表分发到每个数据节点;
其二是对小表 Local 化,这个机制本质上非常类似 Broadcast,只是分发小表这个操作是做导入数据的时候自动完成,性能肯定比 Broadcast 更好,因为减少传输小表的网络消耗和等待时间,但是需要在创建表的时候,做一些额外的设置,这个机制在 MPP 数据是非常常见的,但是在 Hadoop 平台上面还是比较少见,因为其底层的 HDFS 分布式文件系统比较强调硬件无关,地址透明,这个和数据尽可能 Local 化的思路是违背的;
其三是 Shuffle 或者 Partitioned Join 机制,其常用于两张大表之间的 Join。因为将大表都分发给每个节点肯定成本太高了,而且数据节点的内存不一定能放的下这么多数据,所以通过 Shuffle 洗牌机制,也就是将所有参与的 Join 表的相关部分按照某种机制均匀分发到各个节点,并且每个节点数据都是独立的,如图 11 所示,pages 和 visits 都是大表,它们按照 Join 列 Hash 的值来进行再次分布,节点 1 有 Join 列为 A-E 的数据,之后依次类推,虽然成本很高,但是对于大表之间的 Join 是最合理和最可行的方法。
图 10 Broadcast Join
图 11 Shuffle Join
介绍完 Join 机制之后,再深入一下 Join 的优化,也主要有三个方面:
其一是在大表和小表摆放顺序要符合技术规范,这样能避免优化器将大表作为 Broadcast 表来进行分发;
其二是开启或者执行预统计,也就是在查询之前,开启表的预统计,虽然预统计会耗费一点时间,但这样能够让优化器知道表的具体情况,从而做出合理的方案,即使之前表的顺序写错了,还有由于预统计会遍历数据,这样可以将数据预先加载到 Page Cache 上面;
其三是选择合理的 Join 机制,也就是做好 Broadcast 和 Shuffle 之间的抉择,两个大表之间选择 Shuffle,如果不是选择 Broadcast,当然假如优化器能判断出是更好不过了,但当优化器出现问题的时候,可以通过人工输入一些提示符来帮助优化器来判断;
多看 Profile
介绍很多优化技术,但是这样技术都比较笼统,为了更好做优化,做某个产品优化,还是最好能多看看每次执行后的 Profile,这样能对产品更深的理解。
因为大数据产品和技术比较多,并且每个产品和特色和设计都不同,所以在细节方面没有特别深入,但是的确有非常多的共性,所以通过硬件的监测,以及软件方面的优化,应该能把常见的大数据产品发挥到八成的功力。
参考资料
作者介绍
吴朱华:国内资深云计算和大数据专家,在 IBM 中国研究院和上海云人信息科技有限公司参与过多款云计算产和大数据产品的开发工作,同济本科,并曾在北京大学读过硕士。2011 年中,发表业界最好的两本云计算书之一《云计算核心技术剖析》。2016 年和上海华东理工大学的阮彤教授等合著了《大数据技术前沿》一书。
感谢杜小芳对本文的审校。
给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ , @丁晓昀),微信(微信号: InfoQChina )关注我们。
评论