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结果在这里:

1个线程

2个线程

首先在这样的实现中,无论几个线程,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的问题中找答案,

  1. 300万次的write调用的耗时一定比100万次write调用要高,所以要尽可能的减少write调用的次数
  2. 必须坚持多线程,因为实际线上场景肯定比测试场景复杂,非临界区的并行还是很有必要,比如网络收发,数据编码解码等等

所以我尝试了一下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一样的实现

四. 总结

  1. 很多时候不能想当然的以为自己以为的就是以为的,要实际测试跑跑看
  2. 对何时该用何时不该用多线程要明确,不能不加思考无脑用,其中的利弊还是要有自己的分析