redis进阶--IDEA环境

目录

一、解决redis服务器端口问题

二、java环境下使用redis

三、javaSpringt环境下使用redis

四、redis持久化

1、持久化概念

2、redis持久化策略

3、RDB策略

4、AOF策略

5、混合持久化策略

五、redis事务

1、数据库事务

2、redis事务特点

3、redis事务的作用

4、redis事务操作

六、主从复制

1、主从模式

2、主从复制

七、哨兵

1、哨兵的作用

2、哨兵原理

3、基于docker搭建redis哨兵环境

4、查看哨兵效果

八、集群

1、集群概念

2、集群的作用

3、集群原理

4、数据分片算法

5、基于docker搭建redis集群

6、集群效果

7、集群扩容

九、redis典型应用--缓存

1、实现过程

2、热点数据

3、缓存淘汰策略

4、缓存存在的问题

十、redis典型应用--分布式锁

1、分布式锁的作用

2、分布式锁的原理

3、分布式锁存在的问题

一、解决redis服务器端口问题

redis客户端在windows环境下(IDEA),redis在linux云服务下,若客户端想访问服务器,首先需要在云服务器外网IP下访问,其次还需要能访问到服务器端口。修改IP后,如何访问到服务器端口???redis的服务器端口默认是被云服务器的防火墙保护着的,即使在外网ip下也是不能访问的。那需要将redis服务器的端口号像tomcat一样,设成开放形式吗???这种做法是不可行的,tomcat即使设成开放形式,黑客也不容易入侵,但若redis设成开放形式,是很容易被黑客入侵的。那如何在不开放端口的情况下,访问到redis服务器???

解决方法:

1、将java代码打包成可执行的jar包,直接在linux服务器上运行。

2、配置ssh端口转发,把云服务器的redis端口,映射到本地主机

ssh支持端口转发,可以将redis服务器端口6379映射到本地端口8888,后续可以直接访问:127.0.0.1:8888就可以访问本机的redis服务器了。

在linux云服务器上进行配置:

检查是否配置成功,输入netstat查看本地是否有8888端口:

ps:当配置了端口转发后,一定要断开连接进行重新连接。

二、java环境下使用redis

1、引入依赖

2、使用(这里就只拿String举例,语法基本和redis命令一样)

(1)get和set

(2)mget和mset

(3)incr和decr

三、javaSpringt环境下使用redis

1、配置文件,连接redis

2、选择redis

3、使用

springboot对redis不同类型进行了封装,可以使用不同类型下的一些方法。

四、redis持久化

1、持久化概念

持久化是指将数据保存在持久存储介质(如硬盘、数据库等),保证数据的长期存储,以便在需要时能够重新读取和处理数据。

2、redis持久化策略

redis将数据放在内存中,内存的特点是访问速度快,但是不能长期存储数据,当计算机关闭或重启时,内存中的数据就会被清空,如何使得redis能持久化存储数据???redis采用RDB和AOF两种策略,将数据同步到硬盘中,当redis重启时,用来恢复数据,使得redis具有持久化。

3、RDB策略

(1)实现原理

触发RDB持久化后,将redis内存中的所有数据生成快照(RDB二进制文件),保存到硬盘。

(2)触发RDB持久化

①手动触发

在redis客户端,执行特定的命令,触发rdb持久化,生成快照。

save命令:阻塞当前redis服务器,无法处理客户端的其他命令,全力以赴的进行快照生成操作。

bgsave命令:redis进程执行fork操作创建子进程,由子进程负责进行快照生成操作,完成后自动结束,redis服务器阻塞只发生在fork阶段。

bgsave执行流程:

当执行bgsave命令时,redis父进程先判断当前是否存在其他正在执行的子进程,如RDB/AOF子进程,如果存在bgsave直接返回;如果没有父进程执行fork操作创建子进程,子进程根据父进程内存数据生成临时快照文件,完成后对原有RDB文件进行替换,并且该进程发送信号给父进程表示完成,父进程更新统计信息。

举例:

此时就会手动写入rdb文件中。rdb文件保存在配置指定的目录,默认为(/var/lib/redis)下,文件名默认为dump.rdb。

假如没有手动写入硬盘,也没有达到自动触发的条件,若重新启动redis服务器,此时会获取到之前的数据吗???

若是通过正常流程启动redis服务器,此时redis在退出的时候会自动触发生成rdb文件;但若是异常重启(kill-9或者服务器掉电),此时redis来不及生成rdb文件,内存中的尚未保存到快照中的数据就会随着重启而丢失。

正常关闭:

异常关闭:

②自动触发

redis也可以自动触发rdb持久化机制。

使用save配置,如:"save m n "表示m秒内数据集发生了n次修改,自动触发rdb持久化机制;

正常关闭redis服务器时,也会自动触发rdb持久化机制;

redis进行主从复制时,主节点也会自动生成rdb快照,然后将rdb快照文件内容传输给从节点。

以上可以改变自动触发条件。

ps:如果修改了rdb文件内容,并且通过kill -9 关闭进程,则很可能重新启动redis时失败。当rdb文件损坏时,可以使用redis提供的redis-check-dump工具检查rdb文件并获取对应的错误报告。

(3)RDB策略的优缺点

优点:rdb是二进制文件,加载rdb恢复数据较快。

缺点:rdb策略不能实时更新数据到rdb文件,若此时redis服务器挂了,且内存中最新的数据还未更新到rdb文件中,此时这部分数据就丢失了;创建子进程,进行全量数据保存,属于重量级操作,频繁执行成本过高。

4、AOF策略

(1)实现原理

AOF是实时进行持久化的,重启redis时执行AOF文件中的命令达到恢复数据的目的。

所有的写入命令会追加到aof缓存区中,根据缓存区的刷新策略将数据更新到硬盘中(AOF文件),随着AOF文件越来越大,需要定期对AOF文件进行重写,达到压缩目的。当redis服务器进行重启时,可以加载AOF文件进行数据恢复。

(2)AOF文件

首先需要开启aof,当开启aof时,rdb就不再生效了,redis服务器重启时,就不读取rdb文件了。

进入配置文件开启aof:

写入键值对,查看aof文件(路径和rdb一样):

(3)刷新策略

前面提到所有的写入命令会先放到aof缓冲区中,根据刷新策略再将数据更新到硬盘中,从而减少了操作硬盘的次数,提高效率。

刷新策略在配置文件中:

always:命令写入缓冲区就向硬盘中写入,写入频率最高,数据可靠性最高,性能最低;

everysec:同步线程每秒刷新一次缓冲区的数据到硬盘中,写入频率较低,数据可靠性较低,性能较高;

no:redis不会主动刷新缓冲区数据到硬盘中,而是直接交给操作系统去判断,写入频率最低,数据可靠性最低,性能最高。

(4)AOF文件重写

前面提到,当aof文件越来越大时,需要定期对aof文件进行重写,达到压缩的目的。

例如:set key1 111; set key2 222; set key1 222;

最后的结果应该只有两条,但如果不重写,aof会保存3条数据,此时就要求要定期对aof文件进行重写,压缩文件。

aof文件重写原理:

ps:fork之后的写入命令还需要写到缓冲区和旧aof文件,是为了防止新aof文件突然挂了,此时aof文件就没有fork之后的数据了,数据不准确。

(5)AOF策略优缺点

优点:aof策略是实时更新数据到aof文件,数据可靠性高;

缺点:aof文件是文本文件,加载aof恢复数据较慢。

5、混合持久化策略

redis结合了aof和rdb的特点,引入了混合持久化策略,每一个写入命令按照aof的方式(文本)写入文件,当触发重写时,将缓冲区所有内容以rdb(二进制)写入新文件中,使用新文件替换旧文件。后续进行写入命令时,还是按照aof方式写入文件。

五、redis事务

1、数据库事务

了解redis事务前,先回顾数据库事务。

数据库事务的概念:一组操作,要么全部成功,要么全部失败。

数据库事务的特点:①原子性;②一致性;③持久性;④隔离性;

2、redis事务特点

redis事务和数据库事务一样,都是对一组操作进行打包完成。

特点(数据库事务对比):①弱化的原子性;redis没有回滚机制,一组操作中若有一个命令执行失败,这一组操作还是会继续执行,不要求全部失败;②不保证一致性;redis不涉及约束,不保证结果都是合理有效的;③不需要隔离性;redis是单线程处理请求;④不具备持久性;redis数据存储在内存中不具有持久性,aof和rdb持久策略和事务无关。

3、redis事务的作用

当面对不同的redis客户端时,保证服务器对同一个客户端的一组操作全部执行完毕后,再执行下一个客户端的命令。

4、redis事务操作

(1)multi

开启一个事务,执行成功返回ok。后面的每个命令会先放在客户端队列里。

(2)exec

执行一个事务。

(3)discard

放弃当前事务,清空队列。

(4)watch

在执行事务时,假设客户端1先修改了key1的值为100,然后客户端2修改了key1的值为200,按照时间先后顺序,key1的值应为200,但若exec命令是在客户端2之后,此时key1的值就为100了,此时这种情况就会造成歧义。

使用watch命令监督某个key,若在执行事务时,其他客户端修改了该key,真正提交事务时服务器会发现监督的key版本号已经超过了事务开始时的版本号,就会让事务执行失败,所有命令都不执行。

(5)unwatch

取消对key的监控。

六、主从复制

1、主从模式

(1)作用

主从模式是分布式系统中的一种模式。当只有一个服务器来存储redis数据时,此时若该服务器挂了,后续客户端也无法从redis中读取数据了。在分布式系统中,希望有多个服务器来部署redis服务,构成一个redis集群。即使其中一个挂了,客户端还可以去其他服务器上读取数据,对于读操作的并发量下的可用性进行了提高。

(2)原理

主从模式中,有n个节点,其中有一个主节点,n-1个从节点。主节点可以修改数据,可以读数据;从节点只能读数据;当主节点数据发生变化时,从节点数据也需要一起变化;从节点挂了,不影响redis的使用;主节点挂了,此时就不能修改redis数据了。

2、主从复制

参与复制的redis服务器被划分为主节点和从节点,一个redis服务器只能有一个主节点,但可以拥有多个从节点,复制只能由主节点到从节点。

分布式系统中,每个服务器应该处于不同的云服务器上,但此时博主手里就一个云服务器上,所以就只能启动几个端口不同的redis服务器进行演示。

(1)启动三个不同端口的redis

启动3个不同的redis服务器,端口号分别为:6380、6381、6382。

端口号的设置我们在配置文件中修改,先建立一个目录,里面装3个不同端口redis的配置文件:

修改三个文件的端口号以及开启后台模式:

启动三个不同的redis:

(2)建立主从关系

以6380为主节点,6381和6382为从节点,修改从节点配置文件,建立主从关系;

同时需要修改每个节点aof所处的路径,不然会导致每个redis的aof文件是一样的:

以上三个用来放置每个redis服务器对应的aof文件。

修改aof文件的放置路径:

需注意:在修改了配置文件之后,需要重新启动redis.

如果使用命令redis-server+配置文件启动的redis,需要使用kill -p id杀掉redis进程;

如果使用命令service redis-server start这种方式启动的redis,需要使用service redis-server stop杀掉redis进程。

杀掉并重启从节点redis:

重启之后,每个redis就有自己的aof文件了。

(3)客户端与服务器之间建立连接

(4)效果演示

主节点建立key:

从节点获取key:

从节点建立key(失败):

(5)查看节点的状态

主节点:

offset:主节点会不断收到数据,从节点也要同步数据,但这个同步不是瞬间完成的,offset就记录了从节点同步数据的进度。

从节点:

(6)断开连接

主节点与从节点之间也可以断开连接。

例如:断开6380端口对应的redis连接,从节点变为主节点

(7)拓扑

redis的复制拓扑结构可以支持单层或多层复制关系,根据拓扑复杂性可以分为:一主一从、一主多从、树状主从结构。

一主一从:

一主多从:

树状主从结构:

(8)数据同步psync

redis使用psync命令完成主从数据同步,同步过程分为:全量复制和部分复制。

全量复制:一般用于初次复制场景,会把主节点数据一次性全部发送给从节点;

部分复制:由于网络原因导致主从复制数据丢失,从节点再次连接上主节点后,若缺失的数据不多,主节点会部分复制数据给从节点。

实时复制:主从建立连接后,主节点会把自己收到的修改操作,通过tcp长连接,源源不断的传输给从节点,从节点就会根据这些请求修改自身的数据,保证主从节点数据一致。同时,主从节点也都有心跳检测机制,主节点每隔10s对从节点发生ping命令,判断从节点是否存活;从节点每隔1s向主节点发送命令告诉主节点当前复制偏移量。60s后没有响应,判断从节点下线,断开连接,从节点恢复连接后,心跳机制继续进行。

psync语法格式:psync replicationid offset

replicationid:指主节点的复制id,主节点启动或者从节点晋升为主节点都会生成一个replicationid

offset:偏移量,主节点每次在进行写入命令后,其偏移量会累加;从节点每次在复制主节点的数据后,其偏移量也会累加;当主节点的偏移量与主节点的偏移量相等时,此时主从节点数据一样。

注:当replicationid默认值为?,offset的默认值为-1时,此时为全量复制;

(9)psync运行流程

①初次复制,从节点发生psync命令给主节点,replid默认值为?,offset默认值为-1;

②主节点根据psync参数和自身数据情况决定响应结果:

如果回复+FULLRESYNC,则从节点需要进行全量复制;

如果回复+CONTINEU,从节点进行部分复制流程;

如果回复-ERR,说明redis主节点版本过低,不支持psync命令;

七、哨兵

1、哨兵的作用

Redis的主从复制模式下,一旦主节点挂了,不仅需要人工进行主从切换,还需要通知客户端更换到其他节点上,这种方式效率较低,于是redis就提供了哨兵(Redis Sentinel)来解决这个问题。

2、哨兵原理

每个redis sentinel都是一个单独的进程(一般为多个,且是奇数),每个进程会和节点建立tcp长连接,定时发生心跳包。若规定时间内未得到回应,该哨兵就会认为此节点出现了故障,若此节点是主节点,其他哨兵也会来判断主节点是否是真的挂了,若哨兵达成共识认为主节点挂了,此时哨兵之间会通过raft算法选举出一个哨兵领导角色,由该哨兵负责在从节点中挑选出一个主节点,被挑选出的从节点执行slaveof no one,并且其他从节点要和新选出的主节点建立主从联系。哨兵节点也会自动通知客户端新的主节点,并且后续客户端进行写操作是写入到了新的主节点中。后续如果旧的主节点恢复了,它会成为一个从节点。

3、基于docker搭建redis哨兵环境

(1)基于ubuntu下安装docker

①安装依赖

②添加Docker官方GPG密钥

③添加Docker的软件源

④安装docker

⑤配置用户组

⑥安装工具

⑦重启docker

⑧查看版本

(2)安装docker-compose

(3)停止之前的redis服务器

(4)使用docker获取redis的镜像

(5)docker-compose进行容器编排

此时我们需要6个容器,其中3个Redis服务器(1主2从),3个哨兵节点。

可以使用yml配置文件进行批量启动,3个redis服务器为1个配置文件,3个哨兵节点为1个配置文件。

①创建两个目录,分别放redis和哨兵节点的配置

②配置redis服务器的容器

③创建服务器的容器并启动

④配置redis哨兵节点的容器

⑤创建redis节点容器并启动

此时若直接使用命令redis-compose up -d会报错,是因为哨兵节点需要监控主节点或从节点中的一个,但这两部分是分成两部分来创建的容器,处于不同的局域网中,这两部分是不能互通的,此时需要将哨兵节点加入到主从节点局域网中。

查找主从节点的局域网:

将哨兵节点加入到主从节点的局域网中:

启动哨兵节点:

4、查看哨兵效果

(1)客户端分别连接不同端口服务器并查看服务器角色

(2)模仿主节点服务器宕机 观察其他从节点

其他从节点中的一个被改为主节点。此时,即使原本的主节点恢复正常,它也依旧是主节点,原本的主节点被改为从节点。

八、集群

1、集群概念

(1)广义的集群

指多个机器构成了分布式系统。

(2)狭义的集群

redis提供的集群模式指狭义的集群,主要是拓展节点的存储空间,解决存储空间不足的问题。

2、集群的作用

虽然redis在主从复制中可以拥有多个服务器支持读请求,但是此时每个节点中存储的依旧是全量数据,若数据过多,存储空间会不足,此时就需要多个机器存储数据,每个机器存储部分数据,且每个机器又会搭配一组从节点和哨兵节点。

3、集群原理

每个红框部分可以称为一个分片。

4、数据分片算法

对于增加的数据,应如何进行分片呢???

(1)哈希求余

①原理:假设有N个分片,编号为[0,N-1]。对于每个key,计算hash值,hash值%N,结果即为分片编号。

②优点:简单高效,数据分配均匀。

③缺点:一旦分片进行扩容,就需要对全部数据进行重新分配,需要搬运的数据较大,开销较多。

(2)一致性哈希算法

①原理

先将0-->2^32-1个数据空间映射到圆环上,数据按照顺时针方向增长。

假设现在有三个分片:

对于key求完hash值后,hash值在哪个区间内,即就属于对应分片。

若此时增加一个分片:

此时就需要对0号分片的数据进行重分配,判断是属于0号分片还是3号分片,但1号分片和2号分片对应数据不需要改变。

②优点:大大降低了分片扩容时数据搬运的规模,提高扩容操作效率。

③缺点:分片对应数据分配不均匀。

(3)哈希槽分区算法--redis使用

①原理:

假设有三个分片,将哈希值映射到16384个槽位上(哈希值%16384),将这16384个槽位均匀的分配给每个分片上。

0号分片:[0,5461],共5462个槽位;

1号分片:[5462,10923],共5462个槽位;

2号分片:[10924,16384],共5460个槽位。

此时就可以根据key计算hash值,将hash值进行映射得到哈希槽位,根据哈希槽位找到区间判断是几号分片即可。

若此时增加一个分片,一种可能的分配方式:

0号分片:[0,4095],共4096个槽位;

1号分片:[5462,9557],共4096个槽位;

2号分片:[10924,15019],共4096个槽位。

3号分片:[4096,5461]+[9558,10923]+[15019,16384],共4096个槽位。

ps:此时大家一定有个疑问,为什么是16384个槽位呢???

这主要是基于性能和资源利用的考虑,在redis节点发生心跳包时,需要将槽信息放入心跳包中,以便让节点了解当前集群的状态,若槽的数量过多,会导致心跳包的大小增加,进而增加网络传输负担和节点处理压力;若槽的数量过少,则可能无法充分利用集群的资源。因此,考虑到集群的稳定性和资源的充分利用,redis设计者选择了16384槽位作为默认槽位,且建议分片数量不要超过1000。

②优点

进行分片扩容或缩容较为方便。

③缺点

计算复杂。

5、基于docker搭建redis集群

(1)创建redis节点

创建11个redis节点,其中3个一组为1个分片,一组中有1个主节点2个从节点,剩余2个用于扩容示范。

考虑到创建节点较多,可以使用脚本:

创建一个目录:redis-cluster

在该目录下创建两个文件,第一个文件放docker配置;第二个文件放shell脚本,可以批量化执行,创建出11个节点的配置文件。

向generate.sh文件中写入脚本:

执行shell脚本:

执行成功后,就生成了11个配置文件:

(2)配置redis容器

向docker-compose.yml文件中添加docker配置(其中某一个redis节点的docker配置):

172.30.0.0是静态ip地址中的网络号,要保证该ip是内网且不能和当前主机上其他网络号冲突(ifconfig命令可以查询)。

(3)启动redis容器

(4)构建集群

包括参与集群的端口号和每个分片包含的从节点个数。

集群信息:包括主从关系和槽位分配。

集群配置成功:

集群信息:

6、集群效果

(1)添加key

直接在101分片中添加key1:

此时发现会报错,是因为key1的hash值为9189,属于102分片,无法在101分片添加。

换一种方式添加key1:

添加-c选项后,就可以自动根据槽位自动进行分片转换了。

注意:在集群环境下,无法操作多个key。

(2)集群中主节点挂掉

挂掉redis1主节点:

此时发现redis5就会成为主节点。

当redis1恢复后,它也是以从节点方式进行运行的:

(3)集群环境下节点故障处理原理

每个节点,每秒钟会随机给一些节点发生ping包,通过节点是否在规定时间返回pong包判断节点是否正常。

假设A节点发生ping包给B节点,在规定时间内若A节点未收到B节点返回的pong包,A就会尝试和B重新进行tcp连接,若没有连接成功,A就会认为B挂掉了,并且通过Gossip协议和其他节点进行沟通,确认B是否挂掉,若超过一半的集群个数认为B挂掉了,此时A就会把B标记为FAIL,并告诉其他节点。

当B是从节点时,此时不会进行故障迁移;当B是主节点时,会进行故障迁移。

故障迁移:由B的从节点(假设为C和D触发故障迁移)。

①从节点先判断自己是否有资格竞选主节点,若太久没有和主节点通信,则从节点失去竞选资格;

②具有竞选资格的从节点会休眠一定时间,休眠时间和offset值有关,值越大,排名越靠前,休眠时间越少。

③休眠时间到的从节点会进行拉票操作,且只有主节点具有投票资格,当从节点的票数超过主节点个数一半时,该节点就会晋升成主节点。

④该节点会把晋升为主节点的消息告诉给其他集群节点,更新保存集群结构信息。

(4)整个集群环境宕机的情形

①一个分片的主从节点全部挂掉;

②一个分片主节点挂掉,但是没有从节点;

③短时间内,集群环境中超过一半的主节点全部挂掉。

7、集群扩容

实现效果:当前集群环境为3个分片,打算扩容为4个分片,110主机为主节点,111主机为从节点。

(1)将110主机对应节点加入集群环境中

110是一个master,但此时还没有槽位:

(2)重新分配哈希槽位

调用命令:

移动1/4的哈希值:

选择哪个节点(110对应的ID)来接受这些移动的哈希值:

选择这些slots从哪些节点搬运(填写all表示所有主节点都要进行搬运,done表示自定义):

重新分配后的slots:

注意:在进行扩容时,搬运key时,对应key是无法进行访问的。

(3)将111主机对应节点加入集群环境中

111主机对应节点为110的从节点:

九、redis典型应用--缓存

数据访问速度:cpu寄存器>内存>硬盘>网络。

可以将数据存储在内存中,作为硬盘的缓存。数据库是将数据存储在硬盘中,redis是将数据存储在内存中,可以使用redis作为数据库的缓存,从而提高数据的访问效率。

1、实现过程

客户端发起查询请求,服务器先查询redis,如果redis中存在则直接返回数据,如果redis中不存在,则再去数据库中查询。

我们需要在redis中存放20%的热点数据,就可以使80%的请求不再真正查询数据库了。

2、热点数据

哪些数据属于热点数据呢???

(1)定期生成

每隔一定周期(一天/一周/一月),对于访问的数据频率进行调查,选取前20%的数据作为热点数据,但这部分热点数据不具有实时性。

(2)实时生成

给redis设置容量上限(配置文件中maxmemory参数),针对每次查询,如果在redis中查到了就直接返回,若干redis中不存在,就在数据库中查询,把查到的结果写入redis中。如果redis容量已经达到上限,此时就会触发缓存淘汰策略(配置文件中可以修改)。持续一段时间后,redis的数据就是热点数据了。

3、缓存淘汰策略

(1)volatile-lru:当redis容量达到上限后,在设置了过期时间的key中,根据最后一次使用时间算法进行淘汰,淘汰最久未使用的。

(2)allkeys-lru:当redis容量达到上限后,在所有key中,根据最后一次使用时间算法进行淘汰,淘汰最久未使用的。

(3)volatile-lfu:当redis容量达到上限后,在设置了过期时间的key中,根据最近一段时间访问次数算法进行淘汰,淘汰访问次数最少的。

(4)allkeys-lfu:当redis容量达到上限后,在所有key中,根据最近一段时间访问次数算法进行淘汰,淘汰访问次数最少的。

(5)volatile-random:当redis容量达到上限后,在设置了过期时间的key中,随机淘汰key。

(6)allkeys-random:当redis容量达到上限后,在所有key中,随机淘汰key。

(7)volatile-ttl:当redis容量达到上限后,在设置了过期时间的key中,越早过期的优先被淘汰。

(8)noeviction:默认策略,当redis容量达到上限后,新写入操作会报错。

4、缓存存在的问题

(1)缓存预热

①问题描述

刚开始启动redis或redis宕机时,此时redis上没有数据,全部请求都会发送到数据库,会给数据库带来压力。

②产生原因

redis上没有热点数据。

③解决方法

提前将热点数据准备好写入reids中。

(2)缓存雪崩

①问题描述

短时间内,redis上的大量key失效,导致数据库压力剧增。

②产生原因

redis宕机或者大量key同时达到过期时间。

③解决方法

部署高可用的redis集群或者对key不设置过期时间。

(3)缓存穿透

①问题描述

对于一些key,redis和数据库中都没有,但一直持续访问数据库获取结果,会给数据库带来压力。

②产生原因

业务设计不合理,比如缺失校验环节,导致非法key持续查询;错误操作导致数据库数据被误删;

③解决方法

针对要查询的key,检验key是否合法;使用布隆过滤器先判断key是否存在,再真正进行查询。

(4)缓存击穿

①问题描述

一些热点数据在redis上过期失效了,导致数据库压力骤增。

②产生原因

热点数据过期失效。

③解决方法

对于热点数据不设置过期时间;服务降级,限制同时请求数据库的并发数。

十、redis典型应用--分布式锁

1、分布式锁的作用

在一个分布式系统中,会涉及到多个redis服务器访问同一个公共资源的情况,此时就需要加锁来避免出现线程安全类似的问题。

但此时加锁有个问题,java的synchronized锁是针对于同一个进程的不同线程加锁,而分布式系统中,一个服务器就代表一个进程,此时就需要分布式锁来实现进程间的加锁。

2、分布式锁的原理

使用redis实现分布式锁。

对于多个服务器实现买票来说:A服务器需要实现买票请求,就在redis中使用setnx创建一个指定的key,相当于加锁。此时若B服务器也想实现买票请求,当在redis中操作setnx时,key已经存在,此时就会设置失败,B服务器是阻塞还是放弃自行决定;当A服务器实现买票请求后,进行delete操作删除key,相当于释放锁,此时其他服务器就可以进行加锁实现买票请求了。

3、分布式锁存在的问题

(1)锁未释放

①问题描述

在上述中,A服务器在买票过程中,此时A服务器突然挂掉了,但key没有删掉(未释放锁),其他服务器也无法设置key(加锁)。

②问题解决

给key设置过期时间,若超过规定时间,则key就会失效(锁释放)。但若超过规定时间,加锁下的操作还未完成,此时释放锁就会出现问题。但也不能将过期时间设置太长,会影响其他服务器加锁操作。此时我们就可以考虑设置动态过期时间。

动态过期时间:刚开始设置一个过期时间(比如1s),当key的过期时间快到达1s时,加锁下的操作还未完成,就使得key再增加1s,一直重复操作,直到加锁下的操作完成,就可以不再增加过期时间了。此时即使服务器挂掉了,过期时间不会增加,到达过期时间就会删除key。对于上述key的过期时间有专门的线程进行管理--看门狗。

(2)加锁解锁不是同一个服务器操作

①问题描述

在上述中,A服务器进行加锁后,B服务器又紧接着进行了解锁,此时可能会出现类似线程安全问题。

②问题解决

在加锁时引入校验机制。

每个服务器都有自己的身份编号,A服务器在redis中进行setnx时,value值填入自己的身份编号,此时B服务器若想删除该key,就需要获取key对应的value值,value值和自己身份编号相同才能删除,不相同则不能删除。

(3)删除key(释放锁)不是原子的

①问题描述

服务器在释放锁时需要先查询,再删除,两步操作不是原子的,存在问题。

②问题解决

为了使解锁操作具有原子性,可以使用redis的lua脚本。

lua也是一个编程语言,lua语言实现的一段逻辑,可以被原子的执行。此时就可以使用lua语言实现key删除逻辑,并将代码编写成一个.lua后缀的文件,后续执行key删除时,就可以使redis服务器执行该lua脚本,使得删除key是原子操作的。

lua语言编写的删除功能:

(4)redis挂掉了

①问题描述

在使用redis实现分布式锁时,也可能会出现redis挂掉的情况。

②问题解决

引入更多的redis节点作为备份,加锁时,给redis节点加锁成功的个数超过redis总节点数的一半时,此时才会加锁成功;其他服务器想加锁,就需要遍历全部redis节点判断是否存在key。释放锁时,也需要遍历全部redis节点删除key。(redlock算法)

相关推荐
qq_4330994041 分钟前
Ubuntu20.04从零安装IsaacSim/IsaacLab
数据库
Dlwyz43 分钟前
redis-击穿、穿透、雪崩
数据库·redis·缓存
工业甲酰苯胺3 小时前
Redis性能优化的18招
数据库·redis·性能优化
没书读了4 小时前
ssm框架-spring-spring声明式事务
java·数据库·spring
i道i4 小时前
MySQL win安装 和 pymysql使用示例
数据库·mysql
小怪兽ysl4 小时前
【PostgreSQL使用pg_filedump工具解析数据文件以恢复数据】
数据库·postgresql
wqq_9922502775 小时前
springboot基于微信小程序的食堂预约点餐系统
数据库·微信小程序·小程序
爱上口袋的天空5 小时前
09 - Clickhouse的SQL操作
数据库·sql·clickhouse
Oak Zhang6 小时前
sharding-jdbc自定义分片算法,表对应关系存储在mysql中,缓存到redis或者本地
redis·mysql·缓存
聂 可 以6 小时前
Windows环境安装MongoDB
数据库·mongodb