文章作者:李涛(个人微信号:litao851025,备注技术交流)
本文授权转载自:http://www.geekhalo.com
对于一个大型网站,主要有以下几个特征:
-
支撑海量数据
-
非常高的访问量
我们常见的大型网站,如百度、淘宝、京东等,都是一个分布式系统。这么复杂的系统也不是一天建成的,每个系统都经历了漫长的演变过程。
- 架构演变 -
在大型网站中,其最核心的功能就是
计算
和
存储
。因此系统演变过程也主要围绕这两点进行。
1 单机系统
在网站刚刚起步时,数据量、访问量都非常小,通常情况下,只需一台应用服务器就可以了。
1.1 单机部署方案
起步时,我们把所有资源全部打包到部署文件中(如 XXX.war),其中包括
-
class 文件、依赖 jar等;
-
js、css、图片等静态资源;
-
对于用户上传文件的场景,直接在服务器上新建一个目录,将上传的文件放置在目录即可。
然后,将打好的发布包放到 Web 容器中,比如 Tomcat,最后启动容器,让其直接对外提供服务。
该部署策略有以下几个特征:
-
用户通过浏览器直接与 Java 应用程序进行交互(通常是 Tomcat);
-
Java 应用程序通过 JDBC 与本机的数据库进行交互(如 MySQL);
-
如果存在文件读写的需求,Java 应用程序通过文件接口直接对文件进行操作。
这时,有人会问,Java 应用程序直接对外,会不会存在一些安全或性能方面的问题呢?
是的,Tomcat 这种 Web 容器对链接的保持能力比较弱,当存在大量链接时,性能下降很快。同时,Tomcat 并不擅长静态资源的处理,对此,我们可以引入 Nginx,以缓解 Tomcat 的压力。
1.2 单机部署方案进阶
我们在单机部署基础上,添加 Nginx,也就有了进阶方案。
该方案存在以下特征:
-
用户不在直接与 Java 应用程序进行交互,而是与 Nginx 进行交互;
-
Tomcat 挂在 Nginx 后,对动态请求进行处理;
-
对于静态资源的访问,通过 Nginx 直接访问文件系统;
-
当有文件写需求时,通过 Java 应用程序直接写入磁盘。
此时,架构显得清晰很多,但我们发现一个问题,就是系统对静态资源和动态资源的处理是完全不同的。
对于静态资源的处理,相对简单,只是简单的文件读写。而,动态请求(也就是我们的业务承载者)会随着业务的发展越来越复杂。
2 动静分离部署方案
由于
静态请求
与
动态请求
采用不同的处理策略,我们可以将其进行分离。
该部署方案存在以下特性:
-
通过不同的域名对
动态请求
和
静态请求
进行分离;
-
新增
静态资源服务器
,专门处理静态请求,并在服务器上部署
Java 应用程序
,处理文件写需求;
Nginx
只负责文件的读操作;
-
对
动态请求
进行独立部署,应用程序将文件的写请求转发到静态服务器进行处理;
静态资源服务器功能单一,部署繁琐,有没有一种更好的策略呢?
答案就是云服务,比如阿里云的 OSS 提供静态资源存储服务。CDN 提供访问加速服务,两者结合使用,就得到了一个海量容量并且性能超强的静态资源服务器(集群)。
结合 OSS 和 CDN,静态请求不会成为系统的瓶颈,因此,接下来只对动态请求进行讨论。
随着系统访问量的增加,动态请求出现了明显的瓶颈。
3 应用集群化部署
由于所有的动态请求全部由一台应用服务器进行处理,当访问量上升时,这台服务就成了系统的瓶颈。此时,我们需要将系统中的多个组件部署到不同的服务器上。
新部署有以下特征:
-
对 Nginx 进行独立部署,形成
Web 集群
;
-
对 Java 应用程序进行独立部署,形成
应用集群
;
-
对
数据库
进行独立部署;
-
Web 集群
与
应用集群
间通过
HTTP
协议进行交互;
-
应用集群
与
数据库
间通过
JDBC
协议进行交互。
应用集群化,会面临很多挑战,主要的焦点是如何有效的分配用户请求。
3.1 DNS 轮询
首先要解决的问题便是,用户如何将请求发送到不同的 Nginx 中,最常见的方式便是 DNS 轮询。
大多域名注册商都支持多条 A 记录的解析,其实这就是 DNS 轮询,DNS 服务器将解析请求按照 A 记录的顺序,逐一分配到不同的 IP 上,这样就完成了简单的负载均衡。
3.2 负载均衡器
这里的负载均衡器主要指的是 Nginx 的反向代理功能。当用户请求发送到 Nginx 后,Nginx 需要决定将请求转发到哪台应用服务器上。
反向代理(Reverse Proxy)是指以代理服务器来接受 internet 上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给 internet 上请求连接的客户端,此时代理服务器对外就表现为一个反向代理服务器。
Nginx 对于后台服务器配置比较灵活,可以同时配置多台服务器,并根据负载策略将请求分发给后台服务器。
3.3 会话问题
在单机时代,我们的请求只会发送到同一台机器上,不存在会话问题。当将应用集群部署时,用户的多次请求会发送到不同的应用服务器上。此时,如何对会话进行同步便是棘手问题。
3.3.1 Session Sticky
这种方案主要由 Nginx 处理,让同样 session 请求每次都发送到同一台服务器进行处理。
Nginx 会将相同用户的请求发送到同一台应用服务器中。
这是最简单的策略,但存在一定的问题:
-
Web 服务器重启 Session 丢失;
-
负载均衡需要进行应用层解析(第7层),性能损耗较大;
-
负载均衡器变为一个有状态的点,不易容灾;
3.3.2 Session Replication
会话问题的根源在于 Session 由多个应用维护,我们可以使用某种机制,在多台 Web 服务间进行 Session 的数据同步。
由 Session 同步器在各个 Java 应用程序间完成 Session 的同步,最终使每个服务器中都存在所有用户的 Session 数据。
这个方案的问题:
-
造成网络开销;
-
每台 Web 服务器都保存所有的 Session,内存开销大;
3.3.3 集中式Session
我们可以将 Session 从 Web 服务中抽取出来,并对其进行集中存储。
将 Session 信息保存到 Session 存储集群中,Java 应用程序不在负责 Session 的存储。
这个方案的问题:
-
读取Session引入了网络开销;
-
存储设施问题影响应用;
3.3.4 Cookie Based Session
还可以将 session 数据放在 cookie 中,然后在 Web 服务器上从 cookie 中生成对应的 Session 数据。
将 Session 数据编码到 Cookie 中,每次 Java 应用程序使用 Session 时,都从 Cookie 中重建 Session。
该方案的问题:
-
受到 Cookie 大小的限制;
-
存在安全性问题;
-
每次都携带巨大的 Cookie,带宽消耗严重;
-
每次都进行 Session 数据恢复,加大应用服务器的负担;
随着系统访问量的持续增加,面对大量的数据读取请求,数据库有些不堪重负。
此时,我们需要对数据库进行优化。
4 数据库读写分离
通常情况下,数据库的读会成为系统的瓶颈。对此,我们可以使用数据库主从机制,通过添加多个从库来减缓读压力。
与之前部署相比,该架构只是为数据库增加了若干个从库:
-
对数据库实施
主从
部署策略;
-
对于数据的写请求,只能在
主库
上进行;
-
对于数据的读请求,可以在任意的
从库
上进行;
-
主库与从库间,通过
数据库同步策略
进行数据同步。
由于主库与从库间的数据同步需要时间,会出现数据不一致的情况,这块是业务上需要慎重考虑的一点。
随着业务越来越复杂,对功能和性能的要求也越来越高,最常见的便是数据库 like 语句性能已经无法满足需求;对于某些热点数据的访问,其性能也下降很快。
此时,我们需要引入其他组件来有针对性的解决问题。
5 引入搜索和缓存
针对数据库的 like 语句,通常情况下,是通过引入搜索引擎来解决;而热点数据的访问加速,是通过引入缓存服务来解决。
该架构的特征如下:
-
添加
搜索集群
,用以提升数据检索性能;
-
添加
缓存集群
,用以提升热点数据访问性能。
在对数据查询进行优化后,慢慢的系统的写性能成为了瓶颈。
此时,需要对数据的写性能进行扩展。
6 数据库分库分表
随着数据量的增长,写请求量的增加,数据库的写入逐渐成为了瓶颈。常规的写性能优化便是对数据库进行分库分表。
6.1 垂直拆分
将不同的业务数据放到不同的数据库实例中。
6.2 水平切分
把同一个表中的数据拆分到多的数据库中。
随着研发团队的规模越来越多,大家同时在一个项目中进行开发,导致频繁的冲突和相互影响。
此时,会将整个应用程序根据功能模块进行拆分,从而形成多个子网站或子频道。