单点Redis所面临的问题及解决方法

news/2024/12/23 18:47:23 标签: redis, 数据库, 缓存, java

1.数据丢失问题

大家可以设想一下这个场景,假如我们现在只有一个Redis,即单点Redis,我们在往Redis中添加数据的时候突然宕机了,那数据怎么办,如果是一条还好,我在敲一遍就行,那我敲了一万行都没保存,难道我要再敲一遍吗?这是不现实的,所以就出现了单点Redis的时第一个问题:数据丢失问题。那他的解决办法就是:实现Redis的数据持久化

1.1实现Redis的数据持久化

RDB

Redis Database Backup file,又称Redis数据快照,就是把内存中的数据全部记录到磁盘中,当Redis实例故障重启后,从磁盘读取快照文件,恢复数据。快照文件称为RDB文件,默认是保存在当前目录,Redis停机时会执行一次RDB

save 由Redis主进程执行RDB,会阻塞所有命令
bgsave 开启子进程执行RDB,避免主进程收到影响
RDB的原理

bgsave开始时会fork主进程得到子进程,子进程共享主进程的内存数据。完成fork后读取内存数据并写入 RDB 文件。

fork采用的是copy-on-write技术:

  • 当主进程执行读操作时,访问共享内存;

  • 当主进程执行写操作时,则会拷贝一份数据,执行写操作。

RDB方式save的基本流程
  1. fork主进程得到一个子进程,共享内存空间

  2. 子进程读取内存数据并写入新的RDB文件

  3. 用新的RDB文件替换旧的RDB文件

RDB会在什么时候执行?
  • 默认是服务停止时

save 60 1000代表什么含义?
  • 代表60秒内至少执行1000次修改则触发RDB

RDB方式的缺点
  • RDB执行间隔时间长,两次RDB之间写入数据有丢失的风险

  • fork子进程,压缩,写出RDB文件都比较耗时

AOF

Append Only File,即追加文件,Redis处理的每一个写命令都会记录在AOF文件中,看一看做命令日志文件。因为AOF为记录命令,所以AOF文件会比RDB文件大得多,而且会记录对同一个key的多次写操作,但只有最后一次写操作才有意义。通过执行bgrewriteof命令,可以让AOF文件执行重写功能,用最少的命令达到相同的效果。Redis也会在触发阈值时,自动去重写AOF命令。

RDB和AOF区别

二者各有千秋,实际开发中往往会结合使用。

RDBAOF
持久化方式定时对整个内存做快照记录每一次执行的命令
数据完整性不完整,两次备份之间可会有丢失相对完整,取决于刷盘策略
数据恢复优先级低,数据完整性不如AOF高,因为数据完整性更高
系统资源占用高,大量的CPU和内存消耗低,主要是磁盘IO资源,但AOF重写时会占用大量的CPU和内存资源
使用场景可以容忍数分钟的数据丢失,追求更快的启动速度对数据安全性要求较高
文件大小会有压缩,文件体积小记录命令,体积大
宕机恢复速度

2.并发能力问题

现在我们解决了数据丢失的问题,那么还是在一样的场景下,我们现在只有一个Redis,既要读又要写,既当爹又当妈,那么此时此刻就是双11的零点,无数的读写请求在同一时刻打到redis上,那么即使一个Redis再快,也忙活不过来啊,因为读写的请求实在是太多了,这就是并发能力的问题,解决方法也很简单,搭建主从集群,实现读写分离

2.1主从集群实现读写分离

搭建主从架构:单点的Redis的并发能力是有上限的,要进一步提高Redis的并发能力,则要搭建Redis主从集群,实现读写分离。

假设现有A,B两个节点,那么只需要在B节点执行:slaveof A.IP A.port,B就可以成为A的slave节点

那么如何实现主从节点间的数据同步呢?

数据同步原理

主从第一次同步是全量同步

Replicatio ID:简称replID,是数据集的标记,id一致则说明是同一个数据集,每一个master都有唯一的replid,salve则会继承master的replid

offset:偏移量,随着记录在repl_baklog中的数据增多而逐渐增大。slave完成同步时也会记录当前的eoffset。若slave的offset小于master的offset,说明master的新数据slave还未同步,需要更新。

slave做数据同步,必须向master声明自己的replid和offset,master才可以判断哪些数据需要同步

全量同步的流程

  1. slave节点请求增量同步

  2. master判断replid是否一致,不一致则拒绝

  3. master将完整的内存数据生成RDB,发送到slave

  4. slave请求本地数据,加载master的RDB

  5. master将RDB期间的命令记录在repl_baklog上,并持续将log中的命令发送给salve

  6. slave执行接收的命令,保持与master之间同步

若slave重启后同步,则执行增量同步,repl_baklog大小有上限,写满后会覆盖最早的数据,如果slave断开时间过久,导致尚未备份的数据被覆盖,则无法基于log做增量同步,只能再次全量同步

全量同步和增量同步的区别

  • 全量同步:master将完整的内存数据生成RDB文件,发送个slave,后续的命令则记录在repl_baklog中,逐步发送给slave

  • 增量同步:slave提交自己的offset到master,master获取repl_baklog中offset之后的命令个slave

什么时候执行全量同步?

  1. slave第一次连接master节点时

  2. slave节点断开太久,repl_baklog中的节点又被覆盖时

什么时候执行增量同步?

  • slave节点断开又恢复,并且在repl_baklog中能找到offset时

3.故障恢复问题

在第一个问题中,我么解决了数据持久化的问题,那么假如又出现一种新的状况(不要嫌多,实际环境的问题可能更多),那就是连我们数据持久化的RDB,AOF文件也跟着没了,此时我们该怎么办呢?不要担心,如果是slave节点没了,找master节点再进行数据同步就好了,那如果是master节点宕机怎么办呢?使用哨兵机制来实现主从集群的自动故障恢复。

3.1哨兵机制实现主从集群的自动故障恢复

哨兵集群结构

哨兵的作用

在上述的结构中,我们可以清晰的看到哨兵集群的三个作用

  • 监控:sentinal会不断检查master和slave是否按预期工作

  • 通知:哨兵会充当redis客户端的服务来发现来源,当集群发生故障转移时,会将最新消息推送给redis客户端

  • 自动故障恢复:如果master故障,哨兵会提拔一个slave作为新的master,当故障实例恢复后,也已新的master为主

服务状态监控

哨兵基于心跳机制检测服务状态,每隔一秒向集群的每一个实例发送ping命令

主观下线:如果某个哨兵发现某实例在规定时间内未作出响应,则认为该实例主观下线

客观下线:若超过指定数量的哨兵都以为该实例主管下线,则该实例客观下线,指定数量值最好超过哨兵数量的一半

自动故障恢复的过程

  1. 首先选定一个新的slave作为新的master,执行slave of no one

  2. 然后让所有节点执行slaveof 新master

  3. 修改故障节点,修改好后,执行slaveof 新master

4.存储能力问题

经过上述的学习,我们已经通过搭建主从集群,哨兵集群解决了高可用,高并发写的问题。但依然有两个问题未解决:

  • 海量数据存储问题

  • 高并发写的问题

所以使用分片集群解决上述问题

4.1分片集群

分片集群的结构

分片集群的特征

  1. 集群有多个master,每个master保存不同的数据

  2. 每个master节点都可以有多个slave

  3. master节点通过心跳机制监测批次的健康状态

  4. 客户端请求可以访问任意节点,最终都会被转发到正确节点

我们提到每个master保存不同的数据,那这些数据如何选择自己要保存的节点呢?这就要提到散列插槽

散列插槽

Redis会把每一个master节点映射到0--16383,共16384个插槽(hash slot)上,数据key不是与节点绑定,而是与插槽绑定,redis会根据key的有效部分计算插槽值,分两种情况:

  1. key中包含 { },{ }中至少有一个字符,{ }中的为有效值

  2. key中不包含{},整个key都是有效值

Redis如何判断某个key在哪个实例

  1. 将16384个插槽分配到不同的实例

  2. 根据key的有效部分计算哈希值,对16384取余

  3. 余数作为插槽,寻找插槽所在的实例即可

如何将同一类数据固定的保存在同一个Redis实例中

这一类数据使用相同的有效部分,例如key都已{typeID}为前缀

总结与反思

  1. 这四个问题及解决方法的核心,其实都是围绕在高并发的实际环境中展开的,这也就是如何应对日益增长的物质需求。

  2. 在这些集群中所使用到的方式,简直就是社会结构的缩影,在分片集群中甚至连哨兵的开销都省了。

  3. 笔落至此,我唯一顿感精妙之处在散列插槽,其利用了散列表键值对的特点,使算法复杂度降低为O(1),利用key计算哈希值,从而确定value,使本就基于内存的查找速度又更进一步,但是文中并未谈论到如果发生哈希碰撞,会怎么办呢?对插槽数值取余,这看起来像是采用了除法散列法,作为哈希函数的构造函方法,解决哈希碰撞可采用链接法,开放寻址法等,本文是讲解Redis的,就不赘述了。


http://www.niftyadmin.cn/n/5796843.html

相关文章

FFmpeg第二话:FFmpeg 主要结构体剖析

FFmpeg 探索之旅 一、FFmpeg 简介与环境搭建 二、FFmpeg 主要结构体剖析 三、FFmpeg 视频解码详解 FFmpeg 主要结构体剖析 FFmpeg 探索之旅前言1、AVFormatContext2、AVCodecContext3、AVCodec4、AVStream5、AVPacket6、AVFrame7、AVCodecParameters7、SwsContext8、AVIOConte…

《庐山派从入门到...》板载按键启动!

《庐山派从入门到...》板载按键启动! 《庐山派从入门到...》板载按键启动! 视频内容大致如下 我们之前了解了GPIO的输出模式使用方法,并且成功点灯,很明显本篇要来分享的自然是GPIO的输入模式 正好回顾一下之前学的python基础包…

扩散模型经典问题:训练Diffusion Models的Loss有什么特别之处?

AIGC算法工程师 面试八股文 Diffusion Loss的推导原理?训练Diffusion Models的Loss有什么特别之处?Diffusion Models的Loss下降趋势是什么样的?为什么训练Diffusion Models的Loss会有这样的特征? 目录 Diffusion Loss的推导原理 一般Diffusion Models训练Loss特征 Loss可…

Vue.js前端框架教程9:Vue插槽slot用法

文章目录 插槽(Slots)无名插槽(默认插槽)具名插槽`reference` 插槽使用 `v-slot` 的缩写语法插槽(Slots) 在 Vue 中,插槽(Slots)是一种组件内容分发的机制,允许你将内容从父组件传递到子组件的模板中。插槽可以有名字,这样你就可以在子组件中定义多个插槽,并且在父…

分布式链路追踪-03-分布式系统跟踪工具,如何设计 span?

开源项目 auto-log 自动日志输出 分布式系统跟踪工具,如何设计 span 在分布式系统跟踪工具中,"Span" 是一个核心概念,它代表着一个跟踪单元或操作的一部分。 Span 是分布式系统中的一个时间跨度,用于表示一个请求或…

Java入门2-idea 第五章:IO流(java.io包中)

一、理解 1. 简单而言&#xff1a;流就是内存与存储设备之间传输数据的通道、管道。 2. 分类&#xff1a; (1) 按方向 ( 以 JVM 虚拟机为参照物 ) 【重点】 输入流&#xff1a;将< 存储设备 > 中的内容读入到 < 内存 > 中。 输出流&#xff1a;将< 内存 …

dify.ai和fastgpt,各有什么优缺点,有什么区别

从专业技术角度来看&#xff0c;Dify.ai 和 FastGPT 的区别可以从 架构设计、技术生态、适用场景和性能优化 四个方面进行深入对比&#xff1a; 1. 架构设计 Dify.ai&#xff1a; 云端优先&#xff1a; 主要基于 SaaS&#xff08;Software as a Service&#xff09;模式&…

VScode 查看linux 内核代码

0&#xff0c;安装c.c 1&#xff0c;查看linux 目录下的linux代码&#xff0c;安装remote ssh 2&#xff0c; 输入服务器IP 3 选择服务器为linux