redis
2021-01-15 23:06:01 8 举报
AI智能生成
redis
作者其他创作
大纲/内容
基础
数据结构
key-value数据库
key的类型只能是String
value类型
String
最多支持的长度为512M
使用场景
信息缓存、计数器、分布式锁
Hash
hashmap
List
有序的,按照插入顺序排序,元素可重复的队列
延时队列
Set
无序性和确定性(不重复
Sorted Set
有序,无重复值
持久化
AOF持久化
记录每次对服务器写的操作
配置
appendonly yes,默认不开启
模式
每次有新命令追加到 AOF 文件时就执行一次 fsync :非常慢,也非常安全
每秒 fsync 一次:足够快(和使用 RDB 持久化差不多),并且在故障时只会丢失 1 秒钟的数据
推荐
同步刷盘会导致主线程执行效率下降,AOF 有一个异步独立线程以及自己的任务队列
从不 fsync :将数据交给操作系统来处理。更快,也更不安全的选择
AOF命令以redis协议追加保存每次写的操作到文件末尾
所有的写入命令会追加到aof_buf(缓冲区)中
AOF缓冲区根据对应的策略向硬盘做同步操作
能对AOF文件进行后台重写,使得AOF文件的体积不至于过大
bgrewriteaof 重写
AOF文件持续增长而过大时,会fork出一条新进程来将文件重写(也是先写临时文件最后再rename),遍历新进程的内存中数据,每条记录有一条的Set语句。重写aof文件的操作,并没有读取旧的aof文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件,这点和快照有点类似。
copy_on_write
何时会触发重写操作
系统载入时或者上次重写完毕时,Redis会记录此时AOF大小,设为base_size,
如果Redis的AOF当前大小>= base_size +base_size*100% (默认)且当前大小>=64mb(默认)的情况下,Redis会对AOF进行重写
如果Redis的AOF当前大小>= base_size +base_size*100% (默认)且当前大小>=64mb(默认)的情况下,Redis会对AOF进行重写
重写流程
1.fork子进程重写aof文件
2.执行client发来的命令请求;
将写命令追加到现有的AOF文件中;
将写命令追加到AOF重写缓存中。
3.当子进程完成对AOF文件重写之后,会向父进程发送一个完成信号
将AOF重写缓存中的内容全部写入到新的AOF文件中;
这个时候新的AOF文件所保存的数据库状态和服务器当前的数据库状态一致;
这个时候新的AOF文件所保存的数据库状态和服务器当前的数据库状态一致;
对新的AOF文件进行改名,原子的覆盖原有的AOF文件;完成新旧两个AOF文件的替换
当服务器重启的时候会重新执行这些命令来恢复原始的数据
优点
备份机制更稳健,丢失数据概率更低,可读的日志文本
即使由于某些原因(磁盘空间已满,写的过程中宕机等等)未执行完整的写入命令,也可以使用redis-check-aof修复
适合做灾难的误删除紧急操作
缺点
比起RDB占用更多的磁盘空间
恢复备份速度要慢
每次读写都同步的话,有一定的性能压力
BRPOPLPUSH这样的阻塞命令)罕见bug
导致在重新加载时生成的 AOF 不能完全复制相同的数据集
aof开启会对写的QPS造成影响
RDB持久化
能够在指定的时间间隔能对你的数据进行快照存储
例子save 60 10000
60秒后,至少有10000变更操作,才会snapshot
“间隔时间”和“变更次数”共同决定,同时符合2个条件才会触发snapshot,
否则“变更次数”会被继续累加到下一个“间隔时间”上
否则“变更次数”会被继续累加到下一个“间隔时间”上
snapshot过程中并不阻塞客户端请求。snapshot首先将数据写入临时文件,当成功结束后,将临时文件重名为dump.rdb。
触发机制
1.主动触发save命令:阻塞当前redis服务器,直到RDB过程完成为止,如果redis数据较多,可能造成redis进程的长时间阻塞。
2.自动触发 满足配置的save两个条件后 bgsave: redis执行fork创建子进程,
RDB持久化过程由这个子进程负责,完成之后结束。
RDB持久化过程由这个子进程负责,完成之后结束。
bgsave流程
1.执行bgsave命令,Redis父进程判断当前是否存在正在执行的子进程,如RDB/AOF子进程,如果存在则直接返回。
2.父进程执行fork操作创建子进程,fork一个子进程会阻塞父进程。
3.父进程fork完成之后,bgsave命令返回一个信息给父进程,这个时候父进程并不会阻塞。
4.子进程创建RDB文件,根据父进程内存生成临时快照,完成之后对原有的文件进行原子替换。
copy_on_write
5.子进程发送信号给父进程表示完成,父进程更新统计信息。
快照指bgsave那一刻的内存快照,copy_on_write 将那一刻的数据fork,父进程数据修改,
会另对当时修改的内存页创建副本,其他内存依然共享不变,即修改后的数据,rdb文件没有
会另对当时修改的内存页创建副本,其他内存依然共享不变,即修改后的数据,rdb文件没有
3.如果从节点执行全量复制操作,主节点自动执行bgsave生成RDB文件并发送给从节点。
4.默认情况下执行shutdown命令时,如果没有开启AOF持久化功能则 自动执行bgsave。
优点
不同时间不同版本的数据备份
紧凑单一文件,方便传送远端,适合灾难恢复
保存rdb时,只需要父进程阻塞fork出子进程,然后非阻塞,父进程不做其他io操作
恢复速度比aof快
缺点
rdb时间间隔长,宕机易丢失几分钟数据
RDB 子进程保存数据集到硬盘,如果数据集比较大,会持续消耗机器的io,导致父进程不能毫秒级响应客户端请求
同时开启RDB和AOF
RDB与AOF同时开启 默认无脑加载AOF的配置文件
相同数据集,AOF文件要远大于RDB文件,恢复速度慢于RDB
AOF运行效率慢于RDB,但是同步策略效率好,不同步效率和RDB相同
copy_on_write原理
如果有多个调用者(callers)同时请求相同资源(如内存或磁盘上的数据存储),他们会共同获取相同的指针指向相同的资源,直到某个调用者试图修改资源的内容时,系统才会真正复制一份专用副本(private copy)给该调用者,而其他调用者所见到的最初的资源仍然保持不变。这过程对其他的调用者都是透明的(transparently)。此作法主要的优点是如果调用者没有修改该资源,就不会有副本(private copy)被建立,因此多个调用者只是读取操作时可以共享同一份资源。
linux copy_on_write
子进程共享父进程的物理空间,当父子进程中有更改相应段的行为发生时,再为子进程相应的段分配物理空间
减少了Fork的开销
文件系统Copy On Write
不会直接在原来的数据位置上进行操作,而是重新找个位置修改
比如说:要修改数据块A的内容,先把A读出来,写到B块里面去。如果这时候断电了,原来A的内容还在!
线程模型
redis线程模型
单线程Reactor
服务端处理客户端命令的过程
1.文件事件处理器使用 I/O 多路复用(multiplexing)程序来同时监听多个套接字,即将套接字的fd注册到epoll上,当被监听的套接字准备好执行连接应答(accept)、读取(read)、写入(write)、关闭(close)等操作时,与操作相对应的文件事件就会产生。
2.尽管多个文件事件可能会并发地出现,但I/O多路复用程序总是会将所有产生事件的套接字都推到一个队列里面,然后通过这个队列,以有序(sequentially)、同步(synchronously)、每次一个套接字的方式向文件事件分派器传送套接字。
3.此时文件事件处理器就会调用套接字之前关联好的事件处理器来处理这些事件。文件事件处理器以单线程方式运行,这就是之前一直提到的Redis线程模型中,效率很高的那个单线程。
Redis单线程效率高的原因
纯内存访问:数据存放在内存中,内存的响应时间大约是100纳秒,这是Redis每秒万亿级别访问的重要基础。
非阻塞I/O:Redis采用epoll做为I/O多路复用技术的实现,再加上Redis自身的事件处理模型将epoll中的连接,读写,关闭都转换为了事件,不在I/O上浪费过多的时间。
单线程避免了线程切换和竞态产生的消耗。
缓存的各种问题
缓存穿透
场景
缓存穿透是指用户请求的数据在缓存中不存在即没有命中,同时在数据库中也不存在,导致用户每次请求该数据都要去数据库中查询一遍,然后返回空。
影响
如果有恶意攻击者不断请求系统中不存在的数据,会导致短时间大量请求落在数据库上,造成数据库压力过大,甚至击垮数据库系统
解决方案
布隆过滤器
不同数据结构的查找效率
线性表存储
查找时间复杂度为O(N)
平衡二叉排序树(AVL、红黑树)存储
查找时间复杂度为O(logN)
哈希表存储
考虑到哈希碰撞,整体时间复杂度也要O[log(n/m)]
设计思想
长度为m比特的位数组,k个哈希函数 组成,位数组初始化均为0
当要向布隆过滤器中插入一个元素时,该元素经过k个哈希函数计算产生k个哈希值,
以哈希值作为位数组中的下标,将所有k个对应的比特值由0置为1
以哈希值作为位数组中的下标,将所有k个对应的比特值由0置为1
查询元素
同样将其经过哈希函数计算产生哈希值,然后检查对应的k个比特值
如果有任意一个比特为0
表明该元素一定不在集合中
如果所有比特均为1
表明该集合有可能性在集合中,hash冲突
优点
节省空间:不需要存储数据本身,只需要存储数据对应hash比特位
时间复杂度低:插入和查找的时间复杂度都为O(k),k为哈希函数的个数
缺点
存在假阳性:布隆过滤器判断存在,可能出现元素不在集合中;判断准确率取决于哈希函数的个数
不能删除元素:如果一个元素被删除,但是却不能从布隆过滤器中删除,这也是造成假阳性的原因了
布隆过滤器适用场景
爬虫系统url去重
垃圾邮件过滤
黑名单
murmurhash,经过 5 次 Hash(37, 41, 61, 79, 83), 使用 256m 存储 4kw 数据, 31 个碰撞
返回空对象
当缓存未命中,查询持久层也为空,可以将返回的空对象写到缓存中,
这样下次请求该key时直接从缓存中查询返回空对象,请求不会落到持久层数据库
这样下次请求该key时直接从缓存中查询返回空对象,请求不会落到持久层数据库
为了避免存储过多空对象,通常会给空对象设置一个过期时间。
缺点
如果有大量的key穿透,缓存空对象会占用宝贵的内存空间
空对象的key设置了过期时间,在这段时间可能会存在缓存和持久层数据不一致的场景
缓存击穿
场景
指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,
持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。
持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。
影响
数据库瞬时压力骤增,造成大量请求阻塞
解决方案
使用互斥锁
双检分布式锁,如果为空,加锁,在判断为空则查数据库重建缓存
就是让一个线程回写缓存,其他线程等待回写缓存线程执行完,重新读缓存即可
同一时间只有一个线程读数据库然后回写缓存,其他线程都处于阻塞状态。
如果是高并发场景,大量线程阻塞势必会降低吞吐量
如果是高并发场景,大量线程阻塞势必会降低吞吐量
热点数据永不过期
物理不过期,针对热点key不设置过期时间
问题:可能一直访问的是旧数据
用一个异步线程一直更新缓存
逻辑过期,把过期时间存在key对应的value里,如果发现要过期了,
通过一个分布式锁后台的异步线程进行缓存的构建,更新缓存的value和过期时间
通过一个分布式锁后台的异步线程进行缓存的构建,更新缓存的value和过期时间
推荐
缓存雪崩
场景
指缓存中数据大批量到过期时间,而查询数据量巨大,请求直接落到数据库上
影响
引起数据库压力过大甚至宕机
与缓存击穿的影响
缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库
解决方案
均匀过期
设置不同的过期时间,让缓存失效的时间点尽量均匀。通常可以为有效期增加随机值或者统一规划有效期。
加互斥锁重建缓存
跟缓存击穿解决思路一致,同一时间只让一个线程构建缓存,其他线程阻塞排队
缓存永不过期
跟缓存击穿解决思路一致,缓存在物理上永远不过期,用一个异步的线程更新缓存。
双层缓存策略
使用主备两层缓存
主缓存:有效期按照经验值设置,设置为主读取的缓存,主缓存失效后从数据库加载最新值。
备份缓存:有效期长,读取主缓存为空时读取的缓存,主缓存更新时需要同步更新备份缓存。
缓存预热
场景
缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统,这样就可以避免在用户请求的时候,
先查询数据库,然后再将数据回写到缓存。
先查询数据库,然后再将数据回写到缓存。
影响
如果不进行预热, 那么 Redis 初始状态数据为空,系统上线初期,对于高并发的流量,都会访问到数据库中, 对数据库造成流量的压力
解决方案
数据量不大的时候,工程启动的时候进行加载缓存动作
数据量大的时候,设置一个定时任务脚本,进行缓存的刷新
数据量太大的时候,优先保证热点数据进行提前加载到缓存
缓存降级
场景
缓存降级是指缓存失效或缓存服务器挂掉的情况下,不去访问数据库,直接返回默认数据或访问服务的内存数据
一旦缓存出现异常,可以直接使用服务的内存数据,从而避免数据库遭受巨大压力
内存过期+淘汰机制
如何配置最大内存
通过配置文件配置
maxmemory 1024mb //设置Redis最大占用内存大小为1024M
maxmemory默认配置为0,在64位操作系统下redis最大内存为操作系统剩余内存,在32位操作系统下redis最大内存为3GB
通过动态命令配置
config set maxmemory 200mb //设置Redis最大占用内存大小为200M
内存过期策略
主库过期策略
定期删除+惰性删除策略
定期删除
redis默认每隔100ms检查,随机抽取进行检查是否有过期的key,有过期key则删除
定期扫描策略
把设置过期key的放入独立字典表
定时扫描,100ms一次,不会扫描全量的key
贪心策略
1.从过期集合随机抽取20个key
2.删除这20个中已经过期的key
3.如果过期的key超过1/4,重复1
为了保证大量key同一时间过期,导致过度循环扫描,以及内存管理器频繁回收内存页,
也会消耗一定的cpu,同时影响redis主线程的业务请求处理
增加了扫描时间上限,默认不超过25ms
也会消耗一定的cpu,同时影响redis主线程的业务请求处理
增加了扫描时间上限,默认不超过25ms
单个客户端等待25ms,101个客户端,第101个客户端需要等待2500ms,卡顿积少成多
避免大批量数据同时失效,要给key设置一个随机范围
惰性删除
获取某个key的时候,redis会检查一下,这个key如果设置了过期时间那么是否过期了?如果过期了此时就会删除
为什么不用定时删除策略?
定时删除,用一个定时器来负责监视key,过期则自动删除。虽然内存及时释放,但是十分消耗CPU资源。
在大并发请求下,CPU要将时间应用在处理请求,而不是删除key,因此没有采用这一策略.
在大并发请求下,CPU要将时间应用在处理请求,而不是删除key,因此没有采用这一策略.
只采用定期删除策略呢?
会导致很多key到时间没有删除
采用定期删除+惰性删除就没其他问题了么?
如果定期删除没删除key。然后你也没即时去请求key,也就是说惰性删除也没生效,
redis的内存会越来越高
redis的内存会越来越高
需要配合内存淘汰策略一起
从库过期策略
从库过期处理是被动,不会进行过期扫描
当主库key到期,会在AOF文件里面加一条del的指令,同步到从库,从库根据这个指令删除过期的key
问题
指令同步是异步执行的,会导致主库数据已过期,但是从库延迟数据还在
会导致集群环境下的分布式锁算法漏洞
锁已释放,读从库还有数据,获取不到锁
淘汰策略
noeviction
默认策略,对于写请求直接返回错误,不进行淘汰。
allkeys-lru
最近最少使用。从所有的key中使用近似LRU算法进行淘汰
volatile-lru
最近最少使用。从设置了过期时间的key中使用近似LRU算法进行淘汰
allkeys-random
从所有的key中随机淘汰
volatile-random
从设置了过期时间的key中随机淘汰
volatile-ttl
在设置了过期时间的key中根据key的过期时间进行淘汰,越早过期的越优先被淘汰。
allkeys-lfu
最少使用频率。从所有的key中使用近似LFU算法进行淘汰
volatile-lfu
最少使用频率。从设置了过期时间的key中使用近似LFU算法进行淘汰
lru算法
核心思想
如果一个数据在最近一段时间没有被用到,那么将来被使用到的可能性也很小,所以就可以被淘汰掉
常规lru算法步骤
新数据直接插入到列表头部
缓存数据被命中,将数据移动到列表头部
缓存已满的时候,移除列表尾部数据。
java实现 LinkedHashMap
accessOrder true
LinkedHashMap 重写了hashMap的 newNode和afterNodeAccess方法,当新节点或者新访问时,把直接移到最后
mysql的lru
最近最少使用。即最频繁使用的页在LRU链表的前端,
最少使用的页在LRU链表的尾端。当缓存池不能存放取到的页时,首先将LRU链表中释放尾端的页。
最新读取的页面,并不是放入前端而是放入中间(5:3,插入到5/8),防止热块被新读取的大量数据挤出LRU链表。
当LRU里的数据块被修改时,变成脏块,会被添加到FLUSH链表中。
最少使用的页在LRU链表的尾端。当缓存池不能存放取到的页时,首先将LRU链表中释放尾端的页。
最新读取的页面,并不是放入前端而是放入中间(5:3,插入到5/8),防止热块被新读取的大量数据挤出LRU链表。
当LRU里的数据块被修改时,变成脏块,会被添加到FLUSH链表中。
redis的lru
近似LRU算法通过随机采样法淘汰数据,每次随机出5个(默认)key,从里面淘汰掉最近最少使用的key,
如果内存还是不够,继续随机采样淘汰
如果内存还是不够,继续随机采样淘汰
maxmemory-samples 修改采样数量配置
maxmenory-samples配置的越大,淘汰的结果越接近于严格的LRU算法,但因此耗费的CPU也很高
Redis为了实现近似LRU算法,给每个key增加了一个额外增加了一个24bit的字段,用来存储该key最后一次被访问的时间戳
Redis3.0对近似LRU的优化
新算法会维护一个候选池(大小为16),池中的数据根据访问时间进行排序
第一次随机选取的key都会放入池中
随后每次随机选取的key只有在访问时间小于池中最小的时间才会放入池中,直到候选池被放满
当放满后,如果有新的key需要放入,则将池中最后访问时间最大(最近被访问)的移除
当需要淘汰的时候,则直接从池中选取最近访问时间最小(最久没被访问)的key淘汰掉就行
可以用最大堆算法实现
redis LRU只有懒惰处理
内存超出 maxmemory,就会执行一次LRU 淘汰算法
LFU算法
核心思想
根据key的最近被访问的频率进行淘汰,很少被访问的优先被淘汰,被访问的多的则被留下来
能更好的表示一个key被访问的热度
不会出现这种情况,因为使用一次并不会使一个key成为热点数据
事务机制
Redis事务生命周期
开启事务:使用MULTI开启一个事务
命令入队列:每次操作的命令都会加入到一个队列中,但命令此时不会真正被执行
提交事务:使用EXEC命令提交事务,开始顺序执行队列中的命令
watch命令,在MULTI之前监听一个key,如果key值有变更,则事务EXEX执行失败
redis事务是否是原子性
原子性定义
一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。
事务在执行过程中发生错误,会被恢复(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样
事务在执行过程中发生错误,会被恢复(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样
非原子性
在exec执行后所产生的错误中,即使事务中有某个/某些命令在执行时产生了错误,事务中的其他命令仍然会
继续执行。Redis在事务失败时不进行回滚,而是继续执行余下的命令
继续执行。Redis在事务失败时不进行回滚,而是继续执行余下的命令
Redis为什么不支持回滚(roll back)
Redis命令只会因为错误的语法而失败(这些问题不能在入队时发现),或是命令用在了错误类型的键上面
失败的命令不是Redis所致,而是由编程错误造成的,这样错误应该在开发的过程中被发现,生产环境中不应出现的错误。
就是在程序的运行环境中不应该出现语法的错误。而Redis能够保证正确的命令一定会被执行。
失败的命令不是Redis所致,而是由编程错误造成的,这样错误应该在开发的过程中被发现,生产环境中不应出现的错误。
就是在程序的运行环境中不应该出现语法的错误。而Redis能够保证正确的命令一定会被执行。
事务相关命令
watch
CAS,被WATCH的键会被监视,并会发觉这些键是否被改动过了。如果有至少一个被监视的键在 EXEC 执行之前被修改了,
那么整个事务都会被取消, EXEC 返回nil-reply来表示事务已经失败
那么整个事务都会被取消, EXEC 返回nil-reply来表示事务已经失败
multi
开启一个事务,该命令后的指令入队,当exec时,队列中的命令会一起执行
unwatch
取消watch命令对所有key的监视
discard
放弃事务,事务队列清空
exec
触发并执行事务中的所有命令
主从复制
主从
概念
指将一台Redis服务器的数据,复制到其他的Redis服务器
前者称为主节点(master),后者称为从节点(slave)
数据的复制是单向的,只能由主节点到从节点
作用
数据冗余
主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式
故障恢复
当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余
负载均衡
在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务,分担服务器负载
尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量
高可用基石
主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础
主从复制实现原理
连接建立阶段
主从节点之间建立连接,为数据同步做好准备
1.保存主节点信息
slaveof命令是异步的,在从节点上执行slaveof命令,从节点立即向客户端返回ok,从节点服务器内部维护了两个字段,
即masterhost和masterport字段,用于存储主节点的ip和port信息。
即masterhost和masterport字段,用于存储主节点的ip和port信息。
2.建立socket连接
从节点每秒1次调用复制定时函数replicationCron(),如果发现了有主节点可以连接,
便会根据主节点的ip和port,创建socket连接
便会根据主节点的ip和port,创建socket连接
3.发送ping命令
从节点成为主节点的客户端之后,发送ping命令进行首次请求,
目的是:检查socket连接是否可用,以及主节点当前是否能够处理请求。
目的是:检查socket连接是否可用,以及主节点当前是否能够处理请求。
响应
返回pong
说明socket连接正常,且主节点当前可以处理请求,复制过程继续
超时
一定时间后从节点仍未收到主节点的回复,说明socket连接不可用,则从节点断开socket连接,并重连
返回pong以外的结果
如果主节点返回其他结果,如正在处理超时运行的脚本,说明主节点当前无法处理命令,则从节点断开socket连接,并重连
4.身份验证
5.发送从节点端口信息
身份验证之后,从节点会向主节点发送其监听的端口号(前述例子中为6380),
主节点将该信息保存到该从节点对应的客户端的slave_listening_port字段中
主节点将该信息保存到该从节点对应的客户端的slave_listening_port字段中
数据同步阶段
主从节点之间的连接建立以后,便可以开始进行数据同步
从节点向主节点发送psync命令(Redis2.8以前是sync命令),开始同步。
全量复制
部分复制
命令传播阶段
数据同步阶段完成后,主从节点进入命令传播阶段;
在这个阶段主节点将自己执行的写命令发送给从节点,从节点接收命令并执行,从而保证主从节点数据的一致性
命令传播是异步的过程,即主节点发送写命令后并不会等待从节点的回复
消息丢失
异步复制,如果主节点挂掉,从节点可能没有收到全部的同步消息,这部分消息就丢失了
min-slaves-to-write 1
主节点必须至少有一个从节点在进行正常复制,否则就停止对外写服
务,丧失可用性
务,丧失可用性
min-slaves-max-lag 10
单位是秒,表示如
果 10s 没有收到从节点的反馈,就意味着从节点同步不正常,要么网络断开了,要么一直没
有给反馈
果 10s 没有收到从节点的反馈,就意味着从节点同步不正常,要么网络断开了,要么一直没
有给反馈
哨兵模式
原理
能够自动完成故障发现和故障转移,并通知客户端,从而实现高可用
哨兵模式通常由一组 Sentinel 节点和一组(或多组)主从复制节点组成
心跳机制
Sentinel与Redis Node
Redis Sentinel 是一个特殊的 Redis 节点
在哨兵模式创建时,需要通过配置指定 Sentinel 与 Redis Master Node 之间的关系,
然后 Sentinel 会从主节点上获取所有从节点的信息
然后 Sentinel 会从主节点上获取所有从节点的信息
之后 Sentinel 会定时向主节点和从节点发送 info 命令获取其拓扑结构和状态信息
Sentinel与Sentinel
基于 Redis 的订阅发布功能
每个 Sentinel 节点会向主节点的 sentinel:hello 频道上发送该 Sentinel 节点对于主节点的判断以及当前 Sentinel 节点的信息
同时每个 Sentinel 节点也会订阅该频道, 来获取其他 Sentinel 节点的信息以及它们对主节点的判断
所有的 Sentinel 节点以及它们与所有的 Redis 节点之间都已经彼此感知到
之后每个 Sentinel 节点会向主节点、从节点、以及其余 Sentinel 节点定时发送 ping 命令作为心跳检测, 来确认这些节点是否可达。
故障转移
每个 Sentinel 都会定时进行心跳检查,当发现主节点出现心跳检测超时的情况时,
此时认为该主节点已经不可用,这种判定称为主观下线
此时认为该主节点已经不可用,这种判定称为主观下线
之后该 Sentinel 节点发送sentinel ismaster-down-by-addr 命令向其他 Sentinel 节点询问对主节点的判断
当半数以上 Sentinel 节点都认为该节点故障时,客观下线,因此需要一组 Sentinel 节点
故障转移只需要一个Sentine节点,Sentinel 组间会进行Raft 算法选举出一个leader负责转移工作
转移流程
(1)在从节点列表中选出一个节点作为新的主节点
过滤不健康或者不满足要求的节点
选择 slave-priority(优先级)最高的从节点, 如果存在则返回, 不存在则继续;
选择复制偏移量最大的从节点 , 如果存在则返回, 不存在则继续;
选择 runid 最小的从节点
(2)Sentinel 领导者节点会对选出来的从节点执行 slaveof no one 命令让其成为主节点
(3)Sentinel 领导者节点会向剩余的从节点发送命令,让他们从新的主节点上复制数据。
(4)Sentinel 领导者会将原来的主节点更新为从节点, 并对其进行监控, 当其恢复后命令它去复制新的主节点。
Cluster(集群)
引入原因
不管是主从模式还是哨兵模式都只能由一个master在写数据,在海量数据高并发场景,
一个节点写数据容易出现瓶颈,引入Cluster模式可以实现多个节点同时写数据
一个节点写数据容易出现瓶颈,引入Cluster模式可以实现多个节点同时写数据
设计
所有数据分为16384 的 slots,每个节点负责一部分槽位,槽位的信息存在每个节点中
流程
当客户端连接集群时,得到一份槽位配置信息,槽位节点映射
客户端操作某个key时,可以直接定位到目标节点
槽位定位算法
默认对key使用crc32算法hash得到整数值,再对16384取模得到槽位
用户可以强制某个key挂在特定的槽位
通过在key的字符串嵌入tag标记,强制key所在的槽位等于tag的槽位
{tag}
跳转
当客户端向一个错误的节点发出指令,该节点发现槽位不属于自己管理,会向客户端返回MOVED 3999 127.0.0.1:6381 指令,去某个节点的3999槽位获取数据
迁移
redis迁移的单位是槽
当一个槽正在迁移的时,该槽处于中间过渡状态
槽的源节点状态为migrating,目标状态为importing
客户端先访问源节点,源节点有则立即处理,没有返回-ASK targetNodeAddr 的重定向指令
客户端收到重定向指令先向目标节点执行一个不带任何参数的asking指令
asking指令,表示下一条指令必须执行,避免未迁移完成,该槽位还不属于目标节点管理,返回客户端MOVED造成重定向循环
可能下线
Redis 集群节点采用 Gossip 协议来广播自己的状态以及自己对整个集群认知的改变
当某个节点发现A节点失联了 (PFail),它会将这条信息向整个集群广播
其它节点也就可以收到这点失联信息
如果一个节点收到了某个节点失联的数量 (PFail Count) 已经达到了集
群的大多数,就可以标记该节点为确定下线状态 (Fail)
群的大多数,就可以标记该节点为确定下线状态 (Fail)
然后向整个集群广播,强迫其它节点也接收该节点已经下线的事实,并立即对该失联节点进行主从切换
Gossip协议
简介
ES Redis bitcoin都使用该协议
天然的分布式系统容错性 最终一致性协议
天然的分布式系统容错性 最终一致性协议
流程
1.种子节点的状态更新到其他节点
2.周期性散播信息,默认周期为1s
3.被感染节点随机选择临近的K个节点散播消息
4.每次散播消息都选择尚未发送过的节点进行散播
5.收到消息的节点不在往发送节点散播,如A -> B,那么B散播不再发向A
节点间的通信
PUSH : A将数据(key,value,version)及对应的版本号推送给B节点,B节点更新A中比自己新的数据
PULL:A将数据(key,version)推送给B,B将比A新的数据(key,value,version)推送给A,A更新本地
PUSH/PULL: 与PULL类似,只是多了异步,A再将本地比B新的数据推送给B,B更新本地数据。
PUSH/PULL收敛的速度是最快的,可以使两个节点的数据完全一致
PUSH/PULL收敛的速度是最快的,可以使两个节点的数据完全一致
优点
1.扩展性好
网络允许任意的增加或者减少节点,新增的节点状态最终会与其他节点一致
2.容错
网络的任何节点的宕机和重启都不会影响Gossip消息的传播
3.去中心化
所有的节点都是对等的,任何一个节点无需知道整个网络的状态,只要网络连通的,任意一个节点就可以把消息传播到全网
4.一致性收敛
Gossip协议中的消息会一传十,十传百指数级速度在网络中快速传播,系统状态的不一致可以很快在短时间内收敛到达一致,消息传播速度达到logN
5.简单
Gossip协议的过程极其简单,实现不太复杂
网络允许任意的增加或者减少节点,新增的节点状态最终会与其他节点一致
2.容错
网络的任何节点的宕机和重启都不会影响Gossip消息的传播
3.去中心化
所有的节点都是对等的,任何一个节点无需知道整个网络的状态,只要网络连通的,任意一个节点就可以把消息传播到全网
4.一致性收敛
Gossip协议中的消息会一传十,十传百指数级速度在网络中快速传播,系统状态的不一致可以很快在短时间内收敛到达一致,消息传播速度达到logN
5.简单
Gossip协议的过程极其简单,实现不太复杂
缺点
1.消息的延迟
每个节点只会随机向少数的几个节点发送消息,消息是通过多个轮次的散播儿达到全网的,不可避免的有消息延迟
2.消息冗余
节点会定期随机选择周围的节点发送消息,收到消息的节点重复该步骤,因此,消息不可避免的存在消息重复发送给同一节点,造成消息的冗余,同时增加了收到消息的节点的处理压力。而且,消息是定期发送且不反馈,节点即便收到了消息,还是会反复收到重复的消息。
每个节点只会随机向少数的几个节点发送消息,消息是通过多个轮次的散播儿达到全网的,不可避免的有消息延迟
2.消息冗余
节点会定期随机选择周围的节点发送消息,收到消息的节点重复该步骤,因此,消息不可避免的存在消息重复发送给同一节点,造成消息的冗余,同时增加了收到消息的节点的处理压力。而且,消息是定期发送且不反馈,节点即便收到了消息,还是会反复收到重复的消息。
Memcache与Redis的区别
存储方式
Memecache把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小
Redis有部份存在硬盘上,这样能保证数据的持久性。
数据支持类型
Memcache对数据类型支持相对简单。
Redis有丰富的数据类型。
使用底层模型不同
Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求
面试问题
问题
假如Redis里面有1亿个key,其中有10w个key是以某个固定的已知的前缀开头的,如果将它们全部找出来?
keys pre* 扫出指定模式的key列表
如果这个redis正在给线上的业务提供服务,那使用keys指令会有什么问题?
redis 的单线程的。keys 指令会导致线 程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。
可以使用 scan 指令,scan 指令可以无阻塞的提取出指定模式的 key 列表,
会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间 会比直接用 keys 指令长。
会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间 会比直接用 keys 指令长。
如果有大量的key需要设置同一时间过期,一般需要注意什么?
如果大量的key过期时间设置的过于集中,到过期的那个时间点,Redis可能会出现短暂的卡顿现象(因为redis是单线程的)
严重的话可能会导致服务器雪崩,所以我们一般在过期时间上加一个随机值,让过期时间尽量分散
惰性删除
redis为什么需要惰性删除?
删除指令 del 会直接释放对象的内存
如果删除的是大对象,需要内存管理器回收内存页,导致单线程卡顿
redis 4.0之后引入了unlink指令,当大对象删除时,惰性删除异步处理,主线程不在访问unlink的key
flush flushall
增加异步化,在指令后面增加async
异步队列
异步删除的key会添加到一个线程安全的队列
同时被主线程和异步线程操作
主线程添加任务
异步线程取出任务
数据结构
压缩列表 ziplist
zset和hash容器在对象极少的时候使用压缩列表存储
快速列表 quicklist
list 元素少时用压缩列表多时使用快速列表
将linkedlist按段切分,每一段使用ziplist来紧凑存储
跳跃列表
zset内部是一个hash字典加一个跳跃表
紧凑列表
0 条评论
下一页