(一)初识 Redis

简介

  官方文档:Redis is an open source (BSD licensed), in-memory data structure store, used as a database, cache and message broker. It supports data structures such as strings, hashes, lists, sets, sorted sets with range queries, bitmaps, hyperloglogs, geospatial indexes with radius queries and streams. Redis has built-in replication, Lua scripting, LRU eviction, transactions and different levels of on-disk persistence, and provides high availability via Redis Sentinel and automatic partitioning with Redis Cluster.

  翻译:Redis 是 一个开源(BSD 许可),内存数据结构存储,用作数据库,缓存和消息代理。它支持数据结构,如字符串,散列,列表,集合,带有范围查询的排序集,位图,超级日志,具有半径查询和流的地理空间索引。

  Redis 具有内置复制,Lua 脚本,LRU 回收,事务和不同级别的磁盘持久性,同时通过 Redis Sentinel 提供高可用性,通过 Redis Cluster 提供自动分区。

特点

  • Redis 基于内存,并支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用
  • Redis 不仅支持简单的 key-value 类型的数据,同时还提供 list,set,zset,hash 等数据结构的存储
  • Redis 支持数据的备份,即 master-slave 模式的数据备份

优势

  • 性能极高 – Redis 能读的速度是 110000 次 /s ,写的速度是 81000 次 /s
  • 丰富的数据类型 – Redis 支持二进制案例的 Strings,Lists,Hashes,Sets 及 Ordered Sets 数据类型操作
  • 原子 – Redis 的所有操作都是原子性的,同时 Redis 还支持对几个操作全并后的原子性执行操作
  • 丰富的特性 – Redis 还支持 publish/subscribe,通知,key 过期等等特性

应用场景

  Redis 可以在很多场景下使用,比如:

  • 计数器
  • 消息队列
  • 排行榜
  • 秒杀系统
  • 验证码
  • 分布式锁
  • 热数据缓存

下载与安装

  Redis 的版本分为稳定版和非稳定版,其根据次版本号(即第一个小数点后的数字)的奇偶进行区分:

  • 若次版本号为偶数,则为稳定版本,如 2.6、2.8、3.0
  • 若次版本号为奇数,则非稳定版本,如 2.7、2.9、3.1

下载

  我们从 Redis 官网下载地址下载稳定的 Redis 6.2.6 版本。

安装

环境准备

  Redis 基于 C 语言编写的,其依赖于 gcc :

1
yum install -y gcc tcl

解压

1
tar -xzf redis-6.2.6.tar.gz

编译安装

1
2
cd redis-6.2.6
make && make install

  编译安装完成后,将在/usr/local/bin目录下生成以下多个脚本(下面为部分):

  • redis-cli:Redis 客户端启动脚本
  • redis-server:Redis 服务端启动脚本
  • redis-sentinel:Redis 哨兵启动脚本

命令

  下面为一些常用命令:

启动服务器

  通过以下命令可以启动 Redis:

1
redis-server

  当然,以上命令使用的是默认的配置启动的 Redis,我们也可以指定配置文件进行启动:

1
redis-server /data/software/redis-6.2.6/redis.conf

客户端测试

  通过 Redis 客户端连接 Redis 服务器,查看 Redis 是否启动,这使用以下命令:

1
redis-cli

  执行以上命令将打开以下终端:

1
redis 127.0.0.1:6379>

   127.0.0.1 是本机 IP ,6379 是 Redis 服务端口,之后我们输入 PING 命令:

1
2
redis 127.0.0.1:6379> ping
PONG

  出现PONG则说明我们已经成功安装了 Redis.

开机自启

  若想实现 Redis 开机自启,需要进行一些配置。

配置

  首先,新建一个系统服务文件:

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 /data/software/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.conf,通过修改该文件内容可进行自定义配置,启动 Redis 时会使用这些配置。
  下面只介绍几个常用的配置:

  • bind 127.0.0.1 ::1:默认情况下该配置只能接受本机的访问请求(测试阶段使用#注释掉即可无限制接受任何 ip 地址的访问),生产环境请使用应用服务器的地址。若开启了protected-mode且没有设置密码,则 Redis 只接受本机访问。
  • protected-mode yes:保护模式,开启时需要设置 bind ip 或者密码。关闭时外部 ip 可以直接访问
  • port 6379:Redis 服务端口号
  • daemonize no:Redis 默认不是以守护进程(即不会常驻后台)的方式运行,可修改值为yes以启用守护进程
  • tcp-backlog 511:等待最大队列数,此处设置无效,默认为 128 ,需要修改另一处配置文件
  • timeout 0:一个空闲的客户端维持多少秒会关闭, 0 代表永不关闭
  • tcp-keepalive 300:对访问客户端的心跳检测,每隔 300 秒检测一次
  • pidfile:当 Redis 以守护进程方式运行时,Redis 默认会把 pid 写入/var/run/redis.pid文件,可以通过 pidfile 指定自定义文件位置

基本命令

  Redis 命令大小写均可,可是作为中国人,总觉得大写有点变扭,无法像看简体繁体一样直接理解其意,因此个人更习惯使用小写。
  下表显示了一些 Redis 的常用命令:

命令 说明
redis-server 以默认配置启动 Redis 服务器
redis-server [redis.conf] 指定配置文件启动 Redis 服务器
redis-cli 启动客户端连接本地 Redis 服务器
redis-cli -h <host> -p <port> -a <password> 启动客户端连接远程 Redis 服务器(指定服务器 IP 端口和密码)
ping 连接服务器后使用该命令检测 Redis 服务是否启动(成功返回PONG
select <index> 选择某个库
keys * 查询当前库的所有键,*可以使用其他通配符代替
exitsts <key> 判断某个键是否存在
type <key> 判断键的类型
del <key> 删除某个键
expire <key> <second> 为键值设置过期(删除)时间,单位秒
ttl <key> 查看键还有多少秒过期,-1 表示永不过期,-2 表示已过期
dbsize 查看当前数据库的 key 的数量
flushdb 清空当前库数据
flushall 通杀全部库数据

基本组成

  我们知道 Redis 中存放了数据,那么自然就会有一个疑问:这些数据在 Redis 服务器内部是怎么组成的呢?
  简而言之,Redis 服务器中有数据库,数据库中有键空间,键空间中存放了真正的数据。

服务器中的数据库

  Redis 服务器将所有数据库都保存在服务器状态 redis.h/redisServer 结构的 db 数组中:

1
2
3
4
5
6
7
8
struct redisServer {
// ...
// 一个数组,保存着服务器中的所有数据库,每个项都是一个 redis.h/redisDb 结构,每个 redisDb 结构代表一个数据库
redisDb *db;
// 服务器的数据库数量
int dbnum;
// ...
}

  在初始化服务器时,程序会根据服务器状态的 dbnum 属性来决定应该创建多少个数据库,此属性值由服务器配置的 database 决定,默认为 16,因此默认情况下 Redis 服务器会创建 16 个数据库,如下图所示:

数据库中的键空间

  Redis 是一个键值对(key-value pair)数据库服务器,服务器中的每个数据库都由一个 redis.h/redisDb 结构表示,其中,redisDb 结构的 dict 字典保存了数据库中的所有键值对,我们将这个字典称为键空间(keyspace):

1
2
3
4
5
6
typedef struct redisDb {
// ...
// 数据库键空间,保存着数据库中的所有键值对
dict *dict;
// ...
} redisDb;

  键空间和用户所见的数据库是直接对应的:

  • 键空间的键也就是数据库的键,每个键都是一个字符串对象
  • 键空间的值也就是数据库的值,每个值可以是字符串对象、列表对象、哈希表对象、集合对象和有序集合对象中的任意一种 Redis 对象

  举个例子,如果我们在空白的数据库中执行以下命令:

1
2
3
4
5
6
7
8
9
10
127.0.0.1:6379> set msg "hello world"
OK
127.0.0.1:6379> rpush alphabet "a" "b" "c"
(integer) 3
127.0.0.1:6379> hset book name "Redis in Action"
(integer) 1
127.0.0.1:6379> hset book author "Josiah L. Carlson"
(integer) 1
127.0.0.1:6379> hset book publisher "Manning"
(integer) 1

  那么在这些命令执行之后,数据库的键空间将会是下图展示的样子:

  因为数据库的键空间是一个字典,所以所有针对数据库的操作,比如添加一个键值对到数据库,或者从数据库中删除一个键值对,又或者在数据库中获取某个键值对等,实际上都是通过对键空间字典进行操作来实现的。

键空间的对象类型

  既然 Redis 中的键和值都是由对象定义的,那么,Redis 中存在哪些对象呢?
  Redis 支持的对象(亦可称为数据类型),总共可以分为五类:

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

String 类型

  String 是 Redis 最基本的类型。

特点

  String 类型的其特点如下:

  • 一个 key 对应一个 value,相当于 Java 的 Map<String,String>
  • 该类型是二进制安全的,所以可以包含任何数据(如 jpg 图片或者序列化的对象)
  • 一个 Redis 中字符串 value 最大容量为 512M

命令

  String 类型的相关命令:

  • get <key>:获取指定键的值
  • set <key> <value>:设置键与值
  • append <key> <value>:追加指定值到原值末尾
  • strlen <key>:获取键的长度
  • setnx <key> <value>:设置完键值后不可被覆盖
  • incr <key>:将 key 中存储的数字值加 1,只能对数字值操作
  • decr <key>:将 key 中存储的数字值减 1,只能对数字值操作
  • incrby/decrby <key> <步长>:将 key 中存储的数字加/减指定步长
  • mset <key1> <value1> <key2> <value2>:同时设置多个键值对
  • mget <key1> <value1> <key2> <value2>:同时获取多个键值对
  • msetnx <key1> <value1> <key2> <value2>:同时设置多个键值对,不可被覆盖
  • get range <key> <起始位置> <结束位置>:获得值的范围,类似 Java 的substring
  • setrange <key> <起始位置> value:从起始位置以value值代替
  • setex <key> <过期时间> <value>:设置键值的同时,设置过期时间,单位秒
  • psetex <key> <过期时间> <value>:这个命令和setex命令相似,但它以毫秒为单位设置 key 的生存时间,而不是像 setex 命令那样,以秒为单位
  • getset <key> <value>:以新换旧,设置新值同时获得新值

业务场景

  例如小破站某热点视频当前观看用户数,点赞、投币、收藏数,可以选择 String 类型。
  在 Redis 中为视频设定视频信息,以视频主键和属性值作为 key,后台设定定时刷新策略即可:

1
2
3
4
5
6
7
8
9
video_id_BV1CJ411m7Gc_audiences
88888
video_id_BV1CJ411m7Gc_likes
6164
eg:
video_id_BV1CJ411m7Gc_coins
12222
video_id_BV1CJ411m7Gc_collections
666

List 类型

  List 列表的特点如下:

  • 单键多值,相当于 Java 中的Map<String,List<String>
  • 底层是双向循环链表
  • 作为简单的字符串集合,按照插入顺序排序
  • List 列表可以作为栈或队列使用,因此可添加一个元素到列表的头部(左边)或者尾部(右边)
  • 对两端的操作性能很高,通过索引下标的操作中间的节点性能会变差;
  • 最多可以包含 $2^{32}$ - 1个元素 (即 4294967295 )

  下面为其相关命令:

  • lpush/rpush <key> <value1> <value2>:从左边/右边插入一个或多个值到列表头部
  • lpop/rpop <key>:从左边/右边吐出一个值,值在键在,值空键亡
  • rpoplpush <key1> <key2>:从<key1>列表右边吐出一个值,插到<key2>列表右边
  • lrange <key> <start> <end>:按照索引下标获得元素(从左到右)
  • lindex <key> <index>:按照索引下标获得元素(从左到右)
  • llen <key>:获得列表长度
  • linsert <key> before/after <pivot> <value>:在指定列表的指定值前面或后面插入新值
  • lrem <key> <n> <value>:删除n个value(可以有多个相同的value,n为正数代表从左边删除,负数则从右边,0 代表删除所有 )

Set 类型

  Set 集合特点如下:

  • String 类型的无序集合,相当于 Java 中的Map<String,Set<String>>
  • 集合成员唯一,这意味着集合中不能出现重复的数据
  • 该集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)
  • 每个集合可存储 $2^{32}$ - 1 个元素(4294967295)

  下面为其相关命令:

  • sadd <key> <member1> <member2>:向集合添加一个或多个成员,重复成员将被省略
  • smembers <key>:查看集合中的所有成员
  • sismember <key> <member>:判断 member 元素是否为集合中的内容,有返回 1,无返回 0
  • scard <key>:返回该集合的个数
  • srem <key> <member1> <member2>:删除集合中的成员,可同时删除多个
  • spop <key>:随机吐出集合中的一个成员并显示
  • srandmember <key> <n>:随机从该集合中取出 n 个值,不会从集合中删除
  • sinter <key1> <key2>:返回两个集合的交集
  • sunion <keyl> <key2>:返回两个集合的并集
  • sdiff <key1> <key2>:返回两个集合的差集,集合先后顺序不同结果不同

Sorted Set 类型(有序集合)

  Sorted Set 集合特点如下:

  • 也是 String 类型元素的集合,但其有顺序
  • 集合成员唯一,这意味着集合中不能出现重复的数据
  • 不同的是每个元素都会关联一个 double 类型的分数(score),Redis 正是通过分数来为集合中的成员进行从小到大的排序
  • 有序集合的成员是唯一的,但分数(score)却可以重复。
  • 该集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。
  • 每个集合可存储 $2^{32}$ - 1 个元素(4294967295)

  下面为其相关命令:

  • zadd <key> <score1> <member1> <score2> <member2>:向有序集合按分数添加一个或多个成员,或者更新已存在成员的分数
  • zrange key <start> <stop> [withscores]:通过索引区间返回有序集合成指定区间内的成员(可选显示分数)
  • zrangebyscore key min max [withscores]:通过分数从小到大返回有序集合指定区间内的成员
  • zrevrangebyscore key max min [withscores]:通过分数从大到小返回有序集合指定区间内的成员
  • zincrby <key> <increment> <member>:有序集合中对指定成员的分数加上增量 increment
  • zrem <key> <member1> <member2>:移除有序集合中的一个或多个成员
  • zcount <key> <min> <max>:计算在有序集合中指定区间分数的成员数
  • zrank <key> <member>:返回有序集合中指定成员的索引排名

Hash 类型

  Hash 散列表特点如下:

  • String 类型的 field 和 value 的映射表,相当于 Java 中的Map<String,Map<String,String>,键是字符串,值是另一个映射
  • Hash 特别适合用于存储对象
  • 每个 Hash 散列表可以存储 $2^{32}$ - 1 个键值对(40多亿)。

  下面为其相关命令:

  • hset key <field> <value>:将哈希表 key 中的字段 field 的值设为 value
  • hget key <field>:获取存储在哈希表中指定字段的值
  • hmset key <field1> <value1> <fields2> <value2>:同时将多个 field-value (域-值)对设置到哈希表 key 中
  • hexists key <field>:查看哈希表 key 中,给定 field 是否存在
  • hkeys <key>:列出该哈希表中所有的 field
  • hincrby <key> <field>:为哈希表 key 中的 field 的值加上增量
  • hsetnx <key> <field> <value>:为哈希表 key 追加一个 field 字段,其值设置为 value,仅当 field 不存在时

Java API

  作为 Java 开发工程师,肯定想通过 Java 连接 Redis ,此时就需要用到一些常用的依赖包了:

  • Jedis 或 Lettuce
  • Spring Data Redis
  • Redission

Jedis

  不推荐使用了。

Spring Data Redis

  Spring Data Redis 是 Spring Data 家族的一部分。
  Spring Boot 提供了对 Redis 集成的组件包spring-boot-data-redis,它依赖于 spring-data-redis 和 lettuce。
  Spring Boot 1.0 默认使用的是 Jedis 客户端,2.0 替换成了 Lettuce。
  下面对几个概念说明一下:

  • Spring Data:是 Spring 框架中的一个主要项目,目的是为了简化构建基于 Spring 框架应用的数据访问,包括非关系数据库、 Map-Reduce 框架、云数据服务等,另外也包含对关系数据库的访问支持
  • Lettuce:是一个可伸缩线程安全的 Redis 客户端,多个线程可以共享同一个 RedisConnection,它利用优秀的 Netty NIO 框架来高效地管理多个连接
  • Spring Data Redis:是 Spring Data 项目中的一个主要模块,提供一个高度封装的“RedisTemplate”类实现了对 Redis 客户端 API 的高度封装,使得对 Redis 的操作更加便捷,而且Spring data redis 的连接池可以自动管理,针对数据的“序列化/反序列化”,提供了多种可选择策(如 RedisSerializer)

RedisTemplate

  Spring Data Redis 为 Redis 提供了一个 RedisTemplate 工具类,它封装了 Redis 5 种数据结构的各种操作,比如:

  • opsForValue():操作字符串
  • opsForHash():操作 hash
  • opsForList():操作 list
  • opsForSet():操作 set
  • opsForZSet():操作有序 set

Spring Boot 整合 Redis

  ① 在pom.xml中添加依赖:

1
2
3
4
5
6
7
8
9
10
<!-- Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- Lettuce 需要使用 commons-pool2 创建连接池 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>

  ② 在application.yml中进行相关配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
spring:
redis:
# Redis 服务器地址
host: localhost
# Redis 服务器连接端口
port: 6379
# Redis 数据库索引(默认为 0 )
database: 0
# Redis 服务器连接密码(默认为空)
password:
# 连接超时时间(毫秒)
timeout: PT10S
lettuce:
pool:
# 连接池最大连接数,默认 8,使用负值表示没有限制
max-active: 100
# 连接池最大阻塞等待时间,默认 -1,使用负值表示没有限制
max-wait: PT10S
# 连接池中的最大空闲连接,默认 8
max-idle: 30
# 连接池中的最小空闲连接,默认 0
min-idle: 1

  ③ 在平常的开发中我们可以中通过 Spring 的注入方式获取对RedisTemplate对象的引用:

1
2
@Autowired
private RedisTemplate<String, Object> redisTemplate;

  ④ 需要注意的是,首先需要自定义RedisTemplate的序列化方式(需引入fastjson包),具体过程如下:

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
/**
* 自定义 redis 序列化工具类
*
* @author lovike
* @since 2020-05-08
*/
public class CustomRedisSerializer implements RedisSerializer<Object> {
private final Charset charset;

public CustomRedisSerializer() {
this(StandardCharsets.UTF_8);
}

private CustomRedisSerializer(Charset charset) {
Assert.notNull(charset, "Charset must not be null!");
this.charset = charset;
}

@Override
public String deserialize(byte[] bytes) {
return (bytes == null ? null : new String(bytes, charset));
}

@Override
public byte[] serialize(Object object) {
if (object == null) {
return new byte[0];
}
if (object instanceof String) {
return object.toString().getBytes(charset);
} else {
String json = JSON.toJSONString(object);
return json.getBytes(charset);
}
}
}

  ⑤ 修改RedisTemplate配置,之后才可在需要的地方注入RedisTemplate<String, Object>

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
import cn.lovike.seckill.common.redis.CustomRedisSerializer;
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.StringRedisSerializer;

/**
* RedisTemplate 配置
*
* @author lovike
* @since 2020-05-08
*/
@Configuration
public class RedisTemplateConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
CustomRedisSerializer customRedisSerializer = new CustomRedisSerializer();
redisTemplate.setKeySerializer(stringRedisSerializer);
redisTemplate.setValueSerializer(customRedisSerializer);
// redisTemplate.setHashKeySerializer(stringRedisSerializer);
redisTemplate.setHashKeySerializer(customRedisSerializer);
redisTemplate.setHashValueSerializer(customRedisSerializer);
return redisTemplate;
}
}

RedisTemplate 源码解读

  为什么我们都没有在 Spring 中注入RedisTemplate对象,就能直接声明获取到它的引用呢?
  来看一下 Spring Boot 中的源码:

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
package org.springframework.boot.autoconfigure.data.redis;

import ...

@Configuration
@ConditionalOnClass({RedisOperations.class})
@EnableConfigurationProperties({RedisProperties.class})
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {
public RedisAutoConfiguration() {
}

@Bean
@ConditionalOnMissingBean(
name = {"redisTemplate"}
)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}

@Bean
@ConditionalOnMissingBean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}

  通过源码知道,在 Spring Boot 启动后会自动注入两个 Bean:

  • RedisTemplate
  • StringRedisTemplate

  那么它们有何不同呢?
  下面分别看一下RedisTemplateStringRedisTemplate 的源码(部分):

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
45
46
47
48
import ...
public class RedisTemplate<K, V> extends RedisAccessor implements RedisOperations<K, V>, BeanClassLoaderAware {
...

public RedisTemplate() {
}

public void afterPropertiesSet() {
super.afterPropertiesSet();
boolean defaultUsed = false;
if (this.defaultSerializer == null) {
this.defaultSerializer = new JdkSerializationRedisSerializer(this.classLoader != null ? this.classLoader : this.getClass().getClassLoader());
}

if (this.enableDefaultSerializer) {
if (this.keySerializer == null) {
this.keySerializer = this.defaultSerializer;
defaultUsed = true;
}

if (this.valueSerializer == null) {
this.valueSerializer = this.defaultSerializer;
defaultUsed = true;
}

if (this.hashKeySerializer == null) {
this.hashKeySerializer = this.defaultSerializer;
defaultUsed = true;
}

if (this.hashValueSerializer == null) {
this.hashValueSerializer = this.defaultSerializer;
defaultUsed = true;
}
}

if (this.enableDefaultSerializer && defaultUsed) {
Assert.notNull(this.defaultSerializer, "default serializer null and not all serializers initialized");
}

if (this.scriptExecutor == null) {
this.scriptExecutor = new DefaultScriptExecutor(this);
}

this.initialized = true;
}
...
}

1
2
3
4
5
6
7
8
9
10
11
import ...

public class StringRedisTemplate extends RedisTemplate<String, String> {
public StringRedisTemplate() {
this.setKeySerializer(RedisSerializer.string());
this.setValueSerializer(RedisSerializer.string());
this.setHashKeySerializer(RedisSerializer.string());
this.setHashValueSerializer(RedisSerializer.string());
}

...

  对它们而言:

1
2
3
4
5
6
7
8
9
// 若没有特殊的设置,key 和 value 都是使用 defaultSerializer = new JdkSerializationRedisSerializer(); 进行序列化的
// 下面是对 key 的默认序列化器,默认是 JdkSerializationRedisSerializer。
redisTemplate.setKeySerializer();
// 下面是对 value 的默认序列化器,默认是 JdkSerializationRedisSerializer。
redisTemplate.setValueSerializer();
//对 hash 结构数据的 hashkey 序列化器,默认是 JdkSerializationRedisSerializer。
redisTemplate.setHashKeySerializer();
//对 hash 结构数据的 hashvalue 序列化器,默认是 JdkSerializationRedisSerializer
redisTemplate.setHashValueSerializer();

  从上述源码可以看出它们的区别:

  • StringRedisTemplate继承自RedisTemplate
  • RedisTemplate是一个泛型类,而StringRedisTemplate不是
  • StringRedisTemplate只能对key=String,value=String的键值对进行操作,RedisTemplate可以对任何类型的key-value键值对操作
  • 它们各自序列化的方式不同,但最终都是得到了一个字节数组,比如StringRedisTemplate使用的是StringRedisSerializer类;RedisTemplate使用的是JdkSerializationRedisSerializer类。反序列化,前者则是得到String,后者得到Object
  • 另外针对数据的“序列化/反序列化”,提供了多种可选择策略(RedisSerializer):
    • JdkSerializationRedisSerializer:该序列化方法为 Jdk 提供。首先要求被序列化的类实现 Serializeable 接口,然后通过 Jdk 对象序列化的方法保存。(注:该序列化保存的对象,即使是个 String 类型的,在 Redis 控制台,也是看不出来的,因为它保存了一些对象的类型的额外信息。是目前最常用的序列化策略。
    • StringRedisSerializer:通过 String.getBytes() 来实现的。由于在 Redis 中,所有存储的值都是字符串类型的。所以该方法保存后通过 Redis-cli 控制台,可以清楚的查看到具体保存了什么 key 和 value,是最轻量级和高效的策略
    • JacksonJsonRedisSerializerjackson-json工具提供了 JavaBean Json 之间的转换能力,可以将 POJO 实例序列化成 Json 格式存储在 Redis 中,也可以将 Json 格式的数据转换成 POJO 实例。因为jackson工具在序列化和反序列化时,需要明确指定 Class类型,因此策略封装起来稍微复杂

  首先小小的总结一下:

  • RedisTemplate在操作数据的时候,存入数据前会先将数据序列化成字节数组,然后再存入 Redis 数据库(默认使用 JdkSerializationRedisSerializer序列化),在 Redis 控制台查看的数据是以不可读的形式展现的,即字节数组方式显示
  • StringRedisSerializer在操作数据的时候就是通过String.getBytes()来实现的。由于在 Redis 中,所有存储的值都是字符串类型的。因此该方法保存后,通过 Redis-cli 控制台(或 Redis 图形化工具),可以清楚的查看到具体保存了什么 key 和 value

参考

文章信息

时间 说明
2019-04-10 初稿
2022-12-21 部分重构
0%