标签归档:http

Nginx location 匹配顺序整理

Nginx环境

a. 查看当前系统cat /etc/redhat-release

[root@nginx /]# cat /etc/redhat-release

CentOS release 6.7 (Final)

[root@nginx /]#

b. 查看系统内核uname –r

[root@nginx /]# uname -r

2.6.32-573.el6.x86_64

[root@nginx /]#

c. 安装的Nginx版本,/appliation/nginx/sbin/nginx -V

[root@nginx sbin]# ./nginx -V

nginx version: nginx/1.6.2

built by gcc 4.4.7 20120313 (Red Hat 4.4.7-16) (GCC)

TLS SNI support enabled

configure arguments: –user=nginx –group=nginx –prefix=/application/nginx1.6.2 –with-http_stub_status_module –with-http_ssl_module

[root@nginx sbin]#

location模块

Nginx location

location 指令的作用是根据用户请求的URI来执行不同的应用,URI就是根据用户请求到的网址URL进行匹配,匹配成功了进行相关的操作。

location语法

下面是官网的语法结构:

Syntax:    location [ = | ~ | ~* | ^~ ] uri { … }

location @name { … }

Default:   —

Context:   server, location

官网解释翻译和理解

下面会结合官网原文进行解释,以下英文部分均从官网摘抄:

http://nginx.org/en/docs/http/ngx_http_core_module.html#location

(翻译的不好勿喷)

Sets configuration depending on a request URI.

根据请求的URI进行配置

URI 变量是待匹配的请求字符串,

A location can either be defined by a prefix string, or by a regular expression.

Regular expressions are specified with the preceding “~*” modifier (for case-insensitive matching), or the “~” modifier (for case-sensitive matching)

一个location可以用prefix string(前缀字符串)定义,也可以通过regular expression(正则表达式来定义)

通俗的说也就是:我们可以通过使用不同的前缀,表达不同的含义,对于不同的前缀可以分为两大类:普通location和正则location

符号:”~”表示uri包含正则,并且区分大小写

符号:“~*”表示uri包含正则,但不区分大小写

注意:如果你的uri用正则,则你的正则前面必须添加~或者~*,之前我在这里存在误区,以为可以不加~或者~*

To find location matching a given request, nginx first checks locations defined using the prefix strings (prefix locations). Among them, the location with the longest matching prefix is selected and remembered. Then regular expressions are checked, in the order of their appearance in the configuration file. The search of regular expressions terminates on the first match, and the corresponding configuration is used. If no match with a regular expression is found then the configuration of the prefix location remembered earlier is used.

Nginx服务器会首先会检查多个location中是否有普通的uri匹配,如果有多个匹配,会先记住匹配度最高的那个。然后再检查正则匹配,这里切记正则匹配是有顺序的,从上到下依次匹配,一旦匹配成功,则结束检查,并就会使用这个location块处理此请求。如果正则匹配全部失败,就会使用刚才记录普通uri匹配度最高的那个location块处理此请求。

If the longest matching prefix location has the “^~” modifier then regular expressions are not checked.

当普通匹配的最长前缀匹配有符号“^~”的时候,就不会在匹配正则

直接使用当前匹配的这个location块处理此请求

Also, using the “=” modifier it is possible to define an exact match of URI and location. If an exact match is found, the search terminates. For example, if a “/” request happens frequently, defining “location = /” will speed up the processing of these requests, as search terminates right after the first comparison. Such a location cannot obviously contain nested locations.

使用符号“=”修饰符可以定义一个精确匹配的URI和位置,如果找到了一个精确的匹配,则搜索终止,例如,如果一个”/”请求频繁发生,定义“location =/”将加快这些请求的处理,一旦精确匹配只有就结束,这样的location显然不能包含嵌套location

这里我们说一下location / {} 和location =/ {}的区别:

“location / {}”是普通的最大前缀匹配,任何的uri肯定是以“/”开头,所以location / {} 可以说是默认匹配,当其他都不匹配了,则匹配默认匹配

根据上述官网内容进行总结

a. ”=”用于普通uri前,要求精确匹配,如果匹配成功,则停止搜索并用当前location处理此请求

b. ”~” 表示uri包含正则,并且区分大小写

c. “~*”表示uri包含正则,但不区分大小写

d. ”^~”表示在普通uri前要求Nginx服务器找到普通uri匹配度最高的那个location后,立即处理此请求,并不再进行正则匹配

e. ”^~”和“=”都可以阻止继续匹配正则location两者的区别:“^~”依然遵守最大前缀原则,然后“=”是需要严格匹配

关于location网上的一些误解

location 的匹配顺序是“先匹配正则,再匹配普通”

这是一个错误的结论,从上面官网的文章中我们可以知道:

先匹配普通uri,然后记住匹配度最高的那个(官网原话:To find location matching a given request, nginx first checks locations defined using the prefix strings (prefix locations). Among them, the location with the longest matching prefix is selected and remembered.)然后匹配正则,如果正则匹配则结束查找,如果正则不匹配,则匹配之前普通匹配中匹配度最高的那个将执行该请求(官网原话:Then regular expressions are checked, in the order of their appearance in the configuration file. The search of regular expressions terminates on the first match, and the corresponding configuration is used. If no match with a regular expression is found then the configuration of the prefix location remembered earlier is used.)

所以:location 的匹配顺序是“先匹配正则,再匹配普通” 这句话肯定是错误的,况且这里并没有包含”^~”和“=”

location 的执行逻辑跟 location 的编辑顺序无关。

这也是一种错误的理解,我们根据上述内容可以知道:

如果是普通uri 匹配,这个时候是没有顺序的,但是正则匹配则是有顺序的,是从上到下依次匹配,一旦有匹配成功,则停止后面的匹配。

那么顺序到底是怎么匹配呢?

我画了一个location匹配的逻辑图便于理解匹配的顺序规则

通过实验来验证出结果

对www.conf配置如下:

[root@nginx extra]# cat www.conf

    server {

        listen       80;

        server_name  www.zhaofan.com;

        access_log      logs/access_www.log;

        root    html/www;

        location / {

            return 401;

        }

        location = / {

            return 402;

        }

        location  /documents/ {

            return 403;

        }

        location  ^~ /images/ {

            return 404;

        }

        location ~* \.(gif|jpg|jpeg)$ {

            return 500;

        }

    }

[root@nginx extra]#

注意:

        location ~* \.(gif|jpg|jpeg)$ {

            return 500;

这个部分$前面不能有空格,否则会提示如下错误:

[root@nginx extra]# ../../sbin/nginx -s reload

nginx: [emerg] invalid location modifier “~*\.(gif|jpg|jpeg)” in /application/nginx1.6.2/conf/extra/www.conf:19

如果$后面没有空格,则会提示如下错误:

[root@nginx extra]# ../../sbin/nginx -s reload

nginx: [emerg] directive “location” has no opening “{” in /application/nginx1.6.2/conf/extra/www.conf:23

这些都是细节问题,一定要注意

实验一:登录nginx网站,我这里的直接打开:http://192.168.8.105/

可以看出这里是精确匹配

        location = / {

            return 402;

        }

实验二:打开http://192.168.8.105/aaa/

这里可以看出因为都不匹配,所以最后匹配了location / {}

        location / {

            return 401;

        }

实验三:打开http://192.168.8.105/1.gif

这里可以看出是匹配了正则

        location ~* \.(gif|jpg|jpeg)$ {

            return 500;

        }

实验四:打开http://192.168.8.105/aaa/1.gif

这里依然是匹配正则

        location ~* \.(gif|jpg|jpeg)$ {

            return 500;

        }

实验五:打开http://192.168.8.105/images/1.gif

        location / {

            return 401;

        }

        location = / {

            return 402;

        }

        location  /document/ {

            return 403;

        }

        location  ^~ /images/ {

            return 404;

        }

        location ~* \.(gif|jpg|jpeg)$ {

            return 500;

        }

这里通过配置把实验三和实验四的对比就可以看出来因为普通匹配里有“^~”,并且匹配到了images,所以这个就是不进行正则匹配

        location  ^~ /images/ {

            return 404;

        }

实验六:“^~”遵守最大前缀原则

配置如下:

        location / {

            return 401;

        }

        location = / {

            return 402;

        }

        location  = /document/ {

            return 403;

        }

        location  ^~ /images/ {

            return 404;

        }

        location   /images/1/ {

            return 501;

        }

        location ~* \.(gif|jpg|jpeg)$ {

            return 500;

        }

还是关注标红的地方,这个时候我们登陆:http://192.168.1.19/images/

结果如下:

从这里可以看出匹配了:

        location  ^~ /images/ {

            return 404;

        }

但是如果我们登陆:http://192.168.1.19/images/1/

结果如下;

这里匹配了:

        location   /images/1/ {

            return 501;

        }

从这里我们可以看出“^~”遵守最大匹配原则。

实验七:当最长匹配和精确匹配相同时

配置如下:

        location / {

            return 401;

        }

        location = / {

            return 402;

        }

        location  = /document/ {

            return 403;

        }

        location  ^~ /images/ {

            return 404;

        }

        location   /images/1/ {

            return 501;

        }

        location  =  /images/1/ {

            return 502;

        }

        location ~* \.(gif|jpg|jpeg)$ {

            return 500;

        }

登陆:http://192.168.1.19/images/1/

结果如下:

但是如果这个时候登陆:http://192.168.1.19/images/1/aaa/

结果如下:

从这里我们可以看出当精确匹配和最长匹配相同时,匹配的是精确匹配。

不在过多的做实验,根据上面的逻辑图应该可以解决碰到的问题了所有的努力都值得期许,每一份梦想都应该灌溉!

原文:https://www.cnblogs.com/zhaof/p/5945576.html

swoole最新的发行版安装

和任何开源项目一样, Swoole总是在最新的发行版提供最可靠的稳定性和最强的功能, 请尽量保证你使用的是最新版本

1. 直接使用Swoole官方的二进制包 (初学者 + 开发环境)

访问我们官网的下载页面

编译需求

  • Linux, OS X 系统 或 CygWin, WSL
  • PHP 7.0.0 或以上版本 (版本越高性能越好)
  • GCC 4.8 及以上

2. 使用PHP官方的PECL工具安装 (初学者)

pecl install swoole

3. 从源码编译安装 (推荐)

非内核开发研究之用途, 请下载发布版本的源码编译

cd swoole-src && \
phpize && \
./configure && \
make && sudo make install

启用扩展

编译安装到系统成功后, 需要在php.ini中加入一行extension=swoole.so来启用Swoole扩展

额外编译参数

使用例子: ./configure --enable-openssl --enable-sockets

  • --enable-openssl 或 --with-openssl-dir=DIR
  • --enable-sockets
  • --enable-http2
  • --enable-mysqlnd (需要 mysqlnd, 只是为了支持mysql->escape方法)

升级

⚠️ 如果你要从源码升级, 别忘记在源码目录执行 make clean

  1. pecl upgrade swoole
  2. git pull && cd swoole-src && make clean && make && sudo make install
  3. 如果你改变了PHP版本, 请重新执行 phpize clean && phpize后重新编译

Http性能压测工具wrk

用过了很多压测工具,却一直没找到中意的那款。最近试了wrk感觉不错,命令及结果很类似ab,写下这份使用指南给自己备忘用,如果能帮到你,那也很好。

安装

wrk支持大多数类UNIX系统,不支持windows。需要操作系统支持LuaJIT和OpenSSL,不过不用担心,大多数类Unix系统都支持。安装wrk非常简单,只要从github上下载wrk源码,在项目路径下执行make命令即可。

git clone https://github.com/wg/wrk make

make之后,会在项目路径下生成可执行文件wrk,随后就可以用其进行HTTP压测了。可以把这个可执行文件拷贝到某个已在path中的路径,比如/usr/local/bin,这样就可以在任何路径直接使用wrk了。

默认情况下wrk会使用自带的LuaJIT和OpenSSL,如果你想使用系统已安装的版本,可以使用WITH_LUAJIT和WITH_OPENSSL这两个选项来指定它们的路径。比如:

make WITH_LUAJIT=/usr WITH_OPENSSL=/usr

基本使用

  1. 命令行敲下wrk,可以看到使用帮助
Usage: wrk <options> <url>                            
  Options:                                            
    -c, --connections <N>  Connections to keep open  -d, --duration    <T>  Duration of test  -t, --threads     <N>  Number of threads to use  -s, --script      <S>  Load Lua script file  -H, --header      <H>  Add header to request  --latency          Print latency statistics  --timeout     <T>  Socket/request timeout  -v, --version          Print version details  Numeric arguments may include a SI unit (1k, 1M, 1G) Time arguments may include a time unit (2s, 2m, 2h)

简单翻成中文:

使用方法: wrk <选项> <被测HTTP服务的URL> Options:                                            
    -c, --connections <N> 跟服务器建立并保持的TCP连接数量  
    -d, --duration <T> 压测时间           
    -t, --threads <N> 使用多少个线程进行压测   
                                                      
    -s, --script <S> 指定Lua脚本路径       
    -H, --header <H> 为每一个HTTP请求添加HTTP头      
        --latency          在压测结束后,打印延迟统计信息   
        --timeout <T> 超时时间     
    -v, --version          打印正在使用的wrk的详细版本信息 <N>代表数字参数,支持国际单位 (1k, 1M, 1G) <T>代表时间参数,支持时间单位 (2s, 2m, 2h)
  1. 看下版本
wrk -v 输出: wrk 4.0.2 [epoll] Copyright (C) 2012 Will Glozer

看到是4.0.2版本的wrk,使用了epoll。这意味着我们可以用少量的线程来跟被测服务创建大量连接,进行压测。

  1. 做一次简单压测,分析下结果
wrk -t8 -c200 -d30s --latency "http://www.bing.com" 输出:
Running 30s test @ http://www.bing.com 8 threads and 200 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency 46.67ms 215.38ms 1.67s 95.59% Req/Sec 7.91k 1.15k 10.26k 70.77% Latency Distribution 50%    2.93ms 75%    3.78ms 90%    4.73ms 99%    1.35s  1790465 requests in 30.01s, 684.08MB read
Requests/sec: 59658.29 Transfer/sec: 22.79MB

以上使用8个线程200个连接,对bing首页进行了30秒的压测,并要求在压测结果中输出响应延迟信息。以下对压测结果进行简单注释:

Running 30s test @ http://www.bing.com (压测时间30s) 8 threads and 200 connections (共8个测试线程,200个连接)
  Thread Stats   Avg      Stdev     Max   +/- Stdev
              (平均值) (标准差)(最大值)(正负一个标准差所占比例)
    Latency 46.67ms 215.38ms 1.67s 95.59% (延迟)
    Req/Sec 7.91k 1.15k 10.26k 70.77% (处理中的请求数)
  Latency Distribution (延迟分布) 50%    2.93ms 75%    3.78ms 90%    4.73ms 99%    1.35s (99分位的延迟) 1790465 requests in 30.01s, 684.08MB read (30.01秒内共处理完成了1790465个请求,读取了684.08MB数据)
Requests/sec: 59658.29 (平均每秒处理完成59658.29个请求)
Transfer/sec: 22.79MB (平均每秒读取数据22.79MB)

可以看到,wrk使用方便,结果清晰。并且因为非阻塞IO的使用,可以在普通的测试机上创建出大量的连接,从而达到较好的压测效果。

使用Lua脚本个性化wrk压测

以上两节安装并简单使用了wrk,但这种简单的压测可能不能满足我们的需求。比如我们可能需要使用POST METHOD跟服务器交互;可能需要为每一次请求使用不同的参数,以更好的模拟服务的实际使用场景等。wrk支持用户使用–script指定Lua脚本,来定制压测过程,满足个性化需求。

  1. 介绍wrk对Lua脚本的支持

wrk支持在三个阶段对压测进行个性化,分别是启动阶段、运行阶段和结束阶段。每个测试线程,都拥有独立的Lua运行环境。

启动阶段
function setup(thread)

在脚本文件中实现setup方法,wrk就会在测试线程已经初始化但还没有启动的时候调用该方法。wrk会为每一个测试线程调用一次setup方法,并传入代表测试线程的对象thread作为参数。setup方法中可操作该thread对象,获取信息、存储信息、甚至关闭该线程。

thread.addr             - get or set the thread's server address thread:get(name)        - get the value of a global in the thread's env thread:set(name, value) - set the value of a global in the thread's env thread:stop()           - stop the thread
运行阶段
function init(args) function delay() function request() function response(status, headers, body)

init由测试线程调用,只会在进入运行阶段时,调用一次。支持从启动wrk的命令中,获取命令行参数;

delay在每次发送request之前调用,如果需要delay,那么delay相应时间;

request用来生成请求;每一次请求都会调用该方法,所以注意不要在该方法中做耗时的操作;

reponse在每次收到一个响应时调用;为提升性能,如果没有定义该方法,那么wrk不会解析headers和body;

结束阶段
function done(summary, latency, requests)

该方法在整个测试过程中只会调用一次,可从参数给定的对象中,获取压测结果,生成定制化的测试报告。

自定义脚本中可访问的变量和方法

变量:wrk

 wrk = {
    scheme  = "http",
    host    = "localhost",
    port    = nil,
    method  = "GET",
    path    = "/",
    headers = {},
    body    = nil,
    thread  = <userdata>,
  }

一个table类型的变量wrk,是全局变量,修改该table,会影响所有请求。

方法:wrk.fomat wrk.lookup wrk.connect

 function wrk.format(method, path, headers, body) wrk.format returns a HTTP request string containing the passed parameters
    merged with values from the wrk table.
    根据参数和全局变量wrk,生成一个HTTP rquest stringfunction wrk.lookup(host, service) wrk.lookup returns a table containing all known addresses for the host and service pair. This corresponds to the POSIX getaddrinfo() function.
    给定hostserviceport/well known service name),返回所有可用的服务器地址信息。 function wrk.connect(addr) wrk.connect returns true if the address can be connected to, otherwise
    it returns false. The address must be one returned from wrk.lookup().
    测试与给定的服务器地址信息是否可以成功创建连接
  1. 示例
使用POST METHOD
wrk.method = "POST" wrk.body   = "foo=bar&baz=quux" wrk.headers["Content-Type"] = "application/x-www-form-urlencoded"

通过修改全局变量wrk,使得所有请求都使用POST方法,并指定了body和Content-Type头。

为每次request更换一个参数
request = function() uid = math.random(1, 10000000) path = "/test?uid=" .. uid return wrk.format(nil, path) end

通过在request方法中随机生成1~10000000之间的uid,使得请求中的uid参数随机。

每次请求之前延迟10ms
function delay() return 10 end
每个线程要先进行认证,认证之后获取token以进行压测
token = nil path = "/authenticate" request = function() return wrk.format("GET", path) end response = function(status, headers, body) if not token and status == 200 then token = headers["X-Token"] path = "/resource" wrk.headers["X-Token"] = token end end

在没有token的情况下,先访问/authenticate认证。认证成功后,读取token并替换path为/resource。

压测支持HTTP pipeline的服务
init = function(args) local r = {}
   r[1] = wrk.format(nil, "/?foo")
   r[2] = wrk.format(nil, "/?bar")
   r[3] = wrk.format(nil, "/?baz")

   req = table.concat(r) end request = function() return req end

通过在init方法中将三个HTTP request请求拼接在一起,实现每次发送三个请求,以使用HTTP pipeline。

https://www.cnblogs.com/xinzhao/p/6233009.html

HTTP / 3用UDP替换TCP以提高网络速度,可靠性

为站点和服务获得HTTP / 2的性能和安全性优势意味着进行体系结构更改,因为它颠覆了用于提高网站性能的分片等原则; 这可能就是为什么只有大约35%的网站目前使用HTTP / 2。

HTTP / 3加倍,提供非常相似的功能,但用UDP替换TCP。这一次可能需要对网络基础设施进行更根本的改变,以便利用比较差的连接和移动网络更好的性能,但对于大多数开发人员来说,这种改变将是透明的。

HTTP / 3是该协议的第三个主要版本,它包括TLS 1.3和一种称为QUIC(Quick UDP Internet Connection)的新传输协议; 根据最初由Google设计的2013协议,现在有多个贡献者和公司通过互联网工程任务组(IETF)参与其中

不可靠是一个机会

放弃HTTP一直用于UDP的TCP连接并不像看起来那么奇怪。U有时会扩展为“不可靠”而不是用户数据报协议,因为它不保证消息传递或数据包顺序。但是对于今天的网络,这实际上是一个提高HTTP / 2引入的多路复用连接性能的机会。“凭借HTTP / 3,我们将在同一个旧的不可靠互联网之上构建一个新的可靠协议,” Cloudflare首席技术官John Graham-Cumming告诉New Stack。

HTTP / 2通过在同一连接上发送多个HTTP请求,允许应用程序同时处理请求,从而更好地利用网络带宽。但只有在网络运行良好时才能实现这些收益。“你可以最大限度地提高吞吐量和带宽,因为TCP可以达到它可以达到的最大速度,并且所有并行连接都能以最快的速度运行,”Graham-Cumming说。

这很好,直到网络连接出现打嗝,例如网络拥塞或移动网络上从一个小区移动到另一个小区并且数据包丢失。“TCP保证发送数据包的顺序是应用程序接收的顺序 – 所以如果你错过了,那么一切都必须停止,直到特定数据包被重新传输。如果将多个请求复用到单个TCP连接上,则所有这些请求都必须停止并等待,即使丢失的数据包可能只影响其中一个。

Graham-Cumming解释说,这种“线路阻塞问题”是TCP固有的问题,使用UDP通过允许应用程序控制数据包的重传来修复它。“它可以说’数据包丢失,但它只影响这一个数据流,而其他数据流可以继续运行’。”这使得QUIC 在恶劣的网络条件下更加强大

HTTP / 3的连接迁移提议可以扩展在不同网络之间移动的稳健性,例如从Wi-Fi到移动宽带,这通常会因为IP地址的变化而破坏网络连接。“通过此提议,您和服务器之间的标识符将不是IP地址,而是协议本身内的标识符。这样您就可以在新网络上自动重启整个连接,这样您就可以无缝地从Wi-Fi转移到移动连接。原始互联网的一些假设是,存在相当固定的连接,我们现在正在进入一个非常移动的异构世界。“

Graham-Cumming解释说,HTTP / 3还直接集成了TLS,QUIC对数据包传输的额外控制也具有优势。“当事情通过互联网发送时,它们会被分解成数据包; 在TLS中,有一个传输数据缓冲区的概念。如果你想要真正有效率,你想要把所有这些事情排好; 这是一个请求,我将对它进行加密并通过互联网进行传输,以便一次性收到所有这些请求,并且不会分成较小的数据包,其中一些不会被延迟。如果您控制所有级别,如果您只是假设互联网是一种不可靠的发送数据包的方式,那么您可以控制发送内容的顺序,您可以控制加密它们的方式以及这些加密块如何传输。“

与HTTP服务器的初始连接将更快,因为HTTP / 3取出了通常需要建立连接的往返之一安全解释说,Mozilla首席工程师Martin Thomson是QUIC规范的编辑之一。

“使用TCP和TLS,连接设置以TCP握手开始:客户端发送SYN,服务器响应SYN + ACK,客户端使用ACK完成。然后在发送任何请求之前有一个TLS握手 – 它需要另一个类似的三个消息交换。QUIC在大多数情况下将其压缩到单个交换。一个关键特性是0-RTT,允许客户端立即发送请求; 这是TCP和TLS的一个选项,但你仍然需要等待TCP握手完成。“

默认安全

集成TLS还可以提高安全性,因为身份验证和加密是由网络协议提供的,而不是像TLS这样的高级协议提供的 – 而且在HTTP / 3中内置了TLS,使用它也不是可选的。Graham-Cumming指出:“业界正试图在默认情况下保证一切安全。” 当站点使用HTTPS时,浏览器现在会在站点没有加密连接时向您发出警告,而不是显示锁定HTTP / 3和QUIC是这个方向的另一个举措。

“更多的QUIC是加密的,”汤姆森解释道。这包括攻击者可能尝试使用的元数据。“除了一些有助于将数据包识别为QUIC数据包的比特之外,未加密的QUIC数据包的唯一部分是连接的不透明标识符。这包括TCP和TLS无法保护的内容,例如确认(’我从你那里得到5个字节’)。“

Thomson还认为加密实际上将简化部署QUIC。将新协议部署到需要更新的防火墙,路由器,NAT和其他网络设备上的复杂性阻碍了在浏览器中  创建新的,更有效的传输协议和延迟采用TLS 1.3的努力“这是部署QUIC的可能性的一部分。互联网往往会干扰新的协议,加密将保护QUIC免受干扰。“

他坚持认为,这是UDP是一个不错的选择的另一个原因。“新的协议不能直接部署在IP之上,如TCP或UDP,因为这需要更新的互联网。考虑更换或升级每个想要使用此新协议的房屋中的每个路由器的前景。UDP在这种情况下是理想的,因为它做得很少。它非常轻巧,因此我们可以在顶部构建所有必要的部件。UDP的主要缺点是已经显示阻止或降级UDP的少量网络。在极少数情况下,我们有HTTP / 2可以依靠。“

当用户访问站点时,他们的初始连接将通过HTTP或HTTP / 2,服务器将提供HTTP / 3作为替代; 了解提供该连接的标头的浏览器将记住它以供下次访问,但较旧的浏览器和设备将继续使用旧协议。格雷厄姆 – 卡明预测说:“我们预计HTTP / 2和1.1将会存在很长时间。”

网络变化

HTTP / 3可能有它的名字,但它仍在开发中; Graham-Cumming表示,情况稳定前几个月。尽管有一些HTTP / 3端点可用于测试Cloudflare等服务,但Facebook和Litespeed 已经证明了  2018年11月的HTTP / 3实现之间的互操作性,浏览器和Web服务器还没有支持HTTP / 3(Chrome的实验支持除外)对于谷歌的原始版本的QUIC)。

一旦他们这样做,服务提供商和托管公司将遵循,但可能需要更新旧的网络设备。“对于UDP的NAT遍历并不像TCP那样发达,一些家庭路由设备会假设UDP用于流媒体视频,而不是网络,”Graham-Cumming说。

Thomson说,如果你托管自己的网络服务器,你还有更多工作要做。“服务器管理员会发现QUIC需要的不仅仅是软件升级。启用UDP将需要网络和负载平衡基础架构的新支持。“网络运营商可能需要研究可以在软件中实现的更智能的第4层负载平衡解决方案,如Facebook的Katran项目

但由于HTTP / 3旨在解决HTTP / 2的问题,因此对HTTP / 2进行的优化(如域分片的更改)将在可用时自动应用。如果是这样的话,Graham-Cumming希望它能够推动网络的发展。“我们希望的是更快的网络下载和API。它应该使互联网更加可靠和快速,并且希望对于开发者来说,我们可以获得更丰富的API和Web应用程序,因为我们可以通过这种新协议更多地依赖互联网。

https://thenewstack.io/http-3-replaces-tcp-with-udp-to-boost-network-speed-reliability/

Nginx配置支持HTTP2

从 2015 年 5 月 14 日 HTTP/2 协议正式版的发布到现在已经快有一年了,越来越多的网站部署了 HTTP2,HTTP2 的广泛应用带来了更好的浏览体验,只要是 Modern 浏览器都支持,所以部署 HTTP2 并不会带来太多困扰。

虽然 h2 有 h2c (HTTP/2 Cleartext) 可以通过非加密通道传输,但是支持的浏览器初期还是比较少的,所以目前部署 h2 还是需要走加密的,不过由于 Let’s Encrypt 大力推行免费证书和证书的廉价化,部署 h2 的成本并不高。

介绍

HTTP 2.0即超文本传输协议 2.0,是下一代HTTP协议。是由互联网工程任务组(IETF)的Hypertext Transfer Protocol Bis (httpbis)工作小组进行开发。是自1999年http1.1发布后的首个更新。

HTTP/2 协议是从 SPDY 演变而来,SPDY 已经完成了使命并很快就会退出历史舞台(例如 Chrome 将在「2016 年初结束对 SPDY 的支持」;Nginx、Apache 也已经全面支持 HTTP/2 ,并也不再支持 SPDY)。

一般的大家把 HTTP2 简称为 h2,尽管有些朋友可能不怎么愿意,但是这个简称已经默认化了,特别是体现在浏览器对 HTTP2 都是这个简写的。

配置

普通的 HTTPS 网站浏览会比 HTTP 网站稍微慢一些,因为需要处理加密任务,而配置了 h2 的 HTTPS,在低延时的情况下速度会比 HTTP 更快更稳定!

现在电信劫持事件频发,网站部署了 HTTPS 加密后可以杜绝大部分劫持,但不是完全。像电子商务行业对 HTTPS 加密可是标配啊,因此部署 h2 更是势在必行。

证书

这里是 免费和便宜 SSL 证书介绍 ,大家可以从这里购买或者申请免费的 SSL 证书,免得 Chrome 弹出红色的页面令人不悦,从而拒绝了大多数访客。

Web 服务器

说明

默认编译的 Nginx 并不包含 h2 模块,我们需要加入参数来编译,截止发文,Nginx 1.9 开发版及以上版本源码需要自己加入编译参数,从软件源仓库下载的则默认编译。 Tengine 可以同时部署 h2 和 SPDY 保证兼容性,Nginx 则是一刀切不再支持 SPDY。

安装/编译

如果你编译的 Nginx 不支持,那么在 ./configure 中加入:--with-http_v2_module ,如果没有 SSL 支持,还需要加入 --with-http_ssl_module

然后 make && make install 即可。

配置

主要是配置 Nginx 的 server 块, 。
修改相关虚拟机的 .conf 文件,一般在 /usr/local/nginx/conf/vhost/ 或者 /etc/nginx/conf/,具体参考你的环境指导,不懂请回复。

server { listen 443 ssl http2 default_server; server_name www.mf8.biz; ssl_certificate /path/to/public.crt; ssl_certificate_key /path/to/private.key; 

注:将 server_name www.mf8.biz; 中的 www.mf8.biz 替换为你的域名。

然后通过 /usr/local/nginx/sbin/nginx -t 或者 nginx -t 来检测是否配置正确,然后重启 Nginx ,即可。

检验

在 Chrome 浏览器上可以通过,HTTP/2 and SPDY indicator 来检验,如果地址栏出现蓝色的闪电就是 h2

也可以在 chrome://net-internals/#http2 中检查。注意版本要新,姿势要帅!

配置进阶

大家都知道去年的心血漏洞将 SSL 推到了风口浪尖,所以单单支持了 h2 ,我们任然需要对 SSL 做一些安全的优化!

配置赫尔曼密钥

openssl dhparam -out dhparam.pem 2048 // 在 ssh 运行, openssl 生成 2048 位的密钥而不是当作参数写入 nginx.conf 文件。 ssl_dhparam /path/to/dhparam.pem; //在 .conf 中配置 

禁止不安全的 SSL 协议,使用安全协议

ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

禁止已经不安全的加密算法

ssl_ciphers
'ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!CAMELLIA:!DES:!MD5:!PSK:!RC4';

也可以参考:ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4:!DH:!DHE;

缓解 BEAST 攻击

ssl_prefer_server_ciphers on;``` **启用 HSTS**

此举直接跳过 301 跳转,还降低了中间人攻击的风险!配置在 .conf 中即可 `add_header Strict-Transport-Security max-age=15768000;` **301 跳转** 80 端口跳转到 443 端口

server {
listen 80;
add_header Strict-Transport-Security max-age=15768000;
return 301 https://www.yourwebsite.com$request_uri;
}

 **缓存连接凭据** 

ssl_session_cache shared:SSL:20m;
ssl_session_timeout 60m;

 **OCSP 缝合** 

ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/nginx/cert/trustchain.crt;
resolver 119.29.29.29 233.6.6.6 valid=300s;

privoxy安装

Privoxy是一款带过滤功能的代理服务器,针对HTTP、HTTPS协议,经常跟Tor组合使用。通过Privoxy的超级过滤功能,用户从而可以保护隐私、对网页内容进行过滤、管理cookies,以及拦阻各种广告等。Privoxy可以用作单机,也可以应用到多用户的网络。
Privoxy可以把socks5转换为HTTP代理,也就是俗称的APN。

下载
https://nchc.dl.sourceforge.net/project/ijbswa/Sources/3.0.26%20%28stable%29/privoxy-3.0.26-stable-src.tar.gz

添加用户给 Privoxy
echo ‘privoxy:*:7777:7777:privoxy proxy:/no/home:/no/shell’ >> /etc/passwd

分配组给 Privoxy
echo ‘privoxy:*:7777:’ >> /etc/group

编译
autoreconf -if
./configure –prefix=/usr/local/privoxy
make && make install

编写启动脚本
vim /usr/local/privoxy/sbin/start.sh
写入
#!/bin/sh
./privoxy /usr/local/privoxy/etc/config

Nginx负载均衡的优缺点

Nginx的优点是:
1、工作在网络的7层之上,可以针对http应用做一些分流的策略,比如针对域名、目录结构,它的正则规则比HAProxy更为强大和灵活,这也是它目前广泛流行的主要原因之一,Nginx单凭这点可利用的场合就远多于LVS了。
2、Nginx对网络稳定性的依赖非常小,理论上能ping通就就能进行负载功能,这个也是它的优势之一;相反LVS对网络稳定性依赖比较大,这点本人深有体会;
3、Nginx安装和配置比较简单,测试起来比较方便,它基本能把错误用日志打印出来。LVS的配置、测试就要花比较长的时间了,LVS对网络依赖比较大。
3、可以承担高负载压力且稳定,在硬件不差的情况下一般能支撑几万次的并发量,负载度比LVS相对小些。
4、Nginx可以通过端口检测到服务器内部的故障,比如根据服务器处理网页返回的状态码、超时等等,并且会把返回错误的请求重新提交到另一个节点,不过其中缺点就是不支持url来检测。比如用户正在上传一个文件,而处理该上传的节点刚好在上传过程中出现故障,Nginx会把上传切到另一台服务器重新处理,而LVS就直接断掉了,如果是上传一个很大的文件或者很重要的文件的话,用户可能会因此而不满。
5、Nginx不仅仅是一款优秀的负载均衡器/反向代理软件,它同时也是功能强大的Web应用服务器。LNMP也是近几年非常流行的web架构,在高流量的环境中稳定性也很好。
6、Nginx现在作为Web反向加速缓存越来越成熟了,速度比传统的Squid服务器更快,可以考虑用其作为反向代理加速器。
7、Nginx可作为中层反向代理使用,这一层面Nginx基本上无对手,唯一可以对比Nginx的就只有lighttpd了,不过lighttpd目前还没有做到Nginx完全的功能,配置也不那么清晰易读,社区资料也远远没Nginx活跃。
8、Nginx也可作为静态网页和图片服务器,这方面的性能也无对手。还有Nginx社区非常活跃,第三方模块也很多。
淘宝的前端使用的Tengine就是基于nginx做的二次开发定制版。
  Nginx的缺点是:
1、Nginx仅能支持http、https和Email协议,这样就在适用范围上面小些,这个是它的缺点。
2、对后端服务器的健康检查,只支持通过端口来检测,不支持通过url来检测不支持Session的直接保持,但能通过ip_hash来解决

Nginx下利用rewrite实现强制跳转https

Nginx下利用rewrite实现强制跳转https

if ($scheme = http) {
rewrite ^(.*) https://$server_name$1 permanent;
}

以上代码直接放入主机给的rewrite规则中即可,其中

scheme #HTTP的方法(如http,https)

$1 permanent #规则为http://domains/xxx 跳转到https://domains/xxx

项目前期使用http,后期为了安全方面的考虑,启用了https。

项目架构:前端使用nginx作为多个tomcat实例的反向代理和负载均衡。

实际上只需要在nginx上启用https即可,使客户端与nginx之后使用https方式通信,而nginx与tomcat之间依然以http方式通信。

现在需要将之前客户端所有的http请求全部都自动重定向为https,只需要在nginx上添加相应配置即可。

如下配置实现来源于Nginx HTTP 跳转至 HTTPS,但是我都实践验证过。

另外,也加入了一些自己的理解整理而成。

方式1:使用rewrite指令

server {
    listen 80;
    server_name domain.com;
    rewrite ^(.*) https://$server_name$1 permanent;
}
server {
    listen 443 ssl;
    server_name domain.com;
    ssl on;
    ssl_certificate     /etc/nginx/ssl/domain.com.crt;
    ssl_certificate_key /etc/nginx/ssl/domain.com.crt;
    # other
}

如果此时nginx作为Tomcat的前端反向代理的话,需要将相应配置放在配置ssl的server块中。

方式2:使用return指令

server {
    listen 80;
    server_name domain.com;
    return 301 https://$server_name$request_uri;
}
server {
    listen 443 ssl;
    server_name domain.com;
    ssl on;
    ssl_certificate     /etc/nginx/ssl/domain.com.crt;
    ssl_certificate_key /etc/nginx/ssl/domain.com.crt;
    # other
}

如果此时nginx作为Tomcat的前端反向代理的话,需要将相应配置放在配置ssl的server块中。

方式三:使用error_page指令

只允许HTTP来访问时,用HTTP访问会让Nginx报497错误,然后利用error_page将链接重定向至HTTPS上。

server {
    listen 80;
    listen 443 ssl;
    server_name domain.com;
    ssl on;
    ssl_certificate     /etc/nginx/ssl/domain.com.crt; 
    ssl_certificate_key /etc/nginx/ssl/domain.com.crt;
    # other
    error_page 497 https://$server_name$request_uri;
}

使用error_page指令时,将http和https的监听配置写在同一个server块中,对应的其他配置也需要在该server配置块中完成。

需要注意的是,此时需要将error_page指令语句写在最后,否则不能生效。

nginx强制使用https访问(http跳转到https)

nginx的497状态码

error code 497

[html] view plain copy

 print?

  1. 497 – normal request was sent to HTTPS  

解释:当此虚拟站点只允许https访问时,当用http访问时nginx会报出497错误码

思路

利用error_page命令将497状态码的链接重定向到https://test.com这个域名上

配置

[html] view plain copy

 print?

  1. server {  
  2.     listen       192.168.1.11:443;  #ssl端口  
  3.     listen       192.168.1.11:80;   #用户习惯用http访问,加上80,后面通过497状态码让它自动跳到443端口  
  4.     server_name  test.com;  
  5.     #为一个server{……}开启ssl支持  
  6.     ssl                  on;  
  7.     #指定PEM格式的证书文件   
  8.     ssl_certificate      /etc/nginx/test.pem;   
  9.     #指定PEM格式的私钥文件  
  10.     ssl_certificate_key  /etc/nginx/test.key;  
  11.       
  12.     #让http请求重定向到https请求   
  13.     error_page 497  https://$host$uri?$args;  
  14. }  

H5性能优化方案

H5性能优化方案

H5性能优化意义

对于一个H5的产品,功能无疑很重要,但是性能同样是用户体验中不可或缺的一环。原本H5的渲染性能就不及native的app,如果不把性能优化做起来,将极大地影响用户使用产品的积极性。

用户感受

当用户能够在1-2秒内打开H5页面,看到信息的展示,或者能够开始进行下一步的操作,用户会感觉速度还好,可以接受;而页面如果在2-5秒后才进入可用的状态,用户的耐心会逐渐丧失;而如果一个界面超过5秒甚至更久才能显示出来,这对用户来说基本是无法忍受的,也许有一部分用户会退出重新进入,但更多的用户会直接放弃使用。

一秒钟法则

移动互联网的架构、通讯机制,与有线网络有着巨大的差异,这也给H5的开发带来了很大的挑战。

10.jpg

这是一张手机端接入服务器的流程。

首先,手机要通过无线网络协议,从基站获得无线链路分配,才能跟网络进行通讯。 无线网络基站、基站控制器这方面,会给手机进行信号的分配,已完成手机连接和交互。 获得无线链路后,会进行网络附着、加密、鉴权,核心网络会检查你是不是可以连接在这个网络上,是否开通套餐,是不是漫游等。核心网络有SGSN和GGSN,在这一步完成无线网络协议和有线以太网的协议转换。 再下一步,核心网络会给你进行APN选择、IP分配、启动计费。 再往下面,才是传统网络的步骤:DNS查询、响应,建立TCP链接,HTTP GET,RTTP RESPONSE 200 OK,HTTP RESPONSE DATA,LAST HTTP RESPONSE DATA,开始UI展现。

可见,通过运营商的网络上网,情况比较复杂,经过的节点太多;运营商的网络信号强度变化频繁,连接状态切换快;网络延迟高、丢包率高;网络建立连接的代价高,传输速度快慢不等(从2G到4G,相差很大)。

而我们优化的目标,就是所谓的一秒钟法则,即达成以下的标准:

  • 2g网络:1秒内完成dns查询、和后台服务器建立连接
  • 3g网络:1秒内完成首字显示(首字时间)
  • wifi网络:1秒内完成首屏显示(首屏时间)

优化方案

资源加载

首屏加载

用户从点击按钮开始载入网页,在他的感知中,什么时候是“加载完成”?是首屏加载,即在可见的屏幕范围内,内容展现完全,loading进度条消失。因此在H5性能优化中,一个很重要的目的就是尽可能提升这个“首屏加载”的时间,让它满足“一秒钟法则”。

按需加载

首先要明确,按需加载虽然能提升首屏加载的速度,但是可能带来更多的界面重绘,影响渲染性能,因此要评估具体的业务场景再做决定。

Lazyload

Lazyload,即延迟加载,这并不是一个新的技术,在PC时代也是非常常用的一种性能优化手段。这个方案的原则是让屏幕外,或者不影响整体效果显示的图片、背景等资源,在界面就绪之后再进行网络加载。

滚屏加载

滚屏加载是一种常见的无刷新动态加载数据的方案,通常用在列表形式数据展示中。一方面,数据不是通过翻页进行加载,这样就避免了再一次请求和渲染整个页面;另一方面,数据显示的数量是受限的,例如第一次只请求了10条数据,也就只需要渲染这10条数据,下拉滚屏的时候,再去获得下面10条数据。

Media Query(响应式加载)

响应式设计是现在网站设计的一个流行趋势,随着移动互联网的发展,这项技术也越来越受到重视。通过这项技术,我们能够方便地控制资源的加载与显示,例如说在分辨率不同的手机上,分别使用不同的css,加载不同大小的图片资源。 方案参考: http://www.poluoluo.com/jzxy/201206/167034.html

第三方资源异步加载

第三方资源有的时候不可控,比如说页面统计、地图显示、分享组件等,这些第三方资源使用的时候要慎重选择,充分考察它们对于性能的影响,使用异步加载的方式进行,防止第三方资源的使用影响到页面本身的功能。

Loading进度条

在加载时间较长的时候,务必要让用户明确感知到加载完成的提示,通常是在加载过程中显示Loading的进度条,加载完成的时候隐藏它。从心理上,这会让用户有一种“期盼感”,而不至于太过枯燥。

对于一些重量级的H5应用,例如游戏,开始前需要加载很多资源才能让后面的游戏过程更为流畅,一个带百分比进度显示的进度条就更加重要。

避免30*/40*/50*的http status

  • 200是一个正常的response,我们在浏览器中打开一个网页(后面会讲如何针对移动端进行调试),还会看到304,即命中浏览器缓存。这两种状态是正常的http status。

  • 302、301跳转是常见的跳转,尤其前一种,在我们进行鉴权的时候有时会用到,但这个做法要尽可能地优化,一个页面访问,最多只进行一次302跳转即可,切忌频繁地跳转。

  • 404、500,我们对自己开发的代码比较注意,一般不会发生,但是有的时候,加载第三方库,尤其是第三方库中有自己load组件的操作,这时,404和500错误可能会在你不知不觉的时候发生。例如钉钉的第三方微应用中,就遇到过dojo的组件加载问题,加载的一些子组件失败了,但是又没有影响页面显示,这就很容易被忽略。后面也会再讲,如何去测试和发现这样的隐患。

Favicon.ico

如果我们没有设置图标ico,则会加载默认的图标:域名目录下的favicon.ico。很多开发者没有注意到这一点,就会导致这个请求404或者500。

通常,我们在应用内部打开网页,不会显示这个图标出来(除非放到浏览器中显示网页),我们需要保证这个图标存在,尽可能地小(一般4KB以下),并且设置一个较长的缓存过期时间。

图片的使用

格式选择

显示效果较好的图片格式中,有webp、jpg和png24/32这几种常见的图片格式。一般来说,webp的图片最小,但在iOS或者android4.0以下的系统中可能会有兼容性问题需要解决。

  • Jpg是我们最常使用的方案,大小适中,解码速度快,兼容性问题也基本不存在,是我们在H5的应用中使用起来性价比最高的方案。

  • Png24或png32,一般来说,显示效果肯定会比jpg更好,但是实际上人眼很难感知出来,所以在H5应用中要避免这种格式的大图片。

对于少量的图片,推荐用智图或者tinypng等工具来帮助自己选择合适的大小、格式。

像素控制

在H5应用中,图片的像素要严格控制,一般来说不建议宽度超过640px。

小图片合并

在html网页中,如果有多个小图片需要加载,不妨试试CSS Sprites方案,尤其是一些基本不变,大小差不多的操作类型图标。

避免html代码中的大小重设

在html或者css中,如果有类似width: **px这样的代码,就要注意看一看了,如果说图片显示的效果是宽度100px,而下载的图片却是200px宽度,那这大小基本上就是所需要的4倍面积了,所以在H5应用中,使用图片的一个原则就是需要显示成多大,就下载多大的资源。

避免DataURL

DataURL是用Base64的方式,将图片变成一串文本编码放入代码的方式。这种方式有好处,因为它可以减少一次http交互的请求,对于一些体积特别小的图片,或者是动态生成的图片可以考虑使用。但在H5应用中,一般情况下,我们都是需要避免DataURL的,因为它的数据体积通常比二进制图片的格式大1/3,而且它不会被浏览器缓存,每次页面刷新都需要重新加载这部分数据。

使用图片的替代(css3, svg, iconfont)

CSS3和svg可以更好地使用GPU进行渲染加速,而且会避免增加图片资源导致的http请求增加。例如一些div的圆角效果,就完全可以用用css来实现。

Iconfont,可以认为是一种矢量类型的操作字体。如果页面中有较多的操作图标,可以考虑使用iconfont来替代图片资源。

域名/服务端部署

Gzip

服务端要开启Gzip压缩。

资源缓存,长cache

合理设置资源的过期时间,尤其对一些静态的不需要改变的资源,将其缓存过期时间设置得长一些。

分域名部署(静态资源域名)

将动态资源和静态资源放置在不同的域名下,例如图片,放在自己特定的域名下。这样的好处是,静态资源请求时,不会带上动态域名中所设置的cookie头信息,从而减少http请求的大小。

减少Cookie

尽量减少Cookie头信息的大小,因为这部分数据使用的是上行流量,上行带宽更小,所以传输速度更慢,因此要尽量精简其大小。

CDN加速

部署CDN服务器,或者使用第三方的CDN加速服务,优化不同地域接入网站的带宽速度。

代码资源

Javascript, CSS合并

尽量将所有的js和css合并,减少资源请求的次数。

外联使用js, css

外联使用js和css,这样可以有效地利用缓存,避免html页面刷新后重新加载这部分代码。

压缩html, js, css

压缩代码,尤其是js和css资源,压缩后的大小可以降低至原来的1/3以下,有效节约流量。

资源的版本更新

库js、css通常不会更新,但是我们的业务js和css可能会有更新,如果命中浏览器缓存,可能会让一些新的特性不能及时展现,甚至可能导致逻辑上的冲突。

因此对于这些js、css的资源引入,最好用版本号或者更新时间来作为后缀,这样的话,后缀不变,命中缓存;后缀改变,浏览器自动更新最新的代码。

Css位置

CSS要放到html代码的开头的head标签结束前。如果网页是动态生成的,那么在head代码完成后可以强制输出(例如php的flush()操作),这样的话,浏览器就会更快地解析出来head中的内容,开始下载css文件资源。

Js位置

Js放到前,这样的话,js的加载不会影响初始页面的渲染。

代码规范

避免空src

图片设置为空的src地址,在某些浏览器中可能会导致增加一个无效的http请求,因此要避免。

避免css表达式

可能会让页面多次执行计算,造成卡顿等性能问题。

避免空css规则

降低css渲染计算的成本

避免直接设置元素style

直接设置style属性,一方面在html代码中不利于缓存,另一方面也不利于样式的复用,因此要避免,通过指定id或者class的方式,在css代码块中进行样式调整。

服务端接口

接口合并

如果页面需要请求两部分以上的数据接口,建议将其合并,否则会增加一次http请求。

减少接口数据量

有的时候,服务端会把一些无关紧要的数据返回回来,尤其是类似于更新时间、状态等信息,如果在客户端不影响内容的逻辑展示,不妨在接口返回的数据中直接去掉这些内容。

缓存

缓存接口数据,在一些数据新旧敏感性不高的场景下很有作用,在非首次加载数据时候优先使用上次请求来的缓存数据,可以让页面更加快速地渲染出来,而不用等待一个新的http请求结束之后再渲染。这一点我们在后面还会再次提及。

其他一些建议

  • 合理使用css

    • 正确使用Display属性 Display属性会影响页面的渲染,因此请合理使用
    • display:inline后不应该再使用width、height、margin、padding以及float
    • display:inline-block后不应该再使用float
    • display:block后不应该再使用vertical-align
    • display:table-*后不应该再使用margin或者float
    • 不滥用float
    • 不声明过多的font-size
    • 值为0时不需要单位
    • 标准化各种浏览器前缀
    • 无前缀应放在最后
    • CSS动画只用 (-webkit- 无前缀)两种即可
    • 其它前缀为 -webkit- -moz- -ms- 无前缀 四种,(-o-Opera浏览器改用blink内核,所以淘汰)
  • 选择器

    • 避免让选择符看起来像是正则表达式。高级选择器不容易读懂,执行耗时也长
    • 尽量使用ID选择器
    • 尽量使用css3动画
  • 资源加载

    • 使用srcset
    • 首次加载不超过1024KB(或者可以说是越小越好)
  • html和js

    • 减少重绘和回流
    • 缓存dom选择和计算
    • 缓存列表.length
    • 尽量使用事件代理,避免批量绑定事件
    • 使用touchstart,touchend代替click
    • Html使用viewport
    • 减少dom节点
    • 合理使用requestAnimationFrame动画代替setTimeOut
    • 适当使用Canvas动画
    • TouchMove, Scroll事件会导致多次渲染

更快一步

前面的很多建议与普通的PC端web网页的开发是一致的,但是在移动互联网应用下,仅仅做到这些,可能只有60分,那么怎样才能得到80分甚至更高?

单页应用

钉钉的审批微应用,使用的就是单页架构。在这种架构下,基本不存在页面跳转的等待时间,只需要执行js逻辑触发界面变化,最多进行一次网络请求,获得服务端数据,其他资源均不需要再次请求。

资源离线

再快的网络交互,毕竟也是跨越了数个网络节点,因此一张图片、一个js,优化到了极致,也照样可能需要几百毫秒的时间来获得。因此想要打破这个极限,就要使用资源离线的策略。

在钉钉的微应用中,就使用了这样的一个“离线包”策略。一些固定的图片、js库等,被打包放入app中(或根据需要,在app启动的时候进行下载更新)。

微应用中,网页代码里面加载网络资源的需求,就变成了直接加载本地文件,速度自然得到再一次巨大的提升。

本地数据持久化和更新机制(版本管理)

对于一些时效性没有那么高的数据,可以考虑将接口数据缓存。那么页面的渲染将变成这样的流程:

20.png

而非首次进入界面,流程如下:

30.png

可以看出,在非首次进入界面的时候,页面不需要等待网络数据返回,就可以进行界面渲染,渲染的初始数据来自于本地的缓存,页面可以“秒开”。而当服务端的数据返回之后,本地的渲染会再次更新,缓存也被更新。

采用这样的方案有利有弊,好处显而易见,首屏加载的速度简直太快了——静态资源来自本地,数据接口来自本地,这在2G、3G或者其他网络速度较慢的时候,也可以让用户在极短的时间内就看到内容。但是这种方案也并非万能。

  1. 首次加载不可避免,还是会请求网络。
  2. 服务端有更新的时候,客户端不能够快速感知,页面可能还停留在一个“旧的版本”上,尤其是网络速度较慢时,可能还是需要经过好几秒,页面才会更新至最新版本。因此如果应用对数据的新旧很敏感的话,这种方案就不适合
  3. 数据更新后,需要重新渲染界面,界面刷新的性能消耗比正常情况更多,而且增加了程序的复杂度,容易出错。

预加载

有时,我们能够通过用户的行为统计,预判出用户下一步可能进行的操作。假设,我们统计出来针对某个微应用,用户首页渲染完成之后,大部分会点击列表中的第一个项目查看详情。那么在首页渲染完成之后,我们就可以先预先加载第一个项目的部分内容,那么针对这部分用户,他们实际点击之后,立即就能看到新的页面中的内容。

当然,这个方式也并不是在所有场景下都使用。一方面,需要做好充分的用户调研,掌握用户的使用习惯;另一方面,对于小部分用户而言,预加载所带来的就是不必要的流量消耗。

测试方案

工具准备

Chrome

在功能测试中,我们通常可以用chrome来测试不同的分辨率下,或是不同的设备上,网页的展现情况。在我们做性能优化的时候,也可以用这种方式来进行调试,方便地观察在特定设备上,静态资源是否按照我们想象的那样去加载了。40.png

例如,我们想看下百度首页在某个设备下的表现。 通过F12进入控制台,点击图中的短箭头标示出来的那个设备图标,然后就可以在Device和Network中选择不同的设备和网络状况。50.png

例如iphone5下,这个地图的图标,明显就可以看到是用iconfont来实现的效果。

当然,这个功能也仅仅是一种模拟,通过控制屏幕分辨率、UserAgent等来模拟设备请求,在实际的设备上,又该怎么查看呢?

还是Chrome,我们在地址栏中输入chrome://inspect (注意:Android版本需要是4.4+,并且应用中的WebView必须进行相应的调试声明配置)60.png

在这里点击inspect,则可进入我们熟悉的F12控制台界面,只不过debug的对象变成了我们在手机应用中的网页。70.png

输入performance.timing.domComplete – performance.timing.navigationStart,就可以打印出网页加载的时间,domComplete表示所有的处理都已完成并且所有的附属资源都已经下载完毕。navigationStart表示开始加载新页面。两者相减,就代表这个网页完成渲染所需要的时间了。

同样地,我们可以在Elements tab中,debug网页,查看各个资源的使用,在Network中,看看加载了哪些资源,是否都做过了压缩。

然而,这种方式,还是有一定缺陷。 1. 如果打开网页经过了跳转,那么我们只能在这里看到最后一个url页面的加载情况。 2. 第一次打开页面的时候,在Network中,默认是不显示请求的详情的,当我们选择了preserve log upon navigation之后才会捕获,因此首次进入页面的加载情况,我们就很难获得了。

那么有没有一种方法,让我们能够更方便地去查看首次访问时,各种资源的加载使用情况呢?

Charles

Charles Proxy,可以说是H5测试的一个神器。 它的作用是在PC端开启一个代理服务器,手机连到这个代理服务器上之后,所有的http请求就都可以在这里看得清清楚楚。经过配置证书选项,https的请求也可以正常查看。80.png

从图中,我们明显可以看到,有一些404的异常请求,这些都将对我们H5应用的性能造成影响。 如果我们发现有一些资源的Duration比较大,那这些可能是服务端响应太慢,自然也可以作为我们优化的依据。

测试标准

在钉钉的测试中,形成了一套标准。

|指标项|遵循的原则|优先级|检查项|说明| |:-|:-|:—|:—|:–| |内存|内存无泄漏|P0|主功能页面反复打开,功能的重复调用,内存无泄漏| 可以使用sysdump,也可以用我们开发的perfeasy工具进行观察| | | |P1|主功能页面,持续操作,退出后,内存占用不超过总内存的5%|perfeasy| |CPU|减少无端的CPU使用|P1|灭屏,静置2分钟,在5分钟内CPU使用平均1%|adb连接后, 使用top命令| ||||主干功能正常操作CPU占用不超过60%, 持续5秒|perfeasy| |电量|避免无端电量消耗|P0|灭屏状态下,无线程持续运行|一般来说, 静置cpu正常, 这一项就没有问题| ||||灭屏,window.setTimeout()方法停止|一般来说, 静置cpu正常, 这一项就没有问题同上| ||||灭屏,window.setInterval()方法停止|同上| ||||ajax超时时间设置为5000ms以内|结合代码| ||||ajax无retry逻辑|结合代码| |资源|资源的正确使用|P0|是否存在资源的重复拉取|charles| |||P1|H5页面首屏总大小不超过200K|charles, chrome| |||P1|抓包检查(js/css/html)代码去除了空格/注释,JS文件变量名变成a/b等代替|charles, chrome| |||P1|H5引用的单张图片小于60K|charles, chrome

如果影响了显示质量, 可酌情调整| |流畅度|确保给到用户流畅的展示体验|P1|流畅的实时动画展示,avgFPS>=45|perfeasy| |时延|确保给到用户流畅的切换体验|P0|wifi网络下,首次进入页面onload时间<1000ms|Chrome| |时延|确保给到用户流畅的切换体验|P0|wifi网络下,首次进入页面onload时间<1000ms|Chrome| ||||wifi网络下,非首次进入页面onload时间<500ms|Chrome| ||||3G正常网络, 首次进入页面onload时间<2000ms|chrome, 树莓派模拟3G| ||||3G正常网络, 非首次进入页面onload时间<1000ms|chrome, 树莓派模拟3G|

经典案例

图片未优化

通过charles可以方便地进行测试。 从请求监控的情况看,有一张图片超过了60KB,宽度640px,但是在应用中,实际显示的是一张小缩略图,是通过代码控制让它显示成小图的,因此修改方案很简单,将所有头像的图片均改为获取120px宽度的即可。90.png

按需加载

  • 钉钉的教学页面

    • 多个slide页面, 每个页面有2-3个图片, 其中有一个是大图显示
    • 图片是客户提供的, 最大的图片大约是300KB以上
    • 第一次进入页面后, 所有slide的图片均进行加载
    • 3G网络下, loading的图标大约持续6000ms后才会消失
  • 优化方案

    • 尽可能优化图片, 但是压缩后发现噪点明显增加, 影响了显示效果
    • 修改加载方案, 第一次进入后, 只加载本页的图片
    • loading时间降低至1秒左右

最后插播一条广告:)

钉钉开放平台招聘微应用容器iOS工程师 负责钉钉客户端微应用iOS容器开发,参与钉钉iOS容器研发/发布/监控平台建设,建造航母级H5应用基础设施,保障线上产品质量;

需要你:

  • 精通iOS开发,熟练掌握Objective-C语言,C/C++ 语言;熟悉Cocoa开发框架,了解业界技术发展状况;
  • 熟悉iOS开发工具和相关开发测试工具的使用
  • 熟悉HTML5、CSS3和JavaScript,掌握HTTP及相关网络协议,熟悉跨终端、跨浏览器的开发模式和平台特性;