第1章 网站架构及其演变过程
1.1 软件的三大类型:单机类型、C/S类型、B/S类型
C/S结构图
B/S结构图
1.2 基础的结构并不简单
B/S结构网络传输的分解方式有两种:一种是标准的OSI参考模型,另一种是TCP/IP参考模型。它们的分层方式及对应关系如下图所示。
对于TCP/IP的4层模型可以简单地理解为:
网络接入层:将需要相互连接的节点接入网络中,从而为数据传输提供条件。
网络互联层:找到要传输数据的目标节点。
传输层:实际传输数据。
应用层:使用接收到的数据。
TCP/IP参考模型也可以看作一种协议。B/S结构中TCP/IP模型中的网络接入层没有相应协议,网际互联层是IP协议,传输层是TCP协议,应用层是HTTP协议。
1.3 架构演变的起点
基础架构中服务端就一台主机,其中存储了应用程序和数据库,刚上线是没有问题的,当数据和流量变得越来越大的时候就难以应付了,这时候就需要将应用程序和数据库分别放到不同的主机中,其结构如图所示。
应用和数据分离结构图
1.4 海量数据的解决方案
1.4.1 缓存和页面静态化
数据量大这个问题最直接的解决方案就是使用缓存,缓存就是将从数据库中获取的结果暂时保存起来,在下次使用的时候无需重新到数据库中获取,这样可以大大降低数据库的压力。
缓存的使用方式可以分为通过程序直接保存到内存中和使用缓存框架两种方式。程序直接操作主要是使用Map,尤其是ConcurrentHashMap,而常用的缓存框架有Ehcache、Memcache和Redis等。缓存使用过程中最重要问题是什么时候创建缓存和缓存的失效机制。缓存可以在第一次获取的时候创建也可以在程序启动和缓存失效之后立即创建,缓存的失效可以定期失效,也可以在数据发生变化的时候失效,如果按数据发生变化让缓存失效,还可以分粗粒度失效和细粒度失效。
缓存中空数据的管理方法------缓存穿透
如果缓存是在第一次获取的时候创建的,那么在使用缓存的时候最好将没有数据的缓存使用特定的类型值来保存,因为这种方式下如果从缓存中获取不到数据就会从数据库中获取,如果数据库中本来就没有相应的数据就不会创建缓存,这样将每次都会查询数据库。比如有个专门保存文章评论的缓存,不同的评论按照不同文章的Id来保存,如果有一篇文章本来就没有评论,那么就没有相应的缓存或者缓存的值为null,这样程序在每次调用这篇文章的评论时都会查询数据库。这就没起到缓存的作用,我们可以创建一个专门的类(如NoComment)来保存没有评论的缓存,这样程序从缓存中查询后就可以知道是还没有创建缓存还是本来就没有评论内容。
不过缓存也不是什么情况都适用,它主要用于数据变化不是很频繁的情况。而且如果是定期失效(数据修改时不失效)的失效机制,实时性要求也不能太高。因为这样缓存中的数据和真实数据可能会不太一致。如果是文章的评论则关系不大,但如果是企业业务系统中要生成报表的数据则问题就大了。
跟缓存相似的另外一种技术叫页面静态化,它在原理上跟缓存非常相似,缓存是将从数据库中获取到的数据(当然也可以是别的任何可以序列化的东西)保存起来,而页面静态化是将程序最后生成的页面保存起来,使用页面静态化后就不需要每次调用都重新生成页面了,这样不但不需要查询数据库,而且连应用程序处理都省了,所以页面静态化同时对数据量大和并发量高两大问题都有好处。
页面静态化可以在程序中使用模板技术生成,如常用的Freemarker和Velocity都可以根据模板生成静态页面,另外也可以使用缓存服务器在应用服务器的上一层缓存生成的页面,如可以使用Squid,另外Nginx也提供了相应的功能。
1.4.2 数据库优化
数据库优化的方法非常多,常用的有表结构优化、SQL语句优化、分区和分表、索引优化、使用存储过程代替直接操作等,另外有时候合理使用冗余也能获得非常好的效果。
表结构优化
SQL语句优化
SQL优化有一个通用的做法是,首先要将涉及大数据的业务的SQL语句执行时间详细记录下来,其次通过仔细分析日志(同一条语句对不同条件的执行时间也可能不同,这点也需要仔细分析)找出需要优化的语句和其中的问题,然后再有的放矢地优化,而不是不分重点对每条语句都花同样的时间和精力优化。
分区
当数据量变多的时候,如果可以分区或者分表,那将起到非常好的效果。当一张表中的数据量变多的时候操作速度就慢了,所以很容易想到的就是将数据分到多个表中保存,但是这么做之后操作起来比较麻烦,想操作(增删改查)一个数据还需要先找到对应的表,如果涉及多个表还得跨表操作。其实在常用的数据库中可以不分表而达到跟分表类似的效果,那就是分区。分区就是将一张表中的数据按照一定的规则分到不同的区来保存,这样在查询数据时如果数据的范围在同一个区内那么可以只对一个区的数据进行操作,这样操作的数据量更少,速度更快,而且这种方法对程序是透明的,程序不需要做任何改动。
分表
如果一张表中的数据可以分为几种固定不变的类型,而且如果同时对多种类型共同操作的情况不多,那么都可以通过分表来处理,这也需要具体情况具体对待。
另外一种分表的方法是将一个表中不同类型的字段分到不同的表中保存,这么做最直接的好处就是增删改数据的时候锁定的范围减小了,没被锁定的表中的数据不受影响。如果一个表的操作频率很高,在增删改其中一部分字段数据的同时另一部分字段也可能被操作,而且(主要指查询)用不到被增删改的字段,那么就可以把不同类型的字段分别保存到不同的表中,这样可以减少操作时锁定数据的范围。不过这样分表之后,如果需要查询完整的数据就得使用多表操作了。
索引优化
索引的大致原理是在数据发生变化(增删改)的时候就预先按指定字段的顺序排列后保存到一个类似表的结构中,这样在查找索引字段为条件的记录时就可以很快地从索引中找到对应记录的指针并从表中获取到记录,这样速度就快多了。不过索引也是一把双刃剑,它在提高查询速度的同时也降低了增删改的速度,因为每次数据的变化都需要更新相应的索引。
使用存储过程代替直接操作
在操作过程复杂而且调用频率高的业务中,可以通过使用存储过程代替直接操作来提高效率,因为存储过程只需要编译一次,而且可以在一个存储过程里面做一些复杂的操作。
1.4.3 分离活跃数据
通过一个定期处理的任务将不活跃的用户转移到别的数据表中,在主要操作的数据表中只保存活跃用户,查询时先从默认表中查找,如果找不到再从不活跃用户表中查找,这样就可以提高查询的效率。判断活跃用户可以通过最近登录时间,也可以通过指定时间段内登录次数。除了用户外还有很多这种类型的数据,如一个网站上的文章(特别是新闻类的)、企业业务系统中按时间记录的数据等。
1.4.4 批量读取和延迟修改
批量读取和延迟修改的原理是通过减少操作的次数来提高效率。
批量读取是将多次查询合并到一次中进行。
如果每保存一条记录都查询一次数据库,那么对每个需要检查的字段,都需要查询与要保存的记录条数相同次数的数据库,这时可以先将所有要保存的数据的相应字段读取到一个变量中,然后使用in语句统一查询一次数据库,这样就可以将n(要保存记录的条数)次查询变为一次查询了。除了这种对同一个请求中的数据批量读取,在高并发的情况下还可以将多个请求的查询合并到一次进行,如将3秒或5秒内的所有请求合并到一起统一查询一次数据库,这样就可以有效减少查询数据库的次数,这种类型可以用异步请求来处理。
延迟修改主要针对高并发而且频繁修改(包括新增)的数据,如一些统计数据。这种情况可以先将需要修改的数据暂时保存到缓存中,然后定时将缓存中的数据保存到数据库中,程序在读取数据时可以同时读取数据库中和缓存中的数据。这里的缓存和前面介绍的缓存有本质的区别,前面的缓存在使用过程中,数据库中的数据一直是最完整的,但这里数据库中的数据会有一段时间不完整。这种方式下如果保存缓存的机器出现了问题将可能会丢失数据,所以如果是重要的数据就需要做一些特殊处理。
1.4.5 读写分离
读写分离的本质是对数据库进行集群,这样就可以在高并发的情况下将数据库的操作分配到多个数据库服务器去处理从而降低单台服务器的压力,不过由于数据库的特殊性--每台服务器所保存的数据都需要一致,所以数据同步就成了数据库集群中最核心的问题。如果多台服务器都可以写数据那么数据同步将变得非常复杂,所以一般情况下是将写操作交给专门的一台服务器处理,这台专门负责写的服务器叫做主服务器。当主服务器写入(增删改)数据后从底层同步到别的服务器(从服务器),读数据的时候到从服务器读取,从服务器可以有多台,这样就可以实现读写分离,并且将读请求分配到多个服务器处理。主服务器向从服务器同步数据时,如果从服务器数量多,那么可以让主服务器先向其中一部分从服务器同步数据,第一部分从服务器接收到数据后再向另外一部分同步,这时的结构如图所示。
简单的数据同步方式可以采用数据库的热备份功能,不过读取到的数据可能会存在一定的滞后性,高级的方式需要使用专门的软硬件配合。另外既然是集群就涉及负载均衡问题,负载均衡和读写分离的操作一般采用专门程序处理,而且对应用系统来说是透明的。
(负载均衡,英文 名称为Load Balance,指由多台服务器以对称的方式组成一个服务器集合,每台服务器都具有等价的地位,都可以单独对外提供服务而无须其他服务器的辅助。通过某种 负载分担技术,将外部发送来的请求均匀分配到对称结构中的某一台服务器上,而接收到请求的服务器独立地回应客户的请求。负载均衡能够平均分配客户请求到服 务器阵列,借此提供快速获取重要数据,解决大量并发访问服务问题,这种集群技术可以用最少的投资获得接近于大型主机的性能。)
1.4.6 分布式数据库
分布式数据库是将不同的表放到不同的数据库中然后再放到不同的服务器。这样在处理请求时,如果需要调用多个表,则可以让多台服务器同时处理,从而提高处理速度。
数据库集群(读写分离)的作用是将多个请求分配到不同的服务器处理,从而减轻单台服务器的压力,而分布式数据库是解决单个请求本身就非常复杂的问题,它可以将单个请求分配到多个服务器处理,使用分布式后的每个节点还可以同时使用读写分离,从而组成多个节点群,结构图如图所示。
分布式的另外一种使用的思路是将不同业务的数据表保存到不同的节点,让不同的业务调用不同的数据库,这种用法其实是和集群一样起分流的作用,不过这种情况就不需要同步数据了。
1.4.7 NoSQL和Hadoop
NoSQL核心是非结构化。
Hadoop是专门针对大数据处理的一套框架。一般使用的数据库(SQL数据库)都是需要先将表的结构定义出来,一个表有几个字段,每个字段各是什么类型,然后才能往里面按照相应的类型保存数据,而且按照数据库范式的规定,一个字段只能保存单一的信息,不可以包括多层内容,这就对使用的灵活性带来了很大的制约,NoSQL就是突破了这些条条框框,可以非常灵活地进行操作,另外因为NoSQL通过多个块存储数据的特点,其操作大数据的速度也非常快。Hadoop是将同一个表中的数据分成多块保存到多个节点(分布式),而且每一块数据都有多个节点保存(集群),这里集群除了可以并行处理相同的数据,还可以保证数据的稳定性,在其中一个节点出现问题后数据不会丢失。这里的每个节点都不包含一个完整的表的数据,但是一个节点可以保存多个表的数据。
Hadoop数据存储结构图
Hadoop对数据的处理是对每一块的数据找到相应的节点进行处理,然后再对每一个处理的结果进行处理,最后生成最终的结果。
1.5 高并发的解决方案
1.5.1 应用和静态资源分离
刚开始的时候应用和静态资源是保存在一起的,当并发量达到一定程度时就需要将静态资源保存到专门的服务器中,静态资源主要包括图片、视频、js、css和一些资源文件等,这些文件因为没有状态,所以分离比较简单,直接存放到相应的服务器就可以了,一般会使用专门的域名去访问。应用和静态资源分离架构图。
1.5.2 页面缓存
页面缓存是将应用生成的页面缓存起来,这样就不需要每次都重新生成页面了,从而可以节省大量CPU资源,如果将缓存的页面放到内存中速度就更快了。如果使用了Nginx服务器就可以使用它自带的缓存功能,当然也可以使用专门的Squid服务器。页面缓存的默认失效机制一般是按缓存时间处理的,当然也可以在修改数据之后手动让相应缓存失效。
部分经常变化的数据的页面怎样使用页面缓存
页面缓存主要是使用在数据很少发生变化的页面中,但是有很多页面是大部分数据都很少发生变化,而其中有很少一部分数据变化的频率却非常高,比如,一个显示文章的页面正常来说是完全可以静态化的,但是如果在文章后面有“顶”和“踩”的功能而且显示的有相应的数量,这个数据的变化频率就比较高了,这就会影响静态化,在电商系统中显示商品详情的页面中的销售数量也是这种情况,对于这个问题可以先生成静态页面然后使用Ajax来读取并修改相应的数据,这样就可以一举两得了,既可以使用页面缓存也可以实时显示一些变化频率高的数据了。
1.5.3 集群与分布式
集群和分布式处理都是使用多台服务器进行处理的,集群是每台服务器都具有相同的功能,处理请求时调用哪台服务器都可以,主要起分流的作用,分布式是将不同的业务放到不同的服务器中,处理一个请求可能需要用到多台服务器,这样就可以提高一个请求的处理速度,而且集群和分布式也可以同时使用。
集群有两个方式:一种是静态资源集群,另一种是应用程序集群。对于应用程序集群,因为应用程序在处理过程中可能会使用到一些缓存的数据,如果集群就需要同步这些数据,其中最重要的就是Session,Session同步也是因应用程序集群中非常核心的一个问题。Session同步有两种处理方式:一种是在Session发生变化后自动同步到其他服务器,另外一种方式是用一个程序统一管理Session。所有集群的服务器都使用同一个Session,Tomcat默认使用的就是第一种方式,通过简单的配置就可以实现,第二种方式可以使用专门的服务器安装Memcached等高效的缓存程序来统一管理Session,然后在应用程序中通过重写Request并覆盖getSession方法来获取指定服务器中的Session。对于集群来说还有一个核心的问题就是负载均衡,也就是接收到一个请求后具体分配到哪个服务器去处理的问题,这个问题可以通过软件处理也可以使用专门的硬件(如F5)解决。
1.5.4 反向代理
反向代理指的是客户端直接访问的服务器并不真正提供服务,它从别的服务器获取资源然后将结果返回给客户。
反向代理服务器和代理服务器的区别
代理服务器的作用是代我们获取想要的资源然后将结果返回给我们,所要获取的资源是我们主动告诉代理服务器的,比如,我们想访问Facebook,但是直接访问不了,这时就可以让代理服务器访问,然后将结果返回给我们。
反向代理服务器是我们正常访问一台服务器的时候,服务器自己调用了别的服务器的资源并将结果返回给我们,我们自己并不知道。
代理服务器是我们主动使用的,是为我们服务的,它不需要有自己的域名;反向代理服务器是服务器自己使用的,我们并不知道,它有自己的域名,我们访问它跟访问正常的网址没有任何区别。
代理服务器被使用的对象是用户,反向代理服务器被使用的对象是服务器。
反向代理服务器主要有三个作用:①可以作为前端服务器跟实际处理请求的服务器(如Tomcat)集成;②可以作为负载均衡;③转发请求
1.5.5 CDN
1.6 底层的优化
1.7 小结
网站架构的整个演变过程主要是围绕大数据和高并发这两个问题展开的,解决的方案主要分为使用缓存和使用多资源两种类型。多资源主要指多存储(包括多内存)、多CPU和多网络,对于多资源来说又可以分为单个资源处理一个完整的请求和多个资源合作处理一个请求两种类型,如多存储和多CPU中的集群和分布式,多网络中的CDN和静态资源分离。理解了整个思路之后就抓住了架构演变的本质,而且自己可能还可以设计出更好的架构。
原文地址:https://www.cnblogs.com/nachdenken/p/11255700.html