为什么在redis中使用管道时,有100,000条记录这么慢?

时间:2013-05-22 16:49:28

标签: java redis pipeline

据说在redis中需要许多pipeline时,set/get是更好的方法,所以这是我的测试代码:

public class TestPipeline {

    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub

        JedisShardInfo si = new JedisShardInfo("127.0.0.1", 6379);
        List<JedisShardInfo> list = new ArrayList<JedisShardInfo>();
        list.add(si);
        ShardedJedis jedis = new ShardedJedis(list);
        long startTime = System.currentTimeMillis();
        ShardedJedisPipeline pipeline = jedis.pipelined();
        for (int i = 0; i < 100000; i++) {
            Map<String, String> map = new HashMap<String, String>();
            map.put("id", "" + i);
            map.put("name", "lyj" + i);
            pipeline.hmset("m" + i, map);
        }
        pipeline.sync();
        long endTime = System.currentTimeMillis();
        System.out.println(endTime - startTime);
    }
}

当我运行它时,暂时没有对此程序的响应,但是当我不使用pipe时,它只需要20073 ms,所以我很困惑为什么没有{更好{1}}以及差距如何!

感谢您回答我,几个问题,您如何计算6MB数据? 当我发送10K数据时,管道总是比正常模式更快,但是100k,管道没有响应。我认为100-1000操作是一个明智的选择,如下所述。是不是有任何JIT,因为我不明白?

1 个答案:

答案 0 :(得分:14)

在编写此类基准测试之前需要考虑几点(尤其是使用JVM的基准测试):

  • 在大多数(物理)机器上,当使用流水线时,Redis能够处理超过100K的操作数/秒。您的基准仅处理100K项目,因此它的持续时间不足以产生有意义的结果。此外,JIT的连续阶段没有时间进入。

  • 绝对时间不是一个非常相关的指标。在保持基准运行至少10秒的同时显示吞吐量(即每秒的操作次数)将是一个更好,更稳定的指标。

  • 你的内循环会产生大量垃圾。如果您计划对Jedis + Redis进行基准测试,那么您需要将自己程序的开销保持在较低水平。

  • 因为你已经在main函数中定义了所有内容,所以你的循环不会被JIT编译(取决于你使用的JVM)。只有内部方法调用可能是。如果您希望JIT高效,请确保将代码封装到可由JIT编译的方法中。

  • 可选地,您可能希望在执行实际测量之前添加预热阶段,以避免计算使用裸骨解释器运行第一次迭代的开销以及JIT本身的成本。

现在,关于Redis流水线技术,您的管道太长了。管道中的100K命令意味着Jedis必须在向Redis发送任何内容之前构建一个6MB的缓冲区。这意味着套接字缓冲区(在客户端,可能是服务器端)将饱和,Redis也必须处理6 MB通信缓冲区。

此外,您的基准测试仍然是同步的(使用管道不会神奇地使其异步)。换句话说,在将管道的最后一个查询发送到Redis之前,Jedis不会开始阅读回复。当管道太长时,它有可能阻塞事物。

考虑将管道大小限制为100-1000次操作。当然,它会产生更多的往返,但通信栈的压力将降低到可接受的水平。例如,请考虑以下程序:

import redis.clients.jedis.*;
import java.util.*;

public class TestPipeline {

    /**
     * @param args
     */

    int i = 0; 
    Map<String, String> map = new HashMap<String, String>();
    ShardedJedis jedis;  

    // Number of iterations
    // Use 1000 to test with the pipeline, 100 otherwise
    static final int N = 1000;

    public TestPipeline() {
      JedisShardInfo si = new JedisShardInfo("127.0.0.1", 6379);
      List<JedisShardInfo> list = new ArrayList<JedisShardInfo>();
      list.add(si);
      jedis = new ShardedJedis(list);
    } 

    public void push( int n ) {
     ShardedJedisPipeline pipeline = jedis.pipelined();
     for ( int k = 0; k < n; k++) {
      map.put("id", "" + i);
      map.put("name", "lyj" + i);
      pipeline.hmset("m" + i, map);
      ++i;
     }
     pipeline.sync(); 
    }

    public void push2( int n ) {
     for ( int k = 0; k < n; k++) {
      map.put("id", "" + i);
      map.put("name", "lyj" + i);
      jedis.hmset("m" + i, map);
      ++i;
     }
    }

    public static void main(String[] args) {
      TestPipeline obj = new TestPipeline();
      long startTime = System.currentTimeMillis();
      for ( int j=0; j<N; j++ ) {
       // Use push2 instead to test without pipeline
       obj.push(1000); 
       // Uncomment to see the acceleration
       //System.out.println(obj.i);
     }
     long endTime = System.currentTimeMillis();
     double d = 1000.0 * obj.i;
     d /= (double)(endTime - startTime);
     System.out.println("Throughput: "+d);
   }
 }

使用此程序,您可以使用或不使用流水线进行测试。使用流水线时,请务必增加迭代次数(N参数),以使其运行至少10秒。如果你在循环中取消注释println,你会发现程序在开始时很慢并且随着JIT开始优化而变得更快(这就是为什么程序应该运行至少几秒才能产生有意义的结果)。 / p>

在我的硬件(旧的Athlon盒子)上,使用管道时,我可以获得8-9倍的吞吐量。通过优化内循环中的键/值格式化并添加预热阶段,可以进一步改进程序。