Redis相关内容

一、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. 查询的时候应该先查询缓存,如果缓存不存在,在查询数据库
  2. 修改缓存数据时,应先修改数据库,后修改缓存。

 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 编写测试代码

4.3 流程图

五、Redis常见面试题12

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值