Redis 学习笔记

基础篇

认识 Redis

1. 认识 NoSQL

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

(1) 结构化与非结构化

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

img点击并拖拽以移动

② 而NoSql则对数据库格式没有严格约束,往往形式松散,自由。可以是键值型、文档型、图格式。

(2) 关联和非关联

① 传统数据库的表与表之间往往存在关联,例如外键:

img点击并拖拽以移动

② 而非关系型数据库不存在关联关系,要维护关系要么靠代码中的业务逻辑,要么靠数据之间的耦合:

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
}
}
]
}

点击并拖拽以移动

(3) 查询方式

① 传统关系型数据库会基于Sql语句做查询,语法有统一标准;

② 不同的非关系数据库查询语法差异极大,五花八门各种各样。

img点击并拖拽以移动

(4) 事务

① 传统关系型数据库能满足事务ACID的原则。

img点击并拖拽以移动

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

(5) 总结

SQL NoSQL
数据结构 结构化(Structured) 非结构化
数据关联 关联的(Relational) 无关联的
查询方式 SQL查询 非SQL
事务特性 ACID BASE
存储方式 磁盘 内存
扩展性 垂直 水平
使用场景 1)数据结构固定 2)对一致性、安全性要求不高 1)数据结构不固定 2)相关业务对数据安全性、一致性要求较高 3)对性能要求

2. Redis 桌面客户端

(1) 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的访问密码

Redis 常见命令

1. Redis 数据结构

Redis存储的是key-value结构的数据,其中key是字符串类型,value有5中常用的数据类型

  • 字符串:String
  • 哈希:Hash
  • 列表:List
  • 集合:Set
  • 有序集合:Sorted Set

2. Redis命令-String命令

(1) 介绍

String 类型,也就是字符串类型,是Redis中最简单的存储类型。其value是字符串,根据字符串的格式不同,又可以分为3类:

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

img点击并拖拽以移动

说明:

Redis 没有原生的 “整数类型” “浮点类型” 存储结构,String 类型的所有 value,底层都以 简单动态字符串(SDS) 这种字节数组形式存储。

  • 表格中 num10:底层存的是字符 '1''0' 的字节,不是编程语言里占 4 字节的整数 10;
  • 表格中 score92.5:底层存的是字符 '9' '2' '.' '5' 的字节,不是浮点型的二进制存储;
  • 只有格式上的区别,存储本质完全一致。

(2) String 的常见命令有:

  • SET:添加或者修改已经存在的一个String类型的键值对
  • GET:根据key获取String类型的value
  • MSET:批量添加多个String类型的键值对
  • MGET:根据多个key获取多个String类型的value
  • INCR:让一个整型的key自增1
  • INCRBY:让一个整型的key自增并指定步长,例如:incrby num 2 让num值自增2
  • SETNX:添加一个String类型的键值对,前提是这个key不存在,否则不执行
  • SETEX:添加一个String类型的键值对,并且指定有效期

① SET 和 GET:如果key不存在则是新增,如果存在则是修改

1
2
3
4
5
6
7
8
9
10
11
127.0.0.1:6379> set name Rose  //原来不存在
OK

127.0.0.1:6379> get name
"Rose"

127.0.0.1:6379> set name Jack //原来存在,就是修改
OK

127.0.0.1:6379> get name
"Jack"

点击并拖拽以移动

② INCR 和 INCRBY 和 DECY

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
127.0.0.1:6379> get age 
"10"

127.0.0.1:6379> incr age //增加1
(integer) 11

127.0.0.1:6379> get age //获得age
"11"

127.0.0.1:6379> incrby age 2 //一次增加2
(integer) 13 //返回目前的age的值

127.0.0.1:6379> incrby age 2
(integer) 15

127.0.0.1:6379> incrby age -1 //也可以增加负数,相当于减
(integer) 14

127.0.0.1:6379> incrby age -2 //一次减少2个
(integer) 12

127.0.0.1:6379> DECR age //相当于 incr 负数,减少正常用法
(integer) 11

127.0.0.1:6379> get age
"11"

点击并拖拽以移动

3. Redis命令-Key的层级结构

(1) 存在的问题

需要存储用户、商品信息到redis,有一个用户id是1,有一个商品id恰好也是1,此时如果使用id作为key,那就会冲突了,该怎么办?

(2) 解决方案

① Redis的key允许有多个单词形成层级结构,多个单词之间用’:’隔开,格式如下:

img点击并拖拽以移动

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

  • user相关的key:heima:user:1
  • product相关的key:heima:product:1

4. Redis命令-Hash命令

(1) 优势

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

img点击并拖拽以移动

(2) 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不存在,否则不执行

① HSET 和 HGET

1
2
3
4
5
6
7
8
9
10
127.0.0.1:6379> HSET heima:user:3 name Lucy//大key是 heima:user:3 小key是name,小value是Lucy
(integer) 1
127.0.0.1:6379> HSET heima:user:3 age 21// 如果操作不存在的数据,则是新增
(integer) 1
127.0.0.1:6379> HSET heima:user:3 age 17 //如果操作存在的数据,则是修改
(integer) 0
127.0.0.1:6379> HGET heima:user:3 name
"Lucy"
127.0.0.1:6379> HGET heima:user:3 age
"17"

点击并拖拽以移动

② HKEYS和HVALS

1
2
3
4
5
6
7
8
127.0.0.1:6379> HKEYS heima:user:4
1) "name"
2) "age"
3) "sex"
127.0.0.1:6379> HVALS heima:user:4
1) "LiLei"
2) "20"
3) "man"

点击并拖拽以移动

5. Redis命令-List命令

(1) 特征

  • 有序
  • 元素可以重复
  • 插入和删除快
  • 查询速度一般

(2) List的常见命令

  • LPUSH key element … :向列表左侧插入一个或多个元素
  • LPOP key:移除并返回列表左侧的第一个元素,没有则返回nil
  • RPUSH key element … :向列表右侧插入一个或多个元素
  • RPOP key:移除并返回列表右侧的第一个元素
  • LRANGE key star end:返回一段角标范围内的所有元素
  • BLPOP和BRPOP:与LPOP和RPOP类似,只不过在没有元素时等待指定时间,而不是直接返回nil

img点击并拖拽以移动

6. Redis命令-Set命令

(1) 特征

  • 无序
  • 元素不可重复
  • 查找快
  • 支持交集、并集、差集等功能

(2) 常见的命令

命令 描述
SADD key member1 [member2] 向集合添加一个或多个成员
SMEMBERS key 返回集合中的所有成员
SCARD key 获取集合的成员数
SINTER key1 [key2] 返回给定所有集合的交集
SUNION key1 [key2] 返回所有给定集合的并集
SDIFF key1 [key2] 返回给定所有集合的差集
SREM key member1 [member2] 移除集合中一个或多个成员

img点击并拖拽以移动

7. Redis命令-SortedSet类型

(1) 特征

Sorted Set有序集合是String类型元素的集合,且不允许重复的成员。每个元素都会关联一个double类型的分数(score) 。Redis正是通过分数来为集合中的成员进行从小到大排序。有序集合的成员是唯一的,但分数却可以重复。

(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:求差集、交集、并集三、

Redis的Java客户端 - Jedis(了解即可)

1. 操作步骤

① 引入依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
<!--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
private Jedis jedis;

@BeforeEach
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
20
@Test
void testString() {
// 存入数据
String result = jedis.set("name", "虎哥");
System.out.println("result = " + result);
// 获取数据
String name = jedis.get("name");
System.out.println("name = " + name);
}

@Test
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
@AfterEach
void tearDown() {
if (jedis != null) {
jedis.close();
}
}

点击并拖拽以移动

2. Jedis连接池

(1) 创建Jedis的连接池

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
static { // 静态代码块:类加载时自动执行,只执行一次
// 1. 配置连接池的“规则”
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(8); // 最多同时有 8 个连接(满了就等)
poolConfig.setMaxIdle(8); // 最多保留 8 个“空闲”的连接(多余的释放)
poolConfig.setMinIdle(0); // 最少保留 0 个空闲连接(没有就新建)
poolConfig.setMaxWaitMillis(1000); // 没连接时最多等 1000 毫秒(1秒),超时就报错

// 2. 根据规则,创建真正的连接池
jedisPool = new JedisPool(
poolConfig, // 刚才的规则
"192.168.150.101", // Redis 服务器的 IP
6379, // Redis 端口
1000, // 连接超时时间(毫秒)
"123321" // Redis 密码
);
}

public static Jedis getJedis() { // 对外提供的“借连接”的方法
return jedisPool.getResource(); // 从连接池里拿一个可用的连接
}

点击并拖拽以移动

Redis的Java客户端-SpringDataRedis

1. 介绍

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

img点击并拖拽以移动

2. 快速入门

① 需要导入它的maven坐标

1
2
3
4
5
<!--Spring Boot-redis的依赖包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

点击并拖拽以移动

② 配置文件

1
2
3
4
5
6
7
8
9
10
11
spring:
redis:
host: 192.168.150.101
port: 6379
password: 123321
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
@SpringBootTest
class RedisDemoApplicationTests {

@Autowired
private RedisTemplate<String, Object> redisTemplate;

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

点击并拖拽以移动

3. 数据序列化器

(1) 自定义RedisTemplate的序列化方式,代码如下:

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

点击并拖拽以移动

说明:

之所以要写 HashKey 和 HashValue,是因为Redis 里的 Hash 类型,就像一个「嵌套的 Map」:

  • 顶层结构:RedisKey(顶层Key) → Hash结构(顶层Value)
  • Hash 内部结构:HashKey → HashValue

4. StringRedisTemplate

(1) 存在的问题

默认情况下,RedisTemplate 使用的序列化器(比如 JdkSerializationRedisSerializer)会把对象的 完整类信息(class 类型) 也写入到 JSON 结果中,再存入 Redis。

比如一个 User 对象,序列化后可能变成这样:

1
2
3
4
5
{
"@class": "com.example.User",
"name": "Jack",
"age": 21
}

点击并拖拽以移动

这里的 "@class": "com.example.User" 就是额外的类信息,它会占用不必要的内存空间。

(2) 解决方案

  • 先使用ObjectMapper把对象手动转换成纯 JSON 字符串(不带 class 信息)。
  • 再用 StringRedisTemplate把这个 JSON 字符串存入 Redis。
  • 读取时,先从 Redis 取出 JSON 字符串,再手动用 Jackson 把它反序列化为对象。

(3) 代码实现

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
@SpringBootTest
class RedisStringTests {

@Autowired
private StringRedisTemplate stringRedisTemplate;

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

private static final ObjectMapper mapper = new ObjectMapper();

@Test
void testSaveUser() throws JsonProcessingException {
// 创建对象
User user = new User("虎哥", 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);
}

}

点击并拖拽以移动

5. Hash结构操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@SpringBootTest
class RedisStringTests {

@Autowired
private StringRedisTemplate stringRedisTemplate;


@Test
void testHash() {
stringRedisTemplate.opsForHash().put("user:400", "name", "虎哥");
stringRedisTemplate.opsForHash().put("user:400", "age", "21");

Map<Object, Object> entries = stringRedisTemplate.opsForHash().entries("user:400");
System.out.println("entries = " + entries);
}
}

点击并拖拽以移动