web服务器(Tomcat)和数据库分别独占服务器资源,显著地提高两者各自的性能。
随着用户数量的增长,并发读写数据库成为了性能的瓶颈。
在Tomcat服务器或JVM中增加本地缓存,并在外部增加分布式缓存,缓存热门商品信息或热门商品的HTML页面等。通过缓存能把绝大多数请求在读写数据库前拦截掉,大大降低数据库压力。其中涉及的技术包括:使用Memcached作为本地缓存,使用Redis作为分布式缓存,这会涉及到缓存一致性、缓存穿透/击穿、缓存雪崩、热点数据集中失效等问题。
具体和缓存方案:
- 后台应用层和数据库层的缓存
随着访问量的增加,逐渐出现了许多用户访问同一部分内容的情况,对于这些比较热门的内容,没必要每次都从数据库读取。我们可以使用缓存技术,例如可以使用google的开源缓存技术guava或者使用memcacahe作为应用层的缓存,也可以使用redis作为数据库层的缓存。
另外,在某些场景下,关系型数据库并不是很适合,例如我想做一个“每日输入密码错误次数限制”的功能,思路大概是在用户登录时,如果登录错误,则记录下该用户的IP和错误次数,那么这个数据要放在哪里呢?
假如放在内存中,那么显然会占用太大的内容;假如放在关系型数据库中,那么既要建立数据库表,还要建立对应的java bean,还要写SQL等等。而分析一下我们要存储的数据,无非就是类似{ip:errorNumber}这样的key:value数据。对于这种数据,我们可以用NOSQL数据库来代替传统的关系型数据库。
- 页面缓存
除了数据缓存,还有页面缓存。比如使用HTML5的localstroage或者cookie。
-
优点:
-
减轻数据库的压力
-
大幅度提高访问速度
-
缺点:
-
需要维护缓存服务器
-
提高了编码的复杂性
值得一提的是:缓存集群的调度算法不同与上面提到的应用服务器和数据库。最好采用“一致性哈希算法”,这样才能提高命中率。
缓存虽然抗住了大部分的访问请求,但是随着用户数量的增长,并发的压力还是主要落在单机的Tomcat上,响应逐渐变慢。
随着访问量继续增加,单台应用服务器已经无法满足需求了。在假设数据库服务器没有压力的情况下,我们可以把应用服务器从一台变成了两台甚至多台,把用户的请求分散到不同的服务器中,从而提高负载能力。
多台应用服务器之间没有直接的交互,他们都是依赖数据库各自对外提供服务。
在多台服务器上分别部署Tomcat,使用反向代理软件(Nginx)把请求均匀分发到每个Tomcat中。此处假设Tomcat最多支持100个并发,Nginx最多支持50000个并发,那么理论上Nginx把请求分发到500个Tomcat上,就能抗住50000个并发。其中涉及的技术包括:Nginx、HAProxy,两者都是工作在网络第七层(最高层、应用层)的反向代理软件,主要支持HTTP协议,还会涉及Session共享,文件上传、下载的问题。
系统演变到这里,有可能会出现下面四个问题:
-
用户的请求由谁来转发到到具体的应用服务器
-
有什么转发的算法
-
应用服务器如何返回用户的请求
-
用户如果每次访问到的服务器不一样,那么如何维护session的一致性
解决方案:
-
第一个问题即是负载均衡的问题,一般有5种解决方案:
-
http重定向。HTTP重定向就是应用层的请求转发。用户的请求其实已经到了HTTP重定向负载均衡服务器,服务器根据算法要求用户重定向,用户收到重定向请求后,再次请求真正的集群
优点:简单。
缺点:性能较差。
- DNS域名解析负载均衡。DNS域名解析负载均衡就是在用户请求DNS服务器,获取域名对应的IP地址时,DNS服务器直接给出负载均衡后的服务器IP。
优点:交给DNS,不用我们去维护负载均衡服务器。
缺点:当一个应用服务器挂了,不能及时通知DNS,而且DNS负载均衡的控制权在域名服务商那里,网站无法做更多的改善和更强大的管理。
- 反向代理服务器。在用户的请求到达反向代理服务器时(已经到达网站机房),由反向代理服务器根据算法转发到具体的服务器。常用的apache,nginx都可以充当反向代理服务器。
优点:部署简单。
缺点:代理服务器可能成为性能的瓶颈,特别是一次上传非常大的文件。
- IP层负载均衡。在请求到达负载均衡器后,负载均衡器通过修改请求的目的IP地址,从而实现请求的转发,做到负载均衡。
优点:性能更好。
缺点:负载均衡器的宽带成为瓶颈。
-
数据链路层负载均衡。在请求到达负载均衡器后,负载均衡器通过修改请求的mac地址,从而做到负载均衡。与IP负载均衡不一样的是,当请求访问完服务器之后,直接返回客户。而无需再经过负载均衡器。
-
第二个问题即是集群调度算法问题,常见的调度算法有以下10种:
-
rr 轮询调度算法。顾名思义,轮询分发请求。
优点:实现简单
缺点:不考虑每台服务器的处理能力
- wrr 加权调度算法。我们给每个服务器设置权值weight,负载均衡调度器根据权值调度服务器,服务器被调用的次数跟权值成正比。
优点:考虑了服务器处理能力的不同
-
sh 原地址散列:提取用户IP,根据散列函数得出一个key,再根据静态映射表,查出对应的value,即目标服务器IP。如果目标机器超负荷,则返回空。
-
dh 目标地址散列:同上,只是现在用提取的是目标地址的IP来做哈希。
优点:以上两种算法都能实现同一个用户访问同一个服务器。
- lc 最少连接。优先把请求转发给连接数少的服务器。
优点:使得集群中各个服务器的负载更加均匀。
- wlc 加权最少连接。在lc的基础上,为每台服务器加上权值。算法为:(活动连接数*256+非活动连接数)÷权重 ,计算出来的值小的服务器优先被选择。
优点:可以根据服务器的能力分配请求。
-
sed 最短期望延迟。其实sed跟wlc类似,区别是不考虑非活动连接数。算法为:(活动连接数+1)*256÷权重,同样计算出来的值小的服务器优先被选择。
-
nq 永不排队。改进的sed算法。我们想一下什么情况下才能“永不排队”,那就是服务器的连接数为0的时候,那么假如有服务器连接数为0,均衡器直接把请求转发给它,无需经过sed的计算。
-
LBLC 基于局部性的最少连接。均衡器根据请求的目的IP地址,找出该IP地址最近被使用的服务器,把请求转发之;若该服务器超载,最采用最少连接数算法。
-
LBLCR 带复制的基于局部性的最少连接。均衡器根据请求的目的IP地址,找出该IP地址最近使用的“服务器组”。注意,并不是具体某个服务器,然后采用最少连接数从该组中挑出具体的某台服务器出来,把请求转发之。若该服务器超载,那么根据最少连接数算法,在集群的非本服务器组的服务器中,找出一台服务器出来,加入本服务器组,然后把请求转发之。
-
第三个问题是集群模式问题,一般3种解决方案:
-
NAT:负载均衡器接收用户的请求,转发给具体服务器,服务器处理完请求返回给均衡器,均衡器再重新返回给用户。
-
DR:负载均衡器接收用户的请求,转发给具体服务器,服务器处理完请求后直接返回给用户。需要系统支持IP Tunneling协议,难以跨平台。
-
TUN:同上,但无需IP Tunneling协议,跨平台性好,大部分系统都可以支持。
-
第四个问题是session问题,一般有以下4种解决方案:
-
Session Sticky。session sticky就是把同一个用户在某一个会话中的请求,都分配到固定的某一台服务器中,这样我们就不需要解决跨服务器的session问题了,常见的算法有ip_hash法,即上面提到的两种散列算法。
优点:实现简单。
缺点:应用服务器重启则session消失。
- Session Replication。session replication就是在集群中复制session,使得每个服务器都保存有全部用户的session数据。
优点:减轻负载均衡服务器的压力,不需要实现ip_hasp算法来转发请求。
缺点:复制时宽带开销大,访问量大的话session占用内存大且浪费。
- Session数据集中存储:session数据集中存储就是利用数据库来存储session数据,实现了session和应用服务器的解耦。
优点:相比session replication的方案,集群间对于宽带和内存的压力减少了很多。
缺点:需要维护存储session的数据库。
- Cookie Base:cookie base就是把session存在cookie中,有浏览器来告诉应用服务器我的session是什么,同样实现了session和应用服务器的解耦。
优点:实现简单,基本免维护。
缺点:cookie长度限制,安全性低,宽带消耗。
值得一提的是:
-
nginx目前支持的负载均衡算法有wrr、sh(支持一致性哈希)、fair(本人觉得可以归结为lc)。但nginx作为均衡器的话,还可以一同作为静态资源服务器。
-
keepalived+ipvsadm比较强大,目前支持的算法有:rr、wrr、lc、wlc、lblc、sh、dh
-
keepalived支持集群模式有:NAT、DR、TUN
-
nginx本身并没有提供session同步的解决方案,而apache则提供了session共享的支持。
虽然反向代理使应用服务器可以支持的并发量大大增加,但是并发量的增加也意味着更多请求穿透到数据库,单机的数据库最终会称为性能瓶颈。
上面我们总是假设数据库负载正常,但随着访问量的的提高,数据库的负载也在慢慢增大。那么可能有人马上就想到跟应用服务器一样,把数据库一份为二再负载均衡即可。
但对于数据库来说,并没有那么简单。假如我们简单的把数据库一分为二,然后对于数据库的请求,分别负载到A机器和B机器,那么显而易见会造成两台数据库数据不统一的问题。那么对于这种情况,我们可以先考虑使用读写分离的方式。
把数据库划分为读库和写库,读库可以有多个,通过同步机制把写库的数据同步到读库,对于需要查询最新写入数据的场景,可以在缓存中多写一份,通过缓存获得最新数据。其中涉及的技术包括Mycat,它是数据库中间件,可通过它来组织数据库的读写分离和分库分表,客户端通过它来访问下层数据库,还会涉及数据同步,数据一致性的问题。
这个结构变化后也会带来两个问题:
-
主从数据库之间数据同步问题
-
应用对于数据源的选择问题
解决问题方案:
-
我们可以使用MYSQL自带的master+slave的方式实现主从复制。
-
采用第三方数据库中间件,例如mycat。mycat是从cobar发展而来的,而cobar是阿里开源的数据库中间件,后来停止开发。mycat是国内比较好的mysql开源数据库分库分表中间件。
随着业务逐渐变多,不同业务之间的访问量差距较大,不同业务直接竞争数据库资源,相互影响性能。
我们的网站演进到现在,交易、商品、用户的数据都还在同一个数据库中。尽管采取了增加缓存,读写分离的方式,但随着数据库的压力继续增加,数据库的瓶颈越来越突出,此时,我们可以有数据垂直拆分和水平拆分两种选择。
数据库垂直拆分(按业务分库)
垂直拆分的意思是把数据库中不同的业务数据拆分道不同的数据库中,比如把电商项目中的交易、商品、用户的数据分开。
把不同业务的数据保存到不同的数据库中,使业务之间的资源竞争降低。对于访问量大的业务,可以部署更多的服务器来支撑。
-
优点:
-
解决了原来把所有业务放在一个数据库中的压力问题。
-
可以根据业务的特点进行更多的优化
-
缺点:
需要维护多个数据库
-
问题:
-
需要考虑原来跨业务的事务
-
跨数据库的join
-
解决问题方案:
-
应该在应用层尽量避免跨数据库的事物,如果非要跨数据库,尽量在代码中控制。
-
可以通过第三方应用来解决,如上面提到的mycat,mycat提供了丰富的跨库join方案,详情可参考mycat官方文档。
随着用户数量的增长,单机的写库会逐渐达到性能瓶颈。
水平拆分(把大表拆分为小表(分表))
数据水平拆分就是把同一个表中的数据拆分到两个甚至多个数据库中。产生数据水平拆分的原因是某个业务的数据量或者更新量到达了单个数据库的瓶颈,这时就可以把这个表拆分到两个或更多个数据库中。
比如针对评论数据,可以按照商品的ID进行Hash,路由到对应的表中存储;针对支付记录,可以按照支付的小时创建表,每个小时表继续拆分为小表,使用用户ID或记录编号来路由数据。只要实时操作的表数据量足够小,请求能够足够均匀地分发到多台服务器上的小表,那数据库就能通过水平扩展的方式来提升性能。其中前面提到的Mycat也支持在大表拆分为小表的情况下进行访问控制。
这种做法显著地增加了数据库运维的难度,对DBA的要求较高。当数据库设计到这种结构时,已经可以称为分布式数据库,但是这只是一个逻辑的数据库整体,数据库里不同的组成部分是由不同的组件单独来实现的,比如分库分表的管理和请求分发由Mycat实现,SQL的解析由单机的数据库实现,读写分离可能由网关和消息队列来实现,查询结果的汇总可能由数据库接口层来实现等,这种架构其实是MPP
(大规模并行处理)架构的一类实现。
目前开源和商用都已经有不少MPP数据库,开源中比较流行的有Greenplum、TiDB、Postgresql XC、HAWQ等,商用的如南大通用的GBase、睿帆科技的雪球DB、华为的LibraA等,不同的MPP数据库的侧重点也不一样,比如TiDB侧重于分布式OLTP场景,Greenplum侧重于分布式OLAP场景,这些MPP数据库基本都提供了类似Postgresql、Oracle、MySQL那样的SQL标准支持能力,能把一个查询解析为分布式的执行计划分发到每台机器上并行执行,最终由数据库本身汇总数据进行返回,也提供了注入权限管理、分库分表、事务、数据副本等能力,并且大多能够支持100个节点以上的集群,大大降低了数据
库运维的成本,并且使数据库也能够水平扩展。
- 优点:
如果我们能克服以上问题,那么我们将能够很好地应对数据量及写入量增长的情况。
-
问题:
-
访问用户信息(假设用户表进行了水平拆分)的应用系统需要解决SQL路由的问题,因为现在用户信息分在了两个数据库中,需要在进行数据操作时了解需要操作的数据在哪里。
-
主键的处理也变得不同,例如原来自增字段,现在不能简单地继续使用了。
-
如果需要分页,就麻烦了。
-
解决问题方案:
-
可以通过可以解决第三方中间件,如mycat。mycat可以通过SQL解析模块对我们的SQL进行解析,再根据我们的配置,把请求转发到具体的某个数据库。
-
可以通过UUID保证唯一或自定义ID方案来解决。
-
mycat也提供了丰富的分页查询方案,比如先从每个数据库做分页查询,再合并数据做一次分页查询等等。
虽然数据库和Tomcat都能够水平扩展,可以支撑的并发量大幅提升,但是随着用户量的增长,最终单机的Nginx会成为性能上的瓶颈。
由于性能瓶颈在Nginx,因此无法通过两层的Nginx来实现多个Nginx的负载均衡。LVS和F5是工作在网络第四层的负载均衡解决方案,其中:
-
LVS是软件,运行在操作系统内核态,可对TCP请求或更高层级的网络协议进行转发,因此支持的协议更丰富,并且性能也远高于Nginx,可假设单机的LVS可支持几十万个并发的请求转发;
-
F5是一种负载均衡硬件,与LVS提供的能力类似,性能比LVS更高,但价格昂贵。
由于LVS是单机版的软件,若LVS所在服务器宕机则会导致整个后端系统都无法访问,因此需要有备用节点。可使用keepalived软件模拟出虚拟IP,然后把虚拟IP绑定到多台LVS服务器上,浏览器访问虚拟IP时,会被路由器重定向到真实的LVS服务器,当主LVS服务器宕机时,keepalived软件会自动更新路由器中的路由表,把虚拟IP重定向到另外一台正常的LVS服务器,从而达到LVS服务器高可用的效果。
此处需要注意的是,上图中从Nginx层到Tomcat层这样画并不代表全部Nginx都转发请求到全部的Tomcat,在实际使用时,可能会是几个Nginx下面接一部分的Tomcat,这些Nginx之间通过keepalived实现高可用,其他的Nginx接另外的Tomcat,这样接入的Tomcat数量就能成倍的增加。
由于LVS也是单机的,随着并发数量增长到几十万时,LVS服务器最终会达到性能瓶颈,此时用户数量达到千万甚至上亿级别,用户分布在不同的地区,与服务器机房距离不同,导致了访问的延迟会明显不同。
在DNS服务器中可配置一个域名对应多个IP地址,每个IP地址对应到不同的机房里的虚拟IP。当用户访问www.taobao.com时,DNS服务器会使用轮询策略或其他策略,来选择某个IP供用户访问。此方式能实现机房间的负载均衡。至此,系统可做到机房级别的水平扩展,千万级到亿级的并发量都可通过增加机房来解决,系统入口处的请求并发量不再是问
题。
随着数据的丰富程度和业务的发展,检索、分析等需求越来越丰富,单单依靠数据库无法解决如此丰富的需求。
当数据库中的数据多到一定规模的时候,数据库就不适用于复杂查询了,往往只能满足普通查询的场景。对于统计报表的场景,在数据量大时不一定能跑出结果,而且在跑复杂查询时会导致其他查询变慢,对于全文检索、可变数据结构等场景,数据库天生不适用。因此需要针对特定的场景,引入合适的解决方案。
如对于海量文件的存储,可以通过分布式文件系统HDFS解决;对于KEY-VALUE类型的数据,可以通过HBase和Redis等方案解决;对于全文检索场景,可以通过搜索引擎,比如ElasticSearch解决;对于多维分析场景,可以通过Kylin或Druid等方案解决。
当然,引入更多的组件同时会提高系统的复杂度,不同的组件保存的数据需要同步,需要考虑数据一致性的问题,需要有更多的运维手段来管理这些组件等。
性能优化
1.webpack打包文件体积过大?(最终打包为一个js文件)
2.如何优化webpack构建的性能
3.移动端的性能优化
4.Vue的SPA 如何优化加载速度
5.移动端300ms延迟
6.页面的重构
所有的知识点都有详细的解答,我整理成了280页PDF《前端校招面试真题精编解析》。
开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】