一、Redis基本概念
1.1、Redis的概念
Redis是以Key-Value形式进行存储的非关系型数据库(NoSQL)。
Redis平时操作的数据都是放在内存中的,因此读写效率高,因此我们在项目开发时,经常将Redis当成缓存工具使用(在一些框架中还把Redis当做数据存储工具)。注意:Redis只是一个缓存工具,它并不能替代真正的MYSQL数据库。
Redis以slot(槽)作为数据的存储单元,每个槽中可以存储N多个键值对。Redis中固定具有16384。理论上可以实现一个槽是一个Redis。每个向Redis存储数据的key都会进行crc16算法得出一个值后对16384取余就是这个key存放的slot位置。
同时通过Redis Sentinel(哨兵)提供高可用,通过Redis Cluster(集群)提供自动分区。
高可用:只要是集群或者是多主机中没有全部宕机,那这个服务就始终可用(即可写也可读)。
1.2、使用Redis作为缓存工具的流程
1、是边路缓存思想中的一部分
2、开发步骤(思想),以查询为例:
(1)应用程序(前端)发起查询请求,后端先从Redis(内存)中查询数据
(2)Redis会判断前端传入的Key是否存在
(3)若存在,Redis把结果查询出来,并返回给应用程序
(4)若不存在,去MYSQL(磁盘)中查询数据,将结果返回给应用程序,同时将查询的结果以及Key缓存到Redis中。
1.3、Redis数据类型(面试)
Redis中数据是以key-value形式进行存储。不同类型的value是有不同的命令进行操作的。key和value都支持下面类型(在代码中多把key设置成String类型):
(1)String 字符串 (2)Hash 哈希表 (3)List 列表 (4)Set 集合
(5)zset(Sorted set)有序集合 (6)Stream 流(Redis5之后才有)
1.4 Redis持久化策略(面试重点)
Redis不仅仅是一个内存型数据库,还具备持久化功能。(持久化是指,将内存中的数据保存到磁盘中)
Redis每次启动时都会从硬盘存储文件中把数据读取到内存中,运行过程中操作的数据都是内存中的数据。
一共有两种持久化策略:RDB 和 AOF
1.4.1 RDB
rdb模式是默认模式。可以在指定的时间间隔内生成数据快照(定时的、有周期的生成数据快照),默认保存到drump.rdb文件中。当redis重启会自动加载drump.rdb文件中的内容到内存中。
用户可以使用SAVE(同步)或BGSAVE(异步)手动保存数据。
可以设置服务器配置的save选项,让服务器每隔一段时间自动执行一次BGSAVE命令。例如:save 900 1 表示服务器在900秒之内只要有一个key发生了变化,就要对对drump.rdb进行了一次修改。
rdb模式说白了就是将内存中的数据(redis中的数据)原封不动的保存到硬盘中(drump.rdb);由于读写时不需要做任何数据转换操作,因此读写效率要高于AOF。
利用同步方案的时候,redis在进行持久化的(把内存中的数据打成快照,并通过I/O流写到磁盘)过程中,redis拒绝对外提供任何的写服务,所有的服务都给你拒绝掉。BGSAVE(异步)方案,是将内存中的数据先克隆一份,对克隆的这一份数据进行I/O操作。而老数据正常对外提供读写操作。
优点 :
(1)rdb文件是一个紧凑文件,直接使用rdb文件就可以还原数据。
(2)数据保存会由一个子进程进行保存,不影响父进程做其他事情
(3)恢复数据的效率要高于aof
总:性能要高于aof
缺点:
(1)每次保存底单之间导致redis不可意料的关闭,可能会丢失数据。(比如我们设置save 900 1,表示1s内如果有900个key发生变化,就会进行一次数据持久化;但是如果这1s内仅仅有899个key发生了变化,redis就不会去做持久化。若这1s中,由于某种原因redis关闭了,那么这899个数据在硬盘的drump.rdb文件中就丢失了[没有持久化])
(2)由于每次保存数据都需要fork()子进程,在数据量比较大是可能会比较耗费性能。
1.4.2 AOF
AOF默认是关闭的(appendonly no),需要在配置文件redis.conf中开启AOF。
AOF原理:监听执行的命令,如果发现执行了修改数据的操作,同时直接同步到数据库文件中,同时会把命令记录到日志中。由于日志文件中已经记录命令,所有即时redis突然关闭,下次启动时也可以按照日志进行恢复数据。(即每次修改数据操作,内存(redis数据库中的数据)和硬盘数据(mysql数据库中的数据)是实时同步的)
RDB是将内存数据的快照存入到硬盘中(drump.rdb文件中),而AOF是将redis中运行的命令记录到磁盘中(appendonly.aof),只在磁盘中记录一些写操作(如:增加、删除、修改操作)。当我们重启redis时,它会从appendonly.aof文件中逐条读取这些写操作,并逐条运行这些写操作,因此效率上比RDB要慢。
优点:
相对于RBD数据更安全。
缺点:
相同数据集AOF要大于RDB。
相对RDB可能会慢一些。
1.5 主从复制(面试)
1.5.1 优点
rdis主从负责的目的是为了提高安全性。 当主机发生异常宕机或者物理损坏时,由于存在副本(从结点),可以保证数据不会丢失。
redis主从复制提高了redis的读服务的并发能力。所有外部访问连接主机做写操作,然后主机自动将数据备份到各从机中,备份完成之后,主机和从机均可以被外部进行读操作。
(1)增加单一节点的健壮性,从而提升整个集群的稳定性。(Redis中当超过1/2节点不可用时,整个集群不可用)
(2)从节点可以对主节点数据备份,提升容灾能力。
(3)读写分离。在redis主从中,主节点一般用作写(具备读的能力),从节点只能读,利用这个特性实现读写分离,写用主,读用从。
1.5.2 基于Docker一主多从搭建
1、创建并启动三个redis容器。三个容器分别占用系统的6479、6480、6481端口。其中让6479对应的容器作为主机,剩下两个作为从机。
docker run --name redis1 -p 6479:6379 -v /opt/redis:/data -d redis:6.2.6
docker run --name redis2 -p 6480:6379 -v /opt/redis:/data -d redis:6.2.6
docker run --name redis3 -p 6481:6379 -v /opt/redis:/data -d redis:6.2.6
2、在从机中指定主机的端口
(1)设定redis1容器为主机(master)。redis2和redis3容器为从机(slave)
(2)进入redis2容器内部设置主的ip和端口,连接从机,指定主机端口。
docker exec -it redis2 redis-cli
slaveof 192.168.210.129 6479
exit
(3) 进入redis3容器内部设置主的ip和端口
docker exec -it redis3 redis-cli
slaveof 192.168.210.129 6479
exit
3、测试主从效果(在主机中有读写命令,但是在从机中只要读命令,没有写命令)
(1)进入redis1容器内部,新增key-value
docker exec -it redis1 redis-cli
set name "bjsxt"
exit
(2)分别进入redis2和redis3容器,查看是否有name键
docker exec -it redis2 redis-cli
get name
1.6 哨兵 (Sentinel)
1.6.1 简介
在redis主从默认只有主具备写的能力,而从只能读。如果主宕机,整个节点不具备写能力。但是如果这时让一个从变成主,整个节点就可以继续工作。即使之前的主恢复过来也当做这个节点的从即可。
Redis的哨兵就是帮助监控整个节点的,当节点主宕机等情况下,帮助重新选取主。
Redis中哨兵支持单哨兵和多哨兵。单哨兵是只要这个哨兵发现master宕机了,就直接选取另一个master。而多哨兵是根据我们设定,达到一定数量哨兵认为master宕机后才会进行重新选取
注意:集群中是没有哨兵的,哨兵是曾经的单节点高可用的一种搭建方式。目前我们用的都是集群 (因此集群的学习才是重点)
哨兵会不断的利用ping命令去访问(查看)各主从机是否还存活,若是哨兵发现主机(假如是s0)宕机了(ping不通了),它就会从从机中随意挑选一个作为主机(假如被选的是s1),若之前那个主机(s0)可以连接了,就让它(s0)降级为从机。
1.6.2 脑裂问题 (面试)
在单哨兵模式下,哨兵(Sentinel)由于自身的原因,连接主机失败了,它就认为主机(S0)宕机了,但是外部的客户端和主机(S0)仍然在连接且执行读写操作;由于哨兵认为主机(S0)已经宕机了,于是就从从机中选择了一个作为主机(S1),后续的客户端就连接了S1,这时候就相当于有了两个主机,这种情况就称为脑裂。
由于(S0)和(S1)都与客户端有连接,因此在(S0)和(S1)上各有一份不同客户端的读写数据【 s0中是曾经未断开的客户端写入的数据--M1,s1中是s0和s1同时作为主机时,新的客户端写入的数据--M2】。当哨兵可以连接(S0)后,它就会把(S0)变为从机。S0降级为从机后,由于从机中的数据不会反向备份到主机中(即数据M1无法写入到S1中),这时数据M1就没法备份到新主机(S1)中,而(S0)变为从机后,它会从主机(S1)中备份新的数据来替换自己本身的数据,这时M1这部分数据就丢失了,这种情况就称为脑裂。
解决方案:
方式一:在配置文件中设置延迟时间,即当哨兵15s内连接不上主机后,才认为其真正的宕机了。
方式二:将哨兵和客户端用同一根网线进行连接。
1.7 集群 Cluster(面试/学习重点)
1.7.1 集群原理
(1)集群搭建完成后由集群节点(服务器)平分(不能平分时,前几个节点多一个槽)16384个槽。
(2)客户端可以访问集群中任意节点。所以在写代码时都是需要把集群中所有节点都配置上。
(3)当向集群中新增或查询一个键值对时,会对Key进行Crc16算法得出一个小于16384值,这个值就是放在哪个槽中,在判断槽在哪个节点上,然后就操作哪个节点。
上图中每个蓝色的圈就是一个节点,它们组成了一个集群,每个节点中有16384/5个槽。当这5个节点都在集群中,如何判断某节点真的宕机了,如何让它的备份机变成主节点?---> 需要集群中其它4个节点去投票,当有大于或等于1/2的节点发现该节点宕机时(ping不通了),那么就认为该节点真的宕机了。
集群:集群中所有节点都安装在不同服务器上。
伪集群:所有节点都安装在一台服务器上,通过不同端口号进行区分不同节点。
当集群中超过或等于1/2节点不可用时,整个集群不可用。为了搭建稳定集群,都采用奇数节点。
Redis每个节点都支持一主多从。会有哨兵监控主的状态。如果出现(配置文件中配置当多少个哨兵认为主失败时)哨兵发现主不可用时会进行投票,投票选举一个从当作主,如果后期主恢复了,主当作从加入节点。在搭建redis集群时,内置哨兵策略。
演示时:创建3个节点,每个节点搭建一主一从。一共需要有6个redis。
1.7.2 Redis集群安装步骤
1.7.2.1 新建配置模板文件
# 默认文件的位置是任意的
cd /usr/local
mkdir redis-cluster
cd redis-cluster
vim redis-cluster.tmpl
模版文件(redis-cluster.tmpl)编写的内容如下:
# ${PORT}表示从外部传变量,当传入的变量名称为PORT时,值就可以被引入进来。
# port表示端口号
port ${PORT}
# 保护模式是开启状态
protected-mode no
#开启集群版配置模式
cluster-enabled yes
# 集群的配置文件名称为 nodes.conf
cluster-config-file nodes.conf
# 集群的每个节点连接超时时间
cluster-node-timeout 5000
# 集群的每个节点的ip地址
cluster-announce-ip 192.168.210.129 #自己的linux虚拟机IP
# 集群的每个节点的端口号
cluster-announce-port ${PORT}
# 集群中每个节点的关联端口 1${PORT} -- 如果我们传入的端口号为80,那么这就是 180
cluster-announce-bus-port 1${PORT}
appendonly no #yes或者no都可以,它就是要不要用AOF持久化策略
1.7.2.2 使用Shell脚本创建6个目录
在我们编写的配置文件所在目录下运行即可。
for port in `seq 7000 7005`; do \
mkdir -p ./${port}/conf \
&& PORT=${port} envsubst < ./redis-cluster.tmpl > ./${port}/conf/redis.conf \
&& mkdir -p ./${port}/data; \
done
结果如图:
每个目录中的内容,与配置文件设置的匹配:
1.7.2.3 创建桥接网络(网卡)
创建网卡的目的:在后序逻辑中,我们创建的所有的redis集群节点都用这个网卡。同一块网卡相互连接的时候,可以直接找对方。
注意:在运行docker命令之前,要先启动docker服务器,否则会报:Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?错误。启动docker命令如下:
sudo systemctl start docker
创建网卡
docker network create redis-net # 名字任意
查看网络是否能够创建成功
docker network ls
1.7.2.4 创建并启动6个容器
for port in `seq 7000 7005`; do \
docker run -d -ti -p ${port}:${port} -p 1${port}:1${port} \
-v /usr/local/redis-cluster/${port}/conf/redis.conf:/usr/local/etc/redis/redis.conf \
-v /usr/local/redis-cluster/${port}/data:/data \
--restart no --name redis-${port} --net redis-net \
--sysctl net.core.somaxconn=1024 redis:6.2.6 redis-server /usr/local/etc/redis/redis.conf; \
done
1.7.2.5 查看6个容器ip及端口
inspect:检查容器或镜像的详细信息,详细信息中包含ip和端口
docker inspect redis-7000 redis-7001 redis-7002 redis-7003 redis-7004 redis-7005 | grep IPAddress
1.7.2.6 执行集群脚本
进入6个容器中任意一个。示例中以redis-7000举例
docker exec -it redis-7000 bash
执行创建脚本命令。 --cluster-relicas 1表示每个主有1个从。 --cluster-relicas 2表示每个主机有2个从机(6个容器时,就是2个主机4个从机)
# 这个一定要与你上一步创建的容器查询出来的ip一致,不要随便复制,每一个人的可能都不一样
redis-cli --cluster create \
172.18.0.2:7000 \
172.18.0.3:7001 \
172.18.0.4:7002 \
172.18.0.5:7003 \
172.18.0.6:7004 \
172.18.0.7:7005 \
输入后给出集群信息,输入yes后创建集群
1.7.2.7 验证集群
在任意Redis容器内部,进入Redis客户端工具。示例中还是以Redis-7000举例。
redis-cli -c -p 7000
1.8 缓存穿透(面试)
在实际开发中,添加缓存工具的目的,减少对数据库的访问次数,增加访问效率。
肯定会出现Redis中不存在的缓存数据。例如:访问id=-1的数据。可能出现绕过redis依然频繁访问数据库的情况,称为缓存穿透,多出现在数据库查询为null的情况不被缓存时。
解决办法:
如果查询出来为null数据,把null数据依然放入到redis缓存中,同时设置这个key的有效时间比正常有效时间更短一些。
if(list==null){
// key value 有效时间 时间单位
redisTemplate.opsForValue().set(navKey,null,10, TimeUnit.MINUTES);
}else{
redisTemplate.opsForValue().set(navKey,result,7,TimeUnit.DAYS);
}
1.9 缓存击穿(面试)
实际开发中,考虑redis所在服务器中内存压力,都会设置key的有效时间。一定会出现键值对过期的情况。如果正好key过期了,此时出现大量并发访问,这些访问都会去访问数据库,这种情况称为缓存击穿。
解决办法:
永久数据。
加锁。防止出现数据库的并发访问。
1.9.1 ReentrantLock(重入锁)
JDK对于并发访问处理的内容都放入了java.util.concurrent中。
ReentrantLock性能和synchronized没有区别的,但是API使用起来更加方便。
@SpringBootTest
public class MyTest {
@Test
public void test(){
new Thread(){
@Override
public void run() {
test2("第一个线程111111");
}
}.start();
new Thread(){
@Override
public void run() {
test2("第二个线程222222");
}
}.start();
try {
Thread.sleep(20000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
ReentrantLock lock = new ReentrantLock();
public void test2(String who){
lock.lock();
if(lock.isLocked()) {
System.out.println("开始执行:" + who);
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("执行完:" + who);
lock.unlock();
}
}
}
1.9.2 解决缓存穿透实例代码
只有在第一次访问时和Key过期时才会访问数据库。对于性能来说没有过大影响,因为平时都是直接访问redis。
private ReentrantLock lock = new ReentrantLock();
@Override
public Item selectByid(Integer id) {
String key = "item:"+id;
if(redisTemplate.hasKey(key)){
return (Item) redisTemplate.opsForValue().get(key);
}
lock.lock();
if(lock.isLocked()) {
Item item = itemDubboService.selectById(id);
// 由于设置了有效时间,就可能出现缓存击穿问题
redisTemplate.opsForValue().set(key, item, 7, TimeUnit.DAYS);
lock.unlock();
return item;
}
// 如果加锁失败,为了保护数据库,直接返回null
return null;
}
1.10 缓存雪崩(面试)
在一段时间内容,出现大量缓存数据失效,这段时间内容数据库的访问频率骤增,这种情况称为缓存雪崩。
解决办法:
永久生效。
自定义算法,例如:随机有效时间。让所有key尽量避开同一时间段。
int seconds = random.nextInt(10000);
redisTemplate.opsForValue().set(key, item, 100+ seconds, TimeUnit.SECONDS);
1.11 边路缓存
cache aside pattern 边路缓存问题。其实是一种指导思想,思想中包含:
- 查询的时候应该先查询缓存,如果缓存不存在,在查询数据库
- 修改缓存数据时,应先修改数据库,后修改缓存。
1.12 Redis脑裂
Redis脑裂主要是指因为一些网络原因导致Redis Master和Redis Slave和Sentinel集群处于不同的网络分区。Sentinel连接不上Master就会重新选择Master,此时就会出现两个不同Master,好像一个大脑分裂成两个一样。
Redis集群中不同节点存储不同的数据,脑裂会导致大量数据丢失。
解决Redis脑裂只需要在Redis配置文件中配置两个参数
min-slaves-to-write 3 //连接到master的最小slave数量
min-slaves-max-lag 10 //slave连接到master的最大延迟时间
1.13 Redis 缓存淘汰策略/当内存不足时如何回收数据/保证Redis中数据不出现内存溢出情况(面试题)
1.14 Redis集群的三种方式(面试)
Redis集群的三种方式:主从复制、哨兵模式、Redis Cluster集群
- 主从复制。在这种模式下,数据从一个主服务器(master)复制到一个或多个从服务器(slave),主服务器负责处理读写请求,而从服务器通常用于处理只读请求,这种方式的优点是支持读写分离,提高了系统的可用性和可扩展性,缺点包括数据一致性问题,如果主服务器宕机,可能需要手动切换到从服务器,且在主服务器宕机期间,未及时同步到从服务器的数据可能会丢失。156
- 哨兵模式。哨兵(Sentinel)是Redis的监控工具,用于自动化系统监控和故障恢复,哨兵模式在主服务器出现故障时,可以自动将从服务器升级为主服务器,这种模式引入了哨兵节点,用于监控主从服务器的运行状态,并在主服务器故障时进行自动故障转移。17
- Redis Cluster集群。从Redis 3.0版本开始支持的集群方式,Redis Cluster采用了分布式存储的思想,将数据分布到多个节点上,每个节点可以处理读写请求,Redis Cluster使用哈希槽(hash slot)的概念来分配数据,数据通过CRC16算法计算后分配到不同的哈希槽中,每个主节点可以对应多个从节点,提高了系统的可用性和扩展性。
二、Redis在Linux上安装步骤(单击版)
1、由于redis使用C语言编写,所以需要安装C语言库
# yum install -y gcc-c++ automake autoconf libtool make tcl
2、上传并解压
三、Redis在Docker上安装步骤(单机版)
3.1 安装步骤
1、拉取镜像:
docker pull redis:6.2.6 (6.2.6指的是镜像版本)
2、创建并启动一个redis容器:
docker -d --name redis-server -p 6379:6379 --restart always redis:6.2.6
3、客户端测试(可以执行一下redis命令,比如 exists key):
方式一:
先连接到容器: (相当于在docker中打开了一个终端)
docker exec -it redis-server bash
输入redis-cli命令:(在任意目录下输入redis-cli即可进入redis命令行。)
redis-cli
方式二:直接进入redis客户端工具
docker exec -it redis redis-cli
退出容器:exit命令
exit
3.2 判断Redis是否安装成功
3.3 安装时注意事项
(1)对docker命令不熟悉的,可以进入***链接,查看有关docker的相关命令。( -it 命令表示持久的做一个伪终端 。-d表示在后台运行)
(2)--restart always表示启动Docker时自动启动此容器。(安装FastDFS时,只要我们一重启linux,我们就要看一下docker服务有没有启动,若是没有启动,需要systemctl start docker命令启动它,docker服务启动后,我们还要去启动fastdfs的两个容器tracker和storage,有点麻烦。但是我们在创建容器的时候,加入 --restart always 命令后,只要不是利用stop命令关闭的容器,再次运行linux时,它都会自动重启。但是要注意:FastDFS不能设置这一命令,因为:FastDFS没办法自动重启,因为tracker和storage在重启时,很可能受限于pid文件【进程描述文件】未删除而无法启动,但是redis没有这一限制,因此可以自动重启)
(3)在docker中安装redis,一定要先启动docker,否则会报:Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?的问题
启动docker:
systemctl start docker
(4)Redis对外数据库读写操作的时候的访问端口默认是6379,就像mysql默认是3306
(5)redis-cli命令就是连接到redis服务器的客户端命令,有点类似于我们在windows中写的那个mysql -u 用户名 -p 密码连接到mysql数据库
四、Spring Boot 整合 Spring Data Redis
4.1 Spring Data简介
Spring Data是Spring公司的顶级项目,里面包含了N多个二级子项目,这些子项目都是相对独立的项目。每个子项目都是对不同API的封装。
所有的Spring Boot整合Spring Data XXX 的启动器都叫做spring-boot-starter-data-xxxx,Spring Data好处是很方便操作对象类型(基于POJO模型)。
只要是Spring Data的子项目被Spring Boot整合后,都会有一个XXXXTemplate实例。把Redis不同值的类型放到一个opsForXXX方法中。
(1)opsForValue:String值(最常用),如果存储java对象或java集合时就需要使用序列化器,将对象或集合序列化成JSON字符串。
(2)opsForList:列表List
(3)opsForHash:哈希表Hash
(4)opsForSet:集合
(5)opsForZSet:有序集合Sorted Set
4.2 代码步骤
4.2.1 代码步骤
(1)创建一个Maven项目(名称为redis)
(2)导入相关依赖。注意依赖导入时要添加版本号,否则依赖无法导入。导入的spring-boot启动的依赖不要过高,否则会报错。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.7.5</version>
</dependency>
<!-- 这两个注解只是为了在测试类中使用 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.7.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>compile</scope>
</dependency>
</dependencies>
导入依赖过高时:(错误:(3, 32) java: 无法访问org.springframework.boot.SpringApplication 错误的类文件: D:\utility_software\MavenResp\org\springframework\boot\spring-boot\3.2.4\spring-boot-3.2.4.jar(org/springframework/boot/SpringApplication.class) 类文件具有错误的版本)
(3)编写代码,利用Spring Data实现数据的读写访问。
第一步:编写一个springboot启动器,目的是为了连接服务器。
@SpringBootApplication
public class TestSpringDataApp {
public static void main(String[] args) {
SpringApplication.run(TestSpringDataApp.class,args);
}
}
第二步:创建一个配置文件(application.yml),编写连接redis服务器所需要的相关配置。(比如端口号、ip等)
# 单机版
spring:
redis:
host: 192.168.210.129 #配置单机版Redis服务器地址。默认localhost
port: 6379 #配置单机版Redis服务器端口。默认6379
第三步:编写一个测试类(TestDataRedis)来验证客户端是否存在。运行该类中的testClient()方法,若能正确打印出结果,则证明可以找到RedisTemplate对象做注入。
@SpringBootTest(classes = {TestSpringDataApp.class})
@RunWith(SpringRunner.class)
public class TestDataRedis {
/**
* RedisTemplate 是Spring Data Redis提供的客户端对象。
* 这个对象是spring-boot-starter-data-redis构建的。我们直接使用即可
* 默认创建的客户端泛型是RedisTemplate<Object,Object>。
* 代表,当前客户端对象在访问Redis的时候,对key的类型约束是Object,对value的类型约束也是Object
*
* 暂时不能修改成其它的(比如RedisTemplate<String,Object>),否则会报错,
* 需要后面去做一些配置。
* */
@Autowired
private RedisTemplate<Object,Object> redisTemplate;
@Test
public void testClient(){
System.out.println("redisTemplate:"+redisTemplate);
}
}
第四步:利用Spring Data Redis做读写访问。在TestDataRedis中编写testSet()方法,测试读写操作。
// 新增字符串数据
@Test
public void testSet(){
//向redis中写入数据
redisTemplate.opsForValue().set("data-k1","data-v1");
//根据key值从redis中读入数据
Object value = redisTemplate.opsForValue().get("data-k1");
System.out.println(value);
}
运行后在控制台输出的结果为:null
原因是:我们保存的时候 用的是jdk序列化器进行存储【存储到redis数据库中的key和value已经不在是“data-k1”和“data-v1”了,而是进行了转换,变成了“\xac\xed\x00\x05t\x00\adata-k1”和“\xac\xed\x00\x05t\x00\adata-v1”】,但是利用redisTemplate.opsForValue().get("data-k1")进行查询的时候,是按照String序列化进行的查询,因此会找不到。如何解决:写一个configuration类,改写序列化器。
运行后如果报:Unable to connect to Redis; nested exception is io.lettuce.core.RedisConnectionException: Unable to connect to localhost:6379;这个错误,说明应用程序尝试连接到Redis服务器失败了。
可能得原因:
(1)Redis服务器未启动。若为启动,启动Redis服务器。
docker exec -it redis-server redis-cli
(2)检查防火墙是否关闭了,linux和windows的都要关闭。
systemctl stop firewalld
(3)我们配置的yml文件有问题
4.2.2 存在的问题 以及解决
虽然目前数据能够存储到redis数据库中了,但是在存储的时候出现了几个问题。问题如下:
(1)存储到redis缓存中的key-value值与我们本是编写的不太一样,这是由于字符串的序列化导致的。
(2)为了解决这二问题(一:key、vaule名字问题,二:我们上面设置的RedisTemplate的泛型只能是Object问题),我们可以自定义一个序列化工具(编写一个configuration类--MyDataRedisConfiguration),让RedisTemplate 按照我们自定义的序列化工具进行序列化。
@Configuration
public class MyDataRedisConfiguration {
/**
* RedisTemplate 只是客户端模板对象,不负责连接到(redis)服务器。
* 连接到服务器的是连接工厂对象 RedisConnectionFactory
* 连接工厂对象由spring-boot-starter-data-redis自动创建。
* 因此我们改变RedisTemplate的创建是,要传入RedisConnectionFactory,告诉spring容器
* 之后利用我们自己创建的序列化工具对key-value进行序列化。
* */
@Bean
@ConditionalOnSingleCandidate
public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory factory){
RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(factory);
// 设置key的序列化器
redisTemplate.setKeySerializer(new StringRedisSerializer());
/**
* GenericJackson2JsonRedisSerializer() 是基于jackson提供的一个JSON格式字符串序列化器。
* (因此我们需要在pom.xml中添加有关jackson的依赖)
* 在redis中的存储形式是:{"@class":"包名.类名","属性名":"属性值"}
* */
// 设置value的序列化器
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
return redisTemplate;
}
}
此时再次运行测试类中的方法,可能会报如下错误:(就是spring没有给你自动注入这个对象)
解决方案:
把spring boot的依赖降到2.7.0以下,你会神奇的发现,报红线的那里没有了
如果是通过 @Autowired 注入,发现 redisTemplate 报错,那就使用 @Resource
如果你不想降低版本,那你就跟我一样加一个 @ConditionalOnSingleCandidate 注解。(在configuration类的方法上添加注解)
4.2.3 整体流程
4.2.3.1 添加依赖(pom.xml中)
4.2.3.2 配置配置文件(application.yml)
4.2.3.3 编写配置类(RedisConfig)--- 自定义key-value的序列化
4.2.3.4 编写测试代码