文章目录
- 一、认识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。
特点:
-
简单易解析,文本+二进制混合:部分是可读文本,支持传输二进制数据(安全传输图片、字节数组,不会截断)。
-
请求响应模式:客户端发命令 → Redis执行 → 返回RESP格式结果。
-
类型标记:首字符标记数据类型
首字符 类型 含义 +简单字符串 简短非二进制字符串,如 +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:批量字符串,长度4PING:内容
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隧道配置
- 新增本地转发规则:
| 配置项 | 值 | 说明 |
|---|---|---|
| 类型 | 本地(拨出) | 本地程序访问 |
| 源主机 | localhost | 本机 |
| 侦听端口 | 8888 | 本地程序连接这个端口 |
| 目标主机 | localhost | 服务器内部的redis地址 |
| 目标端口 | 6379 | Redis默认端口 |
-
保存配置,保持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工程,勾选两个依赖:
Spring Web:编写HTTP接口方便测试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提供两套模板:
RedisTemplate:默认JDK序列化,key/value会出现乱码;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企业生产项目 |