用户是如何去上网的
在很早以前,用户会通过浏览器进行上网,在浏览器的地址栏里面去输入一个相应的网址,随后去打开相应的网站。那么对于这样的一个网站来讲的话,往往都是一个静态页面,它是一个单页面的一种网站。那么在这个网站里面,它所包含的一些内容仅仅是一些 HTML、JavaScript 以及 CSS 样式。
这样的一种网站的话,其实它们都是在 Web 1.0 里面最为常见的一种模式,那么所有的网站几乎都是静态网站,它没有和用户进行过一些相应的交互。它们都是单向的。
随着社会发展以及技术变更,在 Web 2.0 时代,就会成为这样的一种情况,用户通过浏览器去进行访问,访问的时候,我们的服务器里面也包含了一些 HTML、JavaScript 以及 CSS 样式等相应的内容。但是这个时候会有一个数据库,所以用户和服务器之间他们可以说是一种双向的交互的。
之前其实服务器和用户是单上的,那么服务器它仅仅只是提供了一些相应的内容,给用户去进行一个查验,现在我们的用户和服务器之间有了一定的交互。用户可以去增加、删除以及是修改一些相应的数据。这些数据都会保存到数据库里面,这个就是从最早期慢慢向后进行的一种演进的一个过程。随后我们就到了一种单体的一个架构。
单体架构模式
我们可以直接来看这样的一个架构图。
在这个服务器里面,我们会开发一个相应的项目。这个项目在打包完毕以后,它是一个 war,我们会把这个 war 包直接放到咱们的服务器里面去。
在这个 war 里面会包含相应的内容,比方说在之前我们所提到的 HTML、JavaScript 和 CSS 样式,在这个 war 中都会有。其次,对于我们一些处理用户请求的一些内容,比方说 Model、View 以及 Controller。这个其实就是早期传统 Java Web 开发所包含的一些相应的内容。它本质其实就是一个 Servlet ,或者又可以把它称之为是一种 MVC 的模式。
MVC 模式在 SSH 和 SSM 架构中常常使用,像这样的网站其实最开始是没有太多人的访问的。早期这样的 Java Web 构建的网站,我们只需要有一台服务器足够使用了。在这个服务器里面,除此以外我们还会有相应的文件服务器以及是数据库。
文件服务器是会为用户提供一个文件存储的一个地方,像用户的一些头像,用户上传的一些文件等等。我们都会放在文件服务器里面,只不过这个文件服务器和我们当前这个应用服务器是在一起的。
此外我们还有一个数据库,所有的用户数据也都会保存在这个数据库里面,这个其实就是一种单体的架构模式。单体的架构模式,其实也是早期传统的 Java Web 的这种开发模式。
早期架构分离模式
随着网站业务的一个发展,一台服务器开始不能够满足所有用户的访问需求的。这个时候流量可能会越来越多,当用户量访问过大会导致我们服务器的性能也会越来越差。最常见的问题可能就会导致我们的一个空间不足,不管是图片和数据库都会乏力。所以这个时候我们单体服务器一旦宕机了,我们所有的文件和数据库的数据都将无法访问。
所以我们的可以这样子,用户访问我们的一个服务器的时候,我们服务器里面仅仅只包含了我们的一个网站,网站数据都会在这里面。那么随后用户所上传的一些头像或者其他的文件数据会有一个额外的文件服务器。那么这个时候其实这两台服务器或者说两个节点,它们已经是分离的。另外我们的数据库也会独立的部署到另外一个单独的服务器里面。现在我们就把它们完全的分离了出来。
分离的好处有很多,可以对于我们用户的一些请求进行了一定的降压,这个就是最初的一种分离的模式。当我们的某个服务器挂掉以后,比方说我们的网站服务器宕机了,但是我们的文件服务器以及是数据库还是可以去工作,还是可以被我们进行访问的。像这种情况下,我们每一个节点都是需要去分开部署,独立出来。
网站和数据库还有图片的服务器,所有的内容全部进行分离,不同的服务器充当不同的角色,其中各自不同的服务。并且网站的并发能力以及是数据库存储的空间都有了一定的提高,这个时候企业的一个业务发展就不会被这种单体的架构而束缚了。
缓存中间件引入
随着一个时间的推移,用户的一个请求量,或者我们的用户成倍的增加。企业网站架构又会遭遇到另外的一个问题,即用户的查询问题延迟,数据库的压力会随着这个问题,导致我们的一个用户访问请求的延时。用户的查询一旦会很多的话,其实它的所有的一个请求量都会直接落到我们的一个图上,在这个时候我们会引入一个缓存中间线,它其实就是相当于是一种防护机制。
用户要去进行一些数据查询的时候,它是不会直接接触到数据库的,首先会落在这个缓存的中间件里面,会去这个缓存里面查询一下相关的数据。如果说有数据,那么就直接返回了。如果说没有数据,才会去访问我们的一个数据库。如果一开始用户的请求变慢,用户体验变差,这样子肯定会导致一定用户的一个流失。所以必须要做出这样的一种调整,来引入这样的一个缓存中间件。
用户绝大多数的查询都会落在缓存库里面,而不是直接接触到我们的数据库,为我们数据库做好了一个不错的防范措施,保证我们用户的体验可以有一定的提高,请求响应的速度也会加快。这个时候我们目前所有的节点其实都是单节点部署。
单节点部署还会有一些相应的问题。如果说这个时候用户流量还是很大,会导致我们某个节点宕机,这个时候对于用户来讲,其实是不可访问的。对于企业来讲,是一种毁灭性的打击。
集群部署
单节点部署就会成为我们网站的一种瓶颈。所以在这个时候,我们会引入集群负载均衡教的概念。像我们的应用集群,比如在我们实际工作常常搭建的 Tomcat 容器集群。我们的项目会放在不同的 Tomcat 容器里面,每一个 Tomcat 容器都是一模一样的个体,部署多个 Tomcat 容器构成就是一个集群。
通过集群可以提升我们整个系统的性能以及其负载。负载均衡也会涉及到一些相应的算法。不仅仅我们的网站可以构建成一个集群,对于我们的文件服务器来讲,我们也是可以把它来部署成一个集群的。另外我们的缓存也是可以部署为一个集群,也可以作为一个集群用来提供服务。
我们的缓存、文件服务器以及我们的 Tomcat,它们就不再是一个单节点,当它们都是一种集群模式存在,我们的整体网站性能会有一定的提高。当然我们的数据库,在这里我们暂时还仅仅只是单库,暂时还不会去做一些相应的一个集群。
读写分离出现
虽然我们在前面已经是使用到了缓存,但是,我们还是会有一部分的数据,它的一个读操作会直接落在数据库上。同时还会有所有的一些写操作,也是会直接访问到数据库的。
在网站用户达到百万甚至千万级别了以后,我们数据库的负载能力就会成为我们网站架构的一个瓶颈。那么如何来解决这样的问题呢?大约有 70% ~ 80% 的一个请求都是读请求,写操作大约有 20% ~ 30%。其实这就是一个二八原则。
这个时候数据库又有读,又有写的话,它的压力是非常巨大的。所以我们可以通过数据库的读写分离设置两台数据库。
一台我们可以设置为主库,它的数据是可以写入到主库里面去的。同时我们还会有一个从库,我们还是会有一个缓存,会有一部分的数据会从我们的从库里面去进行一个读取。这个其实就是一个主从分离的一个架构。这样的两拨不同的一个流量,一个是读一个是写,就被我们所分开了,从而可以改善我们数据库的一个负载能力。
读写分离的需要注意我们数据的一个同步的,也就是说我们主库会定时的向从库去同步数据。虽然我们做了读写分离,有了两个库,但是对于用户来讲,那么我们对于用户是透明的,这些东西只有我们自己才知道。那么用户只需要知道他访问的时候,比较快就可以了。
虽然我们已经是做了数据库的读写分离,但是大型网站在一个夜晚,用户访问还是会急剧的增长的。那么这个时候呢怎么办呢?
分库分表出现
如果说我们的数据库扛不住了,我们就需要去针对我们的数据库做分库和分表。
我们来看一下这个时候,我们的主库我们分为了三个数据库,这时就有了主数据库集群。
随后我们的一个读取操作,我们的数据会去从我们的从数据库里面去进行一个读取。前面说到主从分离是需要有数据同步,所以如果说我们的一个数据库做了集群,在这里面我们也是要去做好它的一个数据库集群之间数据的一个同步的,这个其实就是一个分库和分表。
我们的把单台数据库拆分为多个库,既然有多个库了,就将同一张表的数据根据一定的算法和规则散列在不同的数据库。那么这种做法,我们也称之为是一种分布式数据库。这也是我们对数据库拆分的一个最后的手段。并且只有在我们数据库规模非常庞大的时候,我们才会去考虑使用分库分表。
一般来说当我们单表的数据达到 700 万以上的时候,就要考虑进行分库分表的设计了。因为数据量达到一定程度,我们数据库的性能会急剧下降的。当然在我们使用这种分布式数据库的时候,一定要注意,一旦我们进行分库分表,分布式组件也就是我们每一张表里面的每一条数据,它的自己的主键就不能够再使用自增长。我们要使用分布式组件,也就是全局唯一的组件。
搜索引擎
随着我们网站业务的一个持续发展,用户对于我们的数据的解锁可能会出现多样化。我们数据库是可以支持模糊查询的,模糊查询可能就满足不了用户需求,也解决不了相应的需求。所以在这个时候我们就需要去引入相应的搜索引擎来贴合用户的搜索需求。
比如我们可以去引入 Solr、ElasticSearch。那么对于这类的搜索,我们就可以无需再让用户请求来到达数据库。满足用户搜索需求的同时,我们也为数据库提供了一定的保护措施,不会让我们这一部分的搜索到达我们数据库的。在提供海量数据搜索的时候,那么同时也为我们数据库进行了一定的保护。
服务拆分
对于大型网站的业务,其实来是非常非常复杂的。俗话说合久必分,这个是永恒不变的一个道理。所以当我们的一个业务处于非常非常复杂的时候,那么这个时候我们就需要去进行拆分,把我们一个非常大的业务拆分成不同的一个小的产品,不同的业务会独立成一个一个的子项目。
我们来看一下,比方我们是在做一个电商啊,那么我们是可以把商品相关的内容可以直接拆分出来,拆分成一个独立的子系统,这个独立的子系统就专门是用于提供和商品相关的服务的。
与此同时我们也可以把订单拆出来,订单拆出来了以后,它是可以专门为订单相关的内容去做一个的服务。
当然还会有很多,比方说我们可能还会有一些用户服务库存服务等等。那么在这里我们就举例商品和订单了。
既然拆了一些相应的一些服务出来以后,那么一定要注意,一旦去进行拆分,我们在这个时候所做的事情是一个微服务的阶段。我们的数据库也是必须要跟着我们的业务去拆的。相应的我们的商品表相关的一些商品数据也也要进行拆分。比方说电商项目中可能会有一些商品表、商品规格表、商品的图片表等等的内容,把这些表单独的挑出来,作为一个独立的商品数据库。
相应订单标也是一样,并且我们可以参照我们之前所做的针对这些商品数据库以及是单数据库做一个主从分离,对它们进行一些分库分表操作也都可以去做是没有问题的。其实我们每个系统都可以把它们去交给不同的团队去进行维护了。
这样子对于开发、测试和运维来讲,也都是一个比较大的挑战。因为我们的业务变得复杂了。不同的个体整合在一起就是一个庞大的大型系统。
这样设计项目也会有一定的优缺点,优点负载会较低,业务分离。开发人员只要负责自己的一些模块了。缺点进行它的代码会比较复杂,运维也会变得更加的繁琐和复杂。
还有一个是必须考虑的问题,就是分布式事务。因为我们的一个服务拆出来了,用户的一个业务,用户的一个请求,他可能会同时到达多个子系统,所以分布式事务也必须是我们要去考虑的一个问题。
那么在一个系统里面,我们往往会有一些非常通用的一个接口,我们来看一下用户,其实是可以在访问我们的每个子系统的时候,去调用一些相应的公共的服务。比方说我们有一些短信、邮件推送,把消息推送到 APP,这些我们可以把它们称之为公共的服务。
对于公共的服务资源,在服务和服务之间进行相互通用的时候。都会使用到一些相应的分布式的服务中间件。比如 Zookeeper, Zookeeper 就可以去处理一些分布式锁。在我们分布式系统里面,我们还会有一个分布式会话。
与此同时,在我们系统之间调用的时候,我们往往会使用一种异步通信或者说是异步调用的这种方式。那么在这里我们又会使用异步队列,也就是 MQ 消息队列来处理的调用请求。
另外一些分布式锁、分布式事务、分布式会话在系统里面,是必须要去考虑的一些问题。当我们的一个网站架构演变到这里的时候,其实我们可以解决绝大多数的一些技术问题。我们对于我们的网站以及对于我们的架构还是要去进行一些相应的优化的。
比 JVM、Tomcat 以及数据库等等,我们都需要去根据一些不同的一些情况。比方说发生一些问题的,我们都要去根据相应的日志分析,去做一定的相应的调优。
所以对于架构师来讲,调优能力也是应该要具备的。在整个架构演变的过程中,我们会根据企业当前的一个业务去进行演变的。前面我们所提到的一些演变的一个顺序,都是可以自由调整的,并不是说我们必须在某一个节点使用一个特定的技术,这是没有必要的。