Redis 入门不迷路:从 NoSQL 到 Spring Data Redis 实战笔记
Redis 入门不迷路:从 NoSQL 到 Spring Data Redis 实战笔记
这不是一份“命令流水账”,而是一张从概念到项目落地的学习地图。
建议打开 redis-cli 边看边敲;命令别死背,先想清楚它解决什么问题。
先把学习路线铺开
初识 Redis:先别急着背命令
Redis 是一个键值型 NoSQL 数据库。这里有两个关键词:
- 键值型:数据以 Key-Value 形式存储,Value 可以是字符串、数值、JSON 等。
- NoSQL:它和传统关系型数据库的思路差别很大。
你可以先把 Redis 理解成“内存里一个特别能打的数据结构服务器”。但它不只是大号 Map,后面会看到 String、Hash、List、Set、SortedSet 各有各的脾气。
NoSQL 和 SQL 到底差在哪
NoSQL 常被解释为 Not Only SQL,也可以理解为“非关系型数据库”。它不是来完全替代 SQL 的,而是解决另一类问题。
结构化 vs 非结构化
关系型数据库是结构化的:建表时字段名、类型、约束都定好了,插入数据必须守规矩。
NoSQL 通常没有严格约束,可以是键值型、文档型,甚至图结构。
关联 vs 非关联
关系型数据库表与表之间常有外键关联。NoSQL 往往不靠数据库维护关联,要么靠业务代码,要么靠数据冗余。
比如要维护“张三和两个手机订单”的关系,可能得把订单信息塞进用户文档里:
{ "id": 1, "name": "张三", "orders": [ { "id": 1, "item": { "id": 10, "title": "荣耀6", "price": 4999 } }, { "id": 2, "item": { "id": 20, "title": "小米11", "price": 3999 } } ]}这种冗余不够优雅,所以实际项目更推荐用业务逻辑维护关联关系。
查询方式不一样
SQL 有统一语法:
SELECT id, age FROM tb_user WHERE id = 1;NoSQL 则是“各家有各家的方言”:
Redis: get user:1MongoDB: db.user.find({_id: 1})elasticsearch: GET http://localhost:9200/users/1事务能力不一样
关系型数据库通常满足事务 ACID:原子性、一致性、隔离性、持久性。
NoSQL 往往不支持完整事务,或者只能保证基本一致性,更偏向 BASE。
一张表快速对比
| 特性 | SQL | NoSQL |
|---|---|---|
| 数据结构 | 结构化(Structured) | 非结构化 |
| 数据关联 | 关联的(Relational) | 无关联或弱关联 |
| 查询方式 | SQL 查询 | 非 SQL,方言差异大 |
| 事务特性 | ACID | BASE |
| 存储方式 | 磁盘为主 | 内存为主 |
| 扩展性 | 垂直扩展为主 | 水平扩展更常见 |
| 使用场景 | 数据结构固定、一致性/安全性要求高、关系复杂 | 数据结构不固定、性能要求高、可水平扩展、最终一致 |
存储方式与扩展性
关系型数据库基于磁盘,磁盘 IO 多,性能会受影响。
NoSQL 更依赖内存,读写速度很快,性能自然更猛。
扩展性上:
- 垂直扩展:关系型数据库常见主从集群,主从数据一致,主要用于备份和读扩展。
- 水平扩展:NoSQL 可以把数据拆分到不同机器,存海量数据,解决单机内存有限的问题。
关系型数据库表之间有关联,如果硬做水平扩展,查询会变得很麻烦。选型时别只看“能扩”,也要看“扩完好不好查”。
认识 Redis:它到底强在哪
Redis 诞生于 2009 年,全称 Remote Dictionary Server(远程词典服务器),是一个基于内存的键值型 NoSQL 数据库。
核心特征:
- Key-Value 型,Value 支持多种数据结构,功能丰富
- 单线程模型,每个命令具有原子性
- 低延迟、速度快:基于内存、IO 多路复用、编码优化
- 支持数据持久化
- 支持主从集群、分片集群
- 支持多语言客户端
作者:Antirez
官网:https://redis.io/
安装与客户端:工具先备齐
Redis 安装可以参考之前整理的文章:
安装完成后,操作 Redis 常见有三类客户端:
- 命令行客户端
- 图形化桌面客户端
- 编程客户端
redis-cli 命令行客户端
Redis 自带 redis-cli,基本用法:
redis-cli [options] [commands]常见 options:
| 参数 | 说明 |
|---|---|
-h 127.0.0.1 | 指定 Redis 节点 IP,默认 127.0.0.1 |
-p 6379 | 指定 Redis 节点端口,默认 6379 |
-a 123321 | 指定访问密码 |
commands 就是 Redis 操作命令,比如:
ping服务端正常会返回 pong。
图形化桌面客户端
下载地址:https://github.com/lework/RedisDesktopManager-Windows/releases
Redis 默认有 16 个数据库,编号从 0 到 15。数据库数量可以通过配置文件调整,但数据库名称不能自定义。
使用 redis-cli 时,可以通过 select 选择数据库:
# 选择 0 号数据库select 0五大数据结构速通
Redis 是典型 key-value 数据库,key 一般是字符串,value 则有很多类型。先把通用命令拿下,再逐个击破。
通用命令:先学会“看家本领”
| 命令 | 描述 |
|---|---|
KEYS pattern | 查找所有符合给定模式的 key |
EXISTS key | 检查给定 key 是否存在 |
TYPE key | 返回 key 所储存值的类型 |
TTL key | 返回 key 剩余生存时间,单位秒 |
EXPIRE key seconds | 给 key 设置有效期 |
DEL key | key 存在时删除 key |
补充说明:
DEL name age可以一次删除多个 key。EXISTS name存在返回1,不存在返回0。EXPIRE name 20给name设置 20 秒有效期。TTL name未设置有效期通常返回-1。
Redis 是单线程的,KEYS 做模糊查询时可能阻塞其他命令。数据量大时效率极差,生产环境不建议乱用。
String:最基础,也最常用
String 是 Redis 中最简单的存储类型。Value 是字符串,但根据格式又可以分为:
| 类型 | 说明 |
|---|---|
string | 普通字符串 |
int | 整数,可自增自减 |
float | 浮点数,可自增自减 |
不管哪种格式,底层都是字节数组存储,只是编码方式不同。String 类型最大空间不能超过 512M。
常用命令:
| 命令 | 描述 |
|---|---|
SET | 添加或修改 String 类型键值对 |
GET | 根据 key 获取 String 类型 value |
MSET | 批量添加多个 String 类型键值对 |
MGET | 根据多个 key 获取多个 value |
INCR | 整型 key 自增 1 |
INCRBY | 整型 key 自增并指定步长,如 INCRBY num 2 |
INCRBYFLOAT | 浮点类型数字自增并指定步长 |
SETNX | key 不存在时才添加 |
SETEX | 添加键值对并指定有效期 |
Key 结构:别让 id 打架
Redis 没有 MySQL 中 Table 的概念,那怎么区分不同类型的数据?
用户 id 是 1,商品 id 也是 1。如果都用 1 当 key,就会冲突。
解决办法是给 key 加前缀,用 : 分隔形成层级结构:
项目名:业务名:类型:id这个格式不固定,可以按需求增删词条。假设项目名叫 reggie:
user 相关 key: reggie:user:1dish 相关 key: reggie:dish:1如果 Value 是 Java 对象,可以序列化为 JSON 字符串:
| KEY | VALUE |
|---|---|
reggie:user:1 | {"id":1, "name": "Jack", "age": 21} |
reggie:dish:1 | {"id":1, "name": "鲟鱼火锅", "price": 4999} |
图形化客户端里也会按相同前缀展示层次结构,看起来清爽很多。
Hash:对象字段可以单独改
Hash 类型也叫散列,Value 是一个无序字典,类似 Java 的 HashMap。
String 存对象时,要改某个属性得整体改;Hash 可以把对象每个字段独立存储,针对单个字段做 CRUD。
| KEY | field | value |
|---|---|---|
reggie:user:1 | name | Jack |
reggie:user:1 | age | 21 |
reggie:user:2 | name | Rose |
reggie:user:2 | age | 18 |
常用命令:
| 命令 | 描述 |
|---|---|
HSET key field value | 添加或修改 hash 类型 key 的 field 值 |
HGET key field | 获取 hash 类型 key 的 field 值 |
HMSET | 批量添加多个 field 值 |
HMGET | 批量获取多个 field 值 |
HGETALL | 获取所有 field 和 value |
HKEYS | 获取所有 field |
HINCRBY | hash 字段值自增并指定步长 |
HSETNX | field 不存在时才添加 |
List:像 LinkedList 一样排队
Redis 的 List 类似 Java 的 LinkedList,可以看成双向链表,支持正向和反向检索。
特点:
- 有序
- 元素可重复
- 插入和删除快
- 查询速度一般
常用来存有序数据,比如朋友圈点赞列表、评论列表。
常用命令:
| 命令 | 描述 |
|---|---|
LPUSH key element ... | 向列表左侧插入一个或多个元素 |
LPOP key | 移除并返回列表左侧第一个元素,没有则返回 nil |
RPUSH key element ... | 向列表右侧插入一个或多个元素 |
RPOP key | 移除并返回列表右侧第一个元素 |
LRANGE key start end | 返回指定角标范围内的元素 |
BLPOP / BRPOP | 类似 LPOP/RPOP,没有元素时等待指定时间 |
Set:去重和交集的好帮手
Redis 的 Set 类似 Java 的 HashSet,可以理解为 value 为 null 的 HashMap。
特点:
- 无序
- 元素不可重复
- 查找快
- 支持交集、并集、差集
常用命令:
| 命令 | 描述 |
|---|---|
SADD key member ... | 向 set 添加一个或多个元素 |
SREM key member ... | 移除指定元素 |
SCARD key | 返回元素个数 |
SISMEMBER key member | 判断元素是否存在 |
SMEMBERS | 获取所有元素 |
SINTER key1 key2 ... | 求交集 |
SUNION key1 key2 ... | 求并集 |
SDIFF key1 key2 ... | 求差集 |
小练习:用 Set 维护好友关系
# 张三的好友:李四、王五、赵六127.0.0.1:6379> sadd zhangsan lisi wangwu zhaoliu(integer) 3
# 李四的好友:王五、麻子、二狗127.0.0.1:6379> sadd lisi wangwu mazi ergou(integer) 3计算张三的好友人数:
127.0.0.1:6379> scard zhangsan(integer) 3计算张三和李四的共同好友:
127.0.0.1:6379> sinter zhangsan lisi1) "wangwu"查询是张三好友、但不是李四好友的人:
127.0.0.1:6379> sdiff zhangsan lisi1) "zhaoliu"2) "lisi"查询两人的好友总人数:
127.0.0.1:6379> sunion zhangsan lisi1) "wangwu"2) "zhaoliu"3) "ergou"4) "lisi"5) "mazi"判断李四是否是张三的好友:
127.0.0.1:6379> sismember zhangsan lisi(integer) 1判断张三是否是李四的好友:
127.0.0.1:6379> sismember lisi zhangsan(integer) 0把李四从张三好友列表移除:
127.0.0.1:6379> srem zhangsan lisi(integer) 1SortedSet:排行榜专业户
SortedSet 是可排序的 Set,和 Java 的 TreeSet 有点像,但底层差别很大。每个元素带一个 score,可基于 score 排序,底层实现是跳表(SkipList)+ hash 表。
特点:
- 可排序
- 元素不重复
- 查询速度快
因为能排序,SortedSet 经常用来做排行榜。
常用命令:
| 命令 | 描述 |
|---|---|
ZADD key score member | 添加一个或多个元素,已存在则更新 score |
ZREM key member | 删除指定元素 |
ZSCORE key member | 获取指定元素 score |
ZRANK key member | 获取指定元素排名 |
ZCARD key | 获取元素个数 |
ZCOUNT key min max | 统计 score 在给定范围内的元素个数 |
ZINCRBY key increment member | 指定元素自增,步长为 increment |
ZRANGE key min max | 按 score 升序获取指定排名范围元素 |
ZRANGEBYSCORE key min max | 按 score 获取指定分数范围元素 |
ZDIFF / ZINTER / ZUNION | 求差集、交集、并集 |
排名默认升序。如果要降序,在命令的 Z 后面加 REV:
- 升序:
ZRANK key member - 降序:
ZREVRANK key member
小练习:用 SortedSet 做班级成绩榜
存入学生成绩:Jack 85, Lucy 89, Rose 82, Tom 95, Jerry 78, Amy 92, Miles 76
127.0.0.1:6379> zadd stu 85 Jack 89 Lucy 82 Rose 95 Tom 78 Jerry 92 Amy 76 Miles(integer) 7删除 Tom:
127.0.0.1:6379> zrem stu Tom(integer) 1获取 Amy 的分数:
127.0.0.1:6379> zscore stu Amy"92"获取 Rose 的排名:
127.0.0.1:6379> zrank stu Rose(integer) 2查询 80 分以下有几个学生:
127.0.0.1:6379> zcount stu 0 80(integer) 2给 Amy 加 2 分:
127.0.0.1:6379> zincrby stu 2 Amy"94"查出成绩前 3 名:
127.0.0.1:6379> zrange stu 0 21) "Miles"2) "Jerry"3) "Rose"查出 80 分以下的所有同学:
127.0.0.1:6379> zrangebyscore stu 0 801) "Miles"2) "Jerry"Java 客户端:Jedis 与 Spring Data Redis
主流 Redis Java 客户端可以这样理解:
| 客户端 | 说明 |
|---|---|
| Jedis / Lettuce | 提供 Redis 命令对应的 API,方便操作 Redis;Spring Data Redis 对它们做了抽象和封装 |
| Redisson | 在 Redis 基础上实现分布式可伸缩 Java 数据结构(Map、Queue 等),支持跨进程同步机制(Lock、Semaphore 等) |
Spring Data Redis 对 Jedis/Lettuce 都做了封装,后期建议直接以 Spring Data Redis 为主。
Jedis 快速入门
Step 1:导入 Maven 坐标
<!--jedis--><dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>3.7.0</version></dependency><!--单元测试--><dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>5.7.0</version> <scope>test</scope></dependency>Step 2:建立连接
private Jedis jedis;
@BeforeEachvoid setUp() { // 1. 建立连接 jedis = new Jedis("101.42.225.160", 6379); // 2. 设置密码 jedis.auth("root"); // 3. 选择库 jedis.select(0);}Step 3:写两个测试感受一下
@Testvoid testString() { jedis.set("name", "Kyle"); String name = jedis.get("name"); System.out.println("name = " + name);}
@Testvoid testHash() { jedis.hset("reggie:user:1", "name", "Jack"); jedis.hset("reggie:user:2", "name", "Rose"); jedis.hset("reggie:user:1", "age", "21"); jedis.hset("reggie:user:2", "age", "18"); Map<String, String> map = jedis.hgetAll("reggie:user:1"); System.out.println(map);}Step 4:释放资源
@AfterEachvoid tearDown() { if (jedis != null) { jedis.close(); }}Jedis 连接池:别频繁创建销毁连接
Jedis 本身是线程不安全的,频繁创建和销毁连接也有性能损耗,推荐使用 Jedis 连接池。
public class JedisConnectionFactory {
private static JedisPool jedisPool;
static { // 配置连接池 JedisPoolConfig poolConfig = new JedisPoolConfig(); poolConfig.setMaxTotal(8); poolConfig.setMaxIdle(8); poolConfig.setMinIdle(0); poolConfig.setMaxWaitMillis(1000); // 创建连接池对象,参数:连接池配置、服务端 IP、服务端端口、超时时间、密码 jedisPool = new JedisPool(poolConfig, "101.42.225.160", 6379, 1000, "root"); }
public static Jedis getJedis() { return jedisPool.getResource(); }}测试类可以改成:
@SpringBootTestclass RedisTestApplicationTests {
private Jedis jedis = JedisConnectionFactory.getJedis();
@Test void testString() { jedis.set("name", "Kyle"); String name = jedis.get("name"); System.out.println("name = " + name); }
@Test void testHash() { jedis.hset("reggie:user:1", "name", "Jack"); jedis.hset("reggie:user:2", "name", "Rose"); jedis.hset("reggie:user:3", "name", "Kyle"); jedis.hset("reggie:user:1", "age", "21"); jedis.hset("reggie:user:2", "age", "18"); jedis.hset("reggie:user:3", "age", "18"); Map<String, String> map = jedis.hgetAll("reggie:user:1"); System.out.println(map); }
@AfterEach void tearDown() { if (jedis != null) { jedis.close(); } }}Spring Data Redis:项目里更常见的选择
Spring Data 是 Spring 的数据操作模块,其中对 Redis 的集成模块叫 Spring Data Redis。
官网:https://spring.io/projects/spring-data-redis
主要能力:
- 整合不同 Redis 客户端(Lettuce 和 Jedis)
- 提供
RedisTemplate统一 API - 支持 Redis 发布订阅模型
- 支持 Redis 哨兵和集群
- 支持基于 Lettuce 的响应式编程
- 支持 JDK、JSON、字符串、Spring 对象等序列化方式
- 支持基于 Redis 的 JDKCollection 实现
RedisTemplate 把不同数据类型的操作封装到了不同 API 中:
| API | 返回值类型 | 说明 |
|---|---|---|
redisTemplate.opsForValue() | ValueOperations | 操作 String 类型数据 |
redisTemplate.opsForHash() | HashOperations | 操作 Hash 类型数据 |
redisTemplate.opsForList() | ListOperations | 操作 List 类型数据 |
redisTemplate.opsForSet() | SetOperations | 操作 Set 类型数据 |
redisTemplate.opsForZSet() | ZSetOperations | 操作 SortedSet 类型数据 |
redisTemplate | — | 通用命令 |
Step 1:导入依赖
<!--redis依赖--><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId></dependency><!--common-pool--><dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId></dependency><!--Jackson依赖--><dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId></dependency><!--lombok--><dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional></dependency><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope></dependency>Step 2:配置 Redis
spring: redis: host: 101.42.225.160 port: 6379 password: root lettuce: pool: max-active: 8 max-idle: 8 min-idle: 0 max-wait: 100msStep 3:注入 RedisTemplate
有了 Spring Boot 自动装配,可以直接拿来用:
@Autowiredprivate RedisTemplate redisTemplate;Step 4:写个测试
@Testvoid stringTest() { redisTemplate.opsForValue().set("username", "David"); String username = (String) redisTemplate.opsForValue().get("username"); System.out.println(username);}自定义序列化:默认 JDK 序列化不太好看
RedisTemplate 可以接收任意 Object 作为值写入 Redis,但默认会用 JDK 序列化,结果可能是这样的:
\xAC\xED\x00\x05t\x00\x06\xE5\xBC\xA0\xE4\xB8\x89- 可读性差
- 内存占用较大
可以自定义 RedisTemplate 的序列化方式,比如在 com.blog.config 包下配置:
@Configurationpublic class RedisConfig {
@Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) { // 创建 RedisTemplate 对象 RedisTemplate<String, Object> template = new RedisTemplate<>(); // 设置连接工厂 template.setConnectionFactory(connectionFactory); // 创建 JSON 序列化工具 GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer(); // 设置 Key 的序列化 template.setKeySerializer(RedisSerializer.string()); template.setHashKeySerializer(RedisSerializer.string()); // 设置 Value 的序列化 template.setValueSerializer(jsonRedisSerializer); template.setHashValueSerializer(jsonRedisSerializer); // 返回 return template; }}写个 User 类试试:
@Data@AllArgsConstructor@NoArgsConstructorpublic class User { private String name; private Integer age;}测试方法:
@Testvoid stringTest() { redisTemplate.opsForValue().set("userdata", new User("张三", 18));}采用 JSON 序列化后,结果大致如下:
{ "@class": "com.blog.entity.User", "name": "张三", "age": 18}可读性提升明显,而且能把 Java 对象自动序列化为 JSON,查询时再自动反序列化回 Java 对象。
@class 字段它记录了序列化时对应的 class 名称,目的是查询时能自动反序列化。 但这会带来额外内存开销,所以还有更省内存的方案。
StringRedisTemplate:手动序列化,内存更清爽
为了节省内存,可以不使用 JSON 序列化器处理 value,而是统一使用 String 序列化器。这样只能存 String 类型的 key 和 value;需要存 Java 对象时,手动完成序列化和反序列化。
因为序列化和反序列化都由我们自己实现,Spring Data Redis 就不会把 class 信息写进 Redis 了。
Spring Data Redis 提供了 RedisTemplate 的子类 StringRedisTemplate,它的 key 和 value 默认就是 String 序列化:
public class StringRedisTemplate extends RedisTemplate<String, String> { public StringRedisTemplate() { this.setKeySerializer(RedisSerializer.string()); this.setValueSerializer(RedisSerializer.string()); this.setHashKeySerializer(RedisSerializer.string()); this.setHashValueSerializer(RedisSerializer.string()); }}这样就可以省去自定义序列化的步骤,直接使用:
@Autowiredprivate StringRedisTemplate stringRedisTemplate;
@Autowiredprivate ObjectMapper mapper;
@Testvoid stringTest() throws JsonProcessingException { // 创建对象 User user = new User("张三", 18); // 手动序列化 String json = mapper.writeValueAsString(user); // 写入数据 stringRedisTemplate.opsForValue().set("userdata", json); // 获取数据 String userdata = stringRedisTemplate.opsForValue().get("userdata"); // 手动反序列化 User readValue = mapper.readValue(userdata, User.class); System.out.println(readValue);}存入 Redis 中是这样的:
{ "name": "张三", "age": 18}- 不想写序列化代码、且不介意多存
@class字段 → 使用RedisTemplate+GenericJackson2JsonRedisSerializer - 追求极致内存、希望 key 清晰易读 → 使用
StringRedisTemplate+ 手动 JSON 序列化
收个尾:三个阶段完成跃迁
回顾一下,这篇笔记其实完成了三个阶段的升级:
- 认知层:理解 NoSQL 与关系型数据库的核心差异,包括结构化、关联、查询、事务、存储和扩展性。
- 命令层:掌握 Redis 五大数据结构(String、Hash、List、Set、SortedSet)以及通用命令。
- 工程层:通过 Jedis 与 Spring Data Redis 两种 Java 客户端,把 Redis 接入实际项目,并掌握序列化选型技巧。
建议继续深入:持久化机制(RDB/AOF)→ 主从复制 → 哨兵模式 → 分片集群,一步步补齐 Redis 生产知识体系。
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!




