参考文章:
不过这里面的代码感觉不太对,所以按照这个思路自己写代码做了下测试
1、添加依赖
添加jmh依赖:
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>1.21</version>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>1.21</version>
<scope>provided</scope>
</dependency>
添加jedis和lettuce依赖:
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
<version>6.0.2.RELEASE</version>
</dependency>
开始lettuce用5.0版本,发现性能很差,改用6.0之后,性能提升很多,因此这里采用6.0版本进行对比(jedis升级前后性能相差不大)
2、代码编写
2.1 Jedis:
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisPoolConfig;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@BenchmarkMode(Mode.Throughput)
@Warmup(iterations = 1)
@Threads(20)
@State(Scope.Benchmark)
@Measurement(iterations = 10, time = 60000, timeUnit = TimeUnit.MILLISECONDS)
@OutputTimeUnit(TimeUnit.SECONDS)
public class JedisJmhTest {
private JedisCluster jc;
@Setup
public void setup() {
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(60);
config.setMaxIdle(10);
config.setMinIdle(2);
config.setMaxWaitMillis(3000);
Set<HostAndPort> jedisClusterNodes = new HashSet<>();
jedisClusterNodes.add(new HostAndPort(redishost, port));
jc = new JedisCluster(jedisClusterNodes, 2000, 2000, 5, password, config);
}
@Benchmark
public void get() {
jc.get("a");
}
public static void main(String[] args) throws RunnerException {
Options options = new OptionsBuilder().include(JedisJmhTest.class.getSimpleName())
.output("d:/data/logs/benchmark/jedis-Throughput.log").forks(1).build();
new Runner(options).run();
}
}
这里使用的redis集群,3主3从,因此maxTotal要设置大一点,太小的话会导致吞吐量上不来
2.2 Lettuce:
import io.lettuce.core.cluster.RedisClusterClient;
import io.lettuce.core.cluster.api.StatefulRedisClusterConnection;
import io.lettuce.core.cluster.api.sync.RedisAdvancedClusterCommands;
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import java.util.concurrent.TimeUnit;
@BenchmarkMode(Mode.Throughput)
@Warmup(iterations = 1)
@Threads(20)
@State(Scope.Benchmark)
@Measurement(iterations = 10, time = 60000, timeUnit = TimeUnit.MILLISECONDS)
@OutputTimeUnit(TimeUnit.SECONDS)
public class LettuceSyncJmhTest {
RedisClusterClient client;
private StatefulRedisClusterConnection<String, String> connection;
@Setup
public void setup() {
client = RedisClusterClient .create("redis://password@redishost:part/0");
connection = client.connect();
}
@Benchmark
public void get() {
RedisAdvancedClusterCommands<String, String> commands = connection.sync();
commands.get("a");
}
@TearDown
public void tearDown() {
connection.close();
client.shutdown();
}
public static void main(String[] args) throws RunnerException {
Options options = new OptionsBuilder().include(LettuceSyncJmhTest.class.getSimpleName())
.output("d:/data/logs/benchmark/lettuce-Throughput.log").forks(1).build();
new Runner(options).run();
}
}
Lettuce主要测试同步调用的性能
3、测试结果
Jedis:
# Warmup Iteration 1: 2939.828 ops/s
Iteration 1: 3525.044 ops/s
Iteration 2: 3493.547 ops/s
Iteration 3: 3659.585 ops/s
Iteration 4: 3815.806 ops/s
Iteration 5: 3803.759 ops/s
Iteration 6: 3674.697 ops/s
Iteration 7: 3636.788 ops/s
Iteration 8: 3674.486 ops/s
Iteration 9: 3687.034 ops/s
Iteration 10: 3582.874 ops/s
Result "com.tianzy.JedisJmhTest.get":
3655.362 ±(99.9%) 157.996 ops/s [Average]
(min, avg, max) = (3493.547, 3655.362, 3815.806), stdev = 104.505
CI (99.9%): [3497.366, 3813.358] (assumes normal distribution)
Lettuce:
# Warmup Iteration 1: 3332.678 ops/s
Iteration 1: 3378.788 ops/s
Iteration 2: 3363.920 ops/s
Iteration 3: 3346.096 ops/s
Iteration 4: 3271.721 ops/s
Iteration 5: 3134.163 ops/s
Iteration 6: 3145.072 ops/s
Iteration 7: 3268.907 ops/s
Iteration 8: 3242.946 ops/s
Iteration 9: 3194.674 ops/s
Iteration 10: 3464.621 ops/s
Result "com.tianzy.LettuceSyncJmhTest.get":
3281.091 ±(99.9%) 162.177 ops/s [Average]
(min, avg, max) = (3134.163, 3281.091, 3464.621), stdev = 107.270
CI (99.9%): [3118.913, 3443.268] (assumes normal distribution)
以上结果是本地执行的,两者性能看起来差不多,感觉受网络影响比较大,同一个类多次执行偏差都会比较大。
从JVM监控来看,lettuce对内存的使用更高。使用的jdk8默认JVM参数,自动调整堆内存,lettuce使用的堆内存和metaspace更大,GC也更频繁。不过相对来说,这点影响可以忽略不计。
单独从性能表现来看,lettuce并没有明显优势。
Lettuce主要优势在于:只需要跟每个redis节点保持一个连接即可,而jedis可能需要几十个。在实际生产场景,会有很多应用连接到同一个redis集群,使用jedis可能导致redis连接数过多,而lettuce则没有这个问题。
还有lettuce支持响应式编程,本次只是使用了同步api,所以性能优势不明显,在响应式编程的场景,会更有用武之地。
前面的测试结果是本地的,受网络等原因不够准确,试一下在服务器上执行
pom.xml文件中添加:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.0</version>
<configuration>
<compilerVersion>1.8</compilerVersion>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<finalName>microbenchmarks</finalName>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>org.openjdk.jmh.Main</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
然后执行 mvn clean verify进行打包,target目录下会生成microbenchmarks.jar,上传到服务器
$ java -jar microbenchmarks.jar -l
Benchmarks:
com.tianzy.JedisJmhTest.get
com.tianzy.LettuceSyncJmhTest.get
分别执行java -jar microbenchmarks.jar com.tianzy.LettuceSyncJmhTest.get、java -jar microbenchmarks.jar com.tianzy.JedisJmhTest.get,即可开启执行。
Jedis:
# Warmup Iteration 1: 35730.066 ops/s
Iteration 1: 37333.325 ops/s
Iteration 2: 36433.292 ops/s
Iteration 3: 36132.089 ops/s
Iteration 4: 37523.689 ops/s
Iteration 5: 35924.923 ops/s
Iteration 6: 35771.176 ops/s
Iteration 7: 36868.176 ops/s
Iteration 8: 33352.998 ops/s
Iteration 9: 35858.396 ops/s
Iteration 10: 34711.806 ops/s
Result "com.tianzy.JedisJmhTest.get":
35990.987 ±(99.9%) 1872.209 ops/s [Average]
(min, avg, max) = (33352.998, 35990.987, 37523.689), stdev = 1238.350
CI (99.9%): [34118.778, 37863.196] (assumes normal distribution)
Lettuce:
# Warmup Iteration 1: 14142.306 ops/s
Iteration 1: 16096.286 ops/s
Iteration 2: 16054.059 ops/s
Iteration 3: 16509.278 ops/s
Iteration 4: 15447.965 ops/s
Iteration 5: 15807.480 ops/s
Iteration 6: 15885.412 ops/s
Iteration 7: 15649.933 ops/s
Iteration 8: 15680.563 ops/s
Iteration 9: 16211.962 ops/s
Iteration 10: 14812.771 ops/s
Result "com.tianzy.LettuceSyncJmhTest.get":
15815.571 ±(99.9%) 706.501 ops/s [Average]
(min, avg, max) = (14812.771, 15815.571, 16509.278), stdev = 467.307
CI (99.9%): [15109.070, 16522.072] (assumes normal distribution)
惊呆了,Jedis性能有Lettuce的2倍多。经过多次测试,结论是Jedis确实比Lettuce快多了。
查看源码分析和推测:
Jedis直接采用阻塞模型,代码比较简单,也比较高效。
Lettuce采用的Netty,代码较复杂,使用了反射、异步,同步线程中调用异步线程,又要等待异步线程执行完,性能损耗反而比较大。
进一步跟踪Lettuce的代码发现,在使用同步调用时,是会auto flush的,即write完立刻flush。查看官网文档,这种模式是会影响性能的,但是同步调用又必须得这么做。
大概得出结论:Lettuce对于同步调用在性能上有劣势,优势在于响应式异步编程。
最后,我决定还是继续用Jedis吧。