Total Pageviews

Sunday, 19 January 2020

nginx开启http2(server push)支持

http2有什么优点?

一个字:快!
http2主要特性:
  • 二进制分帧
  • 首部压缩
  • 流量控制
  • 多路复用
  • 请求优先级
  • 服务器推送

nginx开启http2前提条件

1、必须配置https证书,RFC并没有规定http2一定要使用https,但目前的浏览器都要求必须使用https。
2、nginx版本必须1.13.9以上(含),在Changes with nginx 1.14中搜索”1.13.9″即可看到:
3、nginx编译时必须加上ngx_http_v2_module模块,如果你之前已经安装了nginx,可使用nginx -V来查看是否带有该模块:
4、编译时openssl必须在1.0.2版本以上,运行以下命令即可查看openssl版本:
openssl version
Bash
如果你之前已经安装nginx,可用nginx -V查看所使用的openssl版本,它会有“built with OpenSSL x.x.x”这样的信息,其中“x.x.x”就是版本号,比如“1.1.1b”就超过“1.0.2”就可以了。
有人可能不明白,nginx支持http2跟openssl有什么关系?因为开启http2必须要使用https,而https就是http+tls,而openssl是用于支持tls协议的。

什么是server push?

server push就是服务器推送,就是在用户首次访问你的网站时,把所有需要的资源直接推送到浏览器,这样浏览器就不需要再次发起请求去获取这些资源了,资源包括:css文件、js文件、图片文件、字体文件(反正就是静态文件),不使用server push的情况下,这些文件都是由浏览器主动发起请求去获取的,每一次请求都要三次握手建立连接需要花费时间,当文件很多的时候就会慢,像css加载图片的时候,我们经常把多个小图片合并到一个图片文件里,这也是为了减少浏览器发出多个请求去分别请求这些图片.

nginx开启http2

该配置并非完整配置,只是告诉你,在原来已经正常使用https的配置的基础上,在listen后面加个http2就开启http2了:
server {
    # listen在原https配置文件基础上添加http2
    listen 443 ssl http2;
    ……
}
nginx
开启http2之后,protocol就会显示h2了,没开的话它是显示http/1.1的.
google的所有网站都用了http2,并且还有些是h2+quic/46,其中quic(Quick UDP Internet Connection)是http3.0的基础,是google研发出来的技术,差距就是这么大,百度连http2都没用上,谷歌却已经在用http3了(并且是自主研发的)。

nginx开启server push方式一

前面只是开启了http2,但还未开启http2的server push功能,该方式很简单,就是使用http2_push指定要推送哪个文件即可(“/style.css”的“斜杠”是指根目录,这个根目录是指root指定的那个目录,并非Linux服务器的根目录):
location / {
    root   /usr/share/nginx/html;
    index  index.html index.htm;
    http2_push /style.css;
    http2_push /example.png;
}
nginx
不过这有两个缺点:
  • 1、静态文件很多,每次添加了都要改nginx配置文件并重启,非常不方便;
  • 2、访问网站的任何一个网页都会执行http2_push,都会推送一次(即使已经推送过了也会重新推送),虽然浏览器接收到这个推送时会先检查本地是否已经缓存了这个文件,如果已经缓存了就会告诉nginx不要再推送,但即使这样,也已经推送了一部分了,这会造成浪费(因为这是不必要的推送)。
    当然这两个缺点是可以通过cookie来解决,在配置文件中也能做到,可参考:Nginx 下实现 HTTP/2 服务器推送 (Server Push) 教程

nginx开启server push方式二(推荐)

方式一非常不方便,并且还有两个缺点,方式二就可以解决这两个缺点。方式二是nginx不直接指定要推送哪个文件,而是直接用http2_push_preload on开启push功能:
server {
    # listen在原https配置文件基础上添加http2
    listen 443 ssl http2;
    # 添加一句http2_push_preload on表示开启server push功能
    http2_push_preload on;
    ……
}
nginx
然后可以通过两种方法告诉nginx要推送哪个文件,通过http header或html页内link标签告诉服务器要推送哪些文件。
方法一:通过http header
告诉nginx推送/styles.css这个css文件
link: </styles.css>; rel=preload; as=style
HTML
告诉nginx推送/script.js这个js文件
link: </script.js>; rel=preload; as=script
HTML
告诉nginx推送/example.png这个图片文件文件
link: </example.png>; rel=preload; as=image
HTML
告诉nginx推送 “https://fonts.example.com/font.woff2” 这个字体文件,并且该字体文件不在本服务器上,而是跨域获取
link: ; rel=preload; as=font; crossorigin; type="font/woff2"
HTML
其实多个Link也可以合并,把多个值用逗号分隔即可:
link: </styles.css>; rel=preload; as=style, </script.js>; rel=preload; as=script, </example.png>; rel=preload; as=image, ; rel=preload; as=font; crossorigin; type="font/woff2"
HTML
怎样发送这个header呢?由后端语言来发送,以下举例php的方法:

    header("link: 
; rel=preload; as=style", false); header("link: ; rel=preload; as=script", false); header("link: ; rel=preload; as=image", false); header('link: ; rel=preload; as=font; crossorigin; type="font/woff2"', false); ?>
PHP
当浏览器请求nginx时,如果是php文件,nginx会把请求交给php-fpm来解析,php-fpm会把这些header代码解析为http header返回给nginx,nginx接收到php-fpm返回的数据,识别到有以上这些header,就会推送对应的文件到浏览器,当然这个header也是会发到浏览器的。
当然实际上写肯定不可能这样写,因为你也不太可能每个文件都写一遍,你可以用一些正则之类的方法判断用户请求的那个页面中需要哪些资源,然后用循环的方法去批量发送这些header。
这里要说一下php的header()函数第二个参数要用false,表示的是允许同名的header title(默认是true,即前面有同名的会被后面的覆盖,但这不是我们想要的),如下就是在浏览器上的response header中看到的多个同名的header(字段名都是“link”)。
方法二:通过html页面中的link标签
<link rel="preload" href="/styles.css" as="style">
<link rel="preload" href="/script.js" as="script">
<link rel="preload" href="/example.png" as="image">
<link rel="preload" href="/styles.css" as="style">
<link rel="preload" href="https://fonts.example.com/font.woff2" as="style" type="font/woff2" crossorigin="true">
HTML
最后一个crossorigin我也不确定是不是这么写的。
还可以用js来动态添加:
<script>
var res = document.createElement("link");
res.rel = "preload";
res.as = "document";
res.href = "/other/widget.html";
document.head.appendChild(res);
</script>
JavaScript
但这种添加link标签的方法我一直没有试成功,后面再看吧,W3C Preload中确实有说这种方法。
------

Ubuntu14.04开启nginx http2支持的方法

手上还有一些老机器还停留在14.04?:

没有足够的时间和动力来升级这几台老机器,但是一些常用的软件准备顺手升级一下。最基本的自然是升级nginx支持http2. 

Ubuntu 14.04开启nginx http2支持的前置条件

  1. nginx >=1.9.5
  2. openSSL >= 1.0.2

第一个条件大家一般都不会漏掉。但是第二个条件一般是http2无法成功开启时才发现。这是因为随14.04一起分发的openSSL版本是1.0.1f. 那么要开启http2支持,有两种方式:

  1. 使用他人编译好的ngingx with http2 support package安装;
  2. 升级本地openSSL版本,然后从源码编译安装。

而随Ubuntu 18.04一起分发的openSSL版本为1.1.0g, 因此不存在这个问题。

使用packaege安装(懒人专用)

这里需要注意一下,很多为提供14.04提供nginx安装包的源虽然可以让你安装更高版本的nginx,但是大多是使用openSSL 1.0.1编译的,因此无法支持http2, 比如jessis, nginx mainline为14.04提供的安装包是使用1.0.1编译的,因此不支持http2的。这里我们使用ondrej提供的源。

  • 卸载已经安装的nginx(会保留配置文件,take is easy):

  • 添加ondrej nginx安装源:

如果出现如下错误:

应该是终端编码问题,尝试使用LC_ALL=C.UTF-8 add-apt-repository ppa:ondrej/nginx解决。

  • 更新源,并安装:

从源码安装

修改nginx配置,开启http2

设置nginx支持http2最关键是在https端口添加http2指令:

一个网站的参考配置模板如下:

小结

nginx开启http2的支持,不要忘记了对openSSL最低版本的要求。其实http2已经不再时髦啦,http2+TLS1.3才是未来。

----------

低延迟与用户体验杂谈

最近在做系统设计梳理的时候,明显感觉到「低延迟」已经成为被提及越来越频繁、考量权重越来越大的因素。 并且,越是靠用户近的系统,对延迟越敏感,对用户体验影响越大,对低延迟要求越高。

HTTP/2如今(2018.02)已经逐渐普及,其设计的第一目标就是降低延迟。主要采用了两个手段来解决:

  1. TCP连接复用。连接复用减少了TCP每次握手带来的延迟,同时避免了每次新建TCP连接的窗口慢启动带来的数据吞吐开启延迟。
  2. 使用数据分帧解决队头阻塞问题。当然,这个问题HTTP/2解决得不彻底

HTTP/2毕竟只能解决应用层的低延迟问题。如果要继续降级延迟,就需要下潜到传输层。因此,Google的QUIC和TLS 1.3应运而生。QUIC目前主要是Google主导,除去其自家的服务,如Google搜索首页,G+等,支持的网站还非常少。TLS 1.3则由标准化组织加持,目前在最新的OpenSSL、nginx已经支持。TLS 1.3能够做到新连接3RTT,恢复连接2RTT(TLS 1.2分别是4RTT, 3RTT;而2RTT已经与http的RTT持平!),的确非常吸引人。

回顾WEB技术过去十几年的发展,很多的特性引入和改进都是基于降低延迟。有些是技术层面的,比如上面提到的HTTP/2的低延迟设计、DNS查询缓存、HTTP1.1并发多个TCP连接请求资源、雪碧图等;有些是用户体验层面的,比如异步加载/预加载js资源、优先加载影响首屏渲染的CSS资源、避免使用大表布局、图片渐变加载、过渡动画等。

而这些年技术层面的发展其实都是受物理定律限制的。祭出程序员延迟心经Latency Numbers Every Programmer Should Know:

软件工程师无论是做什么职位和方向,心里都应该对此有B树。

有了这些当前人类所认识的物理极限,才能做到在「在边界内做事情」(这句话不是我说的,第一次看到这句话是吴军老师的《硅谷来信》)。

举个例子,当前人类认知范围内最快的速度是光速,这是一个上限,而广泛使用的光纤通信速度大概是光速的2/3。那么,要想优化上海到加州的网络延迟,无论你如何优化线缆布设以及质量,RTT都不可能低于127ms. 因此,如你所见,这些年虽然新架设了不少新的跨太平洋光缆,但是最优网络延迟没有什么太大变化,反倒是这些光缆带来的扩容让网络拥塞得到了缓解,让我们感觉出口网络「好像」更快了。

那么在光速这个物理边界的限制下,我们要如何降低延迟呢?显然,固定的两点之间的网络延迟是无法突破该边界的(这里不讨论空间扭曲力场?‍♀️)。但是,很多时候,我们要解决的问题是「让用户感觉延迟低」就可以了。

在这个思想下,CDN应运而生。将内容分发到距离用户近的网络节点来降低用户访问延迟。这个idea非常简单,甚至简陋,但是非常有效,并且廉价。

顺着这个思路下去,如果把算力分发到距离用户近的节点,那是不是也可以让用户觉得计算任务也变快了呢?在一定程度上,这是可以做到的。比如,我们使用了数十年运行于浏览器的javascript,以及我们的多IDC、多主架构方案,都有这方面的考量。近来流行的serverless、边缘计算其实也可以是认为是将算力部署到离用户尽可能近的物理位置或业务流程中。

有时候,技术参数指标的提升在短期内是难以低成本解决的。这个时候,尽量避免死磕参数,投入100%的精力去换取1%的性能提升。可以尝试从设计交互上给用户形成反应很快的体验,降低体感延迟。比如,1)耗时的任务后台化并给出进度条;2)区块处理的逻辑任务尝试修改成流处理,加速部分处理结果的输出,典型应用如以视频直播服务为代表的流媒体服务全力优化首屏播放延迟;批处理任务每处理完一个子任务就反馈结果等。

不仅软件系统的延迟影响着用户体验,硬件更是如此。现在人们普遍认为iPhone的用户体验是优于安卓的。因素有很多,那些复杂的系统参数普通用户未必搞得懂,但是很多用户却承认在滑动屏幕的时候iPhone比安卓更加「跟手指」,打开应用也「感觉更快」。「跟手指」这个体验跟苹果的软件+硬件的技术优化有关,目前安卓阵营也没有赶上。打开app快则是典型的交互优化:苹果打开app未必真的比安卓快,但是从点击app图标到显示story board的确非常快。无论苹果是否鸡贼,至少从这个层面看,它的确是非常了解延迟与用户体验之间的奥义的。至于苹果每次升级系统「故意」让老设备变卡……咳,咳,同学,你怎么有又抬杠呢?

喜欢汽车并且喜欢驾驶的朋友一定知道甚至深入研究过这几款车:马自达3/6/BRZ/86-宝马M2/M4-保时捷718/911。排名分先后,每一辆都是不同级别的驾驶者之车,也是很多玩车朋友的玩车升级路线。这些车都是「运动取向」,这是一个非常抽象的概念,普通消费者未必明白,但是你会发现他们的消费者大多会用「操控好」「响应快」来评价这些车。如果你是参数党,你会发现其实这些车在同级别中都不是最好的,但却是给人愉悦感最强的。

由物及人,当上司交给你一个任务的时候,有反馈好过无反馈,快反馈好过慢反馈。

总结

在节奏日益变快的今天,低延迟很多时候意味着良好的用户体验。而做到低延迟可以通过技术优化,也可以通过一些产品的交互方式和有选择的强调某些方面来让用户感觉很快。内容比较分散,但是都是顺着这根主线,希望你也有所思考和收获。

参考文献

Introducing Zero Round Trip Time Resumption (0-RTT)