如何在Java中整合Redis?

一、Redis 基础认知:为什么选择 Redis?

1.1 Redis 的核心特性

高性能

  • 内存存储机制:Redis 将数据完全存储在内存中,数据读写操作直接在内存中进行,避免了磁盘I/O的瓶颈。内存读写速度通常是磁盘的100倍以上(内存访问约100ns,SSD约100μs)。
  • 单线程模型:采用单线程处理命令请求,避免了多线程上下文切换和锁竞争的开销,同时通过I/O多路复用技术(如epoll)实现高并发处理。
  • 性能基准 :在标准服务器配置(如8核CPU,16GB内存)下,Redis单节点可达到:
    • 读操作(QPS):10万-15万次/秒
    • 写操作(QPS):8万-12万次/秒
    • 特别优化的场景下甚至可达20万+ QPS

丰富的数据结构

  • String:最基本类型,最大512MB,常用于缓存简单数据或计数器
  • Hash:键值对集合,适合存储对象(如用户信息:user:1001 {name:"张三",age:28})
  • List:双向链表,可实现消息队列(LPUSH+BRPOP)或最新N条记录
  • Set:无序集合,支持交并差运算,适合标签系统
  • Sorted Set:带权重的有序集合,可用于排行榜(ZADD game:score 100 "player1")
  • Bitmap:位图操作,适合签到统计(SETBIT sign:202301 100 1)
  • HyperLogLog:基数统计,误差率0.81%,适合UV统计(PFADD uv:20230101 "user1")

持久化机制

  • RDB(快照):

    • 定时将内存数据生成二进制快照文件(dump.rdb)
    • 配置示例:save 900 1(900秒内至少1次修改触发保存)
    • 优点:文件紧凑,恢复速度快
    • 缺点:可能丢失最后一次快照后的数据
  • AOF(Append Only File):

    • 记录所有写操作命令(类似MySQL的binlog)
    • 提供三种同步策略:
      • always:每次写操作都同步
      • everysec:每秒同步(默认)
      • no:由操作系统决定
    • 优点:数据安全性高,最多丢失1秒数据
    • 缺点:文件较大,恢复速度较慢
  • 混合模式:Redis 4.0+支持RDB+AOF混合持久化,结合两者优势

高可用与分布式

  • 主从复制:

    • 一个主节点(Master)可配置多个从节点(Slave)
    • 从节点异步复制主节点数据
    • 读写分离:写操作走主节点,读操作可分散到从节点
  • 哨兵模式(Sentinel):

    • 由2个以上哨兵节点监控Redis集群状态
    • 自动故障转移:主节点宕机时,自动将从节点提升为主节点
    • 提供配置中心和通知机制
  • Redis Cluster:

    • 官方分布式解决方案(Redis 3.0+)
    • 数据分片存储:16384个哈希槽分配到不同节点
    • 节点间使用Gossip协议通信
    • 支持自动故障转移和请求重定向

多语言支持

  • Java:Jedis、Lettuce、Redisson
  • Python:redis-py
  • Go:go-redis
  • Node.js:ioredis
  • 所有客户端库均遵循Redis协议规范,支持连接池、管道等高级特性

1.2 Redis 在 Java 项目中的典型应用场景

缓存热点数据

  • 实现方式 :

    java 复制代码
    // Spring Cache示例
    @Cacheable(value="products", key="#productId")
    public Product getProduct(String productId) {
        return productDao.findById(productId); 
    }
  • 缓存策略 :

    • 缓存穿透:布隆过滤器或空值缓存
    • 缓存雪崩:随机过期时间
    • 缓存击穿:互斥锁更新

分布式锁

  • 原生实现:

    java 复制代码
    // SETNX实现
    String lockKey = "order_lock_"+orderId;
    String clientId = UUID.randomUUID().toString();
    try {
        Boolean result = redisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 30, TimeUnit.SECONDS);
        if(result) {
            // 执行业务逻辑
        } else {
            // 获取锁失败
        }
    } finally {
        // 使用Lua脚本保证原子性
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), 
                            Collections.singletonList(lockKey), clientId);
    }
  • Redisson实现:

    java 复制代码
    RLock lock = redissonClient.getLock("order_lock_"+orderId);
    try {
        // 尝试加锁,最多等待100秒,上锁后30秒自动解锁
        if(lock.tryLock(100, 30, TimeUnit.SECONDS)) {
            // 执行业务逻辑
        }
    } finally {
        lock.unlock();
    }

计数器与限流

  • 计数器示例:

    java 复制代码
    // 文章阅读量计数
    redisTemplate.opsForValue().increment("article:read:count:"+articleId);
    
    // 获取阅读量
    Long count = redisTemplate.opsForValue().increment("article:read:count:"+articleId, 0);
  • 限流实现(滑动窗口算法):

    java 复制代码
    public boolean isAllowed(String key, int maxCount, int period) {
        long now = System.currentTimeMillis();
        long windowStart = now - period * 1000;
        
        // 删除旧时间戳
        redisTemplate.opsForZSet().removeRangeByScore(key, 0, windowStart);
        
        // 获取当前请求数
        long count = redisTemplate.opsForZSet().zCard(key);
        if(count < maxCount) {
            // 添加当前请求
            redisTemplate.opsForZSet().add(key, String.valueOf(now), now);
            return true;
        }
        return false;
    }

消息队列

  • List实现简单队列:

    java 复制代码
    // 生产者
    redisTemplate.opsForList().leftPush("queue:order", orderJson);
    
    // 消费者
    String orderJson = redisTemplate.opsForList().rightPop("queue:order", 30, TimeUnit.SECONDS);
  • Redis Stream实现(Redis 5.0+):

    java 复制代码
    // 创建消费者组
    redisTemplate.opsForStream().createGroup("order_stream", "order_group");
    
    // 生产者
    Map<String, String> message = new HashMap<>();
    message.put("orderId", "1001");
    message.put("amount", "99.9");
    redisTemplate.opsForStream().add("order_stream", message);
    
    // 消费者
    List<MapRecord<String, String, String>> records = redisTemplate.opsForStream()
        .read(Consumer.from("order_group", "consumer1"),
            StreamReadOptions.empty().count(1).block(Duration.ofSeconds(1)),
            StreamOffset.create("order_stream", ReadOffset.lastConsumed()));

分布式Session

  • Spring Session集成:

    XML 复制代码
    <!-- pom.xml -->
    <dependency>
        <groupId>org.springframework.session</groupId>
        <artifactId>spring-session-data-redis</artifactId>
    </dependency>
    properties 复制代码
    # application.properties
    spring.session.store-type=redis
    spring.session.timeout=1800 # 30分钟
    java 复制代码
    // 自动将Session存储到Redis
    @RestController
    @SessionAttributes("user")
    public class UserController {
        @PostMapping("/login")
        public String login(@RequestParam String username, Model model) {
            model.addAttribute("user", username);
            return "login success";
        }
    }
  • Session共享效果:

    • 用户登录后,Session信息存储在Redis
    • 同一用户的请求可路由到集群中任意节点
    • 各服务通过统一Session ID获取用户状态
    • 默认使用Jackson序列化Session对象

二、环境搭建:Redis 服务与 Java 开发环境准备

2.1 Redis 服务端部署(以 Linux 为例)

步骤 1:下载并解压 Redis

下载 Redis(版本可根据需求选择,此处以 6.2.6 为例)
bash 复制代码
# 下载 Redis 源码包
wget https://download.redis.io/releases/redis-6.2.6.tar.gz

# 验证下载完整性(可选)
wget https://download.redis.io/releases/redis-6.2.6.tar.gz.asc
gpg --verify redis-6.2.6.tar.gz.asc

# 解压 Redis 源码包
tar -zxvf redis-6.2.6.tar.gz

# 进入解压后的目录
cd redis-6.2.6
注意事项:
  • 建议使用稳定版本(stable)而非开发版本
  • 下载前可访问 https://download.redis.io/releases/ 查看最新版本
  • 对于生产环境,建议使用 Redis 6.x 及以上版本以支持多线程处理

步骤 2:编译与安装

bash 复制代码
# 安装编译依赖(CentOS/RHEL)
yum install -y gcc make tcl

# 或 Ubuntu/Debian
apt-get install -y build-essential tcl

# 编译 Redis(此过程可能需要几分钟)
make

# 运行测试(可选,但推荐)
make test

# 安装 Redis 到系统目录
make install
安装结果验证:
  • 默认安装路径:/usr/local/bin

  • 检查是否安装成功:

    bash 复制代码
    ls -l /usr/local/bin/redis-*

步骤 3:配置并启动 Redis

基本配置管理:
bash 复制代码
# 创建 Redis 配置目录
mkdir -p /etc/redis

# 复制配置文件模板
cp redis.conf /etc/redis/redis.conf

# 创建数据存储目录
mkdir -p /var/lib/redis
关键配置文件修改(/etc/redis/redis.conf):
bash 复制代码
# 使用 vim 或其他编辑器修改配置文件
vim /etc/redis/redis.conf

需要修改的重要配置项:

  1. 网络配置:

    conf 复制代码
    # 允许远程访问(注释掉或修改为)
    # bind 127.0.0.1
    bind 0.0.0.0
  2. 安全配置:

    conf 复制代码
    # 关闭保护模式
    protected-mode no
    
    # 设置密码(生产环境必须)
    requirepass your_strong_password
  3. 运行模式:

    conf 复制代码
    # 以守护进程方式运行
    daemonize yes
  4. 日志配置:

    conf 复制代码
    # 指定日志文件路径
    logfile "/var/log/redis/redis-server.log"
  5. 数据持久化(根据需求配置):

    conf 复制代码
    # RDB 持久化配置
    save 900 1
    save 300 10
    save 60 10000
    
    # AOF 持久化配置(可选)
    appendonly yes
    appendfsync everysec
启动 Redis 服务:
bash 复制代码
# 创建日志目录
mkdir -p /var/log/redis
chown -R redis:redis /var/log/redis

# 启动 Redis 服务
redis-server /etc/redis/redis.conf

# 验证服务状态
ps aux | grep redis
netstat -tulnp | grep 6379
连接测试:
bash 复制代码
# 本地连接测试
redis-cli -h 127.0.0.1 -p 6379 -a your_password ping

# 远程连接测试(从其他服务器)
redis-cli -h your_server_ip -p 6379 -a your_password ping
设置开机自启(可选):
bash 复制代码
# 创建 systemd 服务文件
vim /etc/systemd/system/redis.service

添加以下内容:

ini 复制代码
[Unit]
Description=Redis In-Memory Data Store
After=network.target

[Service]
User=redis
Group=redis
ExecStart=/usr/local/bin/redis-server /etc/redis/redis.conf
ExecStop=/usr/local/bin/redis-cli shutdown
Restart=always

[Install]
WantedBy=multi-user.target

然后执行:

bash 复制代码
# 重新加载 systemd
systemctl daemon-reload

# 启用开机自启
systemctl enable redis

# 启动服务
systemctl start redis

# 检查状态
systemctl status redis

2.2 Java 开发环境配置

客户端选择说明

Java 操作 Redis 主要有两种主流客户端:

  1. Jedis:

    • 官方推荐的轻量级客户端
    • 直接操作 Redis 命令
    • 支持连接池
    • 适合简单场景和性能要求高的场景
  2. Redisson:

    • 基于 Netty 实现
    • 提供分布式对象和服务
    • 内置分布式锁、原子操作等高级功能
    • 适合复杂分布式场景

2.2.1 Maven 依赖配置

Jedis 依赖配置(推荐使用连接池):
XML 复制代码
<!-- Jedis 核心依赖 -->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>4.3.1</version>
</dependency>

<!-- 连接池依赖(必须) -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
    <version>2.11.1</version>
</dependency>

<!-- 可选:JSON 序列化支持 -->
<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>2.9.0</version>
</dependency>
Redisson 依赖配置:
XML 复制代码
<!-- Redisson 核心依赖 -->
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.17.6</version>
</dependency>

<!-- 可选:配置支持(如 JSON/YAML 配置文件) -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.13.3</version>
</dependency>
版本选择建议:

开发环境验证

创建简单的测试类验证环境是否配置成功:

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

public class RedisTest {
    public static void main(String[] args) {
        // 创建连接
        Jedis jedis = new Jedis("localhost", 6379);
        jedis.auth("your_password");  // 如果有密码
        
        // 测试连接
        System.out.println("连接状态:" + jedis.ping());
        
        // 基本操作
        jedis.set("test_key", "Hello Redis");
        System.out.println("获取值:" + jedis.get("test_key"));
        
        // 关闭连接
        jedis.close();
    }
}

IDE 配置建议

  1. IntelliJ IDEA:

    • 确保 Maven 项目正确导入依赖
    • 安装 Redis 插件(如 "Redis" 插件)方便调试
  2. Eclipse:

    • 使用 M2Eclipse 插件管理依赖
    • 可安装 Redis 客户端插件(如 "RedisClipse")

生产环境注意事项

  1. 连接池配置:

    • 必须使用连接池管理连接

    • 推荐配置参数:

      java 复制代码
      JedisPoolConfig poolConfig = new JedisPoolConfig();
      poolConfig.setMaxTotal(128);       // 最大连接数
      poolConfig.setMaxIdle(32);         // 最大空闲连接
      poolConfig.setMinIdle(8);          // 最小空闲连接
      poolConfig.setTestOnBorrow(true);  // 获取连接时测试
  2. 安全建议:

    • 不要将密码硬编码在代码中
    • 使用配置中心或环境变量管理敏感信息
    • 启用 SSL/TLS 加密传输(Redis 6+支持)
  3. 性能优化:

    • 批量操作使用 pipeline
    • 大 value 考虑分片存储
    • 合理选择序列化方式

三、Jedis 客户端:Java 操作 Redis 的基础实现

3.1 基本使用:单实例连接

步骤 1:创建 Jedis 实例并连接 Redis

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

public class JedisBasicDemo {

    public static void main(String[] args) {
        // 1. 创建Jedis实例(参数说明:Redis地址、端口、连接超时时间(毫秒)、密码)
        // 注意:如果Redis服务器在同一台机器,地址可以是"localhost"
        Jedis jedis = new Jedis("192.168.1.100", 6379, 5000); 
        
        try {
            // 2. 验证密码(若Redis未设置密码,可跳过此步骤)
            // 如果密码错误,会抛出redis.clients.jedis.exceptions.JedisDataException
            jedis.auth("your_password");
            
            // 3. 测试连接
            // ping()返回"PONG"表示连接正常
            System.out.println("Redis连接状态:" + jedis.ping()); // 期望输出:PONG
            
            // 4. 操作String类型数据
            jedis.set("username", "zhangsan"); // 存入数据,默认永不过期
            // 带过期时间的设置(单位秒)
            jedis.setex("temp_token", 3600, "abcd1234"); 
            
            String username = jedis.get("username"); // 获取数据
            System.out.println("获取到的用户名:" + username); // 输出:zhangsan
            
            // 5. 操作Hash类型数据
            // 存储用户信息
            jedis.hset("user:1001", "name", "lisi");
            jedis.hset("user:1001", "age", "25");
            jedis.hset("user:1001", "gender", "male");
            
            // 获取Hash中所有字段和值(返回Map<String,String>)
            System.out.println("用户1001信息:" + jedis.hgetAll("user:1001"));
            // 输出:{name=lisi, age=25, gender=male}
            
            // 获取单个字段
            System.out.println("用户年龄:" + jedis.hget("user:1001", "age")); // 输出:25
            
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 6. 关闭连接(重要!避免连接泄漏)
            if (jedis != null) {
                jedis.close();
            }
        }
    }
}

注意事项

  1. 线程安全性:

    • Jedis实例是非线程安全的,每个线程应该使用自己的Jedis实例
    • 在高并发场景中直接使用单例连接会导致线程安全问题
  2. 性能考量:

    • 每次操作都需要创建和关闭TCP连接,频繁操作时会产生较大性能开销
    • 实测表明,在100次连续操作中,单实例连接方式耗时是连接池方式的10倍以上
  3. 实际应用建议:

    • 开发环境或简单脚本可以使用单实例连接
    • 生产环境必须使用连接池(JedisPool)管理连接
    • 对于长时间运行的任务,建议使用try-with-resources确保连接关闭
  4. 异常处理:

    • 网络中断时可能抛出JedisConnectionException
    • 认证失败抛出JedisDataException
    • 建议对关键操作添加重试机制

3.2 进阶使用:Jedis 连接池

步骤 1:配置 Jedis 连接池

java 复制代码
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

public class JedisPoolDemo {
    
    // 初始化Jedis连接池(使用静态代码块确保只初始化一次)
    private static JedisPool jedisPool;
    
    static {
        // 1. 配置连接池参数
        GenericObjectPoolConfig<Jedis> poolConfig = new GenericObjectPoolConfig<>();
        
        // 关键参数配置(根据实际业务需求调整)
        poolConfig.setMaxTotal(100);       // 最大连接数(根据Redis服务器配置调整)
        poolConfig.setMaxIdle(20);         // 最大空闲连接数(建议设为MaxTotal的20%-50%)
        poolConfig.setMinIdle(5);          // 最小空闲连接数(保持一定数量的热连接)
        poolConfig.setBlockWhenExhausted(true); // 连接耗尽时是否阻塞等待(建议true)
        poolConfig.setMaxWaitMillis(3000); // 获取连接最大等待时间(毫秒)
        poolConfig.setTestOnBorrow(true);  // 获取连接时是否测试有效性(建议true,但会有性能损耗)
        poolConfig.setTestOnReturn(false); // 归还连接时是否测试(建议false)
        poolConfig.setTestWhileIdle(true); // 空闲时是否测试(建议true)
        
        // 2. 初始化连接池
        String redisHost = "192.168.1.100";
        int redisPort = 6379;
        int timeout = 5000; // 连接超时时间(毫秒)
        String password = "your_password";
        
        jedisPool = new JedisPool(
            poolConfig, 
            redisHost, 
            redisPort, 
            timeout, 
            password
        );
    }
    
    // 从连接池获取Jedis实例
    public static Jedis getJedis() {
        return jedisPool.getResource();
    }
    
    // 测试连接池使用
    public static void main(String[] args) {
        // 使用try-with-resources语法自动管理连接
        try (Jedis jedis = getJedis()) {
            // 1. 操作String类型
            jedis.set("product:1001", "iPhone 13");
            System.out.println("商品1001名称:" + jedis.get("product:1001"));
            
            // 2. 操作List类型(模拟消息队列)
            jedis.lpush("message:queue", "msg1", "msg2", "msg3"); // 左推(入队)
            
            // 右拉(出队),0表示无限期阻塞等待
            String msg = jedis.brpop(0, "message:queue").get(1); 
            System.out.println("消费的消息:" + msg);
            
            // 3. 批量操作(使用pipeline提升性能)
            long start = System.currentTimeMillis();
            for (int i = 0; i < 1000; i++) {
                jedis.set("key_" + i, "value_" + i);
            }
            long end = System.currentTimeMillis();
            System.out.println("普通操作耗时:" + (end - start) + "ms");
            
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

核心优势

  1. 性能优化:

    • 连接复用减少TCP三次握手开销
    • 实测在1000次操作中,连接池方式比单实例快8-10倍
    • 支持Pipeline批量操作,进一步提升吞吐量
  2. 资源管理:

    • 自动维护连接池中的活跃连接数
    • 可配置连接最大存活时间(maxAge)避免长时间使用的连接出现问题
    • 通过evictor线程定期检测和清理无效连接
  3. 生产环境配置建议:

    java 复制代码
    // 典型生产环境配置
    poolConfig.setMaxTotal(200);       // 根据QPS估算,一般(平均QPS*平均耗时(ms))/1000
    poolConfig.setMaxIdle(50);         // 根据业务波动情况调整
    poolConfig.setMinIdle(10);         // 保持一定热连接
    poolConfig.setMaxWaitMillis(1000); // 根据系统容忍度设置
    poolConfig.setTestOnBorrow(true);  // 确保获取的连接可用
    poolConfig.setTimeBetweenEvictionRunsMillis(30000); // 30秒检测一次
  4. 监控指标:

    • 可通过JMX监控连接池状态
    • 关键指标:活动连接数、空闲连接数、等待获取连接的线程数等
  5. 最佳实践:

    • 每个应用使用独立的连接池
    • 根据业务类型(读写比例、命令复杂度)配置不同连接池
    • 定期监控连接池状态,及时调整参数

3.3 Jedis 操作核心数据结构

(1)Set 类型(无序、唯一)

java 复制代码
try (Jedis jedis = JedisPoolDemo.getJedis()) {
    // 1. 添加元素(自动去重)
    jedis.sadd("tags:java", "spring", "mybatis", "redis");
    jedis.sadd("tags:java", "spring"); // 重复元素不会被添加
    
    // 2. 获取所有元素(无序)
    System.out.println("Java相关标签:" + jedis.smembers("tags:java")); 
    // 输出可能是:[redis, spring, mybatis]
    
    // 3. 判断元素是否存在
    System.out.println("是否包含spring:" + jedis.sismember("tags:java", "spring")); 
    // 输出:true
    
    // 4. 移除元素
    jedis.srem("tags:java", "mybatis");
    
    // 5. 计算集合大小
    System.out.println("标签数量:" + jedis.scard("tags:java")); // 输出:2
    
    // 6. 集合运算
    jedis.sadd("tags:backend", "spring", "django", "redis");
    
    // 交集
    System.out.println("交集:" + jedis.sinter("tags:java", "tags:backend"));
    // 输出:[spring, redis]
    
    // 并集
    System.out.println("并集:" + jedis.sunion("tags:java", "tags:backend"));
    // 输出:[spring, redis, django]
    
    // 差集(tags:java有而tags:backend没有的)
    System.out.println("差集:" + jedis.sdiff("tags:java", "tags:backend"));
    // 输出:[]
}

(2)Sorted Set 类型(有序、唯一,基于分数排序)

java 复制代码
try (Jedis jedis = JedisPoolDemo.getJedis()) {
    // 1. 添加元素(如果成员已存在,则更新分数)
    jedis.zadd("rank:java", 95, "zhangsan");
    jedis.zadd("rank:java", 88, "lisi");
    jedis.zadd("rank:java", 92, "wangwu");
    jedis.zadd("rank:java", 90, "lisi"); // 更新lisi的分数
    
    // 2. 按分数升序获取(0到-1表示全部)
    System.out.println("升序排名:" + jedis.zrange("rank:java", 0, -1));
    // 输出:[lisi, wangwu, zhangsan]
    
    // 3. 按分数降序获取(带分数)
    Set<Tuple> results = jedis.zrevrangeWithScores("rank:java", 0, -1);
    results.forEach(tuple -> 
        System.out.println(tuple.getElement() + ":" + tuple.getScore())
    );
    // 输出:
    // zhangsan:95.0
    // wangwu:92.0
    // lisi:90.0
    
    // 4. 获取分数
    System.out.println("lisi的分数:" + jedis.zscore("rank:java", "lisi"));
    // 输出:90.0
    
    // 5. 获取排名(从0开始)
    System.out.println("zhangsan的降序排名:" + jedis.zrevrank("rank:java", "zhangsan"));
    // 输出:0
    
    // 6. 范围查询(80<=score<=90)
    System.out.println("80-90分的学生:" + 
        jedis.zrangeByScoreWithScores("rank:java", 80, 90));
    // 输出:[lisi:90.0, wangwu:92.0](注意包含边界)
    
    // 7. 原子操作:增加分数
    jedis.zincrby("rank:java", 5, "lisi"); // lisi分数+5
    System.out.println("新分数:" + jedis.zscore("rank:java", "lisi"));
    // 输出:95.0
}

(3)其他数据结构操作

java 复制代码
// Geo地理位置操作
try (Jedis jedis = JedisPoolDemo.getJedis()) {
    // 添加地理位置(经度、纬度、名称)
    jedis.geoadd("cities", 116.404, 39.915, "beijing");
    jedis.geoadd("cities", 121.474, 31.230, "shanghai");
    
    // 计算两地距离(单位:km)
    Double dist = jedis.geodist("cities", "beijing", "shanghai", GeoUnit.KM);
    System.out.println("北京到上海距离:" + dist + "km");
    
    // 查找附近位置
    List<GeoRadiusResponse> nearby = jedis.georadiusByMember(
        "cities", 
        "beijing", 
        1000, 
        GeoUnit.KM
    );
    nearby.forEach(r -> System.out.println(r.getMemberByString()));
}

// HyperLogLog基数统计
try (Jedis jedis = JedisPoolDemo.getJedis()) {
    // 添加元素(用于统计不重复元素数量)
    for (int i = 0; i < 10000; i++) {
        jedis.pfadd("visitors", "user_" + i);
        jedis.pfadd("visitors", "user_" + (i % 5000)); // 50%重复
    }
    
    // 获取基数估计值
    System.out.println("独立访客数:" + jedis.pfcount("visitors"));
    // 输出约7500(误差率约0.81%)
}

四、Redisson 客户端:Java 操作 Redis 的高级实现

Redisson是基于Jedis的封装,不仅提供了更简洁易用的API,还实现了大量Redis官方推荐的分布式特性。相比原生Jedis,Redisson具有以下核心优势:

  1. 线程安全:内置连接池管理,彻底解决Jedis线程安全问题
  2. 丰富特性:支持分布式锁(可重入锁、公平锁、联锁等)、分布式集合(Set、Map、List)、延迟队列、布隆过滤器等20+种分布式对象
  3. 协议支持:完整支持Redis 5.0+的所有命令和数据类型
  4. 容错机制:自动重连、故障转移、读写分离等企业级特性

4.1 Redisson客户端初始化

Redisson支持多种Redis部署模式,每种模式的配置方式如下:

单点模式配置示例

java 复制代码
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;

/**
 * Redisson客户端工厂类(推荐单例模式)
 * 典型应用场景:Spring Bean管理、静态工具类等
 */
public class RedissonClientFactory {
    
    // 使用volatile保证多线程可见性
    private static volatile RedissonClient redissonClient;
    
    // 双重检查锁实现线程安全初始化
    public static RedissonClient getRedissonClient() {
        if (redissonClient == null) {
            synchronized (RedissonClientFactory.class) {
                if (redissonClient == null) {
                    // 1. 创建配置构建器
                    Config config = new Config();
                    
                    // 2. 配置单点模式(生产环境建议使用YAML/JSON配置文件)
                    config.useSingleServer()
                          .setAddress("redis://192.168.1.100:6379") // 支持rediss://协议(SSL加密)
                          .setPassword("your_password")             // 密码特殊字符需URL编码
                          .setDatabase(0)                           // 默认DB索引
                          .setConnectionPoolSize(100)               // 最大连接数(默认64)
                          .setConnectionMinimumIdleSize(10)         // 最小空闲连接(默认10)
                          .setConnectTimeout(5000)                  // 连接超时(毫秒)
                          .setIdleConnectionTimeout(30000)          // 空闲连接超时
                          .setRetryAttempts(3)                      // 命令重试次数
                          .setRetryInterval(1000);                  // 重试间隔
                    
                    // 3. 创建客户端实例(实际会启动连接池和定时任务)
                    redissonClient = Redisson.create(config);
                }
            }
        }
        return redissonClient;
    }
    
    // 优雅关闭方法
    public static void shutdown() {
        if (redissonClient != null) {
            redissonClient.shutdown();
        }
    }
}

其他部署模式配置示例

哨兵模式
java 复制代码
config.useSentinelServers()
      .addSentinelAddress("redis://sentinel1:26379")
      .addSentinelAddress("redis://sentinel2:26379")
      .setMasterName("mymaster");
集群模式
java 复制代码
config.useClusterServers()
      .addNodeAddress("redis://node1:6379")
      .addNodeAddress("redis://node2:6379")
      .setScanInterval(2000); // 集群状态扫描间隔

最佳实践建议

  1. 连接池配置:根据QPS调整连接数(建议公式:最大连接数 = QPS × 平均响应时间(秒))
  2. 超时设置:网络不稳定的环境适当增大超时时间
  3. 资源释放 :应用关闭时调用shutdown()释放资源
  4. 配置分离:生产环境建议使用外部配置文件(JSON/YAML格式)

4.2 Redisson 操作核心数据结构

Redisson 是一个基于 Redis 的 Java 客户端,它通过 RedissonClient 提供了对应 Redis 数据结构的封装类,使得操作 Redis 更加面向对象和简洁。Redisson 支持 Redis 的所有核心数据结构,包括 String、Hash、List、Set 和 Sorted Set 等,每种数据结构都有对应的 Java 接口实现。

(1)String 类型(RBucket)

RBucket 是 Redisson 对 Redis String 类型的封装,提供了一系列操作字符串的方法。RBucket 支持设置过期时间,适用于缓存场景。

java 复制代码
import org.redisson.api.RBucket;
import org.redisson.api.RedissonClient;

public class RedissonStringDemo {
    public static void main(String[] args) {
        // 获取 RedissonClient 实例
        RedissonClient redissonClient = RedissonClientFactory.getRedissonClient();
        try {
            // 获取 RBucket 对象(对应 Redis 的 String 类型)
            RBucket<String> usernameBucket = redissonClient.getBucket("username");
            
            // 存入数据,并设置过期时间为 3600 秒(1小时)
            usernameBucket.set("zhangsan", 3600);
            
            // 获取数据
            String username = usernameBucket.get();
            System.out.println("用户名:" + username); // 输出:zhangsan
            
            // 判断 key 是否存在
            System.out.println("是否存在:" + usernameBucket.isExists()); // 输出:true
            
            // 删除数据
            usernameBucket.delete();
            System.out.println("删除后是否存在:" + usernameBucket.isExists()); // 输出:false
        } finally {
            // 关闭客户端(若为长期运行的服务,无需频繁关闭)
            redissonClient.shutdown();
        }
    }
}

(2)Hash 类型(RMap)

RMap 是 Redisson 对 Redis Hash 类型的封装,提供类似 Java Map 的操作方法,适用于存储对象或结构化数据。

java 复制代码
import org.redisson.api.RMap;
import org.redisson.api.RedissonClient;

public class RedissonHashDemo {
    public static void main(String[] args) {
        RedissonClient redissonClient = RedissonClientFactory.getRedissonClient();
        try {
            // 获取 RMap 对象(对应 Redis 的 Hash 类型)
            RMap<String, String> userMap = redissonClient.getMap("user:1001");
            
            // 存入数据
            userMap.put("name", "lisi");
            userMap.put("age", "25");
            userMap.put("gender", "male");
            
            // 获取数据
            System.out.println("用户姓名:" + userMap.get("name")); // 输出:lisi
            System.out.println("用户所有信息:" + userMap); // 输出:{name=lisi, age=25, gender=male}
            
            // 删除字段
            userMap.remove("gender");
            System.out.println("删除 gender 后的信息:" + userMap); // 输出:{name=lisi, age=25}
            
            // 判断字段是否存在
            System.out.println("是否包含 age:" + userMap.containsKey("age")); // 输出:true
        } finally {
            // 关闭客户端
            redissonClient.shutdown();
        }
    }
}

(3)List 类型(RList)

RList 是 Redisson 对 Redis List 类型的封装,支持双向添加、删除和获取元素,适用于队列或栈的场景。

java 复制代码
import org.redisson.api.RList;
import org.redisson.api.RedissonClient;

public class RedissonListDemo {
    public static void main(String[] args) {
        RedissonClient redissonClient = RedissonClientFactory.getRedissonClient();
        try {
            // 获取 RList 对象(对应 Redis 的 List 类型)
            RList<String> messageList = redissonClient.getList("message:queue");
            
            // 添加元素(右添加,等同于 RPUSH)
            messageList.add("msg1");
            
            // 左添加(等同于 LPUSH)
            messageList.add(0, "msg0");
            
            // 获取指定索引元素
            System.out.println("索引 0 的元素:" + messageList.get(0)); // 输出:msg0
            
            // 获取所有元素
            System.out.println("所有消息:" + messageList); // 输出:[msg0, msg1]
            
            // 移除并返回最后一个元素(等同于 RPOP)
            String lastMsg = messageList.remove(messageList.size() - 1);
            System.out.println("移除的最后一条消息:" + lastMsg); // 输出:msg1
            
            // 获取列表长度
            System.out.println("列表长度:" + messageList.size()); // 输出:1
        } finally {
            redissonClient.shutdown();
        }
    }
}

(4)Set 类型(RSet)

RSet 是 Redisson 对 Redis Set 类型的封装,支持元素的添加、删除和集合运算,适用于标签或唯一值存储场景。

java 复制代码
import org.redisson.api.RSet;
import org.redisson.api.RedissonClient;

public class RedissonSetDemo {
    public static void main(String[] args) {
        RedissonClient redissonClient = RedissonClientFactory.getRedissonClient();
        try {
            // 获取 RSet 对象(对应 Redis 的 Set 类型)
            RSet<String> tagSet = redissonClient.getSet("tags:java");
            
            // 添加元素
            tagSet.add("spring");
            tagSet.add("mybatis");
            tagSet.add("redis");
            
            // 判断元素是否存在
            System.out.println("是否包含 spring:" + tagSet.contains("spring")); // 输出:true
            
            // 获取所有元素
            System.out.println("Java 标签集合:" + tagSet); // 输出:[spring, mybatis, redis]
            
            // 创建另一个 Set
            RSet<String> backendTagSet = redissonClient.getSet("tags:backend");
            backendTagSet.add("spring");
            backendTagSet.add("django");
            backendTagSet.add("redis");
            
            // 计算并返回交集
            System.out.println("Java 与 Backend 标签交集:" + tagSet.retainAll(backendTagSet));
            // 输出:[spring, redis](retainAll 方法会保留交集元素并返回)
            
            // 获取集合大小
            System.out.println("交集大小:" + tagSet.size()); // 输出:2
        } finally {
            redissonClient.shutdown();
        }
    }
}

(5)Sorted Set 类型(RSortedSet)

RSortedSet 是 Redisson 对 Redis Sorted Set 类型的封装,支持按分数排序和范围查询,适用于排行榜或优先级队列场景。

java 复制代码
import org.redisson.api.RSortedSet;
import org.redisson.api.RedissonClient;
import org.redisson.api.ScoredEntry;
import java.util.Collection;

public class RedissonSortedSetDemo {
    public static void main(String[] args) {
        RedissonClient redissonClient = RedissonClientFactory.getRedissonClient();
        try {
            // 获取 RSortedSet 对象(对应 Redis 的 Sorted Set 类型)
            RSortedSet<String> rankSet = redissonClient.getSortedSet("rank:java");
            
            // 添加元素(指定分数)
            rankSet.add(95, "zhangsan");
            rankSet.add(88, "lisi");
            rankSet.add(92, "wangwu");
            
            // 按分数升序获取元素
            System.out.println("成绩升序排名:" + rankSet); // 输出:[lisi, wangwu, zhangsan]
            
            // 按分数降序获取元素(含分数)
            Collection<ScoredEntry<String>> scoredEntries = rankSet.entryRangeReversed(0, -1);
            for (ScoredEntry<String> entry : scoredEntries) {
                System.out.println(entry.getValue() + ":" + entry.getScore());
                // 输出:
                // zhangsan:95.0
                // wangwu:92.0
                // lisi:88.0
            }
            
            // 获取指定元素的排名(降序)
            System.out.println("zhangsan 的排名:" + rankSet.revRank("zhangsan")); // 输出:0
            
            // 获取分数范围内的元素
            Collection<String> range = rankSet.valueRange(90, true, 95, true);
            System.out.println("分数在 90~95 之间的学生:" + range); // 输出:[wangwu, zhangsan]
        } finally {
            redissonClient.shutdown();
        }
    }
}

4.3 Redisson 高级特性:分布式锁与延迟队列

4.3.1 分布式锁(RLock)

在分布式系统中,多个服务实例可能同时操作同一资源(如订单创建、库存扣减等场景),分布式锁可避免并发问题,保证数据一致性。Redisson 的RLock实现了与Java的ReentrantLock类似的可重入锁特性,且支持自动过期释放机制,无需手动处理死锁问题。

核心特性
  1. 可重入性:同一线程可以多次获取同一把锁
  2. 自动续期:通过watchdog机制自动延长锁持有时间
  3. 公平锁支持:可配置为公平锁模式
  4. 锁等待超时:避免线程无限期等待
  5. 锁释放保障:通过finally块确保锁释放
典型应用场景
  • 秒杀系统中的库存扣减
  • 支付系统中的订单状态变更
  • 分布式定时任务调度
  • 重要数据的并发修改
java 复制代码
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import java.util.concurrent.TimeUnit;

public class RedissonDistributedLockDemo {
    // 锁的key命名规范:业务类型:业务ID(如订单创建锁)
    private static final String LOCK_KEY = "lock:order:create"; 

    public static void createOrder(RedissonClient redissonClient) {
        // 1. 获取分布式锁实例(非阻塞)
        RLock lock = redissonClient.getLock(LOCK_KEY);
        
        try {
            // 2. 尝试获取锁(参数说明)
            // - waitTime: 获取锁的最大等待时间(建议设置短于业务超时时间)
            // - leaseTime: 锁自动释放时间(业务完成前应足够长)
            // - TimeUnit: 时间单位
            boolean isLocked = lock.tryLock(3, 30, TimeUnit.SECONDS);
            
            if (isLocked) {
                try {
                    // 3. 加锁成功,执行业务逻辑
                    System.out.println(Thread.currentThread().getName() + "获取分布式锁成功,开始创建订单...");
                    
                    // 模拟业务处理耗时(如数据库操作、远程调用等)
                    Thread.sleep(5000);
                    
                    System.out.println("订单创建完成!");
                } finally {
                    // 4. 确保业务完成后再释放锁
                    if (lock.isHeldByCurrentThread()) {
                        lock.unlock();
                        System.out.println("分布式锁已释放!");
                    }
                }
            } else {
                // 5. 获取锁失败处理(可记录日志、返回友好提示或重试)
                System.out.println("获取分布式锁失败,请稍后再试!");
                // 可抛出特定异常或返回错误码
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            System.err.println("线程被中断:" + e.getMessage());
        } catch (Exception e) {
            System.err.println("业务处理异常:" + e.getMessage());
        }
    }

    public static void main(String[] args) {
        RedissonClient redissonClient = RedissonClientFactory.getRedissonClient();
        
        // 模拟3个并发线程尝试获取锁
        for (int i = 0; i < 3; i++) {
            new Thread(() -> createOrder(redissonClient), "Thread-"+i).start();
        }
        
        // 注意:实际应用中RedissonClient应该是单例长期存在
        // redissonClient.shutdown();
    }
}
最佳实践
  1. 锁命名规范 :使用业务相关的有意义的名称,如lock:order:{orderId}
  2. 锁超时设置:leaseTime应大于预估的业务处理时间
  3. 异常处理:确保在finally块中释放锁
  4. 避免长时间持有锁:锁持有时间应尽量短
  5. 重试机制:获取锁失败时应有合理的重试策略

4.3.2 延迟队列(RDelayedQueue)

延迟队列可实现"消息延迟一段时间后再被消费"的场景,如订单超时未支付自动取消、定时任务触发、预约提醒等。Redisson的RDelayedQueue基于Redis的Sorted Set实现,提供了简单易用的API,避免了自行实现延迟队列的复杂性。

核心特性
  1. 精确延时:支持毫秒级延迟精度
  2. 持久化存储:消息不会因服务重启而丢失
  3. 高可用:基于Redis集群保证可靠性
  4. 多消费者支持:多个服务实例可以同时消费
  5. 消息顺序保障:按延迟时间顺序消费消息
典型应用场景
  • 电商订单超时自动取消(30分钟未支付)
  • 会议开始前15分钟提醒
  • 延时任务调度
  • 红包24小时未领取自动退回
java 复制代码
import org.redisson.api.RBlockingQueue;
import org.redisson.api.RDelayedQueue;
import org.redisson.api.RedissonClient;
import java.util.concurrent.TimeUnit;

public class RedissonDelayedQueueDemo {
    // 延迟队列命名规范:业务类型:业务目的
    private static final String DELAY_QUEUE_KEY = "delay:queue:order"; 
    private static final String BLOCKING_QUEUE_KEY = "blocking:queue:order";

    /**
     * 生产者:添加延迟消息
     * @param redissonClient Redisson客户端
     * @param message 消息内容(建议包含业务ID)
     * @param delayTime 延迟时间(单位:秒)
     */
    public static void produceDelayMessage(RedissonClient redissonClient, String message, long delayTime) {
        // 1. 获取阻塞队列(底层实际存储结构)
        RBlockingQueue<String> blockingQueue = redissonClient.getBlockingQueue(BLOCKING_QUEUE_KEY);
        
        // 2. 创建延迟队列(与阻塞队列关联)
        try (RDelayedQueue<String> delayedQueue = redissonClient.getDelayedQueue(blockingQueue)) {
            // 3. 添加延迟消息
            delayedQueue.offer(message, delayTime, TimeUnit.SECONDS);
            
            System.out.println("[" + System.currentTimeMillis() + "] 添加延迟消息:" + message 
                + ",延迟时间:" + delayTime + "秒");
        } // 自动关闭延迟队列(Java 7+ try-with-resources)
    }

    /**
     * 消费者:持续消费延迟消息
     * @param redissonClient Redisson客户端
     */
    public static void consumeDelayMessage(RedissonClient redissonClient) {
        // 1. 获取阻塞队列(与生产者使用的相同)
        RBlockingQueue<String> blockingQueue = redissonClient.getBlockingQueue(BLOCKING_QUEUE_KEY);
        
        System.out.println("消费者启动,等待消费延迟消息...");
        
        while (!Thread.currentThread().isInterrupted()) {
            try {
                // 2. 阻塞获取消息(当有消息到期时自动唤醒)
                String message = blockingQueue.take();
                
                // 3. 处理消息
                long currentTime = System.currentTimeMillis();
                System.out.println("[" + currentTime + "] 消费延迟消息:" + message);
                
                // 根据消息内容处理业务
                if (message != null && message.startsWith("order:")) {
                    String[] parts = message.split(":");
                    if (parts.length >= 2) {
                        String orderId = parts[1];
                        System.out.println("处理订单超时:订单" + orderId + "超时未支付,已自动取消!");
                        // 实际业务中此处会调用订单服务取消订单
                    }
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                System.err.println("消费者线程被中断");
                break;
            } catch (Exception e) {
                System.err.println("消费消息异常:" + e.getMessage());
                // 可添加重试或错误处理逻辑
            }
        }
    }

    public static void main(String[] args) {
        RedissonClient redissonClient = RedissonClientFactory.getRedissonClient();
        
        // 启动消费者线程(实际应用中通常是独立服务)
        Thread consumerThread = new Thread(() -> consumeDelayMessage(redissonClient));
        consumerThread.setDaemon(true);
        consumerThread.start();
        
        // 模拟生产3个订单延迟消息
        produceDelayMessage(redissonClient, "order:10001", 5);  // 5秒后超时
        produceDelayMessage(redissonClient, "order:10002", 10); // 10秒后超时
        produceDelayMessage(redissonClient, "order:10003", 15); // 15秒后超时
        
        // 保持主线程运行(演示用)
        try {
            Thread.sleep(20000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        
        // 实际应用中不需要关闭(长期运行的服务)
        // redissonClient.shutdown();
    }
}
最佳实践
  1. 消息设计:消息内容应包含足够业务信息(如订单ID)
  2. 异常处理:消费者需处理各种异常情况
  3. 幂等性:消息处理应保证幂等,防止重复消费
  4. 监控报警:对消费延迟和堆积情况监控
  5. 资源释放:正确关闭延迟队列实例
性能优化建议
  1. 批量消费:可适当批量获取消息减少网络开销
  2. 分区设计:大流量场景可按业务ID分多个队列
  3. 消费并行度:根据业务需求调整消费者数量
  4. 消息压缩:大消息可考虑压缩后再存储

五、Spring Boot 整合 Redis:企业级项目实战

在 Spring Boot 项目中,可通过 spring-boot-starter-data-redis 快速整合 Redis,简化配置和开发。该 starter 默认使用 Spring Data Redis 抽象层,支持 Jedis、Lettuce(Spring Boot 2.x 默认客户端)等底层客户端。Spring Data Redis 提供了统一的 API 来操作 Redis,支持多种数据结构的操作,包括字符串、哈希、列表、集合等。

5.1 环境配置

5.1.1 添加 Maven 依赖

在 pom.xml 中添加以下依赖:

XML 复制代码
<!-- Spring Boot Redis Starter -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <!-- 排除默认的Lettuce客户端(若需使用Jedis,可添加此配置) -->
    <!-- <exclusions>
        <exclusion>
            <groupId>io.lettuce</groupId>
            <artifactId>lettuce-core</artifactId>
        </exclusion>
    </exclusions> -->
</dependency>

<!-- 若排除Lettuce后,需添加Jedis依赖 -->
<!-- <dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
</dependency> -->

<!-- Spring Boot Web Starter(用于演示接口) -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

说明:

  1. spring-boot-starter-data-redis 是 Spring Boot 提供的 Redis 启动器,自动配置了 Redis 相关的 Bean。
  2. 默认使用 Lettuce 客户端,如需切换为 Jedis,需排除 Lettuce 并添加 Jedis 依赖。
  3. spring-boot-starter-web 用于演示 RESTful 接口,实际项目中根据需求添加。
5.1.2 配置 application.yml

在 src/main/resources/application.yml 中添加 Redis 相关配置:

yaml 复制代码
spring:
  redis:
    host: 192.168.1.100 # Redis服务地址,生产环境建议使用域名或内网IP
    port: 6379 # Redis端口,默认6379
    password: your_password # Redis密码(若未设,可省略)
    database: 0 # Redis数据库索引(0-15),默认0
    timeout: 5000ms # 连接超时时间
    lettuce: # Lettuce客户端配置(若使用Jedis,需改为jedis配置)
      pool: # 连接池配置
        max-active: 100 # 最大连接数,默认8
        max-idle: 20 # 最大空闲连接数,默认8
        min-idle: 5 # 最小空闲连接数,默认0
        max-wait: 3000ms # 连接池耗尽时的最大等待时间,默认-1(无限等待)

配置项详解:

  1. host 和 port:Redis 服务器地址和端口,必填项。
  2. password:如果 Redis 设置了密码,需配置此项。
  3. database:Redis 默认有 16 个数据库(0-15),可通过此配置选择使用的数据库。
  4. timeout:连接 Redis 的超时时间,建议设置为 5000ms 以上。
  5. lettuce.pool:Lettuce 连接池配置,适用于高并发场景:
    • max-active:最大连接数,根据业务量调整。
    • max-idle 和 min-idle:空闲连接数,避免频繁创建和销毁连接。
    • max-wait:获取连接的最大等待时间,避免长时间阻塞。

Jedis 配置示例(如需切换):

yaml 复制代码
spring:
  redis:
    host: 192.168.1.100
    port: 6379
    jedis:
      pool:
        max-active: 100
        max-idle: 20
        min-idle: 5
        max-wait: 3000ms

生产环境建议:

  1. 使用连接池以提高性能。
  2. 密码和敏感信息建议通过环境变量或配置中心管理。
  3. 对于集群或哨兵模式,需额外配置。

5.2 核心 API:RedisTemplate 与 StringRedisTemplate

Spring Data Redis 提供了两种主要的模板类用于操作 Redis,它们各自适用于不同的使用场景:

StringRedisTemplate

StringRedisTemplate 是专门用于操作 String 类型数据的模板类,具有以下特点:

  1. 序列化方式:默认使用 StringRedisSerializer,所有键和值都以 UTF-8 编码的字符串形式存储
  2. 优势 :
    • 完全避免中文乱码问题
    • 存储的数据可直接在 Redis CLI 中查看
    • 性能较高,适用于简单字符串操作
  3. 限制 :
    • 只能操作字符串类型的数据
    • 无法直接存储 Java 对象

RedisTemplate

RedisTemplate 是通用模板类,支持所有 Redis 数据结构,但需要注意:

  1. 默认序列化问题 :
    • 默认使用 JdkSerializationRedisSerializer
    • 会导致存储的数据带有"xac\xed\x00\x05t\x00"等序列化前缀
    • 存储的数据在 Redis CLI 中不可读
  2. 适用场景 :
    • 需要操作复杂数据结构(List, Set, ZSet等)
    • 需要直接存储 Java 对象
    • 需要自定义序列化方式

5.2.1 配置 RedisTemplate(自定义序列化)

为优化 RedisTemplate 的默认行为,推荐配置自定义序列化:

java 复制代码
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        
        // 设置连接工厂(必需)
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        
        // Key序列化配置(String类型)
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        redisTemplate.setKeySerializer(stringRedisSerializer);
        redisTemplate.setHashKeySerializer(stringRedisSerializer);
        
        // Value序列化配置(JSON格式)
        GenericJackson2JsonRedisSerializer jackson2JsonRedisSerializer = 
            new GenericJackson2JsonRedisSerializer();
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
        
        // 初始化模板(必需)
        redisTemplate.afterPropertiesSet();
        
        return redisTemplate;
    }
}

配置说明:

  1. 键序列化使用 StringRedisSerializer,保证键的可读性
  2. 值序列化使用 Jackson,支持复杂对象存储
  3. 必须调用 afterPropertiesSet() 完成初始化

5.2.2 StringRedisTemplate 使用示例

StringRedisTemplate 的常见操作示例:

java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.*;

import java.util.Map;
import java.util.concurrent.TimeUnit;

@RestController
@RequestMapping("/redis/string")
public class StringRedisController {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    // 存储String数据(带过期时间)
    @PostMapping("/set")
    public String setString(@RequestParam String key,
                           @RequestParam String value,
                           @RequestParam(required = false, defaultValue = "3600") long timeout) {
        stringRedisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);
        return "存储成功!Key:" + key + ",Value:" + value;
    }

    // 获取String数据
    @GetMapping("/get/{key}")
    public String getString(@PathVariable String key) {
        String value = stringRedisTemplate.opsForValue().get(key);
        return "获取到的数据:Key:" + key + ",Value:" + (value == null ? "无" : value);
    }

    // 存储Hash数据
    @PostMapping("/hash/set")
    public String setHash(@RequestParam String key,
                        @RequestParam Map<String, String> hashMap) {
        stringRedisTemplate.opsForHash().putAll(key, hashMap);
        return "Hash数据存储成功!Key:" + key;
    }

    // 获取Hash数据
    @GetMapping("/hash/get/{key}")
    public Map<Object, Object> getHash(@PathVariable String key) {
        return stringRedisTemplate.opsForHash().entries(key);
    }

    // 设置过期时间
    @PostMapping("/expire")
    public Boolean expireKey(@RequestParam String key, 
                          @RequestParam long timeout) {
        return stringRedisTemplate.expire(key, timeout, TimeUnit.SECONDS);
    }
}

5.2.3 RedisTemplate 使用示例(操作对象)

定义实体类:

java 复制代码
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private Long id;
    private String name;
    private Integer age;
    private String gender;
}

对象操作示例:

java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.*;

import java.util.concurrent.TimeUnit;

@RestController
@RequestMapping("/redis/object")
public class ObjectRedisController {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    // 存储User对象
    @PostMapping("/user/set")
    public String setUser(@RequestBody User user,
                        @RequestParam(required = false, defaultValue = "86400") long timeout) {
        String key = "user:" + user.getId();
        redisTemplate.opsForValue().set(key, user, timeout, TimeUnit.SECONDS);
        return "User对象存储成功!Key:" + key + ",User:" + user;
    }

    // 获取User对象
    @GetMapping("/user/get")
    public User getUser(@RequestParam Long userId) {
        String key = "user:" + userId;
        return (User) redisTemplate.opsForValue().get(key);
    }

    // 删除User对象
    @PostMapping("/user/delete")
    public String deleteUser(@RequestParam Long userId) {
        String key = "user:" + userId;
        Boolean isDeleted = redisTemplate.delete(key);
        return isDeleted ? "User对象删除成功!Key:" + key : "删除失败,Key不存在";
    }
}

操作说明:

  1. 对象存储会自动使用配置的 Jackson 序列化器
  2. 获取时会自动反序列化为 Java 对象
  3. 建议使用"类型:id"的键命名规范(如"user:123")
复制代码

5.3 Spring Boot Redis缓存注解:简化缓存开发

Spring Boot提供了@Cacheable、@CachePut、@CacheEvict等缓存注解,通过声明式的方式快速实现缓存功能,无需手动调用RedisTemplate。这些注解底层基于Spring Cache抽象,可以与多种缓存实现(如Redis、EhCache等)无缝集成,大大简化了缓存开发流程。

5.3.1 开启缓存注解支持

在Spring Boot启动类上添加@EnableCaching注解:

java 复制代码
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;

@SpringBootApplication
@EnableCaching // 开启缓存注解支持,自动配置CacheManager
public class RedisSpringBootApplication {
    public static void main(String[] args) {
        SpringApplication.run(RedisSpringBootApplication.class, args);
    }
}

注意事项:

  1. 确保项目中已引入spring-boot-starter-cache和spring-boot-starter-data-redis依赖
  2. 在application.properties/yml中配置Redis连接信息
  3. 默认情况下会自动配置RedisCacheManager

5.3.2 常用缓存注解详解

(1)@Cacheable:查询缓存

当方法被调用时,先从缓存中查询数据。若缓存存在,则直接返回缓存数据;若缓存不存在,则执行方法逻辑,并将结果存入缓存。

java 复制代码
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

@Service
public class UserService {
    
    /**
     * 根据用户ID查询用户信息
     * @param userId 用户ID
     * @return 用户对象
     * 
     * 缓存配置说明:
     * - value="userCache": 指定缓存名称(相当于Redis中的key前缀)
     * - key="#userId": 动态生成缓存key(使用SpEL表达式获取方法参数)
     * - unless="#result == null": 当方法返回null时不缓存结果
     */
    @Cacheable(value = "userCache", key = "#userId", unless = "#result == null")
    public User getUserById(Long userId) {
        // 模拟从数据库查询数据(实际开发中替换为DAO层调用)
        System.out.println("从数据库查询用户,userId:" + userId);
        return new User(userId, "张三", 25, "男");
    }
}

典型应用场景:

  • 高频访问但数据变化不频繁的查询,如商品详情、用户信息等
  • 复杂计算结果的缓存,如报表数据、统计结果等
(2)@CachePut:更新缓存

执行方法逻辑后,将结果存入缓存(无论缓存是否存在,都会覆盖原有缓存),常用于数据新增或更新场景。

java 复制代码
import org.springframework.cache.annotation.CachePut;
import org.springframework.stereotype.Service;

@Service
public class UserService {
    
    /**
     * 保存或更新用户信息
     * @param user 用户对象
     * @return 更新后的用户对象
     * 
     * 缓存配置说明:
     * - value="userCache": 缓存名称
     * - key="#user.id": 使用用户ID作为缓存key
     * - unless="#result == null": 更新失败不缓存
     */
    @CachePut(value = "userCache", key = "#user.id", unless = "#result == null")
    public User saveOrUpdateUser(User user) {
        // 模拟数据库新增/更新操作
        System.out.println("数据库新增/更新用户,user:" + user);
        return user; // 将更新后的用户对象存入缓存
    }
}

使用建议:

  1. 方法返回值必须是需要缓存的对象
  2. 确保key与查询方法@Cacheable的key一致
  3. 适用于insert和update操作
(3)@CacheEvict:删除缓存

执行方法逻辑后,删除指定缓存,常用于数据删除场景。

java 复制代码
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.stereotype.Service;

@Service
public class UserService {
    
    /**
     * 删除用户
     * @param userId 用户ID
     * 
     * 缓存配置说明:
     * - value="userCache": 缓存名称
     * - key="#userId": 指定要删除的缓存key
     * - allEntries=false: 默认只删除指定key的缓存
     */
    @CacheEvict(value = "userCache", key = "#userId", allEntries = false)
    public void deleteUser(Long userId) {
        // 模拟数据库删除操作
        System.out.println("数据库删除用户,userId:" + userId);
    }

    /**
     * 清空用户缓存
     * 
     * 缓存配置说明:
     * - value="userCache": 缓存名称
     * - allEntries=true: 删除该缓存名称下的所有缓存
     */
    @CacheEvict(value = "userCache", allEntries = true)
    public void clearUserCache() {
        System.out.println("清空用户缓存");
    }
}

删除策略选择:

  1. 单条删除:使用key指定要删除的缓存项
  2. 批量删除:设置allEntries=true清空整个缓存区域
  3. 可在方法执行前删除(beforeInvocation=true)
(4)@Caching:组合缓存注解

当一个方法需要同时使用多个缓存注解(如同时更新和删除缓存)时,可使用@Caching组合注解。

java 复制代码
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Caching;
import org.springframework.stereotype.Service;

@Service
public class UserService {
    
    /**
     * 更新用户信息并清空用户列表缓存
     * @param user 用户对象
     * @return 更新后的用户对象
     */
    @Caching(
        put = {
            // 更新用户缓存
            @CachePut(value = "userCache", key = "#user.id")
        },
        evict = {
            // 删除用户列表缓存(假设存在用户列表缓存)
            @CacheEvict(value = "userListCache", allEntries = true)
        }
    )
    public User updateUserAndClearListCache(User user) {
        // 模拟数据库更新操作
        System.out.println("更新用户并清空用户列表缓存,user:" + user);
        return user;
    }
}

适用场景:

  1. 需要同时操作多个缓存区域
  2. 需要同时执行put和evict操作
  3. 复杂的缓存策略组合

六、Redis 进阶:持久化、高可用与性能优化

在实际生产环境中,仅掌握基础使用还不够,还需深入理解 Redis 的持久化机制、高可用架构配置及性能优化技巧,确保 Redis 服务稳定高效运行。本章将详细介绍这些关键内容。

6.1 Redis 持久化机制

Redis 提供两种持久化方式,可根据业务需求选择单独使用或组合使用,确保数据安全性和系统可靠性。

6.1.1 RDB(Redis Database):快照持久化

原理:在指定时间间隔内,将 Redis 内存中的数据生成二进制快照文件(.rdb 文件)并保存到磁盘。这种机制类似于数据库的全量备份。

触发方式:

  1. 手动触发:

    • save命令:阻塞 Redis 主线程直到快照完成,期间无法处理其他请求(生产环境不推荐)
    • bgsave命令:Redis 会 fork 一个子进程进行快照操作,主进程继续提供服务(推荐方式)
  2. 自动触发:

    • 通过 redis.conf 配置文件中的 save 指令设置触发条件
    • 当 Redis 正常关闭时(shutdown命令)也会自动执行 RDB 持久化

典型配置示例:

conf 复制代码
# 900秒(15分钟)内至少1个key变化,触发RDB
save 900 1
# 300秒(5分钟)内至少10个key变化,触发RDB
save 300 10
# 60秒内至少10000个key变化,触发RDB
save 60 10000

# RDB文件名称
dbfilename dump.rdb
# 工作目录(保存RDB文件和AOF文件的目录)
dir /var/lib/redis

优缺点分析:

  • 优点 :
    • 快照文件体积小,占用空间少
    • 恢复速度快,特别适合大规模数据恢复
    • 对 Redis 性能影响小,适合定期备份
  • 缺点 :
    • 可能丢失最后一次快照后的所有数据(如 Redis 异常崩溃时)
    • 当数据量很大时,fork 子进程的过程可能耗时较长,导致短暂的服务停顿

6.1.2 AOF(Append Only File):日志持久化

原理:将 Redis 的每一条写命令(如 SET、HSET)以协议文本格式追加到 AOF 日志文件中,Redis 重启时通过重新执行日志中的命令来重建数据。

AOF 工作流程:

  1. 命令执行后写入 AOF 缓冲区
  2. 根据配置的刷盘策略将缓冲区内容同步到磁盘
  3. 定期进行 AOF 重写(rewrite)压缩文件体积

配置详解:

conf 复制代码
# 开启AOF(默认关闭,需手动设置为yes)
appendonly yes

# AOF文件名称
appendfilename "appendonly.aof"

# AOF刷盘策略(关键配置)
# everysec:每秒刷盘一次(推荐,平衡性能和数据安全性)
# always:每执行一条命令刷盘一次(数据零丢失,性能较差)
# no:由操作系统决定刷盘时机(性能最好,数据丢失风险最高)
appendfsync everysec

# AOF重写触发条件
# 当前AOF文件大小比起最后一次重写时的大小增长率达到100%时触发
auto-aof-rewrite-percentage 100
# AOF文件最小重写大小,避免文件很小就重写
auto-aof-rewrite-min-size 64mb

# 加载AOF时发现文件末尾不完整时的处理方式
aof-load-truncated yes

AOF 重写机制:

  1. 原理:创建一个新的 AOF 文件,包含重建当前数据集所需的最少命令集
  2. 触发方式:
    • 自动触发:根据 auto-aof-rewrite-percentage 和 auto-aof-rewrite-min-size 配置
    • 手动触发:执行BGREWRITEAOF命令

优缺点分析:

  • 优点 :
    • 数据安全性高,最多丢失1秒数据(everysec配置下)
    • AOF 文件可读性强,便于问题排查
    • 通过重写机制可以压缩文件大小
  • 缺点 :
    • AOF 文件通常比 RDB 文件大
    • 恢复速度比 RDB 慢
    • 在高写入负载下,AOF 可能影响 Redis 性能

6.1.3 RDB 与 AOF 组合使用

生产环境中推荐同时开启 RDB 和 AOF,充分发挥两者的优势:

  1. 数据恢复策略:

    • 日常恢复:优先使用 AOF 恢复(数据更完整)
    • 大规模数据恢复:若 AOF 文件过大,先使用 RDB 快速恢复基础数据,再通过 AOF 补充后续数据
  2. 典型配置组合:

    • RDB 配置为每天全量备份一次
    • AOF 配置为每秒刷盘
    • 定期将 RDB 文件和 AOF 文件备份到异地
  3. 注意事项:

    • Redis 4.0+ 支持混合持久化(aof-use-rdb-preamble),AOF 文件前半部分是 RDB 格式,后半部分是 AOF 格式,可以结合两者优点
    • 需要监控磁盘空间使用情况,避免持久化文件占满磁盘

6.2 Redis 高可用配置

为避免 Redis 单点故障,需搭建高可用架构,确保服务持续可用。Redis 提供了多种高可用方案,适用于不同场景。

6.2.1 主从复制(Master-Slave)

原理:将一台 Redis 服务器(Master)的数据同步到多台 Redis 服务器(Slave),实现数据冗余和读写分离。

核心特性:

  • 异步复制:Slave 异步从 Master 同步数据
  • 读写分离:Master 处理写请求,Slave 处理读请求
  • 级联复制:Slave 可以作为其他 Slave 的 Master

配置步骤(以 1 主 2 从为例):

  1. 准备 3 台 Redis 服务器(假设IP为192.168.1.100-102)

  2. 配置 Master(192.168.1.100): 确保 redis.conf 中有以下配置:

    conf 复制代码
    # 如果需要密码认证
    requirepass your_password
  3. 配置 Slave(192.168.1.101 和 192.168.1.102):

    conf 复制代码
    # 指定Master的地址和端口
    replicaof 192.168.1.100 6379
    
    # 若Master设置了密码,需配置认证密码
    masterauth your_password
    
    # 可选:设置Slave只读
    replica-read-only yes
  4. 验证主从状态:

    • 在 Master 上执行info replication查看 Slave 连接信息
    • 在 Slave 上执行info replication查看复制状态

主从复制过程:

  1. Slave 启动后向 Master 发送 SYNC 命令
  2. Master 执行 BGSAVE 生成 RDB 文件并发送给 Slave
  3. Master 将生成 RDB 期间的新命令缓存起来,之后发送给 Slave
  4. 之后 Master 将所有写命令异步发送给 Slave

注意事项:

  • 复制延迟问题:网络延迟或 Slave 负载过高可能导致数据不一致
  • 数据过期问题:Redis 的过期策略在主从模式下有特殊处理
  • 复制中断处理:可通过replica-serve-stale-data yes配置Slave在断开连接时是否继续提供服务

6.2.2 哨兵模式(Sentinel)

原理:在主从复制的基础上,增加哨兵进程监控节点状态,实现自动故障转移。

哨兵核心功能:

  1. 监控:持续检查 Master 和 Slave 是否正常运行
  2. 通知:当监控的 Redis 实例出现问题时,可以通过API通知管理员
  3. 自动故障转移:当 Master 不可用时,自动选举新的 Master
  4. 配置提供者:为客户端提供最新的 Master 地址

配置步骤(3个哨兵节点):

  1. 准备哨兵配置文件 sentinel.conf:

    conf 复制代码
    port 26379
    sentinel monitor mymaster 192.168.1.100 6379 2
    sentinel auth-pass mymaster your_password
    sentinel down-after-milliseconds mymaster 30000
    sentinel parallel-syncs mymaster 1
    sentinel failover-timeout mymaster 180000
  2. 启动哨兵:

    bash 复制代码
    redis-sentinel /path/to/sentinel.conf
  3. 验证哨兵状态:

    bash 复制代码
    redis-cli -p 26379 info sentinel

故障转移流程:

  1. 哨兵检测到 Master 不可达(超过 down-after-milliseconds 时间)
  2. 哨兵集群选举领导者哨兵(需要至少 quorum 数量的哨兵同意)
  3. 领导者哨兵从 Slave 中选择新的 Master(基于优先级、复制偏移量等)
  4. 将其他 Slave 重新配置为复制新的 Master
  5. 通知客户端配置变更

客户端访问哨兵模式的最佳实践:

  1. 客户端应连接哨兵获取当前 Master 地址
  2. 客户端应订阅哨兵的+switch-master事件处理主从切换
  3. 建议使用支持哨兵的 Redis 客户端库(如Jedis、Lettuce)

6.2.3 Redis Cluster(集群)

原理:将数据分片存储到多个节点,每个节点负责一部分哈希槽(共16384个槽),同时每个分片可配置主从复制。

集群特性:

  • 数据分片:自动将数据分布到多个节点
  • 高可用:每个分片可以配置主从复制
  • 线性扩展:可通过增加节点扩展集群容量和性能
  • 客户端路由:客户端可直接连接正确的节点

配置步骤(3主3从集群):

  1. 准备6台服务器(192.168.1.100-105),确保端口6379和16379开放

  2. 每个节点的 redis.conf 配置:

    conf 复制代码
    cluster-enabled yes
    cluster-config-file nodes-6379.conf
    cluster-node-timeout 15000
    cluster-announce-ip 192.168.1.100 # 每台填写自己的IP
    cluster-announce-port 6379
    cluster-announce-bus-port 16379
  3. 启动所有节点后,创建集群:

    bash 复制代码
    redis-cli --cluster create \
      192.168.1.100:6379 192.168.1.101:6379 192.168.1.102:6379 \
      192.168.1.103:6379 192.168.1.104:6379 192.168.1.105:6379 \
      --cluster-replicas 1
  4. 验证集群状态:

    bash 复制代码
    redis-cli -c -h 192.168.1.100 -p 6379 cluster info
    redis-cli -c -h 192.168.1.100 -p 6379 cluster nodes

数据分片原理:

  1. Redis 使用 CRC16(key) mod 16384 计算 key 所属的槽位
  2. 每个节点负责一部分连续的槽位
  3. 客户端可以缓存槽位到节点的映射关系

集群管理命令:

  • cluster meet:将节点加入集群
  • cluster addslots:分配槽位给节点
  • cluster replicate:设置节点为从节点
  • cluster failover:手动触发故障转移

集群扩展操作:

  1. 添加新节点:

    bash 复制代码
    redis-cli --cluster add-node new_host:new_port existing_host:existing_port
  2. 迁移槽位:

    bash 复制代码
    redis-cli --cluster reshard host:port
  3. 删除节点:

    bash 复制代码
    redis-cli --cluster del-node host:port node_id

6.3 Redis 性能优化建议

6.3.1 硬件优化

内存优化:

  • 为 Redis 分配足够的内存,避免频繁交换
  • 使用maxmemory限制 Redis 最大内存使用量
  • 考虑使用大页内存(transparent huge pages)

CPU优化:

  • Redis 6.0+ 支持多线程IO(I/O threading),可配置:

    conf 复制代码
    io-threads 4
    io-threads-do-reads yes
  • 在高并发场景下,可考虑使用多个 Redis 实例分担负载

磁盘优化:

  • 使用 SSD 存储持久化文件
  • 确保磁盘有足够的写入带宽
  • 对于 AOF,可以考虑使用no-appendfsync-on-rewrite yes减少重写时的磁盘压力

6.3.2 配置优化

内存管理配置:

conf 复制代码
# 内存淘汰策略(当内存达到maxmemory时)
maxmemory-policy allkeys-lru

# 设置最大内存(例如4GB)
maxmemory 4gb

# 避免内存碎片
activedefrag yes

网络配置优化:

conf 复制代码
# 提高TCP连接队列大小
tcp-backlog 511

# 客户端空闲超时时间
timeout 0 # 0表示不超时

# 最大客户端连接数
maxclients 10000

持久化优化:

conf 复制代码
# 禁用持久化(仅用于纯缓存场景)
save ""
appendonly no

# 如果使用RDB,适当调整save参数
save 3600 1  # 1小时内至少有1个变化则保存

6.3.3 开发优化

键设计原则:

  1. 使用简洁但有意义的键名,如user:1000:profile
  2. 避免使用大键(如包含百万元素的hash)
  3. 为键设置合理的过期时间

命令优化:

  1. 使用批量操作命令:

    • MSET替代多个SET
    • HMGET替代多个HGET
  2. 使用管道(pipeline)减少网络往返:

    python 复制代码
    pipeline = redis.pipeline()
    pipeline.set('key1', 'value1')
    pipeline.set('key2', 'value2')
    pipeline.execute()
  3. 避免阻塞命令:

    • 避免在生产环境使用KEYS,改用SCAN
    • 谨慎使用FLUSHALL/FLUSHDB

客户端优化:

  1. 使用连接池管理连接
  2. 合理设置连接池大小
  3. 实现客户端缓存(Redis 6.0+的客户端缓存功能)

监控与调优:

  1. 定期检查info命令输出
  2. 监控慢查询(slowlog get)
  3. 使用redis-benchmark进行性能测试
  4. 考虑使用Redis的Lua脚本减少网络开销
相关推荐
考虑考虑1 小时前
JDK26中的List.ofLazy()
java·后端·java ee
小蒜学长1 小时前
基于SpringBoot的公寓报修管理系统的设计与实现(代码+数据库+LW)
java·spring boot·后端·公寓报修管理系统·多角色协同
for_ever_love__2 小时前
MySQL 主从复制讲透:binlog 原理、搭建步骤与读写分离
java·数据库·mysql·binlog·读写分离·原理·主从复制
Ada's2 小时前
【计算机基础系列】003:Python数据结构
开发语言·数据结构·python
2601_962885722 小时前
如何用 Python 计算 TRIX 三重指数平滑均线指标?
开发语言·python
还卿一钵无情泪3 小时前
Unsloth 微调 构建自己的大模型 没有GPU也能微调
linux·开发语言·人工智能·python·大模型·nlp·unsloth
ly76893 小时前
Redis 内存优化与性能调优:编码转换、内存碎片与慢查询的定位链路
redis·性能调优·内存优化·慢查询·编码转换
小π军4 小时前
操作系统面试题
java·linux·服务器
倔强的石头1064 小时前
应用账号最小权限实践:读写账号、报表账号、运维账号分层
java·运维·开发语言