- 基础篇
- 初识Redis
- Redis常见命令
- Redis的Java客户端
一、基础篇
1.1 初识Redis
1.1.1 认识NoSQL
NoSQL(Not only Structured Query Language)
,非结构型数据库,对数据库格式没有严格约束,往往形式松散,自由,数据库格式可以是key-value
,可以是文档,或者图格式。NoSQL 和 SQL 的区别:
- 结构化和非结构化:
- 传统 SQL 结构型数据库是结构化数据,每一张表都有严格的约束信息:字段名、字段数据类型、字段约束等等信息,插入的数据必须遵守这些约束。
- NoSql 非结构型数据库对数据库格式没有严格约束,往往形式松散,自由,数据库格式可以是
key-value
,可以是文档,或者图格式。
- 关联和非关联:
- 传统数据库的表与表之间往往存在关联,例如外键。
- NoSQL 数据库中的数据是无关联的。
- 查询方式:
- 传统关系型数据库基于 SQL 语句做查询,语法有统一标准。
- 不同的非关系数据库查询语法差异极大,五花八门各种各样。
- 事务:
- 传统关系型数据库能满足事务 ACID 的原则。
- 非关系型数据库往往不支持事务,或者不能严格保证 ACID 的特性,只能实现基本的一致性。
- 结构化和非结构化:
1.1.2 认识Redis
Redis(Remote dictionary server)
,远程词典服务器,是一个基于内存的键值型 NoSQL 数据库。Redis 的特征有以下几个:
- 键值(key - value)型存储,value 支持多种不同数据结构,功能丰富。
- 单线程,每个命令具备原子性。
- 低延迟,速度快(基于内存、IO多路复用、良好的编码)。
- 支持数据持久化(定期将内存搬运到磁盘)。
- 支持主从集群、分片集群(数据拆分)。
- 支持多语言客户端:Java、C、Python。
1.1.3 安装并启动Redis
Redis 作者只开发了 Redis 的 Linux 版本,所以我们要在虚拟机上安装 Redis,这里虚拟机选择 CentOS7 操作系统(CentOS 适合做服务器,因为它更加安全稳定)。
Redis 是基于 C 编写的,所以需要先安装 Redis 所需的 gcc 依赖。
1
yum install -y gcc
将 Redis 的安装包通过 WinSCP 上传到
/usr/local/src
。- 安装包下载地址:Index of /releases/ (redis.io),下载
redis-6.2.6.tar.gz
。
- 安装包下载地址:Index of /releases/ (redis.io),下载
在虚拟机的
/usr/local/src
目录下解压安装包:1
tar -zxvf redis-6.2.6.tar.gz
进入 Redis 的目录:
1
cd redis-6.2.6/
运行编译命令进行安装:
1
[runoob@localhost redis-6.2.6]$ sudo make && make install
安装成功后,在
/usr/local/bin
目录下检查是否有 Redis,有则表示安装成功:1
2
3
4
5
6
7
8[runoob@localhost bin]$ ls -l
总用量 18904
-rwxr-xr-x. 1 root root 4829512 3月 2 20:45 redis-benchmark
lrwxrwxrwx. 1 root root 12 3月 2 20:45 redis-check-aof -> redis-server
lrwxrwxrwx. 1 root root 12 3月 2 20:45 redis-check-rdb -> redis-server
-rwxr-xr-x. 1 root root 5003792 3月 2 20:45 redis-cli
lrwxrwxrwx. 1 root root 12 3月 2 20:45 redis-sentinel -> redis-server
-rwxr-xr-x. 1 root root 9518920 3月 2 20:45 redis-server为 Redis 配置环境变量:
修改
~/.bash_profile
文件,添加 Redis 的环境变量:1
2export REDIS_HOME=/usr/local/bin
export PATH=$PATH:$REDIS_HOME然后重启虚拟机。
接着就可以在任何目录下通过输入
redis-server
来启动 Redis 服务。- 但是这种启动属于前台启动,会阻塞整个会话窗口,窗口关闭或者按下
[CTRL+C]
才能让 Redis 停止,不推荐使用。
- 但是这种启动属于前台启动,会阻塞整个会话窗口,窗口关闭或者按下
如果要让 Redis 以后台方式启动,则必须修改 Redis 的配置文件,就在我们之前解压的 Redis 目录下:
/usr/local/src/redis-6.2.6
,配置文件名称为redis.conf
。- 先备份一份:
sudo cp redis.conf redis.conf.bak
- 先备份一份:
修改
redis.conf
配置文件:根据所写的 Redis 配置文件启动 Redis:
1
[runoob@localhost redis-6.2.6]$ redis-server redis.conf
可以看到,Redis 已经在后台启动了:
1
2
3[runoob@localhost redis-6.2.6]$ ps -ef | grep redis
runoob 2922 1 0 21:55 ? 00:00:00 redis-server 0.0.0.0:6379
runoob 2945 2745 0 21:56 pts/0 00:00:00 grep --color=auto redis
在启动 Redis 服务后,就可以通过客户端和 Redis 进行交互了,这里我们省略了
redis-cli
命令行客户端和图形化界面客户端的内容,后面直接用 Java 客户端与 Redis 进行交互。
1.2 ★★★Redis常见命令
1.2.1 Redis数据结构介绍
Redis 是一个
key-value
数据库,key 一般是String
类型,不过 value 的类型多种多样,常见的 value 类型包括:String
Hash
List
Set
SortedSet
上面的类型都是 Redis 的基本类型。
接下来,我们会分别介绍 Redis 的通用命令以及 Redis 基本类型相应的命令。
1.2.2 Redis命令-通用命令
KEYS pattern
:查看符合 pattern 的所有 key。KEYS *
表示查看所有 key。KEYS a*
表示查看所有以 a 字母开头的 key。KEYS a?
表示查看所有以 a 字母开头,总共两位字符的 key。
不建议在生产环境设备上使用,因为是模糊查询,且 Redis 是单线程,当 key 的数量多了以后,执行该命令会阻塞,将无法进行其他命令,同时对 Redis 服务器也是巨大的负担。
DEL key [key ...]
:删除指定的 key。EXISTS key [key ...]
:判断指定 key 是否存在。EXPIRE key seconds
:给一个 key 设置有效期,有效期到期时该 key 会被自动删除。TTL key
:查看一个 key 的剩余有效期- 不给 key 设置 EXPIRE 的话 TTL 返回值为 -1,表示该 key 永久有效。
- 如果 key 失效了,TTL 返回值为 -2。
1.2.3 Redis命令-String类型
String
类型,也就是字符串类型,是 Redis 中最简单的存储类型,其 value 是字符串,不过根据字符串的格式不同,又可以分为 3 类:string
:普通字符串int
:整数类型,可以做自增、自减操作float
:浮点类型,可以做自增、自减操作
KEY VALUE msg hello world num 10 score 9.5 不管是哪种格式,底层都是字节数组形式存储,只不过编码方式不同。字符串类型的最大空间不能超过 512 MB。
String 类型的常见命令:
思考
- Redis 中没有类似 MySQL 中的 Table 的概念,所有的数据存储在一起,那我们该如何区分不同类型的 key 呢?
- 例如,需要存储用户、商品信息到 redis,有一个用户 id 是 1,有一个商品 id 恰好也是 1。那
SET id 1
存储的究竟是用户的 id,还是商品的 id 呢?- 所以我们要用到 Redis 的 key 的层级格式。
- 例如,需要存储用户、商品信息到 redis,有一个用户 id 是 1,有一个商品 id 恰好也是 1。那
★key的层级格式
Redis 的 key 允许有多个单词形成层级结构,多个单词之间用
:
隔开,格式如下:项目名:业务名:类型:id
- 这个格式并非固定,也可以根据自己的需求来删除或添加词条。
回到 “思考” 中的问题,假如我们的项目名称为 redis,有 user 和 product 两种不同类型的数据,则我们可以这样定义 key:
redis:user:1
redis:product:1
一旦我们在 Redis 中采用这样的方式存储数据,那么在可视化界面中,redis 会以层级结构来对数据进行存储,形成类似于这样的结构,更加方便 Redis 获取数据:
1.2.4 Redis命令-Hash类型
Hash
类型,也叫散列,其 value 是一个无序字典,类似于 Java 中的HashMap
结构。String
结构是将对象序列化为 JSON 字符串后存储,当需要修改对象某个字段时很不方便。Hash
结构可以将对象中的每个字段独立存储,可以针对单个字段做 CRUD。
Hash
类型的常见命令:
1.2.5 Redis命令-List类型
Redis 中的
List
类型与 Java 中的LinkedList
类似,可以看做是一个双向链表结构,既可以支持正向检索,也可以支持反向检索。特征也与
LinkedList
类似:- 有序
- 元素可以重复
- 插入和删除快
- 查询速度一般
常用来存储一个有序数据,例如:朋友圈点赞列表,评论列表等。
List
类型的常见命令:
1.2.6 Redis命令-Set类型
Redis 的
Set
结构与 Java 中的HashSet
类似,可以看做是一个 value 为null
的HashMap
。因为也是一个 hash 表,因此具备与HashSet
类似的特征:- 无序
- 元素不可重复
- 查找快
- 支持交集、并集、差集等功能
Set
类型的常见命令:
1.2.7 Redis命令-SortedSet类型
Redis 的
SortedSet
是一个可排序的 set 集合,与 Java 中的TreeSet
有些类似,但底层数据结构却差别很大。SortedSet
中的每一个元素都带有一个score
属性,可以基于score
属性对元素排序,底层的实现是一个跳表(SkipList)加 hash 表。SortedSet
具备下列特性:- 可排序
- 元素不重复
- 查询速度快
因为
SortedSet
的可排序特性,经常被用来实现排行榜这样的功能。SortedSet
类型的常见命令:
1.3 Redis的Java客户端
目前 Redis 主流的 Java 客户端包括:
Jedis
:以 Redis 命令作为方法名称,学习成本低,简单实用。但是 Jedis 实例是线程不安全的,多线程环境下需要基
于连接池来使用。Lettuce
:Lettuce 是基于 Netty 实现的,支持同步、异步和响应式编程方式,并且是线程安全的。支持 Redis 的哨兵
模式、集群模式和管道模式。Redission
:Redisson 是一个基于 Redis 实现的分布式、可伸缩的 Java 数据结构集合。包含了诸如Map
、Queue、Lock、Semaphore、AtomicLong 等强大功能,用于分布式环境中。
Spring Data Redis
底层兼容了Jedis
和Lettuce
,所以我们重点学习它,不过有些公司也仍在使用Jedis
,所以也要学习一下Jedis
。
1.3.1 Jedis
使用 Jedis 的步骤:
使用 maven 创建新模块
jedis-demo
,设置相应内容:在
pom.xml
文件中导入 jedis 的依赖:1
2
3
4
5
6<!--jedis依赖-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.7.0</version>
</dependency>建立连接。
使用 jedis 操作 Redis 数据库。
释放资源。
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
45package com.f.redis;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import redis.clients.jedis.Jedis;
import java.util.HashMap;
import java.util.Map;
/**
* @author fzy
* @date 2024/3/4 21:03
*/
public class JedisTest {
private Jedis jedis;
// 表示在当前类中的每个@Test方法之前执行该注解方法
public void setUp() {
// 1.建立连接
jedis = new Jedis("192.168.44.130", 6379);
// 2.设置密码
jedis.auth("123456");
// 3.选择库
jedis.select(0);
}
public void test() {
Map<String, String> map = new HashMap<>();
map.put("id", "1");
map.put("name", "jack");
map.put("age", "18");
jedis.hmset("redis:user:1", map);
Map<String, String> data = jedis.hgetAll("redis:user:1");
System.out.println(data);
}
// 表示在当前类中的每个@Test方法之后执行该注解方法
public void tearDown() {
if (jedis != null) {
jedis.close();
}
}
}
Jedis连接池
Jedis 本身是线程不安全的,并且频繁地创建和销毁连接会有性能损耗,因此推荐使用 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
27package com.f.redis.util;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
/**
* @author fzy
* @date 2024/3/4 21:53
*/
public class JedisConnectFactory {
private static final JedisPool jedisPool;
static {
// 配置连接池
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(8);
poolConfig.setMaxIdle(8);
poolConfig.setMinIdle(0);
poolConfig.setMaxWaitMillis(1000);
jedisPool = new JedisPool(poolConfig, "192.168.44.130", 6379, 1000, "123456");
}
public static Jedis getJedis() {
return jedisPool.getResource();
}
}1
2
3
4
5
void setUp() {
jedis = JedisConnectFactory.getJedis();
jedis.select(0);
}
1.3.2 ★★★Spring Data Redis
SpringData
是 Spring 中数据操作的模块,包含对各种数据库的集成,其中对 Redis 的集成模块就叫做SpringDataRedis
,官网地址:Spring Data Redis- 提供了对不同 Redis 客户端的整合(Lettuce 和 Jedis)
- 提供了 RedisTemplate 统一 API 来操作 Redis
- 支持 Redis 的发布订阅模型
- 支持 Redis 哨兵和 Redis 集群
- 支持基于 Lettuce 的响应式编程
- 支持基于JDK、JSON、字符串、Spring对象的数据序列化及反序列化
- 支持基于 Redis 的 JDKCollection 实现
RedisTemplate
SpringDataRedis
中提供了RedisTemplate
工具类,其中封装了各种对 Redis 的操作,并且将不同数据类型的操作 API 封装到了不同的类型中:
1.3.2.1 在 springboot 中使用 Spring Data Redis
在 springboot 中使用 Spring Data Redis:
新建
spring-data-redis-demo
模块,使用Spring Initializr
初始化 springboot 项目,设置相关内容:在技术集中勾选
Spring Data Redis
,另外勾选Lombok
,用于实体类:配置
pom.xml
文件,除了前面导入的spring-boot-starter-data-redis
依赖之外,还要引入commons-pool2
连接池依赖:1
2
3
4
5
6
7
8
9
10<!--Spring Data Redis依赖-->
<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>编辑
application.yml
文件,配置 Redis 的相关信息:1
2
3
4
5
6
7
8
9
10
11
12
13
14spring:
data:
redis:
host: 192.168.44.130
port: 6379
password: 123456
database: 0
# springboot默认使用lettuce,如果要使用jedis,需要引入jedis的依赖
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 0
max-wait: 1000注入
RedisTemplate
。1
2
private RedisTemplate redisTemplate;对
RedisTemplate
的方法进行测试:1
2
3
4
5
6
7
8
public void testString() {
// 写入一条String数据
redisTemplate.opsForValue().set("name", "jack");
// 读取String数据
String name = redisTemplate.opsForValue().get("name").toString();
System.out.println(name);
}
1.3.2.2 自定义RedisTemplate的RedisSerializer
在前面的例子中,由于
redisTemplate.opsForValue()
的set
方法的参数是泛型:void set(K key, V value);
,没有指定K
和V
的话就是Object
类型,所以存储在 Redis 服务器中的数值是这样的:1
2
3
4127.0.0.1:6379> KEYS *
1) "\xac\xed\x00\x05t\x00\x04name"
127.0.0.1:6379> GET "\xac\xed\x00\x05t\x00\x04name"
"\xac\xed\x00\x05t\x00\x04jack"亦即:我们存入 Redis 服务器的键和值都是 Java 对象,会被
RedisTemplate
底层的默认序列化方法:JDK 序列化工具JdkSerializationRedisSerializer
序列化。而
JdkSerializationRedisSerializer
采用的是ObjectOutputStream
(把 Java 对象转成字节)来进行序列化,所以最终存储在 Redis 服务器中的数值就显示成这样了。这种默认的序列化方式有以下几个缺点:
- 可读性差
- 内存占用大
为了解决这个问题,我们需要初始化
RedisTemplate
的RedisSerializer
:RedisTemplate
有以下几个主要的RedisSerializer
:private @Nullable RedisSerializer keySerializer = null;
private @Nullable RedisSerializer valueSerializer = null;
private @Nullable RedisSerializer hashKeySerializer = null;
private @Nullable RedisSerializer hashValueSerializer = null;
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
34package com.f.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.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
/**
* @author fzy
* @date 2024/3/5 13:56
*/
public class RedisConfig {
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
// 1.创建RedisTemplate对象
// key一般是String类型,value一般是Object类型
RedisTemplate<String, Object> template = new RedisTemplate<>();
// 2.设置连接工厂
template.setConnectionFactory(factory);
// 3.创建JSON序列化工具
GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
// 4.设置Key的序列化
template.setKeySerializer(RedisSerializer.string());
template.setHashKeySerializer(RedisSerializer.string());
// 5.设置Value的序列化
template.setValueSerializer(jsonRedisSerializer);
template.setHashValueSerializer(jsonRedisSerializer);
// 6.返回RedisTemplate对象
return template;
}
}然后重新注入
RedisTemplate
对象:1
2
private RedisTemplate<String, Object> redisTemplate;进行测试:
1
2
3
4
5
6
7
8
public void testString() {
// 写入一条String数据
redisTemplate.opsForValue().set("name", "jack");
// 读取String数据
String name = redisTemplate.opsForValue().get("name").toString();
System.out.println(name);
}可以看到存储在 Redis 服务器中的数值是这样的:
1
2
3
4127.0.0.1:6379> KEYS *
1) "name"
127.0.0.1:6379> GET name
"\"jack\""是我们想看到的效果。
因为我们对
Value
的序列化是 JSON 序列化,所以可以测试Value
值为 pojo 对象时,Redis 服务器中数值的样子:1
2
3
4
5
6
7
8
9
10
public void testJson() {
User user1 = new User();
user1.setId(1);
user1.setName("tom");
user1.setAge(18);
redisTemplate.opsForValue().set("redis:user:1", user1);
User result = (User) redisTemplate.opsForValue().get("redis:user:1");
System.out.println(result); // User(id=1, name=tom, age=18)
}1
2
3
4
5
6
7
8
9
10
11
12
13
14package com.f.redis.pojo;
import lombok.Data;
/**
* @author fzy
* @date 2024/3/5 14:39
*/
public class User {
private int id;
private String name;
private int age;
}可以看到存储在 Redis 服务器中的数值是这样的:
1
2
3
4127.0.0.1:6379> KEYS *
1) "redis:user:1"
127.0.0.1:6379> GET redis:user:1
"{\"@class\":\"com.f.redis.pojo.User\",\"id\":1,\"name\":\"tom\",\"age\":18}"- 注意:在
RedisConfig
类中对valueSerializer
进行设置后,RedisTemplate
在将 Java 对象存储到 Redis 服务器中时,会将 Java 对象序列化为 JSON 数据;同理,RedisTemplate
在将 JSON 数据从 Redis 服务器中取出到 Java 内存中时,会将 JSON 数据反序列化为 Java 对象。
- 注意:在
1.3.2.3 StringRedisTemplate
尽管 JSON 的序列化方式可以满足我们的需求,但依然存在一些问题,例如:
1
2127.0.0.1:6379> GET redis:user:1
"{\"@class\":\"com.f.redis.pojo.User\",\"id\":1,\"name\":\"tom\",\"age\":18}"为了在反序列化的时候知道对象的类型,JSON 序列化器在序列化时会将类的
class
类型写入json
结果中,并存入Redis,这会带来额外的内存开销。- 为了节省内存空间,我们并不会使用 JSON 序列化器来处理
value
,而是统一使用 String 序列化器,要求只能存储 String 类型的key
和value
。当需要存储 Java 对象时,手动完成对象的序列化和反序列化。
- 为了节省内存空间,我们并不会使用 JSON 序列化器来处理
Spring 默认提供了一个 StringRedisTemplate
类,它的 key
和 value
的序列化方式默认就是 String
方式,省去了我们自定义 RedisTemplate
的过程:
1 | package com.f.redis; |
然后再看 Redis 服务器的数据:
1
2
3
4127.0.0.1:6379> KEYS *
1) "redis:user:1"
127.0.0.1:6379> GET redis:user:1
"{\"id\":1,\"name\":\"tom\",\"age\":18}"已经没有
@class
的数据了。
1.3.2.4 RedisTemplate操作Hash类型
RedisTemplate
操作Hash
类型的数据的方法名,和 Redis 本身的命令不尽相同,有所差别:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void testHash() {
// HSET <-> put
stringRedisTemplate.opsForHash().put("redis:user:1", "id", "1");
Map<String, String> map = new HashMap<>();
map.put("name", "zhangsan");
map.put("age", "18");
// HMSET <-> putAll
stringRedisTemplate.opsForHash().putAll("redis:user:1", map);
// HGET <-> get
String name = stringRedisTemplate.opsForHash().get("redis:user:1", "name").toString();
System.out.println(name);
// HMGET <-> multiGet
List list = new ArrayList<>();
list.add("id");
list.add("age");
List<Object> values = stringRedisTemplate.opsForHash().multiGet("redis:user:1", list);
values.forEach(value -> {
System.out.println(value);
});
}