pika及pika_hub binlog性能分析
最近pika_hub代码撸的非常爽,它的binlog采用和pika一样的方式,多个连接【线程】加锁去Append,之前一直以为这里性能还不错,直到和同事一起压了一下,两个pika压一个pika_hub,binlog的写入速度只有10万左右。。。简直不能忍了,后来好好分析了一下这里,做了优化,所以写篇博客记录一下吧
一. Pika原有写binlog方式的问题
对pika原有方式,这里可以简化为:
有一个binlog模块,它提供一个Append接口,可以往文件里追加写内容,然后起若干线程,
每个线程都抢锁, 抢到锁之后调用Append,再放锁
我这里测试了一下开不同线程数的写入性能,结果如下:
单条内容长度:5字节
总条数: 300万
线程数 | 耗时 ms | CPU |
---|---|---|
480 | 18634 | 133% |
240 | 18204 | 133% |
120 | 17706 | 133% |
60 | 16993 | 133% |
30 | 16300 | 135% |
15 | 15722 | 135% |
10 | 15664 | 145% |
2 | 14994 | 135% |
1 | 9531 | 99% |
结果大跌眼镜,之前想当然的以为线程数越多写入性能越好,结果发现,这样的方式下1个线程最快,从2到480,基本涨幅不大。
这是为什么呢?
用gperftools分析了一下1个线程和2个线程的CPU,详细svg结果在这里:
首先在这样的实现中,无论几个线程,300万条数据,一定对应调用300万次write,这个是无法避免的,在这种情况下,如果只开1个线程,那么不存抢锁的情况,基本就是300万次write的性能,从1个线程的svg结果里也能看出来,write调用占用了45.9%,而一旦线程数大于1,那么就会有额外的抢锁开销,在同样300万次write不能少的前提下,这部分开销完全是浪费,从2个线程的svg中可以看到,lock_wait和unlock_wake占了差不多39%,完全多余,所以这也就解释了为什么1个线程比2个线程要快:
根本原因就是锁的临界区在整体执行中占比很大,所以开了多个线程对非临界区的并发并不能弥补自身引入锁的开销,导致性能不增反降
那么问题来了,pika和zeppelin都是多线程写binlog,所以这里一定是一个比较大的优化点,必须优化!!!不过具体怎么优化呢?我做了如下尝试
二. pika_hub怎么写binlog
从pika的问题中找答案,
- 300万次的write调用的耗时一定比100万次write调用要高,所以要尽可能的减少write调用的次数
- 必须坚持多线程,因为实际线上场景肯定比测试场景复杂,非临界区的并行还是很有必要,比如网络收发,数据编码解码等等
所以我尝试了一下rocksdb write实现中对多线程的处理办法,细节就不展开了,前面的博客有讲,说下主要思想:
通过batch将多线程的多个请求合并成一次请求来做,用在pika_hub的场景下就可以有效减少write调用次数
pika_hub中这部分的实现代码在这里
实现之后,因为知道性能肯定可以提升,所以索性用3000万条数据来测试时,如下:
1. 小value
单条内容长度:5字节
总条数: 3000万
线程数 | 耗时 ms | CPU |
---|---|---|
600 | 20307 | 1537% |
480 | 20565 | 1400% |
240 | 22582 | 1182% |
120 | 25902 | 936% |
60 | 30506 | 745% |
30 | 37913 | 555% |
15 | 52364 | 398% |
2. 大value
单条内容长度:1024字节
总条数: 3000万
线程数 | 耗时 ms | CPU |
---|---|---|
240 | 76855 | 416% |
120 | 75120 | 416% |
60 | 75709 | 399% |
30 | 85778 | 345% |
15 | 102719 | 298% |
可以看到,即使在数据总量翻10倍的场景下,性能依旧秒杀pika的binlog
pika的binlog实现,最好Append性能QPS也就差不多30万,而pika_hub的实现,能到将近150万,在1K内容的场景下,磁盘顺序写差不多390M/s,也不错了
用gperftools分析一下,(svg图在这里):
发现CPU大头都在condition_wait和condition_broadcast上,但因为Batch打包写,即使同一时刻只有一个线程在真正工作,但它会只使用1次write来打包完成所有等待线程的write任务,这里极大的提高了性能,效果十分理想。其实condition的代价还能进一步靠消耗更多cpu来抵消,我前面的博客有介绍过,不过暂时pika_hub还是用不到的,等以后再优化吧,另外目前实现里string这里用的也比较暴力,还有一些优化空间。
三. pika该何去何从
改啊,必须得改成和pika_hub一样的实现
四. 总结
- 很多时候不能想当然的以为自己以为的就是以为的,要实际测试跑跑看
- 对何时该用何时不该用多线程要明确,不能不加思考无脑用,其中的利弊还是要有自己的分析