【苍穹外卖】Day05 Redis快速入门

一、Redis 概述

1.1 什么是 Redis

Redis(Remote Dictionary Server)是一个开源的、基于内存的高性能 Key-Value 数据库。

|----------|--------------|-----------|
| 特性 | Redis | MySQL |
| 存储介质 | 内存(RAM) | 磁盘(Disk) |
| 数据结构 | Key-Value | 二维表(关系型) |
| 读写性能 | 极高(10万+ QPS) | 中等 |
| 持久化 | 支持(RDB/AOF) | 天然支持 |
| 适用场景 | 缓存、会话、实时数据 | 事务性数据存储 |

1.2 核心优势

  • 高性能:纯内存操作,读写速度极快(10万次/秒以上)
  • 🎯 适合热点数据:短时间内被高频访问的数据(热点商品、资讯、新闻、排行榜等)
  • 💾 数据类型丰富:支持字符串、哈希、列表、集合、有序集合等
  • 🔄 持久化机制:支持 RDB 快照和 AOF 日志,防止数据丢失
  • 📊 原子操作:所有操作都是原子性的,支持事务

二、数据类型详解

2.1 基础结构

Key(字符串) → Value(五种数据类型)

2.2 五种常用数据类型对比

|------------|------------|-----------------------|---------------------|------------|
| 数据类型 | 结构描述 | Java 类比 | 适用场景 | 最大容量 |
| String | 二进制安全字符串 | String | 缓存、计数器、分布式锁、Session | 512 MB |
| Hash | 键值对集合 | HashMap | 存储对象(用户信息、商品信息) | 2³²-1 个键值对 |
| List | 双向链表,有序可重复 | LinkedList | 消息队列、时间线、最新列表 | 2³²-1 个元素 |
| Set | 无序集合,唯一不重复 | HashSet | 标签、共同好友、去重 | 2³²-1 个成员 |
| ZSet | 有序集合,带分数排序 | TreeSet + HashMap | 排行榜、延迟队列、权重排序 | 2³²-1 个成员 |

📌 String(字符串)
复制代码
# 基础操作
SET user:1001 "张三"
GET user:1001
SETEX session:abc 3600 "login_data"  # 设置过期时间(秒)
INCR view_count:article:123          # 原子自增

特点:二进制安全,可存储图片、序列化对象、JSON 字符串等任意数据

📌 Hash(哈希/散列)
复制代码
# 存储对象
HSET user:1001 name "张三" age 25 city "北京"
HGET user:1001 name
HGETALL user:1001                    # 获取所有字段

优势:比 String 更节省内存,适合存储对象属性

📌 List(列表)
复制代码
# 双向链表操作
LPUSH news:list "头条新闻"            # 左侧插入(最新)
RPUSH news:list "旧新闻"              # 右侧插入(最旧)
LRANGE news:list 0 9                  # 获取前10条(分页)
LPOP news:list                        # 左侧弹出(消费)

特点:支持阻塞读取(BLPOP/BRPOP),可实现简单消息队列

📌 Set(集合)
复制代码
# 无序唯一集合
SADD tags:article:123 "Java" "Redis" "后端"
SISMEMBER tags:article:123 "Java"     # 判断是否包含
SINTER user:1001:follows user:1002:follows  # 共同关注(交集)

特点:基于哈希表实现,增删查复杂度都是 O(1)

📌 ZSet(Sorted Set / 有序集合)
复制代码
# 带权重的有序集合
ZADD leaderboard 100 "player:张三" 85 "player:李四" 120 "player:王五"
ZRANGE leaderboard 0 2 WITHSCORES     # 升序取前3名(分数低到高)
ZREVRANGE leaderboard 0 2 WITHSCORES  # 降序取前3名(排行榜)
ZINCRBY leaderboard 10 "player:张三"  # 增加分数

关键:每个元素关联一个 double 类型的 score,按 score 排序;成员唯一,但 score 可重复


三、常用命令速查

key-value结构

key是字符串类型

value有五种常用的数据类型:

字符串(string)

string类型是二进制安全的。意思是redis的string可以包含任何数据。比如jpg图片或者序列化的对象 。

string类型是Redis最基本的数据类型,一个键最大能存储512MB。

哈希(hash) 也叫散列,类似于Java的HashMap, 适合存储对象

列表(list) 按照插入顺序排序,可以有重复元素,类似于Java中的LinkedList。你可以添加一个元素导列表的头部(左边)或者尾部(右边)。

三、常用命令速查

📚 完整命令参考:Redis 命令中心

|------------|----------------------------------------------------------------------------|
| 操作类型 | 常用命令 |
| 键操作 | KEYS pattern, EXISTS key, DEL key , EXPIRE key seconds, TTL key |
| String | SET, GET, MSET, MGET, INCR, DECR , APPEND |
| Hash | HSET, HGET, HMSET, HGETALL, HDEL , HINCRBY |
| List | LPUSH, RPUSH, LPOP, RPOP, LRANGE , LLEN, LINDEX |
| Set | SADD, SREM, SMEMBERS, SISMEMBER , SINTER, SUNION, SCARD |
| ZSet | ZADD, ZRANGE, ZREVRANGE , ZRANGEBYSCORE, ZREM, ZCARD, ZSCORE |
| 事务 | MULTI, EXEC, DISCARD, WATCH |
| 持久化 | SAVE, BGSAVE, LASTSAVE |


四、Spring Data Redis 实战

4.1 快速开始

步骤 1:添加 Maven 依赖
复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
步骤 2:配置数据源(application.yml)
复制代码
//application.yml
spring:
  profiles:
    active: dev
  redis:
    host: ${sky.redis.host}
    port: ${sky.redis.port}
    database: ${sky.redis.database}

//application-dev.yaml
sky:
  redis:
    host: localhost
    port: 6379
    password:      # 如有密码
    database: 0    # 默认数据库
步骤 3:配置 RedisTemplate(⚠️ 关键)
复制代码
@Slf4j
@Configuration
public class RedisConfiguration {

    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        log.info("创建RedisTemplate对象...");

        RedisTemplate redisTemplate = new RedisTemplate();
        // 设置redis的连接工厂对象
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        // 设置redis key的序列化器
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        return redisTemplate;
    }
}
步骤 4:使用 RedisTemplate
复制代码
@Service
public class RedisService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    // String 操作
    public void setString(String key, Object value) {
        redisTemplate.opsForValue().set(key, value, Duration.ofHours(1));
    }
    
    // Hash 操作(适合存对象)
    public void setHash(String key, String field, Object value) {
        redisTemplate.opsForHash().put(key, field, value);
    }
    
    // List 操作
    public void pushToList(String key, Object value) {
        redisTemplate.opsForList().leftPush(key, value);
    }
}

4.2 序列化器详解

序列化器是 Redis 客户端用来将 Java 对象转换为字节流(用于存储到 Redis)以及将字节流转换回 Java 对象(从 Redis 读取时)的工具。

|--------------------------------------|--------------|--------------------|
| 序列化器 | 说明 | 适用场景 |
| StringRedisSerializer | 字符串序列化 | Key 的序列化(可读性好) |
| GenericJackson2JsonRedisSerializer | JSON 格式 | Value 的序列化(跨语言、可读) |
| JdkSerializationRedisSerializer | Java 原生序列化 | 默认,但不推荐(二进制不可读) |
| GenericToStringSerializer | 转 String 序列化 | 简单数值类型 |

💡 建议 :Key 用 StringRedisSerializer,Value 用 GenericJackson2JsonRedisSerializer,避免内存中出现 \xac\xed\x00 这样的乱码前缀。


五、典型应用场景

店铺营业状态存储(热点数据案例)

进到苍穹外卖后台,显示餐厅的营业状态,营业状态分为营业中打烊中,若当前餐厅处于营业状态,自动接收任何订单,客户可在小程序进行下单操作;若当前餐厅处于打烊状态,不接受任何订单,客户便无法在小程序进行下单操作。

场景分析

  • 营业状态被客户端频繁查询(每次进入首页都要请求)
  • 数据量小(只是一个状态值:0/1)
  • 需要快速响应

接口设计:

  • 设置营业状态
  • 管理端查询营业状态
  • 用户端查询营业状态

注:从技术层面分析,其实管理端和用户端查询营业状态时,可通过一个接口去实现即可。因为营业状态是一致的。但是,本项目约定:

  • 管理端发出的请求,统一使用/admin作为前缀。
  • 用户端发出的请求,统一使用/user作为前缀。

因为访问路径不一致,故分为两个接口实现。

管理端:

复制代码
@RestController("adminShopController")
@RequestMapping("/admin/shop")
@Api(tags = "店铺相关接口")
@Slf4j
public class ShopController {

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 设置营业状态
     * @param status
     * @return
     */
    @PutMapping("/{status}")
    @ApiOperation("设置营业状态")
    public Result setStatus(@PathVariable Integer status) {
        log.info("设置店铺营业状态:{}", status == 1 ? "营业中" : "打烊中");
        redisTemplate.opsForValue().set("SHOP_STATUS", status);
        return Result.success();
    }

    /**
     * 获取营业状态
     * @return
     */
    @ApiOperation("获取营业状态")
    @GetMapping("/status")
    public Result<Integer> getStatus() {
        Integer status = (Integer)redisTemplate.opsForValue().get("SHOP_STATUS");
        log.info("获取营业状态:{}", status == 1 ? "营业中" : "打烊中");
        return Result.success(status);
    }
}

用户端:

复制代码
@RestController("userShopController")
@RequestMapping("/user/shop")
@Api(tags = "店铺相关接口")
@Slf4j
public class ShopController {

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 获取营业状态
     * @return
     */
    @ApiOperation("获取营业状态")
    @GetMapping("/status")
    public Result<Integer> getStatus() {
        Integer status = (Integer)redisTemplate.opsForValue().get("SHOP_STATUS");
        log.info("获取营业状态:{}", status == 1 ? "营业中" : "打烊中");
        return Result.success(status);
    }
}

六、Spring Boot 开发技巧

6.1 Controller 命名冲突解决

问题 :管理端和用户端都有 ShopController,Spring Bean 名称冲突。

复制代码
// 管理端
@RestController("adminShopController")  // ✅ 指定 Bean 名称
@RequestMapping("/admin/shop")
public class ShopController {
    // ...
}

// 用户端  
@RestController("userShopController")   // ✅ 指定不同名称
@RequestMapping("/user/shop")
public class ShopController {
    // ...
}

原理

  • Spring 默认使用类名首字母小写 作为 Bean 名称(shopController
  • 同名类会产生冲突,通过 @RestController("指定名称") 显式命名
  • 便于 @Autowired 精确注入,提高代码可维护性

6.2 Knife4j 接口文档分组配置

需求:将管理端和用户端接口分离展示,方便前后端协作。

复制代码
/**
 * 通过knife4j生成接口文档
 *
 * @return
 */
@Bean
public Docket docket() {
    log.info("开始创建knife4j接口文档...");
    ApiInfo apiInfo = new ApiInfoBuilder()
            .title("苍穹外卖项目接口文档")
            .version("2.0")
            .description("苍穹外卖项目接口文档")
            .build();
    Docket docket = new Docket(DocumentationType.SWAGGER_2)
            .groupName("苍穹外卖管理端")
            .apiInfo(apiInfo)
            .select()
            .apis(RequestHandlerSelectors.basePackage("com.sky.controller.admin"))
            .paths(PathSelectors.any())
            .build();
    return docket;
}

/**
 * 通过knife4j生成接口文档
 *
 * @return
 */
@Bean
public Docket docket2() {
    log.info("开始创建knife4j接口文档...");
    ApiInfo apiInfo = new ApiInfoBuilder()
            .title("苍穹外卖项目接口文档")
            .version("2.0")
            .description("苍穹外卖项目接口文档")
            .build();
    Docket docket = new Docket(DocumentationType.SWAGGER_2)
            .groupName("苍穹外卖客户端")
            .apiInfo(apiInfo)
            .select()
            .apis(RequestHandlerSelectors.basePackage("com.sky.controller.user"))
            .paths(PathSelectors.any())
            .build();
    return docket;
}

效果

  • 访问 /doc.html 时,右上角可切换 "苍穹外卖-管理端""苍穹外卖-用户端"
  • 接口按业务模块清晰分组,避免混杂

七、Redis 最佳实践 & 注意事项

7.1 Key 命名规范

复制代码
# 推荐格式:业务模块:实体:标识
user:1001:profile      # 用户资料
order:2024:1001        # 订单信息
cache:article:123:list # 缓存的文章列表

# 避免
key1, a, bbb          # 无意义命名

7.2 常见问题

|--------------|-------------------------|
| 问题 | 解决方案 |
| 缓存穿透 | 布隆过滤器 或 缓存空值 |
| 缓存击穿 | 互斥锁(SETNX)或 逻辑过期 |
| 缓存雪崩 | 随机过期时间 + 集群部署 |
| Key 膨胀 | 设置合理的过期时间(TTL) |
| 大 Key 问题 | 拆分存储,避免单个 Value 超过 10KB |

7.3 性能优化建议

  1. Pipeline 批量操作:减少网络往返次数
  2. 连接池配置:使用 Lettuce 或 Jedis 连接池
  3. 避免全量操作 :慎用 KEYS *,使用 SCAN 代替
  4. 合理设置 TTL:热点数据设置过期时间,冷数据及时清理
相关推荐
晚霞的不甘2 小时前
Flutter for OpenHarmony3D DNA 螺旋可视化:用 Canvas 构建沉浸式分子模型
前端·数据库·经验分享·flutter·3d·前端框架
马尔代夫哈哈哈9 小时前
Spring IoC&DI
数据库·sql
液态不合群10 小时前
[特殊字符] MySQL 覆盖索引详解
数据库·mysql
计算机毕设VX:Fegn089511 小时前
计算机毕业设计|基于springboot + vue蛋糕店管理系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
瀚高PG实验室11 小时前
PostgreSQL到HighgoDB数据迁移
数据库·postgresql·瀚高数据库
打码人的日常分享12 小时前
智能制造数字化工厂解决方案
数据库·安全·web安全·云计算·制造
三水不滴12 小时前
Redis 过期删除与内存淘汰机制
数据库·经验分享·redis·笔记·后端·缓存
-孤存-13 小时前
MyBatis数据库配置与SQL操作全解析
数据库·mybatis
2301_8223663513 小时前
使用Scikit-learn构建你的第一个机器学习模型
jvm·数据库·python