Lettuce客户端连接 Redis 服务器的三种方式你真的懂吗?
mhr18 2024-10-26 10:49 29 浏览 0 评论
Lettuce客户端连接 Redis 服务器的三种方式你真的懂吗?RedisURI 揭秘URI 语法RedisURI 剖析同步方式异步方式Reactive 模式总结Lettuce系列文章
连接 Redis 的方式居然不止一种?你似乎从来没有关注过这个问题,为什么需要这么多种方式呢?也许你真的不懂,或者你知道有几种方式,但是到底是怎么实现的呢 ?
无论 Redis 服务器是,单机版本,哨兵模式还是集群模式,想要和 Redis 服务器建立连接,首选需要指定的 Redis服务器的地址等信息,Lettuce 对与Redis Standalone,Sentinel或Cluster的连接进行了规范连接详细信息。 统一的形式是RedisURI。
RedisURI 揭秘
URI,统一资源标识符(Uniform Resource Identifier,URI)是一个用于标识某一互联网资源名称的字符串。 该种标识允许用户对任何(包括本地和互联网的资源通过特定的协议进行交互操作。URI由包括确定语法和相关协议的方案所定义。顾名思义,RedisURI 是访问 Redis 资源的标识,Lettuce对 URI 进行了封装和抽象,以便能够快速,高效使用。可以在RedisURI中提供数据库,密码和超时信息。 可以通过以下三种方式创建RedisURI:
- 使用一个 URI:RedisURI.create("redis://localhost/");
- 使用 BuilderRedisURI.Builder.redis("localhost", 6379).auth("password").database(1).build();
- 直接给 RedisURI实例进行赋值new RedisURI("localhost", 6379, 60, TimeUnit.SECONDS);
以上三种方式都可以创建要给 RedisURI 实例,三种方式都是为了给属性字段赋值。我们看看关键的属性,核心字段就三个,host,port 和 授权信息(password),RedisURI支持带有明文,SSL,TLS和unix域套接字连接的Redis Standalone,Redis Sentinel和Redis Cluster。以下 Redis 连接的语法来自于官网。
URI 语法
Redis Standalone
redis / [: password@] host [: port] [/ database][? [timeout=timeout[d|h|m|s|ms|us|ns]] [&database=database]]
Redis Standalone (SSL)
rediss / [: password@] host [: port] [/ database][? [timeout=timeout[d|h|m|s|ms|us|ns]] [&database=database]]
Redis Standalone (Unix Domain Sockets)
redis-socket / path [?[timeout=timeout[d|h|m|s|ms|us|ns]][&database=database]]
Redis Sentinel
redis-sentinel / [: password@] host1[: port1] [, host2[: port2]] [, hostN[: portN]] [/ database][?[timeout=timeout[d|h|m|s|ms|us|ns]] [&sentinelMasterId=sentinelMasterId] [&database=database]]
Schemes
- redis Redis Standalone
- rediss Redis Standalone SSL
- redis-socket Redis Standalone Unix Domain Socket
- redis-sentinel Redis Sentinel
Timeout units
- d Days
- h Hours
- m Minutes
- s Seconds
- ms Milliseconds
- us Microseconds
- ns Nanoseconds
RedisURI 剖析
RedisURI 类的主要职责:Redis URI。 包含Redis / Sentinel连接的连接详细信息。 我们可以在RedisURI中提供数据库,客户端名称,密码和超时。 您可以通过上述的三种方式创建RedisURI,类图如下。
顺便我们探一探 RedisURI 的源码,主要结合源码和文档进行分析,相关地址如下:
- 源码文件地址:https://github.com/paodingjiejiagou/lettuce-core/blob/master/src/main/java/io/lettuce/core/RedisURI.java
- JavaDoc地址 : https://lettuce.io/core/release/api/index.html
我们先看看 RedisURI 实现的接口ConnectionPoint,连接点,节点信息的封装,我们可以通过安全或者不安全的方式到达连接点,之后再访问 Redis 服务器的数据库。
public interface ConnectionPoint {
/**
* Returns the host that should represent the hostname or IPv4/IPv6 literal.
*
* @return the hostname/IP address
*/
String getHost();
/**
* Get the current port number.
*
* @return the port number
*/
int getPort();
/**
* Get the socket path.
*
* @return path to a Unix Domain Socket
*/
String getSocket();
}
这种多个参数和模式情况下,创建对象实例最适合 Builder 模式,RedisURI 也不例外,代码片段如下,大家可以复习下 Builder 模式,完整代码地址为:https://github.com/paodingjiejiagou/lettuce-core/blob/master/src/main/java/io/lettuce/core/RedisURI.java
public static class Builder {
private String host;
private String socket;
private String sentinelMasterId;
private int port = DEFAULT_REDIS_PORT;
private int database;
private String clientName;
private String username;
private char[] password;
private char[] sentinelPassword;
private boolean ssl = false;
private boolean verifyPeer = true;
private boolean startTls = false;
private Duration timeout = DEFAULT_TIMEOUT_DURATION;
private final List<RedisURI> sentinels = new ArrayList<>();
private Builder() {
}
/**
* Set Redis host. Creates a new builder.
*
* @param host the host name
* @return new builder with Redis host/port.
*/
public static Builder redis(String host) {
return redis(host, DEFAULT_REDIS_PORT);
}
/**
* Set Redis host and port. Creates a new builder
*
* @param host the host name
* @param port the port
* @return new builder with Redis host/port.
*/
public static Builder redis(String host, int port) {
LettuceAssert.notEmpty(host, "Host must not be empty");
LettuceAssert.isTrue(isValidPort(port), () -> String.format("Port out of range: %s", port));
Builder builder = RedisURI.builder();
return builder.withHost(host).withPort(port);
}
/**
* Configures authentication.
*
* @param password the password
* @return the builder
* @since 4.4
*/
public Builder withPassword(char[] password) {
LettuceAssert.notNull(password, "Password must not be null");
this.password = Arrays.copyOf(password, password.length);
return this;
}
/**
* Configures a timeout.
*
* @param timeout must not be {@literal null} or negative.
* @return the builder
*/
public Builder withTimeout(Duration timeout) {
LettuceAssert.notNull(timeout, "Timeout must not be null");
LettuceAssert.notNull(!timeout.isNegative(), "Timeout must be greater or equal 0");
this.timeout = timeout;
return this;
}
/**
* @return the RedisURI.
*/
public RedisURI build() {
if (sentinels.isEmpty() && LettuceStrings.isEmpty(host) && LettuceStrings.isEmpty(socket)) {
throw new IllegalStateException(
"Cannot build a RedisURI. One of the following must be provided Host, Socket or Sentinel");
}
RedisURI redisURI = new RedisURI();
redisURI.setHost(host);
redisURI.setPort(port);
if (username != null) {
redisURI.setUsername(username);
}
if (password != null) {
redisURI.setPassword(password);
}
redisURI.setDatabase(database);
redisURI.setClientName(clientName);
redisURI.setSentinelMasterId(sentinelMasterId);
for (RedisURI sentinel : sentinels) {
sentinel.setTimeout(timeout);
redisURI.getSentinels().add(sentinel);
}
redisURI.setSocket(socket);
redisURI.setSsl(ssl);
redisURI.setStartTls(startTls);
redisURI.setVerifyPeer(verifyPeer);
redisURI.setTimeout(timeout);
return redisURI;
}
}
分析源码的目的除了欣赏,最主要的要学以致用,在后续的研发过程中,类似的功能,我们可以借鉴 RedisURI 的抽象思路和 Builder 设计模式,书写专业,优雅的代码。
完成了对连接信息的抽象和封装之后,我们该关注连接方式了。
同步方式
同步方式是我们最常使用的方式,也是最容易理解的方式,我们在文章Redis Java 客户端 Lettuce(生菜)食用指南代码示例就是同步方式。再温习下代码示例:
public class LettuceStarted {
public static void main(String[] args) {
RedisClient redisClient = RedisClient.create("redis://localhost:6379/0");
StatefulRedisConnection<String, String> connection = redisClient.connect();
RedisCommands<String, String> syncCommands = connection.sync();
syncCommands.set("key", "Hello, Redis!");
String value = syncCommands.get("key");
System.out.println("value = " + value);
connection.close();
redisClient.shutdown();
}
}
其中最关键的代码connection.sync(),指明了我们将使用同步方式连接的 Redis 服务器,进行相关数据库的操作。
异步方式
为什么我们需要异步方式,显然是因为当前调用的结果对我们来说,重要而不紧急。异步方法使我们可以利用更好的系统资源,而不是浪费线程等待网络或磁盘I / O。 可以充分利用线程来执行其他工作。 lettuce通过在Netty之上构建客户端来促进异步,netty是一个多线程,事件驱动的I / O框架。 所有通信都是异步处理的。 一旦基础设施能够同时处理命令,便可以方便地利用异步性。 然而将阻塞和同步的工作软件转换为并发处理系统要困难得多。一般来说,处理异步响应结果有三种方式。
- 主动轮询,查询处理结果
- 监听异步通知,进行处理
- 采用异步回调模式,异步处理完成回调指定的方法或者指令
我们看看 Lettuce 是如何处理的呢 ?异步API上的每个命令调用都会创建一个RedisFuture ,可以将其取消,等待和订阅(侦听器)。 CompleteableFuture <T>或RedisFuture <T>是指向结果的指针,该结果最初未知,因为其值的计算尚未完成。 RedisFuture <T>提供用于同步和和链式的操作。
庖丁先解剖下CompleteableFuture <T> ,这个是JDK 1.8引入异步处理类,虽然说 Future 接口在 Java 1.5 就引入了,但是由于其过于简单,不能满足异步场景的要求,很多开源组件实现了自己的异步处理类,如 Google 的 Guava库。CompleteableFuture的类图如下:
代码示例如下:
package com.lerith.anatomy.lettuce.started;
import io.lettuce.core.RedisClient;
import io.lettuce.core.RedisFuture;
import io.lettuce.core.api.StatefulRedisConnection;
import io.lettuce.core.api.async.RedisAsyncCommands;
import io.lettuce.core.api.sync.RedisCommands;
import java.util.concurrent.ExecutionException;
/**
*
*/
public class AsynchronousConnected {
public static void main(String[] args) {
RedisClient redisClient = RedisClient.create("redis://localhost:6379/0");
StatefulRedisConnection<String, String> connection = redisClient.connect();
RedisAsyncCommands<String, String> asyncCommands = connection.async();
RedisFuture<String> result = asyncCommands.set("key", "Hello, Redis!");
RedisFuture<String> value = asyncCommands.get("key");
try {
System.out.println("value = " + value.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
connection.close();
redisClient.shutdown();
}
}
其中最关键的代码connection.async(),指明了我们将使用异步方式连接 Redis 服务器,进行相关数据库的操作。
Reactive 模式
异步和响应式方法使您可以利用更好的系统资源,而不是浪费线程等待网络或磁盘I / O。 可以充分利用线程来执行其他工作。
存在广泛的技术来促进这种编程风格,范围从非常有限且不易使用的java.util.concurrent.Future到完整的库和运行时(如Akka)。 Project Reactor具有非常丰富的运算符组合,可以组成异步工作流,它对其他框架没有进一步的依赖关系,并且支持非常成熟的Reactive Streams模型。Reactive Streams是为无阻塞背压的异步流处理提供标准的一项举措。 这包括针对运行时环境(JVM和JavaScript)以及网络协议的工作。它是一种超越编程语言的编程范式。
Lettuce 使用 Project Reactor (https://projectreactor.io/) 来实现响应式模式 ,这个模式是个比较大的话题,后面庖丁专门写一篇文章来阐述它。下图是 Project Reactor项目的主页。可以看出它的使命是创建高效的响应式系统。
代码示例如下:
package com.lerith.anatomy.lettuce.started;
import io.lettuce.core.RedisClient;
import io.lettuce.core.api.StatefulRedisConnection;
import io.lettuce.core.api.reactive.RedisStringReactiveCommands;
import reactor.core.publisher.Mono;
public class ReactiveConnected {
public static void main(String[] args) {
RedisClient redisClient = RedisClient.create("redis://localhost:6379/0");
StatefulRedisConnection<String, String> connection = redisClient.connect();
RedisStringReactiveCommands<String, String> reactive = connection.reactive();
Mono<String> set = reactive.set("key", "value");
Mono<String> get = reactive.get("key");
set.subscribe();
System.out.println("get.block() = " + get.block());
}
}
其中最关键的代码connection.reactive(),指明了我们将使用异步方式连接 Redis 服务器,进行相关数据库的操作。
总结
本文主要阐述了 Lettuce 连接 Redis 的三种方式,也是三种不同的编程风格,适用于不同的场景。示例代码:https://github.com/paodingjiejiagou/anatomy-lettuce 大家可以 clone 下来自己运行。三种连接方式主要定义在StatefulRedisConnection.java 接口中。代码片段如下:
/*
---
*/
package io.lettuce.core.api;
import io.lettuce.core.api.async.RedisAsyncCommands;
import io.lettuce.core.api.reactive.RedisReactiveCommands;
import io.lettuce.core.api.sync.RedisCommands;
import io.lettuce.core.protocol.ConnectionWatchdog;
/**
* A thread-safe connection to a redis server. Multiple threads may share one {@link StatefulRedisConnection}.
*
* A {@link ConnectionWatchdog} monitors each connection and reconnects automatically until {@link #close} is called. All
* pending commands will be (re)sent after successful reconnection.
*
* @param <K> Key type.
* @param <V> Value type.
* @author Mark Paluch
* @since 4.0
*/
public interface StatefulRedisConnection<K, V> extends StatefulConnection<K, V> {
/**
*
* @return true, if the connection is within a transaction.
*/
boolean isMulti();
/**
* Returns the {@link RedisCommands} API for the current connection. Does not create a new connection.
*
* @return the synchronous API for the underlying connection.
*/
RedisCommands<K, V> sync();
/**
* Returns the {@link RedisAsyncCommands} API for the current connection. Does not create a new connection.
*
* @return the asynchronous API for the underlying connection.
*/
RedisAsyncCommands<K, V> async();
/**
* Returns the {@link RedisReactiveCommands} API for the current connection. Does not create a new connection.
*
* @return the reactive API for the underlying connection.
*/
RedisReactiveCommands<K, V> reactive();
}
仔细观察StatefulRedisConnection接口定义的三种连接方式,发现最明显的就是命令操作方式各不相同。lettuce 通过对处理命令的抽象和封装来实现和应对不同的连接方式。下图是三种不同处理命令的类图,比较复杂,但 Redis 支持的操作命令,确实挺多,后续庖丁会继续解剖。
- RedisCommands 接口类图
- RedisAsyncCommands接口类图
- RedisReactiveCommands接口类图
Lettuce系列文章
- 【Lettuce 架构解剖&源码精读】https://www.toutiao.com/i6817805765684757003/
- 【Redis Java 客户端 Lettuce(生菜)食用指南】https://www.toutiao.com/i6818921648201138699/
相关推荐
- 如何检查 Linux 服务器是物理服务器还是虚拟服务器?
-
在企业级运维、故障排查和性能调优过程中,准确了解服务器的运行环境至关重要。无论是物理机还是虚拟机,都存在各自的优势与限制。在很多场景下,尤其是当你继承一台服务器而不清楚底层硬件细节时,如何快速辨识它是...
- 第四节 Windows 系统 Docker 安装全指南
-
一、Docker在Windows上的运行原理(一)架构限制说明Docker本质上依赖Linux内核特性(如Namespaces、Cgroups等),因此在Windows系统上无法直...
- C++ std:shared_ptr自定义allocator引入内存池
-
当C++项目里做了大量的动态内存分配与释放,可能会导致内存碎片,使系统性能降低。当动态内存分配的开销变得不容忽视时,一种解决办法是一次从操作系统分配一块大的静态内存作为内存池进行手动管理,堆对象内存分...
- Activiti 8.0.0 发布,业务流程管理与工作流系统
-
Activiti8.0.0现已发布。Activiti是一个业务流程管理(BPM)和工作流系统,适用于开发人员和系统管理员。其核心是超快速、稳定的BPMN2流程引擎。Activiti可以...
- MyBatis动态SQL的5种高级玩法,90%的人只用过3种
-
MyBatis动态SQL在日常开发中频繁使用,但大多数开发者仅掌握基础标签。本文将介绍五种高阶技巧,助你解锁更灵活的SQL控制能力。一、智能修剪(Trim标签)应用场景:动态处理字段更新,替代<...
- Springboot数据访问(整合Mybatis Plus)
-
Springboot整合MybatisPlus1、创建数据表2、引入maven依赖mybatis-plus-boot-starter主要引入这个依赖,其他相关的依赖在这里就不写了。3、项目结构目录h...
- 盘点金州勇士在奥克兰13年的13大球星 满满的全是...
-
见证了两个月前勇士与猛龙那个史诗般的系列赛后,甲骨文球馆正式成为了历史。那个大大的红色标志被一个字母一个字母地移除,在周四,一切都成为了过去式。然而这座,别名为“Roaracle”(译注:Roar怒吼...
- Mybatis入门看这一篇就够了(mybatis快速入门)
-
什么是MyBatisMyBatis本是apache的一个开源项目iBatis,2010年这个项目由apachesoftwarefoundation迁移到了googlecode,并且改名为M...
- Springboot数据访问(整合druid数据源)
-
Springboot整合druid数据源基本概念SpringBoot默认的数据源是:2.0之前:org.apache.tomcat.jdbc.pool.DataSource2.0及之后:com.z...
- Linux 中的 "/etc/profile.d" 目录有什么作用 ?
-
什么是/etc/profile.d/目录?/etc/profile.d/目录是Linux系统不可或缺的一部分保留配置脚本。它与/etc/profile文件相关联,这是一个启动脚本,该脚...
- 企业数据库安全管理规范(企业数据库安全管理规范最新版)
-
1.目的为规范数据库系统安全使用活动,降低因使用不当而带来的安全风险,保障数据库系统及相关应用系统的安全,特制定本数据库安全管理规范。2.适用范围本规范中所定义的数据管理内容,特指存放在信息系统数据库...
- Oracle 伪列!这些隐藏用法你都知道吗?
-
在Oracle数据库中,有几位特殊的“成员”——伪列,它们虽然不是表中真实存在的物理列,但却能在数据查询、处理过程中发挥出意想不到的强大作用。今天给大家分享Oracle伪列的使用技巧,无论...
- Oracle 高效处理数据的隐藏神器:临时表妙用
-
各位数据库搬砖人,在Oracle的代码世界里闯荡,处理复杂业务时,是不是总被数据“搅得头大”?今天给大家安利一个超实用的隐藏神器——临时表!当你需要临时存储中间计算结果,又不想污染正式数据表...
- Oracle 数据库查询:多表查询(oracle多表关联查询)
-
一、多表查询基础1.JOIN操作-INNERJOIN:返回两个表中满足连接条件的匹配行,不保留未匹配数据。SELECTa.emp_id,b.dept_nameFROMempl...
- 一文掌握怎么利用Shell+Python实现多数据源的异地备份程序
-
简介:在信息化时代,数据安全和业务连续性已成为企业和个人用户关注的焦点。无论是网站数据、数据库、日志文件,还是用户上传的文档、图片等,数据一旦丢失,损失难以估量。尤其是当数据分布在多个不同的目录、服务...
你 发表评论:
欢迎- 一周热门
- 最近发表
-
- 如何检查 Linux 服务器是物理服务器还是虚拟服务器?
- 第四节 Windows 系统 Docker 安装全指南
- C++ std:shared_ptr自定义allocator引入内存池
- Activiti 8.0.0 发布,业务流程管理与工作流系统
- MyBatis动态SQL的5种高级玩法,90%的人只用过3种
- Springboot数据访问(整合Mybatis Plus)
- 盘点金州勇士在奥克兰13年的13大球星 满满的全是...
- Mybatis入门看这一篇就够了(mybatis快速入门)
- Springboot数据访问(整合druid数据源)
- Linux 中的 "/etc/profile.d" 目录有什么作用 ?
- 标签列表
-
- oracle位图索引 (74)
- oracle基目录 (50)
- oracle批量插入数据 (65)
- oracle事务隔离级别 (53)
- oracle主从同步 (55)
- oracle 乐观锁 (51)
- redis 命令 (78)
- php redis (88)
- redis 存储 (66)
- redis 锁 (69)
- 启动 redis (66)
- redis 时间 (56)
- redis 删除 (67)
- redis内存 (57)
- redis并发 (52)
- redis 主从 (69)
- redis 订阅 (51)
- redis 登录 (54)
- redis 面试 (58)
- 阿里 redis (59)
- redis 搭建 (53)
- redis的缓存 (55)
- lua redis (58)
- redis 连接池 (61)
- redis 限流 (51)