0


redis进阶--IDEA环境

一、解决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算法)

标签: redis 数据库 缓存

本文转载自: https://blog.csdn.net/m0_69468924/article/details/137808026
版权归原作者 ambition… 所有, 如有侵权,请联系我们删除。

“redis进阶--IDEA环境”的评论:

还没有评论