Redis 从入门到精通:Spring Boot 实战三部曲(一)—— 基础核心与快速上手

Redis 从入门到精通:Spring Boot 实战三部曲(一)------ 基础核心与快速上手

专题导读:本系列共三篇,从基础到高级,带你系统掌握 Redis 在 Spring Boot 项目中的实战应用。

  • 第一篇:基础核心与快速上手(本文)
  • 第二篇:进阶原理与高可用架构
  • 第三篇:高级特性与性能优化

📖 前言

Redis 作为当前最流行的内存数据库,以其高性能、丰富的数据结构和可靠性著称。在当今的互联网应用中,Redis 几乎成为了标配技术栈。

本文将从 Redis 的基础概念出发,结合 Spring Boot 项目,带你快速上手 Redis 开发,掌握五大核心数据结构的实际应用。

学完本文你将掌握:

  • ✅ Redis 的核心概念与应用场景
  • ✅ 五大数据结构的使用方法与最佳实践
  • ✅ Spring Boot 集成 Redis 的完整配置
  • ✅ 实际业务场景中的缓存实现
  • ✅ 常见问题的解决方案

一、Redis 是什么?为什么需要它?

1.1 Redis 简介

Redis(Remote Dictionary Server)是一个开源的、基于内存的数据结构存储系统,可以用作数据库、缓存和消息中间件。

核心特点:

  • 🚀 高性能:读写速度可达 10万+ QPS
  • 💾 丰富的数据结构:String、List、Hash、Set、ZSet
  • 🔧 持久化支持:RDB 快照 + AOF 日志
  • 🔄 主从复制:支持数据备份和读写分离
  • 原子操作:单线程模型保证操作的原子性

1.2 典型应用场景

复制代码
┌─────────────────────────────────────────────┐
│           Redis 应用场景                      │
├──────────────┬──────────────────────────────┤
│ 缓存         │ 热点数据缓存、Session存储      │
│ 计数器       │ 点赞数、浏览量、排行榜          │
│ 消息队列     │ 异步任务、事件通知              │
│ 分布式锁     │ 防止超卖、幂等性控制            │
│ 社交功能     │ 共同好友、关注列表              │
│ 实时系统     │ 在线用户、实时统计              │
└──────────────┴──────────────────────────────┘

1.3 与传统数据库对比

特性 Redis MySQL
数据存储 内存 磁盘
读写速度 微秒级 毫秒级
数据类型 丰富结构化 关系型表格
持久化 可选 必须
适用场景 缓存、高速读写 持久化存储、复杂查询

最佳实践:Redis 不是要替代 MySQL,而是与之配合使用,形成多级存储架构。


二、环境搭建与快速开始

2.1 Redis 安装

Windows 安装
bash 复制代码
# 下载 Redis for Windows
# 访问:https://github.com/microsoftarchive/redis/releases

# 启动 Redis
redis-server.exe redis.windows.conf

# 测试连接
redis-cli.exe
127.0.0.1:6379> PING
PONG
Linux 安装
bash 复制代码
# Ubuntu/Debian
sudo apt-get install redis-server

# CentOS/RHEL
sudo yum install redis

# 启动服务
sudo systemctl start redis
sudo systemctl enable redis

# 测试
redis-cli ping
Docker 安装(推荐)
bash 复制代码
docker run -d --name redis \
  -p 6379:6379 \
  -v /data/redis:/data \
  redis:7.0 \
  redis-server --appendonly yes

2.2 常用命令行工具

bash 复制代码
# 连接 Redis
redis-cli -h 127.0.0.1 -p 6379

# 基本测试
127.0.0.1:6379> PING
PONG

# 查看服务器信息
127.0.0.1:6379> INFO

# 查看数据库大小
127.0.0.1:6379> DBSIZE

# 清空当前数据库
127.0.0.1:6379> FLUSHDB

三、五大核心数据结构详解

3.1 String(字符串)

最基本的数据类型,二进制安全,可以存储任何数据。

常用命令
bash 复制代码
# 设置值
SET name "张三"

# 获取值
GET name

# 设置带过期时间的值(秒)
SETEX code 300 "123456"

# 数值自增
INCR views
INCRBY score 10

# 批量操作
MSET key1 value1 key2 value2
MGET key1 key2
Spring Boot 实战案例

场景 1:用户 Session 管理

java 复制代码
@Service
public class SessionService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    private static final String SESSION_PREFIX = "session:";
    private static final long SESSION_TIMEOUT = 30; // 分钟

    /**
     * 创建会话
     */
    public String createSession(Long userId, String userInfo) {
        String sessionId = UUID.randomUUID().toString();
        String key = SESSION_PREFIX + sessionId;
        
        // 存储用户信息,30分钟过期
        redisTemplate.opsForValue().set(key, userInfo, SESSION_TIMEOUT, TimeUnit.MINUTES);
        
        return sessionId;
    }

    /**
     * 获取会话信息
     */
    public String getSession(String sessionId) {
        String key = SESSION_PREFIX + sessionId;
        return (String) redisTemplate.opsForValue().get(key);
    }

    /**
     * 刷新会话(延长过期时间)
     */
    public void refreshSession(String sessionId) {
        String key = SESSION_PREFIX + sessionId;
        redisTemplate.expire(key, SESSION_TIMEOUT, TimeUnit.MINUTES);
    }

    /**
     * 销毁会话
     */
    public void destroySession(String sessionId) {
        String key = SESSION_PREFIX + sessionId;
        redisTemplate.delete(key);
    }
}

场景 2:短信验证码

java 复制代码
@Service
public class VerificationCodeService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    private static final String CODE_PREFIX = "sms:code:";
    private static final long CODE_EXPIRE = 5; // 分钟
    private static final int MAX_RETRY = 3; // 最大重试次数

    /**
     * 发送验证码
     */
    public void sendCode(String phone) {
        String code = generateCode();
        String key = CODE_PREFIX + phone;
        
        // 检查是否频繁发送
        Long ttl = redisTemplate.getExpire(key, TimeUnit.SECONDS);
        if (ttl != null && ttl > 60) {
            throw new RuntimeException("请勿频繁发送验证码");
        }
        
        // 存储验证码,5分钟过期
        redisTemplate.opsForValue().set(key, code, CODE_EXPIRE, TimeUnit.MINUTES);
        
        // TODO: 调用短信服务发送
        System.out.println("验证码:" + code);
    }

    /**
     * 验证验证码
     */
    public boolean verifyCode(String phone, String code) {
        String key = CODE_PREFIX + phone;
        String storedCode = (String) redisTemplate.opsForValue().get(key);
        
        if (storedCode != null && storedCode.equals(code)) {
            // 验证成功后删除
            redisTemplate.delete(key);
            return true;
        }
        return false;
    }

    private String generateCode() {
        return String.valueOf((int)((Math.random() * 9 + 1) * 100000));
    }
}

场景 3:文章浏览量统计

java 复制代码
@Service
public class ArticleViewService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    private static final String VIEW_KEY = "article:view:";

    /**
     * 增加浏览量
     */
    public void incrementView(Long articleId) {
        String key = VIEW_KEY + articleId;
        redisTemplate.opsForValue().increment(key);
    }

    /**
     * 获取浏览量
     */
    public Long getViewCount(Long articleId) {
        String key = VIEW_KEY + articleId;
        Object count = redisTemplate.opsForValue().get(key);
        return count != null ? Long.parseLong(count.toString()) : 0L;
    }

    /**
     * 批量获取浏览量
     */
    public Map<Long, Long> batchGetViews(List<Long> articleIds) {
        List<String> keys = articleIds.stream()
            .map(id -> VIEW_KEY + id)
            .collect(Collectors.toList());
        
        List<Object> values = redisTemplate.opsForValue().multiGet(keys);
        
        Map<Long, Long> result = new HashMap<>();
        for (int i = 0; i < articleIds.size(); i++) {
            Long count = values.get(i) != null ? 
                Long.parseLong(values.get(i).toString()) : 0L;
            result.put(articleIds.get(i), count);
        }
        return result;
    }
}

3.2 Hash(哈希)

适合存储对象,比 String 更节省内存。

常用命令
bash 复制代码
# 设置字段
HSET user:1001 name "张三" age 25 email "zhangsan@example.com"

# 获取单个字段
HGET user:1001 name

# 获取所有字段
HGETALL user:1001

# 获取多个字段
HMGET user:1001 name age

# 删除字段
HDEL user:1001 email

# 判断字段是否存在
HEXISTS user:1001 name

# 获取字段数量
HLEN user:1001
Spring Boot 实战案例

场景:用户信息管理

java 复制代码
@Service
public class UserService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    private static final String USER_HASH_KEY = "user:info:";

    /**
     * 保存用户信息
     */
    public void saveUser(User user) {
        String key = USER_HASH_KEY + user.getId();
        
        Map<String, Object> userMap = new HashMap<>();
        userMap.put("id", user.getId());
        userMap.put("name", user.getName());
        userMap.put("age", user.getAge());
        userMap.put("email", user.getEmail());
        userMap.put("phone", user.getPhone());
        userMap.put("createTime", System.currentTimeMillis());
        
        redisTemplate.opsForHash().putAll(key, userMap);
        
        // 设置过期时间(可选)
        redisTemplate.expire(key, 24, TimeUnit.HOURS);
    }

    /**
     * 获取用户信息
     */
    public User getUser(Long userId) {
        String key = USER_HASH_KEY + userId;
        Map<Object, Object> entries = redisTemplate.opsForHash().entries(key);
        
        if (entries.isEmpty()) {
            return null;
        }
        
        User user = new User();
        user.setId(Long.parseLong(entries.get("id").toString()));
        user.setName(entries.get("name").toString());
        user.setAge(Integer.parseInt(entries.get("age").toString()));
        user.setEmail(entries.get("email").toString());
        user.setPhone(entries.get("phone").toString());
        
        return user;
    }

    /**
     * 更新用户部分信息
     */
    public void updateUserField(Long userId, String field, Object value) {
        String key = USER_HASH_KEY + userId;
        redisTemplate.opsForHash().put(key, field, value);
    }

    /**
     * 批量获取用户信息
     */
    public List<User> batchGetUsers(List<Long> userIds) {
        List<User> users = new ArrayList<>();
        for (Long userId : userIds) {
            User user = getUser(userId);
            if (user != null) {
                users.add(user);
            }
        }
        return users;
    }

    /**
     * 删除用户
     */
    public void deleteUser(Long userId) {
        String key = USER_HASH_KEY + userId;
        redisTemplate.delete(key);
    }
}

优势分析:

  • ✅ 相比 String 序列化整个对象,Hash 可以单独更新某个字段
  • ✅ 节省内存(特别是字段较多时)
  • ✅ 更适合存储结构化数据

3.3 List(列表)

双向链表结构,支持从两端插入和弹出。

常用命令
bash 复制代码
# 左侧推入
LPUSH tasks "task1" "task2" "task3"

# 右侧推入
RPUSH messages "msg1" "msg2"

# 左侧弹出
LPOP tasks

# 右侧弹出
RPOP messages

# 获取列表长度
LLEN tasks

# 获取指定范围
LRANGE messages 0 10

# 阻塞式弹出(超时时间秒)
BRPOP tasks 10
Spring Boot 实战案例

场景 1:简单消息队列

java 复制代码
@Service
@Slf4j
public class MessageQueueService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    private static final String QUEUE_KEY = "queue:messages";

    /**
     * 生产消息
     */
    public void produceMessage(String message) {
        redisTemplate.opsForList().leftPush(QUEUE_KEY, message);
        log.info("消息已入队: {}", message);
    }

    /**
     * 消费消息(非阻塞)
     */
    public String consumeMessage() {
        Object message = redisTemplate.opsForList().rightPop(QUEUE_KEY);
        return message != null ? message.toString() : null;
    }

    /**
     * 消费消息(阻塞式,推荐)
     */
    public String consumeMessageBlocking(long timeout, TimeUnit unit) {
        Object message = redisTemplate.opsForList()
            .rightPop(QUEUE_KEY, timeout, unit);
        return message != null ? message.toString() : null;
    }

    /**
     * 获取队列长度
     */
    public Long getQueueSize() {
        return redisTemplate.opsForList().size(QUEUE_KEY);
    }

    /**
     * 批量生产消息
     */
    public void batchProduceMessages(List<String> messages) {
        redisTemplate.opsForList().leftPushAll(QUEUE_KEY, messages.toArray());
        log.info("批量消息已入队,数量: {}", messages.size());
    }
}

消费者示例:

java 复制代码
@Component
@Slf4j
public class MessageConsumer {
    
    @Autowired
    private MessageQueueService queueService;

    @PostConstruct
    public void startConsuming() {
        // 启动后台线程持续消费
        new Thread(() -> {
            while (!Thread.currentThread().isInterrupted()) {
                try {
                    // 阻塞式消费,超时10秒
                    String message = queueService.consumeMessageBlocking(10, TimeUnit.SECONDS);
                    
                    if (message != null) {
                        log.info("处理消息: {}", message);
                        // TODO: 处理业务逻辑
                        processMessage(message);
                    }
                } catch (Exception e) {
                    log.error("消息处理失败", e);
                }
            }
        }, "message-consumer-thread").start();
    }

    private void processMessage(String message) {
        // 业务处理逻辑
        System.out.println("Processing: " + message);
    }
}

场景 2:最新动态列表

java 复制代码
@Service
public class FeedService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    private static final String FEED_KEY = "user:feed:";
    private static final int MAX_FEED_SIZE = 100; // 最多保留100条

    /**
     * 发布动态
     */
    public void publishFeed(Long userId, String content) {
        String key = FEED_KEY + userId;
        
        Map<String, Object> feed = new HashMap<>();
        feed.put("content", content);
        feed.put("timestamp", System.currentTimeMillis());
        feed.put("id", UUID.randomUUID().toString());
        
        // 左侧推入(最新的在前面)
        redisTemplate.opsForList().leftPush(key, feed);
        
        // 限制列表长度,删除多余的
        redisTemplate.opsForList().trim(key, 0, MAX_FEED_SIZE - 1);
    }

    /**
     * 获取用户动态列表
     */
    public List<Object> getUserFeeds(Long userId, int page, int pageSize) {
        String key = FEED_KEY + userId;
        long start = (long) (page - 1) * pageSize;
        long end = start + pageSize - 1;
        
        return redisTemplate.opsForList().range(key, start, end);
    }

    /**
     * 获取最新动态N条
     */
    public List<Object> getLatestFeeds(Long userId, int count) {
        String key = FEED_KEY + userId;
        return redisTemplate.opsForList().range(key, 0, count - 1);
    }
}

3.4 Set(集合)

无序不重复集合,支持交集、并集、差集运算。

常用命令
bash 复制代码
# 添加成员
SADD tags:article:1 "Java" "Redis" "Spring"

# 获取所有成员
SMEMBERS tags:article:1

# 判断成员是否存在
SISMEMBER tags:article:1 "Java"

# 删除成员
SREM tags:article:1 "Redis"

# 获取集合大小
SCARD tags:article:1

# 交集
SINTER tags:article:1 tags:article:2

# 并集
SUNION tags:article:1 tags:article:2

# 差集
SDIFF tags:article:1 tags:article:2

# 随机获取一个成员
SRANDMEMBER tags:article:1

# 随机弹出一个成员
SPOP tags:article:1
Spring Boot 实战案例

场景 1:文章标签系统

java 复制代码
@Service
public class ArticleTagService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    private static final String ARTICLE_TAGS_KEY = "tags:article:";
    private static final String TAG_ARTICLES_KEY = "articles:tag:";

    /**
     * 为文章添加标签
     */
    public void addTags(Long articleId, Set<String> tags) {
        String articleKey = ARTICLE_TAGS_KEY + articleId;
        
        // 文章的标签集合
        redisTemplate.opsForSet().add(articleKey, tags.toArray());
        
        // 反向索引:标签下的文章集合
        for (String tag : tags) {
            String tagKey = TAG_ARTICLES_KEY + tag;
            redisTemplate.opsForSet().add(tagKey, articleId);
        }
    }

    /**
     * 获取文章的标签
     */
    public Set<Object> getArticleTags(Long articleId) {
        String key = ARTICLE_TAGS_KEY + articleId;
        return redisTemplate.opsForSet().members(key);
    }

    /**
     * 获取标签下的文章
     */
    public Set<Object> getArticlesByTag(String tag) {
        String key = TAG_ARTICLES_KEY + tag;
        return redisTemplate.opsForSet().members(key);
    }

    /**
     * 查找同时包含多个标签的文章(交集)
     */
    public Set<Object> findArticlesWithTags(String... tags) {
        if (tags.length == 0) {
            return Collections.emptySet();
        }
        
        String[] keys = Arrays.stream(tags)
            .map(tag -> TAG_ARTICLES_KEY + tag)
            .toArray(String[]::new);
        
        return redisTemplate.opsForSet().intersect(keys);
    }

    /**
     * 查找包含任一标签的文章(并集)
     */
    public Set<Object> findArticlesWithAnyTag(String... tags) {
        if (tags.length == 0) {
            return Collections.emptySet();
        }
        
        String[] keys = Arrays.stream(tags)
            .map(tag -> TAG_ARTICLES_KEY + tag)
            .toArray(String[]::new);
        
        return redisTemplate.opsForSet().union(keys);
    }

    /**
     * 移除文章的标签
     */
    public void removeTag(Long articleId, String tag) {
        String articleKey = ARTICLE_TAGS_KEY + articleId;
        String tagKey = TAG_ARTICLES_KEY + tag;
        
        redisTemplate.opsForSet().remove(articleKey, tag);
        redisTemplate.opsForSet().remove(tagKey, articleId);
    }
}

场景 2:共同好友功能

java 复制代码
@Service
public class FriendService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    private static final String FRIENDS_KEY = "user:friends:";

    /**
     * 添加好友
     */
    public void addFriend(Long userId, Long friendId) {
        String key = FRIENDS_KEY + userId;
        redisTemplate.opsForSet().add(key, friendId);
    }

    /**
     * 获取用户的好友列表
     */
    public Set<Object> getFriends(Long userId) {
        String key = FRIENDS_KEY + userId;
        return redisTemplate.opsForSet().members(key);
    }

    /**
     * 查找共同好友
     */
    public Set<Object> findMutualFriends(Long userId1, Long userId2) {
        String key1 = FRIENDS_KEY + userId1;
        String key2 = FRIENDS_KEY + userId2;
        
        return redisTemplate.opsForSet().intersect(key1, key2);
    }

    /**
     * 推荐好友(好友的好友,排除自己和已有好友)
     */
    public Set<Object> recommendFriends(Long userId) {
        Set<Object> myFriends = getFriends(userId);
        Set<Object> recommended = new HashSet<>();
        
        // 遍历我的每个好友
        for (Object friendId : myFriends) {
            Set<Object> friendOfFriend = getFriends(Long.parseLong(friendId.toString()));
            
            // 排除自己和已有好友
            friendOfFriend.removeAll(myFriends);
            friendOfFriend.remove(userId);
            
            recommended.addAll(friendOfFriend);
        }
        
        return recommended;
    }

    /**
     * 删除好友
     */
    public void removeFriend(Long userId, Long friendId) {
        String key = FRIENDS_KEY + userId;
        redisTemplate.opsForSet().remove(key, friendId);
    }
}

3.5 ZSet(有序集合)

带分数的集合,自动排序,适合排行榜场景。

常用命令
bash 复制代码
# 添加成员(带分数)
ZADD leaderboard 100 "player1" 200 "player2" 150 "player3"

# 获取排名(从小到大,0-based)
ZRANK leaderboard "player1"

# 获取排名(从大到小)
ZREVRANK leaderboard "player1"

# 获取指定范围的成员(从小到大)
ZRANGE leaderboard 0 9

# 获取指定范围的成员(从大到小)
ZREVRANGE leaderboard 0 9 WITHSCORES

# 获取成员的分数
ZSCORE leaderboard "player1"

# 增加分数
ZINCRBY leaderboard 10 "player1"

# 获取集合大小
ZCARD leaderboard

# 删除成员
ZREM leaderboard "player1"

# 获取分数范围内的成员
ZRANGEBYSCORE leaderboard 100 200
Spring Boot 实战案例

场景 1:游戏排行榜

java 复制代码
@Service
public class LeaderboardService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    private static final String LEADERBOARD_KEY = "game:leaderboard";

    /**
     * 更新玩家分数
     */
    public void updateScore(Long playerId, double score) {
        redisTemplate.opsForZSet().add(LEADERBOARD_KEY, playerId, score);
    }

    /**
     * 增加分数
     */
    public void incrementScore(Long playerId, double increment) {
        redisTemplate.opsForZSet().incrementScore(LEADERBOARD_KEY, playerId, increment);
    }

    /**
     * 获取 Top N 玩家
     */
    public Set<Object> getTopN(int n) {
        // 从大到小排序,获取前N个
        return redisTemplate.opsForZSet()
            .reverseRange(LEADERBOARD_KEY, 0, n - 1);
    }

    /**
     * 获取 Top N 玩家(带分数)
     */
    public Set<ZSetOperations.TypedTuple<Object>> getTopNWithScores(int n) {
        return redisTemplate.opsForZSet()
            .reverseRangeWithScores(LEADERBOARD_KEY, 0, n - 1);
    }

    /**
     * 获取玩家排名
     */
    public Long getPlayerRank(Long playerId) {
        Long rank = redisTemplate.opsForZSet()
            .reverseRank(LEADERBOARD_KEY, playerId);
        // 排名从0开始,转换为从1开始
        return rank != null ? rank + 1 : null;
    }

    /**
     * 获取玩家分数
     */
    public Double getPlayerScore(Long playerId) {
        return redisTemplate.opsForZSet()
            .score(LEADERBOARD_KEY, playerId);
    }

    /**
     * 获取玩家的排名和分数
     */
    public Map<String, Object> getPlayerInfo(Long playerId) {
        Map<String, Object> info = new HashMap<>();
        info.put("playerId", playerId);
        info.put("rank", getPlayerRank(playerId));
        info.put("score", getPlayerScore(playerId));
        return info;
    }

    /**
     * 获取分数段内的玩家
     */
    public Set<Object> getPlayersByScoreRange(double minScore, double maxScore) {
        return redisTemplate.opsForZSet()
            .rangeByScore(LEADERBOARD_KEY, minScore, maxScore);
    }

    /**
     * 删除玩家
     */
    public void removePlayer(Long playerId) {
        redisTemplate.opsForZSet().remove(LEADERBOARD_KEY, playerId);
    }

    /**
     * 获取排行榜总人数
     */
    public Long getTotalPlayers() {
        return redisTemplate.opsForZSet().zCard(LEADERBOARD_KEY);
    }
}

Controller 层示例:

java 复制代码
@RestController
@RequestMapping("/api/leaderboard")
public class LeaderboardController {
    
    @Autowired
    private LeaderboardService leaderboardService;

    /**
     * 更新分数
     */
    @PostMapping("/score")
    public ResponseEntity<Void> updateScore(
            @RequestParam Long playerId,
            @RequestParam Double score) {
        leaderboardService.updateScore(playerId, score);
        return ResponseEntity.ok().build();
    }

    /**
     * 获取 Top 10
     */
    @GetMapping("/top10")
    public ResponseEntity<Set<Object>> getTop10() {
        return ResponseEntity.ok(leaderboardService.getTopN(10));
    }

    /**
     * 获取我的排名
     */
    @GetMapping("/my-rank")
    public ResponseEntity<Map<String, Object>> getMyRank(
            @RequestParam Long playerId) {
        return ResponseEntity.ok(leaderboardService.getPlayerInfo(playerId));
    }
}

场景 2:延迟队列

java 复制代码
@Service
@Slf4j
public class DelayQueueService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    private static final String DELAY_QUEUE_KEY = "delay:queue";

    /**
     * 添加延迟任务
     * 
     * @param taskId 任务ID
     * @param delaySeconds 延迟秒数
     */
    public void addDelayTask(String taskId, long delaySeconds) {
        long executeTime = System.currentTimeMillis() + delaySeconds * 1000;
        // 分数为执行时间戳
        redisTemplate.opsForZSet().add(DELAY_QUEUE_KEY, taskId, executeTime);
        log.info("添加延迟任务: {}, 执行时间: {}", taskId, new Date(executeTime));
    }

    /**
     * 获取到期任务
     */
    public Set<Object> getDueTasks() {
        long now = System.currentTimeMillis();
        // 获取分数小于等于当前时间的任务
        return redisTemplate.opsForZSet()
            .rangeByScore(DELAY_QUEUE_KEY, 0, now);
    }

    /**
     * 处理到期任务
     */
    public void processDueTasks() {
        Set<Object> dueTasks = getDueTasks();
        
        for (Object taskId : dueTasks) {
            // 尝试移除任务(保证只处理一次)
            Boolean removed = redisTemplate.opsForZSet()
                .remove(DELAY_QUEUE_KEY, taskId);
            
            if (Boolean.TRUE.equals(removed)) {
                log.info("处理到期任务: {}", taskId);
                // TODO: 执行任务逻辑
                executeTask(taskId.toString());
            }
        }
    }

    private void executeTask(String taskId) {
        // 任务执行逻辑
        System.out.println("Executing task: " + taskId);
    }
}

定时任务处理器:

java 复制代码
@Component
@Slf4j
public class DelayTaskProcessor {
    
    @Autowired
    private DelayQueueService delayQueueService;

    /**
     * 每秒检查一次到期任务
     */
    @Scheduled(fixedRate = 1000)
    public void processTasks() {
        try {
            delayQueueService.processDueTasks();
        } catch (Exception e) {
            log.error("处理延迟任务失败", e);
        }
    }
}

四、Spring Boot 集成 Redis 完整配置

4.1 Maven 依赖

xml 复制代码
<dependencies>
    <!-- Spring Boot Starter Data Redis -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    
    <!-- 连接池支持 -->
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-pool2</artifactId>
    </dependency>
</dependencies>

4.2 application.yml 配置

yaml 复制代码
spring:
  redis:
    host: localhost
    port: 6379
    password:  # 如果有密码则填写
    database: 0
    timeout: 3000ms
    lettuce:
      pool:
        max-active: 20      # 最大连接数
        max-idle: 10        # 最大空闲连接
        min-idle: 5         # 最小空闲连接
        max-wait: 3000ms    # 连接超时时间

4.3 Redis 配置类

java 复制代码
@Configuration
public class RedisConfig {

    /**
     * 配置 RedisTemplate
     */
    @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());
        
        // Value 使用 JSON 序列化
        GenericJackson2JsonRedisSerializer jsonSerializer = 
            new GenericJackson2JsonRedisSerializer();
        template.setValueSerializer(jsonSerializer);
        template.setHashValueSerializer(jsonSerializer);
        
        template.afterPropertiesSet();
        return template;
    }
}

4.4 通用 Redis 工具类

java 复制代码
@Component
public class RedisUtil {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    // ==================== String 操作 ====================

    public void set(String key, Object value) {
        redisTemplate.opsForValue().set(key, value);
    }

    public void set(String key, Object value, long timeout, TimeUnit unit) {
        redisTemplate.opsForValue().set(key, value, timeout, unit);
    }

    public Object get(String key) {
        return redisTemplate.opsForValue().get(key);
    }

    public Boolean delete(String key) {
        return redisTemplate.delete(key);
    }

    public Boolean hasKey(String key) {
        return redisTemplate.hasKey(key);
    }

    public Long expire(String key, long timeout, TimeUnit unit) {
        return redisTemplate.getExpire(key, unit);
    }

    public Boolean expire(String key, long timeout, TimeUnit unit) {
        return redisTemplate.expire(key, timeout, unit);
    }

    public Long increment(String key) {
        return redisTemplate.opsForValue().increment(key);
    }

    // ==================== Hash 操作 ====================

    public void hashSet(String key, String field, Object value) {
        redisTemplate.opsForHash().put(key, field, value);
    }

    public Object hashGet(String key, String field) {
        return redisTemplate.opsForHash().get(key, field);
    }

    public Map<Object, Object> hashGetAll(String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    // ==================== List 操作 ====================

    public Long leftPush(String key, Object value) {
        return redisTemplate.opsForList().leftPush(key, value);
    }

    public Object rightPop(String key) {
        return redisTemplate.opsForList().rightPop(key);
    }

    public List<Object> range(String key, long start, long end) {
        return redisTemplate.opsForList().range(key, start, end);
    }

    // ==================== Set 操作 ====================

    public Long setAdd(String key, Object... values) {
        return redisTemplate.opsForSet().add(key, values);
    }

    public Set<Object> setMembers(String key) {
        return redisTemplate.opsForSet().members(key);
    }

    public Boolean setIsMember(String key, Object value) {
        return redisTemplate.opsForSet().isMember(key, value);
    }

    public Set<Object> setIntersect(String... keys) {
        return redisTemplate.opsForSet().intersect(keys);
    }

    // ==================== ZSet 操作 ====================

    public Boolean zSetAdd(String key, Object value, double score) {
        return redisTemplate.opsForZSet().add(key, value, score);
    }

    public Set<Object> zSetReverseRange(String key, long start, long end) {
        return redisTemplate.opsForZSet().reverseRange(key, start, end);
    }

    public Long zSetRank(String key, Object value) {
        return redisTemplate.opsForZSet().reverseRank(key, value);
    }

    public Double zSetScore(String key, Object value) {
        return redisTemplate.opsForZSet().score(key, value);
    }
}

五、实战项目:简易博客系统

5.1 项目结构

复制代码
blog-system/
├── controller/
│   ├── ArticleController.java
│   └── UserController.java
├── service/
│   ├── ArticleService.java
│   ├── UserService.java
│   └── ViewService.java
├── model/
│   ├── Article.java
│   └── User.java
└── config/
    └── RedisConfig.java

5.2 核心功能实现

文章服务(综合应用多种数据结构):

java 复制代码
@Service
public class ArticleService {
    
    @Autowired
    private RedisUtil redisUtil;
    
    @Autowired
    private ArticleMapper articleMapper;
    
    private static final String ARTICLE_KEY = "article:";
    private static final String ARTICLE_LIST_KEY = "articles:latest";
    private static final String ARTICLE_VIEW_KEY = "article:view:";

    /**
     * 发布文章
     */
    @Transactional
    public Long publishArticle(Article article) {
        // 1. 保存到数据库
        articleMapper.insert(article);
        Long articleId = article.getId();
        
        // 2. 缓存文章详情(Hash)
        cacheArticleDetail(article);
        
        // 3. 添加到最新文章列表(List)
        redisUtil.leftPush(ARTICLE_LIST_KEY, articleId);
        // 只保留最近100篇
        redisUtil.trimList(ARTICLE_LIST_KEY, 0, 99);
        
        // 4. 添加文章标签(Set)
        if (article.getTags() != null && !article.getTags().isEmpty()) {
            redisUtil.setAdd(ARTICLE_KEY + articleId + ":tags", 
                article.getTags().toArray());
        }
        
        return articleId;
    }

    /**
     * 获取文章详情
     */
    public Article getArticle(Long articleId) {
        // 1. 尝试从缓存获取
        Article article = getCachedArticle(articleId);
        if (article != null) {
            return article;
        }
        
        // 2. 从数据库查询
        article = articleMapper.selectById(articleId);
        if (article != null) {
            // 3. 写入缓存
            cacheArticleDetail(article);
        }
        
        return article;
    }

    /**
     * 记录浏览量
     */
    public void recordView(Long articleId) {
        // 使用 String 类型的 INCR
        redisUtil.increment(ARTICLE_VIEW_KEY + articleId);
    }

    /**
     * 获取浏览量
     */
    public Long getViewCount(Long articleId) {
        Object count = redisUtil.get(ARTICLE_VIEW_KEY + articleId);
        return count != null ? Long.parseLong(count.toString()) : 0L;
    }

    /**
     * 获取最新文章列表
     */
    public List<Article> getLatestArticles(int page, int pageSize) {
        long start = (long) (page - 1) * pageSize;
        long end = start + pageSize - 1;
        
        List<Object> articleIds = redisUtil.range(ARTICLE_LIST_KEY, start, end);
        
        List<Article> articles = new ArrayList<>();
        for (Object id : articleIds) {
            Article article = getCachedArticle(Long.parseLong(id.toString()));
            if (article != null) {
                articles.add(article);
            }
        }
        
        return articles;
    }

    /**
     * 缓存文章详情
     */
    private void cacheArticleDetail(Article article) {
        String key = ARTICLE_KEY + article.getId();
        
        redisUtil.hashSet(key, "id", article.getId());
        redisUtil.hashSet(key, "title", article.getTitle());
        redisUtil.hashSet(key, "content", article.getContent());
        redisUtil.hashSet(key, "authorId", article.getAuthorId());
        redisUtil.hashSet(key, "createTime", article.getCreateTime());
        
        // 设置24小时过期
        redisUtil.expire(key, 24, TimeUnit.HOURS);
    }

    /**
     * 从缓存获取文章
     */
    private Article getCachedArticle(Long articleId) {
        String key = ARTICLE_KEY + articleId;
        Map<Object, Object> entries = redisUtil.hashGetAll(key);
        
        if (entries.isEmpty()) {
            return null;
        }
        
        Article article = new Article();
        article.setId(Long.parseLong(entries.get("id").toString()));
        article.setTitle(entries.get("title").toString());
        article.setContent(entries.get("content").toString());
        article.setAuthorId(Long.parseLong(entries.get("authorId").toString()));
        article.setCreateTime(Long.parseLong(entries.get("createTime").toString()));
        
        return article;
    }
}

六、常见问题与解决方案

6.1 缓存穿透

问题:查询不存在的数据,请求直接打到数据库。

解决方案

java 复制代码
public User getUser(Long userId) {
    String key = "user:" + userId;
    User user = (User) redisUtil.get(key);
    
    if (user != null) {
        // 如果是空对象,说明数据库中也不存在
        if (user instanceof NullObject) {
            return null;
        }
        return user;
    }
    
    // 查询数据库
    user = userMapper.selectById(userId);
    
    if (user != null) {
        redisUtil.set(key, user, 30, TimeUnit.MINUTES);
    } else {
        // 缓存空值,防止穿透
        redisUtil.set(key, new NullObject(), 5, TimeUnit.MINUTES);
    }
    
    return user;
}

6.2 缓存击穿

问题:热点 key 过期,大量请求同时访问数据库。

解决方案:使用互斥锁

java 复制代码
public User getUserWithLock(Long userId) {
    String key = "user:" + userId;
    User user = (User) redisUtil.get(key);
    
    if (user != null) {
        return user;
    }
    
    // 加锁
    String lockKey = "lock:user:" + userId;
    String requestId = UUID.randomUUID().toString();
    
    try {
        Boolean locked = redisTemplate.opsForValue()
            .setIfAbsent(lockKey, requestId, 3, TimeUnit.SECONDS);
        
        if (Boolean.TRUE.equals(locked)) {
            // 双重检查
            user = (User) redisUtil.get(key);
            if (user != null) {
                return user;
            }
            
            // 查询数据库并缓存
            user = userMapper.selectById(userId);
            if (user != null) {
                redisUtil.set(key, user, 30, TimeUnit.MINUTES);
            }
        } else {
            // 等待后重试
            Thread.sleep(50);
            return getUserWithLock(userId);
        }
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    } finally {
        // 释放锁
        redisTemplate.delete(lockKey);
    }
    
    return user;
}

6.3 缓存雪崩

问题:大量 key 同时过期,导致数据库压力骤增。

解决方案:设置随机过期时间

java 复制代码
public void setWithRandomTtl(String key, Object value, long baseTtl) {
    // 基础时间 ± 20% 随机波动
    long randomOffset = (long) (baseTtl * 0.2 * Math.random());
    long ttl = baseTtl + (Math.random() > 0.5 ? randomOffset : -randomOffset);
    
    redisUtil.set(key, value, ttl, TimeUnit.SECONDS);
}

七、总结与展望

7.1 本文要点回顾

Redis 基础概念 :理解 Redis 的特点和应用场景

五大数据结构 :掌握 String、Hash、List、Set、ZSet 的使用

Spring Boot 集成 :完成 Redis 的配置和工具类封装

实战案例 :通过博客系统综合运用所学知识

常见问题:了解缓存穿透、击穿、雪崩的解决方案

7.2 下篇预告

在下一篇文章《Redis 从入门到精通:Spring Boot 实战三部曲(二)------ 进阶原理与高可用架构》中,我们将深入探讨:

  • 🔍 Redis 持久化机制(RDB & AOF)深度解析
  • 🔄 主从复制与哨兵模式原理
  • 🔒 事务与 Lua 脚本实战
  • 🛡️ 分布式锁的高级实现
  • 📊 性能监控与调优技巧

7.3 学习建议

  1. 动手实践:不要只看代码,一定要亲手敲一遍
  2. 理解原理:知其然更要知其所以然
  3. 循序渐进:先掌握基础,再深入学习高级特性
  4. 关注官方文档:获取最新的技术信息

📚 参考资料


觉得有用?欢迎点赞、收藏、转发!

下一篇更精彩,敬请期待! 🚀

系列文章:

  • 第一篇 Redis 从入门到精通:Spring Boot 实战三部曲(一)------ 基础核心与快速上手
  • 第二篇 Redis 从入门到精通:Spring Boot 实战三部曲(二)------ 进阶原理与高可用架构
  • 第三篇 Redis 从入门到精通:Spring Boot 实战三部曲(三)------ 高级特性与性能优化
相关推荐
鸽芷咕1 小时前
金仓数据库标量子查询消除:一条SQL从32秒优化到24毫秒
数据库·sql
朝阳5811 小时前
MySQL 主从复制 — 双服务器灾备方案(原生安装)
服务器·数据库·mysql
是狐狸吖1 小时前
Redis分布式锁进阶第十六篇
数据库·redis·分布式
闪电悠米1 小时前
黑马点评-优惠券秒杀-04_one_user_one_order
服务器·网络·数据库
YL200404261 小时前
【Redis实战篇】基于Redis的分布式锁的原理及实现
数据库·redis·缓存
兔子宇航员03011 小时前
HiveSQL 中 NULL 与空字符串的区别与注意事项
数据库·数据仓库·sql
fpcc1 小时前
C++编程实践——提高缓存的命中
c++·缓存
杨云龙UP2 小时前
Oracle CDB巡检脚本使用SOP:从HTML原始报告到Word正式交付_2026-05-29
运维·服务器·数据库·oracle·架构·html·巡检
保定公民2 小时前
Oracle 层次查询(CONNECT BY)完全指南:从入门到精通
数据库·sql·oracle·达梦数据库·层次查询