高并发

【高并发解决方案】php

1. 了解 PV、UV、QPS
2. 优化方案:防盗链、减小HTTP请求、浏览器缓存、CDN、数据库缓存、MySQL读写分离、分区分库分表、LVS负载均衡
 
QPS:每秒钟请求或者查询的数量,在互联网领域,指每秒响应请求数(指HTTP请求);
吞吐量:单位时间内处理的请求数量(一般由QPS与并发数决定);
响应时间:从请求发出到收到响应花费的时间;
PV:综合浏览量(Page View),即页面浏览量或者点击量,一个访客在 24 小时内访问的页面数量。同一我的浏览你的网站同一页面,只记做一次PV;
UV:独立访客(UniQue Visitor),即必定时间范围内相同访客屡次访问网站,只计算为1个独立访客;
 
【QPS】
QPS(Query Per Second),QPS 实际上是衡量吞吐量(Throughput)的一个经常使用指标,
就是说服务器在一秒的时间内处理了多少个请求 —— 咱们一般是指 HTTP请求,显然数字越大表明服务器的负荷越高、处理能力越强。
并发用户数 是指系统能够同时承载的正常使用系统功能的用户的数量。
 
性能测试工具ab的用法:例如模拟并发请求 100次,总共请求 5000次
ab -c 100 -n 5000 待测试网站:
-c 表示 并发数,这儿是100; -n表示 总共请求次数,这儿是5000)
 
QPS达到极限,该怎么办?
数据库缓存层、数据库的负载均衡。 CDN加速、负载均衡。 静态HTML缓存。 作业务分离,分布式存储。
 
高并发解决方案案例
(1)流量优化: 防盗链处理。
(2)前端优化: 减小HTTP请求、 添加异步请求、 启用浏览器缓存和文件压缩、 CDN 加速、 创建独立图片服务器
(3)服务端优化: 页面静态化、 并发处理、 队列处理
(4)数据库优化: 使 用缓存(memcache)、 分库分表分区、 读写分离、 负载均衡
(5)Web 服务器优化: 负载均衡(Nginx 反向代理实现负载均衡;七层/四层(LVS)软件实现负载均衡。)
 
【服务器负载太高的解决方案】
执行top -c命令,找到cpu最高的进程的id
top命令查询服务器负载达到2.0-5之间,nginx的cpu使用率达到104%
问题分析过程:
(1)df -h 命令,查看磁盘是不是否超出正常范围
(2)free 命令,查看内存使用率是否超出正常范围
问题引出:如何肯定nginx的100%的cpu使用率到底问题在哪?
ps -ef | grep nginx 查询出nginx的进程PID(eg:8209)
ps -aux | grep nginx 查询出该进程是哪一个用户启动的(即便ROOT用户可能也导出线程快照失败)
su root 切换到进程启动用户
 
【防盗链】
防盗链的工做原理:
一、经过 Referer 或者 签名,网站能够检测目标网页访问的来源网页,若是是资源文件,则能够跟踪到显示它的网页地址;
二、一旦检测到来源表示本站即进行阻止或者返回指定的页面;
三、经过计算签名的方式,判断请求是否合法,若是合法则显示,不然返回错误信息。
防盗链的实现方法
一、Referer
Nginx 模块 ngx_http_referer_module 用于阻挡来源非法的域名请求;
Nginx 指令 valid_referers,全局变量 $invalid_referer
none :“Referer” 来源头部为空的状况;
blocked:“Referer” 来源头部不为空,可是里面的值被代理或者防火墙删除了;
referer防盗链的实现方法:打开配置文件nginx.conf
#对文件防盗链
location ~.*\.(gif|jpg|png|flv|swf|rar|zip)$
{
valid_referers none blocked imooc.com *. imooc.com;
if ($invalid_referer) {
# return 403;
}
}
#对目录防盗链
location /images/
{
…… 同上
}
传统防盗链遇到的问题:经过伪造Referer来实现盗链,可使用加密签名解决该问题。
 
二、加密签名
使用第三方模块 HttpAccessKeyModule 实现 Nginx防盗链;
accesskey on | off 模块开关;
accesskey_hashmethod md5 | sha-1 签名加密方式;
accesskey_arg GET参数名称;
accesskey_signature 加密规则(在PHP与Nginx中签名的加密规则要一致,这样才能经过,从而进行访问,不然没法进行访问);
加密签名防盗链的实现方法:
location ~.*\.(gif|jpg|png|flv|swf|rar|zip)$
{
accesskey on;
accesskey_hashmethod md5;
accesskey_arg "key";
accesskey_signature "mypass$remote_addr"; // $remote_addr 客户端IP
}
PHP中用以下代码实现:
// 客户端IP 进行md5 加密
$sign = mg5('jason', $_SERVER['REMOTE_ADDR']);
echo '<img src="./logo_new.png?sign='.$sign.'">';
 
HTTP链接产生的开销:域名解析——TCP链接——发送请求——等待——下载资源——解析时间
减小HTTP请求的方式: 图片地图、 合并脚本和样式表
 
【缓存】
HTTP缓存模型中,若是请求成功会有三种状况:
  • 200 from cache:本地缓存
  • 304 Not Modified:协商缓存,请求头中发送必定的校验数据到服务端,若是服务端数据没有改变,浏览器从本地缓存响应,返回 304
【特色:快速,发送的数据不多,只返回一些基本的响应头信息,数据量很小,不发送实际响应体】;
  • 200 OK:以上两种缓存全都失败,服务器返回完整响应。没有用到缓存,相对最慢。
 
本地缓存:
浏览器认为本地缓存可使用,不会去请求服务端【它是最快的】。 相关Header:
  • Pragma:HTTP1.0时代的遗留产物,该字段被设置为 no-cache 时,会告知浏览器禁用本地缓存,即每次都向服务器发送请求。
  • Expires:HTTP1.0时代用来启用本地缓存的字段,expires 值对应一个格林威治时间,告诉浏览器缓存实现的时刻,若是还没到该时刻,标明缓存有效,无需发送请求。
Cache-Control:HTTP1.1针对 Expires 时间不一致的解决方案,Cache-Control 给的是一个秒数,即该文件多少秒后过时。
Cache-Control 相关设置:
no-store:禁止浏览器缓存响应
no-cache:不容许直接使用本地缓存,先发起请求和服务器协商
max-age=delta-seconds:告知浏览器该响应本地缓存有效的最长气象,以秒为单位
若是三个被同时设定,则优先级以下: Pragma > Cache-Control > Expires
 
协商缓存:
当浏览器没有命中本地缓存,如本地缓存过时或者响应中声明不容许直接使用本地缓存,那么浏览器确定会发起服务端请求。
服务端会验证数据是否被修改,若是没有则通知浏览器使用本地缓存。 相关Header:
  • Last-Modified:通知浏览器资源的最后修改时间。将这个信息经过 If-Modified-Since 提交到服务器作检查,若是没有修改,返回 304 状态码。
  • ETag:HTTP1.1推出,文件的指纹标识符,若是文件内容修改,指纹会改变。例如: ETag:“78437822c-6739”
If-None-Match:本地缓存失效,会携带此值去请求服务端,服务端判断该资源是否改变,若是没有改变,直接使用本地缓存,返回 304。例如: If-None-Match:“78437822c-6739
ETag on | off; //默认是 on;
关闭 Etag;也能够去设置 add_header
location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$
{
etag off;
add_header cache-control max-age=3600 ;
}
Nginx中配置expires【经常使用】
location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$
{
expires 30d; //图片缓存30天
}
location ~ .*\.(js|css)?$
{
expires 12h; //js 和 css 缓存 12小时
}
 
缓存策略的选择:
1)适合缓存的内容:不变的图像,如 Logo,图标等;js,css 静态文件;可下载的内容,媒体文件
2)建议使用协商缓存:HTML文件;常常替换的图片;常常修改的 js,css 文件;
若是不想使用缓存,则能够加入签名: index.css?签名 或者: index.签名.js
3)不建议缓存的内容
  • 用户隐私等敏感数据(如:购物车信息,用户我的信息等隐私信息);
  • 常常改变的api数据接口(若是作了缓存,数据返回就不许确了)。
 
PHP配置缓存策略:
$since = $_SERVER['HTTP_IF_MODIFIED_SINCE']; // 获取头
$lifetime = 3600; // 假设该文件 3600s 后过时
// 判断该文件是否过时
if (strtotime($since) + $lifetime > time()) { // 未过时
// 通知浏览器使用本地缓存
header('HTTP/1.1 304 Not Modified');
exit;
}
header('Last-Modified:'. gmdate('D, d M Y H:i:s', time()). ' GMT');
echo time();
 
前端代码和资源的压缩:
优点:让资源文件更小,加快文件在网络中的传输,让网页更快的展示,下降带宽和流量开销。
压缩方式:
(1)JS、CSS、图片、HTML代码的压缩(能够将里面的空白符,变量,进行语法的合并);
(2)Gzip压缩(在服务器开启 Nginx 中的 Gzip压缩)。Nginx配置:
gzip on|off; # 是否开启 gzip
gzip_buffers 32 4K| 16 8K # 缓冲(在内存中缓冲几块?每块多大)
gzip_types text/plain application/xml # 对那些类型的文件用压缩 如:txt,xml,html,css
 
【CDN加速】
一、CDN:即内容分发网络。CDN系统可以实时地根据网络流量和各节点的链接、负载情况以及到用户的距离和响应时间等综合信息,将用户的请求从新导向离用户最近的服务器节点上。
二、使用CDN的优点:a.本地Cache加速,提升了企业站点的访问速度; b.跨运营商的网络加速;c.减小原站点WEB服务器负载等。
 
三、CDN的适用场景:大量静态资源的加速分发,例如:CSS、JS、图片和HTML;大文件下载;直播网站等(节省带宽、流量)。
 
 
【动态语言静态化】
静态化的实现方式
(1)使用模板引擎,例如 Smarty
(2)利用ob系统的函数
ob_start(); 打开输出控制缓冲;
ob_get_contents(); 返回输出缓冲区内容;
ob_clean(); 清空输出缓冲区;
ob_end_flush(); 冲刷出(送出)输出缓冲区内容并关闭缓冲;
 
【动态语言的并发处理】
【关于进程和线程】
进程:正在运行中的程序。线程:进程中的一条执行路径。
一个线程只能属于一个进程,而一个进程能够有多个线程,但至少有一个线程。
不只进程之间能够并发执行,同一个进程的多个线程之间也可并发执行
 
关于进程、线程、协程:
进程是一个"执行中的程序";
进程的三态模型: 运行、 就绪、 堵塞
进程的五态模型: 新建态、 终止态、 活跃就绪、 静止就绪(挂起就绪)、 活跃堵塞、 静止堵塞。
因为用户的并发请求,为每个请求都建立一个进程显然是行不通的,从系统资源开销方面或者是响应用户请求的效率方面来看,所以就须要线程。
 
线程:有时被称为轻量级进程(LWP:Light Weight Process),是程序执行流的最小单元;
线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程本身不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其余线程共享进程所拥有的所有资源。
一个线程能够建立和撤销另外一个线程,同一进程中多个线程之间能够并发执行;
线程是程序中一个单一的顺序控制流程,进程内一个相对独立的,可调度的执行单元,是系统独立调度和分派CPU的基本单位,指运行中的程序的调度单位。在单个程序中同时运行多个线程彻底不一样的工做,称为 多线程
每个程序都至少拥有一个线程,如果程序只有一个线程,那就是程序自己;
线程的状态: 就绪状态运行状态阻塞状态。
 
协程:是一种用户态的轻量级线程, 协程的调度彻底由用户控制(线程是由操做系统控制的)
 
线程与进程的区别:
  • 线程是进程内的一个执行单元,进程内至少有一个线程,他们共拥享进程的地址空间,而进程有本身独立的地址空间;
  • 进程是资源分配和拥有的单位,同一个进程内的线程共享进程的资源
  • 线程是处理器调度的基本单位,而进程不是;
  • 两者都可并发执行;
  • 每一个独立的线程有一个程序运行的入口,顺序执行序列和程序的出口,可是线程不可以独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制;
 
线程和协程的区别:
  • 一个线程能够多个协程,一个进程也能够单独拥有多个协程;
  • 线程进程都是同步机制,而协程是异步;
  • 协程能保留上一次调用时的状态每次过程重入时,就想当于进入上一次调用的状态;
 
什么是多进程,什么是多线程?
多进程:同一时间里,同一个计算机系统中若是容许两个或者两个以上的进程处于运行状态,这就是多进程;
多线程:把一个进程分红不少片,每一片均可以是一个独立的流程;与多进程的区别是只会使用一个进程的资源,线程之间能够通讯;
[举例说明]
单进程单线程:一我的在一个桌子上吃饭
单进程多线程:多我的在同一个桌子上吃饭
多进程单线程:多我的每一个人在本身的桌子上吃饭
 
同步堵塞:
多进程:最先的服务器端程序都是经过多进程、多线程来解决并发IO的问题,一个请求建立一个进程,而后子进程进入循环同步堵塞地与客户端链接进行交互,收发处理数据。
多线程:用多线程模式实现很是简单,线程中能够直接向某一个客户端链接发送数据。
缺点:这种模式最大的问题是,进程/线程建立和销毁的开销很大,严重依赖进程的数量解决并发问题。
 
PHP代码实例:
<?php
//PHP多进程和多线程的处理
//建立socket监听
$socketserv = stream_socket_server(' tcp://0.0.0.0:8000', $errno, $errstr);
//建立5个子进程
for ($i = 0; $i < 5; $i++) {
//使用pcntl_fork()建立进程,会返回pid,若是pid==0,则表示主进程
if (pcntl_fork() == 0) {
//循环监听
while (true) {
$conn = stream_socket_accept($socketserv);
//若是监听失败,则从新去监听
if(!$conn){
continue;
}
//读取流信息,读取的大小 是9000
$request = fread($conn, 9000);
//写入响应
$response = 'hello';
fwrite($conn, $response);
//关闭流
fclose($conn);
}
//建立完全部的子进程,而后退出
exit(0);
}
}
运行 php stream_socket.php,使用 ps -ef 查看进程,会看到多出了以下的5个进程:
 
异步非阻塞:
  • Epoll是Linux内核为处理大批量句柄而做了改进的poll。 要使用epoll只须要这三个系统调用:epoll_create(2), epoll_ctl(2), epoll_wait(2)。它是在2.5.44内核中被引进的(epoll(4) is a new API introduced in Linux kernel 2.5.44),在2.6内核中获得普遍应用。
  • 如今各类高并发异步IO的服务器程序都是基于 epoll实现的。
  • IO复用异步非阻塞程序使用经典的 Reactor模型,Reactor顾名思义就是 反应堆的意思,它自己不处理任何数据收发。只是能够监视一个socket句柄的事件变化。
Reactor 有4个核心的操做:
  • add 添加socket监听到reactor
  • set 修改事件监听,能够设置监听的类型,如可读、可写
  • del 从reactor中移除,再也不监听事件
  • callback 就是事件发生后对应的处理逻辑,通常在add/set时制定。
(C语言用函数指针实现,JS能够用匿名函数,PHP能够用匿名函数、对象方法数组、字符串函数名)
 
Reactor只是一个事件发生器,实际对socket句柄的操做,如 connect/accept、send/recv、close是在callback中完成的。
Reactor模型还能够与多进程、多线程结合起来用,既实现异步非阻塞IO,又利用到多核。
目前流行的异步服务器程序都是这样的方式:如
  • Nginx:多进程Reactor
  • Nginx+Lua:多进程Reactor+协程
  • Golang:单线程Reactor+多线程协程
  • Swoole:多线程Reactor+多进程Worker
 
PHP并发编程实践:
1.PHP的Swoole扩展:
  • PHP的异步、并行、高性能网络通讯引擎,Swoole 使用纯 C 语言编写, 提供了 PHP 语言的异步多线程服务器,异步 TCP/UDP 网络客户端,异步 MySQL,异步 Redis,数据库链接池,AsyncTask,消息队列,毫秒定时器,异步文件读写,异步DNS查询。
  • 除了异步 IO 的支持以外,Swoole 为 PHP 多进程的模式设计了多个并发数据结构和IPC通讯机制,能够大大简化多进程并发编程的工做。其中包括了 并发原子计数器,并发 HashTable,Channel,Lock,进程间通讯IPC等丰富的功能特性
  • Swoole2.0 支持了相似 Go 语言的协程,能够 使用彻底同步的代码实现异步程序。PHP 代码无需额外增长任何关键词,底层自动进行协程调度,实现异步。
2.消息队列
消息队列的应用场景:
(1)注册后,短信和邮件发送。
场景说明:用户注册后,须要发注册邮件和注册短信。
消息队列方式:将注册信息写入数据库成功后,将成功信息写入队列,此时直接返回成功给用户,写入队列的时间很是短,能够忽略不计,而后异步发送邮件和短信
(2)应用解耦。
场景说明:用户下单后,订单系统须要通知库存系统。
用户下单后,订单系统完成持久化处理,将消息写入消息队列,返回用户订单下单成功。
订阅下单的消息,采用拉/推的方式,获取下单信息,库存系统根据下单信息,进行库存操做。
(3)流量削锋。
应用场景:秒杀活动,流量瞬间激增,服务器压力大。
用户发送请求,服务器接受后,先写入消息队列。假如消息队列长度超过最大值,则直接报错或提示用户。
这样作能够控制请求量、缓解高流量。
(4)日志处理。
应用场景:解决大量日志的传输。
日志采集程序将程序写入消息队列,而后经过日志处理程序的订阅消费日志。
(5)消息通信。
应用场景:聊天室。
多个客户端订阅同一主题,进行消息发布和接收。
常见消息队列产品: Kafka、ActiveMQ、ZeroMQ、RabbitMQ、Redis
3.接口的并发请求: curl_multi_init
 
 
 
【数据库缓存层的优化】
 
【MySQL数据库层的优化】
 
 
【Web服务器的负载均衡】
七层负载均衡的实现 (Nginx)
基于URL等应用层信息的负载均衡,通常使用Nginx来实现
Nginx的proxy是它一个很强大的功能,实现了7层负载均衡
功能强大、性能卓越、运行稳定
配置简单灵活
可以自动剔除工做不正常的后端服务器
上传文件使用异步模式
支持多种分配策略,能够分配权重,分配方式灵活
 
1.Nginx负载均衡:
内置策略:IP Hash、加权轮询
扩展策略:fair策略、通用hash、一致性hash
(1)加权轮询策略
首先将请求都分给高权重的机器,直到该机器的权值降到了比其余机器低,才开始将请求分给下一个高权重的机器。
当全部后端机器都down掉时,Nginx会当即将全部机器的标志位清成初始状态,以免形成全部的机器都处在timeout的状态
(2)IP Hash 策略
Nginx内置的另外一个负载均衡的策略,流程和轮询很相似,只有其中的算法和具体的策略有些变化
IP Hash 算法是一种变相的轮询算法
(3)fair策略
根据后端服务器的响应事件判断负载状况,从中选出负载最轻的机器进行分流
(4)通用Hash、一致性Hash策略
通用hash比较简单,能够以Nginx内置的变量为key进行hash,一致性hash采用Nginx内置的一致性hash环,支持memcache
 
2.Nginx配置:
http{
upstream cluster{
server srv1;
server srv2;
server srv3;
}
server {
listen 80;
location /{
proxy_pass http://cluster;
}
}
}
 
四层负载均衡的实现 (LVS/硬件设备)
LVS实现服务器集群负载均衡有三种方式: NAT、DR、ip-TUN。
硬件设备:经过报文中的目标地址和端口,再加上负载均衡设备设置的服务器选择方式,决定最终选择的内部服务器
 
【负载均衡】
MySQL分库分表:垂直分表(列数太多)、水平分表(数据太多)
网站加速技术:Squid代理缓存技术、页面静态化缓存、Memcache、Sphinx搜索加速
Squid反向缓存(动静分离):
静态数据:静态页面、图片、CSS文件、JS文件。
动态数据:从数据库中取出来的数据
 
sphinx能够结合MySQL、PostgreSQL作全文索引。
Sphinx单一索引最大可包含1亿条记录,在1千万条记录状况下的查询速度为 0.x 秒(毫秒级)。
Sphinx建立索引的速度:建立100万条记录的索引只需3~4分钟,建立1000万条记录的索引能够在50分钟内完成,而只包含最新10万条记录的增量索引重建一次只需几十秒。
 
网站服务监控:Mrtg网站监控、Postfix邮件服务器、Shell邮件报警、Cacti网站监控、Cacti邮件报警
网站服务、流量监控:Apache web服务监控、mysql数据库监控、磁盘空间监控
 
Lvs经常使用的三种负载均衡模式:
(1)Lvs nat模式 (网络地址转换技术)
(2)Lvs ip-tun模式 (IP隧道技术)
(3)Lvs dr模式 (直接路由技术)
相关文章
相关标签/搜索