Lettuce和Jedis的基準測試
ofollow,noindex">原文連結:https://www.dubby.cn/detail.html?id=9108
1.準備工作
本地需要安裝Redis,使用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>5.0.3.RELEASE</version> </dependency>
如果你對JMH不是很熟悉,建議你去看看Code Tools: jmh ,和samples 。
2.編寫測試程式碼
2.1 Jedis
@BenchmarkMode(Mode.Throughput) @Warmup(iterations = 1) @Threads(100) @State(Scope.Thread) @Measurement(iterations = 2, time = 600, timeUnit = TimeUnit.MILLISECONDS) @OutputTimeUnit(TimeUnit.MILLISECONDS) public class JedisStudy { private static final int LOOP = 1; private Jedis jedis; @Setup public void setup() { jedis = new Jedis("127.0.0.1", 6379); } @Benchmark public void get() { for (int i = 0; i < LOOP; ++i) { jedis.get("a"); } } }
public static void main(String[] args) throws RunnerException { Options options = new OptionsBuilder().include(JedisStudy.class.getSimpleName()) .output("benchmark/jedis-Throughput.log").forks(1).build(); new Runner(options).run(); }
這裡用到的註解,其中@OutputTimeUnit(TimeUnit.MILLISECONDS)
很容易理解,就是測試結果的單位,@Threads(100)
是開啟多少個執行緒測試;@Warmup(iterations = 1)
是預熱的迴圈次數;@BenchmarkMode(Mode.Throughput)
是測試的模式,可以測試吞吐,延時等;@Measurement(iterations = 2, time = 600, timeUnit = TimeUnit.MILLISECONDS)
是測試的迴圈次數,以及時長;其中比較難以理解的就是@State(Scope.Thread)
,這裡我簡單描述下吧,@State(Scope.Thread)
和下面的@Setup
配合使用,意思是每個測試執行緒,都會使用獨立的一個變數,這個變數就是Jedis jedis
,使用@Setup
所修飾的方法來做初始化。
參考JMHSample_03_States 和JMHSample_04_DefaultState 。
其中因為Jedis是執行緒不安全的,所以每個執行緒使用的都是一個單獨的Jedis物件,這裡可以使用Pool來優化,讀者如果感興趣,可以嘗試。
2.2 Lettuce
@BenchmarkMode(Mode.Throughput) @Warmup(iterations = 1) @Threads(100) @State(Scope.Benchmark) @Measurement(iterations = 2, time = 600, timeUnit = TimeUnit.MILLISECONDS) @OutputTimeUnit(TimeUnit.MILLISECONDS) public class LettuceAsyncStudy { private static final int LOOP = 1; private StatefulRedisConnection<String, String> connection; @Setup public void setup() { RedisClient client = RedisClient.create("redis://localhost"); connection = client.connect(); } @Benchmark public void get() throws ExecutionException, InterruptedException { RedisAsyncCommands<String, String> commands = connection.async(); List<RedisFuture<String>> redisFutureList = new ArrayList<>(); for (int i = 0; i < LOOP; ++i) { RedisFuture<String> future = commands.get("a"); redisFutureList.add(future); future.get(); } redisFutureList.forEach(f -> { try { f.get(); } catch (Exception e) { e.printStackTrace(); } }); } }
public static void main(String[] args) throws RunnerException { Options options = new OptionsBuilder().include(LettuceAsyncStudy.class.getSimpleName()) .output("benchmark/lettuceAsync-Throughput.log").forks(1).build(); new Runner(options).run(); }
這裡和上面Jedis的區別就是@State(Scope.Benchmark)
,其實就是StatefulRedisConnection<String, String> connection
這個物件是所有測試執行緒共享的,因為Lettuce的StatefulRedisConnection是執行緒的安全的,所以可以這麼用。
3.測試結果
Client | 執行緒數(併發數) | 每次測試方法裡迴圈執行get的次數 | Throughput(ops/ms) |
---|---|---|---|
Jedis | 100 | 1 | 46.628 |
Lettuce | 100 | 1 | 106.589 |
-- | -- | -- | -- |
Jedis | 100 | 10 | 5.307 |
Lettuce | 100 | 10 | 14.802 |
-- | -- | -- | -- |
Jedis | 100 | 100 | 0.483 |
Lettuce | 100 | 100 | 1.599 |