网站高并发解决方案【理论篇】

当面试官问:"网站高并发怎么做?"时,该怎么回?
在高并发下,我们(初级程序员)能做什么?

 

一:mysql方面

mysql方面,我们主要要从以下几点去考虑:

1:索引

mysql其实没有想象中的那么差,相反,mysql表数据,只要查到了索引,都不会慢,(1.5亿数据表查索引0.0几秒),所以mysql索引是个好东西,用好之后,查询效率自然很快,

所以,数据表设计,一定要考虑全面,给查询频繁使用的字段增加索引,或者组合索引

索引学习传送门https://www.cnblogs.com/zhaobingqing/p/7071331.html

 

2:查询数据缓存

配置数据,某些变更不频繁或查询频繁的数据可以通过redis,memcache,file(不推荐)等方式增加缓存,避免数据库频繁查询造成额外的数据库性能消耗

场景一:进网站的轮播图,由于变更不频繁,可以设置缓存1天,当轮播图修改更新时,更新缓存

场景二:10万个会员的聊天室,进来需要查询聊天记录,由于聊天记录变更频繁并且查询频繁,可设置缓存1-3秒,缓存失效才去取一次数据库,将大部分查询都进入缓存中查询,大大降低了数据库压力

 

3:查询逻辑优化

场景一:当你想在一个1000万的访问表,统计会员A的访问记录时,你会发现,就算给会员id增加了索引,也会很慢,因为这个涉及到了数据命中条数

mysql命中条数越多,则查询越慢

优化方案:由于是访问表,不算是重要数据,可增加一个统计表,统计每天的访问数量,当你要查会员A的访问总数,则直接去sum统计表中的数据,大大提高了性能

 

场景二:某个抽奖程序,A奖品限制每天只能出1万个,判断当天是否超出限制时,一般情况是count(1) 查出奖记录表,这样做是不正确的,正确做法:

1:增加个库存字段,每天自动更新为10000,每次抽中减一,判断是否出完只需要查看库存字段

2:增加个计数表,按每天为单位,每次抽中则字段num+1,判断是否超出限制,只需要查出当天记录的num字段比对就行

 

场景三:高并发下,会员抢10万个红包怎么做?

每天新增1万条奖品记录,并生成缓存队列(redis),每次抢完则从队列中取数据,抢完批量更新回数据库

 

场景三:额,不算是场景了,当有一个表,字段数有50,而你取数据只需要10个字段时,尽量把select * 写成 select 字段名,字段名,可以让mysql节省没必要的返回数据,从而影响效率

 

二:服务器硬盘方面

大多数人,可能不知道有iops(硬盘每秒输入输出量)这个东西存在,所以在硬盘方面的优化直接被忽略了

下图是阿里云的各硬盘比对

仙士可博客

通俗来讲,就是硬盘的每秒读取文件的数量有限,举个例子,你的程序从启动到输出include了100个文件,高效云盘的iops是3000,代表着你的程序,每秒最多只可以访问3000/100=30的并发数(只是理论数据,当大并发下,操作系统会适当的优化)

这就是laravel框架慢的原因,加载的文件太多了

 

优化方案?优化方案,在前几个月,我的圈子有讨论过,具体方案有以下几种

1:将php框架,编译成一个php文件,这样一次请求下来,只有一个文件的输出,大大的降低了硬盘的压力,其实,tp3.2就已经有了这个功能,只是大家没注意而已

http://document.thinkphp.cn/manual_3_2.html#app_compile

 

2:在swoole文档中,韩大有说https://wiki.swoole.com/wiki/page/836.html

仙士可博客

 

在linux中,/dev/shm是映射的内存路径,当框架启动时,将框架代码复制到该文件夹下面去运行(注意,内存,关机数据就会没有,只能保存固定的业务代码,不能保存业务数据)

windows也有内存盘,可以实现该操作

内存读取速度非常快,所以并不用担心磁盘I/O问题

 

三:服务器带宽方面

服务器,带宽是非常贵的,而网站的访问都离不开带宽,

下图是我的博客一次请求下来的网页大小

仙士可博客

一次请求就需要600kb,这相当于什么呢?当我服务器带宽只有1m(出口带宽128kb/s)时,某次访问进来,最少需要600/128约等于5秒的时间
相当于我服务器的秒并发量只有1/6......

何况这只是个博客,商城呢?假设商城的请求大小有1m,服务器带宽有100M(12.8m/s)的话,秒并发量最多只有12.8.....

这就是带宽方面的限制了
当然,浏览器在一次请求之后,会智能的缓存页面(js,css,图片等静态文件),这样大大的节省了服务器带宽,但是新进来的用户,或者有用户禁止了缓存页面,就得请求这么多数据了

至于优化方案,我的方案是上cdn(内容分发网络)

它的大致原理是:将域名转到他们的dns服务器,由他们进行管理域名的请求ip
例如:我的www.php20.cn,将dns转移到百度云cdn,那么所有访问www.php20.cn的请求都会被百度云cdn接管

在在某个地区(百度云cdn有多个地区的服务器,保证网站资源第一时间响应给用户)第一次请求时,由于百度云还没有缓存,将会请求正确的服务器地址(百度云cdn后台域名解析),把数据返回给用户端并缓存到百度云cdn

当有缓存之后,百度云将不再请求服务器的资源,将百度云缓存的静态数据,直接返回给用户端,这就是cdn的作用了

所以,当网站上cdn之后,所有的静态文件请求,cdn会帮助你缓存,并不消耗服务器的带宽,大大的降低了带宽的消耗,唯一需要返回的,也就是动态输出的html文件了

 

四:使用nginx服务器

可能大家都知道,高并发下,都得使用nginx服务器,这是为什么呢?

咳咳,可以看以下文章https://www.cnblogs.com/yum777/p/6244935.html

 

五:php代码逻辑

再好的架构,也会死在垃圾代码上面,上面的一系列优化好了,那就是php方面了,主要注意以下几点:

1:多使用php内置函数(内置函数写在了php c底层,无需编译,速度快)

2:判断逻辑,(当有个奖品需要判断是每周2万个,每天最多5000个时,先判断是否超过了每周限制,当真的超出了每周限制时,将节省每天判断的时间)

3:避免循环运行sql语句(需要插入/更新多条数据时,请在循环外批量插入/更新)

http://www.php20.cn/article/sw/%E6%95%B0%E6%8D%AE/105  tp5好像已经内部实现了批量更新功能

4:尽量少的查询数据库

能一条语句查出的,尽量不要多条

例如,有个订单表,有2种付款方式(payment_type=1,payment_type=2),对应1个付款金额的字段(total_money),

当你需要统计2种付款方式总计金额时,大多数人会根据payment_type=1,=2进行查询2次数据,该怎么优化呢?

可以使用mysql的判断查询:http://www.php20.cn/article/sw/%E5%88%A4%E6%96%AD/95

5:过滤掉恶意请求

通过验证码,手机短信等方法,将机器人排除在外,为了避免恶意请求,可限制每秒请求次数不得超过10(普通人哪能点击这么快),当超过时,则系统底层拒绝响应,等到下一秒才可以继续请求

 

 

进阶回答

1.cdn代理层

在高并发下,为了解决带宽问题,全站必须做前后分离操作,所有前端资源都可进行cdn代理,进行缓存静态资源,分散服务器带宽压力.  

同时,app端,小程序端等本地资源无需担心这个问题.

 

2.防火墙层

1:在大多数并发情况下,都可能存在恶意请求,例如cc,ddos攻击,通过脚本,1秒请求100次,1000次请求,我们可直接认定该请求以及请求后的客户端为恶意请求,拒绝该客户端请求.

2:由于数据网络的特殊性,可能存在多个手机网络共用一个ip的情况,理论上不能直接封停ip,而是需要根据该次请求,客户端附带的其他信息(ua标识)用于拒绝

3:同时,对已登录用户做好限流,同一个用户,理论上一次性不可能超过10次接口请求(静态资源不算)

4:防火墙其他规则可执行研究

 

3.网关层

网关层可以和防火墙层可以为同一个逻辑,通过防火墙后,进行请求调度,例如 nginx 负载均衡

网关层需要对请求进行调度,将请求通过轮询/空闲判定,权重判定等方式,把请求分配到多台服务器,进行服务器压力分担

注意:以下所有的内部通信,最好都为内网环境,降低带宽压力,以及提升响应

 

4.微服务层

微服务层是个概念性层,可要可不要,在微服务层,可对请求进行服务分散,在大压力情况下,有必要关闭一些微服务应用进行服务降级,保证主要服务的运行.

例如:在淘宝双十一时,关闭了退款通道.

例如:当商城高并发时,关闭查看历史订单功能,关闭小游戏功能,等等.

微服务层可能会出现 分布式事务 问题,有一定的难度.

 

5.进程模型优化

在传统 php-fpm 模型中,单进程作为同步阻塞模型,一个进程在同一时间只能处理一个请求,当出现io阻塞后,进程会一直被请求占用,直到io结束,如果需要提升并发,就必须增加进程数,增加进程数意味着cpu调度进程压力增大,导致cpu耗费资源加大.  

通过 swoole 的多进程多协程并发模型,一个进程可同时处理多个请求,cpu只要调度少量的进程,即可实现处理更多的请求

 

 

6.php 解释层优化

在正常php-fpm中,php运行需要经过以下几个步骤 加载php文件->逐行解释->运行  一个php项目,一次请求可能存在10-50个文件(laravel框架更多),每次请求进来,都需要重新引入文件,加载文件,导致 磁盘io负载 暴涨.

我们可以通过opcache对文件进行解释加载进内存,二次请求不会再去重新加载php文件

如果不想做opcache,也可直接将项目代码放进内存文件系统中,也可提升一点速度

通过 swoole,wokerman 常驻内存框架,也可实现一次加载永久存在的目的,提升性能

php作为服务端语言,主要瓶颈在于io,在高并发下,这些优化也是有限的.没有过于追求的必要.

 

7.服务器缓存层

理论上,一次接口请求进来,非即时性查询,高频请求,都必须先经过缓存层,通过缓存减小数据库压力.

例如 商品缓存,文章缓存等等.

注意:为了避免缓存穿透,需要对所有缓存定时更新期限,并且将失效时间进行错开,确保不会出现一瞬间所有缓存失效,导致请求全部进入数据库.

为了避免内存溢出,需要对所有缓存字段进行管理,做失效时间,定时删除垃圾数据

 

8.并发锁相关

在并发下,如果对数据准确度有一定要求的话,将涉及到并发锁功能.

在mysql中,myisam引擎虽然插入,查询性能高,但是它作为表锁,如果在大表更新情况下,将造成锁表,导致全服务器请求都将锁住等待,所以myisam不能用于大表更新等业务,只适合做日志记录.

同时,innodb支持事务,行级锁,在更新用户金额时,可使用innodb表存储用户金额,进行行级更新.

可查看: 并发锁

 

注意:innodb虽然作为行级锁,但是也需要考虑操作表的单位数量,例如用户金额,理论上只有用户自身请求,并发量并不大,就算是锁表,也不会影响到其他用户的操作.(如果你非得用脚本点1000下下单,那也是你牛逼,给你锁表1000秒也是你自找的)     

但如果是商品库存,将不能直接使用 innodb 实现行锁,原因是该数据可能会被成千上万的用户进行请求,当锁住后,其他请求都需要等待,极度影响用户体验.

 

9.异步队列压力

如果是高并发场景为瞬时并发,例如商品秒杀只有1秒,其他时间都为低流量请求情况,可通过队列转为异步处理,前端等待n秒获取异步结果
通过异步队列,可分散瞬时并发带来的压力,使得服务器不会瞬间宕机导致出错.  

注:异步队列处理缺点在于用户不能即时获取到结果,但是一般不会太久,如果超出3秒,可直接丢弃该请求,直接给用户返回失败.  

如果是不需要获取结果的,例如插入日志,发送邮件,可直接使用队列

 

10.数据库主从

为了避免数据库压力过大,可对数据库做主从环境,非主要数据全部从从数据库读取,减少主数据库压力.

可查看: 关于mysql集群主从服务器搭建

 

注意:需要注意主从同步数据延迟问题,以及主从断开后数据恢复问题

 

11.数据库分表

可查看:mysql分表详解

通过对数据库进行分表,降低单表锁表情况,分散单表查询压力.

 

 

12.数据库索引优化

通过优化数据库索引,保障查询时命中索引,减少 临时表

可查看: Mysql索引优化

 

13.尽量不要出现报错

虽然 notice 报错不会影响服务正常运行,但是一次报错,涉及到了php底层的错误拦截机制,会影响非常多的性能,所以尽量不要出现任何报错.

解决任何可能存在的代码报错情况,改用自己的方式拦截,记录日志.  

 

posted @ 2020-11-17 14:24  EC全攻略  阅读(446)  评论(0编辑  收藏  举报