Redis 基础部分


Redis 概述


Redis是一种 键值型 的 NoSQL 数据库,这里有两个关键字:

  • 键值型
    • 这里的 键值型,是指Redis中存储的数据都是以 key.value 对的形式存储,而 value 的形式多种多样,可以是 字符串、数值、甚至 json
  • NoSQL
    • 这里的 NoSQL数据库,是指对于存储的数据,没有类似MySQL那么严格的约束,比如唯一性,是否可以为 null 等等

SQL vs NoSQL:

SQL vs NoSQL

  • 事务

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

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

Redis 的特征:

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

docker 启动 redis:

1
2
3
4
5
docker exec -it redis容器id /bin/bash

redis-cli

auth password(输入密码)

Redis 常见命令


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

Redis 数据结构

redis 的帮助文档:https://redis.io/commands


Redis 通用命令


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

  • keys:查看 符合模板 的所有 key

    1
    2
    3
    4
    5
    6
    7
    # 查询所有的key
    127.0.0.1:6379> keys *
    1) "author"

    # 查询所有以a开头的key
    127.0.0.1:6379> keys a*
    1) "author"
    • 在生产环境下,不推荐使用 keys 命令,因为 redis 是 单线程 的,这个命令在 key 过多的情况下,效率不高
  • del:删除 一个指定的 key

    1
    2
    3
    # 删除一个指定的key
    127.0.0.1:6379> del author
    (integer) 1
  • exist:判断 key 是否存在

    1
    2
    127.0.0.1:6379> exists author
    (integer) 1
  • expire:给一个 key 设置有效期,有效期到期时该 key 会被自动删除

  • ttl:查看一个 key 的 剩余有效期

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    # 给 key 为 author 设置 10s 的有效期
    127.0.0.1:6379> expire author 10
    (integer) 1

    # key 为 author 的剩余有效期为 2s
    127.0.0.1:6379> ttl author
    (integer) 2

    # 当这个 key 过期 了,那么此时查询出来就是 -2
    127.0.0.1:6379> ttl author
    (integer) -2

    #如果没有设置过期时间
    127.0.0.1:6379> set age 10
    OK

    # ttl的返回值就是-1
    127.0.0.1:6379> ttl age
    (integer) -1
    • 内存非常宝贵,对于一些数据,应当给它设置一些过期时间,当过期时间到了之后,它就会自动被删除

String 类型


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

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

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

举例

不管是哪种格式,底层 都是 字节数组 形式存储,只不过是 编码方式不同

字符串类型 的 最大空间 不能超过 512MB

String 的 常见命令:

  • set:添加(如果不存在即为添加) 或者 修改(如果存在即为修改) 已经存在的一个 String类型 的 键值对

    1
    2
    3
    4
    5
    6
    7
    # 原来存在,就是修改
    127.0.0.1:6379> set name Jack
    OK

    # 原来不存在
    127.0.0.1:6379> set name Rose
    OK
  • get:根据 key 获取 String 类型的 value

    1
    2
    127.0.0.1:6379> get name 
    "Rose"
  • mset:批量添加 多个 String 类型的 键值对

    1
    2
    127.0.0.1:6379> MSET k1 v1 k2 v2 k3 v3
    OK
  • mget:根据多个 key 获取多个 String 类型的 value

    1
    2
    3
    4
    5
    6
    127.0.0.1:6379> MGET name age k1 k2 k3
    1) "Jack" # 之前存在的name
    2) "10" # 之前存在的age
    3) "v1"
    4) "v2"
    5) "v3"
  • incr:让一个 整型 的 key 自增1

    1
    2
    3
    4
    5
    6
    127.0.0.1:6379> get age 
    "10"

    # 增加1
    127.0.0.1:6379> incr age
    (integer) 11
  • incrby:让一个 整型 的 key 自增 并指定 步长

    1
    2
    3
    4
    5
    6
    7
    # 一次增加2
    127.0.0.1:6379> incrby age 2
    (integer) 13 # 返回目前的age的值

    # 一次减少1
    127.0.0.1:6379> incrby age -1
    (integer) 12
  • incrbyfloat:让一个 浮点 类型的数字 自增 并指定 步长

    1
    2
    3
    4
    5
    127.0.0.1:6379> set score 10.1
    OK

    127.0.0.1:6379> incrbyfloat score 1.2
    "11.3"
  • setnx:添加一个 String 类型的 键值对,前提是这个 key 不存在,否则不执行

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # 设置名称
    127.0.0.1:6379> set name Jack
    OK

    # 如果 key 不存在,则添加成功
    127.0.0.1:6379> setnx name lisi
    (integer) 0

    # name2 不存在,所以操作成功
    127.0.0.1:6379> setnx name2 lisi
    (integer) 1
  • setex:添加一个 String 类型的 键值对,并且指定 有效期

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    127.0.0.1:6379> setex name 10 jack
    OK

    127.0.0.1:6379> ttl name
    (integer) 8

    127.0.0.1:6379> ttl name
    (integer) 7

    127.0.0.1:6379> ttl name
    (integer) 5

Key 的 层级结构


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

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

eg:

1
2
3
4
5
# user相关的key
heisenberg:user:1

# product相关的key
heisenberg:product:2

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

KEY VALUE
heisenberg:user:1 {“id”:1, “name”: “Jack”, “age”: 21}
heisenberg:product:1 {“id”:1, “name”: “苹果12”, “price”: 4999}

一旦向 redis 采用这样的方式存储,那么 redis 会以 层级结构 来进行 存储


Hash 类型


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

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

Hash 类型

Hash类型 的 常见命令:

  • hset key field value:添加 或者 修改 hash类型 key 的 field 的值

  • hget key field:获取 一个 hash 类型 key 的 field 的值

    1
    2
    3
    4
    5
    6
    127.0.0.1:6379> hset test1 hello 1
    (integer) 1


    127.0.0.1:6379> hget test1 hello
    "1"
  • hmset:批量 添加 多个 hash 类型 key 的 field 的值

  • hmget:批量 获取 多个 hash 类型 key 的 field 的值

    1
    2
    3
    4
    5
    6
    7
    8
    127.0.0.1:6379> hmset test1 name lilei age 20 sex male 
    OK


    127.0.0.1:6379> hmget test1 name age sex
    1) "lilei"
    2) "20"
    3) "male"
  • hgetall:获取一个 hash 类型的 key 中的所有的 field 和 value

    1
    2
    3
    4
    5
    6
    7
    8
    9
    127.0.0.1:6379> hgetall test1
    1) "hello" # key
    2) "1" # value
    3) "name"
    4) "lilei"
    5) "age"
    6) "20"
    7) "sex"
    8) "male"
  • hkeys:获取一个 hash 类型的 key 中的所有的 field

  • hvals:获取一个 hash 类型的 key 中的所有的 value

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    127.0.0.1:6379> hkeys test1
    1) "hello"
    2) "name"
    3) "age"
    4) "sex"


    127.0.0.1:6379> hvals test1
    1) "1"
    2) "lilei"
    3) "20"
    4) "male"
  • hincrby:让一个 hash 类型 key 的字段值 自增 并指定 步长

    1
    2
    3
    # 取test1字段的age的value自增2
    127.0.0.1:6379> hincrby test1 age 2
    (integer) 22
  • hsetnx:添加一个 hash 类型的 key 的 field 值,前提是这个 field 不存在,否则不执行

    1
    2
    127.0.0.1:6379> hsetnx test1 sex female
    (integer) 0 # 返回值为0表示执行失败

List 类型


Redis 中的 List类型 与 Java 中的 LinkedList 类似,可以看做是一个 双向链表 结构

既可以 支持 正向检索 和也可以 支持 反向检索

List 类型

特征 与 LinkedList 类似:

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

List 类型 的 常见命令:

  • lpush key element ... :向 列表 左侧 插入 一个 或 多个元素

  • rpush key element ... :向 列表 右侧 插入 一个 或 多个元素

    1
    2
    3
    4
    5
    127.0.0.1:6379> lpush test2 1, 2, 3
    (integer) 3

    127.0.0.1:6379> rpush test2 4, 5, 6
    (integer) 6
  • lpop key:移除 并 返回列表 左侧 的 第一个元素,没有则返回 nil

  • rpop key:移除 并 返回列表 右侧 的 第一个元素

    1
    2
    3
    4
    5
    127.0.0.1:6379> lpop test2
    "3"

    127.0.0.1:6379> rpop test2
    "6"
  • lrange key start end:返回一段 角标范围 内的 所有元素

    1
    2
    3
    127.0.0.1:6379> lrange test2 1 2  # 这个角标范围是从0开始
    1) "1,"
    2) "4,"
  • blpop 和 brpop:与 lpop 和 rpop 类似,只不过在 没有元素时 等待指定时间,而不是直接返回 nil

    1
    2
    3
    127.0.0.1:6379> blpop test3 10
    (nil)
    (10.09s)

利用 List 结构模拟一个栈:入口 和 出口 在 同一边

利用 List 结构模拟一个队列:入口 和 出口 在 不同边

利用 List 结构模拟一个阻塞队列:入口 和 出口 在 不同边,出队时采用 BLPOP 或 BRPOP


Set 类型


Redis 的 Set 结构与 Java 中的 HashSe t类似,可以看做是一个 value 为 null 的 HashMap

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

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

Set 类型的常见命令:

  • sadd key member ... :向 set 中 添加 一个或多个 元素

    1
    2
    127.0.0.1:6379> sadd test3 1 2 3 3
    (integer) 3
  • srem key member ... : 移除 set 中的 指定元素

    1
    2
    127.0.0.1:6379> srem test3 3
    (integer) 1
  • scard key: 返回 set 中 元素的个数

    1
    2
    127.0.0.1:6379> scard test3
    (integer) 2
  • sismember key member:判断 一个元素 是否存在于 set 中

    1
    2
    3
    4
    5
    127.0.0.1:6379> sismember test3 1
    (integer) 1

    127.0.0.1:6379> sismember test3 3
    (integer) 0
  • smembers:获取 set 中的 所有元素

    1
    2
    3
    127.0.0.1:6379> smembers test3
    1) "1"
    2) "2"
  • sinter key1 key2 ... :求 key1 与 key2 的交集

    1
    2
    3
    4
    5
    6
    127.0.0.1:6379> sadd test4 1 2 3 4
    (integer) 4

    127.0.0.1:6379> sinter test3 test4
    1) "1"
    2) "2"
  • sdiff key1 key2 ... :求 key1 与 key2 的差集

    1
    2
    127.0.0.1:6379> sdiff test3 test4
    (empty array)

    diff的含义

  • sunion key1 key2 ...:求 key1 和 key2 的并集

    1
    2
    3
    4
    5
    127.0.0.1:6379> sunion test3 test4
    1) "1"
    2) "2"
    3) "3"
    4) "4"

SortedSet 类型


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

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

SortedSet 具备下列特性:

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

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 即可,例如:

  • 升序 获取 sorted set 中的指定元素的排名:zrank key member
  • **降序 **获取 sorted set 中的指定元素的排名:zrevrank key memeber

Redis 的 Java 客户端


Redis 的客户端网站:https://redis.io/docs/clients/

Java 的常用客户端:

  • Jedis
    • 以Redis命令作为方法名称,学习成本低,简单实用
    • 但是Jedis实例是 线程不安全 的,多线程环境下需要 基于连接池 来使用
    • Spring Data Redis 兼容 Jedis
  • Lettuce
    • Lettuce是基于 Netty 实现的,支持同步、异步和 响应式编程方式,并且是 线程安全 的
    • 支持 Redis 的哨兵模式、集群模式 和 管道模式
    • Spring Data Redis 兼容 Lettuce
  • Redission
    • Redisson是一个基于 Redis 实现的 分布式、可伸缩 的Java数据结构集合
    • 包含了诸如 Map 、Queue 、Lock 、 Semaphore 、AtomicLong 等强大功能

Jedis 快速入门


Jedis 的 官网地址: https://github.com/redis/jedis

流程:

  1. 引入依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <!--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>
  2. 建立连接

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    private Jedis jedis;

    // junit5 的方法,类似于 @before
    @BeforeEach
    void setUp() {

    // 1.建立连接
    // jedis = new Jedis("192.168.150.101", 6379);
    jedis = JedisConnectionFactory.getJedis();

    // 2.设置密码
    jedis.auth("123321");

    // 3.选择库
    jedis.select(0);
    }
  3. 测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    void testString() {
    // 存入数据
    String result = jedis.set("name", "Bob");
    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);
    }
  4. 释放资源

    1
    2
    3
    4
    5
    6
    7
    @AfterEach
    void tearDown() {
    // 释放资源
    if (jedis != null) {
    jedis.close();
    }
    }

注意:

  • Jedis 本身是 线程不安全 的,并且 频繁的创建和销毁连接 会有 性能损耗

  • 推荐使用 Jedis连接池 代替 Jedis的直连方式

    • 有关 池化思想,并不仅仅是这里会使用,很多地方都有,比如说:数据库连接池,比如:tomcat 中的线程池,这些都是 池化思想 的体现

Jedis 连接池


流程:

  1. 创建 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
    28
    29
    30
    public class JedisConnectionFacotry {

    private static final JedisPool jedisPool;

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

    // 最大连接
    poolConfig.setMaxTotal(8);

    // 最大空闲连接
    poolConfig.setMaxIdle(8);

    // 最小空闲连接
    poolConfig.setMinIdle(0);

    // 设置最长等待时间,ms
    poolConfig.setMaxWaitMillis(1000);

    // 创建连接池对象
    jedisPool = new JedisPool(poolConfig,
    "192.168.150.101",6379,1000,"123321");
    }

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

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

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

  2. 改造原始代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    @BeforeEach
    void setUp(){

    // 建立连接
    /*jedis = new Jedis("127.0.0.1",6379);*/
    jedis = JedisConnectionFacotry.getJedis();

    // 选择库
    jedis.select(0);
    }

    @AfterEach
    void tearDown() {
    if (jedis != null) {
    jedis.close();
    }
    }

SpringDataRedis


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

官网地址:https://spring.io/projects/spring-data-redis

SpringData 的特点:

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

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

RedisTemplate 工具类


SpringDataRedis 流程


SpringBoot 已经提供了对 SpringDataRedis 的支持

流程:

  1. 导入 pom 坐标

    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
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.5.7</version>
    <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.heima</groupId>
    <artifactId>redis-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>redis-demo</name>
    <description>Demo project for Spring Boot</description>
    <properties>
    <java.version>1.8</java.version>
    </properties>
    <dependencies>

    <!--redis依赖-->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>

    <!--common-pool-->
    <dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
    </dependency>

    <!--Jackson依赖-->
    <dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    </dependency>

    <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
    </dependency>

    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    </dependency>
    </dependencies>

    <build>
    <plugins>
    <plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <configuration>
    <excludes>
    <exclude>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    </exclude>
    </excludes>
    </configuration>
    </plugin>
    </plugins>
    </build>

    </project>
  2. 配置文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    spring:
    redis:
    host: 192.168.88.129
    port: 6379
    password: leadnews
    lettuce:
    pool:
    max-active: 8 # 最大连接
    max-idle: 8 # 最大空闲连接
    min-idle: 0 # 最小空闲连接
    max-wait: 100ms # 连接等待时间
  3. 测试代码

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

    // 注入RedisTemplate
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Test
    void testString() {

    // 写入一条String数据
    redisTemplate.opsForValue().set("name", "Bob");

    // 获取string数据
    Object name = redisTemplate.opsForValue().get("name");
    System.out.println("name = " + name); // Bob
    }
    }

总结:

SpringDataRedis 的 使用步骤:

  • 引入 spring-boot-starter-data-redis 依赖
  • 在 application.yml 配置 Redis信息
  • 注入 RedisTemplate

SpringDataRedis 的序列化方式


RedisTemplate 可以接收任意 Object 作为 值 写入 Redis,只不过写入前会把 Object 序列化为 字节形式,默认是采用 JDK序列化,缺点是会造成 可读性差、内存占用较大 等问题

自定义 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
25
26
27
@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()); // utf-8
template.setHashKeySerializer(RedisSerializer.string()); // utf-8

// 设置Value的序列化
template.setValueSerializer(jsonRedisSerializer);
template.setHashValueSerializer(jsonRedisSerializer);

// 返回
return template;
}
}

整体可读性有了很大提升,并且能将 Java对象 自动的 序列化 为 JSON字符串,并且查询时能 自动 把 JSON 反序列 化为 Java对象

举例

不过,其中记录了 序列化时 对应的 class名称,目的是为了 查询时 实现 自动反序列化。这会带来 额外的内存开销。

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

手动处理序列化

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
// 这种存储在redis的json字符串没有@class部分内容,就是单纯的user对象的json字符串

@SpringBootTest
class RedisStringTests {

// String序列化器
@Autowired
private StringRedisTemplate stringRedisTemplate;

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);
}
}

总结:

RedisTemplate 的 两种 序列化 实践方案:

  • 方案一:

    • 自定义 RedisTemplate
    • 修改 RedisTemplate 的序列化器为 GenericJackson2JsonRedisSerializer
  • 方案二:

    • 使用 StringRedisTemplate
    • 写入 Redis 时,手动把 对象 序列化为 JSON
    • 读取 Redis 时,手动把读取到的 JSON 反序列化为 对象

Hash 结构的操作


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

@Autowired
private StringRedisTemplate stringRedisTemplate;


@Test
void testHash() {

// put(key, field, value);
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);
}
}

Redis 高级部分