服务器性能
继续上次的测试,这次测试换用更大的测试机测试,查看服务器性能。
参数 | linux指令 | 值 |
---|---|---|
内存 | free -h | total:33G free 32G |
系统 | cat /etc/redhat-release | CentOS Linux release 7.8.2003 (Core) |
cpu数量 | cat /proc/cpuinfo | cpu core:4 |
HZ | cat /proc/cpuinfo l grep MHz l uniq | MHz: 2100.000 |
pipeline简介
管道是一种经典的设计思路,它的主要思想为批量发送请求,批量处理请求,这样可与最大限度的减少网络的IO开销。
实际上。pipeline不仅可与减少网络开销,也可以减少系统调用的次数。我们知道,redis客户端写操作和读操作都会涉及read和write系统调用,这意味着从用户域到内核域,而上下文切换是巨大的速度损失。
测试代码:(10线程插入两亿数据)
@Test
public void testJedisPool() throws InterruptedException {
Jedis jedis = new Jedis("10.240.30.102", 6379);
Jedis jedis2 = new Jedis("10.240.30.102", 6379);
Jedis jedis3 = new Jedis("10.240.30.102", 6379);
Jedis jedis4 = new Jedis("10.240.30.102", 6379);
Jedis jedis5 = new Jedis("10.240.30.102", 6379);
Jedis jedis6 = new Jedis("10.240.30.102", 6379);
Jedis jedis7 = new Jedis("10.240.30.102", 6379);
Jedis jedis8 = new Jedis("10.240.30.102", 6379);
Jedis jedis9 = new Jedis("10.240.30.102", 6379);
Jedis jedis10 = new Jedis("10.240.30.102", 6379);
Pipeline pipelined = jedis.pipelined();
Pipeline pipelined2 = jedis2.pipelined();
Pipeline pipelined3 = jedis3.pipelined();
Pipeline pipelined4 = jedis4.pipelined();
Pipeline pipelined5 = jedis5.pipelined();
Pipeline pipelined6 = jedis6.pipelined();
Pipeline pipelined7 = jedis7.pipelined();
Pipeline pipelined8 = jedis8.pipelined();
Pipeline pipelined9 = jedis9.pipelined();
Pipeline pipelined10 = jedis10.pipelined();
jedis.flushDB();
jedis.select(3);
new Thread() {
@Override
public void run() {
String key = "32021420001:90000300009999:10001:1601198414621:";
long startTime = System.currentTimeMillis();
for (int i = 1; i <= 2000; i++) {
long start=System.currentTimeMillis();
for (int j = 1; j <= 10000; j++) {
pipelined.set(key+i+j+"",i+j+"");
}
long end=System.currentTimeMillis();
pipelined.sync();
System.out.println(Thread.currentThread().getName()+":第"+i+"批次的执行时间:"+(end-start));
}
long endTime = System.currentTimeMillis();
System.out.println("总 exec time : " + currentThread().getName()+":"+(endTime - startTime));
if(jedis!=null){
jedis.close();
}
}
}.start();
new Thread() {
@Override
public void run() {
String key = "32021420001:90000300009999:10001:1601198414621:";
long startTime = System.currentTimeMillis();
for (int i = 2001; i <= 4000; i++) {
long start=System.currentTimeMillis();
for (int j = 1; j <= 10000; j++) {
pipelined2.set(key+i+j+"",i+j+"");
}
long end=System.currentTimeMillis();
pipelined2.sync();
System.out.println(Thread.currentThread().getName()+":第"+i+"批次的执行时间:"+(end-start));
}
long endTime = System.currentTimeMillis();
System.out.println("总 exec time : " + currentThread().getName()+":"+(endTime - startTime));
if(jedis2!=null){
jedis2.close();
}
}
}.start();
new Thread() {
@Override
public void run() {
String key = "32021420001:90000300009999:10001:1601198414621:";
long startTime = System.currentTimeMillis();
for (int i = 4001; i <= 6000; i++) {
long start=System.currentTimeMillis();
for (int j = 1; j <= 10000; j++) {
pipelined3.set(key+i+j+"",i+j+"");
}
long end=System.currentTimeMillis();
pipelined3.sync();
System.out.println(Thread.currentThread().getName()+":第"+i+"批次的执行时间:"+(end-start));
}
long endTime = System.currentTimeMillis();
System.out.println("总 exec time : " + currentThread().getName()+":"+(endTime - startTime));
if(jedis3!=null){
jedis3.close();
}
}
}.start();
new Thread() {
@Override
public void run() {
String key = "32021420001:90000300009999:10001:1601198414621:";
long startTime = System.currentTimeMillis();
for (int i = 6001; i <= 8000; i++) {
long start=System.currentTimeMillis();
for (int j = 1; j <= 10000; j++) {
pipelined4.set(key+i+j+"",i+j+"");
}
long end=System.currentTimeMillis();
pipelined4.sync();
System.out.println(Thread.currentThread().getName()+":第"+i+"批次的执行时间:"+(end-start));
}
long endTime = System.currentTimeMillis();
System.out.println("总 exec time : " + currentThread().getName()+":"+(endTime - startTime));
if(jedis4!=null){
jedis4.close();
}
}
}.start();
new Thread() {
@Override
public void run() {
String key = "32021420001:90000300009999:10001:1601198414621:";
long startTime = System.currentTimeMillis();
for (int i = 8001; i <= 10000; i++) {
long start=System.currentTimeMillis();
for (int j = 1; j <= 10000; j++) {
pipelined5.set(key+i+j+"",i+j+"");
}
long end=System.currentTimeMillis();
pipelined5.sync();
System.out.println(Thread.currentThread().getName()+":第"+i+"批次的执行时间:"+(end-start));
}
long endTime = System.currentTimeMillis();
System.out.println("总 exec time : " + currentThread().getName()+":"+(endTime - startTime));
if(jedis5!=null){
jedis5.close();
}
}
}.start();
new Thread() {
@Override
public void run() {
String key = "32021420001:90000300009999:10001:1601198414621:";
long startTime = System.currentTimeMillis();
for (int i = 10001; i <= 12000; i++) {
long start=System.currentTimeMillis();
for (int j = 1; j <= 10000; j++) {
pipelined6.set(key+i+j+"",i+j+"");
}
long end=System.currentTimeMillis();
pipelined6.sync();
System.out.println(Thread.currentThread().getName()+":第"+i+"批次的执行时间:"+(end-start));
}
long endTime = System.currentTimeMillis();
System.out.println("总 exec time : " + currentThread().getName()+":"+(endTime - startTime));
if(jedis6!=null){
jedis6.close();
}
}
}.start();
new Thread() {
@Override
public void run() {
String key = "32021420001:90000300009999:10001:1601198414621:";
long startTime = System.currentTimeMillis();
for (int i = 12001; i <= 14000; i++) {
long start=System.currentTimeMillis();
for (int j = 1; j <= 10000; j++) {
pipelined7.set(key+i+j+"",i+j+"");
}
long end=System.currentTimeMillis();
pipelined7.sync();
System.out.println(Thread.currentThread().getName()+":第"+i+"批次的执行时间:"+(end-start));
}
long endTime = System.currentTimeMillis();
System.out.println("总 exec time : " + currentThread().getName()+":"+(endTime - startTime));
if(jedis7!=null){
jedis7.close();
}
}
}.start();
new Thread() {
@Override
public void run() {
String key = "32021420001:90000300009999:10001:1601198414621:";
long startTime = System.currentTimeMillis();
for (int i = 14001; i <= 16000; i++) {
long start=System.currentTimeMillis();
for (int j = 1; j <= 10000; j++) {
pipelined8.set(key+i+j+"",i+j+"");
}
long end=System.currentTimeMillis();
pipelined8.sync();
System.out.println(Thread.currentThread().getName()+":第"+i+"批次的执行时间:"+(end-start));
}
long endTime = System.currentTimeMillis();
System.out.println("总 exec time : " + currentThread().getName()+":"+(endTime - startTime));
if(jedis8!=null){
jedis8.close();
}
}
}.start();
new Thread() {
@Override
public void run() {
String key = "32021420001:90000300009999:10001:1601198414621:";
long startTime = System.currentTimeMillis();
for (int i = 16001; i <= 18000; i++) {
long start=System.currentTimeMillis();
for (int j = 1; j <= 10000; j++) {
pipelined9.set(key+i+j+"",i+j+"");
}
long end=System.currentTimeMillis();
pipelined9.sync();
System.out.println(Thread.currentThread().getName()+":第"+i+"批次的执行时间:"+(end-start));
}
long endTime = System.currentTimeMillis();
System.out.println("总 exec time : " + currentThread().getName()+":"+(endTime - startTime));
if(jedis9!=null){
jedis9.close();
}
}
}.start();
new Thread() {
@Override
public void run() {
String key = "32021420001:90000300009999:10001:1601198414621:";
long startTime = System.currentTimeMillis();
for (int i = 18001; i <= 20000; i++) {
long start=System.currentTimeMillis();
for (int j = 1; j <= 10000; j++) {
pipelined10.set(key+i+j+"",i+j+"");
}
long end=System.currentTimeMillis();
pipelined10.sync();
System.out.println(Thread.currentThread().getName()+":第"+i+"批次的执行时间:"+(end-start));
}
long endTime = System.currentTimeMillis();
System.out.println("总 exec time : " + currentThread().getName()+":"+(endTime - startTime));
if(jedis10!=null){
jedis10.close();
}
}
}.start();
Thread.sleep(86400000);
}
代码结束后:发现数据库里面的值并没有预期的两亿数据。平均耗时时间为840s插入700多万数据。
原因分析:
为什么预期的两亿数据在数据库里面只显示700多万呢?开始怀疑使用pipeline会造成网络原因,但使用的是公司内网,网络很稳定,后去还去查看jedis的参数和redis的参数设计是否有影响,经过反复测试和分析,最后发现两层for循环
key重复了!!!
例如:111 可能是i=1 j=11 或i=11 j=1。出现这种代码问题让自己变的很尴尬,所以以后出现问题了先怀疑自己,先从自身找结果。
jedispool来测试:
public class JedisPoolUtil {
private static volatile JedisPool jedisPool = null;// 被volatile修饰的变量不会被本地线程缓存,对该变量的读写都是直接操作共享内存。
private JedisPoolUtil() {
}
public static JedisPool getJedisPoolInstance() {
if (null == jedisPool) {
synchronized (JedisPoolUtil.class) {
if (null == jedisPool) {
JedisPoolConfig poolConfig = new JedisPoolConfig();
int timeout=360000;
//最大连接数,默认8个
poolConfig.setMaxTotal(20);
//最大空闲连接处
poolConfig.setMaxIdle(10);
//等待超时时间
poolConfig.setMaxWaitMillis(1000 * 1000);
poolConfig.setTestOnBorrow(true);
jedisPool = new JedisPool(poolConfig, "10.240.30.102", 6379,timeout);
}
}
}
return jedisPool;
}
public static void release(JedisPool jedisPool, Jedis jedis) {
if (null != jedis) {
Jedis jedis2 = null;
try {
jedis2 = jedisPool.getResource();
} finally {
jedis2.close();
}
}
}
}
每批次1w条数据,插入两千万:
new Thread() {
@Override
public void run() {
String key = "32021420001:90000300009999:10001:1601198414621:1";
Jedis jedis=jedisPool.getResource();
jedis.select(3);
Pipeline pipeline=jedis.pipelined();
long startTime = System.currentTimeMillis();
for (int i = 1; i <= 100000000; i++) {
long start=System.currentTimeMillis();
pipeline.set(key+i+"",i+"");
if((i%10000)==0) {
pipeline.sync();
long end=System.currentTimeMillis();
System.out.println(Thread.currentThread().getName()+":第"+i+"批次的执行时间:"+(end-start));
}
}
long endTime = System.currentTimeMillis();
System.out.println("总 exec time : " + Thread.currentThread().getName()+":"+(endTime - startTime));
}
}.start();
1、两个线程跑两千万的时间:210s
2、五个线程跑两千万的时间:170s (每秒11.7w)
3、十个线程跑两千万的时间:190s
结论:线程并非越多越好,得找个时间平衡点。
测试:插入3亿数据测试
因为cpu核数为4,一般线程数设为核数-1,因此用三个线程去各插入一亿数据。插入数据的时候,需要注意linux的内存和redis的内存。
1、在redis中使用info查看内存占用情况。
2、在linux中,使用free -h -s 2 每隔两秒刷新一次内存。
在测试的过程中,发现在插入2亿多条数据的时候,linux的总内存为32G,此时被全部存满了!!而程序还在继续插入,而linux的内存却不再增加了,只剩下几十m左右,但是redis的key还在增加!那么剩余的那些key到底插到哪去了呢?
total used free shared buff/cache available
Mem: 33G 33G 231M 444K 39M 13M
Swap: 3.9G 3.3G 607M
最终程序跑完
redis内存:
# Memory
used_memory:37643237384
used_memory_human:35.06G
used_memory_rss:35134181376
used_memory_rss_human:32.72G
used_memory_peak:37643237384
used_memory_peak_human:35.06G
used_memory_peak_perc:100.00%
used_memory_overhead:18443373494
used_memory_startup:803024
used_memory_dataset:19199863890
used_memory_dataset_perc:51.01%
allocator_allocated:37643230448
allocator_active:37643485184
allocator_resident:38541082624
total_system_memory:35973279744
total_system_memory_human:33.50G
used_memory_lua:37888
used_memory_lua_human:37.00K
used_memory_scripts:0
used_memory_scripts_human:0B
number_of_cached_scripts:0
maxmemory:0
maxmemory_human:0B
maxmemory_policy:noeviction
allocator_frag_ratio:1.00
allocator_frag_bytes:254736
allocator_rss_ratio:1.02
allocator_rss_bytes:897597440
rss_overhead_ratio:0.91
rss_overhead_bytes:-3406901248
mem_fragmentation_ratio:0.93
mem_fragmentation_bytes:-2509013496
mem_not_counted_for_evict:0
mem_replication_backlog:0
mem_clients_slaves:0
mem_clients_normal:118902
mem_aof_buffer:0
mem_allocator:jemalloc-5.1.0
active_defrag_running:0
lazyfree_pending_objects:0
各个线程执行时间:4900s=81分钟,平均每秒6.1w条数据插入
总 exec time : Thread-4:4912535
总 exec time : Thread-3:4923303
总 exec time : Thread-2:4375485
测试redis极限
再插入3亿条数据后,32G内存早已被占满,而used_memory_human:35.06G 应该是超出了3G内存。为了测试redis的极限,改用单线程继续插入:单线程的每批次时间为100多毫秒。在继续插入700万key后redis挂掉,挂掉后,linux会自动杀死这个进程,然后把redis的内存清空!!!
redis内存分析
used_memory_human:以可读的格式返回used_memory used_memory_rss:从操作系统的角度显示Redis进程占用的物理内存总量。 used_memory_rss通常情况是大于used_memory的,因为内存碎片的存在。
mem_fragmentation_ratio > 1: used_memory_rss >used_memory 多出部分没有存储数据,而是被内存碎片消耗了,如果差的很大,说明碎片很严重。
mem_fragmentation_ratio < 1: 一般出现在操作系统把Redis内存交换到硬盘导致。
Redis默认是无限使用内存的,当插入内存超出redis上限后,会产生内存碎片,也就是内存碎片是Redis在分配、回收物理内存过程中产生的这就涉及到redis的内存管理器。
mem_fragmentation_ratio 为0.93,小于1,说明操作系统把redis内存交换到硬盘了。应该是生成了快照文件存放。