初识 Redis

NoSQL

NoSql 可以翻译做 Not Only Sql(不仅仅是SQL),或者是 No Sql(非Sql的)数据库。是相对于传统关系型数据库而言,有很大差异的一种特殊的数据库,因此也称之为 非关系型数据库

结构化与非结构化

传统关系型数据库结构化数据,每一张表都有严格的约束信息:字段名、字段数据类型、字段约束等等信息,插入的数据必须遵守这些约束:

NoSql 则对数据库格式 没有严格约束,往往形式松散,自由。

可以是键值型:

可以是文档型:

可以是图格式:

关联和非关联

传统数据库的表与表之间往往存在关联,如外键。而非关系型数据库 不存在关联关系,要维护关系要么靠代码中的业务逻辑,要么靠数据之间的耦合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
id: 1,
name: "张三",
orders: [
{
id: 1,
item: {
id: 10, title: "荣耀6", price: 4999
}
},
{
id: 2,
item: {
id: 20, title: "小米11", price: 3999
}
}
]
}

查询方式

事务

传统关系型数据库能满足事务 ACID 的原则。 而非关系型数据库往往不支持事务,或者不能严格保证 ACID 的特性,只能实现基本的一致性。

小结

  • 存储方式
    • 关系型数据库基于磁盘进行存储,会有大量的磁盘IO,对性能有一定影响
    • 非关系型数据库,他们的操作更多的是依赖于内存来操作,内存的读写速度会非常快,性能自然会好一些
  • 扩展性
    • 关系型数据库集群模式一般是主从,主从数据一致,起到数据备份的作用,称为垂直扩展。
    • 非关系型数据库可以将数据拆分,存储在不同机器上,可以保存海量数据,解决内存大小有限的问题。称为水平扩展。
    • 关系型数据库因为表之间存在关联关系,如果做水平扩展会给数据查询带来很多麻烦

Redis

Redis 诞生于 2009 年全称是 Remote Dictionary Server 远程词典服务器,是一个基于内存的键值型NoSQL数据库

特征

  • 键值(key-value)型,value 支持多种不同数据结构,功能丰富
  • 单线程,每个命令具备原子性
  • 低延迟,速度快(基于内存、IO 多路复用、良好的编码)。
  • 支持数据持久化
  • 支持主从集群、分片集群
  • 支持多语言客户端

安装 redis

1
2
3
4
5
6
# Redis是基于C语言编写的,因此首先需要安装Redis所需要的gcc依赖
yum install -y gcc tcl
cp /opt/redis/redis-6.2.6.tar.gz /usr/local/redis
tar -xzf redis-6.2.6.tar.gz
cd redis-6.2.6
make && make install

默认的安装路径是在 /usr/local/bin 目录下:

该目录已经默认配置到环境变量,因此可以在任意目录下运行这些命令。其中:

  • redis-cli:是 redis 提供的命令行客户端
  • redis-server:是 redis 的服务端启动脚本
  • redis-sentinel:是 redis 的哨兵启动脚本

默认启动

这种启动属于 前台启动,会阻塞整个会话窗口,窗口关闭或者按下 CTRL + C 则 Redis 停止。不推荐使用。

1
2
# 安装完成后,在任意目录输入redis-server命令即可启动Redis:
redis-server

指定配置启动

如果要让 Redis 以 后台 方式启动,则必须修改 Redis 配置文件(redis.conf)

1
2
# 备份conf
cp redis.conf redis.conf.bck

修改配置

1
2
3
4
5
6
# 允许访问的地址,默认是127.0.0.1,会导致只能在本地访问。修改为0.0.0.0则可以在任意IP访问,生产环境不要设置为0.0.0.0
bind 0.0.0.0
# 守护进程,修改为yes后即可后台运行
daemonize yes
# 密码,设置后访问Redis必须输入密码
requirepass redis

redis 其他配置

1
2
3
4
5
6
7
8
9
10
# 监听的端口
port 6379
# 工作目录,默认是当前目录,也就是运行redis-server时的命令,日志、持久化等文件会保存在这个目录
dir .
# 数据库数量,设置为1,代表只使用1个库,默认有16个库,编号0~15
databases 1
# 设置redis能够使用的最大内存
maxmemory 512mb
# 日志文件,默认为空,不记录日志,可以指定日志文件名
logfile "redis.log"

启动 Redis

1
2
3
4
# 进入redis安装目录 
cd /usr/local/src/redis-6.2.6
# 启动
redis-server redis.conf

停止服务

1
2
3
# 利用redis-cli来执行 shutdown 命令,即可停止 Redis 服务,
# 因为之前配置了密码,因此需要通过 -u 来指定密码
redis-cli -u redis shutdown

开机自启

新建一个系统服务文件:

1
vi /etc/systemd/system/redis.service
1
2
3
4
5
6
7
8
9
10
11
[Unit]
Description=redis-server
After=network.target

[Service]
Type=forking
ExecStart=/usr/local/bin/redis-server /usr/local/src/redis-6.2.6/redis.conf
PrivateTmp=true

[Install]
WantedBy=multi-user.target

然后重载系统服务:

1
systemctl daemon-reload

使用命令操作 redis

指令 说明
systemctl start redis 启动
systemctl stop redis 停止
systemctl restart redis 重启
systemctl status redis 查看状态
systemctl enable redis 开机自启

命令行客户端

Redis 安装完成后就自带了命令行客户端:redis-cli,使用方式如下。如果是基于 redis-cli 连接 Redis 服务,可以通过 select 命令来选择数据库:

1
2
3
4
redis-cli [options] [commonds]

# 选择 0号库
select 0
选项 说明
-h 127.0.0.1 指定要连接的redis节点的IP地址,默认是127.0.0.1
-p 6379 指定要连接的redis节点的端口,默认是6379
-a 123321 指定redis的访问密码
命令 说明
ping 与redis服务端做心跳测试,服务端正常会返回 pong。不指定commond时,会进入 redis-cli 的交互控制台:

图形化客户端

图形化客户端源码

图形化客户端安装包

Redis 常见命令

Redis 为了方便我们学习,将操作不同数据类型的命令也做了分组,在官网( https://redis.io/commands )可以查看到不同的命令

Redis 是典型的 key-value 数据库,key 一般是字符串,而 value 包含很多不同的数据类型:

Redis 通用命令

通过 help [command] 可以查看一个命令的具体用法

1
2
3
4
5
6
7
# 查看keys命令的帮助信息:
127.0.0.1:6379> help keys

KEYS pattern
summary: Find all keys matching the given pattern
since: 1.0.0
group: generic
命令 说明
KEYS 查看符合模板的所有key
DEL 删除一个指定的key
EXISTS 判断key是否存在
EXPIRE 给一个key设置有效期,有效期到期时该key会被自动删除
TTL 查看一个KEY的剩余有效期

String 类型

String 类型,也就是字符串类型,是 Redis 中最简单的存储类型。

其 value 是字符串,不过根据字符串的格式不同,又可以分为 3 类:

  • string:普通字符串
  • int:整数类型,可以做自增、自减操作
  • float:浮点类型,可以做自增、自减操作

不管是哪种格式,底层都是字节数组形式存储,只不过是编码方式不同。字符串类型的最大空间不能超过 512m。

String 常见命令 说明
SET 添加或者修改已经存在的一个String类型的键值对
GET 根据key获取String类型的value
MSET 批量添加多个String类型的键值对
MGET 根据多个key获取多个String类型的value
INCR 让一个整型的key自增1
INCRBY 让一个整型的key自增并指定步长
INCRBYFLOAT 让一个浮点类型的数字自增并指定步长
SETNX 添加一个String类型的键值对,前提是这个key不存在,否则不执行
SETEX 添加一个String类型的键值对,并且指定有效期

Key 结构

Redis 没有类似 MySQL 中的 Table 的概念,我们该如何区分不同类型的 key 呢?例如,需要存储用户、商品信息到 redis,有一个用户 id 是 1,有一个商品 id 恰好也是1,此时如果使用 id 作为 key,那就会冲突了,该怎么办?

可以通过给 key 添加前缀加以区分,不过这个前缀不是随便加的,有一定的规范:

Redis 的 key 允许有 多个单词 形成层级结构,多个单词之间用 : 隔开

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

如果项目名称叫 cyan,有 user 和 product 两种不同类型的数据,我们可以这样定义 key:

  • user 相关的 key:cyan:user:1

  • product 相关的 key:cyan:product:1

如果 Value 是一个 Java 对象,例如一个 User 对象,则可以将对象序列化为 JSON 字符串后存储:

KEY VALUE
cyan:user:1 {“id”:1, “name”: “Jack”, “age”: 21}
cyan:product:1 {“id”:1, “name”: “小米11”, “price”: 4999}

Hash 类型

Hash 结构 可以将对象中的每个字段独立存储,可以针对单个字段做 CRUD:

Hash 常见命令 说明
HSET key field value 添加或者修改hash类型key的field的值
HGET key field 获取一个hash类型key的field的值
HMSET 批量添加多个hash类型key的field的值
HMGET 批量获取多个hash类型key的field的值
HGETALL 获取一个hash类型的key中的所有的field和value
HKEYS 获取一个hash类型的key中的所有的field
HINCRBY 让一个hash类型key的字段值自增并指定步长
HSETNX 添加一个hash类型的key的field值,前提是这个field不存在,否则不执行

List 类型

Redis 中的 List 可以看做 是一个双向链表结构。 既可以支持正向检索和也可以支持反向检索。

  • 有序
  • 元素 可以重复
  • 插入和删除快
  • 查询速度一般
List 常见命令 说明
LPUSH key element ... 向列表左侧插入一个或多个元素
LPOP key 移除并返回列表左侧的第一个元素,没有则返回nil
RPUSH key element ... 向列表右侧插入一个或多个元素
RPOP key 移除并返回列表右侧的第一个元素
LRANGE key star end 返回一段角标范围内的所有元素
BLPOP和BRPOP 与LPOP和RPOP类似,只不过在没有元素时等待指定时间,而不是直接返回nil

Set 类型

Redis 的 Set 结构可以看做是一个 value 为 null 的 HashMap。

  • 无序

  • 元素不可重复

  • 查找快

  • 支持交集、并集、差集等功能

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的差
SUNION key1 key2 ... 求key1与key2的并

SortedSet 类型

Redis 的 SortedSet 是一个 可排序 的 set 集合,SortedSet 中的每一个元素都带有一个 score 属性,可以基于 score 属性对元素排序,底层的实现 是一个跳表(SkipList)加 hash 表。

  • 可排序
  • 元素不重复
  • 查询速度快
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 即可,

命令 说明
ZRANK key member 升序获取sorted set 中的指定元素的排名
ZREVRANK key memeber 降序获取sorted set 中的指定元素的排名

Jedis

在 Redis 官网中提供了各种语言的客户端

Java 客户端包括:

  • Jedis 和 Lettuce:这两个主要是提供了 Redis 命令对应的 API,方便我们操作 Redis,而 SpringDataRedis 又对这两种做了抽象和封装
  • Redisson:是在 Redis 基础上实现了分布式的可伸缩的 java 数据结构,例如 Map.Queue 等,而且支持跨进程的同步机制: Lock.Semaphore 等待,比较适合用来实现特殊的功能需求。

快速入门

1
2
3
4
5
6
<!--jedis-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.7.0</version>
</dependency>
1
2
3
4
5
6
7
8
9
@BeforeEach
void setUp() {
// 建立连接
jedis = new Jedis("localhost", 6379);
// 设置密码
jedis.auth("root");
// 选择库
jedis.select(0);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Test
void testString() {
// 存入数据
String result = jedis.set("name", "Cyan");
System.out.println("result = " + result);
// 获取数据
String name = jedis.get("name");
System.out.println("name = " + name);
}

@Test
void testHash() {
// 存入数据
jedis.hset("user:1","name","Jack");
jedis.hset("user:1","age","21");
// 获取数据
Map<String, String> map = jedis.hgetAll("user:1");
System.out.println(map);
}

Jedis 连接池

创建 Jedis 连接池

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class JedisConnectionFactory {
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,
"localhost",6379,1000,"root");
}

public static Jedis getJedis(){
return jedisPool.getResource();
}
}
  • JedisConnectionFacotry:工厂设计模式是实际开发中非常常用的一种设计模式,我们可以使用工厂,去降低代的耦合,比如Spring 中的 Bean 的创建,就用到了工厂设计模式

  • 静态代码块:随着类的加载而加载,确保只能执行一次,我们在加载当前工厂类的时候,就可以执行 static 的操作完成对连接池的初始化

  • 最后提供返回连接池中连接的方法

改造 setup

1
2
3
4
5
6
7
8
9
10
@BeforeEach
void setUp() {
// 建立连接
// jedis = new Jedis("localhost", 6379);
jedis = JedisConnectionFactory.getJedis();
// 设置密码
jedis.auth("root");
// 选择库
jedis.select(0);
}

SpringDataRedis

SpringData 是 Spring 中数据操作的模块,包含对各种数据库的集成,其中对 Redis 的集成模块就叫做 SpringDataRedis,

  • 提供了对不同 Redis 客户端的整合(Lettuce 和 Jedis)
  • 提供了 RedisTemplate 统一 API 来操作 Redis
  • 支持 Redis 的发布订阅模型
  • 支持 Redis 哨兵和 Redis 集群
  • 支持基于 Lettuce 的响应式编程
  • 支持基于 JDK、JSON、字符串、Spring 对象的数据序列化及反序列化
  • 支持基于 Redis 的 JDKCollection 实现

SpringDataRedis 中提供了 RedisTemplate 工具类,其中封装了各种对 Redis 的操作。并且将不同数据类型的操作 API 封装到了不同的类型中:

快速入门

1
2
3
4
5
6
7
8
9
10
11
<!--redis依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<!--Jackson依赖-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
spring:
redis:
host: 127.0.0.1
port: 6379
password: redis
lettuce:
pool:
max-active: 8 # 最大连接
max-idle: 8 # 最大空闲连接
min-idle: 0 # 最小空闲连接
max-wait: 100ms # 连接等待时间
1
2
3
4
5
6
@SpringBootTest
class RedisStringTests {

@Autowired
private RedisTemplate redisTemplate;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@SpringBootTest
class RedisStringTests {

@Autowired
private RedisTemplate edisTemplate;

@Test
void testString() {
// 写入一条String数据
redisTemplate.opsForValue().set("name", "Cyan Chau");
// 获取string数据
Object name = stringRedisTemplate.opsForValue().get("name");
System.out.println("name = " + name);
}
}

自定义序列化

RedisTemplate 可以接收任意 Object 作为值写入 Redis,只不过写入前会把 Object 序列化为字节形式,默认是采用 JDK 序列化,得到的结果是这样的:

自定义 RedisTemplate 的序列化方式采用了 JSON 序列化来代替默认的 JDK 序列化方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@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.setValueSerializer(jsonRedisSerializer);
// 返回
return template;
}
}

StringRedisTemplate

为了节省内存空间,我们可以不使用 JSON 序列化器来处理 value,而是 统一使用 String 序列化器,要求只能存储 String 类型的 key 和 value。当需要存储 Java 对象时,手动完成对象的序列化和反序列化。

因为存入和读取时的序列化及反序列化都是我们自己实现的,SpringDataRedis 就不会将 class 信息写入 Redis 了

因此 SpringDataRedis 就提供了 RedisTemplate 的子类:StringRedisTemplate,它的 key 和 value 的序列化方式默认就是 String 方式。

省去了我们自定义 RedisTemplate 的序列化方式的步骤,而是直接使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@SpringBootTest
public class StringRedisTest {
@Autowired
private StringRedisTemplate stringRedisTemplate;
// JSON序列化工具
private static final ObjectMapper mapper = new ObjectMapper();

@Test
void testSaveUser() throws JsonProcessingException {
// 创建对象
User user = new User("Cyan", 21);
// 手动序列化
String json = mapper.writeValueAsString(user);
// 写入数据
stringRedisTemplate.opsForValue().set("user:200", json);

// 获取数据
String jsonUser = stringRedisTemplate.opsForValue().get("user:200");
// 手动反序列化
User user1 = mapper.readValue(jsonUser, User.class);
System.out.println("user1 = " + user1);
}
}