Redis 入门不迷路:从 NoSQL 到 Spring Data Redis 实战笔记

4412 字
22 分钟
Redis 入门不迷路:从 NoSQL 到 Spring Data Redis 实战笔记

Redis 入门不迷路:从 NoSQL 到 Spring Data Redis 实战笔记#

阅读姿势

这不是一份“命令流水账”,而是一张从概念到项目落地的学习地图。 建议打开 redis-cli 边看边敲;命令别死背,先想清楚它解决什么问题。

先把学习路线铺开#

NoSQL 是什么

Redis 核心特征

redis-cli 与桌面客户端

五大数据结构

Jedis 直连/连接池

Spring Data Redis

序列化选型

NoSQL 是什么

Redis 核心特征

redis-cli 与桌面客户端

五大数据结构

Jedis 直连/连接池

Spring Data Redis

序列化选型

初识 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:1
MongoDB: db.user.find({_id: 1})
elasticsearch: GET http://localhost:9200/users/1

事务能力不一样#

关系型数据库通常满足事务 ACID:原子性、一致性、隔离性、持久性。

NoSQL 往往不支持完整事务,或者只能保证基本一致性,更偏向 BASE。

一张表快速对比#

特性SQLNoSQL
数据结构结构化(Structured)非结构化
数据关联关联的(Relational)无关联或弱关联
查询方式SQL 查询非 SQL,方言差异大
事务特性ACIDBASE
存储方式磁盘为主内存为主
扩展性垂直扩展为主水平扩展更常见
使用场景数据结构固定、一致性/安全性要求高、关系复杂数据结构不固定、性能要求高、可水平扩展、最终一致

存储方式与扩展性#

关系型数据库基于磁盘,磁盘 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,基本用法:

Terminal window
redis-cli [options] [commands]

常见 options:

参数说明
-h 127.0.0.1指定 Redis 节点 IP,默认 127.0.0.1
-p 6379指定 Redis 节点端口,默认 6379
-a 123321指定访问密码

commands 就是 Redis 操作命令,比如:

Terminal window
ping

服务端正常会返回 pong。

图形化桌面客户端#

下载地址:https://github.com/lework/RedisDesktopManager-Windows/releases

Redis 默认有 16 个数据库,编号从 0 到 15。数据库数量可以通过配置文件调整,但数据库名称不能自定义。

使用 redis-cli 时,可以通过 select 选择数据库:

Terminal window
# 选择 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 keykey 存在时删除 key

补充说明:

  • DEL name age 可以一次删除多个 key。
  • EXISTS name 存在返回 1,不存在返回 0。
  • EXPIRE name 20 给 name 设置 20 秒有效期。
  • TTL name 未设置有效期通常返回 -1。
生产环境慎用 KEYS

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浮点类型数字自增并指定步长
SETNXkey 不存在时才添加
SETEX添加键值对并指定有效期

Key 结构:别让 id 打架#

Redis 没有 MySQL 中 Table 的概念,那怎么区分不同类型的数据?

场景举例

用户 id 是 1,商品 id 也是 1。如果都用 1 当 key,就会冲突。

解决办法是给 key 加前缀,用 : 分隔形成层级结构:

项目名:业务名:类型:id

这个格式不固定,可以按需求增删词条。假设项目名叫 reggie:

user 相关 key: reggie:user:1
dish 相关 key: reggie:dish:1

如果 Value 是 Java 对象,可以序列化为 JSON 字符串:

KEYVALUE
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。

KEYfieldvalue
reggie:user:1nameJack
reggie:user:1age21
reggie:user:2nameRose
reggie:user:2age18

常用命令:

命令描述
HSET key field value添加或修改 hash 类型 key 的 field 值
HGET key field获取 hash 类型 key 的 field 值
HMSET批量添加多个 field 值
HMGET批量获取多个 field 值
HGETALL获取所有 field 和 value
HKEYS获取所有 field
HINCRBYhash 字段值自增并指定步长
HSETNXfield 不存在时才添加

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 维护好友关系#

Terminal window
# 张三的好友:李四、王五、赵六
127.0.0.1:6379> sadd zhangsan lisi wangwu zhaoliu
(integer) 3
# 李四的好友:王五、麻子、二狗
127.0.0.1:6379> sadd lisi wangwu mazi ergou
(integer) 3

计算张三的好友人数:

Terminal window
127.0.0.1:6379> scard zhangsan
(integer) 3

计算张三和李四的共同好友:

Terminal window
127.0.0.1:6379> sinter zhangsan lisi
1) "wangwu"

查询是张三好友、但不是李四好友的人:

Terminal window
127.0.0.1:6379> sdiff zhangsan lisi
1) "zhaoliu"
2) "lisi"

查询两人的好友总人数:

Terminal window
127.0.0.1:6379> sunion zhangsan lisi
1) "wangwu"
2) "zhaoliu"
3) "ergou"
4) "lisi"
5) "mazi"

判断李四是否是张三的好友:

Terminal window
127.0.0.1:6379> sismember zhangsan lisi
(integer) 1

判断张三是否是李四的好友:

Terminal window
127.0.0.1:6379> sismember lisi zhangsan
(integer) 0

把李四从张三好友列表移除:

Terminal window
127.0.0.1:6379> srem zhangsan lisi
(integer) 1

SortedSet:排行榜专业户#

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

Terminal window
127.0.0.1:6379> zadd stu 85 Jack 89 Lucy 82 Rose 95 Tom 78 Jerry 92 Amy 76 Miles
(integer) 7

删除 Tom:

Terminal window
127.0.0.1:6379> zrem stu Tom
(integer) 1

获取 Amy 的分数:

Terminal window
127.0.0.1:6379> zscore stu Amy
"92"

获取 Rose 的排名:

Terminal window
127.0.0.1:6379> zrank stu Rose
(integer) 2

查询 80 分以下有几个学生:

Terminal window
127.0.0.1:6379> zcount stu 0 80
(integer) 2

给 Amy 加 2 分:

Terminal window
127.0.0.1:6379> zincrby stu 2 Amy
"94"

查出成绩前 3 名:

Terminal window
127.0.0.1:6379> zrange stu 0 2
1) "Miles"
2) "Jerry"
3) "Rose"

查出 80 分以下的所有同学:

Terminal window
127.0.0.1:6379> zrangebyscore stu 0 80
1) "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;
@BeforeEach
void setUp() {
// 1. 建立连接
jedis = new Jedis("101.42.225.160", 6379);
// 2. 设置密码
jedis.auth("root");
// 3. 选择库
jedis.select(0);
}

Step 3:写两个测试感受一下#

@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: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:释放资源#

@AfterEach
void 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();
}
}

测试类可以改成:

@SpringBootTest
class 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: 100ms

Step 3:注入 RedisTemplate#

有了 Spring Boot 自动装配,可以直接拿来用:

@Autowired
private RedisTemplate redisTemplate;

Step 4:写个测试#

@Test
void 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
JDK 序列化的缺点
  • 可读性差
  • 内存占用较大

可以自定义 RedisTemplate 的序列化方式,比如在 com.blog.config 包下配置:

@Configuration
public 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
@NoArgsConstructor
public class User {
private String name;
private Integer age;
}

测试方法:

@Test
void 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());
}
}

这样就可以省去自定义序列化的步骤,直接使用:

@Autowired
private StringRedisTemplate stringRedisTemplate;
@Autowired
private ObjectMapper mapper;
@Test
void 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 序列化

收个尾:三个阶段完成跃迁#

回顾一下,这篇笔记其实完成了三个阶段的升级:

  1. 认知层:理解 NoSQL 与关系型数据库的核心差异,包括结构化、关联、查询、事务、存储和扩展性。
  2. 命令层:掌握 Redis 五大数据结构(String、Hash、List、Set、SortedSet)以及通用命令。
  3. 工程层:通过 Jedis 与 Spring Data Redis 两种 Java 客户端,把 Redis 接入实际项目,并掌握序列化选型技巧。
下一步学什么

建议继续深入:持久化机制(RDB/AOF)→ 主从复制 → 哨兵模式 → 分片集群,一步步补齐 Redis 生产知识体系。

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

Redis 入门不迷路:从 NoSQL 到 Spring Data Redis 实战笔记
https://firefly.cuteleaf.cn/posts/learn-rendis/
作者
幽流啊飘
发布于
2025-08-07
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
幽流啊飘
Hello, I'm 幽流啊飘.
公告
欢迎来到我的博客!
分类
标签
最新动态
站点统计
文章
2
分类
1
标签
9
总字数
9,419
运行时长
0 天
最后活动
0 天前
站点信息
构建平台
Cloudflare Pages
博客版本
Firefly v6.16.8
文章许可
CC BY-NC-SA 4.0
文章目录