Redis实战

客户端特点

客户端服务端通信

TCP链接 发送报文

自写客户端

  • 建立 Socket 连接:和 Redis 服务端建立 TCP 连接,获取输入 / 输出流。
  • 发送命令(OutputStream 写入) :按照 RESP 协议的格式,把 Redis 命令(如set/get)拼接成二进制数据,发送给服务端。
  • 接收响应(InputStream 读取):读取 Redis 服务端返回的二进制数据,解析成字符串输出。

客户端初始化构造方法

java 复制代码
public class MyClient {
    private Socket socket;
    private OutputStream write;
    private InputStream read;

    public MyClient(String host, int port) throws IOException {
        // 建立TCP连接
        socket = new Socket(host, port);
        // 获取输出流,用于发送命令
        write = socket.getOutputStream();
        // 获取输入流,用于接收响应
        read = socket.getInputStream();
    }
}
public void set(String key, String val) throws IOException {
    StringBuffer sb = new StringBuffer();
    // 1. 表示这是一个包含3个参数的数组(set命令 + key + value)
    sb.append("*3").append("\r\n");
    // 2. 第一个参数:"SET" 命令
    sb.append("$3").append("\r\n"); // 字符串长度:3
    sb.append("SET").append("\r\n"); // 字符串内容
    // 3. 第二个参数:key(动态长度)
    sb.append("$").append(key.getBytes().length).append("\r\n");
    sb.append(key).append("\r\n");
    // 4. 第三个参数:value(动态长度)
    sb.append("$").append(val.getBytes().length).append("\r\n");
    sb.append(val).append("\r\n");

    // 发送命令到服务端
    write.write(sb.toString().getBytes());
    // 接收响应
    byte[] bytes = new byte[1024];
    read.read(bytes);
    System.out.println(new String(bytes));
}
public void get(String key) throws IOException {
    StringBuffer sb = new StringBuffer();
    // 1. 表示这是一个包含2个参数的数组(get命令 + key)
    sb.append("*2").append("\r\n");
    // 2. 第一个参数:"GET" 命令
    sb.append("$3").append("\r\n");
    sb.append("GET").append("\r\n");
    // 3. 第二个参数:key(动态长度)
    sb.append("$").append(key.getBytes().length).append("\r\n");
    sb.append(key).append("\r\n");

    write.write(sb.toString().getBytes());
    byte[] bytes = new byte[1024];
    read.read(bytes);
    System.out.println(new String(bytes));
}
public static void main(String[] args) throws IOException {
    MyClient client = new MyClient("192.168.44.181", 6379);
    client.set("qingshan", "2673");
    client.get("qingshan");
}

从 Redis 服务端读取数据 ,把读到的二进制数据放进上面的小篮子 bytes 里。

Redis客户端

配置 作用
Jedis A blazingly small and sane redis java client(体系非常小,但是功能很完善)
Lettuce Advanced redis client for thread-safe sync, async, and reactive usage. Supports Cluster, Sentinel, Pipelining, and codecs.(高级客户端,支持线程安全、异步、反应式编程、支持集群、哨兵、pipeline、编解码)
Redisson distributed and scalable Java data structures on top of Redis server(基于 Redis 服务实现的 Java 分布式可扩展的数据结构)

jerdis

连接池类型 适用场景 核心特点
JedisPool 单机 Redis 基础连接池,管理单个 Redis 节点的连接
ShardedJedisPool Redis 分片集群(客户端分片) 基于一致性哈希,支持数据分片存储
JedisSentinelPool Redis 哨兵集群 自动从哨兵节点获取主节点地址,实现主从切换
java 复制代码
public static void ordinaryPool(){
    // 1. 创建连接池,指定 Redis 地址和端口
    JedisPool pool = new JedisPool("192.168.44.181",6379);
    // 2. 从连接池获取 Jedis 实例
    Jedis jedis = pool.getResource();
    // 3. 执行 Redis 命令
    jedis.set("qingshan","平平无奇盆鱼宴");
    System.out.println(jedis.get("qingshan"));
    // 4. 归还连接(try-with-resources 可自动关闭)
    jedis.close();
}
public static void sharedPool() {
    // 1. 配置连接池参数
    JedisPoolConfig poolConfig = new JedisPoolConfig();
    // 2. 定义分片节点信息
    JedisShardInfo shardInfo1 = new JedisShardInfo("192.168.44.181", 6379);
    List<JedisShardInfo> infoList = Arrays.asList(shardInfo1);
    // 3. 创建分片连接池
    ShardedJedisPool jedisPool = new ShardedJedisPool(poolConfig, infoList);
    // 4. 获取分片连接并操作
    ShardedJedis jedis = jedisPool.getResource();
    jedis.set("qingshan","分片测试");
    System.out.println(jedis.get("qingshan"));
    jedis.close();
}
public static void sentinelPool() {
    // 1. 定义主节点名称和哨兵节点地址
    String masterName = "redis-master";
    Set<String> sentinels = new HashSet<>();
    sentinels.add("192.168.44.186:26379");
    sentinels.add("192.168.44.187:26379");
    sentinels.add("192.168.44.188:26379");
    // 2. 创建哨兵连接池
    JedisSentinelPool pool = new JedisSentinelPool(masterName, sentinels);
    // 3. 执行 Redis 命令(自动获取当前主节点连接)
    pool.getResource().set("qingshan", "哨兵" + System.currentTimeMillis() + "盆鱼宴");
    System.out.println(pool.getResource().get("qingshan"));
}
java 复制代码
发布订阅(Pub/Sub)
事务(Transaction)
Lua 脚本
客户端分片
哨兵(Sentinel)
集群(Cluster)
管道(Pipeline)

获取master 分片原理

分布式锁

需求 说明
互斥性 同一时间只有一个客户端能持有锁
防死锁 客户端崩溃后,锁能自动释放
防误释放 只有持有锁的客户端才能释放锁

Redis Pipeline

Redis 是单线程处理命令的,客户端每发一条命令,都要等待响应后才能发下一条,这种「一问一答」模式在批量操作时性能极差:

  • 假设一次网络延迟 1ms,客户端 1 秒最多只能发送 1000 条命令。

  • 批量操作 10 万个 key 时,网络开销会成为性能瓶颈。

  • Pipeline 允许客户端一次性发送多条命令,Redis 批量执行后一次性返回所有结果,大幅减少网络往返次数:

  • 原本 N 条命令需要 N 次网络往返,Pipeline 只需 1 次。

  • 性能提升非常明显,适合批量写入、批量查询场景。

✅ 推荐场景 ❌ 不适合场景
批量写入数据(如初始化缓存、数据迁移) 操作依赖前一条命令的结果(如先 GET 再 DEL)
批量读取数据(如一次查 100 个 key) 需要立即知道命令执行结果是否成功
对结果实时性要求不高的场景 高并发下的单条关键业务操作
  • 命令执行顺序:Pipeline 会按发送顺序执行命令,不会打乱顺序。
  • 内存消耗:客户端缓存命令会占用内存,批量过大时可能导致 OOM。
  • 服务端压力:一次性发送大量命令,会导致 Redis 服务端短时间负载飙升。
  • 非原子性 :Pipeline 只是批量发送命令,不保证原子性(中途出错时前面的命令依然会执行,和事务不同)。
java 复制代码
public void batchSetWithPipeline(Jedis jedis, Map<String, String> data) {
    Pipeline pipeline = jedis.pipelined();
    // 把所有命令缓存起来
    for (Map.Entry<String, String> entry : data.entrySet()) {
        pipeline.set(entry.getKey(), entry.getValue());
    }
    // 一次性发送并执行所有命令
    pipeline.sync();
}

Lettuce

  • 线程安全:完全解决了 Jedis 线程不安全的问题,多个线程可共享同一个连接实例。
  • 多模式支持:支持同步、异步和响应式(Reactive)编程模式。
  • 底层实现:基于 Netty 框架构建,采用非阻塞 I/O,性能高。
  • 高级功能支持:支持 Redis 全部高级特性,包括发布订阅、事务、Lua 脚本、Sentinel、集群、Pipeline 及连接池。
  • 自 2.x 版本起,Lettuce 成为 Spring Boot 默认的 Redis 客户端,替换了 Jedis。
  • 无需手动管理连接,直接通过 RedisTemplate 即可操作 Redis。
  • 依赖配置(Spring Boot 项目中直接引入即可):
  • <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
  • **Lettuce 为什么是线程安全的?**因为它基于 Netty 的连接实例,使用非阻塞 I/O,多个线程可以共享同一个连接而不会出现并发问题。

Redisson

Redisson 是一个基于 Redis 实现的 Java 驻内存数据网格(In-Memory Data Grid),提供了丰富的分布式可扩展 Java 数据结构,比如分布式 Map、List、Queue、Set 等,无需自己额外搭建服务。

  • 高性能:基于 Netty 实现,采用非阻塞 I/O,支持异步请求。
  • 全场景支持:支持连接池、Pipeline、Lua 脚本、Redis Sentinel、Redis 集群,主从、哨兵、集群均兼容。
  • 事务替代方案:不支持 Redis 原生事务,官方建议用 Lua 脚本替代。
  • Spring 集成友好 :可直接配置和注入 RedissonClient

Redisson 内置了多种开箱即用的分布式锁和同步器,大幅降低了分布式场景的开发复杂度:

  • 可重入锁(Reentrant Lock)
  • 公平锁(Fair Lock)
  • 联锁(MultiLock)
  • 红锁(RedLock)
  • 读写锁(ReadWriteLock)
  • 信号量(Semaphore)
  • 可过期性信号量(PermitExpirableSemaphore)
  • 闭锁(CountDownLatch)
特性 Jedis Lettuce Redisson
线程安全 不安全(需连接池) 安全 安全
底层实现 阻塞 I/O Netty 非阻塞 I/O Netty 非阻塞 I/O
编程模式 同步 同步 / 异步 / 响应式 同步 / 异步
Spring Boot 默认版本 1.x 及之前 2.x+ 第三方依赖
分布式支持 需手动实现 基础支持 开箱即用(锁、集合等)
适用场景 简单单机场景 通用高并发场景 复杂分布式场景

Redisson 分布式锁的本质,是用 Redis 的 HASH 数据结构 + Lua 脚本,实现了可重入、防误删、自动续期的分布式锁。

java 复制代码
public static void main(String[] args) throws InterruptedException {
    RLock rLock = redissonClient.getLock("updateAccount");
    // 参数说明:等待时间、锁持有时间、时间单位
    if (rLock.tryLock(100, 10, TimeUnit.SECONDS)) {
        try {
            // 执行业务逻辑
        } finally {
            rLock.unlock();
        }
    }
}

加锁源码:Lua 脚本解析

Redisson 加锁的核心逻辑,是通过一段 Lua 脚本原子执行,避免并发问题:

java 复制代码
-- KEYS[1]: 锁名称
-- ARGV[1]: 锁过期时间(毫秒)
-- ARGV[2]: 线程标识(客户端ID:线程ID)

-- 1. 锁不存在:直接创建锁(HASH 结构,field为线程标识,value为重入次数)
if (redis.call('exists', KEYS[1]) == 0) then
    redis.call('hset', KEYS[1], ARGV[2], 1);
    redis.call('pexpire', KEYS[1], ARGV[1]);
    return nil;
end;

-- 2. 锁已存在,且是当前线程持有:重入次数+1,续期
if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then
    redis.call('hincrby', KEYS[1], ARGV[2], 1);
    redis.call('pexpire', KEYS[1], ARGV[1]);
    return nil;
end;

-- 3. 锁已存在,且不是当前线程持有:返回锁的剩余过期时间
return redis.call('pttl', KEYS[1]);
  • 核心特点:原子执行,整个加锁过程不会被其他命令打断。
  • 可重入实现:用 HASH 结构记录每个线程的重入次数,重入时 hincrby 次数 + 1。

释放锁同样通过 Lua 脚本原子执行,保证「判断 + 删除 + 通知」的原子性:

java 复制代码
-- KEYS[1]: 锁名称
-- KEYS[2]: 释放锁的通知频道
-- ARGV[1]: 释放锁的消息标识
-- ARGV[2]: 锁过期时间
-- ARGV[3]: 线程标识

-- 1. 锁已不存在:直接发布释放通知
if (redis.call('exists', KEYS[1]) == 0) then
    redis.call('publish', KEYS[2], ARGV[1]);
    return 1;
end;

-- 2. 锁存在,但不是当前线程持有:直接返回
if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then
    return nil;
end;

-- 3. 锁存在,且是当前线程持有:重入次数-1
local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1);

-- 4. 重入次数>0:只续期,不删除锁
if (counter > 0) then
    redis.call('pexpire', KEYS[1], ARGV[2]);
    return 0;
else
    -- 5. 重入次数=0:删除锁并发布释放通知
    redis.call('del', KEYS[1]);
    redis.call('publish', KEYS[2], ARGV[1]);
    return 1;
end;
  • 防误删实现:只有 hexists 判断是当前线程持有的锁,才会执行删除操作。
  • 通知机制:锁释放后通过 publish 发布消息,其他等待的线程可以立即尝试获取锁。
  1. 业务没执行完,锁到期了怎么办?

Redisson 提供了 WatchDog(看门狗) 机制:

  • leaseTime 不设置(默认 30 秒)时,看门狗会每隔 10 秒自动续期锁的过期时间。
  • 只有当前线程还持有锁,就会一直续期,直到业务执行完成。
  1. 集群模式下,多个 master 加锁导致重复加锁怎么办?

Redisson 会自动将锁的操作路由到同一个 master 节点,避免跨节点重复加锁。

  1. Redis master 挂了,锁会不会丢失?
  • 主从同步有延迟,极端情况下 master 挂了可能会导致锁丢失。
  • 高可用场景可以使用 Redisson 的 RedLock 红锁算法,向多个独立的 Redis 节点加锁,提高可靠性。
特性 Jedis 手动实现 Redisson 内置实现
可重入 需自己实现(记录线程标识 + 重入次数) 原生支持,HASH 结构自动维护
防误删 需 Lua 脚本保证原子判断 + 删除 内置 Lua 脚本实现
自动续期 需自己实现定时任务续期 WatchDog 自动续期
集群支持 需自己处理节点路由 自动适配哨兵 / 集群模式
锁类型 仅支持简单互斥锁 支持公平锁、读写锁、联锁、红锁等多种类型
相关推荐
眷蓝天7 小时前
OSI七层模型
网络
云安全助手10 小时前
弹性云服务器+高防IP:让DDoS攻击不再是业务“生死劫”
运维·网络·安全
Hello_Embed12 小时前
嵌入式上位机开发入门(二十六):将 MQTT 测试程序加入 APP 任务
网络·笔记·网络协议·tcp/ip·嵌入式
亚空间仓鼠13 小时前
网络学习实例:网络理论知识
网络·学习·智能路由器
上海合宙LuatOS13 小时前
LuatOS扩展库API——【libfota2】远程升级
网络·物联网·junit·luatos
pengyi87101514 小时前
动态IP池快速更换实操方案,5分钟完成IP替换
服务器·网络·tcp/ip
平升电子DATA8614 小时前
地下管网(污水/雨水管网)流量怎么监测?
网络
被摘下的星星14 小时前
以太网技术
服务器·网络
24zhgjx-lxq15 小时前
OSPF的网络类型:NBMA和P2MP
网络·智能路由器·hcip·ensp·ospf