Redis是什么
参考:http://redisdoc.com/index.html
Redis是C 语言开发的一个开源的(遵从 BSD 协议)高性能键值对(key-value)的内存数据库,可以用作数据库、缓存、消息中间件等。
它是一种 NoSQL(not-only sql,泛指非关系型数据库)的数据库。
Redis作为一种内存数据库的特点:
性能优秀,数据在内存中,读写速度非常快,支持并发 10W QPS。
进程单线程,是线程安全的,采用 IO 多路复用机制。
丰富的数据类型,支持字符串(strings)、散列(hashes)、列表(lists)、集合(sets)、有序集合(sorted sets)等。
支持数据持久化。
可以将内存中数据保存在磁盘中,重启时加载。
主从复制,哨兵,高可用。
可以用作分布式锁。
可以作为消息中间件使用,支持发布订阅。
Redis 和 Memcached 的区别
存储方式上:Memcache 会把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小。Redis 有部分数据存在硬盘上,这样能保证数据的持久性。
数据支持类型上:Memcache 对数据类型的支持简单,只支持简单的 key-value,,而 Redis 支持五种数据类型。
使用底层模型不同:它们之间底层实现方式以及与客户端之间通信的应用协议不一样。Redis 直接自己构建了 VM 机制,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求。
Value 的大小:Redis 可以达到 1GB,而Memcache 只有 1MB。
Redis是单线程为什么能这么快
参考:https://zhuanlan.zhihu.com/p/57089960
l 完全基于内存,绝大部分请求是纯粹的内存操作,执行效率高。数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1);
l 数据结构简单,对数据操作也简单,Redis中的数据结构是专门进行设计的;
l 采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;
n 这里单线程是指Redis用单线程处理I/O请求,一个正式的Redis Server运行的时候肯定是不止一个线程的。例如Redis进行持久化的时候会以子进程或者子线程的方式执行
n 多核可以通过启多个Redis服务的实现
l 使用多路I/O复用模型,非阻塞IO;
多路I/O复用模型
参考:https://draveness.me/redis-io-multiplexing/
Redis的数据类型有哪些
参考:https://juejin.im/post/5d8882c8f265da03951a325e
① String 是 Redis 最基本的类型,可以理解成与 Memcached一模一样的类型,一个 Key 对应一个 Value。Value不仅是 String,也可以是数字。
String 类型是二进制安全的,意思是 Redis 的 String 类型可以包含任何数据,比如 jpg 图片或者序列化的对象。String 类型的值最大能存储 512M。
② Hash是一个键值(key-value)的集合。Redis 的 Hash 是一个 String 的Key 和 Value 的映射表,Hash 特别适合存储对象。常用命令:hget,hset,hgetall 等。
③ List 列表是简单的字符串列表,按照插入顺序排序。可以添加一个元素到列表的头部(左边)或者尾部(右边)常用命令:lpush、rpush、lpop、rpop、lrange(获取列表片段)等。
应用场景:List 应用场景非常多,也是 Redis 最重要的数据结构之一,比如 Twitter 的关注列表,粉丝列表都可以用 List 结构来实现。
数据结构:List 就是链表,可以用来当消息队列用。Redis 提供了 List 的 Push 和 Pop 操作,还提供了操作某一段的 API,可以直接查询或者删除某一段的元素。
实现方式:Redis List 的是实现是一个双向链表,既可以支持反向查找和遍历,更方便操作,不过带来了额外的内存开销。
④ Set 是String 类型的无序集合。集合是通过 hashtable 实现的。Set 中的元素是没有顺序的,而且是没有重复的。常用命令:sdd、spop、smembers、sunion等。
应用场景:Redis Set 对外提供的功能和 List 一样是一个列表,特殊之处在于 Set 是自动去重的,而且 Set 提供了判断某个成员是否在一个 Set 集合中。
⑤ Zset 和Set 一样是 String 类型元素的集合,且不允许重复的元素。常用命令:zadd、zrange、zrem、zcard 等。
使用场景:Sorted Set 可以通过用户额外提供一个优先级(score)的参数来为成员排序,并且是插入有序的,即自动排序。
当你需要一个有序的并且不重复的集合列表,那么可以选择 Sorted Set 结构。
和 Set 相比,Sorted Set关联了一个 Double 类型权重的参数 Score,使得集合中的元素能够按照 Score 进行有序排列,Redis 正是通过分数来为集合中的成员进行从小到大的排序。
实现方式:Redis Sorted Set 的内部使用 HashMap 和跳跃表(skipList)来保证数据的存储和有序,HashMap 里放的是成员到 Score 的映射。
而跳跃表里存放的是所有的成员,排序依据是 HashMap 里存的 Score,使用跳跃表的结构可以获得比较高的查找效率,并且在实现上比较简单。
Redis的雪崩、击穿、穿透与解决办法
参考:https://juejin.im/post/5dc416866fb9a04a972e6e3d
缓存雪崩:
现象:
影响轻则,查询变慢,重则当请求并发更高时,出来大面积服务不可用。
原因:
同一时间缓存大面积失效,就像没有缓存一样,所有的请求直接打到数据库上来,DB扛不住挂了,如果是重要的库,例如用户库,那牵联就一大片了,瞬间倒一片。
案例:
电商首页缓存,如果首页的key全部都在某一时刻失效,刚好在那一时刻有秒杀活动,那这样的话就所有的请求都被打到了DB。并发大的情况下DB必然扛不住,没有其他降级之类的方案的话,DBA也只能重启DB,但是这样又会被新的流量搞挂。
解决方案:
批量往redis存数据的时候,把每个key的失效时间加上个随机数,这样的话就能保证数据不会在同一个时间大面积失效。
缓存穿透:
· 现象与原因:
就是指用户不断发起请求的数据,在缓存和DB中都没有,比如DB中的用户ID是自增的,但是用户请求传了-1,或者是一个特别大的数字,这个时候用户很有可能就是一个攻击者,这样的功击会导致DB的压力过大,严重的话就是把DB搞挂了。因为每次都绕开了缓存直接查询DB
· 解决方案:
l 方法一:在接口层增加校验,不合法的参数直接返回。不相信任务调用方,根据自己提供的API接口规范来,作为被调用方,要考虑可能任何的参数传值。
l 方法二:在缓存查不到,DB中也没有的情况,可以将对应的key的value写为null,或者其他特殊值写入缓存,同时将过期失效时间设置短一点,以免影响正常情况。这样是可以防止反复用同一个ID来暴力攻击。
l 方法三:正常用户是不会这样暴力功击,只有是恶意者才会这样做,可以在网关NG作一个配置项,为每一个IP设置访问阀值。
l 方法四:高级用户布隆过滤器(Bloom Filter),这个也能很好地防止缓存穿透。原理就是利用高效的数据结构和算法快速判断出你这个Key是否在DB中存在,不存在你return就好了,存在你就去查了DB刷新KV再return。
Bloom Filter参考:
https://zhuanlan.zhihu.com/p/55574882
https://blog.mimvp.com/article/25772.html
缓存击穿:
· 现象与原因:
跟缓存雪崩类似,但是又有点不一样。雪崩是因为大面积缓存失效,请求全打到DB;而缓存击穿是指一个key是热点,不停地扛住大并发请求,全都集中访问此key,而当此key过期瞬间,持续的大并发就击穿缓存,全都打在DB上。就又引发雪崩的问题。
· 解决方案:
设置热点key不过期。或者加上互斥锁。
Redis的淘汰策略
参考:https://zhuanlan.zhihu.com/p/105587132
如何从海量key里查询出某一固定前缀的key
假如redis里面有一亿个key,其中有某10万个key是以已知的前缀开头的,如何将他们找出来(需要留意细节,摸清数据规模,即问清楚边界)
批量生成redis测试数据
1. LinuxBash下面执行
for((i=1;i<=20000000;i++)); do echo"set k$i v$i" >> /tmp/redisTest.txt ;done;
2. 用vim去掉行尾的^M符号,使用方式如下
vim /tmp/redisTest.txt
:set fileformat=dos #设置文件的格式,通过这句话去掉每行结尾的^M符号
::wq #保存退出
3. 通过redis提供的管道–pipe形式,去跑redis,传入文件的指令批量灌数据,需要花10分钟左右
cat /tmp/redisTest.txt | 路径/redis-5.0.0/src/redis-cli -h 主机ip -p 端口号 --pipe
使用keys命令
KEYS pattern:查找所有符合给定模式pattern的key
从2000万条数据中找到以k1开头的
缺点:
Keys指令一次性返回所有匹配的key
键的数量过大会使服务卡顿,内存消耗过大,对redis-server服务是一个隐患
使用SCAN指令
参考:http://redisdoc.com/database/scan.html
SCAN cursor [MATCH pattern] [COUNT count]
Scan可以无阻塞的提取出匹配的key列表
Scan每次只需只会返回少量元素
可以用于生产环境,不会出现像key命令带来的可能会阻塞服务的问题
基于游标的迭代器,需要基于上一次的游标延续之前的迭代过程。命令中 cursor就是游标
以0作为游标开始一次新的迭代,直到命令返回游标0完成一次便利;当游标参数(即cursor)被置为0时,服务器将开始一次新的迭代,当服务器返回用户游标0时就表示迭代结束
不保证每次只需都返回某个给定数量的元素,支持模糊查询
一次返回的数量不可控,只能是大概率符合count参数
由于游标不一定是递增的,所以取出的key可能是重复的,所以需要去重
如何通过Redis实现分布式锁
https://mp.weixin.qq.com/s/hoZB0wdwXfG3ECKlzjtPdw
https://crossoverjie.top/2018/03/29/distributed-lock/distributed-lock-redis/
https://xiaomi-info.github.io/2019/12/17/redis-distributed-lock/
分布式锁需要解决的问题
互斥性
安全性
避免死锁
容错
SETNX指令
setnx key value:
如果可以不存在,则创建并赋值。返回1
若键 key 已经存在, 则 SETNX 命令不做任何动作。返回0
时间复杂度o(1)
SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写。
原子操作
SETEX 指令
setex key seconds value
将键 key 的值设置为 value , 并将键 key 的生存时间设置为 seconds 秒钟。
如果键 key 已经存在, 那么 SETEX 命令将覆盖已有的值。
SETEX 命令的效果和以下两个命令的效果类似:
SET key value
EXPIRE key seconds # 设置生存时间
原子操作
SET 指令
SET key value [EX seconds] [PXmilliseconds] [NX|XX]
从 Redis 2.6.12 版本开始, SET 命令的行为可以通过一系列参数来修改:
EX seconds :将键的过期时间设置为 seconds 秒。执行 SET key value EX seconds 的效果等同于执行 SETEX keyseconds value 。
PX milliseconds :将键的过期时间设置为 milliseconds 毫秒。执行 SET key value PX milliseconds 的效果等同于执行 PSETEX key milliseconds value 。
NX :只在键不存在时, 才对键进行设置操作。执行 SET key value NX的效果等同于执行 SETNX key value 。
XX :只在键已经存在时, 才对键进行设置操作。
Set操作成功完成时,返回ok,否则返回nil
分布式锁实现
加锁:
privatestaticfinal String SET_IF_NOT_EXIST = "NX";
privatestaticfinal String SET_WITH_EXPIRE_TIME = "PX";
public booleantryLock(String key,String request){
String result = this.jedis.set(LOCK_PREFIX+ key, request, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, 10 * TIME);
if(LOCK_MSG.equals(result)){
returntrue ;
}else {
returnfalse ;
}
}
解锁
public booleanunlock(String key,String request){
//lua script
String script ="if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Object result =null ;
if (jedis instanceof Jedis){
result = ((Jedis)this.jedis).eval(script, Collections.singletonList(LOCK_PREFIX + key), Collections.singletonList(request));
}elseif (jedisinstanceof JedisCluster){
result = ((JedisCluster)this.jedis).eval(script, Collections.singletonList(LOCK_PREFIX + key), Collections.singletonList(request));
}else {
//throw new RuntimeException("instance is error") ;
returnfalse ;
}
if (UNLOCK_MSG.equals(result)){
returntrue ;
}else {
returnfalse ;
}
}
如何使用Redis做异步队列
参考:
https://suiwo.xyz/2019/02/14/redis%E5%A6%82%E4%BD%95%E5%AE%9E%E7%8E%B0%E5%BC%82%E6%AD%A5%E9%98%9F%E5%88%97/
list-RPUSH、LPOP
使用List做为队列,RPUSH生产消息,LPOP消费消息
缺点:
没有等待队列里有值就直接消费
弥补:
可以通过在应用层引入sleep机制去调用LPOP重试
list-RPUSH、BLPOP
BLPOP key [key…] timeout:阻塞直到队列有消息或者超时
缺点:
只能提供一个消费者
Pub/sub:主题订阅模式
发送者(pub)发送消息,订阅者(sub)接收消息
订阅者可以订阅任意数量的频道
订阅消息 subscribe topicName
发布消息 publish topicName message
缺点:
消息的发布时无状态的,无法保证可达
Redis持久化
https://redis.io/topics/persistence
https://www.yasinshaw.com/articles/71
RDB持久化
RDB(快照)持久化:保存某个时间点的全量数据快照
因为redis的读写频率每个时间段是不同的,所以可以根据业务要求灵活配置RDB备份策略
在redis.conf中配置RDB策略
RDB持久化配置
Save:例如如下配置
save 900 1:900秒内,如果有1条写入指令,就触发产生一次快照
save 300 10: 300秒内,如果有10条写入指令,就触发一次快照,如果300秒内有写入指令,但是没有到10条,则等待900秒再触发产生快照
save 60 10000:60秒内,如果有10000条写入指令,就触发一次快照,如果写入条数没有达到10000,则依次向上推
stop-writes-on-bgsave-error yes
当设置为yes,表示当备份出错的时候,主进程就禁止写入操作了,这样可以保证持久化数据的一致性
rdbcompression yes
如果设置为yes,rdb文件需要先压缩后再保存磁盘
建议设置为no,因为redis本身就是CPU密集型服务,再开启压缩会来带更多的CPU消耗,相对于硬盘成本,CPU更值钱
禁用rdb配置
在save最后一行后面加上 save “”
dump.rdb
采用rdb持久化,redis服务会定时生成全量的rdb文件-dump.rdb文件并保存在磁盘上
配置文件中设置备份文件的名字
dbfilename dump.rdb
配置文件中设置备份文件的路径
dir /data
手工触发RDB持久化
Save命令:阻塞Redis的服务器进程,直到RDB文件被创建完毕
BGSave命令:Fork出一个子进程来创建RDB文件,不阻塞服务器进程
自动触发RDB持久化
根据redis.conf配置里的save m n 定时触发(用的是bgsave)
主从复制时,主节点自动触发
执行debug reload
执行shutdown并且没有开启aof持久化
LASTSAVE
返回最近一次 Redis 成功将数据保存到磁盘上的时间,以 UNIX 时间戳格式表示。
返回值是一个 UNIX 时间戳。
Bgsave的原理
系统调用fork():创建进程,实现copy-on-write,
传统模式下, fork()函数创建子进程时,直接把所有资源复制给子进程,此方法效率很低,实现简单,很多复制的数据可能子进程并没有用处
Linux改进fork()创建子进程方式,内核只为子进程创建虚拟空间,主进程和子进程指向同一块物理空间,只用主进程发生更改时,才会对子进程独立分配物理空间
Copy-on-write
如果有多个调用者同时要求相同资源(如内存或磁盘上的数据存储),他们会共同获取相同的指针指向相同的资源,直到某个调用者试图修改资源的内容时,系统才会真正复制一份专用副本给该调用者,而其他调用者所见到的最初的资源仍然保持不变
RDB持久化缺点
内存数据的全量同步,数据量大会由于I/O而严重影响性能
可能会因为Redis挂掉而丢失从当前至最近一次快照期间的数据
AOF持久化
AOF(Append-only-file)持久化:保存写状态
记录下除了查询以外的所有变更数据库状态的指令。
RDB持久化是对数据库状态做快照;AOF持久化是保存所有变更数据库状态的指令
以append的形式追加指令到aof文件中(增量)
参考:
https://redisbook.readthedocs.io/en/latest/internal/aof.html
https://zhuanlan.zhihu.com/p/75894903
https://juejin.im/post/5b70dfcf518825610f1f5c16
AOF持久化配置
Appendonly on
设置为yes,开启AOF持久化,设置为no 关闭AOF持久化
Appendfilename “appendonly.aof”
设置aof文件名称
Appendfsync --aof文件的写入方式
appendfsync always:一旦缓存区的内容发生变化,总是将缓存区的内容写入aof文件
appendfsync everysec:【默认】,每隔1秒将缓存区的内容更新到aof文件
appendfsync no:交由操作系统决定什么时候更新到aof文件,操作系统一般会等缓存区被填满了才会同步aof文件
auto-aof-rewrite-percentage 100
aof重写比例 默认100%
auto-aof-rewrite-min-size 64mb
aof重写最小大小 默认64mb
AOF持久化流程
日志重写
日志重写可以解决AOF文件大小不断增大的问题
日志重写原理,流程如下
Fork()子进程
子进程把新的AOF写到一个临时文件里,不依赖原来的AOF文件
主进程持续将新的变动同时写到内存和原来的AOF里
主进程获取子进程重写AOF的完成信号,往新AOF同步增量变动
使用新的AOF文件替换掉旧的AOF文件
手动触发AOF持久化
BGREWRITEAOF
执行一个 AOF文件 重写操作。重写会创建一个当前 AOF 文件的体积优化版本。
即使 BGREWRITEAOF 执行失败,也不会有任何数据丢失,因为旧的 AOF 文件在 BGREWRITEAOF 成功之前不会被修改。
重写操作只会在没有其他持久化工作在后台执行时被触发,也就是说:
如果 Redis 的子进程正在执行快照的保存工作,那么 AOF 重写的操作会被预定(scheduled),等到保存工作完成之后再执行 AOF 重写。在这种情况下, BGREWRITEAOF 的返回值仍然是 OK ,但还会加上一条额外的信息,说明 BGREWRITEAOF 要等到保存操作完成之后才能执行。在 Redis 2.6 或以上的版本,可以使用 INFO [section] 命令查看 BGREWRITEAOF 是否被预定。
如果已经有别的 AOF 文件重写在执行,那么 BGREWRITEAOF 返回一个错误,并且这个新的 BGREWRITEAOF 请求也不会被预定到下次执行。
从 Redis 2.4 开始, AOF 重写由 Redis 自行触发, BGREWRITEAOF 仅仅用于手动触发重写操作。
自动触发AOF持久化
服务器在 AOF 功能开启的情况下, 会维持以下三个变量:
记录当前 AOF 文件大小的变量 aof_current_size 。
记录最后一次 AOF 重写之后, AOF 文件大小的变量 aof_rewrite_base_size 。
增长百分比变量auto-aof-rewrite-percentage
每次当 serverCron 函数执行时, 它都会检查以下条件是否全部满足,如果是的话, 就会触发自动的 AOF 重写:
没有 BGSAVE 命令在进行。
没有 BGREWRITEAOF 在进行。
当前 AOF 文件大小大于 auto-aof-rewrite-min-size
当前 AOF 文件大小和最后一次 AOF 重写后的大小之间的比率大于等于指定的增长百分比。
默认情况下, 增长百分比为 100% , 也即是说, 如果前面三个条件都已经满足,并且当前 AOF 文件大小比最后一次 AOF 重写时的大小要大一倍的话,那么触发自动 AOF 重写。
怎么从 RDB 持久化切换到 AOF 持久化
在 Redis 2.2 或以上版本,可以在不重启的情况下,从 RDB 切换到 AOF :
为最新的 dump.rdb 文件创建一个备份。
将备份放到一个安全的地方。
执行以下两条命令:
a) redis-cli> CONFIG SETappendonly yes
b) redis-cli> CONFIG SET save""
确保命令执行之后,数据库的键的数量没有改变。
确保写命令会被正确地追加到 AOF 文件的末尾。
步骤 3 执行的第一条命令开启了AOF 功能:Redis 会阻塞直到初始 AOF 文件创建完成为止,之后 Redis 会继续处理命令请求, 并开始将写入命令追加到 AOF 文件末尾。
步骤 3 执行的第二条命令用于关闭RDB 功能。这一步是可选的, 如果你愿意的话, 也可以同时使用 RDB 和 AOF 这两种持久化功能。
Redis数据恢复
RDB和AOF文件共存的情况恢复流程
AOF与RDB比较
RDB优点:全量数据快照,文件小、恢复快
RDB缺点:无法保存最近一次快照之后的数据
AOF优点:可读性高,适合保存增量数据,数据不易丢失
AOF缺点:文件体积大,恢复时间长
Redis如何持久化
RDB-AOF混合持久化方式:BGSAVE做镜像全量持久化,AOF做增量持久化
Redis的Pipeline
Pipeline和Linux的管道类似
Redis基于请求/响应模型,单个请求处理需要一一应答
Pipeline批量执行指令,节省多次IO往返时间
有顺序依赖的指令建议分批发送
说说Redis的主从复制
Redis 单节点存在单点故障问题,为了解决单点问题,一般都需要对 Redis 配置从节点,然后使用哨兵来监听主节点的存活状态,如果主节点挂掉,从节点能继续提供缓存功能
主从配置结合哨兵模式能解决单点故障问题,提高 Redis 可用性。
先说主从复制
从节点仅提供读操作,主节点提供读和写操作。对于读多写少的状况,可给主节点配置多个从节点,从而提高响应效率。
主从复制过程:
从节点执行 slaveof[masterIP][masterPort],保存主节点信息。
1. 从节点中的定时任务发现主节点信息,建立和主节点的 Socket连接。
2. 从节点发送 Ping 信号,主节点返回 Pong,两边能互相通信。
3. 连接建立后,主节点将所有数据发送给从节点(数据同步)。
4. 主节点把当前的数据同步给从节点后,便完成了复制的建立过程。接下来,主节点就会持续的把写命令发送给从节点,保证主从数据一致性。
详细介绍同步之前,先介绍几个概念:
runId:每个 Redis 节点启动都会生成唯一的 uuid,每次 Redis 重启后,runId都会发生变化。
offset:主节点和从节点都各自维护自己的主从复制偏移量 offset,当主节点有写入命令时,offset=offset+命令的字节长度。
从节点在收到主节点发送的命令后,也会增加自己的 offset,并把自己的 offset 发送给主节点。
这样,主节点同时保存自己的 offset 和从节点的 offset,通过对比 offset 来判断主从节点数据是否一致。
repl_backlog_size:保存在主节点上的一个固定长度的先进先出队列,默认大小是 1MB。
主节点发送数据给从节点过程中,主节点还会进行一些写操作,这时候的数据存储在复制缓冲区中。
从节点同步主节点数据完成后,主节点将缓冲区的数据继续发送给从节点,用于部分复制。
主节点响应写命令时,不但会把命名发送给从节点,还会写入复制积压缓冲区,用于复制命令丢失的数据补救。
上面是 Psync 的执行流程,从节点发送 psync[runId][offset] 命令,主节点有三种响应:
FULLRESYNC:第一次连接,进行全量复制
CONTINUE:进行部分复制
ERR:不支持 psync 命令,进行全量复制
全量复制
从节点发送 psync ? -1 命令(因为第一次发送,不知道主节点的 runId,所以为?,因为是第一次复制,所以 offset=-1)。
主节点发现从节点是第一次复制,返回 FULLRESYNC {runId} {offset},runId 是主节点的 runId,offset 是主节点目前的 offset。
从节点接收主节点信息后,保存到 info 中。
主节点在发送 FULLRESYNC 后,启动 bgsave 命令,生成 RDB 文件(数据持久化)。
主节点发送 RDB 文件给从节点。到从节点加载数据完成这段期间主节点的写命令放入缓冲区。
从节点清理自己的数据库数据。
从节点加载 RDB 文件,将数据保存到自己的数据库中。如果从节点开启了 AOF,从节点会异步重写 AOF 文件。
部分复制
部分复制主要是 Redis 针对全量复制的过高开销做出的一种优化措施,使用psync[runId][offset] 命令实现。当从节点正在复制主节点时,如果出现网络闪断或者命令丢失等异常情况时,从节点会向主节点要求补发丢失的命令数据,主节点的复制积压缓冲区将这部分数据直接发送给从节点。这样就可以保持主从节点复制的一致性。补发的这部分数据一般远远小于全量数据。
主从连接中断期间主节点依然响应命令,但因复制连接中断命令无法发送给从节点,不过主节点内的复制积压缓冲区依然可以保存最近一段时间的写命令数据。
当主从连接恢复后,由于从节点之前保存了自身已复制的偏移量和主节点的运行 ID。因此会把它们当做 psync 参数发送给主节点,要求进行部分复制。
主节点接收到 psync 命令后首先核对参数 runId 是否与自身一致,如果一致,说明之前复制的是当前主节点。之后根据参数 offset 在复制积压缓冲区中查找,如果 offset 之后的数据存在,则对从节点发送+COUTINUE 命令,表示可以进行部分复制。因为缓冲区大小固定,若发生缓冲溢出,则进行全量复制。
主节点根据偏移量把复制积压缓冲区里的数据发送给从节点,保证主从复制进入正常状态。
说说主从复制的问题与解决办法(哨兵)
参考:https://juejin.im/post/5b7d226a6fb9a01a1e01ff64
主从复制会存在以下问题:
一旦主节点宕机,从节点晋升为主节点,同时需要修改应用方的主节点地址,还需要命令所有从节点去复制新的主节点,整个过程需要人工干预。
主节点的写能力受到单机的限制。
主节点的存储能力受到单机的限制。
原生复制的弊端在早期的版本中也会比较突出,比如:Redis 复制中断后,从节点会发起 psync。
此时如果同步不成功,则会进行全量同步,主库执行全量备份的同时,可能会造成毫秒或秒级的卡顿。
哨兵-解决主从问题
如图,是 RedisSentinel(哨兵)的架构图。Redis Sentinel(哨兵)主要功能包括主节点存活检测、主从运行情况检测、自动故障转移、主从切换。
Redis Sentinel 最小配置是一主一从。Redis 的 Sentinel 系统可以用来管理多个 Redis 服务器。
该系统可以执行以下四个任务:
· 监控:不断检查主服务器和从服务器是否正常运行。
· 通知:当被监控的某个 Redis 服务器出现问题,Sentinel 通过 API 脚本向管理员或者其他应用程序发出通知。
· 自动故障转移:当主节点不能正常工作时,Sentinel 会开始一次自动的故障转移操作,它会将与失效主节点是主从关系的其中一个从节点升级为新的主节点,并且将其他的从节点指向新的主节点,这样人工干预就可以免了。
· 配置提供者:在 Redis Sentinel 模式下,客户端应用在初始化时连接的是 Sentinel 节点集合,从中获取主节点的信息。
哨兵的工作原理
1. 每个 Sentinel 节点都需要定期执行以下任务:每个 Sentinel 以每秒一次的频率,向它所知的主服务器、从服务器以及其他的Sentinel 实例发送一个 PING 命令。(如图)
2. 如果一个实例距离最后一次有效回复 PING 命令的时间超过 down-after-milliseconds 所指定的值,那么这个实例会被Sentinel 标记为主观下线。(如图)
3. 如果一个主服务器被标记为主观下线,那么正在监视这个服务器的所有Sentinel 节点,要以每秒一次的频率确认主服务器的确进入了主观下线状态。
4. 如果一个主服务器被标记为主观下线,并且有足够数量的Sentinel(至少要达到配置文件指定的数量)在指定的时间范围内同意这一判断,那么这个主服务器被标记为客观下线。
5. 一般情况下,每个 Sentinel 会以每 10 秒一次的频率向它已知的所有主服务器和从服务器发送 INFO 命令。
当一个主服务器被标记为客观下线时,Sentinel 向下线主服务器的所有从服务器发送 INFO 命令的频率,会从 10 秒一次改为每秒一次。
6. Sentinel 和其他 Sentinel 协商客观下线的主节点的状态,如果处于 SDOWN 状态,则投票自动选出新的主节点,将剩余从节点指向新的主节点进行数据复制。
7. 当没有足够数量的 Sentinel 同意主服务器下线时,主服务器的客观下线状态就会被移除。
当主服务器重新向 Sentinel 的 PING 命令返回有效回复时,主服务器的主观下线状态就会被移除。
Redis集群
如何从海量数据里快速找到所需
分片:按照某种规则去划分数据,分散存储在多个节点上
常规的按照哈希划分无法实现节点的动态增减,需要一致性哈希算法
一致性哈希算法参考