我是一个发自内心的探险家,所以当我必须做出技术决定时,比如说,选择一个Redis客户端时,我就去探险。以下是我对Java客户押韵组合的探索:Jedis vs Lettuce。
让我们从基础开始,检查每个客户机,以了解两者之间的差异。
什么是Lettuce?
Lettuce是一个完全无阻塞的Redis Java客户端。它支持同步和异步通信。它的复杂抽象允许您轻松扩展产品。
考虑将Lettuce作为一个更高级的客户端,支持Cluster、Sentinel、Pipelining和codecs。
什么是Jedis?
Jedis是Redis中的一个客户端库,旨在提高性能和易于使用。与其他Redis Java客户端相比,Jedis是一款轻量级产品;它提供的功能较少,但仍可以处理大量内存。
由于其功能更简单,Jedis更易于使用,但它只能与集群同步工作。如果您选择Jedis,您可能会发现专注于应用程序和数据而不是数据存储机制的挑战性较小。
我的计划很简单:
-
在代码中尝试一些简单的事情
-
在代码中尝试一些高级功能
-
达到某种选择标准
-
…
-
利润
利润的目标永远存在,但你可以从中受益的部分是选择标准。这将使我们能够决定什么时候适合Jedis,什么时候适合Lettuce。这一点非常重要,因为我们都知道在选择工具时,任何问题的答案都是“视情况而定”。
一种简单的代码
为了弄清Jedis与Lettuce之间的争论,让我们比较一下所有练习中最简单的代码:设置Redis的单个实例并从中获取值。
首先,我们对Jedis这样做。
package com.guyroyse.blogs.lettucevsjedis; import redis.clients.jedis.Jedis; public class JedisSetGet { private static final String YOUR_CONNECTION_STRING = "redis://:foobared@yourserver:6379/0"; public static void main(String[] args) { Jedis jedis = new Jedis(YOUR_CONNECTION_STRING); jedis.set("foo", "bar"); String result = jedis.get("foo"); jedis.close(); System.out.println(result); // "bar" } }
复制
看看代码,这很简单。创建连接、使用它、关闭它。
接下来,我们用Lettuce来做。
package com.guyroyse.blogs.lettucevsjedis; import io.lettuce.core.RedisClient; import io.lettuce.core.api.StatefulRedisConnection; import io.lettuce.core.api.sync.RedisCommands; public class LettuceSetGet { private static final String YOUR_CONNECTION_STRING = "redis://:foobared@yourserver:6379/0"; public static void main(String[] args) { RedisClient redisClient = RedisClient.create(YOUR_CONNECTION_STRING); StatefulRedisConnection<String, String> connection = redisClient.connect(); RedisCommands<String, String> sync = connection.sync(); sync.set("foo", "bar"); String result = sync.get("foo"); connection.close(); redisClient.shutdown(); System.out.println(result); // "bar" } }
复制
这看起来有点复杂。有一个Java客户端、一个连接和一个命令对象。它们的名字和模板化的性质表明它们可能有多种多样。也许除了StatefulRedisConnection<String,String>类型之外,我们还有一种无状态类型,它需要一个字节[]]?(扰流板:集群和主/副本配置有多种连接类型,但不是无状态连接。)
然而,一旦您完成了设置和拆卸,这两个客户端中的基本代码都是一样的:创建连接、使用它、关闭它。
现在,像这样简单的事情,Jedis看起来更容易了。这是有道理的,因为它的代码更少。但我确信,Lettuce软件拥有所有这些东西,可能是为了处理更高级的场景。
Jedis pipeline、同步和异步
除了pipeline之外,Jedis完全是同步的。pipeline允许Jedis异步使用Redis,但不幸的是,它们不能与集群一起使用。然而,pipeline很容易使用:
package com.guyroyse.blogs.lettucevsjedis; import redis.clients.jedis.Jedis; import redis.clients.jedis.Pipeline; import redis.clients.jedis.Response; import redis.clients.jedis.Tuple; import java.util.Set; import java.util.stream.Collectors; public class JedisPipelining { private static final String YOUR_CONNECTION_STRING = "redis://:foobared@yourserver:6379/0"; public static void main(String[] args) { Jedis jedis = new Jedis(YOUR_CONNECTION_STRING); Pipeline p = jedis.pipelined(); p.set("foo", "bar"); Response<String> get = p.get("foo"); p.zadd("baz", 13, "alpha"); p.zadd("baz", 23, "bravo"); p.zadd("baz", 42, "charlie"); Response<Set<Tuple>> range = p.zrangeWithScores("baz", 0, -1); p.sync(); jedis.close(); System.out.println(get.get()); // "bar" System.out.println(range.get().stream() .map(Object::toString) .collect(Collectors.joining(" "))); // [alpha,13.0] [bravo,23.0] [charlie,42.0] } }
复制
Lettuce库支持同步、异步甚至反应式接口,如果您喜欢这种接口(我就是这样)。然而,这里有一个关于Lettuce库的重要注意事项:这些只是Lettuce’s多线程、基于事件的模型之上的语法糖层,该模型当然使用了流水线。即使您同步使用它,它在本质上也是异步的。
我们已经看到了同步接口在使用我们的超级复杂的集合,并获得了一个示例。但让我们看看异步示例:
package com.guyroyse.blogs.lettucevsjedis; import io.lettuce.core.RedisClient; import io.lettuce.core.api.StatefulRedisConnection; import io.lettuce.core.api.async.RedisAsyncCommands; public class LettuceAsync { private static final String YOUR_CONNECTION_STRING = "redis://:foobared@yourserver:6379/0"; public static void main(String[] args) { RedisClient redisClient = RedisClient.create(YOUR_CONNECTION_STRING); StatefulRedisConnection<String, String> connection = redisClient.connect(); RedisAsyncCommands<String, String> async = connection.async(); final String[] result = new String[1]; async.set("foo", "bar") .thenComposeAsync(ok -> async.get("foo")) .thenAccept(s -> result[0] = s) .toCompletableFuture() .join(); connection.close(); redisClient.shutdown(); System.out.println(result[0]); // "bar" } }
复制
这就像同步示例一样,设置和获取,但显然,这是更复杂的代码。它也是多线程的。
Jedis和多线程代码
Jedis可以很好地处理多线程应用程序,但Jedis连接不是线程安全的。所以不要分享它们。如果您跨线程共享Jedis连接,Redis会脱口而出各种协议错误,如:
expected '$' but got ' '
复制
要解决这类问题,请使用JedisPool,它是一个线程安全对象,用于分发线程不安全的Jedis对象。使用它很简单,就像其他Jedis一样。只需请求一个线程,并在完成后通过.close()将其返回到池,像这样:
package com.guyroyse.blogs.lettucevsjedis; import redis.clients.jedis.*; import java.util.List; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.IntStream; public class JedisMultithreaded { private static final String YOUR_CONNECTION_STRING = "redis://:foobared@yourserver:6379/0"; public static void main(String[] args) { JedisPool pool = new JedisPool(YOUR_CONNECTION_STRING); List<String> allResults = IntStream.rangeClosed(1, 5) .parallel() .mapToObj(n -> { Jedis jedis = pool.getResource(); jedis.set("foo" + n, "bar" + n); String result = jedis.get("foo" + n); jedis.close(); return result; }) .collect(Collectors.toList()); pool.close(); System.out.println(allResults); // "bar1, bar2, bar3, bar4, bar5" } }
复制
这些Jedis对象中的每一个都封装了到Redis的单个连接,因此(取决于池的大小)可能存在阻塞或空闲连接。此外,这些连接是同步的,因此总是有一定程度的空闲。
Jedis, Lettuce和集群
我觉得我应该谈谈集群,但至少在比较方面没有什么可说的。有很多功能需要讨论,但这两个库都支持集群。毫不奇怪,Jedis更易于使用,但它只能与集群同步工作。Lettuce软件更难使用,但能够与集群进行同步、异步和反应式交互。
这是重复出现的主题。这并不奇怪。据Jedis自己承认,“Jedis被认为易于使用。”Lettuce在其主页上表示,“Lettuce’s a scalable Redis client for building non-blocking Reactive applications”。
当然,如果您使用的是Redis Enterprise,则不必担心集群,因为集群是在服务器端处理的。只需使用Jedis或Lettuce的非集群API,管理您的密钥,以便将它们插入正确的碎片,就可以了。
做出决定
那么,Jedis还是Lettuce?嗯,这要看情况而定。(看,我告诉过你我们会在这里结束!)这是代码复杂性和应用程序可伸缩性之间的经典权衡。
如果您需要高可伸缩性的东西,请使用Lettuce软件。它更复杂的抽象提供了使可伸缩产品更容易实现的能力。Lettuce是一个功能强大的解决方案,可以让您充分利用Redis的全部功能。
如果您需要快速构建一些东西,并且可伸缩性不是也可能不会成为问题,请使用Jedis。它简单易用,使您更容易关注应用程序和数据,而不是数据存储机制。
如果你仍然无法决定,你可以一直使用Spring Data Redis,它将抽象出Jedis和Lettuce,这样你可以在未来改变主意。当然,这也有它自己的一套折衷方案。但这是未来博客文章的主题!
原文标题:Jedis vs. Lettuce: An Exploration
原文作者:Guy Royse
原文链接:https://redis.com/blog/jedis-vs-lettuce-an-exploration/