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