分类 PHP 下的文章

独立图片服务器的必要性

我们知道,无论对于Apache还是Nginx,图片始终是最消耗系统资源的,如果将图片服务和应用服务放在同一个服务器的话,应用服务器很容易会因为图片的 高I/O负载而崩溃,因此对于有些大型网站项目,我们有必要将图片服务器和应用服务器分离。

部署独立的图片服务器(甚至是服务器集群)是大型网站图片存储解决方案中最基础的,因为有了独立的图片服务器后,我们才能对图片服务器做更有针对性的性能优化,为图片服务器设置针对性的缓存方案,减少带宽成本,提高访问速度。

从硬件角度说,图片服务器可以配置高端的硬盘,7200转的换成15000转的,而CPU只需要使用一般的CPU就可以了。

从软件角度说,可以为图片服务器配置特殊的文件系统来满足对图片的I/O请求,如淘宝 的TFS,就很好地解决了大规模小图片文件带来的I/O噩梦,同时,我们也可以采用Nginx、squid来代理图片请求,通过增加图片服务器,提高图片吞吐能力。

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

# 采用独立域名

注意,这里是指独立域名,不是子域哦,比如yahoo.com图片服务器用了yimg.com的域名,而不是用二级域名img.yahoo.com。

同一域名下浏览器的并发链接数有限制,突破浏览器链接数的限制,通常情况下浏览器的并发连接数是2到6个。

这样,我们如果给图片服务器配置独立的域名,那么在一个页面中加载图片时,就可以突破浏览器连接数的限制,理论上,增加一个独立域名,并发连接数加倍。

另外还有由于 Cookie 的原因,大部分 Web Cache都只缓存不带 Cookie 的请求,导致每次的图片请求都不能命中 Cache。而仍旧要去原始服务器获取图片,导致图片缓存意义不大。所以,还是给单独搞一个图片独立域名吧,当然,不只是图片,CSS和JavaScript文件也可以参照这个思路来搞。

# 如何进行图片上传和图片同步?

## NFS 共享方式

NFS是Network  File System(网络文件系统)。主要功能是通过网络让不同的服务器之间可以共享文件或者目录。

NFS客户端一般是应用服务器(比如web,负载均衡等),可以通过挂载的方式将NFS服务器端共享的目录挂载到NFS客户端本地的目录下。    

NFS在文件传送过程中依赖与RPC(远程过程调用)协议。NFS本身是没有提供信息传送的协议和功能的,但是能够用过网络进行图片,视频,附件等分享功能。只要用到NFS的地方都需要启动RPC服务,不论是NFS的服务端还是客户端。    

NFS和RPC的关系:可以理解为NFS是一个网络文件系统(比喻为租房的房主),而RPC是负责信息的传输(中介),客户端(相当于租房的租客)

了解 NFS 可以看我的另外一篇博文《LInux 典型应用:》

## 利用 FTP 同步

用户上传完图片后是利用 FTP 同步到各个图片服务器的,PHP、Java、Asp.net基本上都能操作 FTP。这样的话 每个图片服务器就都保存一份图片的副本,也起到了备份的作用。但是缺点是将图片ftp到服务器比较耗时,如果异步去同步的话又会有延时,不过一般的小图片 文件也还好了。

什么是动态语言静态化

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

如果页面中的的数据一直都在变化,那么不建议使用静态化。

为什么要静态化

动态脚本通常会做逻辑运算和数据查询,访问量越大,服务器压力越大。

访问量大的时候可能造成 CPU 负载过高,数据服务器压力过大,静态化可以减轻逻辑处理能力,降低数据库服务器的查询压力。

静态化的实现方式

使用模板引擎

可以使用 Smarty 的缓存机制生成静态 HTML 缓存文件。

$smarty->cache_dir = $ROOT.'/cache'; //缓存目录
$smarty->caching = true;                //是否开启缓存
$smarty->cache_lifetime = '3600';    //缓存时间 
$smarty->display(strign template [,string cache_id[, strign compile_id]]);

如果开启了缓存,Smarty 会自动的生成 HTML 缓存文件。

$smarty->clear_all_cache();            //清除所有缓存
$smarty->clear_cache('file.html');    //清除指定缓存
$smarty->clear_cache('article,html', $cache_id) //清除一同模板下的指定缓存号的缓存。

利用 OB 系列函数

  • ob_start():打开输出控制缓冲
  • ob_get_contents():返回输出缓冲区的内容
  • ob_clean():清空输出缓冲区
  • ob_end_flush():冲刷出(送出)输出缓冲区内容并且关闭缓冲
ob_start();
输出到页面的 HTML 代码
ob_get_contents();
ob_end_flush();
fopen()写入

可以使用 filectime 函数 判断文件的 inode 修改时间,判断是否过期。

<?php
$cache_name = md5(__FILE__).'html';
$cache_lifetime = 3600;

if ( filectime(__FILE__) <= filectime($cache_name) file_exists($cache_name) && filectime($cache_name) + $cache_lifetime > time()) {
    //判断 PHP 文件修改时间
    //判断是否存在缓存
    //判断是否过期
    include $cache_name;
    exit;
}
ob_start();
?>
<b> This is my Script</b>
<?php
$content = ob_get_contents();    
$ob_end_flush();
$handle = fopen($cache_name, 'w');
fwrite($handle, $content);
fclose($handle);
?>

什么是进程、线程、协程

进程

进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。

进程的三态模型

进程的三态模型:多道程序系统中,进程在处理器上交替运行,状态不断地发生变化

运行:当一个进程在处理机上运行时,则称该进程处于运行状态。处于此状态的进程的数目小于等于处理器的数目,对于单机处理系统,处于运行状态的进程只有一个。没有其他进程可以执行时(如所有进程都在阻塞状态),通常会自动执行系统的空闲进程。

就绪:当一个程序获得了除处理机意外的一切所需资源,一旦得到处理机即可运行,则称此进程出于就绪状态。就绪进程可以按多个优先级来划分队列。例如,当一个进程处于时间片用完而进入就绪状态时,排入低优先级队列;当前进程由 I/O 操作完成而进入就绪状态时,排入高优先队列。

阻塞:也称之为等待或者睡眠状态,一个进程正在等待某一事件发生(例如请求 I/O而等待 I/O 完成等)而暂时停止运行,这时即使把处理机分配给进程也无法运行,故称该进程出于阻塞状态。

进程的五态模型

进程的五态模型:对于一个实际的系统,进程的状态及其转换更为复杂分为新建态、活跃就绪/静止就绪、运行、活跃阻塞/静止阻塞、终止态,可见下图。

新建态:对应于进程刚刚被创建时没有被提交的状态,并等待系统完成创建进程的所有必要信息。

活跃就绪:是指进程在主内存并且可被调度的状态。

静止就绪(挂起就绪):是指进程被对换到辅存时的就绪状态,是不能被直接调度的状态,只有当主存中没有活跃就绪态进程,或者是挂起就绪态进程具有更高的优先级,系统将被挂起就绪态进程调回主存并转化为活跃就绪。

活跃阻塞:是指进程已在主存,一旦等待事件产生便进入活跃就绪状态

静止阻塞:进程对换到辅存时的阻塞状态,一旦等地啊的事件产生便进入静止就绪状态。

终止态:进程已经结束运行,回收除进程控制块之外的其他资源,并让其他进程从进程控制块中有关信息。

线程

由于我们用户的并发请求,为每一个请求都创建一个进程显然是行不通的,从系统资源开销方面或是响应用户请求的效率方面来看。因此系统中线程概念便被引进了。

线程,有时被称之为轻量级的进程(Lightweight Process, LWP),是程序执行流的最小单元。

线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属的一个进程的其他线程共享进程所拥有的全部资源。

一个线程可以创建和撤销另一个线程,统一进程的多个线程之间可以并发执行。

线程是程序中一个单一的顺序控制流程。进程内一个相对独立的、可调度的执行单元,是系统独立调度和分派CPU的基本单位指运行中的程序的调度单位。

在单个程序中同事运行多个线程完成不同的工作,称之为多线程。

每一个程序都只要有一个线程,若程序只有一个线程,那就是程序本身。

线程的状态:就绪阻塞运行

就绪状态:线程具备运行的所有条件,逻辑上可以运行,在等待处理机。

阻塞状态:线程在等待一个事件(如某个信号量),逻辑上不可执行。

运行状态:线程占有处理机正在运行。

协程

协程是一种用户态的轻量级线程,协程的调度完全由用户控制。协程已拥有自己的寄存器上下文和栈,协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈基本没有内核切换开销,可以不加锁访问全局变量,所以上下文的切换非常快。

如果想要深入的了解协程的实现,可以读鸟哥有关于协程的博文,里面详细介绍了PHP协程 的实现——传送门

线程与进程的区别

  1. 线程是进程内的一个执行单元,进程内至少有一个线程,它们共享进程的地址空间,而进程有自己独立的地址空间。
  2. 进程是资源分配和拥有的单元,同一个进程内的线程共享进程的资源。
  3. 线城市处理器调度的基本单位,进程不是。
  4. 两者均可并发执行
  5. 每个独立的线程有一个程序运行的入口,顺序执行序列和程序的出口,但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。

线程和协程的区别

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

什么是多进程,多线程

多进程是指同一时间里,统一计算机系统中如果允许两个或者两个以上的进程处于运行状态,就是多进程。多开一个进程,多分配一份资源,进程间通信不方便。

多线程就是把一个进程分为很多片,每一片都可以是一个独立的流程与多进程的却别是只会使用一个进程的资源,线程间可以直接通信。

同步阻塞模型

在最早的服务器端程序透视通过多进程、多线程来解决并发IO的问题。

一个请求创建一个进程,然后子进程进入循环同步阻塞地与客户端进行交互,收发处理数据。

多线程模式实现非常简单,线程可以直接向某一个客户端连接发送数据。

步骤:

  1. 创建一个 socket,绑定服务器端口(bind),监听端口(listen),在PHP中用stream_socket_server一个函数就能完成上面3个步骤,当然也可以使用更底层的sockets扩展分别实现。
  2. 进入while循环,阻塞在accept操作上,等待客户端连接进入。此时程序会进入睡眠状态,直到有新的客户端发起connect到服务器,操作系统会唤醒此进程。accept函数返回客户端连接的socket
  3. 主进程在多进程模型下通过fork(PHP: pcntl_fork)创建子进程,多线程模型下使用pthread_create(PHP: new Thread)创建子线程。下文如无特殊声明将使用进程同时表示进程/线程。
  4. 子进程创建成功后进入while循环,阻塞在recv(PHP: fread)调用上,等待客户端向服务器发送数据。收到数据后服务器程序进行处理然后使用send(PHP: fwrite)向客户端发送响应。长连接的服务会持续与客户端交互,而短连接服务一般收到响应就会close。
  5. 当客户端连接关闭时,子进程/线程退出并销毁所有资源。主进程/线程会回收掉此子进程/线程。

缺点:

  • 这种模型严重依赖进程的数量解决并发问题,一个客户端连接就需要占用一个进程,工作进程的数量有多少,并发处理能力就有多少。操作系统可以创建的进程数量是有限的。
  • 启动大量进程会带来额外的进程调度消耗。数百个进程时可能进程上下文切换调度消耗占CPU不到1%可以忽略不计,如果启动数千甚至数万个进程,消耗就会直线上升。调度消耗可能占到 CPU 的百分之几十甚至 100%。

另外有一些场景多进程模型无法解决,比如即时聊天程序(IM),一台服务器要同时维持上万甚至几十万上百万的连接(经典的C10K问题),多进程模型就力不从心了。

还有一种场景也是多进程模型的软肋。通常Web服务器启动100个进程,如果一个请求消耗100 ms,100个进程可以提供1000 QPS,这样的处理能力还是不错的。但是如果请求内要调用外网HTTP接口,像QQ、微博登录,耗时会很长,一个请求需要10s。那一个进程1秒只能处理0.1个请求,100个进程只能达到QPS,这样的处理能力就太差了。

//创建scoket监听
$scokserv = stream_scoket_server('tcp://0.0.0.0:8880', $errno, $errstr);
for ($i = 0; $i < 5; $i++)
{
    if(pcntl_fork() == 0) {
        while (true) {
            $conn = stream_scoket_accept($sockserv);
            if ($conn == false) {
                continue;
            }
            $request = fread($conn, 9000);
            $response = 'hello';
            fwrite($conn, $response);
            fclose($conn);
        }
        exit();
    } 
}

异步非阻塞模型

现在各种高并发异步IO的服务器程序都是基于 epoll 实现的。

在早期Linux就提供了select,可以在一个进程内维持1024个连接,后来加入了poll,可以维持任意数量个连接。

但是poll需要循环检测是否有事件,如果服务器当前有100 W个连接,但是某一个时间内只有一条连接向服务器发送数据,这样系统就会循环100 W次,对于CPU是一种浪费。

Linux在2.6时提供了epoll,可以在系统内维持无数个连接,而且无需轮训。

IO复用异步非阻塞程序使用经典的 Reactor 模型,Reactor 顾名思义,就是反应堆的意思,它本身不处理任何数据收发,只是可以监视一个socket句柄的事件变化。

Reactor模型:

  • Add: 添加一个 SOCKET到 Reactor
  • Set: 修改 SOCKET 对应的事件,如可读可写
  • Del: 从 Reactor 中移除
  • Callback: 事件发生后回调指定的函数

PHP 并发编程实践

PHP的Swoole扩展

PHP的异步、并行、高性能的网络通信引擎,使用纯C语言编写,提供了PHP语言的异步多线程服务器,异步TCP/UDP 网络客户端,异步 MySQL,异步 Redis,数据库连接池,AsyncTask,消息队列,毫秒定时器,异步文件读写,异步 DNS 查询。

除了异步IO的支持之外,Swoole 为 PHP 多进程的模式设计了多个并发数据结构和 IPC 通信机制,可以大大简化多进程并发编程的工作。

Swoole 2.0支持了类似Go语言的协程,可以使用完全同步的代码实现异步程序。

#### Swoole 的异步MySQL实现

$db = new swoole_mysql;
$server = array(
    'host' => '192.168.56.102',
    'port' => 3306,
    'user' => 'test',
    'password' => 'test',
    'database' => 'test',
    'charset' => 'utf8', //指定字符集
    'timeout' => 2,  // 可选:连接超时时间(非查询超时时间),默认为SW_MYSQL_CONNECT_TIMEOUT(1.0)
);

$db->connect($server, function ($db, $r) {
    if ($r === false) {
        var_dump($db->connect_errno, $db->connect_error);
        die;
    }
    $sql = 'show tables';
    $db->query($sql, function(swoole_mysql $db, $r) {
        if ($r === false)
        {
            var_dump($db->error, $db->errno);
        }
        elseif ($r === true )
        {
            var_dump($db->affected_rows, $db->insert_id);
        }
        var_dump($r);
        $db->close();
    });
});

消息队列

用户注册

场景说明:当用户注册后,需要发注册邮件和注册短信。

串行方式:将注册信息写入数据库成功以后,发送注册邮件,再发送注册短信。

并行方式:将注册写入数据库成功后,发送注册邮件的同事,发送注册短信。

消息队列方式:将注册信息写入数据库成功后,将成功信息写入队列,此时直接返回成功给用户,写入队列的时间非常短,可以忽略不计,然后异步发送邮件和短信。

解耦操作

场景说明:用户下单后,订单系统需要通知库存系统。假如库存系统无法访问,则订单减库存将失败,从而导致订单失败。此时需要进行解耦。

引入队列:

  1. 用户下单后,订单系统完成持久化处理,将消息写入消息队列,返回用户下单成功。
  2. 库存系统订阅下单的消息,采用拉/推得方式,获取下单信息,库存系统根据下单信息,进行库存操作。

流量削峰

应用场景:秒杀活动,流量瞬间激增,服务器压力大

用户发起请求,服务器接收后,先写入消息队列,假如消息队列长度超过最大值,则直接报错或提示用户。

后续程序读取消息队列,在进行处理。

日志处理

应用场景:解决大量日志的传输

日志采集程序可以将日志写入消息队列,然后通过日志处理程序的订阅消费日志。

消息通讯

应用场景: 聊天室

多个客户端订阅同一主题,进行消息发布和接收。

常见的消息队列产品

kafka、ActiveMQ、ZeroMQ, RabbitMQ、Redis等等。

接口的并发请求

curl_multi系列函数。

什么是CDN

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

在网络各处放置节点服务器所构成的在现有的互联网基础之上的一层只能虚拟网络,比如说我们现在有一个服务器的集群,现在在北京,有几台服务器在北京,在上海访问北京服务器就会比较慢,我们就可以使用 CDN 解决这样的问题,可以在香港、上海建立一个 CDN 节点,这样当我的用户在某一个节点访问我们的网站时,可以去请求香港的 CDN 节点,这样距离他比较近,CDN 已经把真实服务器的数据缓存到了 CDN 当中,相当于一个镜像。

CDN 系统能够实时地根据网络流量和各节点的连接、敷在情况以及到用户的距离和响应时间等综合信息将用户的请求重新导向离用户最近的服务节点上。

使用 CDN 的优势

本地 Cache 加速,提高企业站点(尤其含有大量图片和静态资源页面站点)的访问速度。

跨运营商的网络加速,保证不同网络的用户能得到最好的访问质量。

远程访问用户根据 DNS 负载均衡技术只能选择 Cache 服务器。

自动生成服务器的远程 Mirror(镜像)Cache 服务器,远程用户访问时从 Cache 服务器上读取数据,减少远程访问的带宽、分带网络流量、减轻原站点 WEB 服务器负载等能力。

广泛分布的 CDN 节点加上节点之间的智能冗余机制,可以有效地预防黑客入侵。

CDN 的工作原理

传统访问

用户在浏览器输入域名发起请求-->解析域名获取服务器 IP 地址-->根据 IP 地址找到对应的服务器—>服务器响应并返回。

使用 CDN 访问

用户发起请求-->只能 DND 解析(根据 IP 判断地理位置、接入网类型、选择路由最短和负载最轻的服务器)-->获得缓存服务器 IP-->把内容返回给用户(如果缓存中有)—>向源站发起请求—>将结果返回给用户—>将结果存入缓存服务器。

CDN 适用场景

  • 站点或者应用中大量静态资源的加速分发,例如:CSS,JS,图片和 HTML。
  • 大文件下载
  • 直播网站

CDN 的实现

BAT 等都有提供 CDN 服务。

可用 LVS 做4层负载均衡。

可用 Nginx,Varnish,Squid,Apache TrafficServer 做7层负载均衡和 Cache。

使用 squid 反向代理,或者 Nginx 等的反向代理

HTTP 缓存机制

缓存分类

在HTTP缓存模型中,如果请求成功会有三种情况。

  • 200 from cache
  • 304 Not Modified
  • 200 OK

200 from cache: 直接从本地缓存中获取响应,最快速,最省流量,因为根本没有向服务器发送请求I看到。

浏览器本身就有缓存机制,当我们浏览器检测到该资源在本地存在,那么就不需要向服务器发送请求,从下图中我们可以看到size没有大小显示的是 from cache,就是读取的缓存。

查看其响应头我们可以看到下图。

304 Not Modified: 协商缓存,浏览器在本地没有命中的情况下请求头中发送一定的教研数据到服务端,如果服务端数据没有改变,浏览器从本地缓存响应,返回304。

快速,发送的数据很少,只会返回一些基本的响应头信息,数据量很小,不发送实际响应体。

200 OK: 以上两种缓存全部失效,服务器返回完整响应。没有用到缓存,相对较慢,对于200 OK不能称之为缓存,因为根本没有用到缓存机制。

相关 Header

  • Pragma
  • Expires
  • Cache-Control

Pragma: HTTP 1.0 时代的遗留产物,该字段被设置为no-cache时,会告知浏览器禁用本地缓存,即每次都向服务器发送请求。

Expires:HTTP 1.0 时代用来启用本地缓存的字段,expires值对应一个形式如Thu, 31 Dec 2037 23:55:55 GMT的格林威治时间,告诉浏览器缓存实现的时刻,如果还没到该时刻,标明缓存有效,无需发送请求。

浏览器与服务器的时间无法保持一致,如果时间差比较大,就会影响缓存结果。我们可以保证服务器的时间,但是无法保证客户端的时间与服务器时间的一致。(QQ 空间就有对客户端时间进行检测,感兴趣的可以将本地时间调整一下然后去访问QQ空间)

Cache-Control:HTTP 1.1 针对 Expires 时间不一致的解决方案,运用 Cache-Control 告知浏览器缓存过期时间间隔,而不是时刻,即使具体时间不一致,也不影响缓存的管理。

在Cache-Control下我们还可以设置一些头信息:

  • no-store:禁止浏览器缓存响应
  • no-cache:不允许直接使用本地缓存,先发起请求和服务器协商,也就是协商缓存
  • max-age=delta-seconds:告知浏览器该响应本地缓存有效的最长期限,以秒为单位。

优先级: Pragma > Cache-Control > Expires

在上图中,我们可以看到上面的头信息,设置了Cache-Control和Expires。

协商缓存当浏览器没有命中本地缓存,如果本地缓存过期或者响应中生命不允许直接使用本地缓存(Pragma或者Cache-Control设置成了no-cache),那么浏览器肯定会发起服务端请求。服务端会验证数据是否修改,如果没有通知浏览器使用本地缓存。

相关的Header:

  • Last-Modified:通知浏览器资源的最后修改时间,格式如上图。
  • If-Modified-Since:这是与Last-Modified相对应的一个头信息,得到资源的最后修改时间后,会将这个信息通过If-Modified-Since提交到服务器进行检查,如果没有修改则返回304状态码。
  • ETag: HTTP 1.1 推出,文件的指纹标识符,如果文件内容修改,指纹会改变,Last-Modified只能精确到秒,而 ETag 如果发生了改变,它就会改变,它就相当于文件的标识("78437822c-6739"),也更加准确。
  • If-Node-Match:与ETag相对应的一个头信息,本地缓存失效,会携带此值去请求服务端,服务端判断该资源是否改变,如果没有改变直接使用本地缓存,返回304

通过下面的两张图,我们可以看到完整的协商缓存交互:

缓存策略的选择

适合的内容:

  • 不变的图像,如logo,图标等
  • js、css静态文件
  • 可下载的内容,媒体文件

建议使用协商缓存:

  • HTML文件
  • 经常替换的图片
  • 经常修改的js、css
  • js、css问价你的加载可以加入文件的签名来拒绝缓存 (index.css?签名、index.签名.js)

不建议缓存的内容:

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

NGINX 配置缓存策略

首先,我们使用PHP模拟NGINX的缓存。

<?php
    //1. 获取If-Modified-Since
    $since = $_SERVER['HTTP_IF_MODIFIED_SINCE'];
    //2. 设置过期时间一个小时
    $lifetime = 3600;
    //3. 如果没有过期直接返回304告诉浏览器使用本地缓存
    if (strtotime($since) + $lifetime > time()) {
        header('HTTP/1.1 304 Not Modified');
        exit;
    }
    //4. 设置最后修改时间
    header('Last-Modified:'. gmdate('D, d M Y H:i:s', time()). ' GMT');
    //5. 网页显示的内容,用于验证是否开启缓存
    echo time();

上述代码就是NGINX的缓存原理,不过NGINX判断的不是3600秒,而是文件的修改时间。

本地缓存配置

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=指定的时间;
location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$
{
    expires 30d;
}
location ~ .*\.(js|css)?$ 
{
    expires 12h;
}

当设为max时,会把Expires设置为"Thu, 31 Dec 2037 23:55:55 GMT",Cache-Control设置到10年。

协商缓存配置

ETag指令:指定签名

etag on | off 默认是on

location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$
{
    etag on;
}

Cache-Control

location ~ .*\.(js|css)?$ 
{
    add_header cache-control max-age = 3600;
}

前端代码和资源的压缩

  • 优势

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

压缩方式

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

JavaScript代码压缩

JavaScript压缩的原理一般是去掉多余的空格和回车、替换长变量名、简化一些代码的写法。

压缩工具有很多,有在线工具、有应用程序、有编辑器插件。

常用的压缩工具:

  • UglifyJs: 压缩、语法检查、美化代码、代码缩减、转化
  • YUI compressor:来自Yahoo,只有压缩功能
  • Closure Compiler:来自Google、功能和UglifyJs有些类似,压缩的方式不太一样。

CSS代码压缩

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

常用的压缩工具:

  • YUI compressor:来自Yahoo,只有压缩功能
  • CSS Compiler:压缩时候可以选择模式,也可以对一些语法进行优化

HTML代码压缩

不建议使用代码压缩,有时会破坏代码结构,可以使用Gzip压缩,当然也可以使用htmlcompressor工具,不过转换后一定要检查代码结构。

图片压缩

除了代码的压缩外,有时对图片的压缩也是很有必要的,一般情况下图片在Web系统的比重都比较大。

压缩工具

  • tinypng
  • JpegMini
  • ImageOptim

Gzip压缩

GZIP最早由Jean-loup Gailly和Mark Adler创建,用于UNⅨ系统的文件压缩。我们在Linux中经常会用到后缀为.gz的文件,它们就是GZIP格式的。现今已经成为Internet 上使用非常普遍的一种数据压缩格式,或者说一种文件格式。

HTTP协议上的GZIP编码是一种用来改进WEB应用程序性能的技术。大流量的WEB站点常常使用GZIP压缩技术来让用户感受更快的速度。这一般是指WWW服务器中安装的一个功能,当有人来访问这个服务器中的网站时,服务器中的这个功能就将网页内容压缩后传输到来访的电脑浏览器中显示出来.一般对纯文本内容可压缩到原大小的40%.这样传输就快了,效果就是你点击网址后会很快的显示出来.当然这也会增加服务器的负载. 一般服务器中都安装有这个功能模块的。

NGINX配置:

gzip on|of #是否开启gzip
gzip_buffres 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 applecation/xml #对那些文件类型使用压缩
gzip_vary on | off #是否传输gzip压缩标志

其他工具

  • 自动化构建工具Grunt