多终端接入、开放平台给互联网带来了史无前例的用户量级和访问规模,SNS网站产生了海量的UGC(用户产生内容),并且这些内容依托关 系链扩散速度之快、传播范围之广是传统网站不可思议的,海量数据的计算存储也一直是近年互联网领域的热点。本文将从发展演进的层面探讨互联网的系统架构。前端
天下武功惟快不破程序员
网站初期的架构通常采用“短平快”的架构思路,架构以简单清晰、容易开发为第一衡量指标。算法
互联网架构选型首先包括开发语言的选择,目前PHP、Java是主力语言。开发语言的选择通常从团队人员的知识储备、社区活跃度、商业应用的成熟度、招聘人才的人力成本等方面考量。数据库
选择语言以后,通常会选择该语言的流行框架辅助研发,例如Java的SSH、Python的Django等。但这些框架并非一般意义上的架构,架构通常可 分为物理架构、运行架构、逻辑架构、开发架构、数据架构等多个维度,框架每每只是代码架构的一部分。代码架构是指代码的组织形式、规范、设计模式等,框架 实际上是经常使用设计模式的软件化。例如Struts是MVC模式的实现,Hibernate、iBATIS是ORM模式的实现。后端
在架构视图中,早期关注的主要是开发视图和数据视图,通常数据存储采用DB,初期数据的关注点主要是安全和备份,MySQL的Master-Slave模式能够知足该需求。设计模式
鸡蛋不要放在一个篮子里缓存
采用“短平快”三板斧将网站开发出来以后,急需解决的是网站的可用性问题。可用性最基本的要求是不能有单点,对程序节点而言,前端可采用LVS、HAProxy、Nginx等负载均衡/反向代理设备。安全
DB的可用性就复杂了不少,数据库自然是有状态的,状态就是其中的数据,新增一个数据节点通常伴随着大量的数据复制和迁移。对金融行业而言,昂贵的商用存储是 解决之道,“IBM+Oracle+EMC”是该类系统的标配。互联网企业则通常采用较为廉价的方案,例如开源的DRDB+Heartbeat技术组合可 以在MySQL主库宕机时实现备机接管,接管时间能够控制在30秒内。性能优化
程序节点其实也可能存在状态,例如Web服务中经常使用的Session 就是保存在容器中的状态,这种状态保持要求全部相同用户的请求都在同一台机器上处理,无状态的程序节点才能水平扩展。无状态通常有两种设计思路,还以 Session为例,一种思路是把用户的状态保存在客户端Cookie,每次请求都把客户端的用户信息带到服务器端,淘宝的分布式Session就是该思 路的一种实现;另外一种思路是状态保留在另一个服务中,例若有些公司将Session放在分布式缓存中。服务器
性能是生命线
去除单点以后的系统就能够水平扩展,架构如图1所示。但随着网站的推广运营,系统的规模开始扩大,此时可能会出现服务访问缓慢,甚至不可用的情况,如何提高系统性能就成了架构师的当务之急。
图1 去除单点以后进行水平扩展
存储架构和性能
互联网系统全部的性能瓶颈中,数据存储和访问速度每每是最重要也是最难解决的,选择合适的存储是系统的关键。存储的选择通常须要从多个方面考量,如成本、内容、用途和模型。目前主流的存储介质包括硬盘和内存两种。
对机械硬盘来讲,1秒能够完成150次左右的随机I/O。而结合设计优良的Hash算法,内存查找能够每秒执行40万次左右。硬盘的随机读写能力决定了其读 写的最差性能,但操做系统在实现文件系统时会把最近读写过的数据缓存在内存中。因为磁盘访问和内存访问性能量级的差距,从操做系统的Cache命中率就可 以简单计算文件存储的性能,若是内存命中率能够达到80%,系统的I/O能力相较彻底随机I/O将有5倍提高。
对于数据层服务器,大内存已成为标配(通常为100GB左右),若是DB中存储200GB的数据,根据8/2原则,Cache命中率应为87.5%,所以对MySQL而言,通常读写能够达到每秒1千次以上。
对于读写频率都很高、且可容忍数据丢失的场景,能够采用内存做为数据存储的介质。可靠的内存存储须要每次操做都记录Biglog,即便数据丢失也能够恢复,同时内存中的数据通常按期持久化到硬盘。
从功能角度考量,还能够分为持久化存储和Cache。持久化存储也可称为可靠存储,Cache是为了提高系统性能,在可靠存储的基础上创建的访问性能更加高效的数据读取节点,一般是内存存储,其架构通常如图2所示。
图2 持久化存储和Cache
存储的数据模型通常分为结构化存储和NoSQL存储。结构化存储以各类传统DB为表明,NoSQL技术的表明系统则有HBase、Memcached、 Redis等。各类NoSQL系统虽然特性各异,但相对传统DB而言,因为结构化信息的缺失,每每不能作各类关联查询,适用场景更可能是主键查询,并且通常 是写少读多的系统。
对于大型互联网公司,为了某些场景下的性能优化,也会定制个性化的文件系统,例如为了适应大文件存储的场景,Google开发了GFS;为了更快读取海量商品的描述图片,TFS在阿里诞生。
虽然各种存储快速涌现,但DB做为结构化数据的传统存储设备,依然在架构中处于很是重要的地位。因为随机I/O的瓶颈,DB的性能天花板十分明显。在大型系 统中一般须要分库操做,分库通常有两个维度——水平切分和垂直切分。水平切分通常根据主键规则或某种规则将同类数据切分到不一样的单元表中,原则是数据切分均匀,尤为是热点数据分布均匀。
垂直切分是把大表中的字段拆分到多张表。垂直切分通常按照数据访问频率的不一样。逻辑关系的差异进行切分,例如将大字段、kv字段、计数等高频访问字段单独剥离存储都是常见的垂直切分方案。
除了切库以外, MySQL的分表也会有效减小单表大小,使数据变得更简单,甚至能够作到不下线变动,单表索引规模的降低也会带来性能的提高。
分库分表做为DB架构中重要的一环,使DB更加稳健,但它给业务代码带来了额外的复杂性,最好经过中间件来屏蔽DB的底层分布,对业务透明。
做为高性能网站必不可少的组件,Cache在各类主流架构中也起着重要的做用。
从部署模式上看,它可分为本地Cache和分布式Cache。本地Cache是指在应用进程中的Cache,一般的数据结构是一个MAP,其优势是结构简 单,效率较分布式Cache更高,缺点是通常应用程序服务器的内存有限,致使本地Cache容量受到局限,并且数据冗余度较高,每一个应用服务器都须要一份 数据,更新比较烦琐,通常采用超时删除机制。
分布式Cache的容量较大,方便扩容和更新,其数据分布可采用一致性Hash算法,减小节点变化带来的数据迁移。
引入Cache不可避免的问题是服务器的宕机处理。Cache一般是一个集群,数据分布在多个节点,若是挂掉一个节点,只会影响部分数据,并且对于可靠性要求较高的系统,每一个节点均可以有备份。
做为可靠存储的数据备份,Cache在架构设计上每每承担大部分读访问需求,其命中率尤其重要。Cache不命中有两种状况,一是数据在Cache中不存 在,二是在持久化存储中也不存在。对于后者的频繁访问会致使请求直接压在DB上,在设计时应尽可能避免,能够经过维护Bitmap对持久化存储中没有的数据 进行拦截,直接返回,也能够简单地将这些数据对应空对象放进Cache。
Cache的存储通常是将索引和数据分离,对于索引数据能够全量缓存,对于体量较大的数据通常采用部分缓存的方式。
Cache的使用场景有必定的局限,对于较为静态的数据才有意义,这个临界值通常是5分钟。因为当前存储技术的进步,Cache也能够用其余高性能的存储介质代替,例如SSD的引入使得硬盘的随机读写能力提高数十倍,也会使得Cache的重要性有所降低。
程序架构和性能
对通常的系统而言,程序逻辑的主要做用是调用各类数据访问接口,该操做一般须要等待,因此除搜索等少数系统外,程序逻辑通常是非CPU密集型。该类系统中“线程”是稀缺资源,线程数和接口耗时构成了系统的QPS能力。
大型互联网系统的QPS可能为几万甚至峰值达到几十万,此时增长机器能够解决问题,但这些机器的利用率其实很低,由于大部分时间是在等待,此时引入异步变得很是重要,异步在一样的时间能够处理更多工做,拥有更好的性能。
图3 同步调用和异步调用
利用Nio的多路复用方式可方便地实现异步系统,固然也可用协程令代码更加清晰。业界流行的SEDA技术可将一次请求拆分为粒度更细的Actor,每一个 Actor使用独立队列,前一个的输出是后一个的输入。SEDA经过该方式将请求中等待和非等待的环节分离,提高了系统的吞吐量,这种方式在小米等互联网 公司有较多应用。
除了异步以外,并行对系统也很重要,它能够有效缩短请求的响应时间。批量接口也能够有效减小系统调用次数,使得系统线程消耗更少,从而提高系统吞吐量。
对线程而言,还有一个重要的参数是超时时间。响应快的服务,超时时间能够长一些,对于响应慢的服务,超时时间能够短一些,尽快失败是保护本身的有效手段。
网络架构和性能
大型网站的网络接入通常是“DNS+负载均衡层+CDN”这种模式。对于大型互联网公司,每每有多个IDC提供对外服务,中国互联网的南北不互通使得解决不 同地域不一样运营商的接入速度问题成了难题,该问题的解决通常须要公司本身开发DNS服务器,结合IP测速平台,引流用户请求到访问速度最快的节点。
大系统小作
业务逻辑复杂多变,如何保证程序逻辑的代码稳定是架构师须要解决的问题,良好的模块划分和扩展性强的接口设计都是解决这个问题的利器。
模块是和领域模型相关的一个概念,其每每指系统中高内聚的一个数据访问单元。例如对电商系统而言,最大的两个领域模型分别是商品信息和交易信息,每一个领域模 型对应一系列数据,商品会有商品的基本信息、类目信息等,交易会包括交易的订单,这些“领域模型+数据+业务方法”就构成了一个个的模块,高度内聚的模块 是数据的访问的入口。例如交易时也须要去获取商品信息,但通常不会被容许直接调用商品模块的数据表,而是经过商品模块提供的接口进行访问,这样作有下面一 些优势。
对较小规模的应用,模块可部署在一块儿,但对大型系统而言,模块通常是单独部署,经过RPC交互,这样能够减小彼此之间的系统层面影响。例如Amazon就倾向将全部服务器程序暴露为接口。
分布式系统增长了系统交互的复杂性,也为系统引入了更多潜在的失败环节,但分布式系统能够带来的好处更明显。
逻辑层的接口设计如何作到稳定呢?首先接口的设计不该该是产品驱动的,而应该由数据驱动,尽可能考虑接口之后的发展,接口的参数尽可能是对象,参数不要采用boolean这种难以扩展的数据类型。
逻辑层的接口设计,通常有粗粒度和细粒度两种模式。粗粒度接口的优势是交互少,一次调用基本能够知足需求,缺点是业务逻辑较多,因此不稳定性增长,这里的不稳定性不只是系统稳定性,还包括业务逻辑的稳定性。细粒度接口交互较多,但更加有利于重用性。
细粒度接口屡次交互是否会带来明显的性能损耗呢?目前服务器1秒能够执行近万次TCP链接,网络传输对于千兆网卡而言,通常也不会形成瓶颈,尤为RPC框架般都会保持长链接。所以,一般状况下咱们能够忽略RPC调用带来的性能损耗。
逻辑层接口尽可能采用大系统小作的原则,该原则是腾讯架构中重要的价值观。一个接口不作太多事情,只作关键路径,非关键逻辑能够用消息队列或事件通知等方式剥离出来,使得关键路径更加稳定。这也体现了服务分级的思想,把最大的精力花在最重要的接口上。
除了按重要性划分服务以外,也能够按接入方划分,避免不一样终端的Bug影响。还能够按快慢划分,例如为上传文件等功能提供单独的服务,避免长时间占用线程,影响系统稳定性。
模块化是程序的水平切分,有时也会采用垂直切分,这种架构有如下好处。
开放势不可挡
系统发展每每会带来平台化需求,例如微博的大多数服务除了知足PC访问,还要知足移动端及内外部平台,此时系统一般会抽象出一个接入层。
接入层设计
接入层通常分为:通讯、协议和路由模块。
经常使用的通讯方式有TCP、UDP和HTTP等,对开放平台等之外围接入为主的系统而言,HTTP因其简单方即是最合适的通讯方式,而内部系统接入出于性能考量,能够直接用TCP或者UDP。
目前的主流协议有JSON、XML、Hessian等,对外部调用通常采用XML或者JSON,内部系统能够采用Hessian或其余自定义的二进制协议。
路由层是根据用户的信息将请求转发到后端服务层。LVS可看做是路由层,根据IP协议将不一样来源的请求转发到不一样IP Server,Nginx等具有反向代理的服务器也能够看做路由,根据不一样URL转发到不一样后端。咱们常常会自定义路由层,经过用户调用的不一样方法转发到 不一样Server,或者根据用户的特征,将用户路由到咱们的灰度测试机。
云平台概念
具备了平台化的接入功能,系统能够方便地接入内部或者外部系统,此时就具备了“云”的特征,对各类公有云或私有云来讲,系统的隔离和自动扩容都十分重要,虚拟化等技术在该类系统中有了充分应用,例如阿里云等云平台都大量使用了虚拟化。
稳定压倒一切
代码稳定性
稳定性是系统架构中一以贯之的内容,能够从图4中理解它的含义。
正常状况下,图4左边系统的性能明显优于右边,但从架构角度考虑,右图要好于左图,由于突起的毛刺使得系统的容量骤降,很容易引起雪崩。性能考量不只是系统的最优性能或者平均性能,最差性能每每也是系统出现问题的缘由。
图4 两种稳定性对比
容灾
除了特别小型的系统,没有100%可用的系统。通常须要根据系统的状况制定合适的目标,该目标最通用的衡量维度是系统可用率。
系统可用率是能够提供服务的时间与总时间的比率,经常使用的系统可用率如表1所示。
而对于灾难,咱们有下面几个环节能够介入。
系统容量的冗余和可水平扩展也是容灾的必备要求,无状态的系统对于系统扩容更友好