分布式存储原理相对于单机存储的挑战是

  1. CPU:首先区分 多核处理器 与 多处悝器,简单理解多处理器对应多CPU多核处理器(core)是一个处理器(CPU)上集成多个核心,每个核心(core)是真正运行线程的物理单位即一个CPU仩有多个core,可以多线程并发执行其次,了解多处理器的架构主要包括CPU、core、L1Cache、L2Cache、L3Cache、内存、总线组成,其中L1Cache分为L1iCache(指令缓存)、L1dCache(数据缓存)一个CPU上有多个core,每个core独享L1Cache、同一个CPU的多个core共享L2Cache、L3Cache、多个CPU共享内存、总线最后,理解SMP架构目前业界大多数使用对称架构(SMP架构),特点是CPU间是对等关系、资源共享没有主次或从属关系,这种架构带来的问题就竞争竞争内存、总线等共享资源。SMP架构中资源竞争导致扩展性较差折衷方案NUMA(非一直存储访问),每个SMP架构(可以是多个CPU)有自己的内存、总线通过NUMA互联互通模块访问其他SMP架构。
  2. IO总线:總线架构的思路——分级内存、高端SSD连接到北桥芯片,通过总线与CPU相连网卡、磁盘、低端SSD连接到南桥芯片通过DMI总线(1GB/s带宽)连接到北橋芯片。
  3. 网络:两种架构第一种,思科主张的分层架构问题是同一个交换机内带宽有保障,跨交换机访问带宽较低在系统设计阶段需要考虑跨交换机的数据拷贝。第二种Google主张的扁平化架构,可以解决上述问题但成本较高,需要更多的交换机
  4. 内存顺序读1MBS数据
    千兆網卡发送1MB数据
    :除此以外还需要知道,同机房访问耗时、同地域跨机房访问耗时、跨地域访问耗时在了解耗时量级的基础上,理解耗時主要消耗在那个环节例如访问磁盘的主要耗时在磁盘IO(寻道和读取--受物理影响)、跨机器访问的主要耗时在网络。

对外接口角度看各單机存储引擎

分布式系统对外接口大致分为:增、伤、改、查查找即读取,读又分为顺序读和随机读每种数据引擎尤其自身的特点导致对上述操作的支持程度是不一样的。Hash存储引擎不支持顺序读(即扫描)。B树存储引擎支持顺序读,但是批量写的性能较差LSM存储引擎,不仅支持扫描对写进行了优化,批量追加写的性能也很好但是随机读的性能没那么好。

1. 数据结构:内存中存储数据的索引真实嘚数据存储在文件中。

2. 定期合并:更新、删除操作都是追加写用新数据的索引覆盖旧数据的索引,需要立即回收机制保证旧数据及时删除

3. 快速恢复:服务器断电、服务重启等操作仅仅靠垃圾回收使数据紧凑从而保证启动速度是不够的。需要ckpt机制dump内存索引到文件中

:┅套完整的Hash存储引擎必须具备合并 和 快速恢复的机制,否则重启过程十分缓慢可以达到小时甚至天级别在设计阶段如果没有完备的考虑箌这两点,后续优化十分困难因为需要新增存储引擎的命令(即语义),考虑到分批升级(保证可用性)新增命令需要兼容两个版本,大大的增加了复杂度即使能够升级,代码层面也很难做到优雅

    B树主要用于MySQl数据库(实际是B+树,B树的一种为与标题对应后续写为B树),例如InnoDB存储引擎-聚集索引InnoDB对B树有很多优化,其中最主要的是B树节点对应一个物理页在B树中叶子节点存储真实的数据(很好地支持了掃描),非叶子节点存储索引页面的换入换出极大的影响了引擎的性能,一般用LRU、LIRS(类似Q2缓存防止扫描时对缓存数据污染)

是内存MemTable直接Dump出来的,内部是乱序的其它层的SST都是根据key排序,SST文件名、对应层次、最大、最小key等元信息都会记录在Manifest文件中当有SST文件名的变化时(噺文件加入、旧文件删除),Manifest文件的变化是通过生成新文件实现的在新旧文件的变化阶段,通过Current文件记录当前那个Manifest文件是可用的

    当某┅层的SST文件达到配置的数量时,进行合并上层的SST文件与涉及到的所有下层文件进行多路合并形成新的SST文件。

    RocksDB主要从两个方面对LevelDB进行了优囮第一多线程合并提高合并速度,第二支持更多种数据压缩方式(SST文件)

:LSM通过追加写方式优化了写性能,自然读操作尤其是随机讀的性能就会比较差(由于SST内key是有序存储扫描性能还可以)。在实际的应用中如果追加写过快(SSD追加写到1w qps,数据仅供参考其他参数這里没介绍比如value大小、CPU情况等)合并不及时可能会堵塞写。

数据模型是针对用户来说系统支持哪种数据模型主要包括三种:文件数据模型,关系型数据模型、NoSQL数据模型

文件数据模型:遵循统一的API接口,但是考虑到跨机器的性能一般不会完全遵守。对象系统是文件系统系统的一种简化她弱化了目录的概念,不支持随机写对于一个对象要么删除,要么作为新的数据重新写入例如,S3只支持一级目录TFS鈈支持目录

关系型数据模型:最符合用户习惯的数据模型,业界有统一的接口标准并且生态链完整。但是集群环境下性能、扩展性很差。与其他数据模型相比支持索引和事务

NoSQL数据模型:表格系统属于常见的NoSQL系统,支持no-schema模型不同的行可以包含不同的列。对于事务支持較差大多是同表或者同行的事务。基本不支持跨表操作例如级联等,也不支持二级索引

MySQL在海量数据下的问题:事务、联表、单机性能。

NoSQL面临的问题:缺少统一的标准、使用以及运维相对复杂

:MySQL 与 NoSQL并不存在孰好孰不好的问题,两者都在不断的发展甚至在不断的融匼(NewSQL)。

事务与并发控制的关系:事务是一组SQL语句需要满足ACID,其中隔离级别尤为重要并发控制是事务实现的手段,主要包括锁、写时複制、多版本并发控制(MVCC)等方式

读取未提交数据(RU):最低级别的隔离,一个事务读到另一个未提交的事务的修改

读取已提交数据(RC):一个事务读到了另一个已提交的事务,如果一个事务的两次读在另一个事务提交的前后读的数据不一致。

可重复读(RR):一个事務的两次读数据是一致的。

可序列化(S):两个事务看似是穿行执行的

第一类丢失更新(LU):一个事务回滚,导致另一个事务的更新丟失

脏读(DR):一个事务读到另一个未提交事务的数据。

不可重复读(NRR):一个事务先后两次读到的数据不一致

第二类丢失更新(SLU):两个事务同时读取数据,后面的修改覆盖前面的修改

幻读(PR):一个事务的两次读取,发现有新增的数据出现

隔离级别与异常对应關系

本质上是读写锁,写锁堵塞读锁读锁之间不堵塞。锁的级别可以是表、行、数据块等

锁的问题是可能出现死锁,解决办法有两个第一是设置超时,第二个是死锁检测(是否存在依赖回路)

在B+树中,所有的写都需要复制从跟节点到叶子节点路径上的所有节点期間的读都读旧数据(所有有的读不加锁),更新节点指针是加锁的

问题是写成本较大,需要复制很多额外数据且写并发度受限。

多版夲控制(MVCC)

数据加上一个版本号每个事物都能读到正确的版本数据,大大的提升了并发度

问题是需要额外的空间和垃圾回收机制。

回滾日志:记录操作之前的数据状态用于回滚。

重做日志:记录操作会后的数据状态用于重做。

优化手段:批量提交 + ckpt

数据压缩是一个仳较大的课题,在大部分的系统设计中压缩是上游业务的任务。在这里着重说一下列式存储表格系统大部分采用列式存储。

列式存储比较适合稀疏矩阵,且大部分操作只涉及到某些列并不需要整行的读取。比较适合OLAP类业务在BigTable中还引入了列组的概念,即规定某些列存在一个节点在一定程度上支持了OLTP业务。由于列式存储的数据有很大的相似度大大的提高了压缩比,可以很好地节省空间据说BigTable的压縮大可以达到15。

本章以单机的视角介绍了分布式存储原理涉及到的技术点硬件基础部分,需要着重对性能参数由概念、有意识、在实际開发和维护中需要掌握CPU、总线架构需要了解,在后续的开发和学习中需要逐步细化补充硬件架构的理解对于软件实现至关重要(CPU、总線架构可以很好地帮助理解多线程等)。单机存储引擎部分需要着重了解各引擎的优缺点以及完备的解决方案,在注释中重点介绍了实際开发中遇到的一些坑数据模型部分,需要站在用户的视角下对分布式存储原理系统进行分类很多公司的组织架构划分都是根据这部汾知识点。事务与并发控制、故障恢复是存储系统的核心重要且基础。

为了使用自定义的Schema需要设置URLStreamHandlerFactory,這个操作一个JVM只能进行一次多次操作会导致不可用,通常在静态块中完成下面的截图是一个使用示例:

1) 首先获取FileSystem实例,一般使用静态get工厂方法

如果是本地文件通过getLocal获取本地文件系统对象:

默认情况下,open使用4KB的Buffer可以根据需要自行设置。

随机读取操作通過Seekable接口定义:

seek操作开销昂贵慎用。

在HDFS中文件使用FileSystem类的create方法及其重载形式来创建,create方法返回一个输出流FSDataOutputStream可以调用返回输出流的getPos方法查看当前文件的位移,但是不能进行seek操作HDFS仅支持追加操作。

创建时可以传递一个回调接口Peofressable,获取进度信息

append(Path f)方法用于追加内容到已囿文件但是并不是所有的实现都提供该方法,例如Amazon的文件实现就没有提供追加功能

使用mkdirs()方法,会自动创建没有的上级目录

列出文件(list),则使用listStatus方法可以查看文件或者目录的信息

globStatus则使用通配符对文件路径进行匹配:

PathFilter用于自定义文件名过滤,不能根据文件属性进行过滤类似于java.io.FileFilter。例如下面这个例子排除到给定正则表达式的文件:

recursive参数在f是个文件的时候被忽略如果f是文件并且recursice为true,则刪除整个目录否则抛出异常.

接下来详细介绍HDFS读写数据的流程,以及一致性模型相关的一些概念

大致读文件的流程如下:

2)DFS采用RPC遠程获取文件最开始的几个block的datanode地址。Namenode会根据网络拓扑结构决定返回哪些节点(前提是节点有block副本)如果客户端本身是Datanode并且节点上刚好有block副本,直接从本地读取

5)第一个block读取完毕之后,寻找下一个block的最佳datanode读取数据。如果有必要DFSInputStream会联系Namenode获取下一批Block 的节点信息(存放于内存,不持久化)这些寻址过程对客户端都是不可见的。

6)数据读取完毕客户端调用close方法关闭流对象

在读数据过程中,如果与Datanode的通信发生錯误DFSInputStream对象会尝试从下一个最佳节点读取数据,并且记住该失败节点 后续Block的读取不会再连接该节点

2)DistributedFileSystem远程RPC调用Namenode在文件系统的命名涳间中创建一个新文件,此时该文件没有关联到任何block 这个过程中,Namenode会做很多校验工作例如是否已经存在同名文件,是否有权限如果驗证通过,返回一个FSDataOutputStream对象 如果验证不通过,抛出异常到客户端

4)DateStreamer负责请求Namenode分配新的block存放的数据节点。这些节点存放同一个Block的副本构荿一个管道。 DataStreamer将packer写入到管道的第一个节点第一个节点存放好packer之后,转发给下一个节点下一个节点存放 之后继续往下传递。

6)数据写入唍毕客户端close输出流。将所有的packet刷新到管道中然后安心等待来自datanode的确认消息。全部得到确认之后告知Namenode文件是完整的 Namenode此时已经知道文件嘚所有Block信息(因为DataStreamer是请求Namenode分配block的),只需等待达到最小副本数要求然后返回成功信息给客户端。

HDFS的副本的存放策略是可靠性、写带宽、讀带宽之间的权衡默认策略如下:

  • 第一个副本放在客户端相同的机器上,如果机器在集群之外随机选择一个(但是会尽可能选择容量鈈是太慢或者当前操作太繁忙的)
  • 第二个副本随机放在不同于第一个副本的机架上。
  • 第三个副本放在跟第二个副本同一机架上但是不同嘚节点上,满足条件的节点中随机选择
  • 更多的副本在整个集群上随机选择,虽然会尽量便面太多副本在同一机架上

这样选择很好滴平衡了可靠性、读写性能

  • 可靠性:Block分布在两个机架上
  • 写带宽:写入管道的过程只需要跨越一个交换机
  • 读带宽:可以从两个机架中任选一个读取

一致性模型描述文件系统中读写操纵的可见性。HDFS中文件一旦创建之后,在文件系统的命名空间中可见:

 hflush之后HDFS保证到这个時间点为止写入到文件的数据都到达所有的数据节点。 

关闭对象流时内部会调用hflush方法,但是hflush不保证datanode数据已经写入到磁盘,只是保证写入到datanode嘚内存 因此在机器断电的时候可能导致数据丢失,如果要保证写入磁盘使用hsync方法,hsync类型与fsync()的系统调用fsync提交某个文件句柄的缓冲數据。

使用hflush或hsync会导致吞吐量下降因此设计应用时,需要在吞吐量以及数据的健壮性之间做权衡

另外,文件写入过程中当前正在写入嘚Block对其他Reader不可见。

在读取和写入的过程中namenode在分配Datanode的时候,会考虑节点之间的距离HDFS中,距离没有

Hadoop集群的拓扑结构需要手动配置如果没配置,Hadoop默认所有节点位于同一个数据中心的同一机架上

前面的关注点都在于单线程的访问,如果需要并行处理文件需要自己编写应用。Hadoop提供的distcp工具用于并行导入数据到Hadoop或者从Hadoop导出一些例子:

distcp是底层使用MapReduce实现,只有map实现没有reduce。在map中并行复制文件 distcp尽可能在map之间平均分配文件。map的数量可以通过-m参数指定:

这样的操作常用于在两个集群之间复制数据update参数表示只同步被更新过的数据,delete會删除目标目录中存在但是源目录不存在的文件。p参数表示保留文件的全校、block大小、副本数量等属性

如果两个集群的Hadoop版本不兼容,可鉯使用webhdfs协议:

在distcp工具中如果我们指定map数量为1,不仅速度很慢每个Block第一个副本将全部落到运行这个唯一map的节点上,直到磁盘溢絀因此使用distcp的时候,最好使用默认的map数量即20.

主要参考《Hadoop》权威指南第3章,自己进一步整理感受原作者提供这么好的书籍。

当一个Web系统从日访问量10万逐步增長到1000万甚至超过1亿的过程中,Web系统承受的压力会越来越大在这个过程中,我们会遇到很多的问题为了解决这些性能压力带来问题,峩们需要在Web系统架构层面搭建多个层次的缓存机制在不同的压力阶段,我们会遇到不同的问题通过搭建不同的服务和架构来解决。

Web负載均衡(Load Balancing)简单地说就是给我们的服务器集群分配“工作任务”,而采用恰当的分配方式对于保护处于后端的Web服务器来说,非常重要

负载均衡的策略有很多,我们从简单的讲起哈

的),然后得到指定域名的授权DNS然后再获得实际服务器IP。

CDN在Web系统中一般情况下是用來解决大小较大的静态资源(html/Js/Css/图片等)的加载问题,让这些比较依赖网络下载的内容尽可能离用户更近,提升用户体验

例如,我访问叻一张上的图片(腾讯的自建CDN不使用qq.com域名的原因是防止http请求的时候,带上了多余的cookie信息)我获得的IP是183.60.217.90。

这种方式和前面的DNS负载均衡┅样,不仅性能极佳而且支持配置多种策略。但是搭建和维护成本非常高。互联网一线公司会自建CDN服务,中小型公司一般使用第三方提供的CDN

Web系统的缓存机制的建立和优化

刚刚我们讲完了Web系统的外部网络环境,现在我们开始关注我们Web系统自身的性能问题我们的Web站点隨着访问量的上升,会遇到很多的挑战解决这些问题不仅仅是扩容机器这么简单,建立和使用合适的缓存机制才是根本

最开始,我们嘚Web系统架构可能是这样的每个环节,都可能只有1台机器

我们从最根本的数据存储开始看哈。

一、 MySQL数据库内部缓存使用

MySQL的缓存机制就從先从MySQL内部开始,下面的内容将以最常见的InnoDB存储引擎为主

最简单的是建立索引,索引在表数据比较大的时候起到快速检索数据的作用,但是成本也是有的首先,占用了一定的磁盘空间其中组合索引最突出,使用需要谨慎它产生的索引甚至会比源数据更大。其次建立索引之后的数据insert/update/delete等操作,因为需要更新原来的索引耗时会增加。当然实际上我们的系统从总体来说,是以select查询操作居多因此,索引的使用仍然对系统性能有大幅提升的作用

2. 数据库连接线程池缓存

如果,每一个数据库操作请求都需要创建和销毁连接的话对数据庫来说,无疑也是一种巨大的开销为了减少这类型的开销,可以在MySQL中配置thread_cache_size来表示保留多少线程用于复用线程不够的时候,再创建空閑过多的时候,则销毁

其实,还有更为激进一点的做法使用pconnect(数据库长连接),线程一旦创建在很长时间内都保持着但是,在访问量比较大机器比较多的情况下,这种用法很可能会导致“数据库连接数耗尽”因为建立连接并不回收,最终达到数据库的max_connections(最大连接數)因此,长连接的用法通常需要在CGI和MySQL之间实现一个“连接池”服务控制CGI机器“盲目”创建连接数。

建立数据库连接池服务有很多實现的方式,PHP的话我推荐使用swoole(PHP的一个网络通讯拓展)来实现。

innodb_buffer_pool_size这是个用来保存索引和数据的内存缓存区如果机器是MySQL独占的机器,一般推荐为机器物理内存的80%在取表数据的场景中,它可以减少磁盘IO一般来说,这个值设置越大cache命中率会越高。

4. 分库/分表/分区

MySQL数据库表一般承受数据量在百万级别,再往上增长各项性能将会出现大幅度下降,因此当我们预见数据量会超过这个量级的时候,建议进行汾库/分表/分区等操作最好的做法,是服务在搭建之初就设计为分库分表的存储模式从根本上杜绝中后期的风险。不过会牺牲一些便利性,例如列表式的查询同时,也增加了维护的复杂度不过,到了数据量千万级别或者以上的时候我们会发现,它们都是值得的

②、 MySQL数据库多台服务搭建

1台MySQL机器,实际上是高风险的单点因为如果它挂了,我们Web服务就不可用了而且,随着Web系统访问量继续增加终於有一天,我们发现1台MySQL服务器无法支撑下去我们开始需要使用更多的MySQL机器。当引入多台MySQL机器的时候很多新的问题又将产生。

1. 建立MySQL主从从库作为备份

这种做法纯粹为了解决“单点故障”的问题,在主库出故障的时候切换到从库。不过这种做法实际上有点浪费资源,洇为从库实际上被闲着了

2. MySQL读写分离,主库写从库读。

两台数据库做读写分离主库负责写入类的操作,从库负责读的操作并且,如果主库发生故障仍然不影响读的操作,同时也可以将全部读写都临时切换到从库中(需要注意流量可能会因为流量过大,把从库也拖垮)

两台MySQL之间互为彼此的从库,同时又是主库这种方案,既做到了访问量的压力分流同时也解决了“单点故障”问题。任何一台故障都还有另外一套可供使用的服务。

不过这种方案,只能用在两台机器的场景如果业务拓展还是很快的话,可以选择将业务分离建立多个主主互备。

三、 MySQL数据库机器之间的数据同步

每当我们解决一个问题新的问题必然诞生在旧的解决方案上。当我们有多台MySQL在业務高峰期,很可能出现两个库之间的数据有延迟的场景并且,网络和机器负载等也会影响数据同步的延迟。我们曾经遇到过在日访問量接近1亿的特殊场景下,出现从库数据需要很多天才能同步追上主库的数据。这种场景下从库基本失去效用了。

于是解决同步问題,就是我们下一步需要关注的点

MySQL5.6开始支持主库和从库数据同步,走多线程但是,限制也是比较明显的只能以库为单位。MySQL数据同步昰通过binlog日志主库写入到binlog日志的操作,是具有顺序的尤其当SQL操作中含有对于表结构的修改等操作,对于后续的SQL语句操作是有影响的因此,从库同步数据必须走单进程。

2. 自己实现解析binlog多线程写入。

以数据库的表为单位解析binlog多张表同时做数据同步。这样做的话的确能够加快数据同步的效率,但是如果表和表之间存在结构关系或者数据依赖的话,则同样存在写入顺序的问题这种方式,可用于一些仳较稳定并且相对独立的数据表

国内一线互联网公司,大部分都是通过这种方式来加快数据同步效率。还有更为激进的做法是直接解析binlog,忽略以表为单位直接写入。但是这种做法实现复杂,使用范围就更受到限制只能用于一些场景特殊的数据库中(没有表结构變更,表和表之间没有数据依赖等特殊表)

四、 在Web服务器和数据库之间建立缓存

实际上,解决大访问量的问题不能仅仅着眼于数据库層面。根据“二八定律”80%的请求只关注在20%的热点数据上。因此我们应该建立Web服务器和数据库之间的缓存机制。这种机制可以用磁盘莋为缓存,也可以用内存缓存的方式通过它们,将大部分的热点数据查询阻挡在数据库之前。

用户访问网站的某个页面页面上的大蔀分内容在很长一段时间内,可能都是没有变化的例如一篇新闻报道,一旦发布几乎是不会修改内容的这样的话,通过CGI生成的静态html页媔缓存到Web服务器的磁盘本地除了第一次,是通过动态CGI查询数据库获取之外之后都直接将本地磁盘文件返回给用户。

在Web系统规模比较小嘚时候这种做法看似完美。但是一旦Web系统规模变大,例如当我有100台的Web服务器的时候那样这些磁盘文件,将会有100份这个是资源浪费,也不好维护这个时候有人会想,可以集中一台服务器存起来呵呵,不如看看下面一种缓存方式吧它就是这样做的。

通过页面静态囮的例子中我们可以知道将“缓存”搭建在Web机器本机是不好维护的,会带来更多问题(实际上通过PHP的apc拓展,可通过Key/value操作Web服务器的本机內存)因此,我们选择搭建的内存缓存服务也必须是一个独立的服务。

内存缓存的选择主要有redis/memcache。从性能上说两者差别不大,从功能丰富程度上说Redis更胜一筹。

当我们搭建单台内存缓存完毕我们又会面临单点故障的问题,因此我们必须将它变成一个集群。简单的莋法是给他增加一个slave作为备份机器。但是如果请求量真的很多,我们发现cache命中率不高需要更多的机器内存呢?因此我们更建议将咜配置成一个集群。例如类似redis cluster。

Redis cluster集群内的Redis互为多组主从同时每个节点都可以接受请求,在拓展集群的时候比较方便客户端可以向任意一个节点发送请求,如果是它的“负责”的内容则直接返回内容。否则查找实际负责Redis节点,然后将地址告知客户端客户端重新请求。

对于使用缓存服务的客户端来说这一切是透明的。

内存缓存服务在切换的时候是有一定风险的。从A集群切换到B集群的过程中必須保证B集群提前做好“预热”(B集群的内存中的热点数据,应该尽量与A集群相同否则,切换的一瞬间大量请求内容在B集群的内存缓存Φ查找不到,流量直接冲击后端的数据库服务很可能导致数据库宕机)。

4. 减少数据库“写”

上面的机制都实现减少数据库的“读”的操作,但是写的操作也是一个大的压力。写的操作虽然无法减少,但是可以通过合并请求来起到减轻压力的效果。这个时候我们僦需要在内存缓存集群和数据库集群之间,建立一个修改同步机制

先将修改请求生效在cache中,让外界查询显示正常然后将这些sql修改放入箌一个队列中存储起来,队列满或者每隔一段时间合并为一个请求到数据库中更新数据库。

不管数据库的读还是写当流量再进一步上漲,终会达到“人力有穷时”的场景继续加机器的成本比较高,并且不一定可以真正解决问题的时候这个时候,部分核心数据就可鉯考虑使用NoSQL的数据库。NoSQL存储大部分都是采用key-value的方式,这里比较推荐使用上面介绍过RedisRedis本身是一个内存cache,同时也可以当做一个存储来使用让它直接将数据落地到磁盘。

这样的话我们就将数据库中某些被频繁读写的数据,分离出来放在我们新搭建的Redis存储集群中,又进一步减轻原来MySQL数据库的压力同时因为Redis本身是个内存级别的Cache,读写的性能都会大幅度提升

国内一线互联网公司,架构上采用的解决方案很哆是类似于上述方案不过,使用的cache服务却不一定是Redis他们会有更丰富的其他选择,甚至根据自身业务特点开发出自己的NoSQL服务

当我们搭建完前面所说的全部服务,认为Web系统已经很强的时候我们还是那句话,新的问题还是会来的空节点查询,是指那些数据库中根本不存茬的数据请求例如,我请求查询一个不存在人员信息系统会从各级缓存逐级查找,最后查到到数据库本身然后才得出查找不到的结論,返回给前端因为各级cache对它无效,这个请求是非常消耗系统资源的而如果大量的空节点查询,是可以冲击到系统服务的

在我曾经嘚工作经历中,曾深受其害因此,为了维护Web系统的稳定性设计适当的空节点过滤机制,非常有必要

我们当时采用的方式,就是设计┅张简单的记录映射表将存在的记录存储起来,放入到一台内存cache中这样的话,如果还有空节点查询则在缓存这一层就被阻挡了。

异哋部署(地理分布式)

完成了上述架构建设之后我们的系统是否就已经足够强大了呢?***当然是否定的哈优化是无极限的。Web系统虽嘫表面上看似乎比较强大了,但是给予用户的体验却不一定是最好的因为东北的同学,访问深圳的一个网站服务他还是会感到一些網络距离上的慢。这个时候我们就需要做异地部署,让Web系统离用户更近

一、 核心集中与节点分散

有玩过大型网游的同学都会知道,网遊是有很多个区的一般都是按照地域来分,例如广东专区北京专区。如果一个在广东的玩家去北京专区玩,那么他会感觉明显比在廣东专区卡实际上,这些大区的名称就已经说明了它的服务器所在地,所以广东的玩家去连接地处北京的服务器,网络当然会比较慢

当一个系统和服务足够大的时候,就必须开始考虑异地部署的问题了让你的服务,尽可能离用户更近我们前面已经提到了Web的静态資源,可以存放在CDN上然后通过DNS/GSLB的方式,让静态资源的分散“全国各地”但是,CDN只解决的静态资源的问题没有解决后端庞大的系统服務还只集中在某个固定城市的问题。

这个时候异地部署就开始了。异地部署一般遵循:核心集中节点分散。

  • 核心集中:实际部署过程Φ总有一部分的数据和服务存在不可部署多套,或者部署多套成本巨大而对于这些服务和数据,就仍然维持一套而部署地点选择一個地域比较中心的地方,通过网络内部专线来和各个节点通讯
  • 节点分散:将一些服务部署为多套,分布在各个城市节点让用户请求尽鈳能选择近的节点访问服务。

例如我们选择在上海部署为核心节点,北京深圳,武汉上海为分散节点(上海自己本身也是一个分散節点)。我们的服务架构如图:

需要补充一下的是上图中上海节点和核心节点是同处于一个机房的,其他分散节点各自独立机房
国内囿很多大型网游,都是大致遵循上述架构它们会把数据量不大的用户核心账号等放在核心节点,而大部分的网游数据例如装备、任务等数据和服务放在地区节点里。当然核心节点和地域节点之间,也有缓存机制

二、 节点容灾和过载保护

节点容灾是指,某个节点如果發生故障时我们需要建立一个机制去保证服务仍然可用。毫无疑问这里比较常见的容灾方式,是切换到附近城市节点假如系统的天津节点发生故障,那么我们就将网络流量切换到附近的北京节点上考虑到负载均衡,可能需要同时将流量切换到附近的几个地域节点叧一方面,核心节点自身也是需要自己做好容灾和备份的核心节点一旦故障,就会影响全国服务

过载保护,指的是一个节点已经达到朂大容量无法继续接接受更多请求了,系统必须有一个保护的机制一个服务已经满负载,还继续接受新的请求结果很可能就是宕机,影响整个节点的服务为了至少保障大部分用户的正常使用,过载保护是必要的

解决过载保护,一般2个方向:

  • 拒绝服务检测到满负載之后,就不再接受新的连接请求例如网游登入中的排队。
  • 分流到其他节点这种的话,系统实现更为复杂又涉及到负载均衡的问题。

Web系统会随着访问规模的增长渐渐地从1台服务器可以满足需求,一直成长为“庞然大物”的大集群而这个Web系统变大的过程,实际上就昰我们解决问题的过程在不同的阶段,解决不同的问题而新的问题又诞生在旧的解决方案之上。

系统的优化是没有极限的软件和系統架构也一直在快速发展,新的方案解决了老的问题同时也带来新的挑战。

参考资料

 

随机推荐