服务器连接redis的一些配置

服务器连接redis的一些配置,第1张

参考: >锁要实现的三个目标:

该命令仅在密钥尚不存在时才设置key(NX选项),到期时间为30000毫秒(PX选项)。键设置为值 “myrandomvalue” 。此值必须在所有客户端和所有锁定请求中都是唯一的。

使用随机值是为了以安全的方式释放锁,实现上使用了Redis的脚本:只有当key存在且其值恰好是我期望的随机值时才删除key。这是通过以下Lua脚本完成的:

这一点很重要,这可以避免删除由另一个客户端创建的锁。例如:

ok,这样我们完成redis分布式锁的实现。你以为这样就安全了吗,接下来我们设想这样一个情况:

首先,假设我们的Redis是主从的:

这时就会同时有两把锁存在的情况。对于这种情况(非常特殊),Redis官方提供了一个叫RedLock的解决方案。

我们假设我们有N个Redis主机。这些节点完全独立,因此我们不使用复制或任何其他隐式协调系统。我们假设N = 5,这是一个合理的值,因此我们需要在不同的计算机或虚拟机上运行5个Redis主服务器,以确保它们以大多数独立的方式失败。

为了获取锁,客户端执行以下 *** 作:

我个人觉得对于小公司这还是相当昂贵的。

其他的细节请参考 redis官方文档 。

在redis主从架构中,Master节点负责处理写请求,Slave节点只处理读请求。对于写请求少,读请求多的场景,例如电商详情页,通过这种读写分离的 *** 作可以大幅提高并发量,通过增加redis从节点的数量可以使得redis的QPS达到10W+。

Master节点接收到写请求并处理后,需要告知Slave节点数据发生了改变,保持主从节点数据一致的行为称为主从同步,所有的Slave都和Master通信去同步数据也会加大Master节点的负担,实际上,除了主从同步,redis也可以从从同步,我们在这里统一描述为主从同步。

redis 同步的是指令流,主节点会将那些对自己的状态产生修改性影响的指令记录在本地的内存 buffer 中,然后异步将 buffer 中的指令同步到从节点,从节点一边执行同步的指令流来达到和主节点一样的状态,一边向主节点反馈自己同步到哪里了 (偏移量,这是redis-28之后才有的特性)。从节点同步数据的时候不会影响主节点的正常工作,也不会影响自己对外提供读服务的功能,从节点会用旧的数据来提供服务,当同步完成后,需要删除旧数据集,加载新数据,这个时候才会暂停对外服务。

因为内存的 buffer 是有限的,所以 redis 主节点不能将所有的指令都记录在内存 buffer 中。redis 的复制内存 buffer 是一个定长的环形数组,如果数组内容满
了,就会从头开始覆盖前面的内容。

如果节点间网络通信不好,那么当从节点同步的速度不如主节点接收新写请求的速度时,buffer 中会丢失一部分指令,从节点中的数据将与主节点中的数据不一致,此时将会触发快照同步。

快照同步是一个非常耗费资源的 *** 作,它首先需要在主节点上进行一次 bgsave 将当前内存的数据全部快照到RDB文件中,然后再将快照文件的内容全部传送到从节点。从节点将RDB文件接受完毕后,立即执行一次全量加载,加载之前先要将当前内存的数据清空。加载完毕后通知主节点继续进行增量同步。

在整个快照同步进行的过程中,主节点的复制 buffer 还在不停的往前移动,如果快照同步的时间过长或者复制 buffer 太小,都会导致同步期间的增量指令在复制 buffer 中被覆盖,这样就会导致快照同步完成后无法进行增量复制,然后会再次发起快照同步,如此极有可能会陷入快照同步的死循环。所以需要配置一个合适的复制 buffer 大小参数,避免快照复制的死循环。

主节点在进行快照同步时,会进行大量的文件 IO *** 作,特别是对于非 SSD 磁盘存储时,快照会对系统的负载产生较大影响。特别是当系统正在进行 AOF 的 fsync *** 作时如果发生快照复制,fsync 将会被推迟执行,这就会严重影响主节点的服务效率。

从 Redis 2818 版开始支持无盘复制。所谓无盘复制是主节点会一边遍历内存,一遍将序列化的内容发送到从节点,而不是生成完整的 RDB 文件后才进行 IO 传输从节点还是跟之前一样,先将接收到的内容存储到磁盘文件中,再进行一次性加载。

(1) 在从节点的配置文件中的 slaveof 配置项中配置了主节点的IP和port后,从节点就知道自己要和那个主节点进行连接了。

(2) 从节点内部有个定时任务,会每秒检查自己要连接的主节点是否上线,如果发现了主节点上线,就跟主节点进行网络连接。注意,此时仅仅是取得连接,还没有进行主从数据同步。

(3) 从节点发送ping命令给主节点进行连接,如果设置了口令认证(主节点设置了requirepass),那么从节点必须发送正确的口令(masterauth)进行认证。

(4) 主从节点连接成功后,主从节点进行一次快照同步。事实上,是否进行快照同步需要判断主节点的 run id ,当从节点发现已经连接过某个 run id 的主节点,那么视此次连接为重新连接,就不会进行快照同步。相同IP和port的主节点每次重启服务都会生成一个新的 run id ,所以每次主节点重启服务都会进行一次快照同步,如果想重启主节点服务而不改变 run id ,使用 redis-cli debug reload 命令。

(5) 当开始进行快照同步后,主节点在本地生成一份rdb快照文件,并将这个rdb文件发送给从节点,如果复制时间超过60秒(配置项:repl-timeout),那么就会认为复制失败,如果数据量比较大,要适当调大这个参数的值。主从节点进行快照同步的时候,主节点会把接收到的新请求命令写在缓存 buffer 中,当快照同步完成后,再把 buffer 中的指令增量同步到从节点。如果在快照同步期间,内存缓冲区大小超过256MB,或者超过64MB的状态持续时间超过60s(配置项: client-output-buffer-limit slave 256MB 64MB 60 ),那么也会认为快照同步失败。

(6) 从节点接收到RDB文件之后,清空自己的旧数据,然后重新加载RDB到自己的内存中,在这个过程中基于旧的数据对外提供服务。如果主节点开启了AOF,那么在快照同步结束后会立即执行BGREWRITEAOF,重写AOF文件。

(7) 主节点维护了一个backlog文件,默认是1MB大小,主节点向从节点发送全量数据(RDB文件)时,也会同步往backlog中写,这样当发送全量数据这个过程意外中断后,从backlog文件中可以得知数据有哪些是发送成功了,哪些还没有发送,然后当主从节点再次连接后,从失败的地方开始增量同步。这里需要注意的是,当快照同步连接中断后,主从节点再次连接并非是第一次连接,所以进行增量同步,而不是继续进行快照同步。

(8) 快照同步完成后,主节点后续接收到写请求导致数据变化后,将和从节点进行增量同步,遇到 buffer 溢出则再触发快照同步。

(9) 主从节点都会维护一个offset,随着主节点的数据变化以及主从同步的进行,主从节点会不断累加自己维护的offset,从节点每秒都会上报自己的offset给主节点,主节点也会保存每个从节点的offset,这样主从节点就能知道互相之间的数据一致性情况。从节点发送 psync runid offset 命令给主节点从而开始主从同步,主节点会根据自身的情况返回响应信息,可能是FULLRESYNC runid offset触发全量复制,也可能是CONTINUE触发增量复制。

(10) 主从节点因为网络原因导致断开,当网络接通后,不需要手工干预,可以自动重新连接。

(11) 主节点如果发现有多个从节点连接,在快照同步过程中仅仅会生成一个RDB文件,用一份数据服务所有从节点进行快照同步。

(12) 从节点不会处理过期key,当主节点处理了一个过期key,会模拟一条del命令发送给从节点。

(13) 主从节点会保持心跳来检测对方是否在线,主节点默认每隔10秒发送一次heartbeat,从节点默认每隔1秒发送一个heartbeat。

(14) 建议在主节点使用AOF+RDB的持久化方式,并且在主节点定期备份RDB文件,而从节点不要开启AOF机制,原因有两个,一是从节点AOF会降低性能,二是如果主节点数据丢失,主节点数据同步给从节点后,从节点收到了空的数据,如果开启了AOF,会生成空的AOF文件,基于AOF恢复数据后,全部数据就都丢失了,而如果不开启AOF机制,从节点启动后,基于自身的RDB文件恢复数据,这样不至于丢失全部数据。RDB和AOF机制可以参考 详解 redis-4x 持久化机制

使用3个虚拟机搭建一主二从的redis主从架构集群。首先参考 redis-4012单节点安装 在每台机器上安装redis,然后修改redis配置文件,其中一个master节点的配置如下(未列出的保持默认即可):

两个slave节点的配置如下:

启动3个redis服务:

查看master日志:

看日志发现一个问题,我们在原理中介绍说:
主节点如果发现有多个从节点连接,在快照同步过程中仅仅会生成一个RDB文件,用一份数据服务所有从节点进行快照同步。
然而这里master的日志显示写了两次RDB文件,这里我查一些资料再来更新。(!!!待完善)

查看slave日志(这里只列出一个slave的日志):

测试主从架构:

(1) 使用 redis-cli 访问3个redis服务

(2) 在 master 节点上 set 一个数据

(3) 从节点上获取数据

(4) 尝试在slave上写入数据

redis主从架构搭建成功!

wait 提供两个参数,第一个参数是从节点的数量 m,第二个参数是时间 t,以毫秒
为单位。它表示等待 wait 指令之前的所有写 *** 作同步到 n 个子节点 (也就是确保
m 个子节点的同步没有滞后),最多等待时间 t。如果时间 t=0,表示无限等待直到
N 个从库同步完成达成一致。
假设此时某个子节点与主节点网络断开,wait 指令第二个参数时间 t = 0,主从同步无法继续
进行,wait 指令会永远阻塞,redis 服务器将丧失可用性。

由于redis是单线程的且性能很快,所以比较适合做全局分布式锁。

基本流程就是在 *** 作可能某个全局冲突资源的时候,使用一个全局唯一key来判断是否有其他线程占用了资源,如果有其他线程占用,则报错退出或者循环等待。如果没有其他线程占用,则就可以通过添加分布式锁来占用这个资源,然后再执行后续的任务,在任务执行完成之后,再释放分布式锁,其他线程就可以继续使用这个资源了。

那么通过redis加锁的动作是什么呢?

简单加锁命令:
命令是:setnx

内部的实现机制就是判断这个key位置是不是有数据,没有数据就设置成value返回,有数据就返回一个特殊数值。

但是这里有一个问题是,如果占用资源的线程错误退出了,没有来得及释放分布式锁,这个锁就被永远的占用了

改进版的加锁:
命令是:1 setnx 2 expire

添加分布式锁的同时,添加一个锁锁过期的时间。这样,当加锁线程退出之后,至少等一段时间之后,锁是有机会释放掉的。

这里有一个小问题是,这两个命令是分开执行的,不是原子 *** 作。那么就存在理论上来说,第一个命令执行完之后,就出现错误,来不及执行expire命令的可能,一种办法是自己写lua脚本,可以实现多条命令的原子化执行。一种办法是引用一些开源库。在28版本之后,redis为了解决这个问题,提供了官方版的解法,就是命令:set key value nx expireTimeNum ex,将上述两个命令合并成了一个命令。

有了过期时间之后解决了一部分问题,但是也有可能出现锁都过期了,但是中间执行的任务还没有结束,第一个线程还在执行了,第二个线程已经拿到锁开始执行了,那么这时候第一个线程如果执行完成之后,那么就会将第二个线程的锁释放掉了。第二个线程释放锁的时候,要不然出错,要不然是释放的其他线程的锁,这样也会和预期不符。

如果单纯地要解决这个问题的话,可以在设置value的时候使用一个随机数,释放锁的时候,先判断这个随机数是否一致,如果一致再删除锁,否则就退出。但是判断value和删除key也不是一个原子 *** 作,这时候就需要使用lua脚本了。

上面的方案依然不能解决超时释放的问题,依然违背分布式锁的初衷。怎么办了?

解题思路是另外启动一个线程,它的任务就是每隔一段时间判断一下如果发现当前线程的任务快过期了还没有完成,则定期给当前线程的锁续个期。

有个开源库解决了这个问题,它大概率会比你实现得更好一些。这个库就是redisson,非常好记,就是redis的儿子son,连起来就是reidsson,虽然可能不是亲的,但是也足够了。

这个库里面有一个组件是watchdog,直译过来就是看门狗,它的作用就是每隔一段时间判断的。

再继续思考,还有一个更极端的问题是,redis如果是单节点的,它宕机了;或者是主备节点的,但是备份节点还没有来得及同步主节点的数据,主节点拿到锁之后,在同步数据之前就马上宕机了,则也有可能出现锁不住的问题。如果认为这是一个问题,想要解决这个问题,这个问题怎么解决了?

思路是在加锁的时候多加锁几台redis服务器,通常情况下redis部署的时候是2n+1台,那么在加锁的时候需要保证过半数服务器加锁成功了,也就是说n+1台服务器。这时候除非整个集群都不可用了,则这个安全性将大幅度提升。

这个问题也有开源库解决了,就是redis红锁。

下一个问题是分布式锁可以重入么?

如果想要实现可重入的分布式锁的话,需要在设置value的时候加上线程信息和加锁次数的信息。但是这是简单的思路,如果加上过期时间等问题之后,可重入锁就可能比较复杂了。


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zz/12754192.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-05-27
下一篇 2023-05-27

发表评论

登录后才能评论

评论列表(0条)

保存