Redis Java 客户端工具 - Lettuce框架介绍

news2024/9/29 21:31:27

Redis Java 客户端 - Lettuce

今天学习下Redis Java客户端开源项目 - Lettuce,Lettuce支持同步、异步通信的方式 API调用,也支持响应式编程API,包括发布/订阅消息、高可用性服务部署架构。

开始之旅

Maven依赖

<dependency>
    <groupId>io.lettuce</groupId>
    <artifactId>lettuce-core</artifactId>
    <version>6.2.2.RELEASE</version>
</dependency>

第一个示例

public static void main(String[] args) {
        //连接第0个数据库
        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!");
        connection.close();
        redisClient.shutdown();
}

执行以上代码后,在redis服务端存储 key => Hello, Redis! 数据。

创建连接

Redis Standalone、Sentinel、Cluster 模式创建Redis连接需要指定详细的连接参数,如Redis数据库地址、密码、超时信息等,最终形成RedisURI对象,最终通过RedisClient.create方法创建连接对象。使用方式有以下几种

public static RedisClient getRedisClient(){
  			//直接指定连接信息
        RedisURI uri = RedisURI.create("redis://localhost:6379/0");
        return RedisClient.create(uri);
}
    public static RedisClient getRedisClient(){
        RedisURI uri = RedisURI.Builder.redis("localhost", 6379)
                .withDatabase(1)
                .withPassword(new StringBuffer("password"))
                .build();
        return RedisClient.create(uri);
    }
    public static RedisClient getRedisClient(){
        RedisURI uri = new RedisURI("localhost",6379, Duration.ofSeconds(5));
        return RedisClient.create(uri);
    }

URI 语法

Redis 部署方式不同,URI连接的语法也略有区别

  • 单点部署

    redis :// [[username :] password@] host [:port][/database]
              [?[timeout=timeout[d|h|m|s|ms|us|ns]]
    
  • 单点- SSL部署

    rediss :// [[username :] password@] host [: port][/database]
               [?[timeout=timeout[d|h|m|s|ms|us|ns]]
    
  • 单点- Unix Domain Sockets

    redis-socket :// [[username :] password@]path
                     [?[timeout=timeout[d|h|m|s|ms|us|ns]] [&database=database]]
    
  • Redis Sentinel

    redis-sentinel :// [[username :] password@] host1[:port1] [, host2[:port2]] [, hostN[:portN]] [/database]
                       [?[timeout=timeout[d|h|m|s|ms|us|ns]] [&sentinelMasterId=sentinelMasterId]
    

异常捕获

Redis服务发出异常、错误响应的情况下,客户端将收到 RedisException 异常信息(包含其子类),该异常是一种运行时异常。

# redis.conf 配置密码
requirepass andy
public static RedisClient getRedisClient(){
        RedisURI uri = RedisURI.Builder.redis("localhost", 6379)
                .withDatabase(1)
                .withSsl(false)
          			// 指定错误的密码
                .withPassword(new StringBuffer("password"))
                .build();
				 return RedisClient.create(uri);
 }

在这里插入图片描述

同步API

public static void main(String[] args) {
        RedisClient redisClient = getRedisClient();
        StatefulRedisConnection<String, String> connection = redisClient.connect();
        // RedisCommands 为连接生成的同步对象
  			RedisCommands<String, String> syncCommands = connection.sync();
				// 所有依赖 RedisCommands 的方法都同步返回 (即直接返回结果) 
        String value = syncCommands.get("key");
        System.out.println(value);
  			connection.close();
        redisClient.shutdown();
  }

同步API调用的方式非常简单,不做过多的介绍,提一下主要是为了区别后续的异步API

异步API

Lettuce 从4.x的版本开始提供的异步API,Lettuce的异步API是构建在Netty Reactor模型的基础之上,分为连接线程、工作线程。所有的通信过程都是异步处理。目的是为了更好地利用系统资源,而不浪费线程等待网络、磁盘IO的时间。使用异步API将提高系统的吞吐量,但是其异步处理模式会比同步调用复杂,也会给开发者带来一定的难度。

异步示例

Lettuce异步API上的每个命令都会创建一个RedisFuture<T>对象,该提供了取消、等待、订阅、监听相关的功能。RedisFuture是指向初始未知结果的指针,因为其值的计算可能尚未完成,RedisFuture<T>提供同步等待操作。接下来将通过多种方式异步获取结果

public class AsynchronousAPI {
    static RedisClient client = null;
    Executor sharedExecutor = Executors.newSingleThreadExecutor();

    @BeforeAll
    public static void initClient(){
        client = RedisClient.create("redis://andy@localhost:6379/0");
    }

    @AfterAll
    public static void shutdown(){
        client.shutdown();
    }

    @Test
    public void test1() throws Exception{
        RedisAsyncCommands<String, String> commands = client.connect().async();
        RedisFuture<String> future = commands.get("key");
        // 方式1
        System.out.println("同步等待结果: " + future.get());
        // 方式二
        System.out.println("同步超时等待结果: " + future.get(1,TimeUnit.SECONDS));
       }
  }
@Test
    public void test2() throws Exception{
        RedisAsyncCommands<String, String> commands = client.connect().async();
        RedisFuture<String> future = commands.get("key");
        // 方式三
        future.thenAccept(new Consumer<>() {
            @Override
            public void accept(String value) {
                System.out.println(value);
            }
        });
        // 方式四 lambda 写法
        future.thenAccept(System.out::println);
        //方式五
        future.thenAcceptAsync(new Consumer<String>() {
            @Override
            public void accept(String value) {
                System.out.println(value);
            }
        }, sharedExecutor);
    }

如果对JDK juc包比较熟悉的童鞋,相信对以上方式理解起来不会太困难。

@Test
    public void test3() throws Exception{
        RedisAsyncCommands<String, String> commands = client.connect().async();
        RedisFuture<String> future = commands.get("key");
        // 等待指定时间
        if(!future.await(1, TimeUnit.MINUTES)) {
            System.out.println("Could not complete within the timeout");
        }
        System.out.println(future.get());
    }

异步阻塞调用

如果需要对系统的某些部分执行批处理/添加并发,则异步阻塞是一个非常好的选择。批处理的一个示例可以是设置/检索多个值,并在处理的某一点之前等待结果。

@Test
    public void test(){
        RedisAsyncCommands<String, String> commands = client.connect().async();
        List<RedisFuture<String>> futures = new ArrayList<RedisFuture<String>>();
        for (int i = 0; i < 10; i++) {
            futures.add(commands.set("key-" + i, "value-" + i));
        }
        //批量等待结果
        LettuceFutures.awaitAll(1, TimeUnit.MINUTES, futures.toArray(new RedisFuture[futures.size()]));
        //获取返回 直接输出原始协议
        futures.stream().forEach(System.out::println);
    }

错误处理

错误处理是每一个应用程序不可或缺的组成部分 ,需要在设计之处就应该考虑,一种通用的错误处理机制。一般情况下,错误处理有一下几种方式:

  • 返回默认值
  • 使用备份机制
  • 重试

先来看一下Lettuce遇到错误时,如何使用默认值进行处理。测试步骤

  1. 首先在redis 设置一个map 结构数据
首先在redis 设置一个map 结构数据
127.0.0.1:6379> hset mapkey key1 value1 key2 value2
(integer) 2
  1. 使用 get string的方式获取数据,触发异常
  • 方式一

    @Test
        public void test4() throws Exception{
            RedisAsyncCommands<String, String> commands = client.connect().async();
            RedisFuture<String> future = commands.get("mapkey");
            future.handle(new BiFunction<String, Throwable, String>() {
                @Override
                public String apply(String value, Throwable throwable) {
                    if(throwable != null) {
                        return "default value";
                    }
                    return value;
                }
            }).thenAccept(new Consumer<String>() {
                @Override
                public void accept(String value) {
                    System.out.println("Got value: " + value);
                }
            });
        }
    
  • 方式二

    @Test
        public void test5() throws Exception{
            RedisAsyncCommands<String, String> commands = client.connect().async();
            RedisFuture<String> future = commands.get("mapkey");
            future.exceptionally(new Function<Throwable, String>() {
                @Override
                public String apply(Throwable throwable) {
                    if (throwable instanceof IllegalStateException) {
                        String message = "default value";
                        System.out.println(message);
                        return message;
                    }
    
                    return "other default value";
                }
            });
        }
    

发布订阅

Lettuce支持发布/订阅Redis独立和Redis群集连接。订阅通道建立后,将在消息/订阅/未订阅事件中通知连接。提供了同步、异步和反应式API,以与Redis发布/订阅功能交互。

同步订阅

public class SubAPI {

    static RedisClient client = null;

    @BeforeAll
    public static void initClient(){
        client = RedisClient.create("redis://andy@localhost:6379/0");
    }

    @AfterAll
    public static void shutdown(){
        client.shutdown();
    }

    @Test
    public void test0() throws Exception{
        StatefulRedisPubSubConnection<String, String> connection = client.connectPubSub();
        connection.addListener(new RedisPubSubAdapter<String, String>() {
            @Override
            public void message(String channel, String message) {
                System.out.println("sub message :" + message);
            }
        });

        RedisPubSubCommands<String, String> sync = connection.sync();
        sync.subscribe("channel");

        TimeUnit.MINUTES.sleep(1);
    }

}

在这里插入图片描述

异步订阅

从开发角度来看,异步订阅值需要替换相关的API即可

//RedisPubSubCommands<String, String> sync = connection.sync();
RedisPubSubAsyncCommands<String, String> async = connection.async();

响应式API

@Test
    public void test2() throws Exception{
        StatefulRedisPubSubConnection<String, String> connection = client.connectPubSub();
        RedisPubSubReactiveCommands<String, String> reactive = connection.reactive();
        reactive.subscribe("channel").subscribe();
        reactive.observeChannels().doOnNext(patternMessage -> {
            System.out.println(patternMessage.getMessage());
        }).subscribe();
        TimeUnit.MINUTES.sleep(1);
    }

Cluster 集群

Redis群集支持发布/订阅,但通常需要注意。用户发布消息(调用PUBLISH)在整个集群中广播,而不考虑对特定频道/模式的订阅。此行为允许连接到任意群集节点并注册订阅。客户端不需要连接到发布消息的节点。关于单机集群搭建 请参考

因此,订阅代码跟之前一样。

事务处理

Redis事务允许在一个步骤中执行一组命令。可以使用WATCH、UNWATCH、EXEC、MULTI和DISCARD命令控制事务。

同步事务

@Test
    public void test0(){
        StatefulRedisConnection<String, String> connect = client.connect();
        RedisCommands<String, String> sync = connect.sync();
        sync.multi();
        sync.set("hello", "world");
        sync.set("java", "world");
        TransactionResult exec = sync.exec();
        //返回多个命令的执行结果 两个OK
        exec.stream().forEach(t -> System.out.println(t));
    }

异步事务

@Test
    public void test1(){
        RedisAsyncCommands<String, String> async = client.connect().async();

        async.multi();
        async.set("key", "value");
        async.set("java", "world");

        RedisFuture<TransactionResult> future = async.exec();
        future.thenAccept(new Consumer<TransactionResult>() {
            @Override
            public void accept(TransactionResult objects) {
                objects.forEach(t -> System.out.println(t));
            }
        });
    }

响应式API

    @Test
    public void test2(){
        RedisReactiveCommands<String, String> reactive = client.connect().reactive();
        reactive.multi().subscribe(multiResponse -> {
            reactive.set("key", "1").subscribe();
            reactive.incr("key").subscribe();
            reactive.exec().subscribe();

        });
    }

集群事务

默认情况下,群集连接执行路由。这意味着,开发者无法真正确定命令在哪个主机上执行。如果事务中的多个KEY不会分配到同一个哈希槽 由于多点网络通信的问题,无法保证事务的一致性。这一点请特别注意。

集成Spring Boot

  • 添加依赖

集成Spring Boot需要在pom文件中增加如下依赖

<dependencies>
  <dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-redis</artifactId>
    <version>3.0.0</version>
  </dependency>
</dependencies>
  • 新增配置属性

    # 在 application.yml 中添加以下依赖
    spring:
      redis:
        port: 6379
        password: andy
        host: localhost
        timeToLive: 60
    
  • 自动注入

    
    @Configuration
    public class RedisConfiguration {
    
        @Value("${spring.redis.host}")
        private String url;
    
        @Value("${spring.redis.port}")
        private int port;
    
        @Value("${spring.redis.password}")
        private String password;
    
        @Bean
        public RedisStandaloneConfiguration redisStandaloneConfiguration() {
            RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(url, port);
            redisStandaloneConfiguration.setPassword(password);
            return redisStandaloneConfiguration;
        }
    
        @Bean
        public ClientOptions clientOptions() {
            return ClientOptions.builder()
                    .disconnectedBehavior(ClientOptions.DisconnectedBehavior.REJECT_COMMANDS)
                    .autoReconnect(true)
                    .build();
        }
    
    
        @Bean
        public RedisConnectionFactory connectionFactory(RedisStandaloneConfiguration redisStandaloneConfiguration) {
            LettuceClientConfiguration configuration = LettuceClientConfiguration.builder()
                    .clientOptions(clientOptions()).build();
    
            return new LettuceConnectionFactory(redisStandaloneConfiguration, configuration);
        }
    
        @Bean
        @ConditionalOnMissingBean(name = "redisTemplate")
        @Primary
        public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
            StringRedisTemplate template = new StringRedisTemplate();
            template.setConnectionFactory(redisConnectionFactory);
            return template;
        }
    }
    
  • 实例代码

@Autowired
    private StringRedisTemplate redisTemplate;

    @GetMapping("/getkey")
    public String getKey(){
        return redisTemplate.opsForValue().get("hello");
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/96484.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python实现SSH远程操作Linux(paramiko库)

参考&#xff1a;https://blog.csdn.net/qq_40558166/article/details/100172501 一、官网 https://www.paramiko.org/ 二、安装库 1.命令 pip install paramiko 或 pip install paramiko –i https://pypi.douban.com/simple/ 三、辅助软件(可忽略) 1.Xshell(执行命令) …

[附源码]Node.js计算机毕业设计黑格伯爵国际英语贵族学校官网Express

项目运行 环境配置&#xff1a; Node.js最新版 Vscode Mysql5.7 HBuilderXNavicat11Vue。 项目技术&#xff1a; Express框架 Node.js Vue 等等组成&#xff0c;B/S模式 Vscode管理前后端分离等等。 环境需要 1.运行环境&#xff1a;最好是Nodejs最新版&#xff0c;我…

Ubuntu 配置本地root登录

Ubuntu 配置本地root登录–18.04 给root用户设置密码 按照下图方式 命令&#xff1a;sudo passwd root 切换到root用户 使用 su 或者是 su root 切换到 root 修改配置文件 进入该目录&#xff1a; cd /usr/share/lightdm/lightdm.conf.d/ 查看文件 ls 使用gedit 编辑文…

Arduino UNO新手零基础入门学习博客汇总

写在开头 最近在上Arduino的课&#xff0c;可以说Arduino对新手来说非常友好了&#xff0c;因为相比于51和32&#xff0c;Arduino的库函数下载就好&#xff0c;不需要自己去写&#xff0c;就很方便 我的硬件设备 博客汇总 博客内容大多数以实际案例为主&#xff0c;基本都是…

机器学习100天(七):007 简单线性回归理论

机器学习100天,今天讲的是简单线性回归理论。 首先来看第一个问题:什么是线性回归?我们先引入一个例子。 假如我现在有一份数据,这份数据是一些地区人口和对应房价的信息。我们把这份数据展示在二维平面上。横坐标是人口,纵坐标是房价,红色的点就表示每个地区的实际人口…

论文精读:EfficientDet: Scalable and Efficient Object Detection

Abstract 首先&#xff0c;本篇论文提出了一种加权双向特征金字塔网络&#xff08;BiFPN&#xff09;&#xff0c;进行简单、快速的多尺度特征融合&#xff1b;其次&#xff0c;作者提出了一种复合尺度方法&#xff0c;同时统一调整所有主干、特征网络和box/类别预测网络的分辨…

kali linux的安装教程

kali linux的安装教程 在网上输入网址https://www.kali.org/get-kali/#kali-virtual-machines。 向下滚动鼠标滑轮选择如下图所示的图标进行安装iso镜像文件。 我们打开虚拟机&#xff0c;选择创建新的虚拟机 选择其中的自定义&#xff0c;随即点击下一步 点击下一步 点击下一步…

uboot通过bootargs传递内核中的模块传递参数

前言 bootargs是uboot向内核传递参数时使用的&#xff0c;本次我们要验证的是bootargs向内核启动后加载的模块传递的参数&#xff0c;真正的跨过山和大海。跟着我的脚步&#xff0c;来一次bootargs之旅。 这是一个综合性&#xff0c;系统性很强的实例验证&#xff0c;要做这个…

模式识别-期末复习题(问题集锦)

1.什么是模式&#xff1f;监督模式识别和非监督模式识别的典型过程分别是什么&#xff1f; 模式&#xff1a;指需要识别且可测量的对象的描述 监督模式识别&#xff1a;分析问题→原始特征提取→特征提取与选择→分类器设计 非监督模式识别&#xff1a;分析问题→原始特征提…

C++ Reference: Standard C++ Library reference: Containers: map: map: insert

C官网参考链接&#xff1a;https://cplusplus.com/reference/map/map/insert/ 公有成员函数 <map> std::map::insert C98 single element (1) pair<iterator,bool> insert (const value_type& val); with hint (2) iterator insert (iterator positio…

【密码学】MD5、UUID,加盐,JWT的理解与使用范例

文章目录MD5加密&#xff1a;1、MD5加密安全访问认证示例代码&#xff1a;2、UUID简介&#xff1a;使用&#xff1a;3、加盐原理&#xff1a;示例代码&#xff1a;4、jwt认知&#xff1a;JWT 结构&#xff1a;范例代码&#xff1a;MD5加密&#xff1a; 1、MD5加密 Message Di…

计算机毕业设计springboot+vue基本微信小程序的疫情防控平台系统

项目介绍 当今社会疫情防控平台是必不可少的,大家都在听从政府的号召在居家隔离,不管是在城市还是在乡镇、农村,这引起我的注目,设计一套社区疫情防控系统,疫情防控需要大家共同努力、团结对社区居民进行了新型冠状病毒肺炎防控知识普及和宣传教育。针对这一需求,本文设计并实现…

LVS详细介绍,这么讲不信你不明白负载均衡

Linux virtual server&#xff0c;是一个基于集群技术和Linux操作系统&#xff0c;目的是实现一个高性能、高可用的服务器&#xff0c;主要工作在网络层。 他采取IP负载均衡&#xff0c;也叫三层负载均衡&#xff08;因为工作在OSI模型的第三层--网络层&#xff09;&#xff0…

java计算机毕业设计springboot+vue旅游攻略平台

项目介绍 目前,我国旅游业正处于高度发展的状态。越来越多的人在假日里选择出游作为放松自己身心的手段。随着网络的普及和发展,人们开始习惯性地在做某事之前先在网络上浏览相关的内容。旅游网站能够帮助游客了解景点相关信息,推出相应的线路信息供游客们选择,并查看相应的旅…

计算机网络实验——路由器的配置静态与RIP配置

前言 由于几个资料和老师给的ppt说的都不清楚&#xff0c;自己也没想到什么很巧妙的归纳方法&#xff0c;写个总结记录一下。 想必静态路由配置的过程中唯一的难点就是ip route这个指令&#xff0c;其他的什么添加接口&#xff0c;设置ip啥的都是基础路由配置中的内容&#xf…

2023春招面试专题:JAVA基础高频面试

ArrayList和LinkedList有哪些区别 ArrayList扩容机制: ArrayList() 会使用长度为零的数组ArrayList(int initialCapacity) 会使用指定容量的数组public ArrayList(Collection<? extends E> c) 会使用 c 的大小作为数组容量add(Object o) 首次扩容为 10&#xff0c;再次…

3D激光里程计其一:ICP

这里写目录标题1. ICP 整体流程2. ICP 的数学表示3. 基于 SVD 的 ICP3.1 旋转部分求解3.2 平移部分求解4. 基于优化的 ICP5. ICP 系列汇总Reference: 深蓝学院-多传感器融合 1. ICP 整体流程 目的&#xff1a;有两个相似的刚体点云&#xff0c;它们之间没有做好配准&#xff…

Golang原理分析:闭包及for range延迟绑定问题原理及解决

1.Golang中的闭包 1.1.什么是闭包 当一个函数引用了环境的变量&#xff0c;被引用的变量与该函数同时存在组成的系统&#xff0c;被称为闭包。 闭包 环境的变量 函数。 以下JavaScript代码展示了一个基础的闭包&#xff1a; name是init函数中的内部变量displayName()是i…

机器学习 鸢尾花(Iris Flower)数据集分析

目录 一&#xff1a;加载数据 二&#xff1a;提取特征数据 三&#xff1a;提取标签数据 四&#xff1a;数据划分 一&#xff1a;加载数据 加载数据&#xff0c;查看数据特征 from sklearn.datasets import load_iris# 1 加载数据 鸢尾花load_iris iris_datasets load_iri…

数据校验及在数据校验的情况下增加headers拿回数据

什么是数据校验&#xff1a; 当你向一个数据端口请求数据时&#xff0c;如果这个数据端口没有设置&#xff1a;Access-Control-Allow-Origin&#xff1a;*&#xff0c;那就是存在跨域限制了&#xff0c;你是拿不回来数据的。图示&#xff1a; 但是有些数据端口是设置了 Access…