如何设计业务高可用、高性能存储架构

一、数据库存储架构

  1、数据库读写分离

    实现原理:

      (1)数据库服务器搭建主从集群,一主一从或一主多从

      (2)主库负责读写,从库只负责读

      (3)主库通过复制将数据复制到从库,每个服务器都存储了全量的数据

      (4)业务服务器将读写操作发给主库,将读操作发给从库。

    如何判断要使用主从架构:

      业务量持续增长,可能会导致查询性能瓶颈。

      但是一般是先优化,例如优化索引,优化数据访问,将历史数据剥离到其他库,加入缓存等,如果没有优化空间或者优化不足以提高性能,再重构存储架构。

    主从架构复杂度主要是:

      复制延迟和任务分解,复制延迟是指从主库复制到从库有一定的时间延迟,那么从从库中查询刚修改的数据是查不到的;任务分解是我们要区分是读还是写,要区分任务类型发给不同的服务器。

    主从架构复杂度解决:

      复制延迟:

        (1)读写绑定

        写操作后的读操作指定发给数据库主服务器,这样做的缺点是对于业务侵入很大,例如在写之后查询数据,需要判断到底查询的是之前的数据,还是刚刚写入的数据,从而来判断查询请求应该发给主机还是从机,这样的操作对业务侵入很大,非常容易出bug。

        (2)二次读取

        读从机失败后再读一次主机,缺点是如果有很多二次读取,将大大增加主库的读操作压力,一般情况是在大批量插入或者受黑客攻击的时候,会产生大量的二次读取。

        (3)业务分级

        关键业务读写操作全部指向主机,非关键业务采用读写分离,实现简单,缺点是编码人员容易偷懒,全部采用读写主机,因此这种方式只能由编码人员来保证。

        在实际应用中,采用业务分级的方式会更多,因为这种方式对代码要求不高,综合落地成本较低。

      任务分解:

        (1)使用程序代码封装模式

        代表性组件,sharding jdbc,该方式实现简单,基于JDBC封装,维护简单,不需要部署,由于是集成在项目中,无需考虑高可用高性能,缺点是需要将每种语言都实现一遍。

        (2)中间件封装模式

        代表性组件,sharding proxy和mycat,该方式实现复杂,需要独立部署服务器,还需要实现连接管理,维护起来也比较复杂,需要独立部署,且要考虑集群部署,支持高可用和高性能。优点是可以跨语言。

             

   2、数据库分库分表

  分库:将不同的业务分为不同的库,分库存在问题是连表查询和事务问题。

    连表查询:原本一个库中的表分散到不同的库,导致无法使用连表查询。

      解决方案:

    (1)小表冗余:将一些类似字典表的小表在每个库中都冗余存储一份。

    (2)代码Join,在代码里实现Join功能

    (3)字段冗余:例如订单表直接记录商品类型,这里冗余字段也是有技巧的,如果创建后,这个字段基本上不会变化,是可以冗余的,如果经常发生变化,则不适合冗余。

    事务问题:表分散到不同的数据库中,无法通过事务统一修改。

      解决方案:分布式事务

  分表:分表指的的对一张表的拆分,分为垂直分表和水平分表,垂直分表主要见于ToB业务,一张表可能存在一二百个字段,单表数据量大主要是因为列太多,因此需要垂直拆表;水平拆表主要见于ToC业务,单表数据量大主要是因为行多,因此需要水平拆分。

    多大的表需要拆分:一般来说,B+Tree的3层大约是2000W条数据,还有就是数据存储的量超过了Innodb buffer pool的大小,例如如果每条数据100字节,单表就2G了;还有就是数据量持续增长的表。

    水平分表存在的问题:路由问题:如何判断数据存储在哪里,应该查询哪张表;还有就是使用count、join、order by进行查询。这种不建议自己编写,一般使用第三方封装好的组件,例如sharding jdbc即可。

    分库分表可以通过加服务器来提升性能,但是不能通过加服务器无限的提升性能,虽然加服务器可以提升数据库的性能,但是查询、分发、聚合都是通过sharding jdbc来处理的,sharding jdbc本身会成为瓶颈,另外一个原因是如果服务多了之后,每个服务都会连接所有的数据库,那么每个库的连接数是有限的,以Mysql为例,默认是100个链接,实测50-100性能最高,超过200性能会显著下降。同时单个sharding-jdbc的聚合操作会有性能瓶颈。

  3、分布式事务

  分布式事务总体分为2PC和3PC

  2PC:事务协调者给参与者发送Prepare指令,当所有的事务参与者都返回成功后,再给所有的参与者发送Commit指令,让事务参与者提交事务。

    2PC的问题:单个参与者故障会导致整体事务失败;事务协调者单点问题,事务协调者出现故障会导致整体状态不一致,导致参与者一直等待事务协调者指令,可能需要人工修复。

  3PC:提议阶段,事务协者给所有事务参与者发送canCommit指令,准备阶段事务协调者给事务参与者发送Precommit指令,提交阶段,事务协调者给事务参与者发送doCommit指令

    3PC问题:在提议阶段的返回,如果有部分参与者没有返回,则不会执行后续的步骤,相当于丢弃事务;如果再preCommit阶段丢失了部分参与者,那么收到PreCommit消息的参与者会提交事务,因此造成脑裂,导致数据不一致;doCommit丢失会导致参与者超时提交事务。

                

   虽然3PC增加了提议阶段和超时机制来解决事务协调器单点的问题,但是由于其会造成脑裂问题,出现问题后数据更难以修复,因此在实际应用中反而是2PC应用的更多。

   在实际应用中,还是有很多其他变种的解决方案的,如果单纯的使用强制一致性,如果是外部XA,即跨多个Mysql实例的分布式事务,由应用程序代码作为2PC的事务协调者,在外部使用try、catch处理,try中调用各个参与者处理事务,如果发生异常,调用各个参与者回滚事务。如果是内部XA,即跨同一存储实例的不同存储引擎,则可以使用Binlog作为2PC的事务协调者。

二、复制架构

  1、高可用架构常见问题与指标

  存储类常见有故障和灾难,故障是指可以修复的问题,应对故障是让存储达到可用和可恢复的功能即可,常用的手段是采用复制架构,这个是要是开发来保证的;灾难是指不可恢复的问题,应对灾难我们同样是要达到可用和可恢复,但是这里的可用和可恢复与故障中不一样,需要分开解决,可用的手段是多活架构,保证一个机房除了问题,可以使用另外可用的机房,可恢复的手段是备份,多机房间数据备份,保证机房切换后,数据不丢失。

        

   存储高可用的几个指标:

    RPO:Recovery Point Objective,恢复点目标,指最大可接受的数据损失,因为数据备份和复制都需要时间,因此在发生故障时,会导致数据丢失。

    RTO:Recovery Time Objective,恢复时间目标,指最大可接受的系统恢复所需时间,这主要是因为问题的定位、处理、修复都是需要时间的。

    WRT:Work Recovery Time,工作恢复时间,指系统恢复正常后,恢复业务所需时间,因为要进行各种业务检查、校验、修复

    MTD:Maximum Tolerable Woentime,最大可容忍宕机时间,等于PTO + WRT

        

   在高可用计算架构中,没有RPO,因为其不涉及数据复制和数据备份的问题。

  2、主备 & 主从 架构

  (1)主备复制 & 主从复制

    本质:主备复制的本质是通过冗余来提高可用性,而主从架构是通过冗余来提升读性能

    变化:备机是否提供复制功能,备机部署地点、主备主从混合部署等

    优点:实现简单,只需要数据复制,无状态检测和角色切换

    缺点:需要人工干预,RTO比较大

        

   (2)主备级联复制

    是主备的一种变种,上面的主备是所有的从机都从主机中进行数据复制,在该方案中,备机一从主机进行数据复制,备机二从备机一中做数据复制。

    变化:备机作为数据源

    优点:主机故障后,切换备机一为主机,方便快捷,直接修改配置即可,无需修改备机二的配置,无需判断备机一和备机二的数据覆盖问题

    缺点:备机一对于数据备份非常关键,备机一宕机会导致所有的备机都没有备份数据

    应用:Mysql和Redis支持这种模式

  这种模式只可以应用于主备架构,主从架构使用这种模式会导致更大的数据延迟

        

   (3)主备/主从架构的灾备部署

      场景一:IDC-1 和 IDC-2在同一城市,可以应对机房级别的灾难;

      场景二:IDC-1 和 IDC-2在不同城市,可以应对城市级别的灾难。

      如果是主从架构的灾备,读从服务器只能从与主库在同一机房的从库中读,如果跨机房,读取数据的延迟会很高,另外就是两个机房间的数据备份也会比同机房的数据备份的延迟要高。

        

   3、双机切换架构

  (1)双机切换 -- 主备切换

    优点:可以自动实现故障切换,RTO短。

    缺点:实现复杂,需要实现数据复制、状态检测、故障切换、数据冲突处理等

    应用:主要用于内部系统、管理系统等数据变化不频繁、数据量不大的场景,这样发生切换后数据冲突的处理量会很小

        

   (2)双机切换 -- 主从切换

     主从切换和主备切换非常类似,唯一不同的就是在切换阶段,只有主机对外提供读写服务,主机可能存在性能问题。

  4、集群选举架构

    主机发生故障后,集群通过选举的方式从多个从机中选择一个作为主机,然后其他从机从原来的主机上切换到新主机,从新主机上复制数据。

    优点:可以自动实现故障恢复,PTO短,可用性更高

    缺点:实现复杂,需要实现数据复制,状态检测、选举算法、故障切换、数据冲突等

    应用:通用,例如Redis和MongoDB等

        

     样例:

      虽然很多存储都是使用的集群选举架构,其原理都是一样的,但是其具体实现还是有一定差异的,以Redis和MongoDB为例,Redis使用Sentinal进行集群选举,Sentinal本身也是个集群,使用Raft算法选举Sentinal集群的Leader;MongoDB3.2之前使用的是Bully算法,在3.2之后使用的是Raft算法,其将选举节点内置到节点中(Primary节点、Secondary节点),不需要再有单独的选举节点。

        

     最佳实践:使用Zookeeper

      如果我们自己要实现一个状态检测和集群选举,一般使用Zookeeper即可,基于Zookeeper来实现双机切换或集群选举,能够大大降低复杂度,因为Zookeeper本身已经保证了自己的高可用,且基于Zookeeper切换或选举过程实现比较简单,Zookeeper同时又很多其他的用途。

      以HDFS集群为例,其就是基于Zookeeper实现NameNode的选举。

        

三、分片架构和分区架构

  在上面的复制架构中,只有主机负责写,写性能会存在瓶颈,每台机器存储全量数据,存储也会存在瓶颈;因此需要采用分片架构和分区架构来解决这些问题。

  1、分片架构

  分片架构的核心是通过叠加更多的服务器来提升写性能和存储性能。

  在使用分片架构时,需要设计好分片规则和路由规则,分片规则即数据按照什么规则分片,路由规则指业务服务器如何找到数据。

        

   (1)分片规则:

    选取基数比较大的某个数据键值,让数据均匀分布,避免热点分片,基数是指被选数据维度的取值范围,均匀分布是指数据在取值范围内是均匀分布的,但是数据均匀分布不代表数据的读写是均匀分布的,例如常见的微博,大V和明星的数据访问量肯定比一般人的读流量要大的多。

    常见的分片方式:主键分片、时间分片

      主键分片:适合主业务数据,常见场景有用户ID、订单ID、Redis分片的Key、MongoDB的文档ID等

      时间分片:适合流水型业务,例如创建日期,IoT事件、状态等。

    常见分片规则:

      Hash分片:sharding key = hash(原键值)  最常见的是取模,例如使用订单号,按服务器数量分片,好处是分布均匀,不好的是不支持范围查询,另外一个是扩容非常麻烦,需要做数据迁移

      范围分片:例如按照订单生成时间,每三个月做一个分片,这种方式分布不可能均匀,例如电商大促的时候,订单量是很大的。支持范围查询,优点是方便扩容新服务器,无需迁移历史数据

  (2)路由规则:

    路由规则配置有静态路由或动态路由两种,静态路由是指使用配置文件的方式,这种方式实现简单,但是不够灵活,无法动态扩容和平衡,数据库分表采用这种方式;动态路由有使用配置中心和路由转发两种方式,配置中心的方式是有一个集中管理路由规则的配置中心;路由转发的方式并没有集中管理路由规则的配置中心,但是分片服务器知道各个分片服务器的数据存储情况,这种方式实现复杂,支持动态扩容和平衡,Redis、MongoDB、Hbase支持这种。

    那既然动态路由支持动态扩容和平衡,为什么数据库不适用这种方式呢,这是因为数据库对于ACID的要求非常高,比redis、MongoDB、HBase要高得多,因此其要实现动态路由的话,复杂度要高得多,因此数据库分表我们是不会使用动态路由的方式的。

        

    路由规则 -- 配置中心:

      由专属的配置中心记录分片信息,客户端需要向配置中心查询分片信息,然后发起读写操作。以下图的MongoDB和HDFS为例,在MongoDB中,有一个Config Server的配置中心,应用客户端需要嵌入Router,首先Router从Config Server获取数据存储的分片,然后再指定分片进行查询;在HDFS中,有NameNode存储路由规则。

      这种方式可以支持超大规模集群,集群数量可以达到几百上千,但是架构复杂,一般要求独立的配置中心节点,配置中心同时也需要高可用,例如MongoDB使用的是主备复制(replica set)来保证高可用、HDFS是使用的是Zookeeper保证的高可用(HDSF在2.0之前的Namenode是单点)

        

     路由规则 -- 路由转发:

      路由转发也有两种实现方式,客户端重定向和服务端请求转发。客户端重定向典型案例就是Redis,服务端请求转发的典型案例就是ElasticSearch

      路由转发的方式是每个节点都保存所有路由信息,客户端请求任意节点即可,架构相对简单,以Redis的Cluster来说,通过gossip协议来实现分片信息更新,但是这种方式无法支持超大规模集群,以Redis为例,官方建议1000以内,因为集群数量过大,本身集群内节点的数据复制就会占用很大的带宽,会导致集群通讯问题。

        

   (3)分片架构高可用方案

    分片架构的高可用主要可以分为独立备份和相互备份。

    独立备份:每个节点有独立的备份节点,可以用主备、主从、集群选举等方案实现;优点是实现简单,缺点是成本高,主要应用在存储系统已经支持节点级别复制的场景

    互相备份:分片之间的节点相互备份,优点是成本低,缺点是实现复杂;主要应用在存储系统支持数据块级别的复制。

                

     独立备份类似的应用场景有 MongoDB、Redis、Mysql等,互相备份类似的应用有 HDFS、Elasticsearch

        

   2、分区架构

   分片架构主要是为了提升写性能和解决存储限制的,但是其不能应对城市级别的故障,同时也不能应对大数据量远距离调用的性能问题,例如只部署在背景,那么大请求量的情况下,对于广州的用户来说,响应时间肯定会很长。

  而分区架构就是为了解决上述两个问题,其通过冗余 IDC 来避免城市级别的灾难,并提供就近访问。如果应用分区架构,就说明业务量已经非常大了。

  分片架构是不可以跨城市部署的,因为如果跨城市部署,分片架构的性能会急剧下降,就达不到分片架构提高集群性能的目的。

         

   由上图看到,分区架构需要一个全局路由,将用户路由到不同的 IDC 进行访问,全局路由比较常见的是 DNS 和 GSLB,DNS 是标准协议,比较通用,但是其只能实现就近接入的路由; GSLB 非标准,需要独立开发和部署,功能非常强大,可以做状态检测、基于业务规则的定制路由。

        

   分区架构备份策略常见的有:集中式、互备式、独立使

    集中式:所有的 IDC 都用同一个 IDC 备份,设计简单,各分区之间无直接联系,可以做到互不影响;扩展也容易,如果要增加一个新的分区,只需要将新分区的数据复制到备份分区即可,其他的分区不受影响;缺点是成本较高,需要单独建一个备份中心。

    互备式:所有的 IDC 之间数据互相备份,设计比较复杂,各个分区除了要承担业务数据存储,还需要成本备份功能,相互之间互相关联和影响;扩展也比较麻烦,例如要新增一个分区;优点是成本低,可以直接利用已有机房和网络。

    独立式:每个分区都有自己的备份 IDC ,这种方式设计简单,各分区互不影响,扩展也容易,新增的分区只需要搭建自己的备份中心即可;缺点就是成本高,每个分区需要独立分备份中心,备份中心的场地、带宽、网络都是很高的成本。

        

     在实际应用中,集中式用的比较多,因为其可扩展程度高,复杂度低,成本中等。

四、如何设计存储架构

  存储架构设计步骤分为:估算性能需求、选择存储系统、设计存储方案,如果随着时间推移,性能要求发生变化,还可以迭代设计,重新选择存储系统。

  估算性能需求主要是基于业务场景来估算,包括存储量、读写性能等,遇到的挑战可能是不知道如何估算或者担心估算不准等问题

  选择存储系统是指根据技术储备、方案优缺点选择合适的存储系统,遇到的挑战可能是不知道有哪些存储系统,或是知道有哪些存储系统但是不知道怎么选择

  设计存储方案是指基于选择的存储系统,设计具体的存储方案,如果发现不行,需要重新回到步骤2,重新选择存储系统,遇到的挑战可能是不知道如设计存储方案。

  1、估算性能需求

  估算模型:用户量预估,用户行为建模、性能需求计算

  性能估算:

    对于ToB业务来说,是去找客户或者解决方案架构师(偏向于业务分析)澄清具体的用户量;而对于ToC业务来说,需要架构师自己决策,如果架构师没有这方面的业务知识或者害怕自己决策的不准,可以找产品、运维、老板进行沟通,一起决策。为什么ToB是澄清ToC是推测呢,因为ToB业务的甲方对自己的业务是最了解的,澄清的效果和效率是最好的,如果是ToC的业务,没有人会告诉你用户行为和用户量,因此只能推测。

  用户量估算:可以通过规划、推算、对比的方式进行估算

    规划:根据成本、预算、目标等去确定规划,例如某个新业务预算投入2000W进行拉新,首先我们要对行业有一定的了解,在互联网,一般情况下拉新一个安卓用户是100块钱,一个IOS用户是140块钱。那么就可以用成本的2000W计算可以拉新多少用户。再或者年底某业务用户规模达到100W,那么无论这个结论是否是在画饼,那么我们都应该按照100W去预估。

    推算:基于已有数据进行推算,例如做一个面向一个城市的在校大学生购物小程序,首先要查询一下该城市的在校大学生有多少,在预估一下在校大学生有多少会用这个购物小程序,基本上就可以推算出用户量;再例如香港地铁扫码乘车业务,首先查一下香港的总人数,然后在预估一下乘地铁的人数,例如有80%,那么用总人数乘以80%即可得到用户量。

    对比:假设做一个新业务,既不知道规划又没办法推算,但是业界已经有竞争对手或者标杆了,那就可以跟已有标杆对比,例如已经有了拼多多,淘宝要做一个淘宝特价版,要预估一下用户量,这个就可以对比一下拼多多的用户量,然后基于一定的比例,计算出自己的用户量;还有就是可以跟自己已有的同类业务对比,例如美团做酒店的业务预估用户量,就可以拿着美团的机票业务用户量进行预估。

  用户行为建模:可以从行为、数量、频率三个维度进行评估,行为是指用户的典型行为,数量是指采取某种行为的用户数量,频率是指用户某种行为的频率。例如,预计每个月使用钱包付款码的用户由100W,付款笔数达到500W笔;再或者每天使用扫码乘车的用户有500W,平均扫码次数4.6此。

   存储性能需求计算:可以通过数据量、请求量、预留量三个维度进行评估,存储量指需要存储的数据总量,请求量是指对数据的读写请求(QPS/TPS),预留量指的是需要预留出来的增长空间。存储性能需求计算技巧:并不是所有的数据都一定要同样的存储方式,例如当前数据和历史数据需要分开存储;TPS/QPS需要计算出以秒为单位的数值,并且要计算平均值和峰值;预留增长不能太大也不能太小,太大太小没有固定标准,一般1.5倍、2倍都是可以的,但是不能是1.1倍,100倍这种,如果能做到线性伸缩是最好的。

  案例:

    用户行为建模:每天使用扫码乘车的用户有500W,平均扫码次数4.6次。

    分析和计算过程:

      (1)假设总用户量为1000W,则用户存储量是1000W

      (2)每次扫码乘车,都会访问一次用户数据,则用户数据读取次数:每天 500W * 4.6 = 2300W

      (3)每次扫码乘车都会生成一条乘车记录,则单日乘车记录: 500W * 4.6 = 2300W

      (4)乘车记录要保存两年,则总数据量为:2300W * 800 = 200亿

      (5)每条乘车记录对应一条支付记录,单日支付记录为2300W,总数据量为200亿

      (6)地铁乘车60%集中在早晚高峰的两个小时中,因此乘车记录写入的TPS峰值为 2300W * 60% / (2 * 3600) = 2000

    如果这个单指香港地铁的系统,那么就不需要预留空间了,因为香港总人数为七八百万,这里已经预估用户量为1000W了,已经足够大了,因此不再需要预留空间。

  2、选择存储架构

  在选择存储架构时,首先判断单机是否可以存储所有数据,如果可以,需要再判断单机写性能,如果上述两步中有一步不满足,就要做分片或分区架构,做分区架构时,考虑是否需要分区部署,需要的话,就采用分区架构,如果不需要的话,就采用分片架构;

  如果单机可以存储所有数据且满足写性能要求,那么就再看读性能,读性能可以满足,就采用主备架构,不能满足,就采用主从架构,在选择主备和主从架构时,还要判断是否要进行自动切换,主备需要切换就是主备切换架构,主备不需要切换就是主备复制架构;主从不需要切换就是主从复制架构,需要切换还要选择主从切换还是集群选举,如果两台机器可以满足查询性能,使用主从切换就可以了,如果两台不能满足查询性能,就需要使用集群选举。

        

   常见的存储存储系统

    常见的存储系统有关系型数据库、非关系型数据库、大数据存储

        

   这么多的存储系统,如何选择呢?先根据技术本质进行选择,如果有多个同时满足,再根据自己的技术储备,挑选自己熟悉的,如果还有多个同时满足,再从可维护性、成本、成熟度等维度进行综合考虑。

  技术本质:技术本质是一个系统的DNA,是该系统区别其他系统的典型功特征,例如MongoDB是文档数据库,Mysql是关系型数据库,Redis是数据结构、Elasticsearch是倒排索引搜索引擎等

  技术本质影响了系统的核心应用场景和优缺点,例如MongoDB是文档数据库,优点是Schemaless,缺点是事务支持不好,Elasticsearch是搜索引擎而不是存储引擎。

  场景:如果我们存储游戏场景的用户数据,使用MongoDB比较好,因为游戏中用户数据没有什么固定的结构;如果做论坛数据,使用ElasticSearch比较好,因为可以使用文档搜索。

  因此我们学习一个新技术的时候,首先要了解该技术的技术本质,然后再去了解细节,因为技术本质决定了该技术的应用场景和优缺点,技术细节不会影响技术的应用场景,只会对落地时的方案有一定影响。所以在做架构备选方案时,是不需要知道太多技术细节的,只需要知道技术本质和应用场景即可。

  3、如何设计存储方案

  存储方案设计分为三个步骤:设计数据结构、验证读写场景、评估读写性能

    设计数据结构:基于选择的存储系统提供的数据结构,选择或设计具体的数据结构来实现我们的业务需求,例如如何设计具体的表,选择Redis的哪个数据结构

    验证读写场景:将数据结构放到具体的场景进行验证,设计读写具体如何执行

    评估读写性能:评估具体场景下的数据结构设计是否满足性能需求,不满足则重新设计。

  案例:Redis存储粉丝列表,首先我们已经选择了Redis作为存储系统,然后有两种落地方案,一种是List,一种是Sorted Map。

    设计数据结构:选择List,List是有序的,可以重复

    验证读写场景:如果新增关注,需要扫描List判断是否重复,不重复则尾部增加;取消关注时,需要扫描List找到粉丝ID进行删除;拉黑和取消一样

    评估读写性能:新增关注、取消关注都需要扫描整个List,特别是大V,性能会很低,某些爆红的账户可能存在性能问题,因此需要再迭代看看是否有其他更合适的方案。

    设计数据结构:选择Sorted set,有序但不能重复

    验证读写场景:新增关注,使用关注时的时间作为score来排序,无需扫描,Redis会自动去重;取消关注和拉黑时直接删除。

    评估读写性能:无论是读写性能还是实现复杂度,都比List简单。

五、常见存储系统分析

  学习一种技术,首先要理解技术本质、然后明确部署架构、再研究数据模型、最后模拟业务场景。

  理解技术本质:理解系统核心的技术本质,技术本质决定了应用场景和性能量级。例如Redis是K-V存储系统,HBase是sorted map。

  部署架构:学习存储系统支持的部署架构,明确其架构本质。例如Redis可以使用单机、主从、哨兵、集群四种部署架构;HBase只有一种部署架构。

  研究数据模型:研究存储系统提供的存储模型,包含哪些概念,如何应用等。例如Redis支持多种数据类型,HBase的table、column等

  模拟业务场景:模拟一些常见的业务场景,完整实现一个案例,并测试其性能,也可以先参考网上已有的测试结论。例如如何用Redis来存储关注关系,如何用HBase存储关注关系等。

  1、Redis

    技术本质:基于内存和数据结构存储,基于内存意味着高性能,但也意味着持久化不是其核心,可能会造成数据丢失,数据结构存储表示其不是关系型数据库,也不是文件存储

    用途:数据库、缓存、消息服务器

    性能量级:TPS 5~10 万

    Redis部署架构:有主从、哨兵、集群三种方式,主从的技术本质是主从复制、读写分离,但是无自动切换功能;哨兵的技术本质也是主从复制、读写分离,但是器可以自动切换;集群的技术本质是数据分片。

    Redis的数据结构:String、List、Hash、Set、Sorted set

    模拟业务场景:用Redis实现关注列表存储,使用List和使用Sorted set,具体分析上面已经说过。

  2、HBase

    技术本质:no-relational:非关系型数据;versioned:多版本的;after Bigtable:基于谷歌的Bigtable 原理实现的(Bigtable 的原理:multidimensional sorted map,实际上就是按照key排序的map,map里面有字段有值,就相当于一个字段);on top of Hadoop and HDFS:基于 Hadoop 和 HDFS来搭建的,底层存储结构是 LSM。

    用途:大数据存储,那多大的数据才算大数据呢,一般从数据量级上来说,10亿条以上才算大数据;从数据容量上来说,最起码得几T才是大数据

    参考性能量级:四台32核主机每秒插入70000条,读取大约是25000条,扫描100条以内记录,每条15000条。这里可以看到写入和必读取快,这是因为HBase底层存储结构是LSM,是基于日志合并的,写入的时候,只需要尾部追加即可,而读取时,需要合并多条日志,因为可能同一个日志写了多次,因此需要合并日志。

    部署架构:本质上是分片集群,Zookeeper做HMaster的切换,RegionServer由HMaster管理。

        

     数据结构:table-表;row-行;column family-列簇,指多个列合并在一起;Column-列;Cell - 一个具体的存储单元;Timestamp - 多版本,一个数据写入多份,可以根据Timestamp区分多版本。

    模拟业务场景:关注列表存储方案

      方案一:

        key是用户ID,Value:将关注顺序作为Column,被关注的用户ID作为Value,增加count列作为总关注数。

        具体方案:新增关注,先读取count列计算出关注顺序,再由此顺序作为column;取消关注,需要遍历整个 follows column family,并修改 count 列;拉黑和取消关注操作一致。

        方案分析:性能太低,需要读取出整个列表,然后遍历整个列表。

        

       方案二:

        key是用户id+关注人id,value是关注人全名

        具体方案:新增关注直接插入一条数据,取消关注和拉黑直接删除记录,查看列表,使用scan前缀为用户id的key

        方案分析:读写性能都很好,但是一个关注关系就需要插入一条数据。

        如果要存储粉丝数,直接使用用户id+count来作为key存储即可。

        

  3、HDFS

    技术本质:基于Google的GFS论文的开源实现。file system :这是文件存储,不是关系数据,也不是数据结构。distributed:分布式的文件存储,不是类似于 Linux 的 Ext 文件系统。low-cost hardware:运行在低成本硬件,而不是 IOE 的高成本硬件。

    用途:arge data sets:大数据存储,可以横向伸缩,瓶颈是网络的带宽。

    参考性能量级:性能可横向伸缩,瓶颈是带宽。

    部署架构:分片集群,大文件存储,不适合只存储只有几K的小文件,使用Zookeeper来做高可用,用Namenode来做集群管理。

        

     数据模型:简单来说,这就是一个文件系统,你需要自己来规划好目录和文件就可以了,意味着从传统的磁盘文件存储迁移到HDFS存储是非常方便的。

  4、Clickhouse

    技术本质:列式存储,数据库管理系统,OLAP 场景(OLAP,做数据分析用的,而不是做在线的业务访问用的;MySQL 是 OLTP)

    用途:OLAP

    相关知识:OLTP:联机事务处理,执行大量增删改查,关注响应速度、高并发、数据一致性。OLAP:联机分析处理,执行少量复杂查询,关注吞吐量,很少修改数据。行式存储:表中的一行记录存储在一个数据块中。列式存储:表中的一列数据存储在一个数据块中。

    部署架构:分片集群、Zookeeper管理,分片独立复制

        

     数据模型:基于 SQL 表设计即可。如果你需要将原来基于数据库的统计分析剥离出来,且数据量并不大的话,使用ClickHouse的复杂度是很低的;而如果切换为Hadoop、Spark这类平台的话,切换复杂度和成本就会高很多。

六、存储架构设计实战

  以千万学生管理系统设计存储架构为例,按照估算性能需求、选择存储系统、设计存储方案三步进行设计。

  1、存储性能估算

    用户量预估:如果是给教育部做系统,那就直接推算,其实就相当于是ToB业务,可以直接找教育部要学生数量等信息;如果是给创业公司做系统,就是ToC业务,那就按照规划来推算,这里暂定1000万用户

    用户关键行为:登陆注册、文件上传下载、选课、考试

    用户行为建模和性能估算:

      登录:学生管理系统主要管理学生的信息管理、作业、考试等,其中交作业是高频场景,每个学生每天都要交作业。假设每个学生每天提交4次作业,登录的次数就是 1000万 * 4 = 4000万,考虑到提交作业一般是在晚上18:00~22:00,因此登录 TPS 要求为:4000万/(4 * 3600) = 3000/s。

         登录会产生一条登录记录,因此每天有4000万条登录记录要存储,登录记录保存3个月,总的数据条数为:4000万 * 3 * 30 = 36亿条,每条记录包含学生 ID(4字节)、登录时间(4字节)、登录 IP(4字节),总大小为 36亿条 * 12 = 43G。

         登录记录主要是为了事后查验,学生和老师极少主动去查询学生登录信息,因此读取性能可以忽略。

      注册:总共1000万学生,每年只有新生注册,不同学校新生开学时间是分散的,而且注册可以在入学后完成,因此我们假设每年250万新生需要注册,注册时间分散在9.1~9.30这30天内,则注册每天请求次数为:250 万 / 30 = 8.3 万,考虑到开学第一天人数会多一点,计算结果调整为10万每天,且主要在12小时内操作,因此 TPS 计算为:10万/(12 * 3600) ≈ 3 /s。

          学生注册后需要存储学生信息,学生信息主要包含学号(10字节)、身份证(19字节)、头像(图片,不超过1M)、专业(4字节)、家庭信息(100字节)等,且学生信息要永久保存,即使毕业后也不能删除,因此存储分为两部分:在校学生数据存储量 = 1000万 * 200字节 = 2G,图片数据:1000万 *1M = 10T。离校学生数据存储量 = 按年增长,只做备份,每年的数据 = 1/4 在校学生数据存储量。

      考试:假设每门学科每年2次考试,每个学生平均一学期20门课,考试采取机考的方式,每门考试的答案20判断题、20选择题、4道大题(答案200字以内),考试结果永久保存,在校学生能够看到自己曾经的考试结果,则考试结果记录的存储量为:在校学生:1000万 * 20(课)* 2(考试次数) * 1000(答案)* 2(学期) * 3(只有前三年考试)= 2.4T。离校学生:每年250万,存储量为 0.6T。

          假设学校的考试都安排在某一个月内,考试的时候请求试卷,提交答案,中间答题过程浏览器本地完成,由于考试集中在上午4小时和下午4小时,且请求试卷集中在考试开始的前1分钟,提交答案集中在考试结束前的30分钟,因此估算如下:请求试卷:1000万 * 20(课)/ 20(周末不考试) / 4(每天4堂考试)/ 1分钟 = 250万请求/分钟 ≈ 5万/每秒。提交试卷: 1000万 * 20(课)/ 20(周末不考试) / 4(每天4堂考试)/ 30分钟 = 1700/每秒。

    存储性能汇总:

      登录:登录次数:3000/s;登录记录:存储数据量36亿条,存储容量43G,写入 TPS = 登录 TPS = 3000/s,读取 TPS 无需特别设计。

      注册: 注册 TPS = 3/s,可以忽略不计;在校学生数据存储:基本数据 = 2G,图片数据 = 10T;离校学生数据存储:基本数据每年增长 500M,图片数据增长 2.5T。

      考试:在校学生考试结果存储:2.4T;离校学生考试结果存储:每年增长 0.6T;试卷请求 QPS:5万/s;提交试卷 TPS:1700/s。

    根据面向复杂度的架构设计思想,这里的复杂度在于图片数据存储(10T存储,每年增长2.5T),试卷请求QPS 5万QPS

   2、选择存储系统

    存储系统选择逻辑:

        

     登陆注册存储架构分析:

      登录记录,首先判断单机是否可以存储所有数据,显然不能,那么要么是分区架构要么是分片架构,假设我们在合理不需要分区,因为千万学生管理系统的数据量并不是特别大,那么就直接采用分片架构;选择了分片架构。

      在校学生基本信息,首先判断单机是否可以存储,明显可以存储,单机是否支持写性能,也是可以的,单机是否支持读性能,也是可以的,那么就要看是否需要自动切换,假设我们这里需要,那我们的架构就应该是主备复制自动切换或者主备复制集群选举方案。

      在校学生图片数据,单机不能存储,要看是否需要分区,假设这里不需要,那么就采用分片架构。

        

     登陆注册功能:

      根据上面的分析,可以使用Mysql主从复制集群来存储在校学生基本信息,注册的读写请求量并不会很大,使用主备复制即可,另外,注册也不是核心流程,出了问题,在恢复后在操作也是可以的,对流程影响不大。,因此采用主从复制架构即可。

      使用HBase集群来存储登录记录和图片信息,登陆记录和图片信息的存储量比较大,另外这些信息和业务没有关联,因此可以使用HBase来存储,方便扩展,也不需要考虑业务关联。

        

     考试功能:

      根据上面的分析,使用Redis Sentinal存储试卷和访问,因为试卷读取的TPS非常高,因此需要使用Redis来提高访问性能,同时需要从机,但是数据量并不会很大,也不涉及横向扩展,并且为了保证自动切换,选择Sentianl

      使用Mysql分库分表来存储老师课程考试结果,主要功能是让老师查看成绩和分析每一门课的考试情况,因为老师可能查看60分以上有多少人,70分以上有多少人,再或者分析这一道题有多少人做错了等等,有很多类似的这种分析场景,因此使用Mysql即可,同时数据量也比较大,因此采用Mysql的分库分表。但是这里的数据不会无限增长,因为可以把今年之前的所有数据都删除,因为老师只需要分析本年的考试结果即可。

      使用HBase来存储学生考试结果,即每一个学生的答题内容,这是因为学生只需要查看自己的考试结果即可,与其他人无关,不涉及一些业务场景,同时这类数据是不能删除的,因此数据量对比老师的考试成绩存储来说就会大很多,且持续增长,使用HBase可以存储这类数据,同时也可以支持横向扩展。

        

     合并后的存储架构图:

      上面的分析是不需要写入架构设计方案的,但是合并后的存储架构是需要写入架构设计方案的,因为这是需要向各方展示的设计结果。

      可以发现,合并后Mysql的主备复制和分库分表统一为Mysql集群了,这是因为合并后统一使用一套Mysql集群即可,这样就可以减少成本和维护的复杂度。

        

   3、设计存储方案

  (1)HBase存储方案设计 -- 学生图片信息

    数据结构设计:key:学校ID + 学号ID + pic;Column Family:info;Column:pic

    读写分析:学生登陆后,在界面上显示头像,直接按key读取即可。管理员查看班级所有同学图像,可以按照学号前缀里面的班级信息scan,例如20200801代表2000级08系01班

  (2)HBase存储方案设计 -- 学生登陆记录

    数据结构设计:key:学校ID + 学号ID + timestamp;Column Family:login;Column:IP

    读写分析:学生查看自己的登录信息,直接按照前缀查询即可。

    如果要按照时间范围查询或者IP查询,那么就可以存储多个维度的Key,只不过这样会在一次登录存储多条数据,会导致数据量成倍递增,但是HBase本身就可以存储海量数据,因此这并不会影响架构设计。

  (3)HBase存储方案设计 -- 学生考试结果

    数据结构设计:key:学校ID + 学号ID + 考试ID;Column Family:test;Column:result,score,其中result是Json格式,是学生提交的试卷,score是老师的评分结果。

    读写分析:学生提交考试结果,直接按照 key 保存 result;老师改卷后,直接写入 score;学生查看自己的成绩,按照 key 读取 result 和 score,可以看到得分和具体错在哪里。

  

 

posted @ 2022-04-22 09:58  李聪龙  阅读(1458)  评论(0编辑  收藏  举报