在日常的开发中,无不都是使用数据库来进行数据的存储,由于一般的系统任务中通常不会存在高并发的情况,所以这样看起来并没有什么问题,可是一旦涉及大数据量的需求,比如一些商品抢购的情景,或者是主页访问量瞬间较大的时候,单一使用数据库来保存数据的系统会因为面向磁盘,而磁盘读写速度比较慢的问题而存在严重的性能弊端,一瞬间成千上万的请求到来,需要系统在极短的时间内完成成千上万次的读/写操作,这个时候往往不是数据库能够承受的,极其容易造成数据库系统瘫痪,最终导致服务宕机的严重生产问题。为了克服上述的问题,项目通常会引入NoSQL技术,这是一种基于内存的数据库,并且提供一定的持久化功能。
NoSQL,指的是非关系型的数据库。NoSQL也可被认为是Not Only SQL的缩写,是对不同于传统的关系型数据库的数据库管理系统的统称。
对NoSQL最普遍的解释是"非关系型的",强调Key-Value Stores和文档数据库的优点,并不是单纯的反对传统关系型数据库,而是作为补充,一般用于超大规模数据的存储。
关系型数据库与非关系型数据库的区别
关系型数据库:MySQL是关系型数据库中的一种。以表的形式存储数据。
非关系型数据库:Redis是属于非关系型数据库中的一种。以键值对的形式存储。
Redis (全称Remote Dictionary Service,即远程字典服务器)是一个开源的,C语言编写的高性能数据结构存储系统,它可以用作数据库、缓存和消息中间件。
它是基于内存运行并支持持久化的NoSQL数据库,是当前最热门的NoSQL数据库之一。
MySQL的数据存在磁盘中,Redis的数据是存在内存中的,因此具有极快的读写速度。
内存存储:数据主要存储在内存中,提供毫秒级的响应时间
数据持久化:支持RDB快照和AOF日志两种持久化方式
丰富的数据类型:支持字符串、哈希、列表、集合、有序集合等多种数据结构
高可用性:支持主从复制、哨兵模式和集群模式
原子操作:所有操作都是原子性的
Redis默认16个数据库,编号0-15
SELECT + index
: 数据库的切换
SET key value
: 往数据库中添加key + value的键值对,如果key已经存在,则改变value的内容为新的value
DEL key
: 删除记录
KEYS *
: 查看当前数据库中所有的key
KEYS k?
: 查看当前数据库中所有以k开头的两个字符的key(?代表占位符,且?所表示的不能为空),如果没有符合的键,提示为空
DBSIZE
: 查看当前数据库的key的数量
FLUSHDB
: 清空当前数据库
FLUSHALL
: 清空所有数据库
MOVE key + index
: 把key所对应的键值对从当前库移动到目标库
EXISTS key
: 判断key是否存在
EXPIRE key + secons
: 为给定的key对应的数据设置过期时间(秒)
TTL key
: 查看key对应的数据的剩余有效时间
TYPE key
: 查看key所对应的value的类型
Redis中存储的数据都是以键值对的形式存在。key一定是字符串,value的类型则有很多。
Redis支持多种数据类型,其中最常用的五大数据类型分别是:String(字符串)、Hash(哈希)、List(列表)、Set(集合)和Sorted Set(有序集合)。
Redis最基本的数据类型
二进制安全,可以存储任何数据
最大能存储512MB的数据
常用命令:
xxxxxxxxxx
111SET key value # 设置值
2MSET key1 value1 key2 value2 ... # 设置多个key与value
3GET key # 获取值
4MGET key1 key2 ... # 获取多个key与value
5INCR key # 数值递增
6INCRBY key num # 给key对应的value值每次加num
7DECR key # 数值递减
8APPEND key value # 追加字符串
9STRLEN key # 获取字符串长度
10GETRANGE key start end # 获取key对应的value的子串
11SETRANGE key offset newValue # 设置key对应的value值从offset开始用newValue进行替代
键值对集合
适合存储对象
字段数量没有限制
常用命令:
xxxxxxxxxx
91HSET key field value # 设置字段值
2HMSET key field1 value1 field2 value2 ... # 同时将多个field-value对设置到哈希表key中
3HGET key field # 获取字段值
4HGETALL key # 获取所有字段和值
5HDEL key field # 删除字段
6HEXISTS key field # 判断字段是否存在
7HLEN key # 获取字段数量
8HKEYS key # 获取哈希表中的所有的字段
9HVALS key # 获取哈希表中所有值
简单的字符串列表,按照插入顺序排序
支持双端操作
最多可存储2^32-1个元素
允许重复元素
常用命令:
xxxxxxxxxx
111LPUSH key value1 value2 ... # 将一个或多个值插入到列表头部
2RPUSH key value1 value2 ... # 将一个或多个值插入到列表尾部
3LPOP key # 左侧弹出
4RPOP key # 右侧弹出
5LRANGE key start stop # 获取范围内元素
6LLEN key # 获取列表长度
7LINDEX key index # 通过下标获取列表中的元素
8LSET key index value # 通过下标设置列表元素的值,下标从0开始
9LREM key count value # 从队头开始移除count个值为value的列表元素
10LTRIM key start stop # 对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除
11LINSERT key BEFORE|AFTER pivot value # 在列表的元素前或者后插入元素
无序的字符串集合
元素唯一,不允许重复
支持集合运算(交集、并集、差集)
常用命令:
xxxxxxxxxx
111SADD key member # 添加元素
2SREM key member # 删除元素
3SMEMBERS key # 获取所有元素
4SISMEMBER key member # 判断元素是否存在
5SCARD key # 获取集合大小
6SINTER key1 key2 # 交集
7SUNION key1 key2 # 并集
8SDIFF key1 key2 # 差集
9SMOVE source destination member # 将member元素从source集合移动到destination集合
10SRANDMEMBER key num # 在集合中随机选出num个数
11SPOP key [num] # 移除并返回集合中的一个/num个随机元素
有序的字符串集合
每个元素关联一个分数(score)
根据分数自动排序
元素唯一,但分数可以重复
常用命令:
xxxxxxxxxx
111ZADD key score member # 添加元素
2ZREM key member # 删除元素
3ZRANGE key start stop # 按分数升序获取范围内元素
4ZREVRANGE key start stop # 按分数降序获取范围内元素
5ZRANGEBYLEX key min max [LIMIT offset count] # 通过字典区间返回有序集合的成员(分数要一致)
6ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT] # 通过分数返回有序集合指定区间内的成员
7ZREVRANGEBYSCORE key max min [WITHSCORES] #返回有序集中指定分数区间内的成员,分数从高到低排序
8ZSCORE key member # 获取元素分数
9ZCARD key # 获取集合大小
10ZRANK key member # 获取元素排名
11ZCOUNT key min max # 计算在有序集合中指定区间分数的成员数
redis属于内存数据库,数据都存放在内存中,内存具备易失性,如果服务器异常断开或者重启之后,数据就会丢失。
为了防止数据的丢失,需要定期的将数据从内存保存到磁盘,这就是持久化。
Redis有两种持久化方式
RDB持久化(Redis DataBase):将当前数据保存到硬盘(原理是将Reids在内存中的数据库记录定时dump到磁盘上的RDB,从而实现数据存储的持久化)
AOF持久化(Append Only File):将每次执行的写命令保存到硬盘(原理是将Reids的操作日志以追加的方式写入文件,数据一定是通过写命令存到数据库的,失去存好的数据只需要将所有的写命令再执行一次,就可以恢复所有的数据)
RDB(Redis Database)是 Redis 的一种持久化方式,它通过创建数据快照的方式将内存中的数据保存到磁盘上的二进制文件中。RDB 文件是一个经过压缩的二进制文件,代表 Redis 在某个时间点上的数据快照。
RDB是Redis 默认的持久化方案(而AOF默认是关闭的)。
在指定的时间间隔内,执行指定次数的写操作,则会将Redis在内存中的数据写入到磁盘中,即在指定目录下生成一个dump.rdb文件(快照文件)。
Redis重启会通过加载dump.rdb文件恢复数据。(/var/lib/redis/6379)
快照生成过程
Redis 主进程 fork()
创建子进程
子进程将内存数据写入临时 RDB 文件
写入完成后,用临时文件替换旧的 RDB 文件
子进程退出,主进程继续处理客户端请求
Copy-on-Write (COW) 机制
fork 时:子进程和父进程共享相同的内存页
写入时:当父进程修改数据时,操作系统会复制被修改的内存页
优势:节省内存,提高性能
在指定的时间间隔内,执行指定次数的写操作,就会自动触发。
默认在3600秒内写1次,300秒内写100次,或者60秒内写10000次,就会触发快照。
Note
是在指定的时间间隔内,要执行指定次数,这样两个条件都满足的情况下,才能触发快照。
通过配置文件设置自动保存条件:
xxxxxxxxxx
141# redis.conf 配置示例
2save 900 1 # 900秒内至少1个key发生变化
3save 300 10 # 300秒内至少10个key发生变化
4save 60 10000 # 60秒内至少10000个key发生变化
5
6# 设置RDB文件名和路径
7dbfilename dump.rdb
8dir /var/lib/redis/
9
10# 启用压缩
11rdbcompression yes
12
13# 启用校验和
14rdbchecksum yes
SAVE 命令(同步)
xxxxxxxxxx
21127.0.0.1:6379> SAVE
2OK
是一个同步执行的指令。在Redis客户端输入SAVE命令后,Redis服务器会立即开始将当前内存中的所有数据完整地写入磁盘上的RDB文件。在这个过程中,Redis 会阻塞所有其他客户端的请求,直到数据持久化操作完成。
如果数据量非常大,保存操作费时可能很长,影响业务正常运行
适用:服务器即将关闭时使用
BGSAVE 命令(异步)
xxxxxxxxxx
21127.0.0.1:6379> BGSAVE
2Background saving started
是一个异步执行的指令。执行BGSAVE命令时,Redis服务器会fork出一个子进程,由这个子进程负责将内存中的数据写入RDB文件。而主进程会继续处理客户端的请求,不会被阻塞。
fork子进程这个操作会消耗一定系统资源,有可能导致短暂的性能下降
适用:生产环境推荐使用
SHUTDOWN 命令
xxxxxxxxxx
11127.0.0.1:6379> SHUTDOWN SAVE
关闭服务器前自动执行 SAVE
flushall 命令
清空数据库,也会触发快照
xxxxxxxxxx
31+-------+-------------+-----------+-----------------+-----+-----------+
2| REDIS | RDB-VERSION | SELECT-DB | KEY-VALUE-PAIRS | EOF | CHECK-SUM |
3+-------+-------------+-----------+-----------------+-----+-----------+
REDIS:文件标识符(5字节)
RDB-VERSION:RDB 版本号(4字节)
SELECT-DB:数据库选择器
KEY-VALUE-PAIRS:键值对数据
EOF:文件结束标志
CHECK-SUM:校验和(8字节)
优点
当数据量比较大,也就是大规模数据的恢复时,建议使用RDB持久化(相对于AOF要快一些)。
换言之,若数据很多(说明写操作很多),把数据从磁盘复制到内存比重新执行所有的写操作更快。
如果对数据的完整性与一致性要求不高,可以使用RDB(快)
Tip
默认的60秒内10000次写操作触发快照,如果58秒进行了10000次写操作然后系统突然崩溃了,那么Redis还没有来得及触发快照操作来将这10000次写操作后的最新数据保存到RDB文件中。所以,从上次成功生成RDB快照之后到系统崩溃前的这10000次写操作的数据就会丢失。而AOF默认是1秒钟就触发一次。
性能高效:子进程处理,不影响主进程性能
文件紧凑:二进制格式,文件小,传输快
恢复快速:直接加载到内存,启动速度快
适合备份:定期生成完整数据快照
适合灾难恢复:可以轻松复制到远程服务器
缺点
数据丢失风险:数据的完整性与一致性不高,数据丢失可能比较多
fork 开销:在使用BGSAVE指令时,父进程会fork出一个子进程,让子进程进行持久化,所以占用内存
内存占用:COW 机制可能导致内存使用翻倍
不适合实时性:无法做到秒级数据持久化
AOF(Append Only File)是Redis的一种持久化方式,通过记录每个写操作命令来保证数据的持久性。就是将执行过的写指令记录下来,在数据恢复时按照从前到后的顺序再将指令都执行一遍。
Redis将每个写命令以文本协议格式追加到AOF缓冲区
命令按照执行顺序依次记录
只记录修改数据的命令(SET、DEL、LPUSH等)
AOF提供三种同步策略:
always:每个写命令都立即同步到磁盘
优点:数据安全性最高,最多丢失一个命令
缺点:性能最差
everysec(默认):每秒同步一次
优点:性能和安全性的平衡
缺点:最多丢失1秒的数据
no:由操作系统决定何时同步
优点:性能最好
缺点:数据丢失风险最大
Redis可以在不打断服务客户端的情况下,对 AOF 文件进行重写,或者称作重建(rebuild)。
其目的在于减小AOF文件大小、提高数据恢复速度和移除冗余命令。
可以人为地执行BGREWRITEAOF命令,Redis将生成一个新的AOF文件(备份文件),这个文件包含重建当前数据集所需的最少命令。
Redis进行AOF持久化时也会自动触发重写,但要满足一定条件:
Redis会记录上次重写时的AOF大小,默认配置是当AOF文件大小是上次rewrite后AOF文件大小的两倍(增幅100%)且文件大于64M时触发
触发条件
xxxxxxxxxx
31# 配置文件设置
2auto-aof-rewrite-percentage 100 # 当前AOF文件大小超过上次重写后大小的100%时触发
3auto-aof-rewrite-min-size 64mb # AOF文件最小64MB才考虑重写
重写过程
fork子进程:创建子进程进行重写,避免阻塞主进程
生成新AOF文件:遍历当前数据库状态,生成最少的命令集合
处理重写期间的新命令:主进程将新命令同时写入AOF缓冲区和AOF重写缓冲区
替换旧文件:重写完成后,将重写缓冲区的命令追加到新文件,然后替换旧AOF文件
优点
数据安全性高:可以配置为每个命令都同步
文件可读性好:纯文本格式,便于理解和修复
支持增量备份:可以实时备份AOF文件
恢复精度高:可以精确到某个时间点
缺点
文件体积大:如果一直达不到重写条件,AOF文件的大小会比较大
恢复速度慢:需要重放所有命令
性能开销:写操作需要额外的磁盘I/O
Redis事务是一组命令的集合,可以一次执行多个命令,这些命令会被顺序执行,并且在执行过程中不会被其他客户端的命令打断。
一个事务中的所有命令都会序列化,按顺序的串行化执行,而不会被其他命令插入,不许加塞。
一个事务从开始到执行会经历以下三个阶段: 开始事务、 命令入队、执行事务。
特点
如果事务队列中有语法错误的命令,事务中的所有命令要么全部执行,要么全部不执行
事务执行期间不会被其他命令打断
事务执行前后数据保持一致
Redis事务本身不提供持久性保证
MULTI
,开启事务,后续命令将被放入队列中
EXEC
,执行事务队列中的所有命令
DISCARD
,取消事务,清空命令队列
WATCH
, 监视一个或多个键,如果在事务执行前这些键被修改,事务将被取消
UNWATCH
, 取消对所有键的监视
基本流程
xxxxxxxxxx
101# 1. 开启事务
2MULTI
3
4# 2. 添加命令到队列
5SET key1 "value1"
6SET key2 "value2"
7INCR counter
8
9# 3. 执行事务
10EXEC
带监视的事务
xxxxxxxxxx
121# 1. 监视键
2WATCH mykey
3
4# 2. 开启事务
5MULTI
6
7# 3. 添加命令
8SET mykey "newvalue"
9INCR counter
10
11# 4. 执行事务
12EXEC
Note
一旦执行EXEC,WATCH监控会被取消。
如果想要取消监控,在开始事务之前可以使用UNWATCH命令。
UNWATCH 指令是原子操作,它会立即取消所有监控,并且不会被其他客户端的操作打断。
不要在命令入队的阶段使用UNWATCH,因为事务开始后输入的命令都会在EXEC时才执行。
Redis事务有以下三个重要的保证:
批量操作在发送 EXEC 命令前被放入队列缓存。
收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行。
在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中(也就是其他客户端仍然正常使用,不用等待第一个客户端的事务执行完)。
1. 语法错误(编译时错误)
如果事务队列中有语法错误的命令,整个事务都不会执行:
xxxxxxxxxx
51MULTI
2SET key1 value1
3SET key2 # 语法错误,缺少值
4SET key3 value3
5EXEC # 返回错误,所有命令都不执行
2. 运行时错误
如果命令语法正确但执行时出错,其他命令仍会继续执行:
xxxxxxxxxx
51MULTI
2SET key1 "hello"
3INCR key1 # 运行时错误:对字符串执行INCR
4SET key2 "world"
5EXEC # key1设置成功,INCR失败,key2设置成功
Note
Redis的事务是不是原子的?
原子性是一个数据库和并发编程中的重要概念,它描述的是一个操作序列要么全部执行成功,要么全部不执行,不存在部分执行的中间状态。就像一个不可分割的原子一样,操作在执行过程中不会被其他操作中断或干扰。
Redis事务中的前一条命令执行失败,对后面的命令没有影响,看起来Redis的事务不具备原子性。
但如果在事务中包含着错误的指令,整个事务都不会执行,从这个角度看,redis又表现出一定的原子性。
因此,Redis事务在入队错误时具备原子性,但在执行时错误时不具备严格意义上的原子性。
严格来说,redis中的事务是没有原子性的。
1. 不支持回滚
Redis事务不支持回滚机制
如果某个命令执行失败,其他命令仍会继续执行
这是为了保持Redis的简单性和高性能
2. 无法在事务中使用条件判断
事务中的命令在EXEC执行前都是排队状态
无法根据前一个命令的结果来决定后续命令
3. WATCH的限制
WATCH只能在MULTI之前使用
一旦EXEC或DISCARD执行,所有WATCH都会被清除
WATCH是针对整个键的,不是针对键的某个字段
定义:乐观锁假设在大多数情况下,数据在被操作期间不会被其他客户端修改,因此在操作数据时不会对数据进行加锁。只有在提交操作时,才会检查数据是否被其他客户端修改过。如果数据没有被修改,则执行操作;如果数据被修改了,则放弃当前操作或重试。
适用场景:适用于读多写少的场景,因为在这种场景下,数据发生冲突的概率较低,使用乐观锁可以减少加锁和解锁的开销,提高系统的并发性能。
底层的机制:CAS或版本号机制
Tip
CAS机制:
会将变量的预期值A与在内存中的值V进行比较,如果V与A是相等,就可以将该值改为B;如果V与A的值是不相等,那么就不能将其改为新值B。
版本号机制:
一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加1。当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。(如果读取到的version值为当前数据库中的version值不相等,说明有其他线程修改了数据)
在多线程环境下,对于共享资源而言,每个线程访问数据的时候,都认为其他的线程会修改数据,所以每次在拿数据的时候都会上锁。当其他线程想要访问数据时,都需要阻塞挂起。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁、表锁,读锁,写锁等,都是在操作之前先上锁。
冷备份也称为静态备份,是指在Redis服务器停止运行的状态下进行数据备份。操作方式通常是直接拷贝Redis的数据文件(如RDB文件或AOF文件)到其他存储介质或位置。例如,可以使用操作系统的复制命令将RDB文件复制到远程服务器或本地的另一个磁盘分区。
优点
数据一致性:由于Redis服务器处于停止状态,不会有新的数据写入或修改,因此备份的数据具有很强的一致性。
备份速度相对较快:没有服务器的并发读写操作干扰,单纯的文件拷贝速度通常较快,尤其是对于RDB文件(紧凑的二进制文件),复制过程相对简单。
缺点
业务中断:为了进行冷备份,需要停止Redis服务器,会导致依赖Redis的业务系统无法正常访问,造成业务中断,对于一些对可用性要求极高的系统来说是不可接受的。
实时性差:冷备份只能在服务器停止时进行,无法实时反映数据的变化。比如经过一次冷备份后重新启动服务器,经历了一些写操作,如果在下一次冷备份之前,服务器出现故障,需要通过备份恢复数据,就可能存在数据丢失的情况。
热备份也叫在线备份,是指在Redis服务器正常运行、对外提供服务的情况下进行数据备份。一种重要的手段就是主从复制模式。
Redis的主从复制是Redis高可用架构的基础,通过将数据从主服务器(Master)复制到从服务器(Slave),实现数据备份、读写分离和故障恢复。
数据的复制是单向的,只能由主节点到从节点。
主服务器(Master):负责处理写操作,并将数据变更同步给从服务器
从服务器(Slave):从主服务器复制数据,通常用于处理读操作
一主多从:一个主服务器可以有多个从服务器
级联复制:从服务器也可以作为其他从服务器的主服务器
第一阶段:建立连接
xxxxxxxxxx
41# 从服务器连接主服务器
2SLAVEOF 192.168.1.100 6379
3# 或者在配置文件中设置
4replicaof 192.168.1.100 6379
第二阶段:数据同步
全量同步(Full Resynchronization)
从服务器发送PSYNC命令
主服务器执行BGSAVE生成RDB文件
主服务器将RDB文件发送给从服务器
从服务器清空数据库并载入RDB文件
主服务器将缓冲区中的写命令发送给从服务器
增量同步(Partial Resynchronization)
主服务器维护复制偏移量和复制积压缓冲区
从服务器断线重连后发送PSYNC命令
如果偏移量在缓冲区范围内,执行增量同步
否则执行全量同步
主服务器配置
xxxxxxxxxx
151# redis.conf
2# 绑定IP地址
3bind 0.0.0.0
4
5# 设置密码(可选)
6requirepass mypassword
7
8# 允许从服务器连接
9# protected-mode no
10
11# 设置复制积压缓冲区大小
12repl-backlog-size 1mb
13
14# 设置复制积压缓冲区TTL
15repl-backlog-ttl 3600
从服务器配置
xxxxxxxxxx
151# redis.conf
2# 指定主服务器
3replicaof 192.168.1.100 6379
4
5# 主服务器密码
6masterauth mypassword
7
8# 从服务器只读(默认)
9replica-read-only yes
10
11# 复制超时时间
12repl-timeout 60
13
14# 禁用TCP_NODELAY
15repl-disable-tcp-nodelay no
动态配置
xxxxxxxxxx
81# 运行时设置主从关系
2SLAVEOF 192.168.1.100 6379
3
4# 取消主从关系
5SLAVEOF NO ONE
6
7# 查看复制信息
8INFO replication
复制偏移量(Replication Offset)
主服务器偏移量:记录已发送给从服务器的数据量
从服务器偏移量:记录已接收的数据量
作用:判断主从数据是否一致,支持断线重连
复制积压缓冲区(Replication Backlog)
xxxxxxxxxx
81# 查看积压缓冲区信息
2INFO replication
3
4# 输出示例
5repl_backlog_active:1
6repl_backlog_size:1048576
7repl_backlog_first_byte_offset:1001
8repl_backlog_histlen:1000
服务器运行ID(Run ID)
每个Redis服务器都有唯一的运行ID
用于识别服务器身份
服务器重启后运行ID会改变
触发条件:
从服务器首次连接主服务器
从服务器断线时间过长,积压缓冲区数据丢失
主服务器重启,运行ID改变
过程:
xxxxxxxxxx
611. 从服务器发送PSYNC ? -1
22. 主服务器回复+FULLRESYNC <runid> <offset>
33. 主服务器执行BGSAVE
44. 主服务器发送RDB文件
55. 从服务器载入RDB文件
66. 主服务器发送缓冲区命令
触发条件:
从服务器断线重连
主从偏移量差异在积压缓冲区范围内
过程:
xxxxxxxxxx
411. 从服务器发送PSYNC <runid> <offset>
22. 主服务器检查偏移量
33. 主服务器回复+CONTINUE
44. 主服务器发送缺失的命令
优点
数据备份:提供数据冗余,防止数据丢失
读写分离:主服务器处理写操作,从服务器处理读操作
故障恢复:主服务器故障时可以快速切换
负载均衡:分散读请求压力
缺点
数据延迟:主从之间存在数据同步延迟
写操作瓶颈:所有写操作都在主服务器上
故障切换复杂:需要手动或自动故障切换机制
资源消耗:需要额外的服务器资源
Note
1、主机每次进行了写操作,数据会复制到从机;而从机不能进行写指令,只能进行读指令
2、如果从机掉线了,主机不会再记录从机的信息,即使从机后面又上线了,主机也不会记录从机的信息,从机是以主机的身份上来的。
3、如果主机掉线了,那么从机会记录主机的信息,包括:ip、port、在线状态,但是从机依旧是从机,也就是不能写。后续如果主机上线了,那么主从复制的状态会立马恢复,也就是主机依旧是主机,从机依旧是从机。
问题:如果主机一直不上线,那么就不能执行写操作,这就是传统的主从复制的问题。
Redis主从复制有数据热备、负载均衡、故障恢复等作用,但主从复制存在的一个问题,故障恢复无法自动化。
哨兵(Sentinel)模式是Redis官方提供的高可用解决方案,基于Redis主从复制,主要作用便是监控Redis主从集群,并在主服务器故障时自动进行故障转移,进一步提高系统的高可用性。
Redis哨兵系统是一个分布式系统,可以在一个架构中运行多个哨兵进程(Sentinel progress),这些进程使用流言协议(gossip protocols)来接收关于主服务器是否下线的信息,并使用投票协议(agreement protocols)来决定是否执行自动故障迁移,以及选择哪个从服务器作为新的主服务器。
哨兵模式可以解决传统的主从复制下,当主服务器断开之后,从服务器就不能写的问题。
Tip
流言协议(哨兵之间传递消息)
投票协议(少数服从多数)
核心功能
监控(Monitoring):持续监控主从服务器是否正常工作
通知(Notification):当被监控的Redis实例出现问题时,通过API通知系统管理员或其他程序
自动故障转移(Automatic Failover):当主服务器不能正常工作时,自动将某个从服务器升级为主服务器
配置提供者(Configuration Provider):为客户端提供当前主服务器的地址
架构组成
Sentinel节点:独立的Redis进程,专门用于监控
数据节点:主从Redis服务器
客户端:连接Redis服务的应用程序
1. 监控
主观下线(Subjective Down, SDOWN)
xxxxxxxxxx
31# 单个Sentinel认为服务器下线
2# 超过down-after-milliseconds时间无响应
3sentinel down-after-milliseconds mymaster 30000
客观下线(Objective Down, ODOWN)
xxxxxxxxxx
31# 多个Sentinel都认为主服务器下线
2# 达到quorum数量的Sentinel确认
3sentinel monitor mymaster 192.168.1.100 6379 2
2. 故障转移
领导者选举
使用Raft算法选举领导者Sentinel
获得超过半数Sentinel投票的节点成为领导者
领导者负责执行故障转移
新主服务器选择规则
排除下线和断线的从服务器
选择优先级最高的从服务器(replica-priority)
选择复制偏移量最大的从服务器
选择运行ID最小的从服务器
故障转移步骤
向选定的从服务器发送SLAVEOF NO ONE命令
等待从服务器切换为主服务器
向其他从服务器发送SLAVEOF命令
更新Sentinel配置
通知客户端新的主服务器地址
Note
如果只设置一个哨兵,由于流言协议的特性,可能会存在误判的情况。
比如主服务器可能遭遇短暂的硬件故障或软件异常,导致其在短时间内无法响应哨兵的 ping 命令,但很快又恢复正常。如果哨兵在主服务器故障期间没有再次发送 ping 命令或者发送间隔较长,就可能错过主服务器故障的检测,认为主服务器一直处于正常状态。
又或者当网络出现短暂的延迟、丢包等波动情况时,哨兵发送的 ping 命令可能会在网络中丢失或延迟到达主服务器,或者主服务器的响应也可能延迟到达哨兵。如果这种延迟超过了哨兵的主观下线超时时间,哨兵就会误认为主服务器下线。
所以实际使用中可以多设置几个哨兵监测同一个服务器来保证准确性和安全性。
因为多个哨兵之间可以进行相互验证,它们并非仅依赖单一来源的消息来判断主服务器状态,而是会独立地与主服务器及其他哨兵进行通信,并根据自己对主服务器的监测以及从其他多个哨兵处收集到的信息来综合判断主服务器的状态。如果有个别虚假信息传播,很难让所有哨兵都被误导,因为大多数哨兵基于自身真实监测和多数一致的信息会做出正确判断。
缓存雪崩(Cache Avalanche)是指在某一时刻,导致本来可以在缓存中查到数据,现在必须要到底层数据库下找,这样会加大对底层数据库的压力,造成数据库压力骤增甚至宕机的现象。
产生原因
缓存集中过期:大量缓存设置了相同的过期时间
Redis服务器宕机:整个缓存服务不可用
网络故障:缓存服务无法访问
解决方案
设置随机过期时间
缓存预热
多级缓存
熔断降级
缓存击穿(Cache Breakdown)是指某个热点key在缓存过期的瞬间,有大量并发请求同时访问这个key,导致请求直接打到数据库的现象。
产生原因
热点数据过期:高并发访问的key突然过期
没有并发控制:多个线程同时发现缓存失效
解决方案
互斥锁
热点数据永不过期
提前异步刷新
缓存穿透(Cache Penetration)是指查询一个不存在的数据,即要访问的数据在缓存以及底层数据库下都不存在,由于缓存和数据库都没有这个数据,每次请求都会穿透缓存直接查询数据库。
产生原因
恶意攻击:故意查询不存在的数据
业务逻辑问题:程序bug导致查询无效key
数据被删除:缓存和数据库中的数据被删除但仍有查询
解决方案
缓存空值
布隆过滤器
参数校验
限流和监控
问题类型 | 影响范围 | 主要原因 | 核心解决思路 |
---|---|---|---|
缓存雪崩 | 大范围 | 大量缓存同时失效 | 避免同时失效 |
缓存击穿 | 单点热点 | 热点数据过期 | 避免并发重建 |
缓存穿透 | 持续影响 | 查询不存在数据 | 避免无效查询 |