96核ssd机型用之前32核的配置性能较差,大约22万set。优化的时候还发现延迟非常高,需要进行参数调整和代码优化。
压测发现延迟长尾部分分布如下:
命令 | 时间ms | 百分比 |
---|---|---|
SET | 17.000 | 99.06% |
SET | 200.000 | 99.98% |
SET | 410.000 | 99.99% |
SET | 480.000 | 100.00% |
可以看到延迟问题还是很严重的。在测试的过程中发现,第一次测试的时候延迟往往比较严重,后面则会大幅度改善,大约100%都在20ms以内。
初步怀疑是因为第一次压测有一些类似缺页导致内存分配之类的操作,具体原因还待进一步排查。
调整参数
96核机型参数需要做一些调整,经过调整之后,性能大约33w左右。
【结果】
qps提升:
22w->33w
优化后延迟分布:
命令 | 时间ms | 百分比 |
---|---|---|
SET | 15.000 | 99.09% |
SET | 19.000 | 99.99% |
SET | 21.000 | 100.00% |
std::locale问题
可以看到MGLock::MGLock()
构造函数里面调用std::locale::~locale()
占用cpu较多。
分析代码看到getCurThreadId()
函数里面调用了std::ostringstream
,这个接口会对std::locale
进行计数引用计数的操作,这个操作是需要上锁的,在多线程环境下面性能很差。
【解决办法】
干掉getCurThreadId()
【结果】
qps提升:
33w->55w
优化后延迟分布:
命令 | 时间ms | 百分比 |
---|---|---|
SET | 8.000 | 99.00% |
SET | 11.000 | 99.99% |
SET | 15.000 | 100.00% |
store数量
调试发现,kvstorecount
配置从10变为50的时候qps有较大提升,延迟也有很大改善。
为了搞明白代码里面具体是哪儿对kvstorecount这么敏感,分别抓取了两种配置的火焰图
kvstorecount=10
火焰图:
kvstorecount=50
火焰图:
两张火焰图的主要函数占用cpu比例都差不多,但是有两块__lll_lock_wait
函数的占用在kvstorecount=10的图中占比较高。
通过调用pstack
命令10次生成10个时刻的调用栈关系,然后去调用栈里面找到tendisplus::RocksTxn::commit
调用__lll_lock_wait
的栈,看看具体是哪个函数调用了__lll_lock_wait
。 在这10次里面找到了如下调用关系:
出现2次,这个说明tendisplus::RocksKVStore::markCommitted()
这儿的锁竞争对性能产生了影响
出现1次,这个说明rocksdb::TransactionBaseImpl::Put
会在rocksdb对key进行上锁,这儿对性能产生了影响
【解决办法】
配置kvstore 10->50
【结果】
qps提升:
55w->65w
优化后延迟分布:
命令 | 时间ms | 百分比 |
---|---|---|
SET | 8.400 | 99.01% |
SET | 9.900 | 99.99% |
SET | 13.000 | 100.00% |
线程数不能太大
测试的时候发现,线程数executorthreadnum
不能大于64,一旦出现,qps会下降非常的多。
最后经过添加测试代码跳过其中一些步骤,来观察qps是否会下降。结果发现_txn->Commit()
结果发现,96线程的时候,在图中代码块跳过_txn->Commit()
的时候qps大约65万,不跳过qps大约12万。通过调大kvstorecount
也不能带来改善。
说明_txn->Commit()
对性能产生很大的影响,初步怀疑是rocksdb里面对某个全局的资源产生了锁竞争,需要进一步的排查。
rocksdb事务锁
TransactionLockMgr::lock_maps_
会对提交事务的key进行上锁。桶的个数由参数rocksdb::TransactionDBOptions::num_stripes = 16
控制,为了减少锁冲突,调大这个参数。
【解决办法】
RocksKVStore::restart()
函数设置参数txnDbOptions.num_stripes = 40;
【结果】
qps:
无提升
优化后延迟分布:
命令 | 时间ms | 百分比 |
---|---|---|
SET | 7.200 | 99.03% |
SET | 9.600 | 99.99% |
SET | 12.000 | 100.00% |
io任务队列和executor任务队列
io任务队列和executor任务队列调度混乱,考虑一一绑定。结果发现绑定之后,qps虽然没有提升,但是延迟提升明显。
【解决办法】
NetSession在创建连接的时候记录io任务队列id,调度的时候传给executor任务队列。
_executorList[ctxId]->schedule(std::forward<fn>(task));
【结果】
qps:
无提升
优化后延迟分布:
命令 | 时间ms | 百分比 |
---|---|---|
SET | 1.600 | 99.46% |
SET | 2.200 | 99.91% |
SET | 6.900 | 99.99% |
SET | 9.400 | 100.00% |
cpu跑不满
经过上面的优化后,cpu大约只能跑到6000%左右,还有大约20-30个核没有利用起来,还需进一步排查。
延迟彪高
测试发现,第一次压测之后,也会偶尔出现延迟严重的现象。
进一步测试发现,在设置binlog与kv同一个column-family
的时候,
即binlog-using-defaultCF on
,就不会出现了。
原因待进一步追查。
最终配置参数
bind 127.0.0.1
port 51002
binlog-using-defaultCF off
loglevel notice
cluster-enabled on
storage rocks
logdir ./home/log
dir ./home/db
dumpdir ./home/dump
pidfile ./home/tendisplus.pid
rocks.blockcachemb 20480
noexpire true
#maxBinlogKeepNum 2000000
#maxBinlogKeepNum 2
minBinlogKeepSec 600
kvstorecount 50
#requirepass abc
#masterauth abc
netiothreadnum 7
executorthreadnum 56
executorWorkPoolSize 8
truncateBinlogIntervalMs 1000
truncateBinlogNum 5000000
rocks.blockcache_strict_capacity_limit 0
rocks.cache_index_and_filter_blocks 1
rocks.max_open_files -1
incrPushThreadnum 4
rocks.compress_type lz4
#rocks.level0_compress_enabled no
#rocks.level1_compress_enabled no
rocks.level_compaction_dynamic_level_bytes 1
rocks.max_background_compactions 64
#rocks.max_write_buffer_number 16
#rocks.min_write_buffer_number_to_merge 14
rocks.write_buffer_size 67108864
rocks.target_file_size_base 67108864
rocks.max_bytes_for_level_base 536870912
#67108864 64m
#1048576 1m
#rocks.wal_dir /dev/shm/wal
#mapping-command dbsize emptyint
mapping-command keys emptymultibulk
migrateSenderThreadnum 2
migrateReceiveThreadnum 4
#migrateClearThreadnum 40
cluster-migration-binlog-iters 20
cluster-migration-slots-num-per-task 100
cluster-migration-rate-limit 1000
cluster-migration-distance 100