老板让我开发一个亿级流量的大型网站

对于一个大型网站,主要有以下几个特征:

  • 支撑海量数据

  • 非常高的访问量

在大型网站中,其最核心的功能就是计算和存储。因此系统演变过程也主要围绕这两点进行。

单机系统

在网站刚刚起步时,数据量、访问量都非常小,通常情况下,只需一台应用服务器就可以了。

单机部署方案

起步时,我们把所有资源全部打包到部署文件中(如 XXX.war),其中包括:

  • class 文件、依赖 jar 等。

  • js、css、图片等静态资源。

  • 对于用户上传文件的场景,直接在服务器上新建一个目录,将上传的文件放置在目录即可。

然后,将打好的发布包放到 Web 容器中,比如 Tomcat,最后启动容器,让其直接对外提供服务。

该部署策略有以下几个特征:

  • 用户通过浏览器直接与 Java 应用程序进行交互(通常是 Tomcat)。

  • Java 应用程序通过 JDBC 与本机的数据库进行交互(如 MySQL)。

  • 如果存在文件读写的需求,Java 应用程序通过文件接口直接对文件进行操作。

这时,有人会问,Java 应用程序直接对外,会不会存在一些安全或性能方面的问题呢?是的,Tomcat 这种 Web 容器对链接的保持能力比较弱,当存在大量链接时,性能下降很快。同时,Tomcat 并不擅长静态资源的处理,对此,我们可以引入 Nginx,以缓解 Tomcat 的压力。

单机部署方案进阶

我们在单机部署基础上,添加 Nginx,也就有了进阶方案:

该方案存在以下特征:

  • 用户不在直接与 Java 应用程序进行交互,而是与 Nginx 进行交互。

  • Tomcat 挂在 Nginx 后,对动态请求进行处理。

  • 对于静态资源的访问,通过 Nginx 直接访问文件系统。

  • 当有文件写需求时,通过 Java 应用程序直接写入磁盘。

此时,架构显得清晰很多,但我们发现一个问题,就是系统对静态资源和动态资源的处理是完全不同的。对于静态资源的处理,相对简单,只是简单的文件读写。而,动态请求(也就是我们的业务承载者)会随着业务的发展越来越复杂。

动静分离部署方案

由于静态请求与动态请求采用不同的处理策略,我们可以将其进行分离。

该部署方案存在以下特性:

  • 通过不同的域名对动态请求和静态请求进行分离。

  • 新增静态资源服务器,专门处理静态请求,并在服务器上部署 Java 应用程序,处理文件写需求;Nginx 只负责文件的读操作。

  • 对动态请求进行独立部署,应用程序将文件的写请求转发到静态服务器进行处理。

静态资源服务器功能单一,部署繁琐,有没有一种更好的策略呢?

答案就是云服务,比如阿里云的 OSS 提供静态资源存储服务。CDN 提供访问加速服务,两者结合使用,就得到了一个海量容量并且性能超强的静态资源服务器(集群)。

结合 OSS 和 CDN,静态请求不会成为系统的瓶颈,因此,接下来只对动态请求进行讨论。

随着系统访问量的增加,动态请求出现了明显的瓶颈。

应用集群化部署

由于所有的动态请求全部由一台应用服务器进行处理,当访问量上升时,这台服务就成了系统的瓶颈。

此时,我们需要将系统中的多个组件部署到不同的服务器上。

新部署有以下特征:

  • 对 Nginx 进行独立部署,形成 Web 集群。

  • 对 Java 应用程序进行独立部署,形成应用集群。

  • 对数据库进行独立部署。

  • Web 集群与应用集群间通过 HTTP 协议进行交互。

  • 应用集群与数据库间通过 JDBC 协议进行交互。

应用集群化,会面临很多挑战,主要的焦点是如何有效的分配用户请求。

DNS 轮询

首先要解决的问题便是,用户如何将请求发送到不同的 Nginx 中,最常见的方式便是 DNS 轮询。

大多域名注册商都支持多条 A 记录的解析,其实这就是 DNS 轮询,DNS 服务器将解析请求按照 A 记录的顺序,逐一分配到不同的 IP 上,这样就完成了简单的负载均衡。

负载均衡器

这里的负载均衡器主要指的是 Nginx 的反向代理功能。当用户请求发送到 Nginx 后,Nginx 需要决定将请求转发到哪台应用服务器上。反向代理(Reverse Proxy)是指以代理服务器来接受 Internet 上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给 Internet 上请求连接的客户端,此时代理服务器对外就表现为一个反向代理服务器。Nginx 对于后台服务器配置比较灵活,可以同时配置多台服务器,并根据负载策略将请求分发给后台服务器。

会话问题

在单机时代,我们的请求只会发送到同一台机器上,不存在会话问题。当将应用集群部署时,用户的多次请求会发送到不同的应用服务器上。此时,如何对会话进行同步便是棘手问题。

①Session Sticky

这种方案主要由 Nginx 处理,让同样 Session 请求每次都发送到同一台服务器进行处理。

Nginx 会将相同用户的请求发送到同一台应用服务器中。这是最简单的策略,但存在一定的问题:

  • Web 服务器重启 Session 丢失。

  • 负载均衡需要进行应用层解析(第 7 层),性能损耗较大。

  • 负载均衡器变为一个有状态的点,不易容灾。

②Session Replication

会话问题的根源在于 Session 由多个应用维护,我们可以使用某种机制,在多台 Web 服务间进行 Session 的数据同步。

由 Session 同步器在各个 Java 应用程序间完成 Session 的同步,最终使每个服务器中都存在所有用户的 Session 数据。这个方案的问题:

  • 造成网络开销。

  • 每台 Web 服务器都保存所有的 Session,内存开销大。

③集中式 Session

我们可以将 Session 从 Web 服务中抽取出来,并对其进行集中存储。

将 Session 信息保存到 Session 存储集群中,Java 应用程序不在负责 Session 的存储。这个方案的问题:

  • 读取 Session 引入了网络开销。

  • 存储设施问题影响应用。

④Cookie Based Session

还可以将 Session 数据放在 Cookie 中,然后在 Web 服务器上从 Cookie 中生成对应的 Session 数据。

将 Session 数据编码到 Cookie 中,每次 Java 应用程序使用 Session 时,都从 Cookie 中重建 Session。该方案的问题:

  • 受到 Cookie 大小的限制。

  • 存在安全性问题。

  • 每次都携带巨大的 Cookie,带宽消耗严重。

  • 每次都进行 Session 数据恢复,加大应用服务器的负担。

随着系统访问量的持续增加,面对大量的数据读取请求,数据库有些不堪重负。此时,我们需要对数据库进行优化。

数据库读写分离

通常情况下,数据库的读会成为系统的瓶颈。对此,我们可以使用数据库主从机制,通过添加多个从库来减缓读压力。

与之前部署相比,该架构只是为数据库增加了若干个从库:

  • 对数据库实施主从部署策略。

  • 对于数据的写请求,只能在主库上进行。

  • 对于数据的读请求,可以在任意的从库上进行。

  • 主库与从库间,通过数据库同步策略进行数据同步。

由于主库与从库间的数据同步需要时间,会出现数据不一致的情况,这块是业务上需要慎重考虑的一点。随着业务越来越复杂,对功能和性能的要求也越来越高,最常见的便是数据库 like 语句性能已经无法满足需求;对于某些热点数据的访问,其性能也下降很快。此时,我们需要引入其他组件来有针对性的解决问题。

引入搜索和缓存

针对数据库的 like 语句,通常情况下,是通过引入搜索引擎来解决;而热点数据的访问加速,是通过引入缓存服务来解决。

该架构的特征如下:

  • 添加搜索集群,用以提升数据检索性能。

  • 添加缓存集群,用以提升热点数据访问性能。

在对数据查询进行优化后,慢慢的系统的写性能成为了瓶颈。此时,需要对数据的写性能进行扩展。

数据库分库分表

随着数据量的增长,写请求量的增加,数据库的写入逐渐成为了瓶颈。常规的写性能优化便是对数据库进行分库分表。

垂直拆分

将不同的业务数据放到不同的数据库实例中。

水平切分

把同一个表中的数据拆分到多个数据库中。随着研发团队的规模越来越多,大家同时在一个项目中进行开发,导致频繁的冲突和相互影响。此时,会将整个应用程序根据功能模块进行拆分,从而形成多个子网站或子频道。

应用垂直拆分

面对一个巨无霸式的应用,就像面对一团毛线团,总有一种无法下手的感觉。对此,可以将其进行拆分,将其拆分为多个应用,每个应用独立开发、独立部署、独立维护。

该部署方案更加灵活,大大降低维护成本:

  • 通过不同的域名或 URL 将整个系统分解为多个子系统。

  • 用户通过浏览器将各子系统拼接成一个完整的系统。

  • 各系统间存在少量交互,甚至没有交互。

问题慢慢展现出来,系统间公共部分没有统一维护点,同样的功能、同样的代码分布在各个系统中。当然,我们可以通过发布 jar 包的方式,共享功能代码;但当 jar 升级时,就需要所有的子系统同步升级,运维开销巨大。此时,我们需要引入服务化架构。

服务化架构

我们可以将通用功能封装成一个服务,独立开发、独立部署、独立维护。

在该方案中,我们将业务逻辑进行了进一步拆分:

  • 整理各个系统间通用业务功能,将其封装为服务,以承载核心业务逻辑,构建成服务集群。

  • 原来的子系统或子频道,变成薄薄的一层,不承载核心业务,只是根据业务流程对业务服务进行编排。

  • 应用服务与业务服务间通过 HTTP 或其他协议进行通信,常见的包括 Dubbo、Thrift 等。

服务化解决了系统之间的直接调用问题,也就是常说的 RPC,整个系统的协调点全部由应用服务完成。这种架构适用于多种场景,但在一些需要异步处理的极端场景就显得有心无力了。此时,我们需要引入消息中间件。

引入消息队列

服务化解决了直接调用问题,对于异步调用,最常见的便是消息中间件。

相比之前的架构,变化很小,只是在各个业务服务间添加了另外的一种调用方式。

小结

冰冻三尺非一日之寒,一个大型系统的构建也不是一朝一夕的事情。我们需要根据业务情况、数据量情况、请求量情况对系统进行合理规划。切记,架构不是越复杂越好,而是“适合自己的便是最好的”。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容