【Redis】Redis 客户端开发与 Java 集成:RESP协议解析与实战操作

news2024/11/15 10:50:00

目录

    • 客⼾端
      • Redis Java使⽤ 样例列表
        • 引⼊依赖
        • 配置端⼝转发
        • 连接 Redis Server
        • 基础操作
        • 字符串操作
        • 列表操作
        • 哈希表操作
        • 集合操作
        • 有序集合操作
        • 访问集群
      • Redis Java 集成到 Spring Boot
        • 使⽤ Spring Boot 连接 Redis 单机
          • 创建项⽬
          • 配置 redis 服务地址
          • 创建 Controller
          • 使⽤ String
          • 使⽤ List
          • 使⽤ Hash
          • 使⽤ Set
          • 使⽤ ZSet
          • 执⾏结果
        • 使⽤ Spring Boot 连接 Redis 集群
          • 配置 redis 集群地址
        • 小结


客⼾端

要了解 Redis 服务端和客⼾端的通信协议,以及 Java 语⾔的 Redis 客⼾端使⽤⽅法。

为什么我们能编写出一个自定义的 redis 客户端?

网络通信中会用到很多协议:应用层、传输层、网络层、数据链路层、物理层。除了应用层外,其他四层的协议是固定好的,是在系统内核或者驱动程序中实现的,我们只能选择不能修改。

而对于应用层,虽然业界有很多成熟的应用层协议,如 HTTP 等等。但是此处更多的时候,都会“自定义”应用层协议,Redis 此处的应用层协议就是自定义协议(当然,传输层还是基于 TCP)。

  1. 客户端按照这里的应用层协议,发送请求
  2. 服务器按照这个协议进行解析
  3. 再按照这个协议构造响应
  4. 客户端再解析这个响应

之所以能够通信成功,是因为开发客户端的人和开发服务器的人都清楚协议的细节。我们第三方想要开发 redis 客户端,也就需要知道 Redis 的应用层协议,而这个官方开放出来的,RESP portocal spec,Redis serialization protocal (RESP) specification,RESP 是 Redis 自定义的应用层协议的名字,而不是 response 的这个 resp。

RESP 协议

Redis 序列化协议(RESP)是客户端实现的一种用于与Redis服务器通信的协议。虽然该协议专门设计用于 Redis,但您也可以将其用于其他客户端-服务器软件项目。

优点:

  1. 简单好实现
  2. 快速进行解析
  3. 肉眼可读

RESP可以序列化不同的数据类型,包括整数、字符串和数组。它还具有特定于错误的类型。客户端将请求发送到 Redis 服务器,请求是一个字符串数组。数组的内容是服务器应执行的命令及其参数。服务器的响应类型是特定于命令的。

RESP 是二进制安全的,并使用前缀长度来传输批量数据,因此不需要处理从一个进程传输到另一个进程的批量数据。

客户端通过创建到 Redis 服务器端口的 TCP 连接来连接到 Redis 服务器(默认端口为6379)。虽然 RESP 在技术上与 TCP 无关,但在 Redis 的上下文中,该协议仅与 TCP 连接(或类似流式连接的 Unix 套接字)一起使用。

即传输层还是基于 TCP,但和 TCP 没有那么强耦合

Redis 服务器接受由不同参数组成的命令请求。服务器处理命令并将响应发送回客户端。

即请求和响应之间的通信模型是一问一答(一个请求一个响应)的形式

RESP 本质上是一种支持多种数据类型的序列化协议。在 RESP 中,数据的第一个字节确定其类型。在 RESP 序列化的有效负载中,第一个字节始终标识其类型。随后的字节构成类型的内容。

客户端给服务器发送的是 Redis 命令(bulk string、数组,这两种形式发送的)
服务器用 RESP 类型进行响应。响应的类型由命令的实现和可能的客户端协议版本确定

bulk string 可以传输二进制数据,simple string 只能传输文本

Redis 客户端服务器,要做的工作是:

  1. 按照对应格式,构造出字符串,往 socket 中写
  2. 从 socket 中读取字符串,按照对应格式解析

写代码就一定要按照协议解析/构造字符串吗?

不用,因为协议公开已久,有很多现成的库可以使用

Redis Java使⽤ 样例列表

引⼊依赖

Java 操作 redis 的客⼾端有很多. 其中最知名的是 jedis(这里提供的 api 和 Redis 命令是高度一致的).

创建 maven 项⽬, 把 jedis 的依赖拷⻉到 pom.xml 中.

<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>4.4.2</version>
</dependency>

版本选择⼀个相对较新的版本即可

配置端⼝转发

Redis 服务器安装在云服务器上, ⽽我们编写的代码则是在本地主机.

要想让本地主机能访问 redis, 需要把 redis 的端⼝通过云服务器后台⻚⾯的 “防⽕墙” / “安全组” 放开端⼝到公⽹上. 但是这个操作⾮常危险(⿊客会顺着 redis 端⼝进来).

因此我们可以使⽤端⼝转发的⽅式, 直接把服务器的 redis 端⼝映射到本地.

也可以通过直接让 Java 程序在 Linux 上运行的方式,即把代码打包成可执行的 jar 包,拷贝到 Linux 服务器上执行,但这种方式比较繁琐,因此也不常用

在 Xshell 中, 进⾏如下配置:

  1. 右键云服务器的会话, 选择属性.
  2. 找到隧道 -> 配置转移规则.

SSH 功能很强大,比较重要的特性就是能支持端口转发,相当于通过 SSH 的 22 端口来传递其他端口的数据。

比如我们本身是需要通过 Windows 主机,访问云服务器的 6379 端口,于是就构造一个特殊的 SSH 数据报,把要访问 Redis 的请求放大 SSH 数据报里,这个数据报就会通过 22 端口发送给服务器,服务器的 SSH 服务器程序就能解析出上述的数据报,然后把数据报交给 6379 端口的程序。

想在主机访问云服务器的 6379 端口,我们本地通过 SSH 弄一个端口,比如 8888,这是 SSH 程序监听的端口,然后把云服务器的 6379 映射到本地的 8888,此时客户端的程序访问 127.0.0.1:8888 就相当于访问 Linux 服务器的 6379

  1. 使⽤该会话连接服务器

在 Windows 本地计算机上可能就没有 Xshell 这些方法了,直接用一条命令或许就可以了

在本地的命令提示符窗口(通过 cmd 进入)后输入下面的命令

ssh -L 本地端口:localhost:6379 用户名@服务器IP地址
ssh -L 9999:localhost:6379 root@123.207.247.132
C:\Users\幽琴健>ssh -L 9999:localhost:6379 root@123.207.247.132
The authenticity of host '123.207.247.132 (123.207.247.132)' can't be established.
ED25519 key fingerprint is SHA256:lhr4h6nFEGM1h9sB14BWdZ/RoEUOInm2Ms9yuWJ/pg4.
This key is not known by any other names
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '123.207.247.132' (ED25519) to the list of known hosts.
root@123.207.247.132's password:
Last failed login: Sat Apr  6 00:20:41 CST 2024 from 170.64.185.216 on ssh:notty
There were 269 failed login attempts since the last successful login.
Last login: Fri Apr  5 12:07:32 2024 from 127.0.0.1
[root@VM-8-16-centos ~]#

我这里的本地端口选择了 9999

连接是否生效,可以通过查看本地端口监听情况

在另一个窗口输入 netstat -ano | findstr 9999

C:\Users\幽琴健>netstat -ano | findstr 9999
TCP    127.0.0.1:9999         0.0.0.0:0              LISTENING       12312
TCP    [::1]:9999             [::]:0                 LISTENING       12312

C:\Users\幽琴健>

此时成功建立 SSH隧道 的窗口不要关闭,创建了一个从本地端口 9999 到远程服务器上的 Redis 服务端口 6379 的 SSH隧道。,在 IDEA 上执行代码发现就成功了

public class RedisDemo {
public static void main(String[] args) {
  // 连接到 Redis 服务器上
  JedisPool jedisPool=new JedisPool("tcp://127.0.0.1:9999");

  // 从 Redis 连接池中取出一个连接,用完后记得释放,这里的释放不一定是关闭 TCP,而是放回到池子里
  try(Jedis jedis=jedisPool.getResource()){
      // Redis 的各种命令都对应到 jedis 对象的各种方法
      String pong=jedis.ping();
      System.out.println(pong);
  }
}
}

此时, 访问本地的 8888, 就相当于访问对应服务器的 6379

注意, Xshell 和服务器必须处在连接状态, 这样的映射才是有效的.

连接 Redis Server
  • 使⽤ JedisPool 描述 Redis 服务器的位置. 使⽤ url 来表⽰.
  • 使⽤ getResource 和服务器建⽴连接.
  • 连接使⽤完毕需要 close 关闭. 也可以直接使⽤ try ⾃动关闭.
  • 通过 ping ⽅法可以检测连接是否正确建⽴.
public static void main(String[] args) {
    // 连接到 Redis 服务器上,这里只是开发阶段这么写,后续要部署时就要改成云服务器的实际情况
    JedisPool jedisPool=new JedisPool("tcp://127.0.0.1:9999");

    // 从 Redis 连接池中取出一个连接,用完后记得释放,这里的释放不一定是关闭 TCP,而是放回到池子里
    try(Jedis jedis=jedisPool.getResource()){
        // Redis 的各种命令都对应到 jedis 对象的各种方法
        String pong=jedis.ping();
        System.out.println(pong);
    }
}

执⾏结果

PONG

注意:

这个报错没有关系, 不影响使⽤, 忽略即可.

此时程序能跑通除了配置 SSH 端口映射之外,还跟最开始安装 Redis 服务器时,要配置绑定的 ip 以及关闭保护模式有关

bind 0.0.0.0

如果这里还是 127.0.0.1,那么只能本机和本机访问,不能跨主机访问

protected-mode no

如果这里还是 yes,开启保护模式,也不能跨主机访问

基础操作
  1. get / set
  2. exists
  3. del
  4. keys
  5. expire / ttl
  6. type

set 和 get

  • key 不存在时, 得到的 value 为 null
public static void main(String[] args) {
    JedisPool jedisPool = new JedisPool("tcp://127.0.0.1:8888");
    try (Jedis jedis = jedisPool.getResource()) {
        // testPing(jedis);
        testGetSet(jedis);
        jedis.flushDB();
    }
}

private static void testGetSet(Jedis jedis) {
    jedis.set("key1", "value1");
    jedis.set("key2", "value2");

    String value1 = jedis.get("key1");
    System.out.println(value1);

    String value2 = jedis.get("key2");
    System.out.println(value2);

    String valueNull = jedis.get("noSuchKey");
    System.out.println(valueNull);
}

执⾏结果

value1
value2
null

exists 和 del

  • del 可以删除多个 key, 以变⻓参数列表的⽅式体现. 返回值是实际删除的 key 的个数.
private static void testExistsAndDel(Jedis jedis) {
    jedis.set("key1", "value");
    jedis.set("key2", "value");
    jedis.set("key3", "value");
    
    boolean ret = jedis.exists("key1");
    System.out.println(ret);

    long n = jedis.del("key1");
    System.out.println(n);

    ret = jedis.exists("key1");
    System.out.println(ret);

    n = jedis.del("key2", "key3");
    System.out.println(n);
    ret = jedis.exists("key2");
    System.out.println(ret);
}

执行结果

true
1
false
2
false

keys

private static void testKeys(Jedis jedis) {
    jedis.set("key1", "value1");
    jedis.set("key2", "value2");
    jedis.set("key3", "value3");
    jedis.set("myKey", "value4");

    Set<String> keys = jedis.keys("*");
    System.out.println(keys);

    keys = jedis.keys("key?");
    System.out.println(keys);
}

这里用 Set 接收是因为,Redis 的 key 是不能重复的,而且也不在意顺序

执行结果

[key1, key2, key3, myKey]
[key1, key2, key3]

expire 和 ttl

private static void testExpireAndTTL(Jedis jedis) {
    jedis.setex("key", 60, "value");
    long ttl = jedis.ttl("key");
    System.out.println(ttl);
}

执行结果

60

type

private static void testType(Jedis jedis) {
    jedis.set("key1", "value");
    System.out.println(jedis.type("key1"));

    jedis.lpush("key2", "a", "b", "c");
    System.out.println(jedis.type("key2"));

    jedis.hset("key3", "name", "zhangsan");
    System.out.println(jedis.type("key3"));

    jedis.sadd("key4", "111", "222", "333");
    System.out.println(jedis.type("key4"));

    jedis.zadd("key5", 1, "aaa");
    System.out.println(jedis.type("key5"));
}

执行结果

string
list
hash
set
zset
字符串操作
  1. get / set
  2. mget / mset
  3. getrange / setrange
  4. append
  5. indr / decr

mgetmset

private static void testMSetAndMGet(Jedis jedis) {
    jedis.mset("key1", "value1", "key2", "value2", "key3", "value3");
    List<String> values = jedis.mget("key1", "key2", "key3");
    System.out.println(values);
}

执⾏结果

[value1, value2, value3]

如果是 jedis.mget("key1", "key2", "key100", "key3"); 这样的有个不存在的

最终的输出结果就是 [value1, value2, null, value3]

append

private static void testAppend(Jedis jedis) {
    jedis.append("key", "aaa");
    String value = jedis.get("key");
    System.out.println(value);
    jedis.append("key", "bbb");
    value = jedis.get("key");
    System.out.println(value);
}

执⾏结果

aaa
aaabbb

getrangesetrange

  • 注意 getrange 的区间是闭区间
private static void testGetRangeAndSetRange(Jedis jedis) {
    jedis.set("key", "abcdefg");
    String value = jedis.getrange("key", 1, 4);
    System.out.println(value);

    jedis.setrange("key", 0, "xyz");
    value = jedis.get("key");
    System.out.println(value);
}

执⾏结果

bcde
xyzdefg

setnx

private static void testSetnx(Jedis jedis) {
    long n = jedis.setnx("key", "value");
    System.out.println(n);
    String value = jedis.get("key");
    System.out.println(value);

    n = jedis.setnx("key", "value2");
    System.out.println(n);
    value = jedis.get("key");
    System.out.println(value);
}

执⾏结果

1
value
0
value

psetex

  • 获取到的结果不⼀定刚好 1000. pttl 本⾝也是有时间开销的.
private static void testPsetexAndPttl(Jedis jedis) {
    jedis.psetex("key", 1000, "value");
    long ttl = jedis.pttl("key");
    System.out.println(ttl);
}

执⾏结果

998

incrdecr

private static void testIncrAndDecr(Jedis jedis) {
    jedis.set("key", "0");
    jedis.incr("key");
    System.out.println(jedis.get("key"));

    jedis.decr("key");
    System.out.println(jedis.get("key"));
}

执⾏结果

1
0

incrbydecrby

private static void testIncrByAndDecrBy(Jedis jedis) {
    jedis.set("key", "0");
    jedis.incrBy("key", 10);
    System.out.println(jedis.get("key"));

    jedis.decrBy("key", 5);
    System.out.println(jedis.get("key"));
}

执⾏结果

10
5
列表操作
  1. lpush / lrange
  2. rpush、rpop、lpop
  3. blpop、brpop
  4. llen

lpushlpop

private static void testLpushAndLpop(Jedis jedis) {
    long n = jedis.lpush("key", "1", "2", "3", "4");
    System.out.println(n);

    String value = jedis.lpop("key");
    System.out.println(value);

    value = jedis.lpop("key");
    System.out.println(value);

    value = jedis.lpop("key");
    System.out.println(value);

    value = jedis.lpop("key");
    System.out.println(value);

    value = jedis.lpop("key"); // This may print null if the list is empty.
    System.out.println(value);
}

执⾏结果

4
4
3
2
1
null

rpushrpop

private static void testRpushAndRpop(Jedis jedis) {
    long n = jedis.rpush("key", "1", "2", "3", "4");
    System.out.println(n);

    String value = jedis.rpop("key");
    System.out.println(value);

    value = jedis.rpop("key");
    System.out.println(value);

    value = jedis.rpop("key");
    System.out.println(value);

    value = jedis.rpop("key");
    System.out.println(value);

    value = jedis.rpop("key"); // This may print null if the list is empty.
    System.out.println(value);
}

执⾏结果

4
4
3
2
1
null

lrange

lrange 填写的区间为闭区间.

private static void testLrange(Jedis jedis) {
    jedis.rpush("key", "1", "2", "3", "4");
    List<String> values = jedis.lrange("key", 1, 3);
    System.out.println(values);
}

执⾏结果

[2, 3, 4]

blpop

  • 返回值 List 是个⼆元组. [0] 表⽰ key, [1] 表⽰ value
  • 超时时间设为 0 表⽰死等.
  • 在执⾏同时, 起⼀个 redis-cli, 插⼊数据, 即可看到 blpop 的返回结果.

注意: 在代码中另起⼀个线程, 直接通过当前 jedis 这个连接插⼊数据是不⾏的. 必须另起⼀个 jedis 连接

private static void testBLpop(Jedis jedis) {
    while (true) {
        List<String> values = jedis.blpop(0, "key");
        System.out.println(values);
    }
}

执行结果

# 客⼾端执⾏ rpush key 1 2 3

[key, 1]
[key, 2]
[key, 3]

brpop

  • 使⽤⽅式和 blpop 类似
private static void testBRpop(Jedis jedis) {
    System.out.println("开始调用 brpop");
    while (true) {
        List<String> values = jedis.brpop(0, "key");
        System.out.println(values);
    }
}

执行结果

[key, 3]
[key, 2]
[key, 1]

lindex

private static void testLindex(Jedis jedis) {
    jedis.rpush("key", "1", "2", "3", "4");
    String value = jedis.lindex("key", 2);
    System.out.println(value);
}

执⾏结果

3

linsert

  • 通过 ListPosition.BEFORE 和 ListPosition.AFTER 标识插⼊位置.
private static void testLinsert(Jedis jedis) {
    jedis.rpush("key", "a", "b", "c", "d");
    jedis.linsert("key", ListPosition.BEFORE, "c", "100");
    List<String> values = jedis.lrange("key", 0, -1);
    System.out.println(values);
}

执行结果

 [a, b, 100, c, d]

llen

private static void testLlen(Jedis jedis) {
    jedis.rpush("key", "a", "b", "c", "d");
    long n = jedis.llen("key");
    System.out.println(n);
}

执⾏结果

4
哈希表操作
  1. hset / hget
  2. hexists
  3. hdel
  4. hkeys / hvals
  5. hmget / hmset

hsethget

private static void testHsetAndHget(Jedis jedis) {
    jedis.hset("key", "name", "zhangsan");
    jedis.hset("key", "age", "20");
    String name = jedis.hget("key", "name");
    System.out.println(name);
    String age = jedis.hget("key", "age");
    System.out.println(age);
}

执⾏结果

zhangsan
20

hexistshdel

private static void testHexistsAndHdel(Jedis jedis) {
    jedis.hset("key", "name", "zhangsan");
    boolean ok = jedis.hexists("key", "name");
    System.out.println(ok);

    jedis.hdel("key", "name");
    ok = jedis.hexists("key", "name");
    System.out.println(ok);
}

执⾏结果

true
false

hkeyshvalues

private static void testHkeysAndHvalues(Jedis jedis) {
    jedis.hset("key", "name", "zhangsan");
    jedis.hset("key", "age", "20");

    Set<String> keys = jedis.hkeys("key");
    System.out.println(keys);

    List<String> values = jedis.hvals("key");
    System.out.println(values);
}

执⾏结果

[name, age]
[zhangsan, 20]

hmget

private static void testHmget(Jedis jedis) {
    jedis.hset("key", "name", "zhangsan");
    jedis.hset("key", "age", "20");
    List<String> values = jedis.hmget("key", "name", "age");
    System.out.println(values);
}

执⾏结果

[zhangsan, 20]

hlen

private static void testHlen(Jedis jedis) {
    jedis.hset("key", "name", "zhangsan");
    jedis.hset("key", "age", "20");
    long n = jedis.hlen("key");
    System.out.println(n);
}

执⾏结果

2

hincrbyhincrbyfloat

private static void testHIncrByAndIncrByFloat(Jedis jedis) {
    jedis.hset("key", "age", "20");
    long n = jedis.hincrBy("key", "age", 10);
    System.out.println(n);
    String value = jedis.hget("key", "age");
    System.out.println(value);

    double dn = jedis.hincrByFloat("key", "age", 0.5);
    System.out.println(dn);
    value = jedis.hget("key", "age");
    System.out.println(value);
}

执⾏结果

30
30
30.5
30.5
集合操作
  1. sadd / smembers
  2. sismember
  3. scard
  4. spop
  5. sinter / sinterstore

saddsmembers

private static void testSaddAndSmembers(Jedis jedis) {
    jedis.sadd("key", "aaa", "bbb", "ccc");
    Set<String> members = jedis.smembers("key");
    System.out.println(members);
}

执⾏结果

[aaa, ccc, bbb]

sremsismember

private static void testSremAndSismember(Jedis jedis) {
    jedis.sadd("key", "aaa", "bbb", "ccc");
    boolean ok = jedis.sismember("key", "aaa");
    System.out.println(ok);
    long n = jedis.srem("key", "aaa", "bbb");
    System.out.println(n);

    ok = jedis.sismember("key", "aaa");
    System.out.println(ok);
}

执⾏结果

true
2
false

scard

private static void testScard(Jedis jedis) {
    jedis.sadd("key", "aaa", "bbb", "ccc");
    long n = jedis.scard("key");
    System.out.println(n);
}

执⾏结果

3

sinter

private static void testSinter(Jedis jedis) {
    jedis.sadd("key1", "aaa", "bbb", "ccc");
    jedis.sadd("key2", "aaa", "bbb", "ddd");

    Set<String> results = jedis.sinter("key1", "key2");
    System.out.println(results);
}

执行结果

[aaa, bbb]

sunion

private static void testSunion(Jedis jedis) {
    jedis.sadd("key1", "aaa", "bbb", "ccc");
    jedis.sadd("key2", "aaa", "bbb", "ddd");

    Set<String> results = jedis.sunion("key1", "key2");
    System.out.println(results);
}

执⾏结果

 [aaa, ccc, bbb, ddd]

sdiff

private static void testSdiff(Jedis jedis) {
    jedis.sadd("key1", "aaa", "bbb", "ccc");
    jedis.sadd("key2", "aaa", "bbb", "ddd");

    Set<String> results = jedis.sdiff("key1", "key2");
    System.out.println(results);
}

执⾏结果

[ccc]
有序集合操作
  1. zadd / zrange
  2. zcard
  3. zrem
  4. zscore
  5. zrank

zaddzrange

  • zrange 通过下标获取元素. 闭区间
private static void testZaddAndZrange(Jedis jedis) {
    jedis.zadd("key", 100, "吕布");
    jedis.zadd("key", 98, "赵云");
    jedis.zadd("key", 95, "典韦");
    jedis.zadd("key", 92, "关羽");
    jedis.zadd("key", 70, "刘备");

    List<String> members = jedis.zrange("key", 0, 4);
    System.out.println(members);

    List<Tuple> membersWithScore = jedis.zrangeWithScores("key", 0, 4);
    System.out.println(membersWithScore);
}

执⾏结果

[刘备, 关⽻, 典⻙, 赵云, 吕布]
[[刘备,70.0], [关⽻,92.0], [典⻙,95.0], [赵云,98.0], [吕布,100.0]]

zremzcard

private static void testZremAndZcard(Jedis jedis) {
    jedis.zadd("key", 100, "吕布");
    jedis.zadd("key", 98, "赵云");
    jedis.zadd("key", 95, "典韦");
    jedis.zadd("key", 92, "关羽");
    jedis.zadd("key", 70, "刘备");

    long n = jedis.zcard("key");
    System.out.println(n);

    n = jedis.zrem("key", "吕布", "赵云");
    System.out.println(n);
    n = jedis.zcard("key");
    System.out.println(n);
}

执⾏结果

5
2
3

zcount

  • 获取指定分数区间中的元素个数. 闭区间.
private static void testZcount(Jedis jedis) {
    jedis.zadd("key", 100, "吕布");
    jedis.zadd("key", 98, "赵云");
    jedis.zadd("key", 95, "典韦");
    jedis.zadd("key", 92, "关羽");
    jedis.zadd("key", 70, "刘备");

    long n = jedis.zcount("key", 92, 98);
    System.out.println(n);
}

执⾏结果

3

zpopmaxzpopmin

private static void testZpopmaxAndZpopmin(Jedis jedis) {
    jedis.zadd("key", 100, "吕布");
    jedis.zadd("key", 98, "赵云");
    jedis.zadd("key", 95, "典⻙");
    jedis.zadd("key", 92, "关⽻");
    jedis.zadd("key", 70, "刘备");

    Tuple tuple = jedis.zpopmax("key");
    System.out.println(tuple);

    tuple = jedis.zpopmin("key");
    System.out.println(tuple);
}

执⾏结果

[吕布,100.0]
[刘备,70.0]

zrank

  • 获取指定 member 的下标
private static void testZrank(Jedis jedis) {
    jedis.zadd("key", 100, "吕布");
    jedis.zadd("key", 98, "赵云");
    jedis.zadd("key", 95, "典⻙");
    jedis.zadd("key", 92, "关⽻");
    jedis.zadd("key", 70, "刘备");

    long n = jedis.zrank("key", "赵云");
    System.out.println(n);

    n = jedis.zrevrank("key", "赵云");
    System.out.println(n);
}

执⾏结果

3
1

zscore

private static void testZscore(Jedis jedis) {
    jedis.zadd("key", 100, "吕布");
    jedis.zadd("key", 98, "赵云");
    jedis.zadd("key", 95, "典⻙");
    jedis.zadd("key", 92, "关⽻");
    jedis.zadd("key", 70, "刘备");

    double score = jedis.zscore("key", "赵云");
    System.out.println(score);
}

执⾏结果

98.0

zincrby

private static void testZincrby(Jedis jedis) {
    jedis.zadd("key", 100, "吕布");

    double n = jedis.zincrby("key", 10, "吕布");
    System.out.println(n);

    n = jedis.zincrby("key", -20, "吕布");
    System.out.println(n);
}

执⾏结果

110.0
90.0

zinterstore

private static void testZinterstore(Jedis jedis) {
    jedis.zadd("key1", 100, "吕布");
    jedis.zadd("key1", 98, "赵云");
    jedis.zadd("key1", 95, "典⻙");

    jedis.zadd("key2", 100, "吕布");
    jedis.zadd("key2", 98, "赵云");
    jedis.zadd("key2", 92, "关⽻");

    long n = jedis.zinterstore("key3", "key1", "key2");
    System.out.println(n);
    List<Tuple> tuples = jedis.zrangeWithScores("key3", 0, -1);
    System.out.println(tuples);
}

执⾏结果

2
[[赵云,196.0], [吕布,200.0]]

zunionstore

private static void testZunionstore(Jedis jedis) {
    jedis.zadd("key1", 100, "吕布");
    jedis.zadd("key1", 98, "赵云");
    jedis.zadd("key1", 95, "典⻙");

    jedis.zadd("key2", 100, "吕布");
    jedis.zadd("key2", 98, "赵云");
    jedis.zadd("key2", 92, "关⽻");

    long n = jedis.zunionstore("key3", "key1", "key2");
    System.out.println(n);
    List<Tuple> tuples = jedis.zrangeWithScores("key3", 0, -1);
    System.out.println(tuples);
}

执⾏结果

4
[[关⽻,92.0], [典⻙,95.0], [赵云,196.0], [吕布,200.0]]

Jedis 这个库本质就是针对 Redis 的各种命令进行了封装,调用某个方法,就相当于 Redis 客户端中敲下了对应的命令,详细可以去 GitHub 看看 Jedis官方网站,其中有个 Jedis文档

访问集群

使⽤ JedisCluster 类代替 Jedis 类即可.

需要在创建实例的时候, 把多个节点的地址, 都设置进去.

JedisCluster 提供的⽅法和 Jedis 基本⼀致. 都和 Redis 命令是对应的. 具体细节我们不再演⽰了

import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisCluster;

import java.util.HashSet;
import java.util.Set;

public class Main {
    public static void main(String[] args) {
        Set<HostAndPort> nodes = new HashSet<>();
        nodes.add(new HostAndPort("172.30.0.101", 6379));
        nodes.add(new HostAndPort("172.30.0.102", 6379));
        nodes.add(new HostAndPort("172.30.0.103", 6379));
        nodes.add(new HostAndPort("172.30.0.104", 6379));
        nodes.add(new HostAndPort("172.30.0.105", 6379));
        nodes.add(new HostAndPort("172.30.0.106", 6379));
        nodes.add(new HostAndPort("172.30.0.107", 6379));
        nodes.add(new HostAndPort("172.30.0.108", 6379));
        nodes.add(new HostAndPort("172.30.0.109", 6379));

        try (JedisCluster jedisCluster = new JedisCluster(nodes)) {
            jedisCluster.set("k1", "111");
            String value = jedisCluster.get("k1");
            System.out.println(value);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

注意! 由于此处我们的代码是需要访问整个 redis 集群, 因此不能直接在 windows 上运⾏程序了. (上述docker 容器的 ip 都是 windows 主机上⽆法直接访问的).

需要把整个程序打成 jar 包, 上传到 Linux 中, 并通过下列⽅式运⾏.

java -jar [jar包名]

Redis Java 集成到 Spring Boot

使⽤ Spring Boot 连接 Redis 单机
创建项⽬

勾选 NoSQL 中的 Spring Data Redis

当然, 把 Web 中的 Spring Web 也勾选⼀下. ⽅便写接⼝进⾏后续测试.

配置 redis 服务地址

在 application.yml 中配置.

spring:
  redis:
    host: 127.0.0.1
    port: 9999
创建 Controller

创建 MyController

由于当前只是写简单的测试代码, 我们就不进⾏分层了. 就只创建个简单的 Controller 即可.

后续 Redis 测试的各种方法,都通过这个 Controller 提供的 http 接口来触发,访问对应的接口即可在网页和控制台看见对应的结果、输出了

@RestController
public class MyController {
    @Autowired
    private StringRedisTemplate redisTemplate; 
}

此处需要引⼊ StringRedisTemplate 实例.

前面的 jedis,都是通过 Jedis 对象里的各种方法来操作 Redis,现在 Spring 是通过 StringRedisTemplate 来操作 Redis,这是 RedisTemplate 的子类,专门用来处理文本数据的,这个类提供的方法和 Jedis 提供的方法还是有很大差异的

此处的 RedisTemplate 是把这些操作 Redis 的方法分成了几个类别,这是希望通过上述重新的封装,让接口用起来更简单

关于 删库 和 ping 还有其他一些操作就没有直接提供了,但可以通过 execute 方法,这个方法里的参数是 RedisCallBack<T> ,函数式接口,即回调函数,写我们要执行的 redis 命令,这个回调就会被 RedisTemplate 内部进行执行了,在方法内部这么写即可删库,或者其他你想的操作

redisTemplate.execute((RedisConnection connection) -> {
    // execute 要求回调方法中必须写 return 语句,返回个东西
    // 这个回调返回的对象,就会作为 execute 本身的返回值
    connection.flushAll();
    return null;
});
使⽤ String

.opsForValue 是专门操作字符串的

@GetMapping("/testString")
@ResponseBody
public String testString() {
    redisTemplate.opsForValue().set("key", "value");
    String value = redisTemplate.opsForValue().get("key");
    System.out.println(value);

    redisTemplate.delete("key");
    return "OK";
}
使⽤ List
@GetMapping("/testList")
@ResponseBody
public String testList() {
    redisTemplate.opsForList().leftPush("key", "a");
    redisTemplate.opsForList().leftPushAll("key", "b", "c", "d");
    List<String> values = redisTemplate.opsForList().range("key", 1, 2);
    System.out.println(values);

    redisTemplate.delete("key");
    return "OK";
}
使⽤ Hash
@GetMapping("/testHashmap")
@ResponseBody
public String testHashmap() {
    redisTemplate.opsForHash().put("key", "name", "zhangsan");
    String value = (String) redisTemplate.opsForHash().get("key", "name");
    System.out.println(value);

    redisTemplate.opsForHash().delete("key", "name");
    boolean ok = redisTemplate.opsForHash().hasKey("key", "name");
    System.out.println(ok);

    redisTemplate.delete("key");
    return "OK";
}

使⽤ Set
@GetMapping("/testSet")
@ResponseBody
public String testSet() {
    redisTemplate.opsForSet().add("key", "aaa", "bbb", "ccc");
    boolean ok = redisTemplate.opsForSet().isMember("key", "aaa");
    System.out.println(ok);

    redisTemplate.opsForSet().remove("key", "aaa");
    long n = redisTemplate.opsForSet().size("key");
    System.out.println(n);

    redisTemplate.delete("key");
    return "OK";
}
使⽤ ZSet
@GetMapping("/testZSet")
@ResponseBody
public String testZSet() {
    redisTemplate.opsForZSet().add("key", "吕布", 100);
    redisTemplate.opsForZSet().add("key", "赵云", 98);
    redisTemplate.opsForZSet().add("key", "典⻙", 95);

    Set<String> values = redisTemplate.opsForZSet().range("key", 0, 2);
    System.out.println(values);

    long n = redisTemplate.opsForZSet().count("key", 95, 100);
    System.out.println(n);

    redisTemplate.delete("key");
    return "OK";
}
执⾏结果

运⾏程序, 构造上述请求, 即可在服务器的控制台中看到执⾏结果.

使⽤ Spring Boot 连接 Redis 集群
配置 redis 集群地址
spring:
  redis:
    cluster:
      nodes:
        - 172.30.0.101:6379
        - 172.30.0.102:6379
        - 172.30.0.103:6379
        - 172.30.0.104:6379
        - 172.30.0.105:6379
        - 172.30.0.106:6379
        - 172.30.0.107:6379
        - 172.30.0.108:6379
        - 172.30.0.109:6379
    lettuce:
      cluster:
        refresh:
          adaptive: true
          period: 2000

下⽅的 lettuce 系列配置, ⽬的是为了⾃动刷新集群的拓扑结构. 当集群中有节点宕机/加⼊新节点之后, 我们的代码能够⾃动感知到集群的变化.

改完配置之后, 其他代码⽆需做出任何调整, 直接就可以正常运⾏了.

注意!

由于上述 ip 都是 docker 容器的 ip, 在 windows 主机上不能直接访问.

因此需要把程序打成 jar 包, 部署到 linux 上, 再通过 java -jar [jar包名] 的⽅式执⾏.

Spring Data Redis 官方文档
Spring Data Redis 官方中文文档

小结

Spring Boot 2 系列内置的 Redis 是 Lettuce, 和 Jedis 的使⽤还是存在⼀定的差异.

对于 Jedis 来说, 各个⽅法和 Redis 的命令基本是⼀致的.

⽽集成到 Spring Boot 之后, 接⼝上和原始 Redis 命令存在部分差别, 但是使⽤起来也并不困难, 只要⼤家熟悉 Redis 的基本操作, 还是很容易可以通过⽅法名字理解⽤法的.

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

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

相关文章

华为OD机试真题 - 分割均衡字符串 - 贪心算法(Python/JS/C/C++ 2024 D卷 100分)

华为OD机试 2024E卷题库疯狂收录中,刷题点这里 专栏导读 本专栏收录于《华为OD机试真题(Python/JS/C/C++)》。 刷的越多,抽中的概率越大,私信哪吒,备注华为OD,加入华为OD刷题交流群,每一题都有详细的答题思路、详细的代码注释、3个测试用例、为什么这道题采用XX算法、…

集成电路学习:什么是GPIO通用输入输出

GPIO&#xff1a;通用输入输出 GPIO&#xff0c;全称General Purpose Input/Output&#xff0c;即通用输入/输出端口&#xff0c;是嵌入式系统中非常重要的基本硬件资源之一。以下是对GPIO的详细解析&#xff1a; 一、GPIO的定义与功能 GPIO是一种非常灵活的接口&#xff0c;可…

ping不通本地虚拟机的静态ip的解决方案

找到网络配置文件 一般我们设置虚拟机文件为静态IP地址&#xff0c; 比如 /etc/sysconfig/network-scripts/ifcfg-ens33 记住Gateway 192.168.200.2 查看虚拟网络编辑器 把子网ip改为192.168.200.0 前三部分相同&#xff0c;第四部分是0 把nat设置中的网关ip改成Gateway 1…

时空图卷积网络:用于交通流量预测的深度学习框架-1

摘要 准确的交通预测对于城市交通控制和引导至关重要。由于交通流的高度非线性和复杂性&#xff0c;传统方法无法满足中长期预测任务的需求&#xff0c;且往往忽略了空间和时间的依赖关系。本文提出一种新的深度学习框架——时空图卷积网络(STGCN)来解决交通领域的时间序列预测…

「MyBatis」图书管理系统 v1.0

&#x1f387;个人主页&#xff1a;Ice_Sugar_7 &#x1f387;所属专栏&#xff1a;JavaEE &#x1f387;欢迎点赞收藏加关注哦&#xff01; 图书管理系统 v1.0 &#x1f349;登录&#x1f349;图书操作&#x1f34c;图书类&#x1f34c;页面信息&#x1f34c;操作 &#x1f34…

Java中的经典排序算法:快速排序、归并排序和计数排序详解(如果想知道Java中有关快速排序、归并排序和计数排序的知识点,那么只看这一篇就足够了!)

前言&#xff1a;排序算法在计算机科学中占有重要地位&#xff0c;不同的算法适用于不同的场景。本文将深入探讨快速排序、归并排序和计数排序。 ✨✨✨这里是秋刀鱼不做梦的BLOG ✨✨✨想要了解更多内容可以访问我的主页秋刀鱼不做梦-CSDN博客 先让我们看一下本文大致的讲解内…

新生在线分班查询,用这个小程序制作仅需一分钟!

今天许多学校已陆续开学&#xff0c;老师们又开始忙碌起来。他们需要将新生的分班信息逐一通知给每位家长&#xff0c;这不仅是一项繁琐的工作&#xff0c;而且效率也不高。传统的方法是通过电话、短信或邮件一一通知&#xff0c;这不仅耗时耗力&#xff0c;还容易出现信息传递…

百度文库文章-暂存下-------题 目: 链式简单选择排序

题 目: 链式简单选择排序 初始条件&#xff1a; 理论&#xff1a;学习了《数据结构》课程&#xff0c;掌握了基本的数据结构和常用的算法&#xff1b; 实践&#xff1a;计算机技术系实验室提供计算机及软件开发环境。 要求完成的主要任务: &#xff08;包括课程设计工作量…

如何用pytorch进行图像分类

如何用pytorch进行图像分类 使用PyTorch进行图像分类是深度学习中的一个常见任务&#xff0c;涉及一系列步骤&#xff0c;从数据预处理到模型训练和评估。下面将详细描述每个步骤&#xff0c;从零开始构建一个图像分类器。 1. 安装必要的库 在开始之前&#xff0c;首先需要确…

驱动(RK3588S)第四课时:模块化编程

目录 一、什么是模块化编程二、怎么把自己编译代码给加载到开发板上运行三、驱动编程的框架四、驱动编程具体实例1、编写单模块化驱动代码2、编写多模块化驱动代码3、编写向模块传参驱动代码4、编写多模块化驱动代码另一种方式 一、什么是模块化编程 在嵌入式里所谓的模块化编…

Vue——day07之条件渲染、列表渲染以及监测数据

目录 1.template标签 2.条件渲染 3.列表渲染 4.v-for中的key的作用以及原理 5.列表过滤 placeholder 前端空字符串 使用数据监视watch实现 使用计算属性实现 6.列表排序 7.Vue更新数据检测失败 原因 总结 1.template标签 template标签是Vue.js中的一个特殊元素&am…

kube-scheduler调度策略之预选策略(三)

一、概述 摘要&#xff1a;本文我们继续分析源码&#xff0c;并聚焦在预选策略的调度过程的执行。 二、正文 说明&#xff1a;基于 kubernetes v1.12.0 源码分析 上文我们说的(g *genericScheduler) Schedule()函数调用了findNodesThatFit()执行预选策略。 2.1 findNodesTha…

Truncated incorrect max_connections value: ‘999999‘

MySQL 的最大连接数&#xff08;max_connections&#xff09;可以设置的上限值在不同的资料中有所不同。以下是一些关键信息&#xff1a; 默认值和默认范围&#xff1a; MySQL 的默认最大连接数通常为 100 。一些资料提到默认值为 151 。 最大允许值&#xff1a; MySQL 的最大…

ant-design-vue:a-table表格中插入自定义按钮

本文将介绍如何使用ant-design-vue在a-table表格中加入自定义按钮和图标的代码。 结果如下图所示&#xff0c; 一、简单示例 <template><a-table:columns"columns":data-source"data":row-selection"rowSelection":ellipsis"tru…

对称密码学

1. 使用OpenSSL 命令行 在 Ubuntu Linux Distribution (发行版&#xff09;中&#xff0c; OpenSSL 通常可用。当然&#xff0c;如果不可用的话&#xff0c;也可以使用下以下命令安装 OpenSSL: $ sudo apt-get install openssl 安装完后可以使用以下命令检查 OpenSSL 版本&am…

深度学习基础案例4--构建CNN卷积神经网络实现对猴痘病的识别(测试集准确率86.5%)

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 前言 下一周会很忙&#xff0c;更新可能不及时&#xff0c;请大家见谅这个项目我感觉是一个很好的入门案例&#xff0c;但是自己测试的时候测试集准确率只比较…

mcu loader升级固件原理与实现

1 mcu loader升级固件原理 mcu 固件有两部分&#xff0c;如下图所示&#xff0c;一部分是 loader.bin&#xff0c;一部分是 app.bin&#xff0c;将两部分的固件合并在一起烧录进 mcu 的 flash 当中。mcu 上电进入loader 模式执行 loader.bin 部分的程序&#xff0c;然后读取 fl…

前端踩坑记录:javaScript复制对象和数组,不能简单地使用赋值运算

问题 如图&#xff0c;编辑table中某行的信息&#xff0c;发现在编辑框中修改名称的时候&#xff0c;表格中的信息同步更新。。。 检查原因 编辑页面打开时&#xff0c;需要读取选中行的信息&#xff0c;并在页面中回显。代码中直接将当前行的数据对象赋值给编辑框中的表单对…

51单片机——I2C总线

1、I2C总线简介 I2C总线&#xff08;Inter IC BUS&#xff09;是由Philips公司开发的一种通用数据总线 两根通信线&#xff1a;SCL&#xff08;Serial Clock&#xff09;、SDA&#xff08;Serial Data&#xff09; 同步、半双工&#xff0c;带数据应答 通用的I2C总线&#…

Linux基础(包括centos7安装、linux基础命令、vi编辑器)

一、安装CentOS7 需要&#xff1a;1、VMware Workstation&#xff1b;2、CentOS7镜像 1、安装镜像 2、虚拟机配置 开启虚拟机&#xff0c;鼠标从vm中移出来用快捷键ctrlalt 点击开始安装&#xff0c;设置密码&#xff0c;等待安装完成,&#xff0c;重启。 3、注意事项 如果没…