大家好,又见面了。


本文是笔者作为掘金技术社区签约作者的身份输出的缓存专栏系列内容,将会通过系列专题,讲清楚缓存的方方面面。如果感兴趣,欢迎关注以获取后续更新。


在本专栏前面的文章中,我们介绍了各种本地缓存框架,也知晓了本地缓存的常见特性与设计理念。在前两篇文章中,我们介绍了集中式缓存 Redis的一些主流特性与典型使用场景。现在我们来对比一下,分布式缓存相比于本地缓存,在实现层面需要关注的点有哪些不同。梳理如下:

维度 本地缓存 集中式缓存
缓存量 受限于单机内存大小,存储数据有限 需要提供给分布式系统里面所有节点共同使用,对于大型系统而言,对集中式缓存的容量诉求非常的大,远超单机内存的容量大小。
可靠性 影响有限,只有本进程使用,不会影响其他进程的可靠性。 作为整个系统扛压屏障,系统内所有节点共同依赖的通用服务,一旦集中式缓存出问题,会影响与其对接的所有业务节点,对系统的影响是致命性的。
承压性 承载单机节点的压力,请求量有限 承载整个分布式集群所有节点的流量,系统内业务分布式节点部署数量越多、业务体量越大,会导致集中缓存要承载的压力就越大,甚至是上不封顶的。

从上述几个维度的对比可以发现,同样是缓存,但集中式缓存所承担的使命是完全不一样的,业务对集中式缓存的存储容量可靠性承压性等方面的诉求也是天壤之别,不可等同视之。以Redis为例:

  • 如何打破redis缓存容量受限于机器单机内存大小的问题?
  • 如何使得redis能够扛住多方过来的请求压力?
  • 如何保证redis不会成为单点故障源?

其实答案很简单,加机器!通过多台机器的叠加使用,达到比单机更优的效果 —— 现在业务系统的集群化部署,也都是采用的这个思路。Redis的分布式之路亦是如此,但相比于常规的业务系统分布式集群化构建更加复杂:

  1. 很多业务实现集群化部署会很简单,因为每个业务进程节点都是无状态的,只需要部署下然后通过负载均衡的方式对外提供请求应答即可。
  2. Redis作为一个集中式缓存数据库,它是有状态的,不仅需要将进程分别部署在多个节点上,还需要将数据也分散存储在各个节点上,同时还得保证整个Redis集群对外是一个统一整体。

所以对于一个集中式缓存的分布式能力构建,必须要额外提供一些机制,来保障数据在各个节点上的安全与一致性,还需要将分散在各个节点上的数据都组成一个逻辑上的整体。

下面,我们以Redis作为集中式缓存的代表,来看下集Redis面对上述各种难题,交出的是怎样的答卷。

Reids部署方式的演进史

单机部署 —— 原始形态,最简单

单机部署只能算是一个开发或测试场景去小范围使用的场景,它与普通本地缓存无二,在可靠性与承压性上无法得到保证。

虽说Redis的性能很高,但俗话也说双拳难敌四手,单机性能再高,也无法抗住大规模集群中所有节点过来的并发请求。此外,单机部署还有个致命点在于其不具备高可用性,系统容易出现单点故障

所以说,稍微正规点的项目,几乎不会有人天真到会用单机模式去部署线上使用。

主从(master-replica)

前面说过单机节点存在诸多问题,很少在生产环境上使用。在实际项目中,有些项目的存储容量要求其实并不是特别的高(比如常规的16G或者32G就已经足够使用),但是需要保证数据的可靠、并且支持大并发量请求,这种情况下,就可以选择主从部署的方式。

对于redis来说,一主两从是比较常见的搭配,如下所示:

主从模式按照读写分离的策略来提升整体的请求处理能力:

  1. 主节点(Master)同时对外提供读和写操作

  2. 从节点(Slave)通过replicate同步的方式,从主节点复制数据,保持自身数据与主节点一致

  3. 从节点只能对外提供读操作

当然,对于读多写少类的操作,为了提升整体读请求的处理能力,可以采用一主多从的方式:

所有的从节点都从主节点进行数据同步,这样会导致主节点的同步处理压力过大而成为瓶颈。为了解决这个问题,redis还支持了从slave节点分发的能力:

Redis的主从模式重点在于解决整体的承压能力,利用从节点分担读取操作的压力。但是其在容错恢复等可靠性层面欠缺明显,不具备自动的故障转移与恢复能力:

  • 如果slave从节点宕机,整个redis依旧可以正常提供服务,待slave节点重新启动后,可以恢复从master节点的数据同步、然后继续提供服务。
  • 如果master主节点宕机,则redis功能受损,无法继续提供写服务,直到手动修复master节点方可恢复。

当然,master节点故障后,也可以手动将其中一个从节点切换为新的master节点来恢复故障。而原先的master节点恢复后,需要手动将其降级为slave节点,对外提供只读服务。

实际使用的时候,手动故障恢复的时效无法得到保证,为了支持自动的故障转移与恢复能力,Redis在主从模式的基础上进行优化增强,提供了哨兵(Sentinel)架构模式。

哨兵(sentinel)

哨兵模式是在现代自动化系统里面常见的一种模式。比如特斯拉汽车就配置了哨兵模式,当车辆停车锁定并启动哨兵模式时,会通过车辆四周的摄像头持续的监控车辆四周的环境,如果发现异常则启动报警系统。

同样地,在软件架构领域,也可以通过设定一些主进程之外的辅助进程,充当“哨兵”的角色时刻监控着主服务,一旦主服务出现异常则进行报警或者自动介入辅助故障转移,以最大限度的保证系统功能的持续性。

Redis的哨兵模式,就是在主从模式的基础上,额外部署若干独立的哨兵进程,通过哨兵进程去监视者Redis主从节点的状态,一旦发现主节点宕机,则哨兵可以重新从剩余slave节点中推选一个新的节点并将其升级为master节点,以此保证整个系统功能可以正常使用。

比较典型的一个Redis sentinel部署场景是“一主二从三哨兵”的组合,如下:

哨兵可以准实时的监控着组网内所有的节点的状态信息,如果判定master节点宕机之后,所有的sentinel节点会一起推选出一个新的master节点。由于sentinel哨兵节点需要承担着master节点推选的责任,所以实施的时候要去sentinel节点个数必须为基数(比如3个、5个等),这是为了保证投票的时候不会出现平局的情况。

在哨兵模式下:

  1. 如果Redis的master节点宕机之后,Sentinel监控到之后,需要先判定确认master节点已经宕机,然后会从剩余存活的slave节点中投票选出一个新的节点作为master节点。
  2. Sentinel监控到此前宕机的master节点重新恢复之后,会将其作为slave节点,挂到现有的新的master节点下面。

哨兵模式有效的解决了高可用的问题,保证了主节点的自动切换操作,进一步保障了Redis缓存节点的可靠性。但是,不管是哨兵模式还是主从模式,其增加的多台部署机器,都仅仅是扩展其承压能力与可靠性,并没有解决分布式场景下对于集中缓存容量的焦虑 —— 只能适用于数据量有限的场景。

成年人的世界总是贪婪的。如果我们既想要保证Redis的可靠性与承压性,还想要突破容量上的限制,就需要Redis的集群模式登场了。

集群(cluster)

Redis提供了去中心化的集群部署模式,集群内所有Redis节点之间两两连接,而很多的客户端工具会根据key将请求分发到对应的分片下的某一个节点上进行处理。

一个典型的Redis集群部署场景如下图所示:

在Redis集群里面,又会划分出分区的概念,一个集群中可有多个分区。分区有几个特点:

  1. 同一个分区内的Redis节点之间的数据完全一样,多个节点保证了数据有多份副本冗余保存,且可以提供高可用保障。
  2. 不同分片之间的数据不相同。
  3. 通过水平增加多个分片的方式,可以实现整体集群的容量的扩展。

按照Cluster模式进行部署的时候,要求最少需要部署6个Redis节点(3个分片,每个分片中1主1从),其中集群中每个分片的master节点负责对外提供读写操作,slave节点则作为故障转移使用(master出现故障的时候充当新的master)、对外提供只读请求处理。

集群数据分布策略

Redis Sharding(数据分片)

Redis Cluster前,为了解决数据分发到各个分区的问题,普遍采用的是Redis Sharding(数据分片)方案。所谓的Sharding,其实就是一种数据分发的策略。根据key的hash值进行取模,确定最终归属的节点。

使用Redis Sharding方式进行数据分片的时候,当集群内数据分区个数出现变化的时候,比如集群扩容的时候,会导致请求被分发到错误节点上,导致缓存命中率降低

如果需要解决这个问题,就需要对原先扩容前已经存储的数据重新进行一次hash计算和取模操作,将全部的数据重新分发到新的正确节点上进行存储。这个操作被称为重新Sharding,重新sharding期间服务不可用,可能会对业务造成影响。

一致性Hash

为了降低节点的增加或者移除对于整体已有缓存数据访问的影响,最大限度的保证缓存命中率,改良后的一致性Hash算法浮出水面。

通过一致性Hash算法,将所有的存储节点排列在首尾相接的Hash环上,每个key在计算Hash后会顺时针找到最近的存储节点存放。而当有新的分区节点加入或退出时,仅影响该节点在Hash环上顺时针相邻的后续一个节点。

当然咯,如果Hash圆环上的分区节点数太少,可能会出现数据在各个分片中分布不均衡的情况,也即出现数据倾斜

为了解决这个问题,引入了虚拟节点的机制,通过增加虚拟节点,来实现数据尽可能的均匀分布在各个节点上。

上图中,A1、A2实际上都对应真实的A分区节点,而B1、B2则对应真实的B分区节点。通过虚拟节点的方式,尽可能让节点在Hash环上保持均分,实现数据在分区内的均分。

Hash槽

不管是原本的Hash取模,还是经过改良后的一致性Hash,在节点的新增或者删减的时候,始终都会出现部分缓存数据丢失的问题 —— 只是丢失的数据量的多少区别。如何才能实现扩展或者收缩节点的时候,保持已有数据不丢失呢?

既然动态变更调整的方式行不通,那就手动指定咯!Hash槽的实现策略因此产生。何为Hash槽?Hash槽的原理与HashMap有点相似,共有16384个槽位,每个槽位对应一个数据桶,然后每个Redis的分区都可以负责这些hash槽中的部分区间。存储数据的时候,数据key经过Hash计算后会匹配到一个对应的槽位,然后数据存储在该槽位对应的分片中。然后各个分区节点会与Hash槽之间有个映射绑定关系,由指定的Redis分区节点负责存储对应的Has槽对应的具体分片文件。

数据查询的时候,先根据key的Hash值进行计算,确定应该落入哪个Hash槽,进而根据映射关系,确定负责此Hash槽数据存储的redis分区节点是哪个,然后就可以去做对应的查询操作。

执行数据节点增加的时候,需要手动执行下处理:

  • 为新的节点分配新其负责的Hash槽位区间段;
  • 调整已有的节点的Hash槽位负责区间段;
  • 将调整到新节点上的hash槽位区间段对应的数据分片文件拷贝到新的节点上。

这样,就不会出现已有数据无法使用的情况了。鉴于Hash槽的自主可控性以及节点伸缩场景下的优势,其也成为了Redis Cluster中使用的方案。

小结回顾

好啦,关于Redis部署模式的演进探讨,就聊到这里了。通过本篇文章,我们也可以感受出集中式缓存相对本地而言,在实现与设计机制上要更加的复杂,因为需要考虑与解决多方面的问题,比如可靠性、承压性、容量以及后期的水平扩容能力等等,而这些也都是一个合格的集中式缓存所必须要具备的基本品格。

那么,了解Redis对于集中式缓存在节点安全性与扩展性上的实现后,如果让你来设计一个集中缓存的话,你会采用何种方式来保证其可靠性与后续的扩展性呢?欢迎评论区一起交流下,期待和各位小伙伴们一起切磋、共同成长。

补充说明

本文属于《深入理解缓存原理与实战设计》系列专栏的内容之一。该专栏围绕缓存这个宏大命题进行展开阐述,全方位、系统性地深度剖析各种缓存实现策略与原理、以及缓存的各种用法、各种问题应对策略,并一起探讨下缓存设计的哲学。

如果有兴趣,也欢迎关注此专栏。

我是悟道,聊技术、又不仅仅聊技术~

如果觉得有用,请点赞 + 关注让我感受到您的支持。也可以关注下我的公众号【架构悟道】,获取更及时的更新。

期待与你一起探讨,一起成长为更好的自己。

面对集中式缓存实现上的挑战,Redis交出的是何种答卷?聊聊Redis在分布式方面的能力设计的更多相关文章

  1. Spring Boot 2.x基础教程:使用集中式缓存Redis

    之前我们介绍了两种进程内缓存的用法,包括Spring Boot默认使用的ConcurrentMap缓存以及缓存框架EhCache.虽然EhCache已经能够适用很多应用场景,但是由于EhCache是进 ...

  2. HDFS集中式缓存管理(Centralized Cache Management)

    Hadoop从2.3.0版本号開始支持HDFS缓存机制,HDFS同意用户将一部分文件夹或文件缓存在HDFS其中.NameNode会通知拥有相应块的DataNodes将其缓存在DataNode的内存其中 ...

  3. Memcached 1.4.20 发布,集中式缓存系统

    内存缓存Memcached 1.4.20发布.2014-05-12 上一个版本是2014-05-01的1.4.19  此版本只修正了一个1.4.18和1.4.19中引入的Bug. 此版本只是修复了导致 ...

  4. HDFS集中式的缓存管理原理与代码剖析--转载

    原文地址:http://yanbohappy.sinaapp.com/?p=468 Hadoop 2.3.0已经发布了,其中最大的亮点就是集中式的缓存管理(HDFS centralized cache ...

  5. HDFS集中式的缓存管理原理与代码剖析

    转载自:http://www.infoq.com/cn/articles/hdfs-centralized-cache/ HDFS集中式的缓存管理原理与代码剖析 Hadoop 2.3.0已经发布了,其 ...

  6. Redis 集群缓存测试要点--关于 线上 token 失效 BUG 的总结

    在测试账户系统过程中遇到了线上大面积用户登录态失效的严重问题,事后对于其原因及测试盲点做了一些总结记录以便以后查阅,总结分为以下7点,其中原理性的解释有些摘自网络. 1.账户系统token失效问题复盘 ...

  7. 理解OpenShift(6):集中式日志处理

    理解OpenShift(1):网络之 Router 和 Route 理解OpenShift(2):网络之 DNS(域名服务) 理解OpenShift(3):网络之 SDN 理解OpenShift(4) ...

  8. SpringBoot初始教程之Redis集中式Session管理

    1.介绍 有关Session的管理方式这里就不再进行讨论,目前无非就是三种单机Session(基于单机内存,无法部署多台机器).基于Cookie(安全性差).基于全局的统一Session管理(redi ...

  9. Session集中式管理

          Asp.net Session集中式管理主要有StateServer(状态服务器).Sqlserver(数据库服务器).自定义(如Redis缓存服务器)等,本文主要介绍StateServe ...

  10. 集中式日志系统 ELK 协议栈详解

    简介 在我们日常生活中,我们经常需要回顾以前发生的一些事情:或者,当出现了一些问题的时候,可以从某些地方去查找原因,寻找发生问题的痕迹.无可避免需要用到文字的.图像的等等不同形式的记录.用计算机的术语 ...

随机推荐

  1. Vue中、参数传递以及重定向

    1.参数传递 关键部分代码 1.参数传递 <router-link :to="{name:'information',params:{id:1}}">用户信息</ ...

  2. 齐博x1内容评论标签的风格制作

    评论的标签如下: {qb:comment name="xxxxx" rows='5'} HTML代码片段 {/qb:comment} 评论涉及到的元素有{posturl} 这个是代 ...

  3. "一键重装系统软件"操作流程

    博主之前重装系统都是直接用win10系统更新里的重置系统(版本不会变化),和U盘重装电脑出厂映像(有各种品牌自带的软件,而且由于是出场版本,版本很低) 所以这次用"小白一键重装系统" ...

  4. 3.版本穿梭&分支概述

    版本穿梭 如果我们提交了多个版本到本地仓库,想将工作区恢复到历史版本 可以先使用git reflog查看历史记录,获取到版本号 然后使用git rest --hard 版本号 命令恢复到指定版本 gi ...

  5. 狂神说mysql笔记

    1.mysql 基本操作 Windows-->Mysql5.7打开 输入用户名和密码 查看数据库 :show databases:查询所有数据库,记住一定要加分号结尾 这里必须全部为 英文空格 ...

  6. 五、docker网络

    一.Docker 网络 docker网络主要是解决容器联网问题,也是我们使用容器中最重要的一个环节,如果容器没有网络则无法向网络中提供服务. 网络管理命令:docker network [root@z ...

  7. 深度优先搜索(Depth-First-Search)dfs代码模板

    void dfs()//参数用来表示状态 { if(到达终点状态) { ...//根据需求添加 return; } if(越界或者是不合法状态) return; if(特殊状态)//剪枝,去除一些不需 ...

  8. python 总是忘函数

    sorted sorted()函数对所有可迭代对象进行操作,返回的是一个新列表:列表的list方法只对列表有效,且不生成新的列表. # 对成绩升序排列,名字降序排列 d1 = [{'name':'al ...

  9. Oracle:ORA-00911: invalid character解决办法

    问题记录:用jmeter执行sql语句,报错:ORA-00911: invalid character. 解决方法:sql语句末尾";"导致,去掉即可解决. 过程记录: 使用jme ...

  10. 【安装文档】TRex流量分析仪保姆级安装指南--基于VMware虚拟机(ubantu18.04@Intel 82545EM)

    前言 既然你已经知道TRex并尝试搜索它的安装教程,这意味着你有一定的基础知识(至少知道自己需要什么).因此本文对于TRex的介绍部分会偏少 本次主要为TRex安装过程的一次记录(版本为v3.0.0) ...