Fork me on GitHub

从一道百度面试题说起

前言

7月27日我收到百度的面试邀约(8月1日),兴奋之余,我也做了5天的面试准备。从下午两点开始一直面试到五点半,长达三个半小时的面试也让我知道百度确实是一家重视技术的公司。其中有一道面试题尤其让我印象深刻,因为它分别是一面和二面的“压轴题”,可以说这道面试题自己回答的好与否直接决定着自己能否进入下一轮面试,今天我主要就从这道面试题开始,一步一步讲解其中涉及到的知识点。这次的面经(四面)会在下一篇文章总结后分享给大家。

面试真题

真题

php如何解决网站大流量与高并发的问题(一面最后一题)

变体

网站高并发的优化方法,从浏览器到服务器中间经过所有组件的你能想到的优化点(二面最后一题)

考点分析

  • 高并发架构相关概念
  • 高并发解决方案(优化方案)

说明

由于涉及到的知识点太多,我这里先给出我当时回答的总的结构,之后会对每个分点进行更为详细的分析,如果你有更好的答案,或者有一些遗漏的点还请指出。

高并发架构相关概念

并发(百度百科的定义)

并发,在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一处理机上运行,但任意时刻点上都只有一个程序在处理机上运行

web开发中说的高并发指的是什么?

上面的定义明显不是我们通常所言的并发,在互联网时代,所讲的并发、高并发,通常是指并发访问。也就是在某个时间点,有多少个访问同时到来,通常如果一个系统的日PV在干万以上,有可能是一个高并发的系统。

高并发的问题,我们具体该关心什么?

QPS

每秒钟请求或者査询的数量,在互联网领域,指毎秒响应请求数(指HTTP请求);一个页面中可能有多个 http 请求

吞吐量

单位时间内处理的请求数量(通常由QPS与并发数决定)

响应时间

从请求发出到收到响应花费的时间。例如系统处理一个HTTP请求需要100ms,这个100ms就是系统的响应时间

PV

综合浏览量( Pageview),即页面浏览量或者点击量

UV
独立访客( UniqueVisitor),即一定时间范围内相同访客多次访问网站,只计算为1个独立访客

带宽

计算带宽大小需关注两个指标,峰值流量和页面的平均大小

  • 日网站带宽=PV/统计时间(换算到秒)*平均页面大小(单位KB)*8

常用的压测工具

ab、wrk、 http_load、 Web bench、 Siege、 Apache Jmeter

ab 概念

全称是 apache benchmark,是 apache官方推出的工具刨建多个并发访问线程,模拟多个访问者同时对某一URL地址进行访问。它的测试目标是基于URL的,因此,它既可以用来测试apache的负载压力,也可以测试 nginx、 lighthttp、 tomcat、IS等其它Web服务器的压力

ab的使用

模拟并发请求1000次,总共请求50000次
ab -c 1000 -n 50000 www.test.com

注意事项

  • 测试机器与被测试机器分开
  • 不要对线上服务做压力测试
  • 观察测试工具ab所在机器,以及被测试的前端机的cPU,内存,网络等都不超过最高限度的75% (top 命令)

高并发解决方案(优化方案)

流量层优化

  • 防盗链处理

前端层优化

  • 减少http请求
  • 添加异步请求
  • 启用浏览器缓存和文件压缩
  • CDN加速
  • 建立独立图片服务器

服务器层优化

  • 页面静态化
  • 并发处理

数据库层优化

  • 数据库缓存(Memache, Redis)
  • 分库分表(水平、垂直切分)、分区操作
  • 读写分离
  • 负载均衡

web服务器架构优化

  • 负载均衡

流量层优化(防盗链处理)

如果我们发现我们的网站主页的流量很大(服务器负荷很重)但是网站的 PV、UV 却很小,这时候我们就需要考虑我们的网站是不是被盗链了。

盗链概念

盗链是指在自己的页面上展示一些并不在自己服务器上的内容获得他人服务器上的资源地址,绕过别人的资源展示页面,直接在自己的页面上向最终用户提供此内容,常见的是小站盗用大站的图片、音乐、视频、软件等资源,通过盗链的方法可以减轻自己服务器的负担,因为真实的空间和流量均是来自别人的服务器

防盗链概念

防止别人通过一些技术手段绕过本站的资源展示页面,盗用本站的资源,让绕开本站资源展示页面的资源链接失效,可以大大减轻服务器及带宽的压力

工作原理

通过 Referer或者签名,网站可以检测目标网页访问的来源网页,如果是资源文件,则可以跟踪到显示它的网页地址。一旦检测到来源不是本站即进行阻止或者返回指定的页面

Referer 方式防盗链

Nginx模块 ngx_http_referer_module用于阻挡来源非法的域名请求

配置

1
2
3
4
5
6
7
location ~ .*\.(gifljpglpnglflvlswfrarlzips)${
valid_referers none blocked test.com * test.com:
if($invalid_referer){
#return 403:
rewrite ^/ http://www.test.com/403.jpg;
}
}

valid_referers none I blocked I server_names,string

解释

  • none: “Referer” 来源头部为空的情况
  • blocked: “Referer” 来源头部不为空,但是里面的值被代理或者防火墙删除了,这些值都不以htp:/或者https://开头.
  • server names: “Referer” 来源头部包含当前的 server names

加密签名防盗链

Referer 这种方式虽然可以防止一下低中级的防盗链,但是更高级的盗链往往可以伪造 referer,从而绕过 Referer 检测,更安全的方式是可以采用加密签名解决

使用第三方模块HttpaccesskeyModule实现 Nginx防盗链

nginx配置

1
2
3
4
5
6
7
8
9
10
11
12
accesskey on off模块开关
accesskey_hashmethod md5 | sha-1签名加密方式
accesskey_arg GET参数名称
accesskey signature加密规则
location ~ .*\.(gifljpglpnglflvlswfrarlzips)${
accesskey on;
accesskey_hashmethod md5;
accesskey_arg sign;
accesskey_signature"yourPrefix$remote_addr; # 你的前缀码和客户端 ip
}

php代码段

1
2
$sign= md5('yourPrefix'. $_SERVER['remote_addr'];
echo '<img src=”./youimage.png?sign=. $sign.'">';

前端层优化

减少http请求

性能黄金法则

只有10%–20%的最终用户响应时间花在接收请求的HTML文档上,剩下的80%–90%时间花在HTML文档所引用的所有组件(图片,script,css,flash等等)进行的HTTP请求上

如何改善:

改善响应时间的最简单途径就是减少组件数量,并由此减少HTTP请求的数量

减少HTTP请求的方式

图片地图

图片地址允许你在一个图片上关联多个URL,目标URL的选择取决于用户单击了图片上的哪个未知

image

  • 将五张图片合并为一张图片,然后以位置信息定位超链接,把五个HTTP请求减少为一个,可以保证设计的完整性和功能的齐全性
  • 使用标签

CSS精灵(CSS Sprites)

Sprites中文翻译为CSS精灵,通过使用合并图片,通过指定css的background-image 和 background-position来显示元素

合并脚本和样式表

  1. 使用外部的js和css文件引用的方式,因为这要比直接写在页面中性能更好一点
  2. 独立的一个js比用多个js文件组成的页面载入要快38%
  3. 把多个脚本合并为一个脚本,把多个样式表合并为一个样式表

图片使用Base64编码减少页面请求数

采用Base64的编码方式将图片直接嵌入到网页中,而不是从外部载入

添加异步请求

Ajax可以实现动态不刷新(局部刷新),就是能在不更新整个页面的前提下维护数据。这使得Web应用程序更为迅捷地回应用户动作,并避免了在网络上发送那些没有改变过的信息。

优势

  • 通过异步模式,提升了用户体验
  • 优化了浏览器和服务器之间的传输,减少不必要的数据往返,减少了带宽占用
  • Ajax引擎在客户端运行,承担了一部分本来由服务器承担的工作,从而减少了大用户量下的服务器负载。

启用浏览器缓存和文件压缩

缓存分类

HTTP缓存模型中,如果请求成功会有三种请款

  1. 200 from cache:直接从本地缓存中获取响应,最快速,最省流量,因为根本没有向服务器发送请求
  2. 304 Not Modified:协商缓存,浏览器再本地没有命中的情况下请求头中发送一定的校验数据到服务端,如果服务端数据没有改变浏览器从本地缓存响应,返回304,快速,发送的数据很少。只返回一些基本的响应头信息,数据量很小,不发送实际响应体
  3. 200 OK:以上两种缓存全部失败,服务器返回完整响应,没有用到缓存,相对最慢

本地缓存

浏览器认为本地缓存可以使用,不会去请求服务端

相关Header

  • Expires:expires值对应一个形如Thu,32 Dec 2037 23:55:55 GMT的格林威治时间,告诉浏览器缓存实现的时刻,如果还没到该时刻,标明缓存有效,无需发送请求(绝对时间)
  • Cache-Control(设置缓存秒数):运用Cache-Control告知浏览器缓存过期的时间间隔不是时刻(时间间隔)
    • no-store:禁止浏览器缓存响应
    • no-cache:不允许直接使用本地缓存,先发起请求和服务器协商
    • max-age=delta-seconds:告知浏览器该响应本地缓存有效的最长期限,以秒为单位

协商缓存

当浏览器没有命中本地缓存,如本地缓存过期或者响应中声明不允许直接使用本地缓存,那么浏览器肯定会发起服务端请求,服务端会验证数据是否修改,如果没有通知浏览器使用本地缓存

相关Header

  1. Last-Modified:通知浏览器资源的最后修改时间
  2. If-Modified-Since:得到资源的最后修改时间后,会将这个信息通过If-Modified-Sinc提交到服务器做检查,如果没有修改,返回304状态码
  3. Etag:HTTP1.1推出,文件的指纹标识符,如果文件内容修改,指纹会改变
  4. If-None-Match:本地缓存失效,会携带此值去请求服务端,服务端判断该资源是否改变,如果没有改变,直接使用本地缓存,返回304

适合缓存的内容

  1. 不变的图像,如logo,图标等
  2. js、CSS静态文件
  3. 可下载的内容,媒体文件

建议使用协商缓存

  1. HTML文件
  2. 经常替换的图片
  3. 经常修改的js、css文件
  4. js、css文件的加载可以加入文件的签名来拒绝缓存
  5. index.css?签名

不建议缓存的内容

  1. 用户隐私等敏感数据
  2. 经常改变的api数据接口

前端代码和资源的压缩

优势

让资源文件更小,加快文件再网络中的传输,让网页更快的展现,降低带宽和流量开销

压缩方式

JS、CSS、图片、HTML代码的压缩、Gzip压缩

JavaScript代码压缩

  • JavaScript压缩的原理一般是去掉多余的空格和回车、替换长变量名、简化一些代码写法等
  • JavaScript代码压缩工具很多,有在线工具,有应用程序,有编辑器插件

CSS代码压缩

  • 原理跟JavaScript压缩原理类似,同样是去除空白符、注释并且优化一些CSS语义规则等

使用CDN加速

CDN的全称是Content Delivery Network, 即内容分发网络,尽可能避开互联网上有可能影响数据传输速度和稳定性的瓶颈和环节,使内容传输的更快,更稳定

  • 在网络各处放置节点服务器所构成的在现有的互联网基础之上的一层智能虚拟网络
  • CDN系统能够实时地根据网络流量和各节点的连接、负载状况以及到用户的距离和响应实践等综合信息将用户的请求重新导向离用户最近的服务器节点上。

使用CDN的优势

  • 本地Cache加速, 提高企业站点(尤其含有大量图片和静态页面站点)的访问速度
  • 跨运营商的网络加速, 保证不同网络的用户都得到良好的访问质量
  • 远程访问用户根据DNS负载均衡技术智能自动选择Cache服务器
  • 自动生成服务器的远程Mirror(镜像)cache服务器,远程用户访问时从cache服务器上读取数据,减少远程访问的带宽、分担网络流量、减轻原站点web服务器负载等功能。

建立独立的图片服务器

好处

  • 分担Web服务器的I/O负载将耗费资源的图片服务分离出来,提高服务器的性能和稳定性
  • 能够专门对图片服务器进行优化-为图片服务设置有针对性的缓存方案,减少带宽成本,提高访问速度
  • 提高网站的可扩展性-通过增加图片服务器,提高图片吞吐能力

服务器层优化

页面静态化(动态语言静态化)

将现有 PHP、python 等动态语言的逻辑代码生成为静态 HTML 文件,用户访问动态脚本重定向到静态 HTML 文件的过程

使用场景

对实时性要求不高的页面

好处

  • 动态脚本通常会做逻辑计算和数据查询,访问量越大,服务器压力越大
  • 访问量大时可能会造成CPU负载过高,数据库服务器压力过大

php的并发处理

PHP的 Swoole扩展

PHP的异步、并行、高性能网络通信引擎,使用纯C语言编写,提供了PHP语言的异步多线程服务器,异步TcP/UDP网络客户端,异步 MYSQL,异步 Redis,数据库连接池,Asynctask,消息队列,毫秒定时器,异步文件读写,异步DNS查询,除了异步Io的支持之外, Swoole为PHP多进程的模式设计了多个并发数据结构和IPC通信机制,可以大大简化多进程并发编程的工作。

消息队列

使用场景

用户注册

  • 场景说明
    用户注册后,需要发注册邮件和注册短信
  • 串行方式
    将注册信息写入数据库成功后,发送注册邮件,再发送注册短信
  • 并行方式
    将注册信息写入数据库成功后,发送注册郎件的同时发送注册短信
  • 消息队列方式
    将注册信息写入数据库成功后,将成功信息写入队列,此时直接返回成功给用户,写入队列的时间非常短,可以忽略不计,然后异步发送邮件和短信

日志处理

  • 应用场景
    解决大量日志的传输,日志采集程序将程序写入消息队列,然后通过日志处理程序的订阅消费日志

常见消息队列产品

Kafka、 Activemq、 Zeros、 Rabbitmq、 Redis等

数据库层优化

数据库缓存(Memache, Redis)

Mysql 等一些常见的关系型数据库的数据都存储在磁盘当中,在高并发场景下,业务应用对 MYSQL 产生的增、删、改、查的操作造成巨大的 I/O 开销和查询压力,这无疑对数据库和服务器都是一种巨大的压力,为了解决此类问题,缓存数据的概念应运而生。

优点

  • 极大地解决数据库服务器的压力
  • 提高应用数据的响应速度

使用 Memcache缓存查询数据

对于大型站点,如果没有中间缓存层,当流量打入数据库层时,即便有之前的几层为我们挡住一部分流量,但是在大并发的情况下,还是会有大量请求涌入数据库层,这样对于数据库服务器的压力冲击很大,响应速度也会下降,因此添加中间缓存层很有必要。

工作原理

Memcache 是一个高性能的分布式的内存对象缓存系统,通过在内存里维护一个统一的巨大的 hash 表,它能够用来存储各种格式的数据,包括图像、视频、文件以及数据库检索的结果等。简单的说就是将数据调用到内存,然后从内存中读取,从而大大提高读取速度

使用 Redis绶存查询数据

Memcache的区别

  • Redis支持(快照、AOF),依赖快照进行持久化,aof增强了可靠性的同时,对性能有所影响
  • Memcache不支持持久化,通常做缓存,提升性能;
  • Redis支持多种类的数据类型
  • Redis用于数据量较小的高性能操作和运算上
  • Memcache用于在动态系统中减少数据库负载,提升性能;适合做缓存,提高性能

数据表数据类型优化

  • tinyint (0-255) smallint , bigint (考虑空间的问题,考虑范围的问题)
  • char,vachar
  • enum 特定、固定的分类可以使用enUm存储,效率更快
  • IP地址的存储 //用 php 的 ip2long('192.168.1.38'); //3232235814

索引优化

  • 索引不是越多越好,在合适的字段上创建合适的索引
  • 复合索引的前缀原则
  • lke查询%的问题(% 在前如:%name 则索引失败)
  • 全表扫描优化(mysql自动识别)
  • or条件索引使用情况(or后的索引不会使用)
  • 字符串类型索引失效的问题(如字符串类型的字段必须要加引号查询)

SQL语句的优化

使用慢查询日志找到需要优化的sql语句,一般会再用explain分析。

  • 优化查询过程中的数据访问

    • 使用 Limit
    • 返回列不用*
  • 优化特定类型的查询语句

    • 优化关联查询
    • 优化子查询
    • 优化 Group by和 distinct

数据库服务器架构的优化

  • 分库分表
    • 水平切分
    • 垂直切分
  • 读写分离
  • 负载均衡
    • 通过LVS的三种基本模式实现负载均衡
    • My Cat数据库中间件实现负载均衡

web服务器架构优化

七层负载均衡的实现

  • 基于URL等应用层信息的负载均衡
  • Nginx的 proxy是它一个很强大的功能,实现了7层负载均衡

nginx的简单配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Nginx配置
http{
upstream test_cluster {
server 121.41.68.2:8001 weight=11;// 加权中
server 121.41.69.2:8002 weight=10;
#server 121.41.68.2:8003;
#server 121.41.68.9;
}
server {
listen 80;
location / {
proxy_pass http: //test_cluster;
}
}
}

四层负载均衡的实现

  • 通过报文中的目标地址和端口,再加上负载均衡设备设置的服务器选择方式,决定最终选择的内部服务器

  • LVS实现服务器集群负载均衡有三种方式,NAT,DR和TUN

参考资料

-------------本文结束 感谢您的阅读-------------
坚持原创技术分享,您的支持将鼓励我继续创作!