高并发

  • QPS每秒钟请求或者查询的数量, 在互联网领域, 指每秒响应请求数(HTTP请求)
    • 峰值每秒请求数(QPS) = (总PV数 80%) / (6小时秒数 20%)
    • 80% 的访问量集中在 20% 的时间
  • 吞吐量单位时间内处理的请求数量(通常由 QPS 与 并发数 决定)
    • QPS 是每秒 HTTP 请求数量, 并发连接数 是系统同时处理的请求数量
    • 一个并发连接数 会有多个 HTTP请求
  • 响应时间从请求发出到收到响应花费的时间
  • PV页面浏览量
  • UV独立访客, 即一定时间范围内相同访客多次访问网站, 只计算为一个独立访客
  • 带宽计算带宽大小需关注两个指标, 峰值流量(PV / 统计时间s) 和 页面的平均大小

压力测试

ab、wrk、http_load、Web Bench、Siege、Apache JMeter

abapache benchmark, 是 Apache 官方推出的工具

创建多个并发访问线程, 模拟多个访问者同时对某一 URL 地址进行访问。它的测试目标是基于 URL 的, 因此, 它既可以用来测试 Apache 的负载压力, 也可以测试 Nginx、lighthttp、Tomcat、IIS 等其它 Web 服务器的压力

模拟并发请求100, 总共请求5000次
ab -c 100 -n 5000 https://www.github.com/

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

  • QPS 达到 50
    • 小型网站, 一般的服务器就可以应付
  • QPS 达到 100
    • 假设关系型数据库的每次请求在 0.01s 完成
    • 假设单页面只有一个 SQL 查询, 那么 100QPS 意味着 1s 完成 100 次请求, 但是此时我们并不能保证数据库查询能完成 100次
    • 方案:数据库缓存层、数据库的负载均衡
  • QPS 达到 800
    • 假设我们使用百兆带宽, 意味着网站出口的实际带宽是 8M 左右
    • 假设每个页面只有 10kb, 在这个并发条件下, 百兆宽带已经吃完
    • 方案:CDN 静态加速、Nginx 负载均衡
  • QPS 达到 1000
    • 假设使用 Memcache 缓存数据库查询数据, 每个页面对 Memcache 的请求远大于直接对 DB 的请求
    • Memcache 的悲观并发数在 2w 左右, 但有可能在之前内网宽带已经吃光, QPS 在 800 左右 Memcache 就不是很稳定了
      方案:静态HTML缓存
  • QPS 达到 2000
    • 这个级别下, 文件系统访问锁都成为了灾难
    • 方案:做业务分离, 分布式存储

流量优化

防盗链

referer

  • Nginx 模块 ngx_http_referer_module, 用来阻挡来源非法的域名请求
  • Nginx 指令 valid_referers, 全局变量 $invalid_referer
    • valid_referers 值包含 none|blocked|server_names|string…
      • noneReferer 来源头部为空的情况
      • blockedReferer 来源头部不为空, 但是里面的值被代理或者防火墙删除了, 这些值都不以 http 或者 https 开头
      • server_namesReferer 来源头部包含当前的 server_names
1
2
3
4
5
6
7
8
9
10
location ~.*\.(gif|jpg|png|flv|swf|rar|zip)$
# location /images/ 针对目录防盗链
{
valid_referers none blocked daidaibo.com *.daidaibo.com;
if ($invalid_referer)
{
# return 403;
rewrite ^/ https://www.daidaibo.com/403.jpg;
}
}

加密签名

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

  • accesskey on|off 模块开关
  • accesskey_hashmethod md5|sha-1 签名加密方式
  • accesskey_arg GET参数名称
  • accesskey_signature 加密规则
1
2
3
4
5
6
7
location ~.*\.(gif|jpg|png|flv|swf|rar|zip)$
{
accesskey on;
accesskey_hashmethod md5;
accesskey_arg sign;
accesskey_signature "daidai$remote_addr";
}
1
2
$sign = md5('daidai' . $_SERVER['REMOTE_ADDR']);
echo '<img src="./logo.png?sign='. $sign .'" />';

前端优化

减少 HTTP 请求

  • CSS Sprites
  • 合并脚本和样式表
  • 小图片 Base64 编码

浏览器缓存

  1. 适合缓存的内容
    • 不变的图像, 如logo、图标
    • js、css 静态文件
    • 可下载的内容, 媒体文件
  2. 建议使用协商缓存
    • html 静态文件
    • 经常替换的图片
    • 经常修改的 js、css 文件
      • index.css?版本Version
      • index.Hash签名.js
  3. 不建议缓存的内容
    • 用户隐私等敏感数据
    • 经常改变的 api 数据接口
1
2
3
4
5
6
7
8
$since = $_SERVER['HTTP_IF_MODIFIED_SINCE'];
$lifetime = 3600;
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();

Nginx 配置缓存策略

  • add_header 指令, 添加状态码为 2xx 和 3xx 的响应头信息
    • add_header name value [always];
  • 可以设置 Pragma/Expires/Cache-Control, 可以继承
    • expires 指令, 通知浏览器过期时长expires time;
    • 为负值时表示 Cache-Control: no-cache;
    • 当为正或者0时, 就表示 Cache-Control: max-age=指定的时间
    • expires max 会把 Expires 设置为 Thu, 31 Dec 2037 23:55:55 GMT, 相当于 Cache-Control 设置到 10年

文件压缩

  1. JS
    • UglifyJs、(Yahoo)YUI Compressor、(Google)Closure Compliler
  2. CSS
    • YUI Compressor、CSS Compressor
  3. HTML
    • 不建议使用代码压缩, 有时会破坏代码结构, 可以使用 Gzip 压缩
    • 当然也可以使用 htmlcompressor 工具, 不过转换后要检查代码结构
    • html-minifier
  4. 图片
  5. Gzip 压缩
    • Nginx 配置

      1
      2
      3
      4
      5
      6
      7
      8
      9
      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/javascript image/gif # 对哪些类型的文件压缩
      gzip_vary on|off # 是否传输 gzip 压缩标志

CDN 静态加速

内容分发网络 Content Delivery Network

  1. 可用 LVS 做 4层 负载均衡
  2. 可用 Nginx, Varnish, Squid, Apache TrafficServer 做 7层 负载均衡 和 Cache
  3. 使用 Squid 反向代理, 或者 Nginx

独立图片服务器

分担 Web 服务器的 I/O 负载, 将耗费资源的图片服务分离出来, 提高服务器的性能和稳定性

  1. 采用独立域名, 二级域名不算
    同一域名下浏览器的并发连接数有限制6~8
    由于 cookie 的原因, 对缓存不利, 大部分 Web cache 都只缓存不带 cookie 的请求, 导致每次的图片请求都不能被命中
  2. 图片上传和同步
    • NFS 共享
    • FTP 同步
  3. 阿里云对象存储 OSS

前端监控

页面埋点

性能监控

首屏优化

性能优化
  1. 路由懒加载
  2. 异步组件按需加载
  3. SSR 服务端渲染 SEO

    传统的 SPA 方式过程繁多

    • 下载 html,解析,渲染
    • 下载 js,执行
    • ajax 异步加载数据
    • 重新渲染页面

    而 SSR 则只有一步

    • 下载 html,解析,渲染
  4. App 预取
  5. 分页加载
  6. 图片懒加载
体验优化

骨架屏 loading

一个页面很慢?

加载慢
  • 性能指标

    1. First Paint(FP) 首次绘制像素
    2. First Contentful Paint(FCP) 首次绘制来自 DOM 的内容
    3. First Meaningful Paint(FMP) 主要内容 👎 不好衡量
    4. DomContentLoaded(DCL) DOMContentLoaded
    5. Largest Contentful Paint(LCP) 可视区域中最大的内容元素呈现到屏幕上的时间,用以估算页面的主要内容对用户可见时间 👍
    6. Load(L) window.onload
  • 性能分析工具

    1. Chrome devtools Performance
    2. Lighthouse
      • lighthouse https://www.github.com/ --view --preset=desktop
  • 性能统计 持续跟进

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // console.table(performance.timing)
    let timing = window.performance && window.performance.timing

    // let navigation = window.performance && window.performance.navigation

    // DNS解析
    let dns = timing.domainLookupEnd - timing.domainLookupStart
    // 网络耗时
    let network = timing.responseEnd - timing.navigationStart
    // 渲染处理
    let processing = (timing.domComplete || timing.domLoading) - timing.domLoading
    // 可交互
    let active = timing.domInteractive - timing.navigationStart

运行慢、渲染慢

内存泄漏、节流防抖、重绘回流、SSR

错误监控

  • 即时运行错误
    1. try…catch
    2. window.onerror
  • 资源加载错误
    1. object.onerror
    2. performance.getEntries()
      • performance.getEntriesByType(‘navigation’)
      • 根据已经成功加载好的资源和代码中本需要加载的资源对比
    3. Error 事件捕获
      • window.addEventListener(‘error’, (e) => {}, true)
  • 跨域的 JS 运行错误
    • 可以捕获到, 但是没有权限拿到具体的错误信息
    • 在 script 标签中增加 crossorigin 属性
    • 后端设置 js 资源响应头 Access-Control-Allow-Origin: *
  • Promise 错误
    • window.onunhandledrejection

利用 Image 对象上报错误 (new Image()).src 兼容性好 可跨域

服务端优化

页面静态化

  1. 使用模板引擎: ejs smarty jsp
  2. 利用 ob 系列的函数
    • ob_start() 打开输出控制缓冲
    • ob_get_contents() 返回输出缓冲区内容
    • ob_clean() 清空输出缓冲区
    • ob_end_flush() 冲刷出(送出)输出缓冲区内容并关闭缓冲
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$cache_name = md5(__FILE__) . '.html';
$cache_lifetime = 3600;

if (filectime(__FILE__) < filectime($cache_name) && file_exists($cache_name) && filectime($cache_name) + $cache_lifetime > time()) {
include $cache_name;
exit;
}
ob_start();

...逻辑输出...

$content = ob_get_contents();
ob_end_flush();

$handle = fopen($cache_name, 'w');
fwrite($handle, $content);
fclose($handle);

并发处理、防雪崩

  • 一个线程可以有多个协程, 一个进程也可以单独拥有多个协程
  • 线程进程都是同步机制, 而协程则是异步
  • 协程能保留上一次调用时的状态, 每次过程重入时, 就相当于进入上一次调用的状态
  1. 同步阻塞

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    $sockserver = stream_socket_server('tcp://0.0.0.0:8000', $errno, $errstr);
    for ($i = 0; $i < 5; $i++) {
    if (pcntl_fork() == 0) {
    while (true) {
    $connect = stream_socket_accept($sockserver);
    if ($connect == false) {
    continue;
    }
    $request = fread($connect, 9000);
    $response = 'hello world';
    fwrite($connect, $response);
    fclose($connect);
    }
    exit();
    }
    }
  2. 异步非阻塞

    • 现在各种高并发异步 IO 的服务器程序都是基于 epoll 实现的Nginx Golang Nodejs
    • Nginx多线程 Reactor
    • Swoole多线程 Reactor + 多进程 Worker
  3. PHP 并发编程

    • Swoole 扩展
    • 消息队列Kafka ActiveMQ ZeroMQ RabbitMQ Redis
      1. 用户注册
        • 用户注册后, 需要发送注册邮件和短信
      2. 应用解耦
        • 用户下单后, 订单系统完成持久化处理, 将消息写入消息队列, 返回用户订单下单成功
        • 订阅下单的消息, 采用拉/推的方式, 获取下单信息, 库存系统根据下单信息, 进行库存操作
      3. 流量削峰
        • 秒杀活动, 流量瞬时激增, 服务器压力大
        • 用户发起请求, 服务器接收后, 先写入消息队列; 假如消息队列长度超过最大值, 则直接报错或提示用户
      4. 日志处理
        • 解决大量日志的传输
        • 日志采集程序将程序写入消息队列, 然后通过日志处理程序的订阅来消费日志
      5. 消息通讯
        • 聊天室, 多个客户端订阅同一主题, 进行消息发布和接收
    • 接口的并发处理curl_multi_init()

数据库优化

数据库缓存层优化

  1. MySQL 查询缓存
    • 启用 MySQL 查询缓存, 极大地降低 CPU 使用率
      1. query_cache_type 0不使用查询缓存 1始终使用查询缓存 2按需使用查询缓存
      2. query_cache_type 为 1 时, 也可关闭查询缓存
        select SQL_NO_CACHE * from my_table where condition;
      3. query_cache_type 为 2 时, 也可使用查询缓存
        select SQL_CACHE * from my_table where condition;
      4. query_cache_size 默认为 0, 表示查询缓存预留的内存为 0, 无法使用查询缓存
        set global query_cache_size = 134217728;
    • 查询缓存可以看做是 SQL文本 和 查询结果 的映射
      • 第二次查询的 SQL 和第一次查询的 SQL 完全相同, 则会使用缓存
    • 查看查询缓存命中次数show status like 'Qcache_hits';
    • 表的结构或数据发生改变时, 查询缓存中的数据不再有效
    • 清理查询缓存内存碎片flush query cache;
    • 从查询缓存中移出所有查询reset query cache;
    • 关闭所有打开的表, 清空查询缓存flush tables;
  2. Memcache 查询缓存
  3. Redis 查询缓存
    • Redis 在 2.0 版本后增加了自己的 VM 特性, 突破物理内存的限制; Memcache 可以修改最大可用内存, 采用 LRU 算法
    • Redis 依赖客户端来实现分布式读写
    • Memcache 本身没有数据冗余机制
    • Redis 支持快照, AOF, 依赖快照进行持久化, AOF 增强了可靠性的同时, 对性能有所影响
    • Memcache 不支持持久化, 通常做缓存, 提升性能
    • Memcache 在并发场景下, 用 cas 保证一致性; Redis 事务支持比较弱, 只能保证事务中的每个操作连续执行
    • Redis 支持丰富的数据类型
    • Redis 用于数据量较小的高性能操作和运算上
    • Memcache 用于在动态系统中减少数据库负载, 提升性能; 适合做缓存, 提高性能

MySQL数据层优化

  • 数据类型优化
  • 索引优化
  • SQL 语句优化
  • 存储引擎优化
  • 表结构优化
    • 分库分表、分区设计
  • 数据库服务器架构
    • 日志
      1. 主从复制
      2. 读写分离
      3. 双主热备
    • 负载均衡
      1. LVS
      2. MyCat 数据库中间件

负载均衡 分发请求

  1. Nginx 反向代理
    • 七层模型, 基于 URL 等应用层信息的负载均衡
    • 功能强大, 性能卓越, 运行稳定
    • 配置简单灵活
    • 能够自动剔除工作不正常的后端服务器
    • 上传文件使用异步模式
    • 支持多种分配策略, 可以分配权重, 分配方式灵活
      • 内置策略: IP Hash、加权轮询
      • 扩展策略: fair 策略、通用 hash、一致性 hash
  2. 四层模型
    通过报文中的目标地址和端口, 再加上负载均衡设备设置的服务器选择方式, 决定最终选择的内部服务器
  • 软件: LVS
    NAT DR IP-TUNNELING
  • 硬件: F5

正向代理 CDN 翻墙
隐藏了真实的请求客户端 代理的对象是客户端

反向代理 devServe 负载均衡
隐藏了真实的服务端 代理的对象是服务端