Lettuce 是 Redis 的一款高級 Java 用戶端,與 Jedis 並列成為最熱門的客戶端之一,目前已成為 SpringBoot 2.0 版本預設的 redis 用戶端。
相比老牌 Jedis,Lettuce 屬於後起之秀,不僅功能豐富,而且提供了許多新的功能特性,例如非同步操作、響應式程式設計等等,同時還解決了 Jedis 中線程不安全的問題。
首先,建立一個 maven 項目,引入lettuce-core
包,就可以使用了。
<dependency> <groupId>io.lettuce</groupId> <artifactId>lettuce-core</artifactId> <version>5.3.1.RELEASE</version> </dependency>
使用 lettuce 連接 redis,測試是否能正常聯通!
public class LettuceMain { public static void main(String[] args) { RedisURI redisUri = RedisURI.builder() .withHost("127.0.0.1") .withPort(6379) .withPassword("111111") .withTimeout(Duration.of(10, ChronoUnit.SECONDS)) .build(); RedisClient redisClient = RedisClient.create(redisUri); StatefulRedisConnection<String, String> connection = redisClient.connect(); RedisCommands<String, String> commands = connection.sync(); System.out.println(commands.ping()); connection.close(); redisClient.shutdown(); } }
基本上只要是 Jedis 支援的同步指令操作,Lettuce 都會支援。
下面,我們以同步運算字串為例,Lettuce 的 api 運算如下!
public class LettuceSyncMain { public static void main(String[] args) { RedisURI redisUri = RedisURI.builder() .withHost("127.0.0.1").withPort(6379).withPassword("111111") .withTimeout(Duration.of(10, ChronoUnit.SECONDS)) .build(); RedisClient redisClient = RedisClient.create(redisUri); StatefulRedisConnection<String, String> connection = redisClient.connect(); //获取同步操作命令工具 RedisCommands<String, String> commands = connection.sync(); System.out.println("清空数据:"+commands.flushdb()); System.out.println("判断某个键是否存在:"+commands.exists("username")); System.out.println("新增<'username','xmr'>的键值对:"+commands.set("username", "xmr")); System.out.println("新增<'password','password'>的键值对:"+commands.set("password", "123")); System.out.println("获取<'password'>键的值:"+commands.get("password")); System.out.println("系统中所有的键如下:" + commands.keys("*")); System.out.println("删除键password:"+commands.del("password")); System.out.println("判断键password是否存在:"+commands.exists("password")); System.out.println("设置键username的过期时间为5s:"+commands.expire("username", 5L)); System.out.println("查看键username的剩余生存时间:"+commands.ttl("username")); System.out.println("移除键username的生存时间:"+commands.persist("username")); System.out.println("查看键username的剩余生存时间:"+commands.ttl("username")); System.out.println("查看键username所存储的值的类型:"+commands.type("username")); connection.close(); redisClient.shutdown(); } }
除此之外,Lettuce 也支援非同步操作,將上面的操作改成非同步處理,結果如下!
public class LettuceASyncMain { public static void main(String[] args) throws Exception { RedisURI redisUri = RedisURI.builder() .withHost("127.0.0.1").withPort(6379).withPassword("111111") .withTimeout(Duration.of(10, ChronoUnit.SECONDS)) .build(); RedisClient redisClient = RedisClient.create(redisUri); StatefulRedisConnection<String, String> connection = redisClient.connect(); //获取异步操作命令工具 RedisAsyncCommands<String, String> commands = connection.async(); System.out.println("清空数据:"+commands.flushdb().get()); System.out.println("判断某个键是否存在:"+commands.exists("username").get()); System.out.println("新增<'username','xmr'>的键值对:"+commands.set("username", "xmr").get()); System.out.println("新增<'password','password'>的键值对:"+commands.set("password", "123").get()); System.out.println("获取<'password'>键的值:"+commands.get("password").get()); System.out.println("系统中所有的键如下:" + commands.keys("*").get()); System.out.println("删除键password:"+commands.del("password").get()); System.out.println("判断键password是否存在:"+commands.exists("password").get()); System.out.println("设置键username的过期时间为5s:"+commands.expire("username", 5L).get()); System.out.println("查看键username的剩余生存时间:"+commands.ttl("username").get()); System.out.println("移除键username的生存时间:"+commands.persist("username").get()); System.out.println("查看键username的剩余生存时间:"+commands.ttl("username").get()); System.out.println("查看键username所存储的值的类型:"+commands.type("username").get()); connection.close(); redisClient.shutdown(); } }
Lettuce 除了支援非同步編程以外,還支援響應式編程,Lettuce 引入的響應式編程框架是Project Reactor
,如果沒有響應式程式設計經驗可以先自行了解。
響應式程式設計使用案例如下:
public class LettuceMain { public static void main(String[] args) throws Exception { RedisURI redisUri = RedisURI.builder() .withHost("127.0.0.1").withPort(6379).withPassword("111111") .withTimeout(Duration.of(10, ChronoUnit.SECONDS)) .build(); RedisClient redisClient = RedisClient.create(redisUri); StatefulRedisConnection<String, String> connection = redisClient.connect(); //获取响应式API操作命令工具 RedisReactiveCommands<String, String> commands = connection.reactive(); Mono<String> setc = commands.set("name", "mayun"); System.out.println(setc.block()); Mono<String> getc = commands.get("name"); getc.subscribe(System.out::println); Flux<String> keys = commands.keys("*"); keys.subscribe(System.out::println); //开启一个事务,先把count设置为1,再将count自增1 commands.multi().doOnSuccess(r -> { commands.set("count", "1").doOnNext(value -> System.out.println("count1:" + value)).subscribe(); commands.incr("count").doOnNext(value -> System.out.println("count2:" + value)).subscribe(); }).flatMap(s -> commands.exec()) .doOnNext(transactionResult -> System.out.println("transactionResult:" + transactionResult.wasDiscarded())).subscribe(); Thread.sleep(1000 * 5); connection.close(); redisClient.shutdown(); } }
Lettuce 也支援redis 的訊息發布和訂閱,具體實作案例如下:
public class LettuceReactiveMain1 { public static void main(String[] args) throws Exception { RedisURI redisUri = RedisURI.builder() .withHost("127.0.0.1").withPort(6379).withPassword("111111") .withTimeout(Duration.of(10, ChronoUnit.SECONDS)) .build(); RedisClient redisClient = RedisClient.create(redisUri); //获取发布订阅操作命令工具 StatefulRedisPubSubConnection<String, String> pubsubConn = redisClient.connectPubSub(); pubsubConn.addListener(new RedisPubSubListener<String, String>() { @Override public void unsubscribed(String channel, long count) { System.out.println("[unsubscribed]" + channel); } @Override public void subscribed(String channel, long count) { System.out.println("[subscribed]" + channel); } @Override public void punsubscribed(String pattern, long count) { System.out.println("[punsubscribed]" + pattern); } @Override public void psubscribed(String pattern, long count) { System.out.println("[psubscribed]" + pattern); } @Override public void message(String pattern, String channel, String message) { System.out.println("[message]" + pattern + " -> " + channel + " -> " + message); } @Override public void message(String channel, String message) { System.out.println("[message]" + channel + " -> " + message); } }); RedisPubSubAsyncCommands<String, String> pubsubCmd = pubsubConn.async(); pubsubCmd.psubscribe("CH"); pubsubCmd.psubscribe("CH2"); pubsubCmd.unsubscribe("CH"); Thread.sleep(100 * 5); pubsubConn.close(); redisClient.shutdown(); } }
Lettuce 用戶端的通訊框架整合了Netty 的非阻塞IO 操作,客戶端資源的設定與Lettuce 的效能、並發和事件處理緊密相關,如果不是特別熟悉客戶端參數配置,不建議在沒有經驗的前提下憑直覺修改預設值,保持預設配置就行。
非叢集環境下,具體的設定案例如下:
public class LettuceMain { public static void main(String[] args) throws Exception { ClientResources resources = DefaultClientResources.builder() .ioThreadPoolSize(4) //I/O线程数 .computationThreadPoolSize(4) //任务线程数 .build(); RedisURI redisUri = RedisURI.builder() .withHost("127.0.0.1").withPort(6379).withPassword("111111") .withTimeout(Duration.of(10, ChronoUnit.SECONDS)) .build(); ClientOptions options = ClientOptions.builder() .autoReconnect(true)//是否自动重连 .pingBeforeActivateConnection(true)//连接激活之前是否执行PING命令 .build(); RedisClient client = RedisClient.create(resources, redisUri); client.setOptions(options); StatefulRedisConnection<String, String> connection = client.connect(); RedisCommands<String, String> commands = connection.sync(); commands.set("name", "关羽"); System.out.println(commands.get("name")); connection.close(); client.shutdown(); resources.shutdown(); } }
叢集環境下,具體的設定案例如下:
public class LettuceMain { public static void main(String[] args) throws Exception { ClientResources resources = DefaultClientResources.builder() .ioThreadPoolSize(4) //I/O线程数 .computationThreadPoolSize(4) //任务线程数 .build(); RedisURI redisUri = RedisURI.builder() .withHost("127.0.0.1").withPort(6379).withPassword("111111") .withTimeout(Duration.of(10, ChronoUnit.SECONDS)) .build(); ClusterClientOptions options = ClusterClientOptions.builder() .autoReconnect(true)//是否自动重连 .pingBeforeActivateConnection(true)//连接激活之前是否执行PING命令 .validateClusterNodeMembership(true)//是否校验集群节点的成员关系 .build(); RedisClusterClient client = RedisClusterClient.create(resources, redisUri); client.setOptions(options); StatefulRedisClusterConnection<String, String> connection = client.connect(); RedisAdvancedClusterCommands<String, String> commands = connection.sync(); commands.set("name", "张飞"); System.out.println(commands.get("name")); connection.close(); client.shutdown(); resources.shutdown(); } }
Lettuce 連接設計的時候,就是線程安全的,所以一個連接可以被多個線程共享,同時lettuce 連接預設是自動重連的,使用單連接基本上可以滿足業務需求,大多數情況下不需要配置連接池,多重連線不會為操作帶來效能上的提升。
但在某些特殊場景下,例如事物操作,使用連線池會是比較好的方案,那麼如何配置執行緒池呢?
public class LettuceMain { public static void main(String[] args) throws Exception { RedisURI redisUri = RedisURI.builder() .withHost("127.0.0.1") .withPort(6379) .withPassword("111111") .withTimeout(Duration.of(10, ChronoUnit.SECONDS)) .build(); RedisClient client = RedisClient.create(redisUri); //连接池配置 GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig(); poolConfig.setMaxIdle(2); GenericObjectPool<StatefulRedisConnection<String, String>> pool = ConnectionPoolSupport.createGenericObjectPool(client::connect, poolConfig); StatefulRedisConnection<String, String> connection = pool.borrowObject(); RedisCommands<String, String> commands = connection.sync(); commands.set("name", "张飞"); System.out.println(commands.get("name")); connection.close(); pool.close(); client.shutdown(); } }
redis 一般採用主從複製模式,搭建高可用的架構,簡單的說就一個主節點,多個從節點,自動從主節點同步最新資料。
Lettuce 支援自動發現主從模式下的節點信息,然後保存到本地,具體配置如下:
public class LettuceMain { public static void main(String[] args) throws Exception { //这里只需要配置一个节点的连接信息,不一定需要是主节点的信息,从节点也可以;可以自动发现主从节点 RedisURI uri = RedisURI.builder().withHost("192.168.31.111").withPort(6379).withPassword("123456").build(); RedisClient client = RedisClient.create(uri); StatefulRedisMasterReplicaConnection<String, String> connection = MasterReplica.connect(client, StringCodec.UTF8, uri); //从节点读取数据 connection.setReadFrom(ReadFrom.REPLICA); RedisCommands<String, String> commands = connection.sync(); commands.set("name", "张飞"); System.out.println(commands.get("name")); connection.close(); client.shutdown(); } }
當然我們也可以手動指定集群節點來加載,具體配置如下:
public class LettuceMain { public static void main(String[] args) throws Exception { //集群节点 List<RedisURI> uris = new ArrayList(); uris.add(RedisURI.builder().withHost("192.168.31.111").withPort(6379).withPassword("111111").build()); uris.add(RedisURI.builder().withHost("192.168.31.112").withPort(6379).withPassword("111111").build()); uris.add(RedisURI.builder().withHost("192.168.31.113").withPort(6379).withPassword("111111").build()); RedisClient client = RedisClient.create(); StatefulRedisMasterReplicaConnection<String, String> connection = MasterReplica.connect(client, StringCodec.UTF8, uris); //从节点读取数据 connection.setReadFrom(ReadFrom.REPLICA); RedisCommands<String, String> commands = connection.sync(); commands.set("name", "张飞"); System.out.println(commands.get("name")); connection.close(); client.shutdown(); } }
哨兵模式,也是redis 實現服務高可用的一大亮點,具體配置實現如下:
public class LettuceMain { public static void main(String[] args) throws Exception { //集群节点 List<RedisURI> uris = new ArrayList(); uris.add(RedisURI.builder().withSentinel("192.168.31.111", 26379).withSentinelMasterId("mymaster").withPassword("123456").build()); uris.add(RedisURI.builder().withSentinel("192.168.31.112", 26379).withSentinelMasterId("mymaster").withPassword("123456").build()); uris.add(RedisURI.builder().withSentinel("192.168.31.113", 26379).withSentinelMasterId("mymaster").withPassword("123456").build()); RedisClient client = RedisClient.create(); StatefulRedisMasterReplicaConnection<String, String> connection = MasterReplica.connect(client, StringCodec.UTF8, uris); //从节点读取数据 connection.setReadFrom(ReadFrom.REPLICA); RedisCommands<String, String> commands = connection.sync(); commands.set("name", "赵云"); System.out.println(commands.get("name")); connection.close(); client.shutdown(); } }
Cluster 叢集模式,是之後推出的一種高可用的架構模型,主要是採用分片方式來儲存數據,具體配置如下:
public class LettuceReactiveMain4 { public static void main(String[] args) throws Exception { Set<RedisURI> uris = new HashSet<>(); uris.add(RedisURI.builder().withHost("192.168.31.111").withPort(7000).withPassword("123456").build()); uris.add(RedisURI.builder().withHost("192.168.31.112").withPort(7000).withPassword("123456").build()); uris.add(RedisURI.builder().withHost("192.168.31.113").withPort(7000).withPassword("123456").build()); uris.add(RedisURI.builder().withHost("192.168.31.114").withPort(7000).withPassword("123456").build()); uris.add(RedisURI.builder().withHost("192.168.31.115").withPort(7000).withPassword("123456").build()); uris.add(RedisURI.builder().withHost("192.168.31.116").withPort(7001).withPassword("123456").build()); RedisClusterClient client = RedisClusterClient.create(uris); StatefulRedisClusterConnection<String, String> connection = client.connect(); RedisAdvancedClusterCommands<String, String> commands = connection.sync(); commands.set("name", "关羽"); System.out.println(commands.get("name")); //选择从节点,只读 NodeSelection<String, String> replicas = commands.replicas(); NodeSelectionCommands<String, String> nodeSelectionCommands = replicas.commands(); Executions<List<String>> keys = nodeSelectionCommands.keys("*"); keys.forEach(key -> System.out.println(key)); connection.close(); client.shutdown(); } }
以上是Redis之Lettuce怎麼使用的詳細內容。更多資訊請關注PHP中文網其他相關文章!