“众里寻他千百度,蓦然回首,那人却在,灯火阑珊处”。多年的IT生涯,始终心愿本人写的程序可能有很强的健壮性,也始终心愿能找到一个高可用的标杆程序去借鉴学习,不畏惧内存溢出、磁盘满了、断网、断电、机器重启等等状况。但意想不到的是,这个标杆程序居然就是从一开始就在应用的分布式缓存——Redis。
Redis(Remote Dictionary Server ),即近程字典服务,是 C 语言开发的一个开源的高性能键值对(key-value)的内存数据库。因为它是基于内存的所以它要比基于磁盘读写的数据库效率更快。因而Redis也就成了大家解决数据库高并发拜访、分布式读写和分布式锁等首选解决方案。
那么既然它是基于内存的,如果内存满了怎么办?程序会不会解体?既然它是基于内存的,如果服务器宕机了怎么办?数据是不是就失落了?既然它是分布式的,这台Redis服务器断网了怎么办?
明天咱们就一起来看看Redis的设计者,一名来自意大利的小伙,是如何打造出一个超强健壮性和高可用性的程序,从而不害怕这些状况。
一、 Redis的内存管理策略——内存永不溢出
Redis次要有两种策略机制来保障存储的key-value数据不会把内存塞满,它们是:过期策略和淘汰策略。
1、 过期策略
用过Redis的人都晓得,咱们往Redis里增加key-value的数据时,会有个选填参数——过期工夫。如果设置了这个参数的值,Redis到过期工夫后会自行把过期的数据给革除掉。“过期策略”指的就是Redis外部是如何实现将过期的key对应的缓存数据革除的。
在Redis源码中有三个外围的对象构造:redisObject、redisDb和serverCron。
- redisObject:Redis 外部应用redisObject 对象来形象示意所有的 key-value。简略地说,redisObject就是string、hash、list、set、zset的父类。为了便于操作,Redis采纳redisObject构造来对立这五种不同的数据类型。
- redisDb:Redis是一个键值对数据库服务器,这个数据库就是用redisDb形象示意的。redisDb构造中有很多dict字典保留了数据库中的所有键值对,这些字典就叫做键空间。如下图所示其中有个“expires”的字典就保留了设置过期工夫的键值对。而Redis的过期策略也是围绕它来进行的。
- serverCron:Redis 将serverCron作为工夫事件来运行,从而确保它每隔一段时间就会主动运行一次。因而redis中所有定时执行的事件工作都在serverCron中执行。
理解完Redis的三大外围构造后,咱们回到“过期策略”的具体实现上,其实Redis次要是靠两种机制来解决过期的数据被革除:定期过期(被动革除)和惰性过期(被动革除)。
- 惰性过期(被动革除):就是每次拜访的时候都去判断一下该key是否过期,如果过期了就删除掉。该策略就能够最大化地节俭CPU资源,然而却对内存十分不敌对。因为不实时过期了,本来该过期删除的就可能始终沉积在内存外面!极其状况可能呈现大量的过期key没有再次被拜访,从而不会被革除,占用大量内存。
- 定期过期(被动革除):每隔肯定的工夫,会扫描Redis数据库的expires字典中肯定数量的key,并革除其中已过期的 key。Redis默认配置会每100毫秒进行1次(redis.conf 中通过 hz 配置)过期扫描,扫描并不是遍历过期字典中的所有键,而是采纳了如下办法:
(1)从过期字典中随机取出20个键;
(server.h文件下ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP
配置20)
(2)删除这20个键中过期的键;
(3)如果过期键的比例超过 25% ,反复步骤 1 和 2;
具体逻辑如下图:
因为Redis中同时应用了惰性过期和定期过期两种过期策略,所以在不同状况下使得 CPU 和内存资源达到最优的均衡成果的同时,保障过期的数据会被及时革除掉。
2、淘汰策略
在Redis可能没有须要过期的数据的状况下,还是会把咱们的内存都占满。比方每个key设置的过期工夫都很长或不过期,始终增加就有可能把内存给塞满。那么Redis又是怎么解决这个问题的呢?——那就是“淘汰策略”。
官网地址:https://redis.io/topics/lru-c...
Reids官网下面列出的淘汰策略一共有8种,但从本质算法来看只有两种实现算法,别离是LRU和LFU。
LRU(Least Recently Used):翻译过去是最久未应用,依据时间轴来走,淘汰那些间隔上一次应用工夫最长远的数据。
LRU的简略原理如下图:
从上图咱们能够看出,在容器满了的状况下,间隔上次读写工夫最长远的E被淘汰掉了。那么数据每次读取或者插入都须要获取一下以后零碎工夫,以及每次淘汰的时候都须要拿以后零碎工夫和各个数据的最初操作工夫做比照,这么干势必会减少CPU的负荷从而影响Redis的性能。Redis的设计者为了解决这一问题,做了肯定的改善,整体的LRU思路如下:
(1)、Redis里设置了一个全局变量 server.lruclock 用来寄存零碎以后的工夫戳。这个全局变量通过serverCron 每100毫秒调用一次updateCachedTime()更新一次值。
(2)、每当redisObject数据被读或写的时候,将以后的 server.lruclock值赋值给 redisObject 的lru属性,记录这个数据最初的lru值。
(3)、触发淘汰策略时,随机从数据库中抉择采样值配置个数key, 淘汰其中热度最低的key对应的缓存数据。
注:热度就是拿以后的全局server.lruclock 值与各个数据的lru属性做比照,相差最长远的就是热度最低的。
Redis中所有对象构造都有一个lru字段, 且应用了unsigned的低24位,这个字段就是用来记录对象的热度。
LFU(Least Frequently Used):翻译成中文就是最不罕用。是按着应用频次来算的,淘汰那些应用频次最低的数据。说白了就是“开端淘汰制”!
方才讲过的LRU依照最久未应用尽管能达到淘汰数据开释空间的目标,然而它有一个比拟大的弊病,如下图:
如图所示A在10秒内被拜访了5次,而B在10秒内被拜访了3 次。因为 B 最初一次被拜访的工夫比A要晚,在等同的状况下,A反而先被回收。那么它就是不合理的。LFU就完满解决了LRU的这个弊病,具体原理如下:
上图是开端淘汰的原理示意图,仅是按次数这个维度做的开端淘汰,但如果Redis仅按应用次数,也会有一个问题,就是某个数据之前被拜访过很屡次比方上万次,但后续就始终不必了,它自身按应用频次来讲是应该被淘汰的。因而Redis在实现LFU时,用两局部数据来标记这个数据:应用频率和上次访问工夫。整体思路就是:有读写我就减少热度,一段时间内没有读写我就缩小相应热度。
不论是LRU还是LFU淘汰策略,Redis都是用lru这个字段实现的具体逻辑,如果配置的淘汰策略是LFU时,lru的低8位代表的是频率,高16位就是记录上次访问工夫。整体的LRU思路如下:
(1)每当数据被写或读的时候都会调用LFULogIncr(counter)办法,减少lru低8位的拜访频率数值;具体每次减少的数值在redis.conf中配置默认是10(# lfu-log-factor 10)
(2)还有另外一个配置lfu-decay-time 默认是1分钟,来管制每隔多久没人拜访则热度会递加相应数值。这样就躲避了一个超大拜访次数的数据很久都不被淘汰的破绽。
小结:“过期策略” 保障过期的key对应的数据会被及时革除;“淘汰策略”保障内存满的时候会主动开释相应空间,因而Redis的内存能够自运行保障不会产生溢出异样。
二、 Redis的数据长久化策略——宕机可立刻复原数据到内存
有了内存不会溢出保障后,咱们再来看看Redis是如何保障服务器宕机或重启,原来缓存在内存中的数据是不会失落的。也就是Redis的长久化机制。
Redis 的长久化策略有两种:RDB(快照全量长久化)和AOF(增量日志长久化)
1、 RDB
RDB 是 Redis 默认的长久化计划。RDB快照(Redis DataBase),当触发肯定条件的时候,会把以后内存中的数据写入磁盘,生成一个快照文件dump.rdb。Redis重启会通过dump.rdb文件复原数据。那那个肯定的条件是啥呢?到底什么时候写入rdb 文件?
触发Redis执行rdb的形式有两类:主动触发和手动触发
“主动触发”的状况有三种:达到配置文件触发规定时触发、执行shutdown命令时触发、执行flushall命令时触发。
注:在redis.conf中有个 SNAPSHOTTING配置,其中定义了触发把数据保留到磁盘触发频率。
“手动触发”的形式有两种:执行save 或 bgsave命令。执行save命令在生成快照的时候会阻塞以后Redis服务器,Redis不能解决其余命令。如果内存中的数据比拟多,会造成Redis长时间的阻塞。生产环境不倡议应用这个命令。
为了解决这个问题,Redis 提供了第二种形式bgsave命令进行数据备份,执行bgsave时,Redis会在后盾异步进行快照操作,快照同时还能够响应客户端申请。
具体操作是Redis过程执行fork(创立过程函数)操作创立子过程(copy-on-write),RDB长久化过程由子过程负责,实现后主动完结。它不会记录 fork 之后后续的命令。阻塞只产生在fork阶段,个别工夫很短。手动触发的场景个别仅用在迁徙数据时才会用到。
咱们晓得了RDB的实现的原理逻辑,那么咱们就来剖析下RDB到底有什么优劣势。
劣势:
(1)RDB是一个十分紧凑(compact类型)的文件,它保留了redis在某个工夫点上的数据集。这种文件非常适合用于进行备份和劫难复原。
(2)生成RDB文件的时候,redis主过程会fork()一个子过程来解决所有保留工作,主过程不须要进行任何磁盘IO操作。
(3)RDB在复原大数据集时的速度比AOF的复原速度要快。
劣势:
RDB形式数据没方法做到实时长久化/秒级长久化。在肯定间隔时间做一次备份,所以如果Redis意外down掉的话,就会失落最初一次快照之后的所有批改
2、 AOF(Append Only File)
AOF采纳日志的模式来记录每个写操作的命令,并追加到文件中。开启后,执行更改 Redis数据的命令时,就会把命令写入到AOF文件中。Redis重启时会依据日志文件的内容把写指令从前到后执行一次以实现数据的复原工作。
其实AOF也不肯定是齐全实时的备份操作命令,在redis.conf 咱们能够配置抉择 AOF的执行形式,次要有三种:always、everysec和no
AOF是追加更改命令文件,那么大家想下始终追加追加,就是会导致文件过大,那么Redis是怎么解决这个问题的呢?
Redis解决这个问题的办法是AOF上面有个机制叫做bgrewriteaof重写机制,咱们来看下它是个啥
注:AOF文件重写并不是对原文件进行重新整理,而是间接读取服务器现有的键值对,而后用一条命令去代替之前记录这个键值对的多条命令,生成一个新的文件后去替换原来的AOF文件。
咱们晓得了AOF的实现原理,咱们来剖析下它的优缺点。
长处:
能最大限度的保障数据安全,就算用默认的配置everysec,也最多只会造成1s的数据失落。
毛病:
数据量比RDB要大很多,所以性能没有RDB好!
小结:因为有了长久化机制,因而Redis即便服务器宕机或重启了,也能够最大限度的复原数据到内存中,提供给client持续应用。
三、Redis的哨兵模式——可战到最初一兵一卒的高可用集群
内存满了不会挂,服务器宕机重启也没问题。足见Redis的程序健壮性曾经足够弱小。但Redis的设计者,在面向高可用背后,仍持续向前迈进了一步,那就是Redis的高可用集群计划——哨兵模式。
所谓的“哨兵模式”就是有一群哨兵(Sentinel)在Redis服务器后面帮咱们监控这Redis集群各个机器的运行状况,并且哨兵间互相通告告诉,并指引咱们应用那些衰弱的服务。
Sentinel工作原理:
1、 Sentinel 默认以每秒钟1次的频率向Redis所有服务节点发送 PING 命令。如果在down-after-milliseconds 内都没有收到无效回复,Sentinel会将该服务器标记为下线(主观下线)。
2、 这个时候Sentinel节点会持续询问其余的Sentinel节点,确认这个节点是否下线, 如果少数 Sentinel节点都认为master下线,master才真正确认被下线(主观下线),这个时候就须要从新选举master。
Sentinel的作用:
1、监控:Sentinel 会一直查看主服务器和从服务器是否失常运行
2、故障解决:如果主服务器产生故障,Sentinel能够启动故障转移过程。把某台服务器降级为主服务器,并发出通知
3、配置管理:客户端连贯到 Sentinel,获取以后的 Redis 主服务器的地址。咱们不是间接去获取Redis主服务的地址,而是依据sentinel去主动获取谁是主机,即便主机产生故障后咱们也不必改代码的连贯!
小结:有了“哨兵模式”只有集群中有一个Redis服务器还衰弱存活,哨兵就能把这个衰弱的Redis服务器提供给咱们(如上图的1、2两步),那么咱们客户端的链接就不会出错。因而,Redis集群能够战斗至最初一兵一卒。
这就是Redis,一个“高可用、强健壮性”的标杆程序!
作者:宜信技术学院 谭文涛