一篇速通Redis 从原理到Java实战(含缓存问题解决方案+集群配置)

在现代互联网架构中,Redis 作为一款高性能、多功能的内存型 NoSQL 数据库,早已成为后端开发的核心组件。它不仅能作为缓存缓解数据库压力,还能实现分布式锁消息队列实时统计等多种场景需求。本文将从 Redis 核心原理出发,结合 Spring Boot + Spring Data Redis 实战代码,完整覆盖数据结构、高级功能、缓存异常解决方案、Lua 原子操作及集群配置,助力开发者从入门到精通 Redis 实战应用。

一、Redis 核心基础

1.1 定义与核心定位

Redis(Remote Dictionary Server)是一款开源、基于内存、可持久化的 Key-Value 型 NoSQL 数据库,由意大利程序员 Salvatore Sanfilippo(antirez)于2009年发布。其核心定位分为四类:

  • 高性能缓存:缓存热点数据,拦截80%以上的数据库查询,降低MySQL等关系型数据库压力;

  • 临时数据库:适合高频读写、短期存储的数据(如验证码、会话信息);

  • 中间件:实现分布式锁、消息队列、接口限流、发布订阅等功能;

  • 统计引擎:用于签到、点赞、UV统计、排行榜等实时数据统计场景。

开源协议说明:Redis 7.2及更早版本采用宽松的BSD协议(商用无限制),Redis 8.0+ 改为SSPL/AGPL协议,企业私有化部署需注意协议约束。

1.2 核心架构与运行模型

1.2.1 单线程模型(核心优势)

Redis 命令执行核心全程采用单线程,所有CRUD、事务、Lua脚本均串行执行,这也是其高性能的关键原因之一:

  • 避免多线程的锁竞争、上下文切换、死锁等开销;

  • 纯内存操作,无需磁盘IO耗时,读写延迟可达微秒级;

  • 通过IO多路复用(epoll/kqueue),单线程可监听万级客户端连接,支撑高并发。

注意:Redis 6.0+ 引入多线程IO优化,但仅用于连接建立、数据读写、协议解析,命令执行仍保持单线程,确保数据安全无并发冲突。

1.2.2 数据持久化(解决内存断电丢失问题)

Redis 数据默认存储在内存中,断电易丢失,提供两套持久化方案,可单独或组合使用,生产环境推荐混合持久化。

  1. RDB(快照持久化):定时将全量内存数据生成二进制快照文件保存到磁盘,文件体积小、恢复速度快,适合冷备份和灾备,但两次快照之间的数据会丢失,实时性差;

  2. AOF(日志持久化):记录所有写操作命令,以日志形式追加写入文件,支持三种刷盘策略(always/everysec/no),其中everysec(每秒刷盘)为生产默认,数据几乎不丢失,但日志文件大、重启恢复慢;

  3. 混合持久化(Redis 4.0+):结合RDB和AOF的优势,重启时先用RDB快速加载全量数据,再用AOF补充增量数据,兼顾恢复速度与数据安全性,是生产环境标配。

1.2.3 内存淘汰策略(面试必背)

当Redis内存占满时,会自动淘汰冷数据,常用策略如下(生产首选前2种):

  • allkeys-lru:淘汰所有key中最近最少使用的,适合热点数据集中的场景;

  • volatile-lru:只淘汰带有过期时间的key中最近最少使用的,适合部分数据需要长期存储的场景;

  • allkeys-lfu:淘汰所有key中使用频率最低的;

  • volatile-ttl:淘汰带有过期时间的key中剩余过期时间最短的;

  • random:随机淘汰key,适合无明显热点数据的场景。

二、Redis 九大数据结构(原理+Java实战)

Redis 的核心竞争力在于支持10种原生数据结构,远超普通KV存储,以下结合 Spring Boot + Spring Data Redis 实战代码,讲解每种结构的原理与应用场景(代码可直接复制到项目中运行)。

2.1 环境准备

2.1.1 Maven依赖

复制代码
<!-- SpringBoot 整合 Redis -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 用于JSON序列化 -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
</dependency>

2.1.2 application.yml配置

复制代码
spring:
  redis:
    host: 127.0.0.1  # 本地Redis地址,生产改为服务器地址
    port: 6379       # Redis默认端口
    password:        # 若Redis设置了密码,填写对应密码
    database: 0      # 操作的Redis数据库(默认0号库)
    lettuce:
      pool:
        max-active: 8  # 连接池最大活跃连接数
        max-idle: 8    # 连接池最大空闲连接数
        min-idle: 2    # 连接池最小空闲连接数
        max-wait: 1000ms # 连接池最大等待时间

2.1.3 Redis配置类(序列化配置)

解决Redis默认序列化乱码问题,采用String序列化key、JSON序列化value,适配所有数据类型。

复制代码
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 factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        // 绑定Redis连接工厂
        template.setConnectionFactory(factory);

        // key 序列化(String类型,避免乱码)
        template.setKeySerializer(new StringRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());

        // value 序列化(JSON类型,支持对象序列化)
        GenericJackson2JsonRedisSerializer jsonSerializer = new GenericJackson2JsonRedisSerializer();
        template.setValueSerializer(jsonSerializer);
        template.setHashValueSerializer(jsonSerializer);

        // 初始化模板
        template.afterPropertiesSet();
        return template;
    }
}

2.1.4 全局Redis工具类(项目直接复用)

复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;

@Component
public class RedisUtil {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    // 普通存值(无过期时间)
    public void set(String key, Object value) {
        redisTemplate.opsForValue().set(key, value);
    }

    // 存值并设置过期时间
    public void set(String key, Object value, long time, TimeUnit unit) {
        redisTemplate.opsForValue().set(key, value, time, unit);
    }

    // 取值
    public Object get(String key) {
        return key == null ? null : redisTemplate.opsForValue().get(key);
    }

    // 删除key
    public void del(String key) {
        if (key != null) {
            redisTemplate.delete(key);
        }
    }

    // 判断key是否存在
    public boolean exists(String key) {
        return Boolean.TRUE.equals(redisTemplate.hasKey(key));
    }

    // 原子自增(计数器常用)
    public Long increment(String key) {
        return redisTemplate.opsForValue().increment(key);
    }

    // 原子自减
    public Long decrement(String key) {
        return redisTemplate.opsForValue().decrement(key);
    }
}

2.2 九大数据结构实战

2.2.1 String 字符串

原理:二进制安全,可存储字符串、数字、序列化对象,单个value最大512MB,支持原子自增/自减、过期时间,核心场景:缓存、验证码、计数器、分布式ID。

复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.TimeUnit;

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

    @Autowired
    private RedisUtil redisUtil;

    // 存值(验证码示例,5分钟过期)
    @GetMapping("/set/code")
    public String setCode() {
        String key = "phoneCode:13800001111";
        redisUtil.set(key, "886666", 5, TimeUnit.MINUTES);
        return "验证码存入成功,key:" + key;
    }

    // 取值(获取验证码)
    @GetMapping("/get/code")
    public String getCode() {
        String key = "phoneCode:13800001111";
        Object code = redisUtil.get(key);
        return "获取验证码:" + (code == null ? "已过期" : code);
    }

    // 原子自增(文章阅读量统计)
    @GetMapping("/increment/read")
    public String incrementReadCount() {
        String key = "article:read:1001"; // 文章ID=1001
        Long readCount = redisUtil.increment(key);
        return "文章1001阅读量:" + readCount;
    }
}

2.2.2 Hash 哈希(存储对象首选)

原理:一个key对应多个field-value,类似Java的Map<String, Object>,支持字段级更新,无需序列化整个对象,节省内存,核心场景:用户信息、商品资料、系统配置。

复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/redis/hash")
public class HashController {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    // 批量存入用户信息(Hash)
    @GetMapping("/set/user")
    public String setUser() {
        String key = "user:info:1001"; // 用户ID=1001
        Map<String, Object> userMap = new HashMap<>();
        userMap.put("username", "张三");
        userMap.put("age", 23);
        userMap.put("sex", "男");
        userMap.put("phone", "13800001111");
        // 批量存入Hash
        redisTemplate.opsForHash().putAll(key, userMap);
        return "用户信息存入成功";
    }

    // 获取单个字段(获取用户年龄)
    @GetMapping("/get/user/age")
    public String getUserAge() {
        String key = "user:info:1001";
        Object age = redisTemplate.opsForHash().get(key, "age");
        return "用户1001年龄:" + age;
    }

    // 获取全部字段(获取完整用户信息)
    @GetMapping("/get/user/all")
    public Map<Object, Object> getUserAll() {
        String key = "user:info:1001";
        return redisTemplate.opsForHash().entries(key);
    }

    // 更新单个字段(修改用户年龄)
    @GetMapping("/update/user/age")
    public String updateUserAge() {
        String key = "user:info:1001";
        redisTemplate.opsForHash().put(key, "age", 24);
        return "用户年龄更新成功";
    }
}

2.2.3 List 列表(简单消息队列)

原理:双向链表,两头操作(左进右出/右进左出)效率极高(O(1)),有序、可重复,核心场景:简单消息队列、任务队列、时间轴、评论列表。

复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/redis/list")
public class ListController {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    // 左进(添加任务到队列)
    @GetMapping("/push/task")
    public String pushTask() {
        String key = "task:queue"; // 任务队列key
        // 批量添加任务
        redisTemplate.opsForList().leftPushAll(key, "任务1", "任务2", "任务3", "任务4");
        return "任务添加到队列成功";
    }

    // 右出(从队列取出任务,先进先出)
    @GetMapping("/pop/task")
    public Object popTask() {
        String key = "task:queue";
        // 右出:取出队列最前面的任务
        return redisTemplate.opsForList().rightPop(key);
    }

    // 获取队列所有任务
    @GetMapping("/get/all/task")
    public Object getAllTask() {
        String key = "task:queue";
        // 0:起始索引,-1:结束索引(获取所有)
        return redisTemplate.opsForList().range(key, 0, -1);
    }
}

2.2.4 Set 集合(自动去重)

原理:无序、元素唯一,自动去重,支持交集、并集、差集运算,核心场景:好友关系、标签筛选、权限控制、数据去重。

复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/redis/set")
public class SetController {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    // 添加标签(自动去重)
    @GetMapping("/add/tag")
    public String addTag() {
        String key = "user:tag:1001"; // 用户1001的标签
        // 添加重复标签(Java会自动去重)
        redisTemplate.opsForSet().add(key, "Java", "Redis", "MySQL", "Java", "SpringBoot");
        return "标签添加成功(自动去重)";
    }

    // 获取用户所有标签
    @GetMapping("/get/all/tag")
    public Object getAllTag() {
        String key = "user:tag:1001";
        return redisTemplate.opsForSet().members(key);
    }

    // 交集(获取两个用户的共同标签)
    @GetMapping("/inter/tag")
    public Object interTag() {
        String key1 = "user:tag:1001";
        String key2 = "user:tag:1002";
        // 交集:两个用户都有的标签
        return redisTemplate.opsForSet().intersect(key1, key2);
    }
}

2.2.5 ZSet 有序集合(排行榜核心)

原理:元素唯一,绑定score(分数),按分数自动排序,底层采用跳表实现,兼顾排序与查询效率,核心场景:排行榜、积分排名、热度排名、延迟队列。

复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/redis/zset")
public class ZSetController {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    // 添加排行榜数据(文章热度排名)
    @GetMapping("/add/rank")
    public String addRank() {
        String key = "rank:article:hot"; // 文章热度排行榜
        // 参数:key, value(文章标题), score(热度分数)
        redisTemplate.opsForZSet().add(key, "Redis实战教程", 98);
        redisTemplate.opsForZSet().add(key, "SpringBoot入门", 85);
        redisTemplate.opsForZSet().add(key, "MySQL优化", 92);
        redisTemplate.opsForZSet().add(key, "Java并发编程", 95);
        return "排行榜数据写入成功";
    }

    // 升序获取排行榜(从低到高)
    @GetMapping("/get/rank/asc")
    public Object getRankAsc() {
        String key = "rank:article:hot";
        // 0:起始索引,-1:结束索引(所有数据)
        return redisTemplate.opsForZSet().range(key, 0, -1);
    }

    // 降序获取排行榜(从高到低,常用)
    @GetMapping("/get/rank/desc")
    public Object getRankDesc() {
        String key = "rank:article:hot";
        return redisTemplate.opsForZSet().reverseRange(key, 0, -1);
    }

    // 获取文章热度分数
    @GetMapping("/get/score")
    public Object getScore() {
        String key = "rank:article:hot";
        return redisTemplate.opsForZSet().score(key, "Redis实战教程");
    }
}

2.2.6 Bitmap 位图(省内存统计)

原理:特殊的String类型,按bit位存储数据,极度节省内存(1字节可存储8个状态),核心场景:每日签到、用户在线状态、亿级状态标记。

复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/redis/bitmap")
public class BitmapController {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    // 签到(用户1001 2026年4月26日签到)
    @GetMapping("/sign")
    public String sign() {
        String key = "sign:20260426"; // 日期作为key
        long userId = 1001; // 用户ID作为bit位索引
        // 设置第1001位为1(表示签到成功)
        redisTemplate.opsForValue().setBit(key, userId, true);
        return "用户1001 2026年4月26日签到成功";
    }

    // 查询签到状态
    @GetMapping("/check/sign")
    public String checkSign() {
        String key = "sign:20260426";
        long userId = 1001;
        Boolean isSign = redisTemplate.opsForValue().getBit(key, userId);
        return "用户1001 2026年4月26日" + (isSign ? "已签到" : "未签到");
    }
}

2.2.7 HyperLogLog(基数统计)

原理:概率型数据结构,用极小的内存(约12KB)实现基数统计(独立数量),允许0.81%的误差,核心场景:页面UV、独立访客统计、不要求绝对精准的去重统计。

复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/redis/bitmap")
public class BitmapController {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    // 签到(用户1001 2026年4月26日签到)
    @GetMapping("/sign")
    public String sign() {
        String key = "sign:20260426"; // 日期作为key
        long userId = 1001; // 用户ID作为bit位索引
        // 设置第1001位为1(表示签到成功)
        redisTemplate.opsForValue().setBit(key, userId, true);
        return "用户1001 2026年4月26日签到成功";
    }

    // 查询签到状态
    @GetMapping("/check/sign")
    public String checkSign() {
        String key = "sign:20260426";
        long userId = 1001;
        Boolean isSign = redisTemplate.opsForValue().getBit(key, userId);
        return "用户1001 2026年4月26日" + (isSign ? "已签到" : "未签到");
    }
}

2.2.8 Geospatial 地理位置(LBS场景)

原理:封装经纬度坐标,支持距离计算、范围查找,核心场景:附近的人、门店定位、LBS(地理位置服务)应用。

复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.geo.Circle;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.Point;
import org.springframework.data.geo.Polygon;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/redis/geo")
public class GeoController {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    // 添加门店坐标
    @GetMapping("/add/shop")
    public String addShop() {
        String key = "shop:geo"; // 门店地理位置集合
        // 参数:key, 经纬度(经度,纬度), 门店名称
        redisTemplate.opsForGeo().add(key, new Point(116.40, 39.90), "门店A");
        redisTemplate.opsForGeo().add(key, new Point(116.41, 39.91), "门店B");
        redisTemplate.opsForGeo().add(key, new Point(116.39, 39.89), "门店C");
        return "门店坐标添加成功";
    }

    // 查询范围内的门店(以116.40,39.90为中心,1公里内)
    @GetMapping("/get/shop/near")
    public Object getShopNear() {
        String key = "shop:geo";
        Point center = new Point(116.40, 39.90);
        Distance distance = new Distance(1, org.springframework.data.geo.Metric.KILOMETERS);
        Circle circle = new Circle(center, distance);
        // 查询1公里内的门店
        return redisTemplate.opsForGeo().radius(key, circle);
    }
}

2.2.9 Stream 消息队列(可靠异步)

原理:Redis 5.0+ 新增的专业消息队列结构,支持消息持久化、消费者组、消息确认、死信队列、回溯消费,替代简单MQ,实现可靠异步业务。

复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.stream.StreamListener;
import org.springframework.data.redis.stream.Subscription;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Collections;
import java.util.Map;

@RestController
@RequestMapping("/redis/stream")
public class StreamController {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    // 发送消息(订单创建消息)
    @GetMapping("/send/order")
    public String sendOrderMessage() {
        String key = "stream:order"; // 订单消息流key
        // 消息内容(订单ID、用户ID、金额)
        Map<String, Object> message = Collections.singletonMap("orderId", "1008611");
        // 发送消息,返回消息ID
        String messageId = redisTemplate.opsForStream().add(key, message);
        return "订单消息发送成功,消息ID:" + messageId;
    }

    // 消费消息(简单消费,无消费者组)
    @GetMapping("/consumer/order")
    public Object consumerOrderMessage() {
        String key = "stream:order";
        // 读取消息(从消息ID 0开始,读取1条)
        return redisTemplate.opsForStream().read(Collections.singletonMap(key, "0"), 1);
    }
}

三、Redis 高频高级功能(Java实战)

3.1 分布式锁(生产级实现)

原理:利用Redis的SET NX EX原子命令(key不存在则创建,同时设置过期时间),解决分布式集群下的并发竞争问题(如秒杀、库存扣减),避免超卖、重复操作。

复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;

@Component
public class RedisDistributedLock {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    // 尝试获取分布式锁
    public boolean tryLock(String lockKey, long expireTime) {
        // NX:key不存在才创建(保证原子性),EX:设置过期时间(防止死锁)
        return Boolean.TRUE.equals(
                redisTemplate.opsForValue().setIfAbsent(lockKey, "lock_value", expireTime, TimeUnit.SECONDS)
        );
    }

    // 释放分布式锁
    public void unLock(String lockKey) {
        redisTemplate.delete(lockKey);
    }

    // 业务实战(秒杀场景示例)
    public String seckill(Long goodsId, Long userId) {
        String lockKey = "lock:seckill:goods:" + goodsId; // 按商品ID加锁,避免全局锁阻塞
        try {
            // 尝试获取锁,30秒过期(防止死锁)
            boolean hasLock = tryLock(lockKey, 30);
            if (!hasLock) {
                return "系统繁忙,请稍后再试";
            }
            // 执行业务逻辑(库存扣减、订单创建等)
            // 此处省略库存扣减代码,实际开发中需结合数据库或Redis库存
            return "秒杀成功!商品ID:" + goodsId + ",用户ID:" + userId;
        } finally {
            // 无论业务是否成功,都释放锁(避免死锁)
            unLock(lockKey);
        }
    }
}

3.2 Lua 脚本实现原子操作(Java示例)

原理:Lua脚本可将多条Redis命令封装为一个脚本,在Redis服务端原子执行,减少网络往返,同时保证命令的原子性(避免并发问题),核心场景:复杂原子操作(如库存扣减+判断)、分布式锁优化。

以下示例:Lua脚本实现"库存扣减"原子操作(判断库存是否充足,充足则扣减,不足则返回失败)。

复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;

@Component
public class RedisLuaUtil {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    // 1. 定义Lua脚本(库存扣减:key=库存key,argv[1]=扣减数量)
    private static final String STOCK_DEDUCT_LUA = "local stockKey = KEYS[1]\n" +
            "local deductNum = tonumber(ARGV[1])\n" +
            "local currentStock = tonumber(redis.call('get', stockKey))\n" +
            "if currentStock < deductNum then\n" +
            "    return 0  -- 库存不足,扣减失败\n" +
            "end\n" +
            "redis.call('decrby', stockKey, deductNum)\n" +
            "return 1  -- 扣减成功";

    // 2. 执行Lua脚本
    public Integer deductStock(String stockKey, int deductNum) {
        // 初始化Lua脚本
        DefaultRedisScript<Integer> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptText(STOCK_DEDUCT_LUA);
        redisScript.setResultType(Integer.class); // 脚本返回值类型

        // 封装KEYS和ARGV
        List<String> keys = Arrays.asList(stockKey); // KEYS[1] = stockKey
        List<Object> args = Arrays.asList(deductNum); // ARGV[1] = deductNum

        // 执行脚本并返回结果(1=成功,0=失败)
        return redisTemplate.execute(redisScript, keys, args.toArray());
    }

    // 业务调用示例
    public String doDeductStock() {
        String stockKey = "stock:goods:1001"; // 商品1001的库存key
        int deductNum = 1; // 扣减1件库存
        Integer result = deductStock(stockKey, deductNum);
        return result == 1 ? "库存扣减成功" : "库存不足,扣减失败";
    }
}

补充说明:Lua脚本的核心优势是"原子性",脚本中的所有命令会串行执行,不会被其他命令打断,适合解决并发场景下的竞态问题。

四、Redis 缓存异常(穿透/击穿/雪崩)完整解决方案 + Java 代码

Redis 作为缓存使用时,会遇到三大经典异常:缓存穿透、缓存击穿、缓存雪崩,若不处理会导致数据库压力剧增,甚至服务宕机,以下是完整解决方案及Java实战代码。

4.1 缓存穿透(解决方案:布隆过滤器 + 空值缓存)

4.1.1 问题描述

缓存穿透是指:客户端请求的key在Redis和数据库中都不存在,导致所有请求都直接穿透到数据库,大量无效请求压垮数据库(如恶意查询不存在的用户ID、商品ID)。

4.1.2 解决方案(双重防护)

  1. 布隆过滤器:提前将数据库中所有存在的key(如商品ID、用户ID)存入布隆过滤器,请求先经过布隆过滤器校验,不存在的key直接返回,不进入Redis和数据库;

  2. 空值缓存:若布隆过滤器校验通过,但Redis中无该key,查询数据库后发现也不存在,将该key的空值(或特定标记)存入Redis并设置短期过期时间,避免后续相同请求再次穿透。

4.1.3 Java 实战代码

使用Google Guava的布隆过滤器(简单易用,适合中小规模场景),结合空值缓存实现防护。

复制代码
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;

@Service
public class CachePenetrationService {

    @Autowired
    private RedisUtil redisUtil;

    // 1. 初始化布隆过滤器(假设数据库中有100万条商品数据,误判率0.01)
    private static final BloomFilter<String> BLOOM_FILTER = BloomFilter.create(
            Funnels.stringFunnel(StandardCharsets.UTF_8),
            1000000, // 预计数据量
            0.01    // 误判率(越小越精准,占用内存越大)
    );

    // 模拟:项目启动时,将数据库中的商品ID加载到布隆过滤器
    static {
        // 实际开发中,此处应从数据库查询所有商品ID,批量添加到布隆过滤器
        BLOOM_FILTER.put("goods:1001");
        BLOOM_FILTER.put("goods:1002");
        BLOOM_FILTER.put("goods:1003");
        // ... 其他商品ID
    }

    // 2. 缓存查询方法(解决缓存穿透)
    public Object getGoodsInfo(String goodsId) {
        String key = "goods:info:" + goodsId;

        // 第一步:布隆过滤器校验,不存在直接返回
        if (!BLOOM_FILTER.mightContain(goodsId)) {
            return "商品不存在";
        }

        // 第二步:查询Redis缓存
        Object goodsInfo = redisUtil.get(key);
        if (goodsInfo != null) {
            // 缓存命中,返回数据(注意:若为null标记,需返回商品不存在)
            return goodsInfo == "null" ? "商品不存在" : goodsInfo;
        }

        // 第三步:缓存未命中,查询数据库
        Object dbGoodsInfo = queryGoodsFromDb(goodsId);

        // 第四步:数据库查询结果处理
        if (dbGoodsInfo == null) {
            // 数据库中也不存在,缓存空值(短期过期,如5分钟)
            redisUtil.set(key, "null", 5, TimeUnit.MINUTES);
            return "商品不存在";
        } else {
            // 数据库中存在,缓存数据(设置合理过期时间,如1小时)
            redisUtil.set(key, dbGoodsInfo, 60, TimeUnit.MINUTES);
            return dbGoodsInfo;
        }
    }

    // 模拟:从数据库查询商品信息
    private Object queryGoodsFromDb(String goodsId) {
        // 实际开发中,此处是JDBC/MyBatis查询数据库的逻辑
        if ("goods:1001".equals(goodsId)) {
            return "商品1001:Redis实战教程,价格:99元";
        } else if ("goods:1002".equals(goodsId)) {
            return "商品1002:SpringBoot入门,价格:88元";
        } else {
            return null; // 模拟商品不存在
        }
    }
}

4.2 缓存击穿(解决方案:互斥锁 + 热点key永不过期)

4.2.1 问题描述

缓存击穿是指:某个热点key(如热门商品、首页数据)过期时,大量并发请求同时访问该key,导致所有请求都穿透到数据库,瞬间压垮数据库。

4.2.2 解决方案(两种方案可选)

  1. 互斥锁:当缓存过期时,只允许一个请求去查询数据库,其他请求等待,查询完成后更新缓存,后续请求从缓存获取数据;

  2. 热点key永不过期:对于核心热点key,不设置过期时间,通过后台定时任务(如每小时)更新缓存数据,避免过期导致的击穿。

4.2.3 Java 实战代码(互斥锁方案,生产常用)

复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

@Service
public class CacheBreakdownService {

    @Autowired
    private RedisUtil redisUtil;

    @Autowired
    private RedisDistributedLock distributedLock;

    // 本地锁(单服务节点可用),分布式场景用Redis分布式锁
    private final Lock localLock = new ReentrantLock();

    // 缓存查询方法(解决缓存击穿)
    public Object getHotGoodsInfo(String goodsId) {
        String key = "goods:hot:info:" + goodsId;
        Object goodsInfo = redisUtil.get(key);

        // 缓存未命中(可能过期)
        if (goodsInfo == null) {
            try {
                // 方案1:本地锁(单服务节点)
                localLock.lock();
                // 再次查询缓存(防止多线程等待后重复查询数据库)
                goodsInfo = redisUtil.get(key);
                if (goodsInfo != null) {
                    return goodsInfo;
                }

                // 方案2:分布式锁(多服务节点集群,推荐)
                // String lockKey = "lock:hot:goods:" + goodsId;
                // if (!distributedLock.tryLock(lockKey, 30)) {
                //     // 获取锁失败,重试或返回默认值
                //     return "系统繁忙,请稍后再试";
                // }

                // 查询数据库
                goodsInfo = queryGoodsFromDb(goodsId);
                // 更新缓存(设置合理过期时间,如1小时)
                redisUtil.set(key, goodsInfo, 60, TimeUnit.MINUTES);
            } finally {
                // 释放锁
                localLock.unlock();
                // 分布式锁释放:distributedLock.unLock(lockKey);
            }
        }

        return goodsInfo;
    }

    // 模拟:从数据库查询热门商品信息
    private Object queryGoodsFromDb(String goodsId) {
        // 实际开发中,此处是数据库查询逻辑
        if ("goods:1001".equals(goodsId)) {
            return "热门商品1001:Redis实战教程,价格:99元,库存:1000件";
        }
        return null;
    }
}

4.3 缓存雪崩(解决方案:过期时间随机化 + 多级缓存 + 集群高可用)

4.3.1 问题描述

缓存雪崩是指:大量缓存key在同一时间过期,导致大量并发请求同时穿透到数据库,数据库无法承受压力而宕机;或Redis集群宕机,所有请求直接打向数据库。

4.3.2 解决方案(三重防护)

  1. 过期时间随机化:给每个缓存key设置过期时间时,增加随机偏移量(如10~60秒),避免大量key同时过期;

  2. 多级缓存:引入本地缓存(如Caffeine),请求先查询本地缓存,再查询Redis,最后查询数据库,减少Redis压力;

  3. Redis集群高可用:部署Redis主从+哨兵或Redis Cluster集群,避免Redis单点故障,确保缓存服务稳定。

4.3.3 Java 实战代码

复制代码
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Random;
import java.util.concurrent.TimeUnit;

@Service
public class CacheAvalancheService {

    @Autowired
    private RedisUtil redisUtil;

    // 1. 本地缓存(Caffeine,高性能本地缓存)
    private final LoadingCache<String, Object> localCache = Caffeine.newBuilder()
            .expireAfterWrite(5, TimeUnit.MINUTES) // 本地缓存5分钟过期
            .maximumSize(1000) // 本地缓存最大容量
            .build(this::queryRedisCache); // 本地缓存未命中时,查询Redis

    // 2. 随机过期时间(避免大量key同时过期)
    private final Random random = new Random();
    private static final int BASE_EXPIRE = 60; // 基础过期时间(1小时)
    private static final int RANDOM_RANGE = 50; // 随机偏移量(0~50秒)

    // 缓存查询方法(解决缓存雪崩)
    public Object getGoodsInfo(String goodsId) {
        try {
            // 第一步:查询本地缓存(Caffeine)
            return localCache.get(goodsId);
        } catch (Exception e) {
            // 本地缓存查询失败,直接查询Redis和数据库
            return getGoodsInfoFromRedisAndDb(goodsId);
        }
    }

    // 本地缓存未命中,查询Redis
    private Object queryRedisCache(String goodsId) {
        String key = "goods:info:" + goodsId;
        Object goodsInfo = redisUtil.get(key);
        if (goodsInfo == null) {
            // Redis未命中,查询数据库并更新缓存
            goodsInfo = queryGoodsFromDb(goodsId);
            if (goodsInfo != null) {
                // 设置随机过期时间(1小时 ± 0~50秒)
                int expireTime = BASE_EXPIRE + random.nextInt(RANDOM_RANGE);
                redisUtil.set(key, goodsInfo, expireTime, TimeUnit.MINUTES);
            }
        }
        return goodsInfo;
    }

    // 本地缓存异常时,直接查询Redis和数据库
    private Object getGoodsInfoFromRedisAndDb(String goodsId) {
        String key = "goods:info:" + goodsId;
        Object goodsInfo = redisUtil.get(key);
        if (goodsInfo == null) {
            goodsInfo = queryGoodsFromDb(goodsId);
            if (goodsInfo != null) {
                int expireTime = BASE_EXPIRE + random.nextInt(RANDOM_RANGE);
                redisUtil.set(key, goodsInfo, expireTime, TimeUnit.MINUTES);
            }
        }
        return goodsInfo;
    }

    // 模拟:从数据库查询商品信息
    private Object queryGoodsFromDb(String goodsId) {
        // 实际开发中,此处是数据库查询逻辑
        return "商品" + goodsId + ":Redis实战教程,价格:99元";
    }
}

五、Redis 集群、哨兵 Java 连接配置

生产环境中,Redis 单机部署无法满足高可用和高并发需求,通常采用两种部署模式:主从+哨兵(中小规模)、Redis Cluster(大规模、大数据量),以下是两种模式的Java连接配置(Spring Boot)。

5.1 Redis 主从 + 哨兵 配置(中小规模高可用)

5.1.1 部署架构说明

1主2从3哨兵:1个主节点(Master,负责写操作)、2个从节点(Slave,负责读操作)、3个哨兵节点(监控主从,自动故障转移)。

5.1.2 Java 连接配置(application.yml)

复制代码
<!-- Caffeine 本地缓存 -->
<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
    <version>3.1.8</version>
</dependency>

说明:配置哨兵后,Spring Data Redis 会自动感知主从节点状态,当主节点宕机,哨兵自动选举新主节点,Java客户端无需修改配置,自动切换连接。

5.2 Redis Cluster 集群配置(大规模、大数据量)

5.2.1 部署架构说明

Redis Cluster 采用哈希槽分片机制(共16384个哈希槽),将数据均匀分配到多个节点,每个节点负责部分哈希槽,集群内自带主从复制,支持故障转移,可水平扩容至千级节点。

5.2.2 Java 连接配置(application.yml)

复制代码
spring:
  redis:
    password: 123456  # Redis密码(生产环境必须设置)
    database: 0
    lettuce:
      pool:
        max-active: 16
        max-idle: 8
        min-idle: 4
        max-wait: 1000ms
    sentinel:
      master: mymaster  # 哨兵监控的主节点名称(与哨兵配置一致)
      nodes: 192.168.1.101:26379,192.168.1.102:26379,192.168.1.103:26379  # 3个哨兵节点地址

配置说明:

  • nodes:配置集群中所有节点的地址(包括主节点和从节点),Spring Data Redis会自动识别主从关系,无需手动区分;

  • max-redirects:当请求的key不在当前节点负责的哈希槽时,Redis会返回重定向指令,该参数设置最大重定向次数,避免无限循环;

  • lettuce.cluster.refresh:集群节点信息刷新策略,adaptive(自适应刷新)会在节点连接失败时自动刷新,period(定期刷新)会按指定时间(30秒)主动刷新,确保客户端能及时感知集群节点变化(如故障转移、新增节点)。

5.2.3 集群配置补充(生产必配)

在实际生产环境中,需结合业务场景补充以下配置,避免集群连接异常、性能瓶颈:

复制代码
spring:
  redis:
    # 超时配置(避免集群连接超时)
    timeout: 5000ms
    cluster:
      nodes: 192.168.1.104:6379,192.168.1.105:6379,192.168.1.106:6379,192.168.1.107:6379,192.168.1.108:6379,192.168.1.109:6379
      max-redirects: 3
    lettuce:
      pool:
        max-active: 32  # 连接池最大活跃连接数,根据并发量调整(集群场景需比单机高)
        max-idle: 16
        min-idle: 8
        max-wait: 1000ms
      cluster:
        refresh:
          adaptive: true
          period: 30000
      shutdown-timeout: 1000ms  # 连接关闭超时时间,避免关闭时阻塞

注意:Redis Cluster 不支持多数据库(database),默认只能使用0号库,配置中database参数可省略,若填写需保持为0,否则会报错。

5.3 集群连接注意事项(生产避坑)

  1. 节点配置:集群节点地址需填写所有主从节点,避免只填主节点,否则当主节点故障转移后,客户端无法连接新主节点;

  2. 密码配置:若集群中所有节点密码一致,直接配置spring.redis.password即可;若密码不一致,需通过自定义Redis配置类手动配置(生产环境建议所有节点密码统一,降低维护成本);

  3. 连接池配置:集群场景下,连接池max-active需根据业务并发量调整,建议设置为单机场景的2-3倍,避免连接不足导致请求阻塞;

  4. 超时配置:集群跨节点请求耗时比单机长,需合理设置timeout(建议3000-5000ms),避免因超时导致请求失败;

  5. 分片注意:Redis Cluster 按哈希槽分片,key的哈希值决定存储在哪个节点,若需指定key存储节点,可通过"{key前缀}key"的格式(如{user}1001),确保同一前缀的key落在同一节点(适合需批量操作的场景)。

六、Redis 生产环境优化(Java实战适配)

Redis 实战中,除了核心功能和集群配置,生产环境的优化的是保障服务稳定、高性能的关键。以下从Java客户端优化、Redis服务端优化、业务层面优化三个维度,结合实战场景给出具体方案。

6.1 Java客户端优化(Spring Boot场景)

6.1.1 连接池优化(核心)

Spring Data Redis 默认使用Lettuce客户端(替代早期的Jedis),Lettuce基于Netty实现,支持异步、连接池复用,性能优于Jedis,生产环境需重点优化连接池参数:

复制代码
spring:
  redis:
    lettuce:
      pool:
        max-active: 16-32  # 核心参数,根据并发量调整(单机16,集群32)
        max-idle: 8-16     # 最大空闲连接,建议为max-active的1/2
        min-idle: 2-4      # 最小空闲连接,避免频繁创建连接
        max-wait: 1000-3000ms  # 最大等待时间,超时抛出异常,避免无限阻塞
      shutdown-timeout: 1000ms  # 关闭连接超时时间,防止服务停机时阻塞

补充说明:连接池参数需结合服务器配置(CPU、内存)和业务并发量调整,避免配置过大导致内存占用过高,或配置过小导致连接不足。

6.1.2 序列化优化(避免乱码、节省内存)

前文配置类采用"String序列化key + JSON序列化value",适合大多数场景,生产环境可进一步优化:

  1. 避免使用JDK默认序列化:JDK序列化会导致value体积过大、乱码,且不支持跨语言解析,优先使用JSON序列化(GenericJackson2JsonRedisSerializer);

  2. 自定义序列化:对于频繁存储的对象(如用户信息、商品信息),可自定义序列化器,减少JSON序列化的额外开销(如排除无用字段);

  3. key序列化规范:key统一采用"业务模块:具体标识"格式(如user:info:1001),避免key混乱,同时便于Redis管理和排查问题。

    // 优化后的Redis配置类(自定义序列化,排除无用字段)
    import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
    import com.fasterxml.jackson.databind.ObjectMapper;
    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 OptimizedRedisConfig {

    复制代码
     @Bean
     public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
         RedisTemplate<String, Object> template = new RedisTemplate<>();
         template.setConnectionFactory(factory);
    
         // key序列化(String)
         template.setKeySerializer(new StringRedisSerializer());
         template.setHashKeySerializer(new StringRedisSerializer());
    
         // 自定义JSON序列化,排除对象中无用字段
         ObjectMapper objectMapper = new ObjectMapper();
         // 忽略未知字段,避免序列化失败
         objectMapper.configure(com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
         // 排除带有@JsonIgnore注解的字段
         objectMapper.addMixIn(Object.class, IgnoreUnknownPropertiesMixin.class);
    
         GenericJackson2JsonRedisSerializer jsonSerializer = new GenericJackson2JsonRedisSerializer(objectMapper);
         template.setValueSerializer(jsonSerializer);
         template.setHashValueSerializer(jsonSerializer);
    
         template.afterPropertiesSet();
         return template;
     }
    
     // 自定义混入类,用于排除无用字段
     @JsonIgnoreProperties(ignoreUnknown = true)
     private static class IgnoreUnknownPropertiesMixin {}

    }
    }

6.1.3 避免频繁创建RedisTemplate实例

RedisTemplate是线程安全的,无需每次使用时创建新实例,生产环境需将其注入Spring容器(如前文配置类),通过@Autowired注入使用,避免重复创建导致的资源浪费。

6.2 Redis服务端优化(配合Java客户端)

6.2.1 内存优化(避免内存溢出)

  1. 设置合理的内存上限:通过Redis配置文件(redis.conf)设置maxmemory,避免Redis占用过多内存导致服务器宕机,建议设置为服务器内存的50%-70%;

  2. 选择合适的内存淘汰策略:生产环境首选allkeys-lru(淘汰所有key中最近最少使用的),配合定期清理冷数据,减少内存占用;

  3. 避免存储大value:单个value建议不超过100KB,若需存储大对象(如文件、图片),可将对象分片存储,或使用分布式文件系统(如MinIO),Redis仅存储文件地址。

6.2.2 持久化优化(兼顾性能与数据安全)

生产环境推荐使用Redis混合持久化(Redis 4.0+),优化配置如下(redis.conf):

复制代码
# 开启混合持久化(AOF + RDB)
aof-use-rdb-preamble yes
# AOF刷盘策略(生产默认everysec,兼顾性能与数据安全)
appendfsync everysec
# 关闭AOF重写时的自动触发(手动触发或定时触发,避免影响业务)
auto-aof-rewrite-percentage 0
# RDB快照定时触发(避免频繁快照影响性能,建议每6小时一次)
save 21600 1  # 6小时内至少1个key变化,触发RDB快照

补充:AOF重写建议在业务低峰期(如凌晨)手动触发,避免重写过程中占用过多CPU和IO,影响Java客户端请求。

6.2.3 高并发优化

  1. 开启Redis持久化压缩:AOF文件和RDB文件启用压缩,减少磁盘占用和IO耗时;

  2. 限制客户端连接数:通过redis.conf设置maxclients,避免过多客户端连接导致Redis崩溃,建议设置为10000-20000;

  3. 避免慢查询:禁止在生产环境执行keys *、hgetall等全量查询命令,若需批量查询,使用scan命令(分批查询,避免阻塞Redis)。

6.3 业务层面优化

6.3.1 缓存设计优化

  1. 缓存key过期时间合理设置:根据业务场景设置过期时间,避免过期时间过长导致内存浪费,或过短导致缓存穿透/击穿;热点数据可设置较长过期时间,配合定时更新;

  2. 缓存预热:项目启动时,将热点数据(如首页数据、热门商品)提前加载到Redis,避免项目启动后大量请求穿透到数据库;

  3. 缓存更新策略:采用"Cache-Aside"(缓存旁路)策略,更新数据库后同步删除缓存(而非更新缓存),避免缓存与数据库数据不一致;高并发场景下,可配合Lua脚本实现原子更新。

6.3.2 避免缓存滥用

  1. 不缓存高频更新的数据:如实时库存(需结合业务场景,可采用Redis原子操作实现实时更新)、频繁变化的日志数据;

  2. 不缓存大数据量、低访问频率的数据:如历史订单(超过3个月的订单),可直接查询数据库,避免占用Redis内存;

  3. 避免缓存重复数据:同一数据不重复存储多个key,可通过合理的key设计(如统一前缀)避免重复缓存。

6.3.3 异常处理优化(Java代码层面)

生产环境中,需处理Redis连接异常、超时、宕机等场景,避免影响业务正常运行,示例代码如下:

复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.RedisConnectionFailureException;
import org.springframework.stereotype.Service;

@Service
public class BusinessService {

    @Autowired
    private RedisUtil redisUtil;

    // 优化后的缓存查询方法(增加异常处理)
    public Object getBusinessData(String key) {
        try {
            // 1. 查询缓存
            Object data = redisUtil.get(key);
            if (data != null) {
                return data;
            }
            // 2. 缓存未命中,查询数据库
            Object dbData = queryDataFromDb(key);
            if (dbData != null) {
                // 3. 更新缓存(设置合理过期时间)
                redisUtil.set(key, dbData, 30, java.util.concurrent.TimeUnit.MINUTES);
            }
            return dbData;
        } catch (RedisConnectionFailureException e) {
            // 处理Redis连接异常(如Redis宕机),直接查询数据库,保证业务可用
            System.err.println("Redis连接异常,直接查询数据库:" + e.getMessage());
            return queryDataFromDb(key);
        } catch (Exception e) {
            // 处理其他异常(如超时、序列化失败)
            System.err.println("缓存查询异常:" + e.getMessage());
            return "系统繁忙,请稍后再试";
        }
    }

    // 模拟查询数据库
    private Object queryDataFromDb(String key) {
        // 实际开发中为数据库查询逻辑
        return "业务数据:" + key;
    }
}

七、Redis 常见问题排查(Java实战场景)

生产环境中,Redis 可能出现连接失败、性能下降、数据不一致等问题,以下结合Java客户端场景,给出常见问题的排查思路和解决方案。

7.1 连接失败(Java客户端报错)

7.1.1 常见报错

RedisConnectionFailureException、Could not get a resource from the pool、Connection refused等。

7.1.2 排查思路与解决方案

  1. 检查Redis服务状态:登录Redis服务器,执行redis-cli ping,若返回PONG则服务正常,否则启动Redis服务;

  2. 检查连接配置:确认Java客户端配置的host、port、password是否正确,集群场景需确认所有节点地址正确;

  3. 检查防火墙/网络:确认Redis服务器防火墙开放6379(单机)、26379(哨兵)端口,Java客户端所在服务器能ping通Redis服务器;

  4. 检查连接池配置:若报错"Could not get a resource from the pool",说明连接池耗尽,需增大max-active参数,同时检查是否有连接未释放(如未关闭Redis连接);

  5. 检查Redis最大连接数:若Redis连接数达到maxclients,会拒绝新连接,需增大maxclients参数。

7.2 缓存与数据库数据不一致

7.2.1 常见原因

  1. 更新数据库后未删除缓存,导致缓存中存的是旧数据;

  2. 高并发场景下,查询缓存未命中 → 查询数据库 → 数据库更新 → 缓存未更新(并发竞争);

  3. 缓存过期时间设置过短,数据库更新后缓存已过期,重新查询时获取旧数据。

7.2.2 解决方案

  1. 采用"更新数据库 + 删除缓存"策略,而非"更新数据库 + 更新缓存";

  2. 高并发场景下,使用Lua脚本实现"查询缓存 → 缓存未命中 → 查询数据库 → 更新缓存"的原子操作,避免并发竞争;

  3. 热点数据设置合理的过期时间,配合定时任务定期更新缓存,确保缓存与数据库数据一致;

  4. 引入缓存一致性校验机制,定期对比缓存与数据库数据,发现不一致时及时修复。

7.3 Redis 性能下降(Java客户端请求耗时增加)

7.3.1 常见原因

  1. Redis服务端存在慢查询(如keys *、hgetall),阻塞Redis线程;

  2. Redis内存占用过高,触发内存淘汰策略,频繁淘汰数据导致缓存命中率下降;

  3. Java客户端连接池配置不合理,连接不足导致请求阻塞;

  4. Redis持久化(AOF重写、RDB快照)占用过多CPU和IO资源。

7.3.2 排查思路与解决方案

  1. 查看Redis慢查询日志:执行redis-cli slowlog get,排查慢查询命令,禁止在生产环境执行全量查询命令,替换为scan命令;

  2. 查看Redis内存使用情况:执行redis-cli info memory,若内存占用过高,清理冷数据、优化缓存策略,调整内存淘汰策略;

  3. 优化Java客户端连接池:增大max-active参数,调整max-wait、min-idle等参数,避免连接阻塞;

  4. 优化Redis持久化:在业务低峰期触发AOF重写和RDB快照,减少对业务的影响;

  5. 查看Redis CPU和IO占用:若CPU占用过高,检查是否有高频写入操作,可通过批量操作减少请求次数;若IO占用过高,优化持久化策略、启用压缩。

八、总结

本文从Redis核心原理出发,结合Spring Boot + Spring Data Redis 实战代码,完整覆盖了Redis基础原理、九大数据结构实战、高级功能(分布式锁、Lua脚本)、缓存异常解决方案(穿透/击穿/雪崩)、集群与哨兵配置、生产环境优化及常见问题排查,形成了一套从入门到精通的Redis实战指南。

Redis 实战的核心是"原理+场景+优化":掌握Redis单线程模型、持久化、内存淘汰等核心原理,是实现正确应用的基础;结合业务场景选择合适的数据结构和功能,是提升开发效率的关键;生产环境的优化和问题排查,是保障服务稳定、高性能运行的核心。

后续可结合具体业务场景(如秒杀、分布式会话、实时统计),进一步深化Redis的应用,同时关注Redis新版本特性(如Redis 8.0的新功能),持续优化Redis实战方案。

相关推荐
小卓(friendhan2005)2 小时前
基于Qt的音乐播放器项目
数据库·c++·qt
2401_882273722 小时前
golang如何处理zip压缩包_golang zip压缩包处理思路
jvm·数据库·python
猫的玖月2 小时前
SQL语法简介
数据库·sql·oracle
tjc199010052 小时前
Golang怎么实现分布式定时任务_Golang如何保证集群中定时任务不重复执行【进阶】
jvm·数据库·python
2301_773553622 小时前
构建 Go CLI 应用的最佳实践:纯 Go 交互式命令行库选型与使用指南
jvm·数据库·python
qq_372906932 小时前
c#如何添加按钮点击事件_c#添加按钮点击事件的几种常见用法
jvm·数据库·python
AI木马人2 小时前
8.【向量数据库深度对比】Milvus vs FAISS vs Pinecone(真实项目选型指南)
数据库·milvus·faiss
2301_817672262 小时前
JavaScript 中高效定位二维数组间不匹配元素的行列索引
jvm·数据库·python
2401_831419442 小时前
golang如何实现验证码图片生成_golang验证码图片生成实现实战
jvm·数据库·python