
02 Redis 笔记
Redis 基础部分
Redis 概述
Redis是一种 键值型 的 NoSQL 数据库,这里有两个关键字:
- 键值型
- 这里的 键值型,是指Redis中存储的数据都是以
key.value对的形式存储,而 value 的形式多种多样,可以是 字符串、数值、甚至 json
- 这里的 键值型,是指Redis中存储的数据都是以
- NoSQL
- 这里的 NoSQL数据库,是指对于存储的数据,没有类似MySQL那么严格的约束,比如唯一性,是否可以为 null 等等
SQL vs NoSQL:

-
事务
- 传统关系型数据库 能满足 事务 ACID 的原则
- 非关系型数据库 往往不支持 事务,或者不能严格保证 ACID 的特性,只能实现基本的 一致性
-
存储方式
- 关系型数据库 基于 磁盘 进行存储,会有大量的磁盘IO,对性能有一定影响
- 非关系型数据库,它们的操作更多的是依赖于 内存 来操作,内存的读写速度会非常快,性能自然会好一些
- 扩展性
- 关系型数据库 集群模式 一般是 主从,主从数据一致,起到 数据备份 的作用,称为 垂直扩展
- 非关系型数据库 可以将 数据 拆分,存储在不同机器上,可以保存海量数据,解决内存大小有限的问题,称为 水平扩展
- 关系型数据库因为表之间存在关联关系,如果做水平扩展会给数据查询带来很多麻烦
Redis 的特征:
- 键值(key-value)型,value支持多种不同数据结构,功能丰富
- 单线程,每个命令具备原子性
- 低延迟,速度快(基于内存、IO多路复用、良好的编码)。
- 支持数据持久化(定期从内存持久化到硬盘)
- 支持主从集群、分片集群
- 支持多语言客户端
docker 启动 redis:
1 | docker exec -it redis容器id /bin/bash |
Redis 常见命令
Redis是一个 key-value 的 数据库,key 一般是 String 类型,不过 value 的类型多种多样:

redis 的帮助文档:https://redis.io/commands
Redis 通用命令
通用指令 是 部分数据类型 的,都可以使用的指令,常见的有:
-
keys:查看 符合模板 的所有 key1
2
3
4
5
6
7查询所有的key
127.0.0.1:6379> keys *
1) "author"
查询所有以a开头的key
127.0.0.1:6379> keys a*
1) "author"- 在生产环境下,不推荐使用 keys 命令,因为 redis 是 单线程 的,这个命令在 key 过多的情况下,效率不高
-
del:删除 一个指定的 key1
2
3删除一个指定的key
127.0.0.1:6379> del author
(integer) 1 -
exist:判断 key 是否存在1
2127.0.0.1:6379> exists author
(integer) 1 -
expire:给一个 key 设置有效期,有效期到期时该 key 会被自动删除 -
ttl:查看一个 key 的 剩余有效期1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19给 key 为 author 设置 10s 的有效期
127.0.0.1:6379> expire author 10
(integer) 1
key 为 author 的剩余有效期为 2s
127.0.0.1:6379> ttl author
(integer) 2
当这个 key 过期 了,那么此时查询出来就是 -2
127.0.0.1:6379> ttl author
(integer) -2
如果没有设置过期时间
127.0.0.1:6379> set age 10
OK
ttl的返回值就是-1
127.0.0.1:6379> ttl age
(integer) -1- 内存非常宝贵,对于一些数据,应当给它设置一些过期时间,当过期时间到了之后,它就会自动被删除
String 类型
String 类型,也就是 字符串 类型,是 Redis 中 最简单 的 存储类型
其 value 是字符串,不过根据 字符串的格式 不同,又可以分为3类:
string:普通 字符串int:整数 类型,可以做自增、自减操作float:浮点 类型,可以做自增、自减操作

不管是哪种格式,底层 都是 字节数组 形式存储,只不过是 编码方式不同
字符串类型 的 最大空间 不能超过 512MB
String 的 常见命令:
-
set:添加(如果不存在即为添加) 或者 修改(如果存在即为修改) 已经存在的一个 String类型 的 键值对1
2
3
4
5
6
7原来存在,就是修改
127.0.0.1:6379> set name Jack
OK
原来不存在
127.0.0.1:6379> set name Rose
OK -
get:根据 key 获取 String 类型的 value1
2127.0.0.1:6379> get name
"Rose" -
mset:批量添加 多个 String 类型的 键值对1
2127.0.0.1:6379> MSET k1 v1 k2 v2 k3 v3
OK -
mget:根据多个 key 获取多个 String 类型的 value1
2
3
4
5
6127.0.0.1:6379> MGET name age k1 k2 k3
1) "Jack" # 之前存在的name
2) "10" # 之前存在的age
3) "v1"
4) "v2"
5) "v3" -
incr:让一个 整型 的 key 自增11
2
3
4
5
6127.0.0.1:6379> get age
"10"
增加1
127.0.0.1:6379> incr age
(integer) 11 -
incrby:让一个 整型 的 key 自增 并指定 步长1
2
3
4
5
6
7一次增加2
127.0.0.1:6379> incrby age 2
(integer) 13 # 返回目前的age的值
一次减少1
127.0.0.1:6379> incrby age -1
(integer) 12 -
incrbyfloat:让一个 浮点 类型的数字 自增 并指定 步长1
2
3
4
5127.0.0.1:6379> set score 10.1
OK
127.0.0.1:6379> incrbyfloat score 1.2
"11.3" -
setnx:添加一个 String 类型的 键值对,前提是这个 key 不存在,否则不执行1
2
3
4
5
6
7
8
9
10
11设置名称
127.0.0.1:6379> set name Jack
OK
如果 key 不存在,则添加成功
127.0.0.1:6379> setnx name lisi
(integer) 0
name2 不存在,所以操作成功
127.0.0.1:6379> setnx name2 lisi
(integer) 1 -
setex:添加一个 String 类型的 键值对,并且指定 有效期1
2
3
4
5
6
7
8
9
10
11127.0.0.1:6379> setex name 10 jack
OK
127.0.0.1:6379> ttl name
(integer) 8
127.0.0.1:6379> ttl name
(integer) 7
127.0.0.1:6379> ttl name
(integer) 5
Key 的 层级结构
Redis 的 key 允许有 多个单词 形成 层级结构,多个单词之间用 : 隔开,格式如下:
1 | [项目名]:[业务名]:[类型]:[id] |
eg:
1 | user相关的key |
如果 value 是一个 Java 对象,例如一个 User 对象,则可以将对象序列化为 JSON字符串 后存储:
| KEY | VALUE |
|---|---|
| heisenberg:user:1 | {“id”:1, “name”: “Jack”, “age”: 21} |
| heisenberg:product:1 | {“id”:1, “name”: “苹果12”, “price”: 4999} |
一旦向 redis 采用这样的方式存储,那么 redis 会以 层级结构 来进行 存储
Hash 类型
Hash类型,也叫 散列,其 value 是一个 无序字典,类似于Java中的HashMap结构
Hash结构 可以将 对象 中的 每个字段 独立存储,可以针对 单个字段 做 CRUD:

Hash类型 的 常见命令:
-
hset key field value:添加 或者 修改 hash类型 key 的 field 的值 -
hget key field:获取 一个 hash 类型 key 的 field 的值1
2
3
4
5
6127.0.0.1:6379> hset test1 hello 1
(integer) 1
127.0.0.1:6379> hget test1 hello
"1" -
hmset:批量 添加 多个 hash 类型 key 的 field 的值 -
hmget:批量 获取 多个 hash 类型 key 的 field 的值1
2
3
4
5
6
7
8127.0.0.1:6379> hmset test1 name lilei age 20 sex male
OK
127.0.0.1:6379> hmget test1 name age sex
1) "lilei"
2) "20"
3) "male" -
hgetall:获取一个 hash 类型的 key 中的所有的 field 和 value1
2
3
4
5
6
7
8
9127.0.0.1:6379> hgetall test1
1) "hello" # key
2) "1" # value
3) "name"
4) "lilei"
5) "age"
6) "20"
7) "sex"
8) "male" -
hkeys:获取一个 hash 类型的 key 中的所有的 field -
hvals:获取一个 hash 类型的 key 中的所有的 value1
2
3
4
5
6
7
8
9
10
11
12127.0.0.1:6379> hkeys test1
1) "hello"
2) "name"
3) "age"
4) "sex"
127.0.0.1:6379> hvals test1
1) "1"
2) "lilei"
3) "20"
4) "male" -
hincrby:让一个 hash 类型 key 的字段值 自增 并指定 步长1
2
3取test1字段的age的value自增2
127.0.0.1:6379> hincrby test1 age 2
(integer) 22 -
hsetnx:添加一个 hash 类型的 key 的 field 值,前提是这个 field 不存在,否则不执行1
2127.0.0.1:6379> hsetnx test1 sex female
(integer) 0 # 返回值为0表示执行失败
List 类型
Redis 中的 List类型 与 Java 中的 LinkedList 类似,可以看做是一个 双向链表 结构
既可以 支持 正向检索 和也可以 支持 反向检索

特征 与 LinkedList 类似:
- 有序
- 元素可以重复
- 插入 和 删除 快
- 查询 速度一般
List 类型 的 常见命令:
-
lpush key element ...:向 列表 左侧 插入 一个 或 多个元素 -
rpush key element ...:向 列表 右侧 插入 一个 或 多个元素1
2
3
4
5127.0.0.1:6379> lpush test2 1, 2, 3
(integer) 3
127.0.0.1:6379> rpush test2 4, 5, 6
(integer) 6 -
lpop key:移除 并 返回列表 左侧 的 第一个元素,没有则返回 nil -
rpop key:移除 并 返回列表 右侧 的 第一个元素1
2
3
4
5127.0.0.1:6379> lpop test2
"3"
127.0.0.1:6379> rpop test2
"6" -
lrange key start end:返回一段 角标范围 内的 所有元素1
2
3127.0.0.1:6379> lrange test2 1 2 # 这个角标范围是从0开始
1) "1,"
2) "4," -
blpop和brpop:与 lpop 和 rpop 类似,只不过在 没有元素时 等待指定时间,而不是直接返回 nil1
2
3127.0.0.1:6379> blpop test3 10
(nil)
(10.09s)
利用 List 结构模拟一个栈:入口 和 出口 在 同一边
利用 List 结构模拟一个队列:入口 和 出口 在 不同边
利用 List 结构模拟一个阻塞队列:入口 和 出口 在 不同边,出队时采用 BLPOP 或 BRPOP
Set 类型
Redis 的 Set 结构与 Java 中的 HashSe t类似,可以看做是一个 value 为 null 的 HashMap
因为是一个 hash 表,因此具备与 HashSet 类似的特征:
- 无序
- 元素 不可重复
- 查找 快
- 支持交集、并集、差集等功能
Set 类型的常见命令:
-
sadd key member ...:向 set 中 添加 一个或多个 元素1
2127.0.0.1:6379> sadd test3 1 2 3 3
(integer) 3 -
srem key member ...: 移除 set 中的 指定元素1
2127.0.0.1:6379> srem test3 3
(integer) 1 -
scard key: 返回 set 中 元素的个数1
2127.0.0.1:6379> scard test3
(integer) 2 -
sismember key member:判断 一个元素 是否存在于 set 中1
2
3
4
5127.0.0.1:6379> sismember test3 1
(integer) 1
127.0.0.1:6379> sismember test3 3
(integer) 0 -
smembers:获取 set 中的 所有元素1
2
3127.0.0.1:6379> smembers test3
1) "1"
2) "2" -
sinter key1 key2 ...:求 key1 与 key2 的交集1
2
3
4
5
6127.0.0.1:6379> sadd test4 1 2 3 4
(integer) 4
127.0.0.1:6379> sinter test3 test4
1) "1"
2) "2" -
sdiff key1 key2 ...:求 key1 与 key2 的差集1
2127.0.0.1:6379> sdiff test3 test4
(empty array)
-
sunion key1 key2 ...:求 key1 和 key2 的并集1
2
3
4
5127.0.0.1:6379> sunion test3 test4
1) "1"
2) "2"
3) "3"
4) "4"
SortedSet 类型
Redis 的 SortedSet 是一个 可排序 的 set 集合,与 Java 中的 TreeSet 有些类似,但 底层数据结构 却差别很大
SortedSet 中的 每一个元素 都带有一个 score 属性,可以 基于 score 属性 对 元素 排序,**底层的实现 **是一个 跳表(SkipList)加 hash表
SortedSet 具备下列特性:
- 可 排序
- 元素 不重复
- 查询速度 快
SortedSet 类型 的 常见命令:
zadd key score member:添加 一个或多个元素 到 sorted set ,如果 已经存在 则 更新 其 score 值zrem key member:删除 sorted set 中的一个 指定元素zscore key member:获取 sorted set 中的 指定元素 的 score 值zrank key member:获取 sorted set 中的 指定元素 的 排名zcard key:获取 sorted set 中的 元素 个数zcount key min max:统计 score 值在 给定范围内 的 所有元素 的 个数zincrby key increment member:让 sorted set 中的 指定元素 自增,步长 为 指定的 increment 值zrange key min max:按照 score 排序后,获取 指定排名 范围内的元素zrangebyscore key min max:按照 score 排序后,获取指定 score 范围内的元素zdiff、zinter、zunion:求 差集、交集、并集
所有的排名 默认 都是 升序,如果要 降序 则在 命令 的 Z 后面添加 REV 即可,例如:
- 升序 获取 sorted set 中的指定元素的排名:
zrank key member - **降序 **获取 sorted set 中的指定元素的排名:
zrevrank key memeber
Redis 的 Java 客户端
Redis 的客户端网站:https://redis.io/docs/clients/
Java 的常用客户端:
- Jedis
- 以Redis命令作为方法名称,学习成本低,简单实用
- 但是Jedis实例是 线程不安全 的,多线程环境下需要 基于连接池 来使用
- Spring Data Redis 兼容 Jedis
- Lettuce
- Lettuce是基于 Netty 实现的,支持同步、异步和 响应式编程方式,并且是 线程安全 的
- 支持 Redis 的哨兵模式、集群模式 和 管道模式
- Spring Data Redis 兼容 Lettuce
- Redission
- Redisson是一个基于 Redis 实现的 分布式、可伸缩 的Java数据结构集合
- 包含了诸如 Map 、Queue 、Lock 、 Semaphore 、AtomicLong 等强大功能
Jedis 快速入门
Jedis 的 官网地址: https://github.com/redis/jedis
流程:
-
引入依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14<!--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> -
建立连接
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16private Jedis jedis;
// junit5 的方法,类似于 @before
void setUp() {
// 1.建立连接
// jedis = new Jedis("192.168.150.101", 6379);
jedis = JedisConnectionFactory.getJedis();
// 2.设置密码
jedis.auth("123321");
// 3.选择库
jedis.select(0);
} -
测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20void testString() {
// 存入数据
String result = jedis.set("name", "Bob");
System.out.println("result = " + result);
// 获取数据
String name = jedis.get("name");
System.out.println("name = " + name);
}
void testHash() {
// 插入hash数据
jedis.hset("user:1", "name", "Jack");
jedis.hset("user:1", "age", "21");
// 获取
Map<String, String> map = jedis.hgetAll("user:1");
System.out.println(map);
} -
释放资源
1
2
3
4
5
6
7
void tearDown() {
// 释放资源
if (jedis != null) {
jedis.close();
}
}
注意:
-
Jedis 本身是 线程不安全 的,并且 频繁的创建和销毁连接 会有 性能损耗
-
推荐使用 Jedis连接池 代替 Jedis的直连方式
- 有关 池化思想,并不仅仅是这里会使用,很多地方都有,比如说:数据库连接池,比如:tomcat 中的线程池,这些都是 池化思想 的体现
Jedis 连接池
流程:
-
创建 Jedis 的连接池
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30public class JedisConnectionFacotry {
private static final JedisPool jedisPool;
static {
// 配置连接池
JedisPoolConfig poolConfig = new JedisPoolConfig();
// 最大连接
poolConfig.setMaxTotal(8);
// 最大空闲连接
poolConfig.setMaxIdle(8);
// 最小空闲连接
poolConfig.setMinIdle(0);
// 设置最长等待时间,ms
poolConfig.setMaxWaitMillis(1000);
// 创建连接池对象
jedisPool = new JedisPool(poolConfig,
"192.168.150.101",6379,1000,"123321");
}
// 获取jedis对象
public static Jedis getJedis(){
return jedisPool.getResource();
}
}-
JedisConnectionFactory:工厂设计模式是实际开发中非常常用的一种设计模式,可以使用工厂,去降低代的耦合,比如Spring中的Bean的创建,就用到了工厂设计模式
-
静态代码块:随着类的加载而加载,确保只能执行一次,在加载当前工厂类的时候,就可以执行static的操作完成对 连接池的初始化
-
最后提供返回连接池中连接的方法
-
-
改造原始代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void setUp(){
// 建立连接
/*jedis = new Jedis("127.0.0.1",6379);*/
jedis = JedisConnectionFacotry.getJedis();
// 选择库
jedis.select(0);
}
void tearDown() {
if (jedis != null) {
jedis.close();
}
}
SpringDataRedis
SpringData 是 Spring 中数据操作的模块,包含对 各种数据库 的集成,其中对 Redis 的 集成模块 就叫做SpringDataRedis
官网地址:https://spring.io/projects/spring-data-redis
SpringData 的特点:
- 提供了对 不同Redis客户端 的整合( Lettuce 和 Jedis )
- 提供了 RedisTemplate 统一API 来操作 Redis
- 支持 Redis 的发布 订阅模型
- 支持 Redis哨兵 和 Redis集群
- 支持基于 Lettuce 的 响应式编程
- 支持基于 JDK、JSON、字符串、Spring对象的数据 序列化 及 反序列化
- 支持基于 Redis 的 JDKCollection 实现
SpringDataRedis 中提供了 RedisTemplate 工具类,其中封装了各种对 Redis 的操作。并且将不同数据类型的操作 API 封装到了不同的类型中:

SpringDataRedis 流程
SpringBoot 已经提供了对 SpringDataRedis 的支持
流程:
-
导入 pom 坐标
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.7</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.heima</groupId>
<artifactId>redis-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>redis-demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!--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>
<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>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project> -
配置文件
1
2
3
4
5
6
7
8
9
10
11spring:
redis:
host: 192.168.88.129
port: 6379
password: leadnews
lettuce:
pool:
max-active: 8 # 最大连接
max-idle: 8 # 最大空闲连接
min-idle: 0 # 最小空闲连接
max-wait: 100ms # 连接等待时间 -
测试代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class RedisDemoApplicationTests {
// 注入RedisTemplate
private RedisTemplate<String, Object> redisTemplate;
void testString() {
// 写入一条String数据
redisTemplate.opsForValue().set("name", "Bob");
// 获取string数据
Object name = redisTemplate.opsForValue().get("name");
System.out.println("name = " + name); // Bob
}
}
总结:
SpringDataRedis 的 使用步骤:
- 引入 spring-boot-starter-data-redis 依赖
- 在 application.yml 配置 Redis信息
- 注入 RedisTemplate
SpringDataRedis 的序列化方式
RedisTemplate 可以接收任意 Object 作为 值 写入 Redis,只不过写入前会把 Object 序列化为 字节形式,默认是采用 JDK序列化,缺点是会造成 可读性差、内存占用较大 等问题
自定义 RedisTemplate 的 序列化方式:
1 |
|
整体可读性有了很大提升,并且能将 Java对象 自动的 序列化 为 JSON字符串,并且查询时能 自动 把 JSON 反序列 化为 Java对象

不过,其中记录了 序列化时 对应的 class名称,目的是为了 查询时 实现 自动反序列化。这会带来 额外的内存开销。
为了节省内存空间,不会使用 JSON序列化器 来处理 value,而是统一使用 String序列化器,要求只能存储String 类型的 key 和 value 。当需要存储 Java对象 时,手动完成对象的 序列化 和 反序列化。

1 | // 这种存储在redis的json字符串没有@class部分内容,就是单纯的user对象的json字符串 |
总结:
RedisTemplate 的 两种 序列化 实践方案:
-
方案一:
- 自定义 RedisTemplate
- 修改 RedisTemplate 的序列化器为 GenericJackson2JsonRedisSerializer
-
方案二:
- 使用 StringRedisTemplate
- 写入 Redis 时,手动把 对象 序列化为 JSON
- 读取 Redis 时,手动把读取到的 JSON 反序列化为 对象
Hash 结构的操作
1 |
|








