Redis 基础篇

gs_huang

初识 Redis

认识 Nosql

SQL:

  1. 结构化的
  2. 关联的
  3. SQL 查询
  4. ACID

NoSQL:

  1. 非结构化的
  2. 无关联的
  3. 非 SQL
  4. BASE

认识 Redis

基于内存的键值型 NoSQL 数据库。

特征:

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

安装 Redis

Redis 常见命令

Redis 数据结构介绍

Redis 是一个 key-value 的数据库,key 一般是 String 类型,不过 value 的类型多种多样:

基本类型

  1. String
  2. Hash
  3. List
  4. Set
  5. SortedSet

特殊类型

  1. GEO
  2. BitMap
  3. HyperLog

Redis 通用命令

每一个命令都可以通过 help 命令 来查询其使用方法

通用命令是不分数据类型的,都可以使用的指令,常见的有:

1、KEYS:查看符合模板的所有 key,模板的格式类似于 mysql 的模糊查询。不建议在生产环境设备上使用。

2、DEL:删除一个指定的 key。

3、EXISTS:判断 key 是否存在。

4、EXPIRE:给一个 key 设置有效期,有效期到期后会自动删除。

5、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 自增并指定步长。例如:incrby num 2 让 num 值自增 2
  • INCRBYFLOAT:让一个浮点类型的数字自增并指定步长。
  • SETNX:添加一个 String 类型的键值对,前提是这个 key 不存在,否则不执行
  • SETEX:添加一个 String 类型的键值对,并指定有效期

key 的层级结构

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

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

此格式并非固定,也可以根据自己的需求来删除或添加词条。

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

user 相关的 key:itsheng:user:1

product 相关的 key:itsheng:product:1

如果 value 是一个 java 对象,例如一个 user 对象,则可以将对象序列化为 JSON 字符串后存储。

Hash 类型

Hash 类型,也叫散列,其 value 是一个无序字典,类似于 java 中的 HashMap 结构。

String 结构是将对象序列化为 JSON 字符串存储,当需要修改对象某个字段时很不方便。

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
  • HVALS:获取一个 hash 类型 key 的所有 value
  • HINCRBY:让一个 hash 类型 key 的字段自增并指定步长
  • HSETNX:添加一个 hash 类型 key 的 field 值,前提是这个 field 不存在,否则不执行

List 类型

Redis 中的 List 类型与 Java 中的 LinkedList 类似,可以看作是一个双向链表结构。既可以支持正向检索也可以支持反向检索。

特征也与 LinkedList 类似:

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

List 常见命令

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

Set 类型

Redis 中的 Set 结构与 Java 中的 HashSet 类似,可以看作是一个 value 为 null 的 HashMap。

因为它也是一个 hash 表,因此具备与 HashSet 类似的特征:

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

Set 类型常见命令

  • SADD key member … :向 set 中添加一个或多个元素
  • SREM key member … :移除 set 中的指定元素
  • SCARD key:返回 set 中元素的个数
  • SISMEMBER key member:判断一个元素是否存在于 set 中
  • SMEMBERS key:获取 set 中的所有元素
  • SINTER key1 key2 … :求 key1 与 key2 的交集
  • SDIFF key1 key2 … :求 key1 与 key2 的差集(key1 - key2)
  • SUNION key1 key2 … :求 key1 与 key2 的并集(key1 + key2)

SortedSet 类型

Redis 的 SortedSet 是一个可排序的 set 集合,与 Java 中的 TreeSet 有些类似,但底层数据结构却差别很大。

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

SortedSet 具备以下特性:

  • 可排序
  • 元素不重复
  • 查询速度快

因为 SortedSet 的可排序性,经常被用来实现排行榜这样的功能。

SortedSet 常见命令

  • ZADD key score member:添加一个或多个元素到 sorted set 如果已经存在,则更新其 score 值
  • ZREM key member:删除 sorted set 中的一个指定元素
  • ZSCORE key member:获取 sorted set 中指定元素的 score 值
  • ZRANK key member:获取指定元素的排名
  • ZCARD key:获取 sorted set 中的元素个数
  • ZCOUNT key min max:统计 score 值在给定范围内的所有元素的个数
  • ZINCRBY key increment member:指定元素自增,步长为 increment
  • ZRANGE key min max:按照 score 排序后,获取指定排名范围内的元素
  • ZRANGEBYSCORE key min max:按照 score 排序后,获取指定 score 范围内的元素
  • ZDIFF,ZINTER,ZUNION:求差集、交集、并集

注:所有的排序默认升序,如果要降序,只需要在 Z 后面加上 REV 即可。

Redis 的 Java 客户端

Jedis

Jedis 官网地址 Jedis 官网

快速入门:

1、引入依赖

1
2
3
4
5
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>4.3.0</version>
</dependency>

2、建立连接

1
2
3
4
5
6
7
8
private Jedis jedis;

@BeforeEach
void setUp() {
jedis = new Jedis("*.*.*.*", 6379);
jedis.auth("***");
jedis.select(0);
}

3、测试 String

1
2
3
4
5
6
7
8
9
10
11
@Test
void testString(){

// 存入数据
String result = jedis.set("hobby","coding");
System.out.println("result: " + result);

// 获取数据
String hobby = jedis.get("hobby");
System.out.println("hobby: " +hobby);
}

4、释放资源

1
2
3
4
5
6
@AfterEach
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
27
public class JedisConnectionFactory {
private static JedisPool jedisPool;

static {
// 配置连接池
JedisPoolConfig poolConfig = new JedisPoolConfig();

// 设置最大连接数量
poolConfig.setMaxTotal(8);

// 设置最大等待连接数量
poolConfig.setMaxIdle(8);

// 设置最小等待连接数量
poolConfig.setMinIdle(0);

// 设置最大等待时间(连接池中没有剩余连接时的等待)
poolConfig.setMaxWaitMillis(1000);

// 创建连接池对象
jedisPool = new JedisPool(poolConfig,"ip地址",端口号,超时时间,"密码");
}

public static Jedis getJedis(){
return jedisPool.getResource();
}
}

SpringDataRedis

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

官网地址:SpringDataRedis 官网

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

SpringDataRedis 快速入门

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

API 返回值类型 说明
redisTemplate.opsForValue ValueOperations 操作 String 类型数据
redisTemplate.opsForHash HashOperations 操作 Hash 类型数据
redisTemplate.opsForList ListOperations 操作 List 类型数据
redisTemplate.opsForSet SetOperations 操作 Set 类型数据
redisTemplate.opsForZSet ZSetOperations 操作 SortedSet 类型数据
redisTemplate 通用的命令

使用步骤

1、引入依赖

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>

<!-- 连接池依赖 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>

2、配置文件

1
2
3
4
5
6
7
8
9
10
11
spring:
redis:
host: ip地址
port: 端口号
password: 密码
lettuce:
pool:
max-active: 8 # 最大连接
max-idle: 8 # 最大空闲连接
min-idle: 0 # 最小空闲连接
max-wait: 1000ms # 连接等待时间

3、注入 RedisTemplate

1
2
@Autowired
private RedisTemplate redisTemplate;

4、编写测试

1
2
3
4
5
6
7
8
@Test
void testString() {
// 写入一条数据
redisTemplate.opsForValue().set("hobby","敲代码");
// 获取数据
Object hobby = redisTemplate.opsForValue().get("hobby");
System.out.println("hobby: " +hobby);
}

SpringDataRedis 的序列化方式

在使用 RedisTemplate 往 redis 里面存数据后,再从 redis 控制台读取数据时,会发现和存入的数据不一样。

这是因为,RedisTemplate 默认把存的 key 和 value 当作对象来处理,会先把它们序列化然后存入数据库。

这会极大降低其数据库中数据的可读性。

方案一:自定义 RedisTemplate

我们可以自己定义 RedisTemplate 的序列化方式来解决这个问题,代码如下:

1、先引入 jackson-databind 依赖(平时开发不需要引,因为 SpringMVC 自带)

1
2
3
4
5
<!-- Jackson 依赖 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>

2、编写序列化配置文件

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
@Configuration
public class RedisConfig {

@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
// 创建 RedisTemplate 对象
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();

// 设置连接工厂
redisTemplate.setConnectionFactory(redisConnectionFactory);

// 创建 JSON 序列化工具
GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer();

// 设置 key 的序列化
redisTemplate.setKeySerializer(RedisSerializer.string());
redisTemplate.setHashKeySerializer(RedisSerializer.string());

// 设置 value 的序列化
redisTemplate.setValueSerializer(jsonRedisSerializer);
redisTemplate.setHashValueSerializer(jsonRedisSerializer);

// 返回
return redisTemplate;
}
}

方案二:使用 StringRedisTemplate

尽管 JSON 的序列化方式可以满足我们的需求,但依然存在一些问题,如图:

redis-string-object1

为了在反序列化时知道对象的类型,JSON 序列化器会在 JSON 中存入对象的类型。

这样会带来额外的内存开销。

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

String 默认提供了一个 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
24
@Autowired
private StringRedisTemplate stringRedisTemplate;

private static ObjectMapper mapper = new ObjectMapper();

@Test
void testObject() throws JsonProcessingException {
// 创建对象
User user = new User(1001L, "guosheng", 19);

// 手动序列化
String json = mapper.writeValueAsString(user);

// 存入数据
stringRedisTemplate.opsForValue().set("user:1001", json);

// 获取数据
String jsonUser = stringRedisTemplate.opsForValue().get("user:1001");

// 手动反序列化
User user1 = mapper.readValue(jsonUser, User.class);

System.out.println("user: " + user1);
}

RedisTemplate 操作 Hash 值

1
2
3
4
5
6
7
8
@Test
void testHash(){
stringRedisTemplate.opsForHash().put("user:1002", "name", "itsheng");
stringRedisTemplate.opsForHash().put("user:1002", "age", "19");

Map<Object,Object> entries = stringRedisTemplate.opsForHash().entries("user:1002");
System.out.println("entries: " + entries);
}
  • 标题: Redis 基础篇
  • 作者: gs_huang
  • 创建于: 2023-05-06 17:45:52
  • 更新于: 2023-05-09 14:39:03
  • 链接: https://blog.itsheng.cn/2023/05/06/Redis-基础篇/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
 评论