Redis 基础
Redis 基础篇
NoSql 数据库
1. 入门
1.1 NoSQL
SQL 和 NoSQL
关系型数据库 和 非关系型数据库
区别:
(1)结构化和非结构化
- SQL关系型数据库是结构化数据,每一张表都有严格的约束信息:字段名、字段数据类型、字段约束等等信息,插入的数据必须遵守这些约束
- NoSql对数据库格式没有严格约束,往往形式松散,自由。可以是key-value,可以是文档,或者图格式
(2)关联和非关联
关系型:
非关系型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19{
id: 1,
name: "张三",
orders: [
{
id: 1,
item: {
id: 10, title: "荣耀6", price: 4999
}
},
{
id: 2,
item: {
id: 20, title: "小米11", price: 3999
}
}
]
}
(3)查询方式
(4)事务
- 传统关系型数据库能满足事务ACID的原则 ,而非关系型数据库往往不支持事务,或者不能严格保证ACID的特性,只能实现基本的一致性。
1.2 Redis 安装-尚硅谷
此处主要参考尚硅谷的教程:
1.2.1 安装
- Redis是基于C编写,所以需要先安装Redis所需的gcc依赖,可以用
gcc -v
进行查看验证
1 | yum install -y gcc |
- 官网下载链接:Download | Redis
- 这里我用的 xftp7 上传到虚拟机的
/opt
目录下,在该目录下进行解压缩
1 | tar -zxvf redis-7.0.12.tar.gz |
- 进入
/opt/redis-7.0.12
目录下
- 执行 make 命令进行编译安装,出现如下即安装成功
1 | make && make install |
- 查看默认安装目录:
usr/local/bin
,其类似于windows系统的C:\Program Files
,因为能帮我们直接配置到 path 里
redis-benchmark:性能测试工具,服务启动后运行该命令,看看自己本子性能如何
redis-check-aof:修复有问题的 AOF 文件,rdb 和 aof 后面讲
redis-check-rdb:修复有问题的 dump.rdb 文件
redis-cli:客户端,操作入口
redis-sentinel:redis 集群使用
redis-server:Redis 服务器启动命令
1.2.2 备份并修改配置文件
- 在
/opt/redis-7.0.12
目录下,先备份以下自带的配置文件到新建的/myredis
文件夹(该文件夹在根目录下)下,不要在原件上改,然后再修改拷贝的redis7.conf
做初始化设置
1 | mkdir /myredis |
修改
/myredis
目录下redis.conf
配置文件做初始化设置,配置完记得重启!1
2cd /myredis
vim redis7.conf- 默认 daemonize no 改为 daemonize yes
- 默认 protected-mode yes 改为 protected-mode no
- 默认 bind 127.0.0.1 改为 直接注释掉(默认的只能访问本机IP),否则影响远程连接
- 添加 redis 密码 改为 requirepass 你自己设置的密码
- 最后输入
:wq!
保存并退出
补充:Vim 的一些用法
1.2.3 启动并连接服务
启动服务
1
2redis-server /myredis/redis7.conf
ps -ef | grep redis | grep -v grep端口 6379 被占用,说明后台启动成功
ps 用于查看进程
连接服务
1
2redis-cli -a thr -p 6379
ps -ef | grep redis | grep -v grep在 127.0.0.1:6370 窗口下,执行
ping
,若结果为pong
则表示连接成功补充
退出连接:
quit
若连接时不想看见警告,可用:
redis-cli -a thr -p 6379 2>/dev/nul
该操作是将警告重定向至linux的黑洞文件
若想开机自启,则跳转到 1.3.3
1.2.4 测试 hello world
1 | set k1 helloworld |
1.2.5 关闭 redis
- 如果已经在 redis 服务器里,就直接用
shutdown
即可,可用lsof -i:6379
验证
- 单实例关闭
1 | redis-cli -a thr shutdown |
- 多实例关闭,指定端口关闭
1 | redis-cli -p 6379 shutdown |
1.2.6 卸载 redis
- 先停止 redis-server 服务
1 | redis-cli -a thr shutdown |
- 删除 /usr/local/lib 目录下与 redis 相关的文件
1 | ls -l /usr/local/bin/redis-* |
1.3 Redis 安装启动-黑马
redis的启动方式有很多种,例如:
- 默认启动
- 指定配置启动
- 开机自启
1.3.1 默认启动
- 安装完成后,在任意目录输入
redis-server
命令即可启动 Redis:
- 这种启动属于前台启动,会阻塞整个会话窗口,需要重开一个窗口才可以进行连接,窗口关闭或者按下
CTRL + C
则Redis停止,不推荐使用。
1.3.2 指定配置启动
- 如果要让Redis以后台方式启动,则必须修改Redis配置文件,就在我们之前解压的redis安装包下,叫redis.conf
- 先备份:
cp redis.conf redis.conf.bck
- 然后修改redis.conf文件中的一些配置:
1 | 允许访问的地址,默认是127.0.0.1,会导致只能在本地访问。修改为0.0.0.0则可以在任意IP访问,生产环境不要设置为0.0.0.0 |
- Redis的其它常见配置:
1 | 监听的端口 |
- 启动Redis:
1 | 进入redis安装目录 |
- 停止服务:
1 | 利用redis-cli来执行 shutdown 命令,即可停止 Redis 服务, |
1.3.3 开机自启
这个地方需要跟着做一下
- 首先,新建一个系统服务文件:
1 | vi /etc/systemd/system/redis.service |
- 内容如下:
1 | [Unit] |
重点在这一行,
ExecStart=/usr/local/bin/redis-server /usr/local/src/redis-6.2.6/redis.conf
前者是安装目录不变,后者需改成配置文件所在位置(由于我是根据尚硅谷的教程做的,所以配置文件所在位置为
/myredis/redis7.conf
)
- 然后重载系统服务:
1 | systemctl daemon-reload |
- 现在,我们可以用下面这组命令来操作redis了:
1 | 启动 |
- 执行下面的命令,可以让redis开机自启:
1 | systemctl enable redis |
可以通过
ps -ef | grep redis | grep -v grep
查看是否开启 redis
1.4 Redis 客户端
安装完成Redis,我们就可以操作Redis,实现数据的CRUD了。这需要用到Redis客户端,包括:
- 命令行客户端
- 图形化桌面客户端
- 编程客户端
1.4.1 命令行客户端
这个在 1.2.3 有详细图解
- Redis安装完成后就自带了命令行客户端:
redis-cli
,使用方式如下:
1 | 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
- 不指定commond时,会进入
redis-cli
的交互控制台:
1.4.2 图形化桌面客户端
GitHub上的大神编写了Redis的图形化桌面客户端,地址:
https://github.com/uglide/RedisDesktopManager
不过该仓库提供的是RedisDesktopManager的源码,并未提供windows安装包
在下面这个仓库可以找到安装包:
https://github.com/lework/RedisDesktopManager-Windows/releases
- 下载安装即可,然后点击连接 redis 服务器
- 连不上,尝试关闭防火墙后成功连接
1 | systemctl status firewalld # 查看防火墙状态 |
一共有0-15共16个库
1.4.3 测试一下
- 命令行客户端
1 | redis-cli -a thr # 先进入命令行客户端 |
图形化客户端
- 可以看到刚刚添加的数据
- 手动在图形化界面也可添加
- 同时在命令行中也可以查到
2. Redis 常见命令
官方文档里的常见命令:Commands | Redis ,可以分组查找相关的命令
也可在命令行里用
help @xxx
进行查找,如,help @string
2.1 Redis 数据结构介绍
- Redis 是 key-value键值对 的数据库,一般 key 是 String,不过value 数据结构是多种多样的,下面的前五种是比较常见的数据结构:
2.2 Redis 通用命令
在命令行中可以用
help @generic
查询通过
help [command]
也可以查看一个命令的具体用法官方文档上还会有使用案例:
通用命令是每种数据类型都可以使用的命令,常见的有:
keys:查看符合模板的所有key, 类似模糊查询,效率不高,不建议生产环境设备上使用
*
代表 0-多个任意字符?
代表一个任意字符
del:删除一个指定的key,可以传多个参数代表删多个key
exists:判断key是否存在
expire:给一个key设置有效期,有效期到期时该key会被自动删除,单位秒,可以节省内存空间,经常配合
ttl
使用因为 redis 是基于内存存储的,不及时清理的话内存可能会占满,业务中比如:短信验证码保留五分钟
ttl:查看一个key的剩余有效期
ttl key
- 返回结果:
-2
代表过期或者没有该key,-1
代表有效期永久
- 返回结果:
2.3 String 类型
2.3.1 基本介绍
- String 类型,也是字符串类型,是Redis中最简单的存储类型。其 value 是字符串,不过根据字符串的格式不同,又可以分为3类:
- String:普通字符串
- int:整数类型,可以做自增、自减操作
- float:浮点类型,可以做自增、自减操作
不管是哪种格式,底层都是字节数组形式存储,只不过是编码方式不同。字符串类型的最大空间不能超过521m
2.3.2 常见命令
- set:添加或者修改已存在的一个 String 类型的键值对
- get:根据 key 获取 String 类型的 value
- mset:批量添加多个 String 类型的键值对
- mget:根据多个 key 获取多个 String 类型的 value
incr:让一个整型的 key 自增1
incrby:让一个整型的 key 自增并指定步长,例如
incrby num 2
, 让num值自增2incrbyfloat:让一个浮点类型的数字自增并指定步长
setnx:添加一个 String 类型的键值对,前提是 key 不存在,否则不执行(也就是只有新增效果)
跟
set xxx xxx nx
效果一样,如set name thr nx
setex:添加一个 String 类型的键值对,并指定有效期
补充:Key 的层级格式
Q:Redis没有类似MySQL中Table的概念,我们该如何区分不同类型的key 呢?
- 例如,需要存储用户、商品信息到redis,有一个用户id是1,有一个商品id恰好也是1
- 测试一下:
###2.4 Hash 类型
2.4.1 基本介绍
Hash 类型,也叫散列,其 value 是一个无序字典,类似于Java中的 HashMap 结构
String 结构是将对象序列化为JSON字符串后存储,当需要修改对象某个字段时很不方便:
Hash结构可以将对象中的每个字段独立存储,可以针对单个字段做CRUD:
2.4.2 常见命令
- 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
HVALS:获取一个hash类型key中所有的value 就像Java中values
- HINCRBY:让一个 hash 类型 key 的字段值自增并指定步长
- HSETNX:添加一个 hash 类型的 key 的 field 值,前提是这个 field 不存在,否则不执行
2.5 List 类型
2.5.1 基本介绍
- List 类型与 Java 中的 LinkedList类似,可以看做是一个双向链表结构,既可以支持正向检索和也可以支持反向检索
- 特征也与 LinkedList 类似:
- 有序
- 元素可以重复
- 插入和删除快
- 查询速度一般
- 常用来存储一个有序数据,例如:朋友圈点赞列表,评论列表等
2.5.2 常见命令
- LPUSH key element … :向列表左侧插入一个或多个元素
- LPOP key:移除并返回列表左侧的第一个元素,没有则返回nil
- RPUSH key element … :向列表右侧插入一个或多个元素
- RPOP key:移除并返回列表右侧的第一个元素
- LRANGE key star end:返回一段角标范围内的所有元素
- BLPOP和BRPOP:与LPOP和RPOP类似,只不过在没有元素时等待指定时间,而不是直接返回nil
2.5.3 思考题
- 如何利用List结构模拟一个栈?
- 入口和出口在同一边
- 如何利用List结构模拟一个队列?
- 入口和出口在不同边
- 如何利用List结构模拟一个阻塞队列?
- 入口和出口在不同边
- 出队时采用BLPOP或BRPOP
2.6 Set 类型
2.6.1 基本介绍
- Set 结构与 Java 中的 HashSet 类似,可以看做是一个value为 null 的HashMap
- 因为也是一个hash表,因此具备与HashSet类似的特征:
- 无序
- 元素不可重复
- 查找快
- 支持交集、并集、差集等功能
- 应用场景:共同好友
2.6.2 常见命令
- 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并集和
2.6.3 Set 命令的练习
- 用 set 集合存储下列数据:
1 | sadd zs ls wangwu zhaoliu |
- 用 set 命令实现下列功能
1 | scard zs # 计算张三的好友有几人 |
2.7 SortedSet 类型
2.7.1 基本介绍
- SortedSet 是一个可排序的 set 集合,与 Java 中的TreeSet有些类似,但底层数据结构却差别很大
- SortedSet中的每一个元素都带有一个score属性,可以基于score属性对元素排序,底层的实现是一个跳表(SkipList)加 hash 表
- 可排序
- 元素不重复
- 查询速度快(因为有哈希表)
- 因为 SortedSet 的可排序特性,经常被用来实现排行榜这样的功能
2.7.2 常见命令
- 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
2.7.3 SortedSet 命令的练习
- 将下列学生得分存入 redis 的 SortedSet 中:
1 | zadd stus 85 jack 89 Lucy 82 Rose 95 Tom 78 Jerry 92 Amy 76 Miles |
- 并实现下列功能:
1 | zrem stus Tom # 删除 Tom 同学 |
3. Redis 的 Java 客户端
在Redis官网中提供了各种语言的客户端,地址:
Get started using Redis clients | Redis
其推荐 Java 使用的有:Java guide | Redis
3.1 客户端对比
3.2 Jedis
Jedis 的官网地址:https://github.com/redis/jedis
3.2.1 快速入门
- 先新建一个 maven 工程
- 引入依赖
1 | <!-- jedis --> |
- 建立连接
在 test 包下新建一个类,JedisTest
1 | private Jedis jedis; |
- 测试
1 | // 测试 string |
- 释放资源
1 |
|
- 成功结果
总结:Jedis 使用的步骤:
- 引入依赖
- 创建 Jedis 对象,引入连接(需要 ip 地址、端口号、密码等)
- 使用 Jedis ,方法名和 Redis 命令一致
- 释放资源
3.2.2 Jedis 线程池
Jedis 本身是线程不安全的,并且频繁的创建和销毁连接会有性能损耗,因此我们推荐大家使用 Jedis 连接池代替 Jedis 的直接连接:
- 创建工具类
JedisConnectionFactory
1 | import redis.clients.jedis.Jedis; |
代码说明:
- 1)JedisConnectionFacotry:工厂设计模式是实际开发中非常常用的一种设计模式,我们可以使用工厂,去降低代的耦合,比如Spring中的Bean的创建,就用到了工厂设计模式
- 2)静态代码块:随着类的加载而加载,确保只能执行一次,我们在加载当前工厂类的时候,就可以执行static的操作完成对连接池的初始化
- 3)最后提供返回连接池中连接的方法.
- 修改之前的测试类,重新测试,成功
1 | jedis = JedisConnectionFactory.getJedis(); |
代码说明:
- 我们在完成了使用工厂设计模式来完成代码的编写后,我们可以通过工厂来获得连接,不用去new对象,减低耦合,且使用的还是连接池对象
- 当我们使用连接池后,当我们关闭连接其实并不是关闭,而是将 Jedis 归还给连接池
3.3 SpringDataRedis(重点)
3.3.1 基本介绍
SpringData 是 Spring 中数据操作的模块,包含对各种数据库的集成,其中对 Redis 的集成模块就叫做 SpringDataRedis,官网地址:https://spring.io/projects/spring-data-redis
- 提供了对不同Redis客户端的整合(Lettuce和Jedis)
- 提供了RedisTemplate统一API来操作Redis
- 支持Redis的发布订阅模型
- 支持Redis哨兵和Redis集群
- 支持基于Lettuce的响应式编程(Lettuce之前是在es那里有)
- 支持基于JDK.JSON.字符串.Spring对象的数据序列化及反序列化
- 支持基于Redis的JDKCollection实现
3.3.2 快速入门
SpringBoot 已经提供了对 SpringDataRedis 的支持,使用非常简单,所以我们先创建一个 SpringBoot 项目:
- 引入依赖
1 | <!--Redis依赖--> |
- 配置文件
1 | spring: |
这个地方的 pool 咱们选择的是 lettuce 而不是 jedis,因为 spring 默认引入的是 lettuce,如果要用 jedis 还需要自己引下依赖
- 注入 RedisTemplate,编写测试类
1 |
|
- 成功结果
总结:SpringDataRedis 的使用步骤
- 引入依赖
- 在 yaml 文件里配置 Redis 信息
- 注入 RedisTemplate
3.3.3 RedisTemplate 的 RedisSerializer
Q:根据前面的快速入门,我们去图形化界面做验证检查的时候会发现一些问题:多了一个这样的 key:
\xac\xed\x00\x05t\x00\x04name
原因在序列化?前面使用的
redisTemplate.opsForValue().set("name", "thr");
这里面 redisTemplate 的 set 方法,操作的参数并不是字符串,而是Object
类型,接收任何类型的对象,并将其转成 redis 可以 处理的字节,所以我们存进去的name
和thr
就被当成 java 对象了,而默认的序列化器是 jdk 的序列化器,得到的结果就是如下这样:缺点
- 可读性差
- 内存占用较大
解决办法:不用默认的方法
- 快捷键
ctrl + H
打开类层级关系模板
- 所以我们可以自定义 RedisTemplate 的序列化方式来解决这个问题
- 快捷键
详细解决过程:
- 编写配置类:
RedisConfig
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
public class RedisConfig {
// springboot 会帮我们自动创建工厂, 我们只要注入下就行
// 此处 形参名爆红 需要降低 springboot 的版本
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;
}
}- 引入 Jackson 依赖
因为这里我们没有用到 springmvc 所以需要引入下这个依赖,平常开发中 springmvc 会自带这个依赖
1
2
3
4
5<!-- Jackson依赖 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>- 修改测试类:加个泛型即可
- 成功
- 编写配置类:
尝试写入对象
- 先创建一个实体类:
User
1
2
3
4
5
6
7
public class User {
private String name;
private Integer age;
}- 然后编写测试方法:存储 user 对象
1
2
3
4
5
6
7
8
9
void testSavaUser() {
// 写入数据
redisTemplate.opsForValue().set("user:100", new User("hhh", 21));
// 获取数据
User o = (User) redisTemplate.opsForValue().get("user:100");
System.out.println("o = " + o);
}- 查看图形化界面验证结果:
- 先创建一个实体类:
3.3.4 StringRedisTemplate
- 问题:尽管 Json 的自动序列化方式可以满足我们的需求,但仍然存在一些问题,为了在反序列化时知道对象的类型,JSON 序列化器会将类的 class 类型写入 json 结果中,存入 Redis,会带来额外的内存开销,如上 3.3.3 的结果
- 解决:因此为了节省内存空间,我们并不会使用 JSON 序列化器来处理value,而是统一使用 String 序列化器,要求只能存储 String 类型的 key 和 value。当需要存储 Java 对象时,手动完成对象的序列化和反序列化
String 默认提供了一个 StringRedisTemplate 类,他的key和value的序列化默认就是String方式,省去了定义redisTemplate 过程
具体实现:
- 新建测试类:
SpringdataredisStringApplicationTests
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
class SpringdataredisStringApplicationTests {
// 注入 StringRedisTemplate
private StringRedisTemplate stringRedisTemplate;
void testString() {
// 写一条 string 数据
stringRedisTemplate.opsForValue().set("name", "thr");
// 获取 string 数据
Object name = stringRedisTemplate.opsForValue().get("name");
System.out.println("name = " + name); // name = thr
}
// json 的序列化工具, 也可以用 fastjson
private static final ObjectMapper mapper = new ObjectMapper();
void testSavaUser() throws JsonProcessingException {
// 创建对象
User user = new User("thr", 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);
}
}- 成功结果:
- 新建测试类:
3.3.5 总结:RedisTemplate 的两种序列化实践方案
- 自定义 RedisTemplate
- 修改RedisTemplate的序列化器为GenericJackson2JsonRedisSerializer
- 使用 StringRedisTemplate
- 写入Redis时,手动把对象序列化为Json
- 读取Redis时,手动把读取到的Json反序列化为对象
3.3.6 补充:RedisTemplate 操作 Hash 类型
- 编写测试方法:
1 |
|
- 成功结果: