redis性能测试:多线程向redis插入亿级数据的时间性能(二)

1,054 阅读2分钟

服务器性能

继续上次的测试,这次测试换用更大的测试机测试,查看服务器性能。

参数linux指令
内存free -htotal:33G free 32G
系统cat /etc/redhat-releaseCentOS Linux release 7.8.2003 (Core)
cpu数量cat /proc/cpuinfocpu core:4
HZcat /proc/cpuinfo l grep MHz l uniqMHz: 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:4912535exec time : Thread-3:4923303exec 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内存交换到硬盘了。应该是生成了快照文件存放。