大型网站是一种很常见的分布式系统,那么什么是大型网站呢?在学术上没有明确的定义,但我觉得它有几个衡量指标:高并发量(高访问量),海量数据,自身业务和系统复杂度等。同时,大型网站的核心功能是计算和存储。
接下来说下演进过程: 随着访问量,数据量,自身业务和系统复杂度等的不断增加(演进原因),一个网站从大到小基本上都是围绕计算和存储这两个方面进行处理的。
1.用java技术和单机构建的网站
在开发方面,基本使用一些开源框架如jsp/Servlet,spring,mybatis,springmvc等来构建我们的应用程序,选择一个数据库管理系统来存储数据,通过jdbc进行数据库的连接和操作。在部署方面 我们基本上会选择一个开源的Server容器 如tomcat来执行我们的应用程序。这样一个最基础的环境就可以工作了。
图中可以看到,一个服务器上部署了应用程序和数据库。其中应用程序(Application Server)用于完成业务功能和逻辑(计算),数据库(DB)是用来持久化存储的(存储)。应用和数据库之间是通过jdbc连接进行交互的,此时的jdbc连接地址是本机(localhost)
2.数据库与应用分离
例:单机的交易网站
上图:交易,商品,用户模块之间的交互是通过JVM内部的方法调用来进行交互的。
随着访问量的增加,一台服务器的负载压力持续增大,怎么办呢?
前面说到,一台服务器部署了两部分:应用程序和数据库,因此,一个字拆
上图:应用程序和数据库分别在不同的服务器上,此时jdbc连接的变化:由本机地址——》远程地址,其它貌似没啥变化,开发部署貌似和单机结构一样。
3.应用服务器集群
随着访问量持续增大,一台服务器一个应用程序的结构负载压力越来越大,到了快奔溃的边缘,怎么办呢?两个字:集群
上图:将应用程序部署在两台服务器上,这两个相同的应用程序之间没有直接的交互。
此时出现了两个问题:
(1)用户对应用服务器选择问题,假如说现在由10个用户在访问,最好的结果莫过于,每个应用程序支持5个。最差的结果一个应用服务器支持10个,另一台啥也不干,那这跟上一阶段没啥区别了,还不如不集群。怎么解决呢?
解决方式:可以通过DNS,可以通过在应用服务器集群前增加负债均衡器来解决。
采用负债均衡方案:
看似好像没问题,其实有问题,引入集群过程中遇到的第二个问题:
(2)session问题
首先介绍什么是session:用户使用网站时,一般在网站上做了很多操作,没事这里点点那里点点,因此需要浏览器与web服务器多次交互(HTTP请求),恰不巧,HTTP协议是无状态协议,也就是说上一次请求和下一次请求之间没关系(就相当于昨天你俩还认识,今天就不认识了,你得告诉他你是谁谁谁,他才哦我知道你是谁了),这就很麻烦了,比如我已经登录了某个网站,然后想看看网站中的美女图片,点击搜索,然而直接跳转了登陆页面,服务器认为你没登录。因此session会话机制应运而生,它可以使Web服务器从多次单独的HTTP请求中看到“会话”,也就相当于服务器知道哪些请求是来自哪个会话的。
具体实现方式:在会话开始时,分配一个唯一的会话标识(sessionId),通过cookie把这个标识告诉浏览器,以后每次请求的时候,浏览器都会带着这个会话标识来告诉web服务器请求时属于哪个会话的,在web服务器上,会话之间时独立的,如果浏览器禁用了cookie,可以把这个会话标识放到URL参数中。
当使用集群时,当一个带有会话标识的HTTP请求到了web服务器后,需要在服务器端找到对应的会话数据,问题在于会话数据时保存在单机服务器上的。
如果我第一次请求负载均衡器分配到了左边的应用服务器,那session就创建在了左边,如果我第二次请求负债均衡器分配到了右边,右边应用服务器发现没有对应的session,然后创建了一个新的session,这里就相当于反复登录的例子了。如果我们不做处理,就不能保证接下来同一个会话中每一个请求都落在同一个应用服务器上,这就是session问题
解决方案:
第一种:session s'ti'cky
配置负债均衡器,让每次请求的同一个会话标识转到同一台应用服务器处理,这种方式对于会话来说就相当于上一阶段单机了。
缺点:如果这台应用服务器挂了或重启了,这台服务器的会话数据丢失,如果会话由登录状态,那么用户得重新登陆了。
负债均衡器变成了有状态的节点,与无状态节点相比,更消耗内存。
第二种:session集中存储
这时同一个会话中的请求可以转发到不同的应用服务器处理了,应用服务器只需要到session存储地取就可以了。
缺点:读写session数据网络操作,可能由于网络不稳定导致延时,不过通信基本时内网,问题不大。
如果集中存储session地有(可以有多个,也可以配集群)问题。会影响应用。
3.数据库读写分离
随着业务的发展,数据量和访问量继续变大,对于网站来说,有很多业务读多写少,对于这个情况,怎么办呢,4个字:读写分离
上图:增加了读库DB4Read,只负责读服务。解决读的压力
这是需要考虑两个问题:
(1)数据复制问题:数据延时带来的数据不一致,不能够实时同步,如果修改了某条信息,这个信息还没复制到读库中,这个时候用户取看,发现好像没有修改成功一样。
(2)应用对数据源的选择问题:这个时候我们要切换数据源,写的时候走DB,读的时候走DB4Read
4.数据水平和垂直拆分
当数据库的负载压力增大,可以使用垂直拆分
垂直拆分:把数据库中不同业务数据拆分到不同的数据库中去,达到专库专用。
上图:
这种架构带来了一个问题:不同的业务的数据分布到不同的数据库中,那么如何处理跨业务的事务。
一种办法时分布式事务,其性能低于之前的单机事务,另一种办法就是去掉事务或者不去追求强事务支持,则原来在单库中可以使用的表关联的查询也就需要改变实现了。
水平拆分:
当单表中的数据量庞大或者更新量达到了单个数据库的瓶颈,就把同一个表的数据水平拆分到两个数据库中去。
上图:解决了单表数据量庞大的问题
同时也带来了一些问题:
以用户信息为例:(1)用户信息现在分在了两个数据库中,操作数据库时,需要知道我所需要的数据保存在哪个数据库中。
(2) 主键的处理方面:原先单个数据库时,我们一般采用索引sequence(oracle)auto_increment(mysql)
来自增主键,现在不能简单使用了,怎么才能保证主键连续自增又不重复呢?
(3)查询数据时,需要从两个用户数据库中查询数据,如果数据量太大而需要分页,又怎么办呢?
垂直拆分和水平拆分的区别:垂直拆分时将不同的表拆分到不同数据库中去(一般按业务划分),水平拆分时将同一个表拆分到不同数据库中去(一般拆表数据量打的表)。
说到现在,大体说了啥:访问量大,应用程序压力大(计算)搞了个集群;数据量大,数据库压力大(存储)搞了个拆分。
现在数据库被拆了,还有应用程序没被拆呢
那如果访问量继续加大,咋整,有人说,好办,横向扩展(集群)多复制几个应用程序在服务器中跑,这样做不是不可以,那如果业务逻辑和系统复杂性越来越大呢,简单的横向扩展还好使吗,一个应用程序都非常庞大了,处理请求越来越慢 ,多复制几个又有啥用呢?咋整?一个字:拆,说到现在有没有发现,不是拆就是集群。
5.拆分应用
根据业务的特性拆分
上图:将左边拆分成右边,以交易和商品为主的两个应用,由于交易和商品都用到用户,我们呢让两个系统自己完成涉及用户的工作,而类似用户注册,登录等基础操作,可以交给交易和商品其中一个系统统一完成,有人说为嘛不把用户也独立出来,这样做的目的是避免应用之间的直接调用,一定程度上也将大应用变成了相互独立的多个应用。
缺点:大量重复代码,如交易用到用户信息,交易系统写一份,商品用到用户信息,商品系统又写了一份。
6.服务化
随着业务逻辑和系统复杂性持续加大,重复代码越来越多,还要确保交易和商品系统所维护的用户信息一致,难度越来越大,简单的应用拆分已经不饿能满足日益的变化了,这时候,应用与应用之间交互不可避免。因此基于远程服务调用(RPC)应运而生。将共享的代码抽取出来(如商品和交易中的用户),形成独立的服务中心。
上图:商品,用户,交易 共享的代码抽取出来形成商品中心,用户中心,交易中心。
总共分为三层:上层是web系统,用于完成不同的业务功能,处于中间的则是一些服务中心,下层则是业务的数据库。
几个重大变化:(1)业务功能之间的访问不仅是单机内部了,用到了RPC。
(2)共享的代码不再是散落在不同的应用中了,这些实现被放在了各个服务中心。
(3)数据库连接也发生了变化,我们把数据库的交互工作放到了服务中心,服务中心与数据库交互。
服务中心不仅把一些可以共用的之前散落在各个业务系统中的代码集中起来统一管理,而且还能使之更好的维护。
主要参考书籍: