【Redis】Java & JavaSpring 客户端

文章目录

  • 一、认识RESP协议
  • 二、Jedis原生客户端
    • [1. Maven依赖引入](#1. Maven依赖引入)
    • [2. 网络处理:云服务器Redis端口转发](#2. 网络处理:云服务器Redis端口转发)
    • [3. 连接Redis单机](#3. 连接Redis单机)
    • [4. Redis五大数据类型完整Jedis示例](#4. Redis五大数据类型完整Jedis示例)
      • [4.1 通用key操作](#4.1 通用key操作)
      • [4.2 String 字符串](#4.2 String 字符串)
      • [4.3 List 列表](#4.3 List 列表)
      • [4.4 Hash 哈希(key-field‑value)](#4.4 Hash 哈希(key-field‑value))
      • [4.5 Set集合](#4.5 Set集合)
      • [4.6 ZSet有序集合](#4.6 ZSet有序集合)
    • [5. JedisCluster 访问Redis集群](#5. JedisCluster 访问Redis集群)
  • [三、SpringBoot 集成Redis](#三、SpringBoot 集成Redis)
    • [1. 创建SpringBoot项目](#1. 创建SpringBoot项目)
    • [2. 单机Redis配置 application.yml](#2. 单机Redis配置 application.yml)
    • [3. StringRedisTemplate](#3. StringRedisTemplate)
    • [4. 完整Controller测试代码](#4. 完整Controller测试代码)
    • [5. SpringBoot连接Redis集群yml配置](#5. SpringBoot连接Redis集群yml配置)
  • [三、Jedis 和 Lettuce对比总结](#三、Jedis 和 Lettuce对比总结)

一、认识RESP协议

RESP(Redis Serialization Protocol ,Redis序列化协议),是Redis服务端与客户端通信的应用层协议,基于TCP(但耦合度很低),默认端口6379。

特点:

  1. 简单易解析,文本+二进制混合:部分是可读文本,支持传输二进制数据(安全传输图片、字节数组,不会截断)。

  2. 请求响应模式:客户端发命令 → Redis执行 → 返回RESP格式结果。

  3. 类型标记:首字符标记数据类型

    首字符 类型 含义
    + 简单字符串 简短非二进制字符串,如+PONG\r\n
    - 错误信息 异常提示,-ERR wrong command\r\n
    : 整数 数字,:\d+\r\n
    $ 批量字符串 二进制/长字符串,$长度\r\n内容\r\n;$-1代表nil空值
    * 数组 多条数据集合,*元素个数\r\n后面拼接多个RESP对象

换行固定是 \r\n(CRLF),作为分隔符。

示例: PING交互

客户端发送:

复制代码
*1\r\n$4\r\nPING\r\n
  • *1:数组,一共1个元素
  • $4:批量字符串,长度4
  • PING:内容

Redis返回:

复制代码
+PONG\r\n

二、Jedis原生客户端

Jedis是Redis官方推荐的Java客户端,方法名几乎和Redis命令完全对应,学习Redis命令之后,可以直接看懂Jedis代码。

1. Maven依赖引入

在pom.xml添加jedis依赖,选择较新版本即可,文档示例版本4.3.2

xml 复制代码
<!-- jedis 依赖 -->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>4.4.2</version>
</dependency>

2. 网络处理:云服务器Redis端口转发

⚠️高危提醒:不要直接把Redis的6379端口开放到公网。Redis默认没有密码,黑客扫描端口后会入侵服务器。 使用Xshell SSH隧道端口转发,不需要修改服务器防火墙。

Xshell隧道配置

  1. 新增本地转发规则:
配置项 值 说明
类型 本地(拨出) 本地程序访问
源主机 localhost 本机
侦听端口 8888 本地程序连接这个端口
目标主机 localhost 服务器内部的redis地址
目标端口 6379 Redis默认端口
  1. 保存配置,保持Xshell会话处于连接状态。

    访问本机 127.0.0.1:8888 等价访问云服务器的 127.0.0.1:6379;一旦关闭Xshell,端口映射立刻失效。

3. 连接Redis单机

不直接创建Jedis对象,使用JedisPool连接池管理连接,避免频繁创建销毁连接的开销。

使用try‑with‑resources语法,可以自动关闭Jedis连接,不需要手动调用close()。

java 复制代码
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

public class JedisDemo {
    public static void main(String[] args) {
        // 创建连接池,指向本地转发后的端口8888
        JedisPool jedisPool = new JedisPool("tcp://127.0.0.1:8888");
        // try() 中获取资源,代码块结束自动关闭jedis连接
        try (Jedis jedis = jedisPool.getResource()) {
            // ping命令测试连通性,成功返回PONG
            String pong = jedis.ping();
            System.out.println("连接测试:" + pong);
        }
        jedisPool.close();
    }
}

常见日志警告说明

java 复制代码
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder"
SLF4J: Defaulting to no‑operation (NOP) logger implementation

这个只是SLF4J日志框架没有找到日志实现类,不影响Redis业务逻辑,直接忽略,不需要修复。

4. Redis五大数据类型完整Jedis示例

所有测试方法都在try的Jedis作用域中执行;jedis.flushDB()可以清空当前数据库,方便测试。

main函数: 建立连接

java 复制代码
public static void main(String[] args) {  
    //连接redis服务器  
    JedisPool jedisPool = new JedisPool("tcp://127.0.0.1:8888");  
  
    try(Jedis jedis = jedisPool.getResource()){
	    //测试方法
	    test(jedis); 
    } 
    jedisPoll.close();    
}

4.1 通用key操作

java 复制代码
private static void testKeyCommon(Jedis jedis) {
    jedis.set("k1","v1");
    jedis.set("k2","v2");
    
    // exists 判断key是否存在
    System.out.println(jedis.exists("k1"));
    
    // del 删除key,支持多key,返回成功删除数量
    System.out.println(jedis.del("k1","k2"));
    
    // keys 模糊查询,返回Set<String>类(keys返回的内容不重复且无序)
    // 生产环境禁止使用keys命令,会阻塞redis
    Set<String> keys = jedis.keys("*");
    
    //expire 设置过期时间、ttl 返回剩余存活时间
	jedis.expire("k1",10);  
	System.out.println("ttl:"+jedis.ttl("key1"));  //10
	Thread.sleep(3000);  //休眠3s
	System.out.println("ttl:"+jedis.ttl("key1"));  //7
    
    // type 获取key对应的数据类型
    System.out.println(jedis.type("k1"));
}

4.2 String 字符串

java 复制代码
private static void testString(Jedis jedis) {
    // set key value
    jedis.set("key1", "value1");
    jedis.set("key2", "value2");

    // get key
    String v1 = jedis.get("key1");
    String vNull = jedis.get("noExistKey"); // key不存在返回null
    System.out.println(v1 + " | " + vNull);

    // mset 批量设置
    jedis.mset("k1", "v1", "k2", "v2", "k3", "v3");
    // mget 批量获取,返回List<String>(value可能重复)
    // 某个key不存在时使用null占位,返回的数量与传入key数量保持一致
    System.out.println(jedis.mget("k1", "k2", "k3"));

    // append 字符串追加
    jedis.set("appendKey", "aaa");
    jedis.append("appendKey", "bbb");
    System.out.println(jedis.get("appendKey")); // aaabbb

    // getrange 截取字符串 [start,end]闭区间
    jedis.set("rangeKey", "abcdefg");
    System.out.println(jedis.getrange("rangeKey",1,4)); // bcde

    // setrange 从指定下标覆盖内容
    //
    jedis.setrange("rangeKey",0,"xyz");
    System.out.println(jedis.get("rangeKey")); // xyzdefg

    // setnx key value:key不存在才设置,存在则不操作;返回1成功,0失败
    System.out.println(jedis.setnx("nxKey","val")); //1
    System.out.println(jedis.setnx("nxKey","val2"));//0

    // setex  设置key+过期时间,单位秒
    jedis.setex("exKey",60,"testValue");
    // psetex 设置过期时间,单位毫秒
    jedis.psetex("pExKey",1000,"test");

    // incr decr 自增自减
    jedis.set("num","0");
    jedis.incr("num");
    jedis.decr("num");
    // incrBy decrBy 指定步长增减
    jedis.incrBy("num",10);
    jedis.decrBy("num",5);
    System.out.println(jedis.get("num"));
}

4.3 List 列表

java 复制代码
private static void testList(Jedis jedis) {
    // lpush:头插;lpop:头删
    jedis.lpush("listKey", "1","2","3","4");
    System.out.println(jedis.lpop("listKey"));

    // rpush:尾插;rpop:尾删
    jedis.rpush("rList","1","2","3","4");
    System.out.println(jedis.rpop("rList"));

    // lrange key start end 查询区间元素,闭区间,-1代表末尾
    jedis.rpush("rangeList","1","2","3","4");
    System.out.println(jedis.lrange("rangeList",1,3));//【2,3,4】

    // lindex 通过下标获取元素
    System.out.println(jedis.lindex("rangeList",2));

    // llen 获取列表长度
    System.out.println(jedis.llen("rangeList"));

    // linsert 在指定元素前后插入 ListPosition.BEFORE / AFTER
    jedis.rpush("insertList","a","b","c","d");
    jedis.linsert("insertList", redis.clients.jedis.params.ListPosition.BEFORE,"c","100");
    System.out.println(jedis.lrange("insertList",0,-1));

    // blpop brpop (timeout,key) 阻塞弹出,超时时间0代表永久阻塞等待
    // 返回结果为二元组,分别是删除元素来自的key以及删除的元素
    List<String> res = jedis.blpop(0,"blockList");
}

注意: 同一个jedis连接不能一边blpop,一边写入数据;必须另起一个客户端做生产者

4.4 Hash 哈希(key-field‑value)

java 复制代码
private static void testHash(Jedis jedis) {
    // hset 设置字段
    jedis.hset("user:1001", "name", "zhangsan");
    jedis.hset("user:1001", "age", "20");

    // hget 获取单个字段
    System.out.println(jedis.hget("user:1001","name"));

    // hmget 批量获取多个字段 返回Map<String,String>类型
    System.out.println(jedis.hmget("user:1001","name","age"));

    // hexists 判断field是否存在
    System.out.println(jedis.hexists("user:1001","name"));

    // hdel 删除hash的field
    jedis.hdel("user:1001","name");

    // hkeys 获取所有field,Set<String>类型
    // hvals获取所有value,返回List<String>类型
    jedis.hset("user:1001","name","lisi");
    System.out.println(jedis.hkeys("user:1001"));
    System.out.println(jedis.hvals("user:1001"));

    // hlen 获取hash的字段总数量
    System.out.println(jedis.hlen("user:1001"));

    // hincrBy 整数自增;hincrByFloat浮点数自增
    jedis.hincrBy("user:1001","age",10);
    jedis.hincrByFloat("user:1001","age",0.5);
    System.out.println(jedis.hget("user:1001","age"));
}

4.5 Set集合

java 复制代码
private static void testSet(Jedis jedis) {
    // sadd 添加元素,自动去重
    jedis.sadd("setKey","aaa","bbb","ccc");
    // smembers 获取全部元素
    System.out.println(jedis.smembers("setKey"));

    // sismember 判断元素是否在集合
    System.out.println(jedis.sismember("setKey","aaa"));

    // srem 删除元素
    jedis.srem("setKey","aaa");

    // scard 获取集合元素个数
    System.out.println(jedis.scard("setKey"));
    
    // spop 随机删除元素,返回被删除的元素
    System.out.println(jedis.spop("set1"));

    // 集合运算
    jedis.sadd("s1","aaa","bbb","ccc");
    jedis.sadd("s2","aaa","bbb","ddd");
    System.out.println("交集:" + jedis.sinter("s1","s2"));
    System.out.println("并集:" + jedis.sunion("s1","s2"));
    System.out.println("差集:" + jedis.sdiff("s1","s2"));
}

4.6 ZSet有序集合

java 复制代码
private static void testZSet(Jedis jedis) {
    // zadd 添加成员与分数
    jedis.zadd("hero",100,"吕布");
    jedis.zadd("hero",98,"赵云");
    jedis.zadd("hero",95,"典韦");
    jedis.zadd("hero",92,"关羽");
    jedis.zadd("hero",70,"刘备");

    // zrange 按下标范围获取(从小到大) 返回List<String>类型(考虑有序性)
    System.out.println(jedis.zrange("hero",0,4));
    
    // zrangeWithScores 获取成员+分数 返回List<Tuple>类型
    List<Tuple> tuples = jedis.zrangeWithScores("zset1", 0, -1);  
    //Tuple 是Jedis提供的元组类型,使用getElement和getScore获取member和对应的score
	System.out.println(tuples.get(0).getElement());  
	//score对应double类型,最好使用包装类接收,防止空指针异常
	System.out.println(tuples.get(0).getScore());

    // zrem 删除成员
    jedis.zrem("hero","吕布");
    // zcard 获取总元素数量
    System.out.println(jedis.zcard("hero"));

    // zcount 统计指定分数区间的元素数量
    System.out.println(jedis.zcount("hero",92,98));

    // zpopmax / zpopmin 弹出分数最大、最小的元素
    System.out.println(jedis.zpopmax("hero"));
    System.out.println(jedis.zpopmin("hero"));

    // zrank 获取正序下标;zrevrank逆序下标
    System.out.println(jedis.zrank("hero","赵云"));
    System.out.println(jedis.zrevrank("hero","赵云"));

    // zscore 获取成员分数
    System.out.println(jedis.zscore("hero","赵云"));

    // zincrby 修改成员分数,支持正负
    jedis.zincrby("hero",10,"赵云");

    // zinterstore 交集存入新key;zunionstore并集存入新key
    jedis.zadd("z1",100,"吕布");
    jedis.zadd("z1",98,"赵云");
    jedis.zadd("z2",100,"吕布");
    jedis.zadd("z2",92,"关羽");
    jedis.zinterstore("z_inter","z1","z2");
    jedis.zunionstore("z_union","z1","z2");
    System.out.println(jedis.zrangeWithScores("z_inter",0,-1));
    System.out.println(jedis.zrangeWithScores("z_union",0,-1));
}

5. JedisCluster 访问Redis集群

集群环境使用JedisCluster对象,API方法和单机Jedis几乎一致。

java 复制代码
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisCluster;
import java.util.HashSet;
import java.util.Set;

public class JedisClusterDemo {
    public static void main(String[] args) {
        Set<HostAndPort> nodes = new HashSet<>();
        // 添加集群全部节点地址(docker容器内网IP)
        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));

        try(JedisCluster jedisCluster = new JedisCluster(nodes)){
            jedisCluster.set("clusterKey","clusterValue");
            System.out.println(jedisCluster.get("clusterKey"));
        }
    }
}

注意: docker部署的Redis集群使用容器内网IP,Windows本机无法访问这些IP。 必须把项目打包jar,上传Linux服务器运行。

运行命令:java -jar xxx.jar

三、SpringBoot 集成Redis

SpringBoot2.x默认使用Lettuce客户端,线程安全,不需要手动维护连接池;封装后的API和原始Redis命令名字不完全一样,但是语义一一对应。

1. 创建SpringBoot项目

新建SpringBoot工程,勾选两个依赖:

  1. Spring Web:编写HTTP接口方便测试
  2. Spring Data Redis (Access+Driver)

也可以手动在pom.xml引入starter依赖:

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring‑boot‑starter‑web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring‑boot‑starter‑data‑redis</artifactId>
</dependency>

2. 单机Redis配置 application.yml

使用Xshell端口转发,本机8888映射服务器Redis的6379

yaml 复制代码
spring:  
  data:  
    redis:  
      host: 127.0.0.1  
      port: 8888
      # 如果redis设置密码,添加 password: xxx

3. StringRedisTemplate

Spring‑Data‑Redis提供两套模板:

  1. RedisTemplate:默认JDK序列化,key/value会出现乱码;
  2. StringRedisTemplate:是RedisTemplate的子类,全部使用String序列化(开发优先选用这个,避免乱码)。

直接@Autowired注入即可,Spring自动创建Bean。

4. 完整Controller测试代码

java 复制代码
import org.springframework.data.redis.core.*;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.List;
import java.util.Set;

@RestController
public class RedisTestController {

    @Resource
    private StringRedisTemplate redisTemplate;

    // String字符串
    @GetMapping("/testString")
    public String testString(){
        ValueOperations<String, String> ops = redisTemplate.opsForValue();
        ops.set("sKey","sValue");
        String val = ops.get("sKey");
        System.out.println(val);
        redisTemplate.delete("sKey");
        return "ok‑string";
    }

    // List列表
    @GetMapping("/testList")
    public String testList(){
        ListOperations<String,String> ops = redisTemplate.opsForList();
        ops.leftPush("lKey","a");
        ops.leftPushAll("lKey","b","c","d");
        // range 闭区间查询
        List<String> list = ops.range("lKey",1,2);
        System.out.println(list);
        redisTemplate.delete("lKey");
        return "ok‑list";
    }

    // Hash哈希
    @GetMapping("/testHash")
    public String testHash(){
        HashOperations<String,String,String> ops = redisTemplate.opsForHash();
        ops.put("hKey","name","zhangsan");
        String name = ops.get("hKey","name");
        System.out.println(name);
        ops.delete("hKey","name");
        boolean exists = ops.hasKey("hKey","name");
        System.out.println(exists);
        redisTemplate.delete("hKey");
        return "ok‑hash";
    }

    // Set集合
    @GetMapping("/testSet")
    public String testSet(){
        SetOperations<String,String> ops = redisTemplate.opsForSet();
        ops.add("setKey","aaa","bbb","ccc");
        boolean isMember = ops.isMember("setKey", "aaa");
        System.out.println(isMember);
        ops.remove("setKey","aaa");
        Long size = ops.size("setKey");
        System.out.println(size);
        redisTemplate.delete("setKey");
        return "ok‑set";
    }

    // ZSet有序集合
    @GetMapping("/testZSet")
    public String testZSet(){
        ZSetOperations<String,String> ops = redisTemplate.opsForZSet();
        ops.add("zKey","吕布",100);
        ops.add("zKey","赵云",98);
        ops.add("zKey","典韦",95);
        Set<String> zSet = ops.range("zKey",0,2);
        System.out.println(zSet);
        Long count = ops.count("zKey",95,100);
        System.out.println(count);
        redisTemplate.delete("zKey");
        return "ok‑zset";
    }
}

启动SpringBoot,浏览器访问各个接口,查看控制台输出Redis执行结果。

5. SpringBoot连接Redis集群yml配置

yaml 复制代码
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    # 每2000毫秒刷新集群拓扑

配置集群之后,业务Controller代码一行不用修改。 注意:docker集群容器内网IP,Windows本机无法连通,必须把SpringBoot打包jar上传Linux服务器运行。

三、Jedis 和 Lettuce对比总结

特性 Jedis Lettuce(SpringBoot默认)
底层IO BIO阻塞IO Netty NIO非阻塞
线程安全 Jedis实例非线程安全,必须使用连接池 原生线程安全,多线程共用实例
API风格 几乎和Redis命令一模一样,上手快 Spring封装,方法名改变,语义不变
集群支持 JedisCluster SpringDataRedis自动封装,支持拓扑自动刷新
适用场景 学习Redis命令,简单小项目 SpringBoot企业生产项目
相关推荐
郭小李1 小时前
mybatis的 Mapper.xml语法
xml·java·mybatis
小蒜学长1 小时前
基于SpringBoot 框架的自媒体新闻系统的设计与实现(代码+数据库+LW)
java·spring boot·后端·mysql数据库·自媒体新闻管理系统
9624561 小时前
补打一条卡,为什么要从前一天重新算:人工补录与实时重算业务复盘
java·mysql·kafka
Java后端的Ai之路2 小时前
01、Java并发编程:学习攻略如何才能学好并发编程?
java·开发语言·学习
是程序喵呀2 小时前
Redis实现持久化
数据库·redis·缓存
ScilogyHunter2 小时前
Hercules系列安全MCU功能安全原理详解
java·单片机·安全·hercules
余槐i2 小时前
PostgreSQL 加索引后查询反而更慢:优化器弃用索引的 3 种情形
spring boot·redis·mysql·postgresql·事务
我不会起名字3222 小时前
Redis 从 0 到 1(十三):分布式锁被误删、锁不住?Go 无 Lua 实现 3 级防御
redis·go·分布式锁·watch·setnx
博风2 小时前
eclipse中配置maven,以及在本地使用maven。
java·eclipse·maven