乐趣区

关于redis:Redis架构原理及应用实践

一.Redis 简介

Redis 是齐全开源收费的,是一个高性能的 key-value 类型的内存数据库。整个数据库通通加载在内存当中进行操作,定期通过异步操作把数据库数据 flush 到硬盘上进行保留。因为是纯内存操作,Redis 的性能十分杰出,每秒能够解决超过 10 万次读写操作,是已知性能最快的 Key-Value DB。

Redis 的杰出之处不仅仅是性能,Redis 最大的魅力是反对保留多种数据结构,此外单个 value 的最大限度是 1GB,因而 Redis 能够用来实现很多有用的性能,比方说用 List 来做 FIFO 双向链表,实现一个轻量级的高性 能音讯队列服务,用他的 Set 能够做高性能的 tag 零碎等等。另外 Redis 也能够对存入的 Key-Value 设置 expire 工夫。总结来说,应用 Redis 的益处如下:

1. 速度快,因为数据存在内存中,读的速度是 110000 次 /s, 写的速度是 81000 次 /s;

2. 反对丰盛数据类型,反对 string,list,set,sorted set,hash;

3. 反对事务,操作都是原子性,对数据的更改要么全副执行,要么全副不执行,事务中任意命令执行失败,其余命令仍然被执行。也就是说 Redis 事务不保障原子性,也不反对回滚;事务中的多条命令被一次性发送给服务器,服务器在执行命令期间,不会去执行其余客户端的命令申请。

4. 丰盛的个性:可用于缓存,音讯(反对 publish/subscribe 告诉),按 key 设置过期工夫,过期后将会主动删除,具体淘汰策略有:

4.1.volatile-lru:从曾经设置过期工夫的数据集中,筛选最近起码应用的数据淘汰

4.2.volatile-ttl:从曾经设置过期工夫的数据集中,筛选行将要过期的数据淘汰

4.3.volatile-random:从曾经设置过期工夫的数据集中,随机筛选数据淘汰

4.4.allkeys-lru:从所有的数据集中,筛选最近起码应用的数据淘汰

4.5.allkeys-random:从所有的数据集中,随机筛选数据淘汰

4.6.no-enviction:禁止淘汰数据

具体过期键的策略有:定时删除(缓存过期工夫到就删除,创立 timer 耗 CPU),惰性删除(获取的时候查看,不获取始终留在内存,对内存不敌对),定期删除(CPU 和内存的折中计划)

5. 反对数据长久化,能够将内存中的数据保留在磁盘中,重启的时候能够再次加载进行应用;

6. 反对数据的备份,即 master – slave 模式的数据备份。

Redis 的次要毛病是数据库容量受到物理内存的限度,不能用作海量数据的高性能读写,因而 Redis 适宜的场景次要局限在较小数据量的高性能操作和运算上。

二.Redis 的数据类型

Redis 反对 5 中数据类型:string(字符串),hash(哈希),list(列表),set(汇合),zset(sorted set:有序汇合)。每种数据类型的具体命令请参考 Redis 命令参考

string

string 是 redis 最根本的数据类型。一个 key 对应一个 value。string 是二进制平安的。也就是说 redis 的 string 能够蕴含任何数据。比方 jpg 图片或者序列化的对象。string 类型是 redis 最根本的数据类型,string 类型的值最大能存储 512 MB。

hash

Redis hash 是一个键值对(key – value)汇合。Redis hash 是一个 string 类型的 key 和 value 的映射表,hash 特地适宜用于存储对象。并且能够像数据库中一样只对某一项属性值进行存储、读取、批改等操作。

list

Redis 列表是简略的字符串列表,依照插入程序排序。咱们能够网列表的右边或者左边增加元素。list 就是一个简略的字符串汇合,和 Java 中的 list 相差不大,区别就是这里的 list 寄存的是字符串。list 内的元素是可反复的。能够做音讯队列或最新消息排行等性能。

set

redis 的 set 是字符串类型的无序汇合。汇合是通过哈希表实现的,因而增加、删除、查找的复杂度都是 O(1)。redis 的 set 是一个 key 对应着多个字符串类型的 value,也是一个字符串类型的汇合,和 redis 的 list 不同的是 set 中的字符串汇合元素不能反复,然而 list 能够。利用唯一性,能够统计拜访网站的所有独立 ip。

Zset

redis zset 和 set 一样都是字符串类型元素的汇合,并且汇合内的元素不能反复。不同的是 zset 每个元素都会关联一个 double 类型的分数。redis 通过分数来为汇合中的成员进行从小到大的排序。zset 的元素是惟一的,然而分数(score)却能够反复。可用作排行榜等场景。

三.redis 实用场景

1. 会话缓存(Session Cache)

最罕用的一种应用 Redis 的情景是会话缓存(session cache)。用 Redis 缓存会话比其余存储(如 Memcached)的劣势在于:Redis 提供长久化。当保护一个不是严格要求一致性的缓存时,如果用户的购物车信息全副失落,大部分人都会不快乐的。

2. 队列

Reids 在内存存储引擎畛域的一大长处是提供 list 和 set 操作,这使得 Redis 能作为一个很好的音讯队列平台来应用。Redis 作为队列应用的操作,就相似于本地程序语言(如 Python)对 list 的 push/pop 操作。

3. 全页缓存

大型互联网公司都会应用 Redis 作为缓存存储数据,晋升页面相应速度。即便重启了 Redis 实例,因为有磁盘的长久化,用户也不会看到页面加载速度的降落。

4. 排行榜 / 计数器

Redis 在内存中对数字进行递增或递加的操作实现的十分好。汇合(Set)和有序汇合(Sorted Set)也使得咱们在执行这些操作的时候变的非常简单。

四.Redis 高可用架构

1. 长久化

Redis 是内存型数据库,为了保证数据在断电后不会失落,须要将内存中的数据长久化到硬盘上。Redis 提供了两种长久化的形式,别离是 RDB(Redis DataBase)和 AOF(Append Only File)。

RDB

简而言之,就是在不同的工夫点,将 redis 存储的数据生成快照并存储到磁盘等介质上,能够将快照复制到其余服务器从而创立具备雷同数据的服务器正本。如果零碎产生故障,将会失落最初一次创立快照之后的数据。如果数据量大,保留快照的工夫会很长。

AOF

换了一个角度来实现长久化,那就是将 redis 执行过的所有写指令记录下来,在下次 redis 重新启动时,只有把这些写指令从前到后再反复执行一遍,就能够实现数据恢复了。将写命令增加到 AOF 文件(append only file)开端。

应用 AOF 长久化须要设置同步选项,从而确保写命令同步到磁盘文件上的机会。这是因为对文件进行写入并不会马上将内容同步到磁盘上,而是先存储到缓冲区,而后由操作系统决定什么时候同步到磁盘。选项同步频率 always 每个写命令都同步,eyerysec 每秒同步一次,no 让操作系统来决定何时同步,always 选项会重大减低服务器的性能,everysec 选项比拟适合,能够保证系统解体时只会失落一秒左右的数据,并且 Redis 每秒执行一次同步对服务器简直没有任何影响。no 选项并不能给服务器性能带来多大的晋升,而且会减少零碎解体时数据失落的数量。随着服务器写申请的增多,AOF 文件会越来越大。Redis 提供了一种将 AOF 重写的个性,可能去除 AOF 文件中的冗余写命令。

其实 RDB 和 AOF 两种形式也能够同时应用,在这种状况下,如果 redis 重启的话,则会优先采纳 AOF 形式来进行数据恢复,这是因为 AOF 形式的数据恢复残缺度更高。如果你没有数据长久化的需要,也齐全能够敞开 RDB 和 AOF 形式,这样的话,redis 将变成一个纯内存数据库。

2. 复制

Redis 为了解决单点数据库问题,会把数据复制多个正本部署到其余节点上,通过复制,实现 Redis 的高可用性,实现对数据的冗余备份,保证数据和服务的高度可靠性。Redis 有主从和主备两种形式解决单点问题,主备(keepalived)模式下主机备机对外提供同一个虚构 IP,客户端通过虚构 IP 进行数据操作,失常期间主机始终对外提供服务,宕机后 VIP 主动漂移到备机上。主从模式下当 Master 宕机后,通过选举算法 (Paxos、Raft) 从 slave 中选举出新 Master 持续对外提供服务,主机复原后以 slave 的身份重新加入,此模式下能够应用读写拆散,如果数据量比拟大,不心愿过多节约机器,还心愿在宕机后,做一些自定义的措施,比方报警、记日志、数据迁徙等操作,举荐应用主从形式,因为和主从搭配的个别还有个治理监控核心(哨兵)。

①从数据库向主数据库发送 sync(数据同步)命令。

②主数据库接管同步命令后,会保留快照,创立一个 RDB 文件。

③当主数据库执行完放弃快照后,会向从数据库发送 RDB 文件,而从数据库会接管并载入该文件。

④主数据库将缓冲区的所有写命令发给从服务器执行。

⑤以上解决完之后,之后主数据库每执行一个写命令,都会将被执行的写命令发送给从数据库。能够同步发送也能够异步发送,同步发送能够不必每台都同步,能够配置一台 master,一台 slave,同时这台 salve 又作为其余 slave 的 master。异步形式无奈保证数据的完整性,比方在异步同步过程中主机忽然宕机了,也称这种形式为数据弱一致性。

留神:在 Redis2.8 之后,主从断开重连后会依据断开之前最新的命令偏移量进行增量复制。

3. 哨兵

哨兵是 Redis 集群架构中十分重要的一个组件,哨兵的呈现次要是解决了主从复制呈现故障时须要人为干涉的问题。

1.Redis 哨兵次要性能

(1)集群监控:负责监控 Redis master 和 slave 过程是否失常工作

(2)音讯告诉:如果某个 Redis 实例有故障,那么哨兵负责发送音讯作为报警告诉给管理员

(3)故障转移:如果 master node 挂掉了,会主动转移到 slave node 上

(4)配置核心:如果故障转移产生了,告诉 client 客户端新的 master 地址

2.Redis 哨兵的高可用


原理:当主节点呈现故障时,由 Redis Sentinel 主动实现故障发现和转移,并告诉利用方,实现高可用性。哨兵机制建设了多个哨兵节点(过程),独特监控数据节点的运行状况。同时哨兵节点之间也相互通信,替换对主从节点的监控情况。每隔 1 秒每个哨兵会向整个集群:Master 主服务器 +Slave 从服务器 + 其余 Sentinel(哨兵)过程,发送一次 ping 命令做一次心跳检测。这个就是哨兵用来判断节点是否失常的重要依据,波及两个新的概念:主观下线和主观下线。一个哨兵节点断定主节点 down 掉是主观下线,只有半数哨兵节点都主观断定主节点 down 掉,此时多个哨兵节点替换主观断定后果,才会断定主节点主观下线。基本上哪个哨兵节点最先判断出这个主节点主观下线,就会在各个哨兵节点中发动投票机制 Raft 算法(选举算法),最终被投为领导者的哨兵节点实现主从自动化切换的过程。

4. 集群

至多部署两台 Redis 服务器形成一个小的集群,次要有 2 个目标:

高可用性:在主机挂掉后,主动故障转移,使前端服务对用户无影响。

读写拆散:将主机读压力分流到从机上。

可在客户端组件上实现负载平衡,依据不同服务器的运行状况,分担不同比例的读申请压力。


缓存数据量一直减少时,单机内存不够应用,须要把数据切分不同局部,散布到多台服务器上。可在客户端对数据进行分片,数据分片算法详见一致性 Hash 详解、虚构桶分片。

当数据量继续减少时,利用可依据不同场景下的业务申请对应的分布式集群。这块最要害的是缓存治理这块,其中最重要的局部是退出了代理服务(Codis 和 Twemproxy)。利用通过代理拜访实在的 Redis 服务器进行读写,这样做的益处是防止越来越多的客户端间接拜访 Redis 服务器难以治理,而造成危险,在代理这一层能够做对应的安全措施,比方限流、受权、分片,防止客户端越来越多的逻辑代码,岂但臃肿降级还比拟麻烦。代理这层无状态的,可任意扩大节点,对于客户端来说,拜访代理跟拜访单机 Redis 一样。


Redis Cluster 是 Redis 官网给出的集群架构

客户端与 Redis 节点直连, 不须要两头 Proxy 层,间接连贯任意一个 Master 节点,依据公式 HASH_SLOT=CRC16(key) mod 16384,计算出映射到哪个分片上,而后 Redis 会去相应的节点进行操作

具备如下长处:

(1)无需 Sentinel 哨兵监控,如果 Master 挂了,Redis Cluster 外部主动将 Slave 切换 Master

(2)能够进行程度扩容

(3)反对自动化迁徙,当呈现某个 Slave 宕机了,那么就只有 Master 了,这时候的高可用性就无奈很好的保障了,万一 Master 也宕机了,咋办呢?针对这种状况,如果说其余 Master 有多余的 Slave,集群主动把多余的 Slave 迁徙到没有 Slave 的 Master 中。

毛病:

(1)批量操作是个坑,不同的 key 会划分到不同的 slot 中,因而间接应用 mset 或者 mget 等操作是行不通的。如果执行的 key 数量比拟少,就不必 mget 了,就用串行 get 操作。如果真的须要执行的 key 很多,就应用 Hashtag 保障这些 key 映射到同一台 Redis 节点上。

(2)资源隔离性较差,容易呈现相互影响的状况。

五.Redis 高并发及热 key 解决之道

1. 并发设置 key 及分布式锁

Redis 是一种单线程机制的 nosql 数据库,基于 key-value,数据可长久化落盘。因为单线程所以 Redis 自身并没有锁的概念,多个客户端连贯并不存在竞争关系,然而利用 jedis 等客户端对 Redis 进行并发拜访时会呈现问题。比方多客户端同时并发写一个 key,一个 key 的值是 1,原本按程序批改为 2,3,4,最初是 4,然而程序变成了 4,3,2,最初变成了 2。应用分布式锁避免并发设置 Key 的原理及代码见:应用 Redis 实现分布式锁及其优化,另外一种形式是应用音讯队列, 把并行读写进行串行化。

2. 热 key 问题

热 key 问题说来也很简略,就是霎时有几十万的申请去拜访 redis 上某个固定的 key,从而压垮缓存服务的情状况。其实生存中也是有不少这样的例子。比方 XX 明星结婚。那么对于 XX 明星的 Key 就会霎时增大,就会呈现热数据问题。那么如何发现热 KEY 呢:

1. 凭借业务教训,进行预估哪些是热 key

2. 在客户端进行收集

3. 在 Proxy 层做收集

4. 用 redis 自带命令(monitor 命令、hotkeys 参数)

5. 本人抓包评估

解决方案:

1. 利用二级缓存,比方利用 ehcache,或者一个 HashMap 都能够。在你发现热 key 当前,把热 key 加载到零碎的 JVM 中。

2. 备份热 key,不要让 key 走到同一台 redis 上。咱们把这个 key,在多个 redis 上都存一份。能够用 HOTKEY 加上一个随机数(N,集群分片数)组成一个新 key。

3. 热点数据尽量不要设置过期工夫,在数据变更时同步写缓存,避免高并发下重建缓存的资源损耗。能够用 setnx 做分布式锁保障只有一个线程在重建缓存,其余线程期待重建缓存的线程执行完,从新从缓存获取数据即可。

3. 缓存穿透

缓存穿透是指查问一个基本不存在的数据,缓存层和存储层都不会命中,然而出于容错的思考,如果从存储层查不到数据则不写入缓存层。缓存穿透将导致不存在的数据每次申请都要到存储层去查问,失去了缓存爱护后端存储的意义。造成缓存穿透的根本有两个。第一,业务本身代码或者数据呈现问题,第二,一些歹意攻打、爬虫等造成大量空命中,上面咱们来看一下如何解决缓存穿透问题。解决缓存穿透的两种计划:

1)缓存空对象

缓存空对象会有两个问题:

第一,空值做了缓存,意味着缓存层中存了更多的键,须要更多的内存空间 (如果是攻打,问题更重大),比拟无效的办法是针对这类数据设置一个较短的过期工夫,让其主动剔除。

第二,缓存层和存储层的数据会有一段时间窗口的不统一,可能会对业务有肯定影响。例如过期工夫设置为 5 分钟,如果此时存储层增加了这个数据,那此段时间就会呈现缓存层和存储层数据的不统一,此时能够利用音讯零碎或者其余形式革除掉缓存层中的空对象。

2)布隆过滤器拦挡

如下图所示,在拜访缓存层和存储层之前,将存在的 key 用布隆过滤器提前保存起来,做第一层拦挡。如果布隆过滤器认为该用户 ID 不存在,那么就不会拜访存储层,在肯定水平爱护了存储层。无关布隆过滤器的相干常识,能够参考:布隆过滤器,能够利用 Redis 的 Bitmaps 实现布隆过滤器,GitHub 上曾经开源了相似的计划,读者能够进行参考:redis bitmaps 实现布隆过滤器。
缓存空对象和布隆过滤器计划比照

4. 缓存雪崩

数据未加载到缓存中,或者缓存同一时间大面积的生效,从而导致所有申请都去查数据库,导致数据库 CPU 和内存负载过高,甚至宕机。

图片
能够从以下几个方面避免缓存雪崩:

1)保障缓存层服务高可用性

和飞机都有多个引擎一样,如果缓存层设计成高可用的,即便个别节点、个别机器、甚至是机房宕掉,仍然能够提供服务,例如后面介绍过的 Redis Sentinel 和 Redis Cluster 都实现了高可用。

2)Redis 备份和疾速预热

Redis 备份保障 master 出问题切换为 slave 迅速可能承当线上理论流量,疾速预热保障缓存及时被写入缓存,避免穿透到库。

3)依赖隔离组件为后端限流并降级

无论是缓存层还是存储层都会有出错的概率,能够将它们视同为资源。作为并发量较大的零碎,如果有一个资源不可用,可能会造成线程全副 hang 在这个资源上,造成整个零碎不可用。降级在高并发零碎中是十分失常的:比方举荐服务中,如果个性化举荐服务不可用,能够降级补充热点数据,不至于造成前端页面是开天窗。

在理论我的项目中,咱们须要对重要的资源 (例如 Redis、MySQL、Hbase、内部接口) 都进行隔离,让每种资源都独自运行在本人的线程池中,即便个别资源呈现了问题,对其余服务没有影响。然而线程池如何治理,比方如何敞开资源池,开启资源池,资源池阀值治理,这些做起来还是相当简单的,这里举荐一个 Java 依赖隔离工具 Hystrix(https://github.com/Netflix/Hy…),如下图所示。

4)提前演练

在我的项目上线前,演练缓存层宕掉后,利用以及后端的负载状况以及可能呈现的问题,在此基础上做一些预案设定。

5. 缓存预热

缓存预热就是零碎上线前,将相干的缓存数据间接加载到缓存零碎。这样就能够防止上线后在用户申请的时候,先查询数据库,而后再将数据缓存的问题!用户间接查问当时被预热的缓存数据!

退出移动版