Redis
适合后端学习的同学
引导视频:学 Redis 前,请务必先看完这个!_哔哩哔哩_bilibili
基础入门
Redis 是什么?
全称Remote Dictionary Server,远程词典服务器,是一个基于内存的键值对NoSQL数据库
Redis是知名的高性能内存 K / V 存储系统
SQL和NoSQL
| SQL | NoSQL | |
|---|---|---|
| 数据结构 | 结构化(Structured) | 非结构化 |
| 数据关联 | 关联的(Relational) | 无关联的 |
| 查询方式 | SQL查询 | 非SQL |
| 事务特性 | ACID | BASE |
| 存储方式 | 磁盘 | 内存 |
| 扩展性 | 垂直 | 水平 |
| 使用场景 | 1)数据结构固定 2)相关业务对数据安全性、一致性要求较高 | 1)数据结构不固定 2)对一致性、安全性要求不高 3)对性能有要求 |
补充:
键值类型(Redis)
文档类型(MongoDB)
列类型(HBase)
Graph类型(Neo4j)
Redis 特点
键值(key-value)型,value支持多种不同数据结构,功能丰富
单线程,每个命令具备原子性
低延迟、速度快(基于内存、IO多路复用、良好编码)
支持数据持久化
支持主从集群、分片集群
多语言客户端
Redis 安装
这是基于Liunx服务器的安装(Linux版本为CentOS 7.)
Liunx服务器的安装参考我写的这篇文章
因为大多数企业都是基于Linux服务器来部署项目,而且Redis官方没有提供Window版的安装包。
1.资源获取
这里下载7.0的
去官网https://redis.io/下也可以去我提供的资源中获取redis-7.0.15.tar.gz
Redis
下载成功
2.资源上传
写下来准备在一台Linux服务器上安装 (我这里是使用Finalshell连接我在本人Window版电脑上装的Linux虚拟机,具体安装配置步骤看我主页Linux的相关文档)
进入/usr/local/src文件夹中上传(权限不足则进入管理员,记得重新cd /usr/local/src)
图形化上传
如果失败
![]()
在Window本机使用cmd命令上传(用管理员身份)# 基本格式 scp C:\Users\13715\Desktop\fsdownload\redis-7.0.15.tar.gz username@虚拟机IP地址:/目标路径/ # 实际示例(替换为你的信息) scp D:\13715\redis-7.0.15.tar.gz root@192.168.64.128:/usr/local/src上传成功
3.资源解压
使用管理员权限进入/usr/local/src进行解压,
tar -zxvf redis-7.0.15.tar.gztar
解压后在src目录下有个redis-7.0.15文件夹
进入安装目录
cd redis-7.0.15
运行编译命令
make && make install
验证成功(/usr/local/bin目录下有相关文件)
该目录已经默认配置到环境变量,因此可以在任意目录下运行这些命令:
redis-cil:是redis提供的命令行客户端
redis-server::是redis的服务器启动脚本
redis-sentinel:是redis的哨兵启动脚本
这是Redis官网指令帮助文档
Redis启动
前台启动
redis-server
这种启动,会阻塞整个会话窗口,窗口关闭或者按下Ctrl+C,则Redis停止 (不推荐使用)
后台启动
如果要让Redis以后台方式启动,则必须修改Redis配置文件(root权限),就在/usr/local/src/redis-7.0.15目录下,名字叫redis.conf
先备份
使用vi命令回车进入
vi redis.conf
修改redis.conf配置
#监听的地址,默认是127.0.0.1,会导致只能只能在本地访问
#修改为0.0.0.0则可以在任意IP访问,但是生产环境不要设置为0.0.0.0
bind 0.0.0.0
#守护进程,修改为yes后即可后台运行
daemonize yes
#密码,设置后访问Redis必须输入密码
requirepass 123321
在命令模式时可以使用 / + 关键词搜索,定位到要修改的位置,点击Esc退出,点击 i 进入到插入模式
命令模式时输入 /,搜索requirepass(重复搜几次就能找到)
改成如图,保存 (记得去掉井号)
Redis的其他常见配置
#监听的端口
port 6379
#工作目录,默认是当前目录,也就是运行redis-server时的命令,日志、持久化等文件会保存在这个目录
dir .
#数据库数量,设置为1,代表只使用1个库,默认有16个库,编号0~15
database 1
#设置redis能够使用的最大内存
maxmemory 512mb
#日志文件,默认为空,不记录日志,可以指定日志文件名
logfile "redis.log"
启动Redis
#进入redis安装目录
cd /usr/local/src/redis-7.0.15
#启动
redis-server redis.conf
这样就后台启动成功
检查
ps -ef | grep redis
停止后台启动的redis服务
开机自启
新建一个系统服务文件
vi /etc/systemd/system/redis.service
复制一下内容(注意自己的安装目录)
[Unit]
Description=redis-server
After=network.target
[Service]
Type=forking
ExecStart=/usr/local/bin/redis-server /usr/local/src/redis-7.0.15/redis.conf
PrivateTmp=true
[Install]
WantedBy=multi-user.target
退出保存
为使生效,重载系统服务
systemctl daemon-reload
注意:现在还没实现开机自启动,只是redis被系统管理了
可以通过systemctl管理redis(启动、查看状态和停止)
开机自启命令
systemctl enable redis
连接方式
实现数据的CRUD,需要用到Redis客户端,包括:
- 命令行客户端(命令形式交互)
- 图形化桌面客户端
- 编程客户端(各种不同语言的客户端)
命令行客户端
Redis安装完成后自带了命令行客户端:redis-cli
redis-cli [options] [commonds]
其中常见的options有:
-h 127.0.0.1:指定要连接的redis节点的IP地址,默认是127.0.0.1-p 6379:指定要连接的redis节点的端口,默认是6379-a 123321:指定redis的访问密码
其中的commonds就是Redis的操作命令,例如:
ping:与redis服务器做心跳测试,服务器正常会返回pong
不指定commonds时,会进入redis-cli的交互控制台
图中的警告信息时提醒我们这样 -a 输入密码的方式不安全
成功连接后就可以开始使用了
可以向里面添加键值,默认先向0号库,也可以选择其他库
图形化桌面客户端
我们选择 Another Redis Desktop Manager
找到我们想要的安装包
在虚拟机中创建放置该软件的文件夹(我是/home/yzy/Downloads/redisGUI)
在该目录下执行
wget https://gitee.com/qishibo/AnotherRedisDesktopManager/releases/download/v1.7.1/Another-Redis-Desktop-Manager-linux-1.7.1-x86_64.AppImage
下载好后得
给予执行权限
chmod +x Another-Redis-Desktop-Manager-linux-1.7.1-x86_64.AppImage
回到虚拟机看到
打开虚拟机终端,进入该下载目录,运行
./Another-Redis-Desktop-Manager-linux-1.7.1-x86_64.AppImage
可能会出错
使用这个命令就可以
./Another-Redis-Desktop-Manager-linux-1.7.1-x86_64.AppImage --no-sandbox
原因解释:
就像一道安全门出了问题。
- 这个软件自带了一道“安全门”(沙盒)。
- 这道门的作用是关住软件,防止它乱动你电脑里的其他东西。
- 但是,在你的电脑上,这道门的“锁”坏了。
- 软件启动时检查这个锁,发现是坏的,它心想:“天啊,门锁坏了太危险了!我干脆不启动了!” 于是它就崩溃了。
--no-sandbox这个命令的意思就是:
- “别管那道破门了,直接进来吧!”
- 你告诉软件,跳过安全检查,直接运行。
所以,不是软件本身坏了,而是它自带的安全机制在你的系统上不兼容。你通过命令关掉了这个机制,所以它能跑了。
对于你个人在电脑上使用这个工具来说,关掉这个安全机制是完全可以接受的。
创建连接,输入密码即可
如果是在自己电脑上的连接,要填写虚拟机在内网的ip地址
试着输入一下
查看到
快捷打开方式设置 (设置用户环境变量)
编辑用户目录下的一个配置文件
vi ~/.bashrc![]()
使其生效source ~/.bashrc在任何地方输入 redis-gui 都可以打开redis图形化了
![]()
编程客户端
后续讲Java客户端
Redis命令
所以命令在官方文档Commands | Docs 可以根据类型查询

数据结构
Redis是一个key-value的数据库,key一般是String类型,value的类型多种如下:
基础类型
- String:hello world
- Hash:{name:“Jack”,age:21} 本质为哈希表
- List:[A -> B -> C -> C] 本质为链表
- Set:{A,B,C}无序集合
- SortedSet:{A:1,B:2,C:3}可排序集合
特殊类型
- GEO:{A:(120.3,30.5)}
- BitMap:0110110101110101011
- HyperLog:0110110101110101011
通用命令
Redis 的通用命令是与数据类型无关的命令,它们不专门针对 String、List、Hash 等某一种数据结构,而是用于管理 Redis 服务器本身、管理所有的键(key)以及查看服务器状态。
KEYS
查看符合模板pattern的所有key,不建议在生产环境设备上使用
KEYS pattern
pattern参考下面(官网查看)
h?llomatches , andhello``hallo``hxlloh*llomatches andhllo``heeeelloh[ae]llomatches and but nothello``hallo,``hilloh[^e]llomatches , , … but nothallo``hbllo``helloh[a-b]llomatches andhallo``hbllo
例子
但是当redis数据量大时,用这种模糊查询,可能会给服务器带来巨大负担,花费大量时间,redis又是单线程的,在他这个搜索时间内会造成服务的阻塞,其他命令执行不了,所以不建议在生产设备上使用
DEL
删除指定的key
DEL key [key ...]
例子
mset是批量添加
del会返回成功删除的个数(如上面操作者想删除4个,实际删除了3个)
EXISTS
判断key是否存在
EXISTS key [key ...]
例子

EXPIRE
给一个key设置有效期,有效期到期时该key会被自动删除
因为Redis是基于内存的,如果往里面存入大量的永久数据,内存会被占满(例如短信验证码的5分钟储存)
EXPIRE key seconds [NX | XX | GT | LT]
NX- 仅当键没有过期时间时设置
> SET mykey "hello"
OK
> TTL mykey # 查看过期时间,返回 -1 表示永不过期
(integer) -1
> EXPIRE mykey 60 NX # 设置60秒过期(成功,因为原本没有过期时间)
(integer) 1
> EXPIRE mykey 30 NX # 再次设置失败,因为已经有过期时间了
(integer) 0
XX- 仅当键已有过期时间时更新
> SET mykey "hello"
OK
> EXPIRE mykey 60 XX # 失败,因为键还没有设置过期时间
(integer) 0
> EXPIRE mykey 60 # 先设置过期时间
(integer) 1
> EXPIRE mykey 30 XX # 成功,更新已存在的过期时间
(integer) 1
GT- 仅当新过期时间大于当前剩余时间时设置
> SET mykey "hello"
OK
> EXPIRE mykey 60 # 设置60秒过期
(integer) 1
# 假设现在剩余50秒
> EXPIRE mykey 40 GT # 失败,40 < 50
(integer) 0
> EXPIRE mykey 70 GT # 成功,70 > 50
(integer) 1
LT- 仅当新过期时间小于当前剩余时间时设置
> SET mykey "hello"
OK
> EXPIRE mykey 60 # 设置60秒过期
(integer) 1
# 假设现在剩余50秒
> EXPIRE mykey 70 LT # 失败,70 > 50
(integer) 0
> EXPIRE mykey 30 LT # 成功,30 < 50
(integer) 1
TTL:查看一个KEY的剩余有效期
TTL key
例子
过期的返回-2,永久的返回-1
String类型
在Redis中,String类型(value是String类型的)是最基本的数据类型,但实际上它可以存储三种类型的值:
- 字符串(包括文本和二进制数据)(普通字符串)
- 整数(可以做自增、自减操作)
- 浮点数(可以做自增、自减操作)
虽然Redis对外统一将这些东西称为String类型,但在内部处理时,会根据值的不同采取不同的编码方式,以便更高效地存储和操作。(字符串类型的最大空间不能超过512m)
| KEY | VALUE |
|---|---|
| msg | hello world |
| num | 10 |
| score | 92.5 |
注意:这三个类型对应的内部编码
- 整数类型 →
int编码 - 浮点数类型 →
embstr编码 - 字符串类型(短,≤44字节) →
embstr编码 - 字符串类型(长,>44字节) →
raw编码
执行SET时,值类型检测就已经分出整数、浮点数和字符串了,为什么还要将它们三个归为String??
因为SET属于String类型的命令。。。
String类型的命令:
- SET:添加一个String类型键值对,或修改一个已经存在的String类型键值对
- GET:根据key获取String类型的value
- MSET:批量添加多个String类型的键值对
- MGET:根据多个key获取多个String类型的value
- INCR:让一个整形的key自增1 (执行一次让int类型的加一)
- INCRBY:指定步长自增(自减)
- INCRBYFLOAT:让一个浮点数自增并指定步长
- SET UX:添加一个键值对,前提是key不存在,否则不执行(如图第二句没有执行)
- SET EX:添加一个键值对,并指定有效期
KEY的层级格式
思考:Redis没有类似MySQL中的Table的概念,我们如何区别不同类型的key呢?
例如,需要存储用户、商品信息到Redis,用户的id是1,商品的id也是1,难道两个都像下面储存吗?
SET id 1
key允许有多个单词形成层级结构,多个单词之间用 ’ : ’ 隔开
项目名:业务名:类型:id
但是不是project:user:id 1这样储存了,value是要存储对象的 (对象序列化后的json字符串)
| key | value |
|---|---|
| project:user:1 | {“id”:1,“name”:“Jack”,“age”:21} |
| project:product:1 | {“id”:1,“name”:“华为”,“price”:1999} |
执行后,界面如下
Hash类型
也叫散列,其value是一个无序字典,类似Java中的HashMap结构
相较之前,String类型是将对象序列化为JSON字符串后存储,当修改某个对象字段很不方便:
| key | value |
|---|---|
| project:user:1 | {“id”:1,“name”:“Jack”,“age”:21} |
| project:product:1 | {“id”:1,“name”:“华为”,“price”:1999} |
Hash结构可以将对象中的某个字段独立存储,可针对单个字段做CRUD:
| KEY | VALUE | VALUE |
|---|---|---|
| field | value | |
| project:user:1 | name | Jack |
| age | 21 | |
| project:product:1 | name | 华为 |
| price | 1999 |
Hash类型的命令:
- HSET key field value:添加或修改hash类型key的field值
- HSET key field:获取一个hash类型key的field的值
- HMSET:批量添加多个hash类型key的field值
- HMGET:批量获取多个hash类型key的field值
- HGETALL:获取一个hash类型的key中的所有的field和value
- HKEYS:获取一个hash类型的key中的所有的field
- HVALS:获取一个hash类型的key中的所有的value
- HINCRBY:让一个hash类型key的字段值自增并指定步长
-
HSET NX:添加一个hash类型的key的field值,前提是这个field不存在,否则不执行
project:user:4的sex字段已经存在,修改不了
List类型
Redis中的List类型(value的类型是List的)与Java中的LinkedList类似,可以看作一个双向链表结构
- 有序
- 元素可以重复
- 插入和删除快(只需改变节点的指向)
- 查询速度一般(每个节点遍历)
- 适用场景如朋友圈点赞列表、评论列表
支持正向检索和反向检索
List类型的命令:
- LPUSH key element ……:向列表左侧插入一个或多个元素
> lpush users 1 2 3
注意最后存进去的顺序
- LPOP key [count]:指定个数移除并返回列表左侧元素(不指定默认一个),没有则返回null
- RPUSH key element ……:向列表右侧插入一个或多个元素
> rpush users 4 5 6
6
- RPOP key [count]:指定个数移除并返回列表右侧元素(不指定默认一个),没有则返回null
- LRANGE key star end:返回一段角标范围内的所有元素(编号012……)
- BLPOP和BRPOP:与LPOP和RPOP类似,只不过在没有元素时等待指定时间,而不是直接返回null,属于阻塞式获取
等待中(等待120秒)
新开一个窗口
回到原窗口,等到结果(等待时间是58秒)
所以可以用List结构模拟,一个栈、一个队列、一个阻塞队列
Set类型
Redis的Set结构与Java中的HashSet(集合)类似,可以看作是一个value为null的HashMap
HashSet 实际上是基于 HashMap 实现的:
HashSet ↓ HashMap (只使用 Key 部分) ↓ 数组 + 链表/红黑树
- 无序
- 元素不可重复
- 查找快
- 支持交集、并集、差集等功能
Set类型的命令
- SADD key member ……:向set添加一个或多个元素
- SREM key member ……:移除set中的指定元素
- SCARD key:返回set中的元素个数
- SISMEMBER key member:判断一个元素是否存在于set中
- SMEMBERS:获取set中的所有元素
- SINTER key1 key2 ……:求key1与key2的交集
- SDIFF key1 key2 ……:求key1与key2的差集
例子:查询是张三好友却不是李四的
sdiff zhangsan lisi
- SUNION key1 key2 ……:求key1与key2的并集
SortedSet
Redis的SortedSet是一个可排序的set集合,与Java中的TreeSet有些类似,但底层数据结构却差别很大(TreeSet是数组+链表加红黑树)
SortedSet中的每一个元素都有一个score属性,可以基于score属性对元素排序(其实就是Set多了一个属性,这个属性用来排名)
底层的实现是一个跳表(SkipList)加hash表
- 可排序
- 元素不重复
- 查询速度快
- 适用于排行榜等功能
SortedSet类型的命令
- ZADD key score member [score member……]:添加一个或多个元素到sorted set,如果已经存在则更新其score值
> zadd stus 98 Tom 76 Marry 87 Jack 90 Rose 83 Amy 79 Jerry 80 Lucy
7
插入时是乱序的,但是插入后是排好序的
- ZREM key member:删除sorted set中的一个指定元素
> zrem stus Tom
1
- ZSCORE key member:获取sorted set中的指定元素的score值
- ZRANK key member:获取sorted set中的指定元素的排名
> zrank stus Rose
5
> zrevrank stus Rose
0
因为升序
升序排列(ZRANK 的视角):
0: Tom (60分)
1: Jerry (70分)
2: Mike (75分)
3: Lucy (80分)
4: John (85分)
5: Rose (95分) ← ZRANK 返回 5
-
ZCARD key:获取sorted set的元素个数
-
ZCOUNT key min max:统计score值在给定范围内的所有元素的个数 (min、max是score值)
-
ZINCRBY key increment member:让sorted set中的指定元素的score自增,步长为指定的increment
-
ZRANGE key min max:按照score排序后,获取排名范围内的元素 (min、max是名次,升序0~4是倒数前五名)
降序之后,0~2才是前3名
- ZRANGEBYSCORE key min max:按照score排序后,获取指定score范围内的元素 (min、max是score)
- ZDIFF、ZINTER、ZUNION:求差集、交集、并集
注意:上面的排名默认都是升序,如果要降序则在命令的Z后面添加REV即可
Redis的Java客户端
三种常用的Java客户端
Jedis - 最经典的客户端
- 老牌经典,使用最广泛
- 同步阻塞的 API()
一个实例一次只能做一件事
Jedis jedis = new Jedis("餐厅电话", 6379);
// 就像打电话:你说了"我要宫保鸡丁",然后必须拿着电话等
String 结果 = jedis.get("菜单"); // 📞 在这里等,不能挂电话
// 在餐厅回复"宫保鸡丁58元"之前,你什么都干不了
System.out.println("价格是:" + 结果);
- 轻量级,API 与 Redis 命令基本一一对应
- 线程不安全,需要配合连接池使用
危险:多个线程共享同一个Jedis实例 ->
// ❌ 危险:多个线程共享同一个Jedis实例
public class 危险例子 {
private static Jedis jedis = new Jedis("localhost", 6379);
public static void 线程A() {
jedis.set("key", "A的值"); // 可能被线程B干扰
}
public static void 线程B() {
jedis.set("key", "B的值"); // 可能覆盖线程A的设置
}
}
安全:每个线程有自己的Jedis实例 ->
// ✅ 安全:每个线程有自己的Jedis实例
public class 安全例子 {
public static void 线程A() {
Jedis 我的电话 = new Jedis("localhost", 6379); // A的专用电话
我的电话.set("key", "A的值");
我的电话.close();
}
public static void 线程B() {
Jedis 我的电话 = new Jedis("localhost", 6379); // B的专用电话
我的电话.set("key", "B的值");
我的电话.close();
}
}
为了不用每次新建/销毁连接->
使用连接池为每个线程获得独立的连接(每个线程都有自己实例)
// 创建电话总机(连接池)
JedisPool 电话总机 = new JedisPool("餐厅", 6379);
// 员工需要打电话时,向总机申请一部电话
try (Jedis 分机 = 电话总机.getResource()) {
// 使用分机打电话
分机.set("订单", "员工午餐");
// 打完电话自动归还给总机
}
// 另一个员工也要打电话
try (Jedis 分机2 = 电话总机.getResource()) {
分机2.set("订单", "客户礼品");
}
Lettuce - 现代化的客户端
- Spring Boot 2.x 默认客户端
- 异步非阻塞,基于 Netty
// 传统Jedis(电话订餐)
Jedis jedis = new Jedis();
String 结果 = jedis.get("菜单"); // 📞 必须拿着电话等回复
// 在这里阻塞,什么都干不了
// Lettuce(外卖APP)
RedisAsyncCommands<String, String> async = connection.async();
RedisFuture<String> 未来结果 = async.get("菜单"); // 📱 下单后立即返回
// 立即继续执行其他代码,不用等待
System.out.println("我可以继续刷朋友圈...");
// 当结果准备好时,自动处理
未来结果.thenAccept(菜单 -> {
System.out.println("收到菜单:" + 菜单);
});
- 线程安全,一个连接可多线程共享(相当于上面Jedis的线程池电话总机分机的例子,)
// 创建一个大账号(一个Lettuce连接)
StatefulRedisConnection<String, String> 家庭账号 = client.connect();
// 爸爸用这个账号点餐
new Thread(() -> {
RedisCommands<String, String> 爸爸 = 家庭账号.sync();//这是同步代码
爸爸.set("爸爸的午餐", "牛肉面");
}).start();
// 妈妈同时用同一个账号点餐
new Thread(() -> {
RedisCommands<String, String> 妈妈 = 家庭账号.sync();
妈妈.set("妈妈的午餐", "沙拉");
}).start();
// 孩子同时用同一个账号点零食
new Thread(() -> {
RedisCommands<String, String> 孩子 = 家庭账号.sync();
孩子.set("孩子的零食", "冰淇淋");
}).start();
// 全家人都用一个账号,互不干扰!👨👩👧👦
- 支持 响应式编程
// 响应式编程 = 智能订单状态订阅
RedisReactiveCommands<String, String> reactive = connection.reactive();
// 订阅订单状态变化(像开启推送通知)
reactive.get("订单状态")
.subscribe(状态 -> {
System.out.println("订单状态更新:" + 状态);
});
// 当餐厅接单、制作、配送时,都会自动通知你
// 你不需要主动查询,系统会推送更新
- 性能优于 Jedis
Redisson - 分布式服务客户端
- 不仅仅是个 Redis 客户端,更是分布式服务框架
- 提供很多 分布式对象 和 服务
- 支持 分布式锁、分布式集合 等
- API 设计更符合 Java 习惯
Jedis官网地址 https://github.com/redis/jedis
SpringDataRedis
SpringData是Spring中数据操作的模块,包含对各种数据库的集成 (操作各种数据库的依赖项)
对于Redis的:Spring Data Redis
SpringDataRedis与之前三个客户端的关系
你的应用程序
↓
Spring Data Redis ←── 抽象层(统一API)
↓
┌─────────┬─────────┬──────────┐
│ Jedis │ Lettuce │ Redisson │ ←── 具体实现(驱动)
└─────────┴─────────┴──────────┘
↓
Redis服务器
- 提供了对不同Redis客户端的整合
- 提供了RedisTemplate统一API来操作Redis
- 支持Redis的发布订阅模型、哨兵、集群
- 支持基于Lettuce的响应式编程
- 支持基于JDK、JSON、字符串、Spring对象的数据序列化及反序列化
- 支持基于Redis的JDKCollection实现
| API 方法 | 返回值类型 | 说明 |
|---|---|---|
redisTemplate.opsForValue() | ValueOperations | 操作 String 类型数据 |
redisTemplate.opsForHash() | HashOperations | 操作 Hash 类型数据 |
redisTemplate.opsForList() | ListOperations | 操作 List 类型数据 |
redisTemplate.opsForSet() | SetOperations | 操作 Set 类型数据 |
redisTemplate.opsForZSet() | ZSetOperations | 操作 SortedSet 类型数据 |
redisTemplate | - | 通用的命令 |
Jedis实践
创建Maven项目
如图
引入依赖
在pom.xml中引入依赖
<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.6.3</version>
<scope>test</scope>
</dependency>
Maven、依赖项和外部库的理解:
Maven的本质是一个项目管理工具,它有一个Maven 中央仓库(Maven Central Repository)(官方地址 https://repo.maven.apache.org/maven2)。它是全球最大、最常用的 Java 库和依赖项的存储仓库。
Maven就会从这个网址下载依赖
依赖项本质上是一个 压缩包(.jar 文件),里面包含了:
- ✅ 编译好的 .class 文件(机器可执行的字节码)
- ✅ 资源文件(图片、配置文件等)
- ✅ 元数据(描述这个库的信息)
- ✅ java文件 原始的 .java 源文件(除非是源码包)
两种类型的 JAR 包
类型1:普通JAR(运行时用)
- 只包含
.class文件- 体积小,适合生产环境
- 就是我们刚才看的
gson-2.8.9.jargson-2.8.9/ ├── META-INF/ │ ├── MANIFEST.MF # 元数据:版本、作者等信息 │ └── NOTICE # 版权声明 │ └── com/ └── google/ └── gson/ ├── Gson.class # 编译后的主类 ├── JsonElement.class # 编译后的类 ├── JsonObject.class ├── JsonArray.class ├── TypeAdapter.class ├── annotations/ │ ├── SerializedName.class │ └── Expose.class ├── internal/ │ ├── Bindings.class │ └── Streams.class ├── reflect/ │ └── TypeToken.class └── stream/ └── JsonReader.class类型2:源码JAR(开发时用)
- 包含原始
.java文件- 文件名通常带
sources:gson-2.8.9-sources.jar- IDE可以下载这个包,让你查看源代码
gson-2.8.9-sources/ └── com/ └── google/ └── gson/ ├── Gson.java # 原始Java源码 ├── JsonElement.java ├── JsonObject.java └── ...问:竟然是一个压缩包,那为什么不用 .zip 而用 .jar?
jar本质上是zip文件,但是是Java应用压缩文件
JAR文件有一些ZIP没有的特殊能力:
// JAR特有的功能: // 1. 可执行JAR java -jar myapp.jar // 直接运行! // 2. 类路径自动识别 // JVM知道从JAR文件中加载类 // 3. 清单文件(MANIFEST.MF)特殊处理 // 指定主类、版本信息等创建一个简单的JAR文件
步骤1:写一个Java类
// Hello.java public class Hello { public static void main(String[] args) { System.out.println("你好,我是JAR文件!"); } }步骤2:编译和打包
# 编译 javac Hello.java # 打包成JAR jar cf myhello.jar Hello.class # 查看内容 jar tf myhello.jar # 输出: # META-INF/ # META-INF/MANIFEST.MF # Hello.class #可以执行JAR java -jar myhello.jar // 直接运行!步骤3:验证是ZIP格式
# 重命名为ZIP cp myhello.jar myhello.zip # 用解压软件打开,看到同样内容!jar就是人家写好的java代码打包给我们用
测试String
package com.yzy;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import redis.clients.jedis.Jedis;
public class JedisTest {
private Jedis jedis;
@BeforeEach
void setUp() {
//建立连接
jedis = new Jedis("192.168.88.130",6379);//redis装在虚拟机中,需要在虚拟机终端使用ifconfig查找到该ip地址
//设置密码
jedis.auth("123321");
//选择库
jedis.select(0);
String pong = jedis.ping();
System.out.println("Redis连接测试: " + pong);
}
@Test
void testString() {
//存入数据
String result = jedis.set("name","虎哥");
System.out.println("result = " + result);
//获取数据
String name = jedis.get("name");
System.out.println("name = " + name);
}
@AfterEach
void tearDown() {
if(jedis != null) {
jedis.close();
}
}
}
代码说明
上面是单元测试代码,注解来自 Junit5 版本的
<dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>5.6.3</version> <scope>test</scope> </dependency>
- SetUp 方法 (
@Before)- TearDown 方法 (
@After)- BeforeClass 方法 (
@BeforeClass)- AfterClass 方法 (
@AfterClass)- 测试方法 (
@Test)开始测试套件 ↓ beforeClass() // 执行一次 ↓ setUp() → testSetAndGet() → tearDown() // 第一个测试 ↓ setUp() → testConnection() → tearDown() // 第二个测试 ↓ afterClass() // 执行一次 结束测试套件
如果出现 redis.clients.jedis.exceptions.JedisConnectionException: Failed to create socket. 的问题
确保redis.conf的配置正确
bind 0.0.0.0
在虚拟机上
# 临时关闭防火墙
sudo systemctl stop firewalld
# 测试完成后再开启
sudo ufw enable
测试通过
测试Hash
@Test
void testHash() {
//插入hash数据
jedis.hset("project:user:1","name","zyz");
jedis.hset("project:user:1","age","10");
//获取数据
Map<String, String> map = jedis.hgetAll("project:user:1");
System.out.println(map);
}
测试通过
使用连接池
package com.yzy;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
public class JedisConnectionFactory {
private static final JedisPool jedisPool;
static {
//配置连接池
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxTotal(8);
jedisPoolConfig.setMaxIdle(8);
jedisPoolConfig.setMinIdle(8);
jedisPoolConfig.setMaxWaitMillis(1000);
//创建连接池对象
jedisPool = new JedisPool(jedisPoolConfig, "192.168.88.130",6379,1000,"123321");
}
public static Jedis getJedis() {
return jedisPool.getResource();
}
}
连接方式改成如下
SpringDataRedis实践
创建SpringBoot项目
选择这两个依赖项,确认创建
引入依赖
确认pom.xml有着两个依赖项
<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>
这里连接池依赖项的作用
有连接池(高性能)
应用请求连接 → 从池中取出现成连接 → 使用后归还到池中配置
# 配置示例 spring: redis: lettuce: pool: max-active: 8 # 最大连接数 max-idle: 8 # 最大空闲连接 min-idle: 2 # 最小空闲连接实现代码
// 100次操作,使用同一个连接池 for (int i = 0; i < 100; i++) { RedisConnection connection = pool.getResource(); // 毫秒级获取 connection.set("key", "value"); connection.close(); // 实际是归还到池中 } // 总耗时:~200ms没有连接池(低性能)
应用请求连接 → 创建新连接 → 使用后关闭连接 → 下次再创建新连接无配置
# 没有连接池配置 spring: redis: # 无pool配置实现代码
// 100次操作,每次新建连接 for (int i = 0; i < 100; i++) { RedisConnection connection = createNewConnection(); // 需要TCP握手 connection.set("key", "value"); connection.close(); // 实际关闭连接 } // 总耗时:~2000ms(10倍慢!)
配置文件
记得修改配置文件后缀为 .yml
spring:
data:
redis:
host: 192.168.88.130
port: 6379
password: 123321
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 0
max-wait: 100ms
实现代码
package com.yzy;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
@SpringBootTest
class SpringdataredisApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void testString(){
redisTemplate.opsForValue().set("age",10);
Object age = redisTemplate.opsForValue().get("age");
System.out.println("age = " + age);
}
}
测试成功
小tips
redisTemplate.获取不同的操作对象对应不同的Redis数据类型L
Spring Data Redis 操作 对应的 Redis 数据类型 Redis 命令 opsForValue()String SET,GET,INCR,DECRopsForHash()Hash HSET,HGET,HGETALLopsForList()List LPUSH,RPOP,LRANGEopsForSet()Set SADD,SMEMBERS,SINTERopsForZSet()SortedSet ZADD,ZRANGE,ZRANK
疑问1
刚刚写入的连接池依赖项有没有被使用呢?
有
应用请求连接 → 从连接池取出现成连接 → 使用后归还到池中如果没有连接池的依赖项,Spring Boot 会自动降级到「无连接池模式」
应用请求连接 → 创建新连接 → 使用后关闭连接 → 下次再创建新连接实际对比一下
无,耗时1165ms
![]()
有,耗时901ms
![]()
疑问2
age不是10吗?
因为刚刚存入的age被序列化了
RedisTemplate可以接收任意Object作为值写入Redis,只不过写入前会把Object序列化为字节形式,默认采用JDK序列化,得到的结果所以像上面那样
- 可读性差
- 内存占用大
序列化解答
序列化是指将内存中的对象转换为可以存储或传输的字节序列的过程。反序列化则是将字节序列恢复为内存中对象的过程。
为什么Java客户端存入Redis需要序列化?
-
Redis 只能存储二进制数据:Redis 的键和值本质上都是二进制安全的字节数组
(一)Redis底层存储模式:Redis数据类型 = 字符串集合 + 组织规则 + 类型标识
例如:// String类型最简单 //String = 1个字符串 + 编码规则 + String标识 robj = { type: REDIS_STRING, // 类型标识 encoding: int/embstr/raw, // 编码规则(如何存储这1个字符串) ptr: → 字符串数据 // 字符串集合(只有1个) }注意:序列化器不是针对整个 robj,字符串集合中的字符串长什么样取决于序列化器
不同的Redis数据类型的底层存储形式 (value的不同组织形式)
数据类型 类型标识 (type) 组织规则 (encoding)———————————————————— 示例命令——————————————————— 底层存储示例 String (字符串) REDIS_STRING(0x00)int: 值可表示为64位整数时使用
embstr: 字符串长度≤44字节时使用
raw: 字符串长度>44字节时使用SET name "zyz"
SET count 100robj{type:STRING,encoding:INT,ptr→100}robj{type:STRING,encoding:EMBSTR,ptr→"zyz"}List (列表) REDIS_LIST(0x01)ziplist: Redis 3.2前小列表使用 linkedlist: Redis 3.2前大列表使用(已废弃) quicklist: Redis 3.2+默认,所有列表使用 LPUSH list A B C
RPOP listrobj{type:LIST,encoding:QUICKLIST,ptr→quicklist{node→ziplist["A","B","C"]}}Set (集合) REDIS_SET(0x02)intset: 元素全是整数且数量≤set-max-intset-entries(默认512)时使用
hashtable: 其他所有情况使用SADD tags Java RedisSMEMBERS tagsrobj{type:SET,encoding:HASHTABLE,ptr→dict{"Java":NULL,"Redis":NULL}}Hash (哈希表) REDIS_HASH(0x03)ziplist: 字段数≤hash-max-ziplist-entries(默认512)且所有值≤hash-max-ziplist-value(默认64字节)时使用
hashtable: 其他情况使用HSET user name zyz age 10HGETALL userrobj{type:HASH,encoding:ZIPLIST,ptr→["name","zyz","age","10"]}ZSet (有序集合) REDIS_ZSET(0x04)ziplist: 元素数≤zset-max-ziplist-entries(默认128)且所有值≤zset-max-ziplist-value(默认64字节)时使用 skiplist: 其他情况使用 ZADD rank 100 Jack 90 RoseZRANGE rank 0 -1robj{type:ZSET,encoding:SKIPLIST,ptr→zset{dict{"Jack":100},zsl→[节点...]}}Stream (流) REDIS_STREAM(0x05)listpack: Redis 7.0+使用,所有Stream都使用此编码
rax: Redis 5.0-6.2使用基数树(已弃用)XADD mystream * name zyz age 10XREAD mystreamrobj{type:STREAM,encoding:LISTPACK,ptr→listpack[...]}HyperLogLog (基数统计) REDIS_STRING(伪装成String)raw: 所有HyperLogLog都使用此编码,固定12KB大小 PFADD hll a b cPFCOUNT hllrobj{type:STRING,encoding:RAW,ptr→HLL二进制数据}Bitmap (位图) REDIS_STRING(伪装成String)int: 位图很小(可表示为整数)时使用
raw: 位图较大时使用SETBIT bitmap 0 1GETBIT bitmap 0robj{type:STRING,encoding:RAW,ptr→二进制位数组}GEO (地理空间) REDIS_ZSET(基于ZSet)skiplist: 所有GEO数据都使用此编码,因为基于ZSet实现 GEOADD cities 116.40 39.90 BeijingGEORADIUS cities 116 39 100 kmrobj{type:ZSET,encoding:SKIPLIST,ptr→zset{dict{"Beijing":geohash},zsl→[...]}}(二)Redis的每层实现都是byte[]
-
Java 对象不能直接存储:Java 对象在内存中有复杂的结构,不能直接存入 Redis
即Java对象在内存中是"活"的:有方法、有继承关系、有复杂结构,Redis只能存"死"数据:字节数组,没有结构概念所以序列化的作用就是把Java对象转到这个程度:字符串集合 + 组织规则 + 类型标识
-
跨平台兼容性:序列化后的数据可以被不同语言、不同系统的程序读取
想象你要给朋友寄一个乐高玩具
- 不能直接寄:你不能把拼好的乐高直接扔进快递箱,会摔碎
- 需要拆解:你把乐高拆成一块块的,放进盒子
- 贴说明书:附上拼装说明书(步骤、每块的位置)
- 封箱寄出:打包成快递包裹
- 朋友接收:朋友按说明书重新拼起来
这就是序列化的全过程:
- 拆乐高 = 序列化(对象 → 字节)
- 说明书 = 序列化规则
- 打包寄出 = 存入Redis
- 重新拼装 = 反序列化(字节 → 对象)
不同的序列化器就是不同的快递公司,它们有着不同的序列化方式(打包方式,打包大小,运输范围和运输形式都不同,以及可以运输的物品类型也不同)
这是RedisSerializer序列化器的多个实现类
| 快递公司 | 适合寄送的物品(Java数据类型)———————————————— | 不适合的数据类型———————— | 打包特点———————————— | 运费(性能)————————————— | 到货状态(Redis存储) |
|---|---|---|---|---|---|
| 邮政EMS (JdkSerializationRedisSerializer) | 复杂大件物品 - 任意可序列化对象 | 跨语言、高效存储 | 原包装全打包,体积最大 | ⭐⭐⭐⭐⭐ (最贵) | \xac\xed\x00\x05... (二进制乱码) |
| 顺丰标准件 (Jackson2JsonRedisSerializer) | 标准规格物品 - 固定类型的对象 | 多态、不确定类型 | 标准拆解包装,体积中等 | ⭐⭐ (性价比高) | {"name":"Jack","age":25} (干净JSON) |
| 顺丰智能件 (GenericJackson2JsonRedisSerializer) ✅ | 通用日常物品 - 所有对象类型 | 无 | 带智能标签包装,体积稍大 | ⭐⭐⭐ (最佳平衡) | {"@class":"User","name":"Jack"...} (带类型JSON) |
| 只送信件 (StringRedisSerializer) | 纸面文件 - 字符串、简单文本数据 | 对象、集合、数字 | 只送描述纸条,体积最小 | ⭐ (最快) | "Jack-25" 或 "{\"name\":\"Jack\"}" (纯字符串) |
| 德邦大件 (OxmSerializer - XML) | XML格式物品 - XML格式对象 | 所有其他类型 | XML格式包装,体积最大 | ⭐⭐⭐⭐ (很贵) | <user><name>Jack</name></user> (XML格式) |
| 专业冷链 (Protobuf序列化器) | 高价值精密仪器 - 微服务通信数据 - 高并发消息、跨语言高性能场景(自查) | 自查 | 高度压缩包装,体积最小 | ⭐⭐ (专业但快) | \x0A\x04Jack\x10\x19 (二进制压缩) |
| 转换站 (GenericToStringSerializer) | 可字符串化的物品 - 数字、日期、枚举 | 复杂对象、集合 | 先转为字符串再邮寄 | ⭐⭐ (快) | "100"、"1672502400000" (字符串形式) |
点击进入RedisTemplate,看到四个不同岗位的序列化器
这四个序列化器是RedisTemplate序列化的四个"岗位",每个负责序列化不同位置的数据:
| 序列化器 | 负责位置 | 类比 | 主要使用场景 |
|---|---|---|---|
keySerializer | Redis键本身 | 仓库货架标签 | 所有Redis命令的第一个参数 |
valueSerializer | String类型的值 | 货架上整箱货物 | SET、GET等命令 |
hashKeySerializer | Hash内部的字段名 | 箱子里的物品标签 | HSET、HGET等命令的字段名 |
hashValueSerializer | Hash内部的字段值 | 箱子里的物品 | HSET、HGET等命令的字段值 |
// 整个用户对象打包成一个箱子
User user = new User("张三", 25, "zhangsan@example.com");
redisTemplate.opsForValue().set("user:1001", user);
// 使用的序列化器:
// keySerializer → 序列化 "user:1001"(货架标签)
// valueSerializer → 序列化 User对象(整箱货物)
// 用户信息拆开,放在不同的小箱子里
Map<String, Object> userMap = new HashMap<>();
userMap.put("name", "张三"); // 小箱子1:姓名
userMap.put("age", 25); // 小箱子2:年龄
userMap.put("email", "zhangsan@example.com"); // 小箱子3:邮箱
userMap.put("profile", userProfile); // 小箱子4:详细资料(对象)
redisTemplate.opsForHash().putAll("user:1001", userMap);
// 使用的序列化器:
// keySerializer → 序列化 "user:1001"(货架标签)
// hashKeySerializer → 序列化 "name"、"age"、"email"、"profile"(小箱子标签)
// hashValueSerializer → 序列化 "张三"、25、"zhangsan@example.com"、userProfile(小箱子里的物品)
如果没有手动设置这四个序列化器的具体实现,默认使用JDK序列化器
自定义RedisTemplate
为了设置我们想要的序列化器,可以自定义RedisTemplate
先了解一下SpringBoot怎么自动化配置RedisTemplate (背后做的事)
当我们引入了依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
也写了数据库配置
spring:
data:
redis:
host: 192.168.88.130
port: 6379
password: 123321
就可以依赖注入,使用
@Autowired
private RedisTemplate redisTemplate;
为什么可以直接使用?SpringBoot也自定义配置了RedisTemplate(自动化配置)
// RedisAutoConfiguration.java
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisOperations.class) // 当有RedisOperations类时生效
@EnableConfigurationProperties(RedisProperties.class) // 启用配置属性
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
// 创建 RedisTemplate(泛型是<Object, Object>)
@Bean
@ConditionalOnMissingBean(name = "redisTemplate") // 只有当没有名为redisTemplate的Bean时才创建
public RedisTemplate<Object, Object> redisTemplate(
RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory); // 设置连接工厂
// 注意:这里没有设置序列化器!使用默认的JdkSerializationRedisSerializer
return template;
}
// 创建 StringRedisTemplate
@Bean
@ConditionalOnMissingBean // 只有当没有StringRedisTemplate Bean时才创建
public StringRedisTemplate stringRedisTemplate(
RedisConnectionFactory redisConnectionFactory) {
return new StringRedisTemplate(redisConnectionFactory);
}
}
所以我们也可以学着自动化配置,自定义RedisTemplate
package com.yzy.redis.config;
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.RedisSerializer;
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
//创建RedisTemplate对象
RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>();
//设置连接工厂
redisTemplate.setConnectionFactory(redisConnectionFactory);
//设置序列化方式
redisTemplate.setKeySerializer(RedisSerializer.string());
redisTemplate.setValueSerializer(RedisSerializer.json());
redisTemplate.setHashKeySerializer(RedisSerializer.string());
redisTemplate.setHashValueSerializer(RedisSerializer.json());
//返回
return redisTemplate;
}
}
除了设置连接工厂和序列化器,还能设置什么?
配置项 方法 作用 默认值 连接工厂 setConnectionFactory()设置Redis连接源 必须设置 序列化器 setKeySerializer()等序列化键值对 JdkSerializationRedisSerializer 是否暴露连接 setExposeConnection()是否在回调中暴露原生连接 true 启用事务支持 setEnableTransactionSupport()是否启用Redis事务 false 字符串序列化器 setStringSerializer()字符串操作的序列化器 null 脚本执行器 setScriptExecutor()执行Lua脚本的执行器 自动创建 值操作器 setValueOperations()自定义Value操作 自动创建 错误处理器 setErrorHandler()异常处理器 默认转换异常
修改代码,运行(记得关闭虚拟机防火墙)
来一个json化存储对象
@class 字段是 JSON 中的"身份证",告诉反序列化器:“这个 JSON 数据原本是哪个 Java 类的对象,请按这个类来还原我!”
但是,缺点是占用空间大,甚至比真正要存的数据还大,成千上万条数据时浪费空间
StringRedisTemplate的使用
为了节省内存空间,我们并不会使用JSON序列化器来处理value,因为它要自己处理Java对象和json字符串之间的转换,转换过程中存储的信息会占用内存
统一使用String序列化器value,要求只能存储String类型的key和value
Spring默认提供了一个StringRedisTemplate类,它的key和value的序列化方式默认是String方式,省区自定义RedisTemplete过程
所以我们要存储对象时,手动完成对象的序列化和反序列化
深度理解: 我怎么知道什么时候选什么序列化器?和针对的Redis数据类型有关吗?和Java对象有关吗?
Redis定义的数据类型仅仅代表value的组织形式
而 Java数据类型决定用什么序列化器
序列化器 最适合的 Java 数据类型 不适合的数据类型 最终序列化结果(存 User(“yzy”, 20)) 使用场景 StringRedisSerializer 字符串、简单文本数据 对象、集合、数字 纯文本字符串 "{"name":"yzy","age":20}"(需手动转)配置项、状态标识 GenericJackson2JsonRedisSerializer ✅ 所有对象类型 无 带类信息的JSON {"@class":"User","name":"yzy","age":20}通用对象缓存 Jackson2JsonRedisSerializer 固定类型的对象 多态、不确定类型 纯JSON(无类信息)
{“name”:“yzy”,“age”:20}单一类型缓存 JdkSerializationRedisSerializer 任意可序列化对象 跨语言、高效存储 乱码不可读
\xac\xed\x00\x05t\x00\x0bUser…遗留系统兼容 GenericToStringSerializer 数字、日期、枚举 复杂对象、集合 "User[name=yzy, age=20]"(不可逆)计数器、ID缓存 OxmSerializer XML格式对象 所有其他类型 yzy20 XML系统集成 不同的序列化器最终都会转成字节数组,只是不同表达方式的字节数组
序列化器工作:某个Java数据类型——>使用某个序列化器——>转成可以储存的字节数组因为序列化器是工作在应用层,前面说到Redis底层存储模式:字符串集合 + 组织规则 + 类型标识,是Redis在存储层的内部工作
这个存储层的内部工作根据Redis数据类型和传入的字节数组,确定组织规则和字符串集合的排列方式,形成
// Redis收到字节数组后,创建内部结构 // 无论什么序列化器的结果,在Redis中都变成: robj { type: REDIS_STRING, // 类型标识 encoding: RAW, // 组织规则(编码) ptr: → bytes[] // 字符串集合(你的序列化结果字节数组) } // 然后存储为: 键值对:key → robj结构完整流程:从Java对象到Redis存储
Java User对象 ("yzy", 20) ↓ 应用层序列化(由序列化器决定) 字节数组(内容取决于序列化器) ↓ 传输给Redis Redis接收字节数组 ↓ Redis内部处理 创建 robj { type: REDIS_STRING, ← 类型标识(总是String类型) encoding: RAW/EMBSTR, ← 组织规则(根据长度决定) ptr: → 你的字节数组 ← 字符串集合(就是序列化结果!) } ↓ 存入内存 成为Redis的一个String类型键值对所以能不能写入成功,完全取决于 RedisTemplate 配置的序列化器!
这两种序列化方案自己按需选择
- 自定义RedisTemplate,序列化器为GenericJackson2JsonRedisSerializer
- StringRedisTemplate,手动序列化和反序列化



1420

被折叠的 条评论
为什么被折叠?



