高并发WEB网站优化方案
一、什么是高并发
在互联网时代,所讲的并发、高并发,通常是指并发访问,也就是在某个时间点,有多少个访问同时到来。比如,百度首页同时有1000个人访问,那么也就是并发为1000。
通常一个系统的日PV在千万以上,有可能是一个高并发系统(但有可能不算是一个高并发系统,比如有的公司不走技术路线,全靠机器堆...因为有钱任性!)
二、高并发,我们具体应该关心什么?
QPS:每秒请求或者查询的数量,在互联网领域,指的是每秒相应请求数(指HTTP请求)。
吞吐量:单位时间内处理的请求数(通常由QPS于并发数决定)。
响应时间:从请求发出到收到响应花费的时间。例如系统处理一个HTTP请求需要100ms,这个100ms就是系统的响应时间(可从浏览器的NetWork查看到)
PV:综合浏览量,即页面浏览量或者点击量,一个访客在24小时内访问的页面数量(在一个系统中访问多个页面,每个页面访问各算一个PV,如果在一个页面刷新多次,也只算一个PV)。
UV:独立访客,即一定时间范围内相同访客多次访问网站,只计算为一个独立访客(和IP类似,一个用户一个IP)。
带宽:计算带宽大小需要关注两个指标,峰值流量和页面的平均大小。
日网站带宽的计算公式: 日网站带宽 = PV / 统计时间(换算到秒)* 平均页面大小(单位KB)* 8。
比如统计时间是一天,那么一天就是24小时,24 * 60 * 60,比如一个页面10K。峰值一般是平均值的倍数,这个需要根据实际情况来定。
QPS不等于并发连接数
QPS是每秒HTTP请求数量,并发连接数是系统同时处理的请求数量。有可能一个并发连接数里有多个HTTP请求。
峰值每秒请求数(QPS) = (总PV数 * 80% ) / (6小时秒数 * 20%),意思是80%的访问量集中在20%的时间内(2/8定律,这里的6小时只是估计,比如早上中午下午各两小时)。
压力测试
为什么要做测试呢?因为对于我们的服务器,我们应该知道我们的服务器最大承受多少QPS,而我们的网站一天多少PV,那么计算出来整个峰值的QPS,那么可以根据需要来优化,并不是感觉访问量大了才优化,是需要依据的!
测试出最大能够承受的并发出,测试出最大承受的QPS值。比如我们的日QPS是200,单机峰值的QPS承受是50,那么我们至少需要4台同样配置的服务器才能正常访问。
常用的性能测试工具
ab、wrk、http_load、Web Bench、Siege、Apache JMeter
ab全程是apache benchmark,是apache官方推出的一个工具,创建多个并发访问线程,模拟多个访问者同时对一个URL地址进行访问。它的测试目标是基于URL的,因此它既可以来测试apache的负载压力,也可以测试Nginx等服务器的压力。
测试的注意事项:
测试机器与被测试机器要分开,别在同一台机器上测试,否则结果是不准确的
不要对线上的服务器做测试,否则挂了就不好了哦。
观察测试工具ab所在的机器,以及被测试的前端机器的CPU,内存,网络等都不超过最高限度的75%。
http://www.cnblogs.com/wt645631686/p/6868430.html //简单的ab压力测试方法
QPS达到极限
随着QPS的增长,每个阶段都需要根据实际情况来进行优化,优化的方案也与硬件条件、网络带宽息息相关。
如果QPS达到50,可以称之为小型网站,一般的服务器就可以应付。
如果QPS达到100,每秒达到100的话,假设关系型数据库的每秒请求在0.01秒完成,这时候数据库优化的就很好了。假设单个页面只有一个SQL查询,那么100QPS意味着1秒完成100次请求,但是此时我们并不能保证数据库查询能完成100次,所以就达到极限了。那么需要优化了,方案如:数据库层的缓存、数据库的负载均衡。
如果QPS达到800,即使我们是用百兆带宽,意味着网站出口的实际带宽是8M左右,假设每个页面只有10K,在这个并发条件下,百兆带宽已经吃完了,那么带宽极限就达到了,方案如:CDN加速、负载均衡。
如果QPS达到了1000,假设使用Memcache、Redis缓存做数据库查询,每个页面对Memcache的请求远远大于直接对DB的请求,Memcache的悲观并发数在2W左右,但是有可能在之前内网带宽已经吃光,表现出不稳定。方案如:静态HTML缓存。
如果QPS达到了2000,这个级别下,文件系统访问锁都成了灾难,方案如:做业务分离,分布式存储。
三、高并发方案的措施详解
A:流量优化
①防盗链
盗链的概念
指在自己的页面上展示一些并不在自己服务器上的内容。也就是获得他人服务器上的资源地址,绕过别人的资 源展示页面,直接在自己的页面上向最终用户提供此内容。如,小站盗用大站的图片、音乐、视频、软件等资源来减轻自己服务器的负担。
防盗链的概念
防止别人通过一些技术手段绕过本站的资源展示页面,盗用本站的资源。绕开本站资源展示页面的资源链接失效就达到了防盗链。
防盗链的工作原理
通过Referer或者签名,网站可以检测到目标页面访问的来源页面,如果是资源文件,则可以跟踪到显示它的网址页面。一旦检测到来源不是本站即进行组织或者返回指定的页面。
观察一下NetWork中Request Header中的Referer,可以根据此方法判断是不是盗链。
还比如用签名,就是在请求图片的时候,带一个参数,这个参数是彼此之前约定好的签名,服务器在显示图片的时候判断签名是否正确来判断,通常签名是很复杂的。
http://www.cnblogs.com/wt645631686/p/7285703.html //本篇文章有说明
B:前端优化
①减少HTTP请求(比如JS合并、CSS合并、图片合并。虽然文件大了点,但是减少了请求)。
性能黄金法则
只有10%-20%的最终用户响应时间花在了接收HTML文档请求上,剩下的80%-90%时间花在HTML文档所引用的所有组件上(图片、javascript、css等)进行HTTP请求。
如何改善
改善响应时间得最简单途径就是减少组件数量,并由此减少HTTP请求。
HTTP连接会产生开销
域名解析->TCP连接->发送请求->等待下载资源->下载资源->解析时间
疑问?
本身我们的DNS解析是有缓存的,再者HTTP协议1.1版本是用Keep-Alive方式进行操作,也就是不会花费TCP握手时间了。但我们HTTP本身是串行请求的,还是有时间的。
解答:
查找DNS缓存也需要时间,多个缓存就要查找多次有可能缓存被清除了。
HTTP1.1协议规定请求只能串行发送,也就是一百个请求必须依次逐个发送,签名的请求完成才能进行下一个,这里也会造成响应时间的影响。
图片地图
允许你在一个图片上关联多个URL。目标URL的选择取决于用户单击了哪个位置。就比如常见的一个大图很多零零碎碎的小图标,CSS中定位找位置
合并脚本和样式表适
使用外部的JS和CSS文件引用的方式,因为这要比直接写在页面中性能好一些。但是也会增加HTTP请求,可以合并JS和CSS文件,独立一个JS比用多个JS文件组成的页面载入要快38%
图片使用Base64编码减少页面请求数
采用Base64的编码方式将图片直接嵌入到网页中,而不是从外部载入,如<img src="data:image/gif;base64,/9j/2SDFG..." >,这样下载HTML文档的时间就会增长了。在CSS背景图中也是可以这么做的
②添加异步请求(比如不太重要的东西先不展示,用户需要的时候再放一些事件,juqery等添加异步请求获取)。
③启用浏览器缓存和文件压缩(启用浏览器缓存html文件,设置缓存时间等。将js或者图片等前端静态文件设置过期时间做浏览器缓存,下次请求就直接从浏览器输出,减少了请求。其次,图片文件压缩也减少了流量的消耗,启用Nginx的gzip压缩)。
HTTP缓存机制
a:浏览器缓存
可以降低服务器压力,降低带宽和流量。
缓存分类
HTTP缓存模型中,如果请求成功会有三种情况
200 from cache :直接从本地缓存中获取响应,最快速,最省流量,因为根本没有向服务器发送请求。
304 Not Modified :协商缓存,浏览器在本地没有命中缓存的情况下,请求头中发送一定的校验数据到服务端,如果服务端数据没有改变浏览器从本地缓存响应,返回304。也就是说本地缓存失效,会去服务端请求一下,带头信息过去让服务器端判断这个资源是不是已经过去,如果没有过期还有效的话,那么告诉浏览器还可以使用本地缓存,最终返回304,否则返回真实数据。特点:发送速度快,数据小,只返回一些基本的响应头信息,数据量小,不发送实际响应体。
200 OK:以上两种缓存全部失败,服务器返回完整响应,也就是最慢的了。
b:本地缓存
Pragma:HTTP1.0时代时遗留的产物,该字段被设置为no-cache时,会告知浏览器禁用本地缓存,即每次都向服务器发送请求。
Expires:HTTP1.0时代用来启用本地缓存的字段,expires值对应一个行如Thu,31 Dec 2037 23:55:55 GMT的格林威治时间,告诉浏览器缓存实现的时刻,如果还没到该时刻,表明缓存有效,无需发送请求。但对于Expires有一个问题,当我们去请求服务端的时候,服务端会返回Expires回来,告诉响应时间和过期时间,这个时间是服务器返回,那么这个时间就是服务器时间,再去进行判断是否过期的时候,判断是通过浏览器判断,浏览器是通过客户端时间判断,那么有可能会出现时间差,那么就会影响缓存结果。
Cache-Control:HTTP1.14针对Expires时间不一致的解决方案,运用Cache-Control告知浏览器缓存过期的时间间隔而不是时刻,即使具体时间不一致,也不影响缓存的管理。也就是Cache-Control给的是一个秒数,就说这个文件多少秒之后过期,不是时间点了,这样就没有对比时间的问题。Cache-Control可以禁止浏览器缓存响应no-store,也可以不允许直接使用本地缓存,先发起请求和服务器协商 no-cache,也可以告知浏览器该响应本地缓存有效的最长期限,以秒为单位 max-age=delta-seconds,比如设定3600秒,那么就是一小时了。
优先级
Pargma > Cache-Control > Expires ,如果同时设定,那么最高的是Pargma...依次....
哪些适合本地缓存?
不变的图像,如LOGO,图标等;js,css等静态文件
c:协商缓存
当浏览器没有命中本地缓存,如本地缓存过期或者响应中声明不允许直接使用本地缓存,那么浏览器肯定会发起服务端请求。比如Cache-Control设定no-cache,那么浏览器肯定发起服务端请求,服务端会验证数据是否修改,如果没有修改通知浏览器使用缓存。
Last-Modified:通知浏览器资源的最后修改时间。当我在页面上请求一个资源的时候,服务端会给一个相应,会相应一个Last-Modified,当然这个需要设置,相应Last-Modified表示资源的最后修改时间是多少,便于下次请求服务端的时候,带这个时间过来,服务端会判断在这个时间节点之后这个文件是否发生修改,如果没有修改会返回304,让浏览器使用本地缓存。格式Last-Modified:Mon,28 Sep 2015 08:06:43 GMTIf-Modified-Since: 得到资源的最后修改时有修改,返回304状态码。格式Last-Modified:Mon,28 Sep 201508:06:43 GMT
ETag: HTTP1.1推出,文件的指纹标识符,如果文件内容修改,指纹会改变。也就是如果文件发生了内容的修改,ETag就会改变,也就脱离了时间的约束,会更加准确,对于ETag也就是文件的标识,下次请求的时候,服务器会给响应一个ETag,告诉文件资源的标识是多少,下次请求服务端的时候,会带着值去,服务端会看这个文件是否发生改变,如果改变,那么ETag会发生改变,如果没改变就告诉浏览器使用本地缓存,返回304状态码。格式:“78437556c-6739”
If-None-Match:本地缓存失效,会携带此值去请求服务端,服务端判断该资源是否改变,如果没有改变,直接使用本地缓存,返回304。原理差不多的。
哪些适合协商缓存?
比如经常改变的,HTML文件。比如在服务端生成了一个静态HTML文件,为了减轻压力负载,但HTML文件有可能会改变,因为文件是根据数据生成的,数据可能会改变,这种文件适合做协商缓存;还比如经常改变的图片;
再比如经常修改的css、js等静态文件。
css、js文件的加载可以加入文件的签名来拒绝缓存,如index.css?签名或者index.签名.js
不建议缓存的内容
用户隐私的等敏感数据、购物车信息、个人信息等、经常改变的api数据接口
Nginx配置缓存策略
a:本地缓存配置
add_header指令:添加状态码为2XX和3XX的响应头信息
add_header name value [always];
可以设置Pragma/Expires/Cache-control,可以继承
expires指令:通知浏览器过期时长
expires time; 可以是分钟可以是小时或者天,比如30秒,就是30s,30分钟,就是30m
如果为负值时,就表示Cache-Control:no-cache;
如果为正值或者0时,就表示Cache-Control:max-age=指定的时间;
location ~ .*\.(gif|jpg|jpeg|png|bmp|swf|flv|mp4|ico)$ {
expires 30d;
access_log off;
}
location ~ .*\.(js|css)?$ {
expires 7d;
access_log off;
}
b:协商缓存配置
Etag指令:指定签名。用的少
add_header cache-control,也用的少,了解就够了。
前端代码和资源的压缩
优势
让资源文件更小,加快文件在网络中的传输,让网页更快的展现,降低带宽和流量开销。
压缩方式
js、css、图片等压缩|服务器端的Gzip压缩。
js代码的压缩
js代码压缩的原理一般是去掉多余的空格和回车、替换长变量名、简化一些代码写法等。有在线压缩
HTML代码压缩
不建议使用代码压缩,因为会破坏代码结构,可以使用Gzip压缩,当然也可以使用htmlcompressor工具,不过
转换后一定要检查代码结构。
图片压缩
对图片压缩也很有必要,一般情况下图片在Web系统的比重都比较大。
Nginx开启Gzip压缩
gzip on|off; #是否开启gzip
gzip_buffers 32 4k | 16 8k #缓冲(在内存中缓冲几块?每块多大)
gzip_comp_level [1-9] #推荐6压缩级别(级别越高,压缩的越小,越浪费CPU计算资源)
gzip_disable #正则匹配UA,什么样的Uri不进行Gzip
gzip_min_length 200 #开始压缩的最小长度
gzip_http_version 1.0|1.1 #开始压缩的http协议版本
gzip_proxied #设置请求者代理服务器,该如何缓存内容
gzip_types text/plain application/xml #对那些类型的文件用压缩,如txt,xml,html,css
gzip_vary on | off #是否传输gzip压缩标志
#Gzip Compression
gzip on;
gzip_buffers 16 8k;
gzip_comp_level 6;
gzip_http_version 1.1;
gzip_min_length 256;
gzip_proxied any;
gzip_vary on;
gzip_types
text/xml application/xml application/atom+xml application/rss+xml application/xhtml+xml
image/svg+xml
text/javascript application/javascript application/x-javascript
text/x-json application/json application/x-web-app-manifest+json
text/css text/plain text/x-component
font/opentype application/x-font-ttf application/vnd.ms-fontobject
image/x-icon;
gzip_disable "MSIE [1-6]\.(?!.*SV1)";
④CDN加速(把前端的文件,前端的资源全部放到CDN中,用户就近访问,从而提高访问速度,从一定意义也解决了流量不够用的问题)。
什么是CDN
全称是Content Delivery Network,即内容分发网络,把我们站点的内容分发到各个站点,尽可能避开互联网上有可能影响数据传输速度和稳定性的瓶颈和环节,使内容传输的更快、稳定。其实就是在网络各处防止节点服务器所构成的现有的互联网基础之上的一层只能虚拟网络。比如站点在北京,你在深圳访问就会比北京访问慢,那么在深圳建立一个节点,那么会请求深圳的节点,也就是真实服务器的一个镜像,快很多了。CDN系统能够实时地根据网络流量和各个节点的连接、负载状况以及到用户的距离和响应时间等综合信息将用户的骑牛重新导向离用户最近的服务器节点上。
使用CDN的优势
本地Cache加速,提高了企业站点的访问速度,尤其是含有大量图片和静态页面的站点。
跨运营商的网络加速,保证不同网络的用户都能得到良好的访问质量。
远程访问用户根据DNS负载均衡技术智能自动选择Cache服务器
自动生成服务器的远程镜像,远程用户访问时从Cache服务器上读取数据,减少远程访问的带宽、分担网络流量、减轻原站点WEB服务器负载等功能。
广泛分布的CDN节点加上节点之间的智能冗余机制,可以有效地预防黑客入侵。
CDN的工作原理
先了解一下传统的访问模式
用户在浏览器输入域名发起请求-->解析域名获取服务器IP地址-->根据IP地址找到对应的服务器-->服务器响应并返回数据
CDN的访问模式
用户发起请求-->只能DNS的解析(根据IP判断地址位置、接入网类型、选择路由最短和负载最轻的服务器)-->取得缓存服务器IP-->把内容返给用户(如果缓存中有)-->向源站点发起请求-->将结果返回给用户-->将结果存储到缓存服务器 。
CDN的适用场景
站点或者应用当中大量静态资源的分发,如CSS,JS等。
大文件下载
直播网站等
比较消耗带宽流量,CDN可以减少
CDN的实现方式
BAT等都有提供CDN服务,阿里云、腾讯云
可用LVS做4层负载均衡
也可以用Nginx、Apache 等做7层负载均衡和cache
使用squid或者Nginx做反向代理
⑤建立独立的图片服务器(因为本身图片服务器比较吃I/O,为了减少I/O损耗,跟WEB服务器分离开。还可以针对性的对图片服务器做优化,比如对硬盘转数提高,CPU的计算降低,做集群等)。
必要性
分担WEB服务器的I/O负载,将耗费资源的图片服务分离出来,提高服务器的性能和稳定性。
能够专门对图片服务器进行优化,为图片服务设置有针对性的缓存方案,减少带宽成本,提高访问速度,比如硬盘转数高一些;CPU的计算调低些;当然也可以做图片服务器集群,提高图片吞吐能力。
采用独立域名
原因:同一域名下浏览器的并发连接数有限制,突破浏览器连接数的限制。
由于cookie的原因,对缓存不利,大部分Web Cache都只缓存不带cookie的请求,导致每次的图片请求都不能命中cache
独立后的问题
如何进行图片上传和图片同步
NFS共享方式,上传到一台服务器进行NFS共享同步
利用FTP同步
接口方式,如七牛云
C:服务端优化
①页面静态化处理(把现有的服务端的逻辑,比如PHP,请求PHP文件的时候,把PHP的逻辑数据,要生成的要显示的HTML内容缓存成HTML代码,那么速度更快,QPS也提高,对服务器的负载压力会减少很多)。
原因
动态脚本通常会做逻辑运算和数据查询,访问量越大,服务器器压力越大。
访问量大时可能会造成CPU负载过高,数据库服务器压力过大,静态化可以减低逻辑处理压力,降低数据库服务器的查询压力
实现方式
使用Smarty缓存机制生成静态HTML缓存文件。
利用ob系列的函数。
http://www.cnblogs.com/wt645631686/p/6867923.html //简单介绍和案例
②并发处理(如果用户穿透了静态化,比如我们做了静态化,静态化有设定过期时间,不可能永远显示之前的静态数据,这样动态的意义就不存在了。对于实时性比较高的数据,那么做静态化就不合理了,那么需要穿透静态化,绕过静态化访问真实数据,那么就要做程序的并发处理,比如多线程多进程的异步处理,比如做队列的处理等等,从而提升请求响应速度,提高并发数)。
什么是进程、线程、协程
进程
是计算机中程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。也就是进程是一个执行中的程序。
进程的三态模型:多道程序系统中,进程在处理器上交替运行,状态不断发生变化。因为我们的一个程序至少开启一个进程,比如我们有三个程序,对于CPU来处理的时候,不会同时处理这三个程序,要交替处理,因为很快,所以我们感受不到是交替运行,其实是在快速切换的状态。
①运行:当一个进程在处理机上运行的时候,则称该进程处于运行状态。处于此状态的进程的数目小于等于处理器的数目,对于单处理机系统,处于运行状态的进程只有一个。在没有其他进程可以执行的时候(如果所有进程都在阻塞状态)。通常会自动执行系统的空闲进程。
②就绪:当一个进程获得了除处理机以外的所需资源,一旦得到处理机即可运行,则称此进程处于就绪状态。就绪进程可以按多个优先级来划分队列。例如,当一个进程由于时间片用完而进入就绪状态时,排入低优先级队列;当进程由I/O操作完成而进入就绪状态时,排入高优先级队列。
③阻塞:也称为等待或者睡眠状态,一个进程正在等待某一事件发生(例如请求I/O而等待I/O完成等)而暂时停止运行,这时即使把处理机分配给进程也无法运行,故称该进程处于阻塞状态。
进程的五态模型:对于一个实际的系统,进程的状态机器转换更为复杂。
分类
新建态:对应于进程刚刚被创建时没有被提交的状态,并等待系统完成创建进程的所有必要信息。
活跃就绪:是指进程在主存并且可被调度的状态。
静止就绪(挂起就绪):是指进程被对换到辅存时的就绪状态,是不能被直接调度的状态,只有当主存中没有活跃就绪进程,或者是挂起就绪进程具有更高的优先级,系统将把挂起就绪态进程调回主存并转换为活跃就绪。
活跃阻塞:是指进程已在主存,一旦等待的事件产生便进入活跃就绪状态。
静止阻塞:进程对换到辅存时的阻塞状态,一旦等待的事件产生便进入静止就绪状态。
终止态:进程已经结束,回收除进程控制块之外的其他资源,并让其他进程从进程控制块中收集有关信息。
由于用户的并发请求,为每一个请求都创建一个进程显然是行不通的,从系统资源开销方面或者是相应用户请求的效率方面来看。因此操作系统中线程的概念便被引进了。
线程
有时候被称为轻量级的进程,是程序执行流程的最小单元。
线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。
一个线程可以创建和撤销另一个线程,同一进程中的多个线程之间可以并发执行。
线程是程序中一个单一的顺序控制流程。进程内一个相对独立的、可调度的执行单元,是系统独立调度和分派CPU的基本单位指运行中的程序的调度单位。
在单个程序中同时运行多个线程完成不同的工作,称为多线程。
每一个程序都至少有一个线程,若程序只有一个线程,那就是程序本身了。
线程的状态:就绪、阻塞、运行
就绪状态:线程具备运行的所有条件,逻辑上可以运行,在等待处理机。
运行状态:线程占有处理机正在运行。
阻塞状态:线程在等待一个事件,逻辑上不可执行。
协程
协程是一种用户态的轻量级线程,协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方, 在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。协程有点儿类似轻量级的线程,但并不完全一样,协程的调度由用户控制,线程由操作系统控制。
线程与进程的区别
1:线程是进程内的一个执行单元,进程内至少有一个线程,它们共享进程的地址空间,而进程有自己独立的地址空间。
2:进程是资源分配和拥有的单元,同一个进程内的线程共享进程的资源。
3:线程是处理器调度的基本单位,但进程不是。
4:两者均可以并发执行。所谓并发执行指的是同时运行,但是要注意的是,这个并发执行交给处理机执行的时候,处理机是需要等待的,只不过处理机在执行的时候非常的快,因为CPU性能快,用户感受不到,好像感觉同时运行。
5:每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口,但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
线程跟协程的区别
1:一个线程可以多个协程,一个进程也可以单独拥有多个协程。协程其实就是程序来进程控制的,比如我们想调用一个内容,我们可以从程序层面调用另外一个程序,是用户操作的。
2:线程进程都是同步机制,而协程是异步。
3:协程能保留上一次调用时的状态,每次过程虫入时,就相当于进入上一次调用的状态。
什么是多线程、多进程
多进程
同一个时间里,同一个计算机系统中如果允许两个或者两个以上的进程处于运行状态,这就是多进程。 比如我们在计算机上边玩游戏边听音乐,就是多进程。
多开一个进程,就会多分配一份资源,进程间通讯不方便,因为他们是独立的。
多线程
线程就是把一个进程分为很多片,每一片都可以是一个独立的流程,与多进程的区别是只会使用一个进程的资源,线程间可以直接通信。
举例:
单进程单线程:一个人在一个桌子上吃饭,霸占一个桌子。
单进程多线程:多个人在同一个桌子上吃饭。
多进程单线程:多个人每个人在自己的桌子上吃饭。
同步阻塞模型
多进程
最早的服务器端程序都是通过多进程、多线程来解决并发IO的问题,一个请求创建一个进程,然后子进程进入循环同步阻塞地与客户端连接进行交互,收发处理数据。
多线程
线程当中可以直接向某一个客户端直接发送数据。
举例步骤:
①创建一个socket监听
②进入while循环,阻塞在进程accept操作上,等待客户端连接进入。
这就是一个阻塞的状态。主进程在多进程模型下通过fork创建子进程出来
③多线程模式下可以创建子进程
④子进程/线程创建成功后进入while循环,阻塞在recv调用上,等待客户端向服务器发送数据。
⑤收到数据后服务器程序进行处理然后使用send向客户端发送响应
⑥当客户端连接关闭时,子进程/线程退出并销毁所有资源。主进程/线程会回收掉此子进程/线程。
缺点:这种模型严重依赖进程数量解决并发问题。启用大量的进程会带来额外的进程调度消耗,CPU 负载很严重。
异步非阻塞模型
现在各种高并发异步IO的服务器程序都是基于epoll实现的,可以维持无限的连接,无需轮询。
IO复用异步非阻塞程序使用经典的Reactor模型,Reactor顾名思义就是反应堆的意思,它本身不处理任何数据收发。只是可以监听一个socket句柄的事件变化。
Reactor模型的四个核心操作
Add:添加一个SOCKET监听到Reactor
Set:修改SOCKET对应的事件,可设置类型,如可读可写
Del:从Reactor中移除,不再监听事件
CallBack:事件发生后调指定的函数
例子:Nginx(多线程Reactor),Swoole(多线程,多进程)
PHP并发编程实践
PHP的Swoole扩展、消息队列、接口的并发请求(curl_multi_init)
③队列处理
Kafka、ActiveMQ、ZeroMQ、RabbitMQ、Redis
D:数据库的优化
①数据库缓存(Memcache或者Redis、MongoDB缓存、Mysql自带数据查询缓存)。
什么是数据库缓存?
Mysql等一些常见的关系型数据库的数据都存储在磁盘当中吗,在高并发场景下,业务应用对MySQL产生的增删改查操作造成巨大的I/O压力,这无疑对数据库和服务器都是较大压力,为了解决此问题,缓存数据的概念应用而生。
作用:
极大地解决数据库服务器的压力、提高应用数据的响应速度
常见的缓存形式:
内存缓存(减少IO和磁盘开销)、文件缓存
使用MySQL查询缓存
优点:极大地降低了CPU的使用率
query_cacche_type
查询缓存有0、1、2三个取值。0则不适用查询缓存。1标示始终是用查询缓存。2标示按需使用查询缓存。
query_cache_type为1时表示使用查询缓存,但是也可以在查询的时候临时关闭查询缓存
SELECT SQL_NO_CACHE * FROM....WHERE,这样就可以避免使用本条语句的缓存
query_cache_type为2时表示关闭查询缓存,但同样也可以临时使用查询缓存
SELECT SQL_CACHE * FROM...WHERE....
query_cache_size
默认情况下值为0,标示查询缓存预留的内存为0,则无法使用查询缓存。
可以通过语句临时修改,当然也可以修改my.cnf 修改。
查询缓存可以看做是SQL文本和查询结果的映射,第二次查询的SQL和第一次查询的SQL完全相同才会使用缓存
SHOW STATUS LIKE 'Qcache_hits'; //查看命中缓存次数
如果表的结构或者数据发生改变了,查询缓存中的数据不再有效
清理缓存
FLUSH QUERY CACHE; //清理查询缓存内存碎片
RESET QUERY CACHE; //从查询中移出所有查询缓存
FLUSH TABLES; //关闭所有打开的表我,同时该操作将会清空查询缓存中的内容
使用Memcache、Redis缓存(两者区别)
Redis和Memcache的性能其实相差不大,在进行选择的时候不能通过性能,应该根据场景,比如进行持久化,必须Redis;比如复杂的数据结构模拟,那么还要选择Redis,因为Redis的类型多。
Redis在2.0版本后增加了自己的VM特性,突破物理内存的限制,Memache可以修改最大可用内存,采用LRU算法。
Redis依赖客户端实现分布式读写
Memcache本身没有数据冗余机制
Redis支持(快照和AOF持久化),依赖快照进行之九华,AOF增强了可靠性的同时,对性能有所影响。
Memcache本身不支持持久化,通常做缓存提高性能
Memcache在并发场景下,用cas保证一致性,Redis事务支持比较弱,只能保证事务中的每个操作连续执行
Redis可以支持多种数据类型
Redis用于数据量较小的高性能操作和运算上,Memcache用于在动态系统中国减少数据库负载,提升性能
②分库分表、分区操作(如果做了缓存,绕过了数据库缓存,量很大的话,我们需要分库分表,做拆分,垂直和水平等)。
③读写分离(把一些服务器完全分开,部分服务器进行读操作,部分做写操作)。
④负载均衡
E:WEB服务器优化
①负载均衡(用Nginx反向代理实现负载均衡,当然还有其他方法)。
②硬件优化