目录

缓存

redis分布式缓存

缓存设计

1. Redis入门

1.1 Redis简介

1.2 Redis下载与安装

1.2.1 Redis下载

1.2.2 Redis安装

1.3 Redis服务启动与停止

1.3.1 redis服务的启动命令

1.3.2 客户端连接redis服务的命令

1.3.3 修改Redis配置文件

1.3.4 Redis客户端图形工具

2. Redis中的数据类型

2.1 五种常用数据类型介绍

2.2 各种数据类型特点

Redis中Key的层级结构

3. Redis的常用命令

3.1 操作字符串类型的命令

3.2 操作哈希的命令

3.3 操作列表类型命令

3.4 操作集合类型命令

3.5 操作有序集合的命令

3.6 通用命令

4.在Java中操作Redis

4.1 Redis的Java客户端

4.1.1Jedis快速入门

4.1.2 Jedis连接池

​​​​​ 4.1.3 Spring Data Redis

4.1.3.1 介绍

4.1.3.2 快速入门

4.1.3.3 .SpringDataRedis的序列化方式

4.1.3.4 StringRedisTemplate

获取操作其他数据类型对象的演示

操作常见类型数据演示


缓存

对于经常访问的数据,每次都从数据库(硬盘)中获取是比较慢的,所以可以利用性能更高的存储来提高系统响应速度,俗称缓存。

合理使用缓存可以显著降低数据库的压力、提高系统性能。

什么样的数据适合缓存?对于“读多写少”的数据就非常适合缓存,也就是要频繁查询的,不怎么修改的数据。

具体来说:

  1. 高频访问的数据:如系统首页、热门推荐内容等。
  2. 计算成本较高的数据:如复杂查询结果、大量数据的统计结果。
  3. 允许短时间延迟的数据:如不需要实时更新的排行榜、图片列表等。

redis分布式缓存

Redis分布式缓存是指将缓存数据分布存储在多台服务器上,以便在高并发场景下提供更高的吞吐量和更好的容错性。

Redis是实现分布式缓存的主流方案,也是后端开发必学的技能, 主要是由于它具有下面几个优势:

  • 高性能:基于内存操作,访问速度极快。单节点Redis的读写QPS可达10w次每秒!
  • 丰富的数据结构:支持字符串、列表、集合、哈希、位图等,适用于各种数据结构存储。
  • 分布式支持:可以通过Redis Cluster构建高可用、高性能的分布式缓存,还提供哨兵集群机制提升可用性、提供分片集群机制提高可扩展性。

缓存设计

使用redis前一定要先明确如何设计缓存,比如需要缓存首页的图片列表数据,也就是对listPictureVOByPage接口进行缓存,首先要按照缓存的三要素“key、value、过期时间"进行设计。

1)缓存key设计

由于接口支持传入不同的查询条件,不同查询条件查询到的数据也不同,因此需要将查询条件作为缓存key的一部分。

可以将查询条件对象转换为SON字符串,但这个JSON会比较长,可以利用哈希算法(md5)来压缩key。

此外,由于使用分布式缓存,可能有多个项目和业务共享,因此需要在key的开头拼接前缀进行隔离。设计出的key如下:

项目名:listPictureVOByPage:${查询条件key}

2)缓存value设计

缓存从数据库中查到的Page分页对象,存储为什么格式呢?这里有2种选择:

  • 为了可读性,可以转换为JSON结构的字符串。
  • 为了压缩空间,可以存为二进制等其他结构

但是对应的Redis数据结构都是string

3)缓存的过期时间设置

必须设置缓存的过期时间!根据实际业务场景和缓存空间的大小、数据的一致性的要求设置,合适即可,此处由于查询条件较多、而且考虑到图片会持续更新,设置为5~60分钟即可。

1. Redis入门

1.1 Redis简介

Redis是一个基于内存的键值型(key-value)NoSQL数据库,其中的数据都是以key-value对的形式来存储的。Redis 是互联网技术领域使用最为广泛的存储中间件

官网:https://redis.io 中文网:Redis中文网

key-value结构存储:

主要特点:

  • 基于内存存储,读写性能高

  • 存储键值(key-value)型数据,value支持多种不同的数据类型,功能丰富

  • 单线程,每个命令具备原子性

  • 适合存储热点数据(热点商品、资讯、新闻)

  • 企业应用广泛

  • 支持数据持久化

  • 支持主从集群.分片集群

  • 支持多语言客户端

Redis是用C语言开发的一个开源的高性能键值对(key-value)数据库,官方提供的数据是可以达到100000+的QPS(每秒内查询次数)。它存储的value类型比较丰富,也被称为结构化的NoSql数据库。

NoSql(Not Only SQL),不仅仅是SQL,泛指非关系型数据库。NoSql数据库并不是要取代关系型数据库,而是关系型数据库的补充。

结构化与非结构化:

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

而NoSql数据库则没有严格约束,往往形式松散,自由。

可以是键值型:

也可以是文档型:

甚至可以是图格式:

关联和非关联:

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

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

{
  id: 1,
  name: "张三",
  orders: [
    {
       id: 1,
       item: {
	 id: 10, title: "荣耀6", price: 4999
       }
    },
    {
       id: 2,
       item: {
	 id: 20, title: "小米11", price: 3999
       }
    }
  ]
}

此处要维护“张三”的订单与商品“荣耀”和“小米11”的关系,不得不冗余的将这两个商品保存在张三的订单文档中,不够优雅。还是建议用业务来维护关联关系。

查询方式

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

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

事务

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

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

总结:

除了上述四点以外,在存储方式、扩展性、查询性能上关系型与非关系型也都有着显著差异,总结如下:

  • 存储方式

    • 关系型数据库基于磁盘进行存储,会有大量的磁盘IO,对性能有一定影响

    • 非关系型数据库,他们的操作更多的是依赖于内存来操作,内存的读写速度会非常快,性能自然会好一些

  • 扩展性

    • 关系型数据库集群模式一般是主从,主从数据一致,起到数据备份的作用,称为垂直扩展。

    • 非关系型数据库可以将数据拆分,存储在不同机器上,可以保存海量数据,解决内存大小有限的问题。称为水平扩展。

    • 关系型数据库因为表之间存在关联关系,如果做水平扩展会给数据查询带来很多麻烦

关系型数据库(RDBMS):

  • Mysql

  • Oracle

  • DB2

  • SQLServer

非关系型数据库(NoSql):

  • Redis

  • Mongo db

  • MemCached

1.2 Redis下载与安装

1.2.1 Redis下载

Redis安装包分为windows版和Linux版:

1.2.2 Redis安装

1)在Windows中安装Redis

Redis的Windows版属于绿色软件,直接解压即可使用,解压后目录结构如下:

2)在Linux中安装Redis(简单了解)

在Linux系统安装Redis步骤:

  1. 将Redis安装包上传到Linux

  2. 解压安装包,命令:tar -zxvf redis-4.0.0.tar.gz -C /usr/local

  3. 安装Redis的依赖环境gcc,命令:yum install gcc-c++

  4. 进入/usr/local/redis-4.0.0,进行编译,命令:make

  5. 进入redis的src目录进行安装,命令:make install

安装后重点文件说明:

  • /usr/local/redis-4.0.0/src/redis-server:Redis服务启动脚本

  • /usr/local/redis-4.0.0/src/redis-cli:Redis客户端脚本

  • /usr/local/redis-4.0.0/redis.conf:Redis配置文件

1.3 Redis服务启动与停止

以window版Redis进行演示:

1.3.1 redis服务的启动命令

redis-server.exe redis.windows.conf

Redis服务的默认端口号为 6379 ,通过快捷键Ctrl + C 即可停止Redis服务

当Redis服务启动成功后,可通过客户端进行连接。

1.3.2 客户端连接redis服务的命令

redis-cli.exe

通过redis-cli.exe命令默认连接的是本地的redis服务,并且使用默认6379端口。也可以通过指定如下参数连接:

  • -h 要连接的redis节点的IP地址,默认是127.0.0.1

  • -p 指定要连接的redis节点的端口,默认是6379

  • -a 指定redis的访问密码

1.3.3 修改Redis配置文件

如何设置Redis服务的密码,需要修改redis.windows.conf(redis的配置文件)

#requirepass 123456     
requirepass 123456			将前面的#去掉,表示去除注释

注意:

  • 修改密码后需要重启Redis服务才能生效

  • Redis配置文件中 # 表示注释

重启Redis后,再次连接Redis时,需加上密码,否则连接失败。

redis-cli.exe -h localhost -p 6379 -a 123456

此时,-h 和 -p 参数可省略不写。

1.3.4 Redis客户端图形工具

默认提供的客户端连接工具界面不太友好,同时操作也较为麻烦,接下来,引入一个Redis客户端图形工具。

安装完毕后,直接双击启动

新建连接

连接成功

2. Redis中的数据类型

2.1 五种常用数据类型介绍

Redis中存储的都是key-value对结构的数据,其中:

key一般都是字符串类型。

而value的数据类型多种多样:

  • 字符串 string

  • 哈希 hash

  • 列表 list

  • 集合 set

  • 有序集合 sorted set / zset

  • GEO

  • BitMap

  • HyperLog

2.2 各种数据类型特点

解释说明:

  • 字符串(string):普通字符串,Redis中最简单的数据类型,根据字符串的格式不同又可以分为三类(不管是哪种格式,底层都是字节数组形式存储,只不过是编码方式不同。字符串类型的最大空间不能超过512m. ):

    • string:普通字符串

    • int:整数类型,可以做自增、自减操作

    • float:浮点类型,可以做自增、自减操作

  • 哈希(hash):也叫散列,类似于Java中的HashMap结构

  • 列表(list):按照插入顺序排序,可以有重复元素,类似于Java中的LinkedList,可以看做是一个双向链表结构。既可以支持正向检索和也可以支持反向检索,特征也与LinkedList类似

    • 有序,和插入顺序有关

    • 元素可以重复

    • 插入和删除快

    • 查询速度一般

    • 常用来存储一个有序数据,例如:朋友圈点赞列表,评论列表等。

  • 集合(set):无序(数据的存储顺序和插入顺序无关),且没有重复元素,类似于Java中的HashSet,查找快,支持交集.并集.差集等功能

  • 有序集合(sorted set/zset):集合中每个元素关联一个分数(score),根据分数升序排序,没有重复元素

Redis中Key的设计结构

redis中key必须是唯一的,一般用id作为key,但还有一个问题,该如何区分不同类型的key呢?

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

通过给key添加前缀来区分,不过这个前缀不是随便加的,有一定的规范:

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

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

  • 项目名:业务名:数据名:数据id
  • 业务名:数据名:数据id

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

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

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

再例如我们的登录业务,保存用户信息,其key可以设计成如下格式:

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

KEY VALUE
heima:user:1 {"id":1, "name": "Jack", "age": 21}
heima:product:1 {"id":1, "name": "小米11", "price": 4999}

一旦我们向redis采用这样的方式存储,那么在可视化界面中,redis会以层级结构来进行存储,形成类似于这样的结构,更加方便Redis获取数据

这样设计的好处:

  • 可读性强

  • 避免key冲突

  • 方便管理

当 key 对应的value是string类型时,string类型的底层编码包含int、embstr和raw三种

  • embstr在小于44字节时自动使用,采用连续内存空间,内存占用更小。
  • 当字节数大于44字节时,会转为raw模式存储,在raw模式下,内存空间不是连续的,而是采用一个指针指向了另外一段内存空间,在这段空间里存储SDS内容,这样空间不连续,访问的时候性能也就会收到影响,还有可能产生内存碎片
  • 如果全是数字的字符串,使用的编码是int(这种占用的内存很小)

所以,尽量长度不超过44字节,不包含特殊字符

3. Redis的常用命令

3.1 操作字符串类型的命令

Redis 中操作字符串类型常用的命令

  • SET key value 设置指定key的值为value

  • GET key 获取指定key对应的值

  • MSET:批量添加多个String类型的键值对

  • MGET:根据多个key获取多个String类型的value

  • INCR:让一个整型的key自增1

  • INCRBY:让一个整型的key自增并指定步长,例如:incrby num 2 让num值自增2

  • INCRBYFLOAT:让一个浮点类型的数字自增并指定步长

  • SETEX key seconds value 设置指定key的过期时间设为 seconds 秒,值为value

  • SETNX key value 当 key不存在时,设置 key 的值

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

更多命令可以参考Redis中文网:https://www.redis.net.cn

3.2 操作哈希的命令

Redis hash 是一个string类型的 field 和 value 的映射表,hash特别适合用于存储对象,常用命令:

  • HSET key field value 设置哈希表key中,field字段的值为value

  • HGET key field 获取哈希表key中field字段对应的值

  • HMSET:批量添加多个hash类型key的field的值

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

  • HGETALL:获取一个哈希类型的key中的所有的field和value

  • HDEL key field 删除哈希表key中 指定的字段

  • HKEYS key 获取哈希表key中 所有的field

  • HVALS key 获取哈希表key中 所有的值

  • HINCRBY:让一个hash类型key的字段值自增并指定步长

  • HSETNX:添加一个hash类型的key的field值,前提是这个field不存在,否则不执行

3.3 操作列表类型命令

Redis中的列表类型是简单的字符串列表,按照插入顺序排序,常用命令:

  • LPUSH key value1 [value2] 向列表左侧依次插入一个或多个元素

  • LPOP key:移除并返回列表左侧的第一个元素,没有则返回nil

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

  • RPOP key 移除并获取列表key中的最后一个元素

  • LRANGE key start stop 获取列表key中指定下标内的元素

  • LLEN key 获取列表key的长度

  • BRPOP key1 [key2 ] timeout 移出并获取列表的最后一个元素, 如果列表没有元素会阻塞直到等待超时或发现可弹出元素为止

  • BLPOP和BRPOP:与LPOP和RPOP类似,只不过在没有元素时等待指定的时间,而不是直接返回nil

    3.4 操作集合类型命令

    Redis set 是string类型元素的无序集合。集合中的元素是唯一的,常用命令:

    • SADD key member1 [member2] 向集合key添加一个或多个成员

    • SREM key member1 [member2] 移除集合中一个或多个成员

    • SMEMBERS key 返回集合key中的所有成员

    • SCARD key 获取集合key的成员数

    • SISMEMBER key member:判断一个元素是否存在于set中

    • SINTER key1 [key2] 返回给定所有集合的交集

    • SUNION key1 [key2] 返回所有给定集合的并集

    • SDIFF key1 key2 ... :求key1与key2的差集

    3.5 操作有序集合的命令

    Redis有序集合是string类型的元素集合,不允许有重复的成员,每个元素都会关联一个double类型的分数。常用命令:

    常用命令:

    • ZADD key score1 member1 [score2 member2] 向有序集合key中添加一个或多个成员

    • ZREM key member [member ...] 移除有序集合中的一个或多个成员

    • ZSCORE key member : 获取sorted set中的指定元素的score值

    • ZRANK key member:获取sorted set 中的指定元素的排名,排名其实也就是下标,从0开始

    • ZCARD key:获取sorted set中的元素个数

    • ZCOUNT key min max:统计score值在给定范围内的所有元素的个数

    • ZINCRBY key increment member:给sorted set中指定的元素自增,步长为指定的increment值

    • ZRANGE key start stop [WITHSCORES] 返回有序集合key中指定下标区间内的成员,其中成员的位置按分数值递增(从小到大)来排序。

    • ZRANGEBYSCORE key min max:按照score排序后,获取指定score范围内的元素

    • ZINCRBY key increment member 对有序集合中指定成员的分数加上 increment增量

    • ZDIFF.ZINTER.ZUNION:求差集.交集.并集

      注意:所有的排名score默认都是升序排序的,如果要降序则在命令的Z后面添加REV即可,例如:

      • 升序获取sorted set 中的指定元素的排名:ZRANK key member

      • 降序获取sorted set 中的指定元素的排名:ZREVRANK key memeber

    • 3.6 通用命令

      Redis的通用命令是不分数据类型的,都可以使用的命令:

    • KEYS pattern:查找所有符合指定模式( pattern)的 key

    • EXISTS key:判断指定 key 是否存在

    • TYPE key 返回 key 所储存的值的类型

    • DEL key:在 key 存在时,删除一个指定的key

    • EXPIRE key time:给一个key设置有效期,有效期到时,该key会被自动删除

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

    在生产环境下,不推荐使用keys 命令,因为这个命令在key过多的情况下,效率不高

    127.0.0.1:6379> keys *
    1) "name"
    2) "age"
    127.0.0.1:6379>
    
    # 查询以a开头的key
    127.0.0.1:6379> keys a*
    1) "age"
    127.0.0.1:6379>
    127.0.0.1:6379> exists age
    (integer) 1
    
    127.0.0.1:6379> exists name
    (integer) 0
    127.0.0.1:6379> del name #删除单个
    (integer) 1  #成功删除1个
    
    127.0.0.1:6379> del k1 k2 k3 k4
    (integer) 3   #此处返回的是成功删除的key的数量,由于redis中只有k1,k2,k3 所以只成功删除3个,最终返回
    127.0.0.1:6379>
    127.0.0.1:6379> expire age 10
    (integer) 1
    
    127.0.0.1:6379> ttl age
    (integer) 8
    
    127.0.0.1:6379> ttl age
    (integer) 6
    
    127.0.0.1:6379> ttl age
    (integer) -2
    
    127.0.0.1:6379> ttl age
    (integer) -2  #当这个key过期了,那么此时查询出来就是-2 
    
    127.0.0.1:6379> keys *
    (empty list or set)
    
    127.0.0.1:6379> set age 10 #如果没有设置过期时间
    OK
    
    127.0.0.1:6379> ttl age
    (integer) -1  # ttl的返回值就是-1

    4.在Java中操作Redis

    在Redis官网中提供了各种语言的客户端,地址:https://redis.io/docs/clients/

    其中Java客户端也包含很多:

    标记为❤的就是推荐使用的java客户端

    4.1 Redis的Java客户端

    Redis的Java客户端就是用java代码来操作redis依赖,就如同我们使用JDBC操作MySQL数据库一样。

    Redis 的 Java 客户端很多,常用的几种:

    • Jedis和Lettuce:这两个主要是提供了Redis命令对应的API,方便我们操作Redis。

      • Jedis的java客户端以redis的命令作为方法名称,学习成本低,但是Jedis实例是线程不安全的,在多线程环境下需要为每一个线程创建独立的Jedis连接,所以多线程环境下必须基于连接池来使用

      • Lettuce是基于Netty实现的,支持同步,异步和响应式编程方式,并且是线程安全的,支持redis的哨兵模式、集群模式和管道模式,在Spring官方默认使用的是这个客户端

    • Spring Data Redis:Spring的一部分,在SpringBoot项目中还提供了对应的Starter,即 spring-boot-starter-data-redis,SpringDataRedis对Jedis和Lettuce两种客户端做了抽象和封装,因此我们后期会直接以SpringDataRedis来学习

    • Redisson:是一个基于Redis实现的分布式、可伸缩的java数据结构的集合,包含了如Map、Queue、Lock、Semaphore、AtomicLong等强大的功能,而且支持跨进程的同步机制:Lock.Semaphore等待,比较适合用来实现特殊的功能需求。

    4.1.1Jedis快速入门

    1)引入jedis的客户端依赖:

    <!--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)建立连接,并操作redis

    package com.heima;
    
    import org.junit.jupiter.api.AfterEach;
    import org.junit.jupiter.api.BeforeEach;
    import org.junit.jupiter.api.Test;
    import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
    import redis.clients.jedis.Jedis;
    
    import java.util.Map;
    
    public class JedisTest {
    
        private  Jedis jedis;
    
        //@BeforeEach注解:在每个 @Test 测试方法执行之前自动调用被标注的方法
        @BeforeEach
        void setUp() {
            // 1.创建Jedis对象,并指定id和端口,建立和redis的连接
            jedis = new Jedis("你的ip", 6379);
            // 2.连接reids的密码
            jedis.auth("200203");
            // 3.选择库,如果不选择,默认使用0号库
            jedis.select(0);
        }
    
        @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);
        }
    
        //@AfterEach注解:在每个 @Test 测试方法执行之后自动调用被标注的方法
        @AfterEach
        void tearDown() {
            if (jedis != null) {
                jedis.close();
            }
        }
    }
    4.1.2 Jedis连接池

    Jedis本身是线程不安全的,在多线程环境下使用会出现线程安全问题,并且频繁的创建和销毁连接会有性能损耗,因此我们推荐大家使用Jedis连接池代替Jedis的直连方式

    ①编写Jedis连接池工具类:

    package com.heima.jedis;
    
    import redis.clients.jedis.Jedis;
    import redis.clients.jedis.JedisPool;
    import redis.clients.jedis.JedisPoolConfig;
    
    public class JedisConnectionFacotry {
    
         private static final JedisPool jedisPool; //官方提供的Jedis连接池对象
    
         static {
             //配置Jedis连接池的参数
             JedisPoolConfig poolConfig = new JedisPoolConfig();
             poolConfig.setMaxTotal(8); //连接池中的允许创建的最大连接数
             poolConfig.setMaxIdle(8); //连接池中最大的空闲连接数
             poolConfig.setMinIdle(0); //连接池中最小的空闲连接数
             poolConfig.setMaxWaitMillis(2000); //当连接池中没有空闲连接可用时,最长等待多长时间来获取一个连接,ms
             //创建连接池对象,第四个参数代表客户端尝试连接到 Redis 服务器的最大等待时间
             jedisPool = new JedisPool(poolConfig,
                     "你的ip",6379,5000,"200203");
         }
    
        //从连接池中获取Jedis连接对象,每次调用这个方法的时候都从连接池中获取,用完后归还到连接池中
         public static Jedis getJedis(){
              return jedisPool.getResource();
         }
    }

    代码说明:

    • 1) JedisConnectionFacotry:工厂设计模式是实际开发中非常常用的一种设计模式,我们可以使用工厂,去降低代的耦合,比如Spring中的Bean的创建,就用到了工厂设计模式

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

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

    ②操作redis:

    package com.heima;
    
    import com.heima.jedis.JedisConnectionFacotry;
    import org.junit.jupiter.api.AfterEach;
    import org.junit.jupiter.api.BeforeEach;
    import org.junit.jupiter.api.Test;
    import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
    import redis.clients.jedis.Jedis;
    
    import java.util.Map;
    
    public class JedisTest {
    
        private  Jedis jedis;
    
        //@BeforeEach注解:在每个 @Test 测试方法执行之前自动调用被标注的方法
        @BeforeEach
        void setUp() {
            // 1.从连接池中获取Jedis对象,建立和redis的连接
            jedis = JedisConnectionFacotry.getJedis();
            // 2.选择库,如果不选择,默认使用0号库
            jedis.select(0);
        }
    
        @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);
        }
    
        //@AfterEach注解:在每个 @Test 测试方法执行之后自动调用被标注的方法
        @AfterEach
        void tearDown() {
            if (jedis != null) {
                //当我们使用了连接池后,关闭连接时其实并不是关闭连接,而是将Jedis连接还回到连接池中
                jedis.close();
            }
        }
    }

    ​​​​​ 4.1.3 Spring Data Redis
    4.1.3.1 介绍

    Spring Data Redis 是 Spring 的一部分,提供了在 Spring 应用中通过简单的配置就可以访问 Redis 服务,对 Redis 底层开发包进行了高度封装。在 Spring 项目中,可以使用Spring Data Redis来简化 Redis 操作。

    SpringData是Spring中数据操作的模块,包含对各种数据库的集成,其中对Redis的集成模块就叫做SpringDataRedis,官网地址:https://spring.io/projects/spring-data-redis

    • 提供了对不同Redis客户端的整合(Lettuce和Jedis)

    • 提供了RedisTemplate统一API来操作Redis

    • 支持Redis的发布订阅模型

    • 支持Redis哨兵和Redis集群

    • 支持基于Lettuce的响应式编程

    • 支持基于JDK、JSON、字符串、Spring对象数据的序列化及反序列化,redis底层都是以字节数组的形式存储的,只不过是编码方式不同

      • 支持序列化将各种数据变成字节或字符串,然后往redis中写

      • 支持反序列化将读到的字节变成Java中的对象或字符串

    • 支持基于Redis的JDKCollection实现

    网址:Spring Data Redis

    Spring Boot提供了对应的Starter,maven坐标:

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

    Spring Data Redis中有一个高度封装的类:RedisTemplate类,其中封装了各种对Redis的操作,并且将操作不同数据类型的API都封装到了不同的类型中:

    • ValueOperations:操作string类型数据的接口

    • SetOperations:操作set类型数据的接口

    • ZSetOperations:操作zset类型数据的接口

    • HashOperations:操作hash类型数据的接口

    • ListOperations:操作list类型数据的接口



    使用步骤:①先创建RedisTemplate类的对象②通过它调用opsForxxx()方法,获取到操作各种数据类型的对象③使用operations对象操作Redis

    4.1.3.2 快速入门

    1). 导入Spring Data Redis的maven坐标

    <?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>
            <!--spring-data-redis的起步依赖-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-redis</artifactId>
            </dependency>
            <!--common-pool:不管是Jedis还是Lucttures底层都会基于这个依赖来实现连接池-->
            <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). 配置Redis连接信息

    在application-dev.yml中添加

    spring:
      redis:
        host: 你的ip地址
        port: 6379
        password: 200203
        lettuce: # redis连接池的配置,可以配置使用lettuce还是jedis实现,如果要使用jedis的实现还需要引入jedis依赖
          pool:   # 因为Spring默认使用lettuce实现,所以不需要单独引入lettuce依赖
            max-active: 8  #最大连接
            max-idle: 8   #最大空闲连接
            min-idle: 0   #最小空闲连接
            max-wait: 100ms #连接等待时间

    操作redis:

    package com.heima;
    
    import com.heima.redis.pojo.User;
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.data.redis.core.RedisTemplate;
    
    @SpringBootTest
    class RedisDemoApplicationTests {
    
        //注入RedisTemplate对象
        @Autowired
        private RedisTemplate redisTemplate;
    
        @Test
        void testString() {
            // 写入一条String数据
            redisTemplate.opsForValue().set("name", "虎哥");
            // 获取string数据
            Object name = redisTemplate.opsForValue().get("name");
            System.out.println("name = " + name);
        }
        
        @Test
        void testSaveUser() {
            // 写入数据
            redisTemplate.opsForValue().set("user:100", new User("虎哥", 21));
            // 获取数据
            User o = (User) redisTemplate.opsForValue().get("user:100");
            System.out.println("o = " + o);
        }
    }
    
    4.1.3.3 .SpringDataRedis的序列化方式

    执行6.1.3的测试代码可以发现

    RedisTemplate可以接收任意类型(Object)的数据作为值写入到Redis中,只不过在写入redis前会把其序列化为redis可以处理的字节,而SpringDataRedis默认采用的是JDK序列化方式,而JDK序列化方式序列化数据后得到的结果是这样的:

    JDK序列化方式的缺点:

    • 可读性差

    • 内存占用较大

    所以我们需要修改设置一下RedisTemplate的序列化方式,代码如下:

    package com.heima.redis.config;
    
    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.GenericJackson2JsonRedisSerializer;
    import org.springframework.data.redis.serializer.RedisSerializer;
    
    @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的序列化器为JSON序列化方式
            template.setValueSerializer(jsonRedisSerializer);
            template.setHashValueSerializer(jsonRedisSerializer);
            // 返回
            return template;
        }
    }

    再次执行执行6.1.3的测试代码可以发现,采用了JSON序列化来代替默认的JDK序列化方式。最终结果如图:

    整体可读性有了很大提升

    • 存的时候能将Java对象自动序列化为JSON字符串

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

    4.1.3.4 StringRedisTemplate

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

    • JSON序列化器为了在反序列化时知道对象的类型,会将类的class类型写入json结果中,存入Redis,会带来额外的内存开销。

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

    因为这种用法比较普遍,所以SpringDataRedis提供了RedisTemplate的子类:StringRedisTemplate,其key和value的序列化方式默认都是使用String序列化方式。

    省去了我们自定义RedisTemplate的序列化方式的步骤,而是直接使用:

    package com.heima;
    
    import com.fasterxml.jackson.core.JsonProcessingException;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.heima.redis.pojo.User;
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.core.StringRedisTemplate;
    
    @SpringBootTest
    class RedisStringTests {
    
        @Autowired
        private StringRedisTemplate stringRedisTemplate;
        private static final ObjectMapper mapper = new ObjectMapper();
    
        @Test
        void testString() {
            // 写入一条String数据
            stringRedisTemplate.opsForValue().set("name", "虎哥");
            // 获取string数据
            Object name = stringRedisTemplate.opsForValue().get("name");
            System.out.println("name = " + name);
        }
    
        @Test
        void testSaveUser() throws JsonProcessingException {
            User user = new User("虎哥", 21);
            //手动将java对象序列化为json字符串
            String json = mapper.writeValueAsString(user);
            // 写入数据
            stringRedisTemplate.opsForValue().set("user:100", json);
            // 获取数据
            String jsonUser =  stringRedisTemplate.opsForValue().get("user:100");
            //手动将json字符串反序列化为java对象
            User user1 = mapper.readValue(jsonUser, User.class);
            System.out.println("user1 = " + user1);
        }
    }

    此时再来看一看存储的数据,就会发现那个class数据已经不在了,节约了我们的空间~

    获取操作其他数据类型对象的演示

    在test下新建测试类

    package com.sky.test;
    
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.data.redis.core.*;
    
    @SpringBootTest
    public class SpringDataRedisTest {
        @Autowired
        private RedisTemplate redisTemplate;
    
        @Test
        public void testRedisTemplate(){
            System.out.println(redisTemplate);
            //获取操作string数据类型的对象
            ValueOperations valueOperations = redisTemplate.opsForValue();
            //操作hash数据类型的对象
            HashOperations hashOperations = redisTemplate.opsForHash();
            //操作list数据类型的对象
            ListOperations listOperations = redisTemplate.opsForList();
            //set类型数据操作对象
            SetOperations setOperations = redisTemplate.opsForSet();
            //zset类型数据操作对象
            ZSetOperations zSetOperations = redisTemplate.opsForZSet();
        }
    }

    测试:

    说明RedisTemplate对象注入成功,并且通过该RedisTemplate对象获取操作5种数据类型相关对象。

    上述环境搭建完毕后,接下来,我们就来具体对常见5种数据类型进行操作。

    操作常见类型数据演示

    1). 操作字符串类型数据

    	/**
         * 操作字符串类型的数据
         */
        @Test
        public void testString(){
            // set get setex setnx
            redisTemplate.opsForValue().set("name","小明");
            String city = (String) redisTemplate.opsForValue().get("name");
            System.out.println(city);
            redisTemplate.opsForValue().set("code","1234",3, TimeUnit.MINUTES);
            redisTemplate.opsForValue().setIfAbsent("lock","1");
            redisTemplate.opsForValue().setIfAbsent("lock","2");
        }

    2). 操作哈希类型数据

    	/**
         * 操作哈希类型的数据
         */
        @Test
        public void testHash(){
            //hset hget hdel hkeys hvals
            HashOperations hashOperations = redisTemplate.opsForHash();
    
            hashOperations.put("100","name","tom");
            hashOperations.put("100","age","20");
    
            String name = (String) hashOperations.get("100", "name");
            System.out.println(name);
    
            Set keys = hashOperations.keys("100");
            System.out.println(keys);
    
            List values = hashOperations.values("100");
            System.out.println(values);
    
            hashOperations.delete("100","age");
        }

    3). 操作列表类型数据

    	/**
         * 操作列表类型的数据
         */
        @Test
        public void testList(){
            //lpush lrange rpop llen
            ListOperations listOperations = redisTemplate.opsForList();
    
            listOperations.leftPushAll("mylist","a","b","c");
            listOperations.leftPush("mylist","d");
    
            List mylist = listOperations.range("mylist", 0, -1);
            System.out.println(mylist);
    
            listOperations.rightPop("mylist");
    
            Long size = listOperations.size("mylist");
            System.out.println(size);
        }

    4). 操作集合类型数据

    	/**
         * 操作集合类型的数据
         */
        @Test
        public void testSet(){
            //sadd smembers scard sinter sunion srem
            SetOperations setOperations = redisTemplate.opsForSet();
    
            setOperations.add("set1","a","b","c","d");
            setOperations.add("set2","a","b","x","y");
    
            Set members = setOperations.members("set1");
            System.out.println(members);
    
            Long size = setOperations.size("set1");
            System.out.println(size);
    
            Set intersect = setOperations.intersect("set1", "set2");
            System.out.println(intersect);
    
            Set union = setOperations.union("set1", "set2");
            System.out.println(union);
    
            setOperations.remove("set1","a","b");
        }

    5). 操作有序集合类型数据

    	/**
         * 操作有序集合类型的数据
         */
        @Test
        public void testZset(){
            //zadd zrange zincrby zrem
            ZSetOperations zSetOperations = redisTemplate.opsForZSet();
    
            zSetOperations.add("zset1","a",10);
            zSetOperations.add("zset1","b",12);
            zSetOperations.add("zset1","c",9);
    
            Set zset1 = zSetOperations.range("zset1", 0, -1);
            System.out.println(zset1);
    
            zSetOperations.incrementScore("zset1","c",10);
    
            zSetOperations.remove("zset1","a","b");
        }

    6). 通用命令操作

    	/**
         * 通用命令操作
         */
        @Test
        public void testCommon(){
            //keys exists type del
            Set keys = redisTemplate.keys("*");
            System.out.println(keys);
    
            Boolean name = redisTemplate.hasKey("name");
            Boolean set1 = redisTemplate.hasKey("set1");
    
            for (Object key : keys) {
                DataType type = redisTemplate.type(key);
                System.out.println(type.name());
            }
    
            redisTemplate.delete("mylist");
        }

    Logo

    AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

    更多推荐