大型互联网系统设计综述
前言
1994年,中国科学院设立了中国第一个web服务器,推出了中国第一个网站。刚刚过去的2015年双十一,阿里巴巴当日线上交易额超过700亿,支付宝交易峰值为8.95万笔/秒。中国的互联网,正在以不可思议的速度释放着它的能量。
大众对互联网爆炸式的需求,不断推动着互联网技术向着更高的层次发展,从最初的静态新闻展示,到大型的电子商务平台,网站技术日新月异。也许在四五年前,我们谈到网站制作,想到的还是Html、IIS、论坛系统,但是现在推出一个网站,已经不是简简单单开发一套供人浏览的Html那么容易,而是要克服高并发、高可用、大容量、体验友好、可扩展等诸多设计和技术上的难题。
本文从技术角度简要分析设计大型互联网系统的一些考虑因素,没有高深之语,只是工作读书之余的总结。
一、互联网技术的学习历程
事物的发展总是循序渐进,不可能一蹴而就。
2003年的淘宝网最初是基于经典的开源黄金搭档LAMP(Linux+Apache+Mysql+PHP)建立的。当时的淘宝,好比学武之人刚刚练就一套太祖长拳,只是能够做到行走江湖不吃亏。后来随着业务的不断扩大,系统经过不断重新设计和优化,能够支撑的访问量也从百万级别逐渐上升的千万级别。此时的淘宝网,就已经脱胎换骨,不但吸取了众家之长融会贯通,而且能够自创武功,足以笑傲江湖了。
1、还是从最简单的场景说起。搭建一个网站其实很简单,写几个Html文件,运行一个web服务器软件,就可以通过浏览器访问之,这样网站就算建成了,能够为用户提供浏览html网页的服务。
2、静态的Html网页其实没什么看头,没有新鲜的资讯吸引用户的网站是不会有什么访问量的,于是动态网页技术应运而生。管理员可以随时发布和管理资讯,用户能够看到更多的动态的内容,这种网站可以称作是CMS系统,例如帝国CMS、Joomla、PHPCMS等。
大部分同学经过学校的课程或者自学,都能够掌握制作动态网站的技术,从而掌握从前端Html到后端数据库的相关技巧。达到这一阶段,就具备了行走江湖的资本,可以凭此手艺挣口饭讨生活了。但是此时若没能访得明师指点、增长见识,继而打通任督二脉,则会沉沦于庸常的增删改查之中,无法一窥网站技术之堂奥。
3、所谓的名师指点,就是在大项目中遇到困难,因为困难是最好的老师。
这里所说的困难多种多样:例如指访问量太大,网站撑不住了;业务需求爆炸,代码无法维护;前台交互繁琐,页面难看等等。每一个困难要彻底解决都不简单,一代一代的网站构架师,正式从不断地尝试解决这些困难的过程中,成长起来的。
此时,最先想到的办法——借鉴成功的开源框架的经验。比如经典的SSH框架(Spirng+Struts+Hibernate),采用了通用的MVC分层模式,能够将业务逻辑、前端展示、持久化数据库隔离,并分别提供比较专业的解决方案;比如BootStrap,解决了前端显示的风格统一;比如Angularjs,让程序员从噩梦般的前端开发中脱离出来。在这个阶段,我们参考了各式各样的框架设计,所谓观千剑而后识器,心中形成些许的丘壑,但尚不能融会贯通。
4、网站的业务更加复杂、访问量更大。要求我们的视线从单纯的增删改查上跳出来,从整体上全面地重构系统。我们要从前端想到后端,从高可用性考虑到安全性,从用户角度思考后再转过身来从开发者角度考虑。这样我们被迫站到了新的高度,这是一个前所未见的绝地,没有现成的方案可以搬过来使用,在经历无数次反复思考和尝试之后,才会完成一个软件工程师到构架师的艰难转变。
记得以前听说过一个理论,计算机领域的任何问题都可以通过增加一个虚拟层来解决。大型网站通用的分层策略是将网站分为应用层、服务层、数据层,下面首先分别讨论一下这三个层次所涉及到的技术要点。
二、应用层技术要点
应用层,即利用服务层提供的标准服务,对用户提供个性化的业务服务。
前端构架是这一层需要考虑的东西,前端框架需要考虑自适应用户的浏览器版本、屏幕尺寸,避免给用户带来糟糕的体验。
为了提高性能,前端设计需要考虑:
1、合理设置http meta中的缓存指令,利用浏览器缓存提高访问速度
2、为提高并发访问连接的数目,可将资源至于不同的域名之下
3、减少页面cookies传输,提高传输效率
4、在服务器压缩图片和文件,在浏览器端进行解压,减少传输的数据量
5、合并css、JavaScript、合并图片,减少请求的次数
6、CSS放在页面最上面,JavaScript放到页面最下面。浏览器会在下载完全部css之后才对页面进行渲染,为了让用户尽快看到页面,css文件要放在上面;相反,JavaScript加载之后会立即执行,这样会影响页面的显示速度,所以JavaScript要放到页面下面。
7、自适应的页面设计,满足不同屏幕尺寸的要求
Angularjs是采用JavaScript编写的客户端MVC框架,由Google团队于2012年正式推出,Angularjs致力于帮助开发者编写现代化的单页应用,尤其适用于编写大量CRUD操作的、具有Ajax风格的富客户端应用。
Angularjs直接面向html操作,不鼓励进行DOM级别的操作。最具有吸引力的特性包括双向数据绑定、模块依赖注入、自定义指令等等。
Bootstrap是Twitter推出的一个用于前端开发的开源工具包。它由Twitter的设计师Mark Otto和Jacob Thornton合作开发,是一个CSS/HTML框架。附加大量的JavaScript插件,适合对CSS设计不熟悉的开发团队快速打造自适应的、简洁的前端页面。
三、服务层技术要点
服务层,是在业务梳理的基础上,将功能独立的业务单独提取出来,对外提供统一的无状态的服务,比如用户管理、产品管理、库存管理、订单管理等等。
1、应用层是如何知道有哪些服务可以使用呢,这就需要一个高效率的服务注册、管理、发布机制,即分布式服务访问框架,例如阿里开源的Dubbo(http://dubbo.io/Home-zh.htm)以及谷歌的Grpc。
分布式服务访问框架一般可以分解为服务提供者(Provider)、服务消费者(Consumer)、注册中心(Registry)三个部分。
Provider:以接口的方式将服务注册到Registry,提供的信息包括服务的名称、版本、依赖、描述、所在机器地址等。
Registry:接收服务注册请求,对这些服务进行管理和维护,Dubbo采用开源Zookeeper作为自己的Registry。注册中心负责将服务注册信息的更新通知实时的Push给服务消费者(主要是通过Zookeeper的Watcher机制来实现的)。
Consumer:在启动的时候从服务注册中心获取需要的服务注册信息;将注册信息缓存到本地;监听服务注册信息的变更,如接收到服务注册中心的服务变更通知,则在本地缓存中更新服务的注册信息;
)来转发请求; 对服务提供方的存活进行检测,如果出现服务不可用的服务提供方,将从本地缓存中剔除;构造请求调用,从本地缓存列表中,采用负载均衡算法,选出合适的服务提供者进行服务调用。
图1 分布式服务框架结构
服务路由:如果多个Provider同时提供了相同的服务,就存在一个路由的问题,可以采用的机制有轮询、随机、根据时间的负载均衡机制等。能够实现软件层次的负载均衡,同时也满足了失效转移的要求(即一个服务失效,将请求转移到另外一个可用的服务上去)。
底层通信:基于RPC模式,封装Netty提供高效的异步IO通信的能力。
链路维护:Provider、Registry、Consumer之间采用心跳检测机制维护链路的可用性,发现链路不可用、或者服务不可用时,可以及时予以告警,实现高可用性。
通信格式:可选择使用xml、json等等。
ZooKeeper简介:http://www.cnblogs.com/mingziday/p/5182550.html
四、数据层技术要点
数据层有时候又称为持久层,即将数据持久化到数据库的过程。随着数据存储量的增大,数据查询会越来越慢,单一数据库服务不能满足大容量的要求,于是对数据层进行改造成为必然的抉择。
1、改造数据库结构,设置合理的索引;将大数据字段进行拆分。
2、数据库的读写分离,由一个读写库拆分为一个写库+若干读库的结构,读写库之间通过定时复制机制实现同步。
3、分库,分为垂直分库和水平分库。垂直分库是将不同的业务数据放到不同的数据库中,水平分库是指依据某种拆分原则,把一个表分为相同结构的若干个表,分到不同的数据库中。进行分库之后,要考虑数据库之间的联合查询该怎么处理,例如阿里构架师行颠研发的数据库路由框架DBRoute,统一处理了数据的合并排序分页,让程序员像使用一个数据库一样使用多个数据库。
4、支持异构数据库的统一处理,例如Mysql、Oracle,能够对外展示透明的访问接口,即对上层屏蔽了不同数据库的差异。
5、数据量持续加大,考虑采用分布式数据库中间件,例如阿里的Cobar。不但支持分库、屏蔽数据库异构,而且提供动态伸缩的能力,提供SQL的汇聚、分析、和优化能力。
目前比较成熟的分布式数据库开源系统主要有Amoeba(http://sourceforge.net/projects/amoeba/)
以及阿里开源的Cobar https://github.com/alibaba/cobar)
6、采用集群数据库缓存,使得大部分热点数据都从缓存获取,提高数据访问效率。例如memcached就是一款比较流行的数据库缓存系统,将在下面一节介绍。
五、缓存技术在互联网构架中的应用
缓存就是讲数据放在距离用户最近的位置以缩短处理流程、加快传输速度。
下面是一个常见的网站访问流程框架:
图2 缓存在web访问流程中的位置
1、浏览器缓存
http meta中的Cache-Control、Expire等设置,能够控制用户浏览器缓存。
2、CDN
CDN,Content Delivery Network,即内容分发网络,部署在距离终端用户最近的网络服务商的机房里面(即所谓网络访问第一跳),服务器端的大量静态资源放置于CDN中,是一种比较特殊而且实用的缓存技术。
3、RedWare硬件负载均衡器
基于IP+Port级别的负载均衡器,有效对网络流量进行分流
链路层负载均衡:不同于RedWare的Ip级别的负载均衡。顾名思义,链路层负载均衡是在TCP协议的链路层进行操作。当数据请求到达后,其IP地址并不被修改,修改的是mac地址,将数据包路由给响应的服务器,服务器处理完成后,可以直接返回数据给客户,不必经过负载均衡器。Linux平台上最好的链路负载均衡器为LVS(Linux Virtual Server,章文嵩博士发起的最早的开源项目之一)
4、Nginx
反向代理服务器,可以作为应用级别的负载均衡器使用,根据Url的不同将访问转发到不同的内部服务器
来自互联网的访问请求必须经过反向代理服务器,相当于在web服务器和网络攻击之间建立了一个屏障,起到安全防护的作用;反向代理服务器也可以配置缓存功能加速web请求;反向代理也可以实现负载均衡配置,进而改善网站高并发下的性能。
另外一款著名的反向代理服务器是Squid,官网http://www.squid-cache.org
5、Varnish
静态资源(img/css/html)缓存服务器,Nginx根据路由策略,会将静态资源路由到Varnish,命中后直接返回缓存的资源
6、分布式缓存服务器
Memcached,最常用的分布式内存对象缓存系统。其缓存服务器和应用服务器分开部署,应用服务器通过Hash路由算法选择缓存服务器获取数据,缓存服务器之间不相互通信,如图3所示。
和应用服务器集群不同(各个服务器运行相同的应用),分布式缓存集群服务器中,不同的服务器存储的是不同的数据。
图3 Memcached集群和应用服务器的交互
图4 memcached的访问模型
memcached:一套分布式的高速缓存系统,由LiveJournal的Brad Fitzpatrick采用C语言开发,但目前被许多网站使用以提升网站的访问速度,尤其对于一些大型的、需要频繁访问数据库的网站访问速度提升效果十分显著。memcached的通信模块基于Libevent,是一个支持事件触发的网络通信程序库。采用key-vaule键值对的方式存数数据,时间复杂度O(1),效率非常高;内存分配借鉴了Linux内存管理的Slab机制(寻找大于Size的最小内存块存储该数据);采用LRU算法淘汰最近最久未被访问的数据;只支持内存方式存储。
memcached访问过程如图4所示,应用程序输入需要写缓存的数据<BEIJING,DATA>,API将BEIJING输入路由算法模块,路由算法根据key和memcached集群服务器列表计算得到一台服务器编号NODE1,进而得到该机器的IP地址和端口10.0.0.0:91000。API调用通信模块和NODE1的服务器通信,将数据<BEIJING,DATA>写入分布式缓存中,读取数据的过程与之类似。
memcached采用固定内存分配,即将空间分配成固定的大小,存储数据的时候根据Size的大小,寻找一个大于Size的最小chunk将数据写入,避免频繁分配性能问题以及内存分片问题,但时也会引入内存浪费的问题。
当考虑加入一个新的缓存服务器的时候,因为路由算法没有改变,会导致大量的缓存不命中,因此无法支持动态伸缩,解决方法是采用一致性Hash算法。
我的问题是,写入缓存的数据如何与数据库的数据保持同步?
这里介绍一下其它几种分布式缓存系统:
Tair: 淘宝开源缓存系统,支持内存/文件级别缓存,采用C++实现。
Redis :C语言实现,支持Set/Map/List等多种数据结构
Ehcache: 健全的,经过验证的,全特性的,使用广泛的分布式缓存实现,采用Java实现,支持内存/文件级别的缓存
Oracle coherence:这是一个基于Oracle的收费的缓存系统
缓存预热:缓存中存放的是热点数据,这些数据是通过大量用户的访问通过LRU算法筛选出来的。如果一开始就能够预料到哪些数据是热点数据,首先通过一定的手段将他们加载到缓存中,将大大提高缓存的利用率,例如热点促销商品的信息,就可以首先刷到缓存里面。
六、分布式技术
在上面提到了分布式缓存和分布式服务。所谓的分布式,就是把不同的功能拆分到不同的服务器去完成。要注意的是,分布式并不是只有好处没有坏处。
1、分布式意味着服务调用必须通过网络,这可能带来比较严重的性能损耗。
2、分布式事务给业务的一致性要求带来很大挑战。
七、集群技术
说的简单点,网站通过集群的方式将多台提供相同服务的服务器当做一个整体对外提供服务,通过路由技术决定到底该访问那一台服务器。所谓集群的伸缩性,就是可以在访问压力大的时候,向集群中添加服务器以缓解压力;当访问压力变小的时候,可以把部分服务器分流出去,以节省成本。
集群提高了系统的整体可用性,当发现集群中的某个机器不可用的时候,可以将其从服务器剔除并发出告警。
灰度发布:是集群应用中的一个技巧,即网站新功能发布的时候不中断对外服务。先关闭服务器集群中部分机器的路由,更新软件包,重启服务,启动路由,当这部分新功能验证没有bug后,再用类似的办法更新其他服务器的服务。
八、异步
异步的设计思路是:用户请求发出后,服务端将请求加入消息队列中,直接返回,由单独的线程去处理队列中的请求。因为没有直接和数据库进行交互,所以能够很好改善响应速度,在高并发的时刻有很好的削峰作用。
异步消息队列的思想来源于事件驱动机制,即不依靠简单直接的函数调用来实现功能,而是借助事件消息的通信完成模块之间的通信,极大地降低了模块之间的耦合性。
分布式消息队列:队列是一种先进先出的数据结构,分布式消息队列可以看做将这种数据结构部署到独立的服务器上。应用程序可以使用远程访问接口分布式消息队列,进行消息存取操作,进而实现分布式的消息存取操作。
分布式消息队列利用发布-订阅模式工作。消息生产者通过远程访问接口将消息推送给消息度激烈服务器,消息队列服务器将消息写入本地内存后立即返回成功响应给消息生产者。消息队列服务器,查找消息订阅列表,找到需要给消息的消息消费者,并将消息队列中的消息以FIFO的方式发送给消息消费者。
异步的问题在于,后台线程在处理队列中的请求的时候可能会出现校验、操作数据库失败。例如在订单提交流程中,就不能直接给用户显示订单提交成功,取而代之,可以显示“您提交的订单已被接受,我们会尽快反馈订单处理进度,请关注短信或者邮箱”。
目前异步消息处理框架有阿里的消息中间件RocketMQ(http://alibaba.github.io/RocketMQ-docs/document/design/RocketMQ_design.pdf)等等。
RocketMQ介绍:http://www.cnblogs.com/mingziday/p/5182551.html
九、其他提高性能的方法
1、代码优化
多线程编程,web服务器也是采用多线程的方式处理用户的并发请求的:避免全局变量,合理使用锁,无状态对象设计;
启用对象池、连接池(阿里Druid数据连接池),减少不必要的资源损耗。
2、存储性能优化
采用RAID技术,提高数据读写的并发率、安全性
3、操作系统和文件系统
商用Linux操作系统并不是专门针对web服务器进行设计,这就需要我们对Linux的一些设定进行调优,以提高web服务器的数据访问性能。例如修改linux参数提高Tcpsocket使用的系统内存、修改最大并发链接数等等。
4、页面静态化
让访问不经过Java系统,只在到达web服务器之后就直接返回了,这样我们缓存的是html页面,不是java数据。这就要求把html页面中cookies、时间、动态内容单独抽取出来。这些抽取出来的内容如何再组装起来呢,有两种方式,ESI和CSI。
ESI:在Web服务器上做动态内容请求,并将请求插入到html页面中,当用户拿到页面的时候已经是一个完整的页面了。
CSI:在用户拿到页面之后,再单独发送一个js异步请求获取动态数据,服务器性能会更好,但是用户体验上有些延迟。
通过ESI组织成的html页面也可以缓存起来,这样用户直接冲缓存中直接拿到html,相当于到一个静态网站简单取一份数据而已,不涉及具体的逻辑计算,访问效率是很高的。但是天下没有完美的解决方案,这种情况要考虑cache失效问题。
十、其他技术
1、Session管理
http协议是一种无状态协议,而业务总是有状态的,比如购物车、用户登录信心等等,在一个集群的应用服务器中,如何维持session是个必须要考虑的问题。常用的方法有session复制、session绑定(会话粘滞)、cookies记录session等。
最可靠的方式是使用独立的session服务器,即利用独立部署的session服务器管理一切session请求。
2、搜索引擎
Apache Lucene,是一款开源的全文检索系统(http://www.cnblogs.com/xing901022/p/3933675.html)
搜索引擎的原理,将热点数据通过同步方法从数据库中读取到硬盘/缓存里面。然后对这些数据建立高效的索引,通过全文检索算法,就能够快速定位出来。
3、服务幂等性
服务重复调用和第一次调用产生的结果相同。
例如一次调用服务超时失败了,但实际上该服务已经执行完成了。这个时候业务层面会尝试重新操作,就需要和第一次操作的时候产生一样的结果。
参考文献
《大型网站技术架构:核心原理与案例分析》
《淘宝技术十年》
《构建高性能WEB站点》