前言
微信公众号:潇雷 当努力到一定程度,幸运自与你不期而遇。
背景
公司的数据库技术选型采用redis,每天会产生8亿条的数据往里面筛入。因此需要测一波redis的存储性能。下面就记录下自己的测试过程,以及一些踩过的坑。
测试需求
测试用多线程向redis中插入亿级数据。预期用10条线程向redis中插入8亿条数据。
服务器性能
本次测试采用自己的虚拟机测试:
参数 | linux指令 | 值 |
---|---|---|
系统 | cat /etc/redhat-release | CentOS Linux release 7.5.1804 (Core) |
内存 | free -h | total:3.7G available:3.3G |
cpu数量 | cat /proc/cpuinfo | cpu cores:2 |
HZ | cat /proc/cpuinfo ] grep MHz ] uniq | 1991.999 |
一、Jedis 单独插入测试
插入1w条数据
1、单线程:
@Test
void exec() throws InterruptedException {
Jedis jedis = new Jedis("192.168.44.101", 6379);
jedis.flushDB();
new Thread() {
@Override
public void run() {
String key = "32021420001:90000300009999:10001:1601198414621:";
long startTime = System.currentTimeMillis();
for (int j = 1; j <= 10000; j++) {
jedis.set(key+j+"",key+j+"");
}
long endTime = System.currentTimeMillis();
System.out.println("exec time : " + currentThread().getName()+":"+(endTime - startTime));
}
}.start();
System.out.println(Thread.currentThread().getName());
Thread.sleep(40000);
}
单线程插入1w条数据需要的时间为4.7s
main
exec time : Thread-2:4784
2、两个线程
@Test
void exec() throws InterruptedException {
Jedis jedis = new Jedis("192.168.44.101", 6379);
Jedis jedis2 = new Jedis("192.168.44.101", 6379);
jedis.flushDB();
new Thread() {
@Override
public void run() {
String key = "32021420001:90000300009999:10001:1601198414621:";
long startTime = System.currentTimeMillis();
for (int j = 1; j <= 5000; j++) {
jedis.set(key+j+"",key+j+"");
}
long endTime = System.currentTimeMillis();
System.out.println("exec time : " + currentThread().getName()+":"+(endTime - startTime));
}
}.start();
new Thread() {
@Override
public void run() {
String key = "32021420001:90000300009999:10001:1601198414621:";
long startTime = System.currentTimeMillis();
for (int j = 5001; j <= 10000; j++) {
jedis2.set(key+j+"",key+j+"");
}
long endTime = System.currentTimeMillis();
System.out.println("exec time : " + currentThread().getName()+":"+(endTime - startTime));
}
}.start();
System.out.println(Thread.currentThread().getName());
Thread.sleep(40000);
}
两个线程插入共1w条数据需要1.2s
main
exec time : Thread-2:1266
exec time : Thread-3:1277
3、四个线程插入1w条数据800多毫秒
main
exec time : Thread-4:877
exec time : Thread-2:878
exec time : Thread-3:879
exec time : Thread-5:884
4、十个线程插入1w条数据:500多毫秒
main
exec time : Thread-2:573
exec time : Thread-4:573
exec time : Thread-3:575
exec time : Thread-8:574
exec time : Thread-11:572
exec time : Thread-5:576
exec time : Thread-7:575
exec time : Thread-10:573
exec time : Thread-6:576
exec time : Thread-9:588
【结论分析】:可以看到时间曲线是正常的。线程越多,最终消耗的时间也会降低。但是redis号称10w的吞吐量,在使用这种普通方法插入时,效果却非常的差!!主要原因是我们插入的时候会产生多次连接操作,创建连接需要时间,同时,一个连接就会有数据包,多个数据包的传达网络并不能保证一致,这些都是影响我们大量数据插入性能的。
二、Jedis使用pipeline插入1w条数据
2.1 pipeline出现的背景:
redis客户端执行一条命令分4个过程: 发送命令->命令排队->命令执行->返回结果
这个过程被称为RTT(Round trip time,往返时间),mget,mset等命令有效的节约了RTT,个人理解这就类比于连接mysql获取数据,多次连接造成的io数据会降低性能。但是大部分命令不支持批量操作,需要消耗N次RTT,这个时候就出现了pipeline来解决这个问题。
2.2 pipeline的性能
1、没有使用pipeline执行N次命令的示意图:
2、使用pipeline执行N次命令:
2.3 测试:插入1w条数据
单线程:
@Test
void aaaa() throws InterruptedException {
Jedis jedis = new Jedis("192.168.44.101", 6379);
Pipeline pipelined = jedis.pipelined();
jedis.flushDB();
new Thread() {
@Override
public void run() {
String key = "32021420001:90000300009999:10001:1601198414621:";
long startTime = System.currentTimeMillis();
IntStream.range(0,10000).forEach(i->pipelined.set(key+i+"",i+""));
pipelined.syncAndReturnAll();
long endTime = System.currentTimeMillis();
System.out.println("exec time : " + currentThread().getName()+":"+(endTime - startTime));
}
}.start();
Thread.sleep(40000);
}
时间竟然来到了几十毫秒级别:
exec time : Thread-2:55
两个线程测试:
在插入一万条数据的时候时间变成了70毫秒
exec time : Thread-3:71
exec time : Thread-2:72
四个线程:
可以看到时间变得更长了:
exec time : Thread-4:100
exec time : Thread-5:101
exec time : Thread-2:103
exec time : Thread-3:103
猜测:可能是数据量不大,导致多线程处理时io开销占用时间太多。继续测试!
2.4 加大数据到1000w测试
/**
* jedis 单线程测试
**/
@Test
void aaa() throws InterruptedException {
Jedis jedis = new Jedis("192.168.44.101", 6379);
jedis.flushDB();
Pipeline pipeline=jedis.pipelined();
new Thread() {
@Override
public void run() {
String key = "32021420001:90000300009999:10001:1601198414621:";
long startTime = System.currentTimeMillis();
IntStream.range(0,10000000).forEach(i->pipeline.set(key+i+"",i+""));
pipeline.syncAndReturnAll();
long endTime = System.currentTimeMillis();
System.out.println("exec time : " + currentThread().getName()+":"+(endTime - startTime));
}
}.start();
Thread.sleep(500000);
}
测了三次之后,得出最终结论:单线程在插入一千万条数据耗时在70s左右。
2.5 多线程测试1000w数据
两个线程:jedis要关闭连接
@Test
void aaa() throws InterruptedException {
Jedis jedis = new Jedis("192.168.44.101", 6379);
Jedis jedis2 = new Jedis("192.168.44.101", 6379);
jedis.flushDB();
jedis.select(3);
Pipeline pipeline=jedis.pipelined();
Pipeline pipeline2=jedis2.pipelined();
new Thread() {
@Override
public void run() {
String key = "32021420001:90000300009999:10001:1601198414621:";
long startTime = System.currentTimeMillis();
IntStream.range(0,5000000).forEach(
i->pipeline.set(key+i+"",i+"")
);
pipeline.syncAndReturnAll();
long endTime = System.currentTimeMillis();
if(jedis != null){
jedis.close();
}
System.out.println("总 exec time : " + currentThread().getName()+":"+(endTime - startTime));
}
}.start();
new Thread() {
@Override
public void run() {
String key = "32021420001:90000300009999:10001:1601198414621:";
long startTime = System.currentTimeMillis();
IntStream.range(5000000,10000000).forEach(
i->pipeline2.set(key+i+"",i+"")
);
pipeline2.syncAndReturnAll();
long endTime = System.currentTimeMillis();
if(jedis2 != null){
jedis2.close();
}
System.out.println("总 exec time : " + currentThread().getName()+":"+(endTime - startTime));
}
}.start();
Thread.sleep(500000);
}
总 exec time : Thread-2:65198
总 exec time : Thread-3:65197
五个线程:
总 exec time : Thread-2:41193
总 exec time : Thread-6:42956
总 exec time : Thread-4:44909
总 exec time : Thread-5:44900
总 exec time : Thread-3:44944
【结论分析】:多线程确实可以在一定程度上降低存储时间。
2.6 拓展思考
分析:for循环和IntStream.forEach()循环没有性能差距。但是如果IntStream后面加上parallel()的并行流操作,可以根据cpu的内核数量成倍的提升速度。但是stream.parallel.forEach()中执行的操作并非线程安全。在刚执行的时候出现了数组越界异常,因此不考虑。
IntStream.parallel().forEach()