weekly of 20180826

工具技巧

系统设计

专题topic

其他

nginx 1.15.3 版本说明

0x00 Feature List

主要包含如下两点:

  • Feature: now TLSv1.3 can be used with BoringSSL.

  • Feature: the “ssl_early_data” directive, currently available with BoringSSL.

  • Feature: the “keepalive_timeout” and “keepalive_requests” directives in the “upstream” block.

前两者关于BoringSSL和 TLS 相关的优化,后者是 keepalive 特性的优化,下面分别介绍。

0x01 UPSTREAM KEEPALIVE

关于Nginx Upstream 长连接池的机制可以参看之前的让nginx支持TCP长连接代理这篇文章,这次主要现在如下两个指令, 如下:

1
2
3
4
5
6
* Syntax: keepalive_timeout timeout;
* Default: keepalive_timeout 60s;
* Context: upstream
> This directive appeared in version 1.15.3.
Sets a timeout during which an idle keepalive connection to an upstream server will stay open

以及如下:

1
2
3
4
5
6
* Syntax: keepalive_requests number;
* Default: keepalive_requests 100;
* Context: upstream
> This directive appeared in version 1.15.3.
After the maximum number of requests is made, the connection is closed.

可以看到其主要作用是,当一个请求空闲一段时间(keepalive_timeout)或者处理过足够多(keepalive_requests)的请求, 则会关闭连接,以便重启;

其核心理念是希望长链接的请求能够回收重启, 原因是长连接容易造成一些边缘 case 和异常,比如implement keepalive timeout for upstreamTimeouts when proxying to Apache and using Keepalive, 提到的proxy server和 proxy 的竞争发送问题, Proxy servertime_wait过多等问题; 代码修复实现相对比较简单,不再赘述。

0x02 BoringSSL与 TLS

我们知道 OpenSSL是应用最为广泛的TLS协议的实现, 由于 TLS 的重要性以及 OpenSSL 实现的复杂性,其安全问题一直都存在着; 所以,业内也有提出其他的 OpenSSL 的实现方式,比如基于OpenSSL修改衍生的BoringSSLLibreSSL, 这一类实现和 OpenSSL 基本保持接口的兼容,也就意味着宿主程序可以以较低的成本进行迁移; 还有一类是独立实现的,比如说BearSSL (建议可以深入了解)之类,其有自己特意的针对性目标和场景,其接口和openssl也不一致, 迁移成本较大。所以, Nginx从1.7.4开始, 开始兼容BoringSSL and LibreSSL, 其兼容成本也不算特别高。

具体编译的方法可以参考Nginx替换OpenSSL为LibreSSLNginx 启用 BoringSSL, 其核心是Nginx 编译脚本依赖的两类文件(考虑类Unix环境), 包括:

  • $OPENSSL/.openssl/include/openssl/ssl.h(参考auto/lib/openssl/make文件) : 头文件的依赖
  • $CORE_LIBS $OPENSSL/.openssl/lib/libssl.a$CORE_LIBS $OPENSSL/.openssl/lib/libcrypto.a(参考参考auto/lib/openssl/conf文件) : 链接库的依赖

由于编译的脚本是死的,所以在准备LibreSSL或者BoringSSL的时候,保证上面两者的兼容性就可以了

再简单分析LibreSSL/BoringSSL和 OpenSSL 的一些差异点, 整体不同的TLS library的对比可以参考Wiki:Comparison of TLS implementations, 上面有比较详细的对比

0x03 Reference

  • LibreSSL : 比较系统的介绍LibreSSL项目初衷,目标以及如何实现(主要在代码实现方面有所取舍)的, 可以快速了解

  • BearSSL: Overview : BearSSL也是一个TLS的实现, 里面保护很多比较最新的特性,比如说时间无关的支持, 是学习TLS的一个很好入门

  • Make SSL boring again : CloudFlare的BoringSSL的迁移实践和总结, 包括其优势,比如说较早的TLS1.3的支持, X25519的支持等, 其不足之处, 比如说Slow Base64(因为保证constant-time的), missing OCSP等,可以看出和了解BoringSSL和Openssl 的异同点

  • Analysing and improving the crypto ecosystem of Rust : 我们知道OpenSSL 现在主要的核心问题实现方面的漏洞, 其和C/C++语言的内存模型有一些关系, Rust的语言设计能很大的规避这方面的问题,这篇长 paper(一百多页) 就是介绍了如果通过Rust 构建一个更健壮的密码系统

weekly of 20180812

工具技巧

系统设计

专题topic

其他

  • Web Traceability : 本文是前ACM 主席对于互联网”可追踪性”的一个讨论和想法。互联网的兴起大大改变了我们的生活方式,但是其虚拟,匿名,加密等特性也是一把双刃剑,一方面使得用户的隐私得到有些保障, 另一个方面也引发的网络犯罪等行为; 作者的思路是: 首先,可追踪性还是要支持的,不过需要引入一些附加的流程和手段,比如说我们路上可以随便看到车牌,但是真正能查到车票对应的人只有执法部门;其次,对于技术上面的实现,其设计的点和面比较多,需要跟进前者的机制再针对性的建设~

  • Literary Clock Made From E-reader: 6 Steps (with Pictures) : 文章介绍了如何对Kindle进行破解,把其改成一个时钟, 先 mark,有空研究下 kindle 的破解手段

  • 都去炒AI和大数据了,落地的事儿谁来做? : 真正的系统架构师,既要往上高瞻远瞩,更要考虑落地以及业务的困难点; 其实现在 AI 的概念和场景『感觉』上面可以解决很多问题,但是真正落地的时候其坑和问题肯定不会少,AI 工程的标准化和流程感觉需要持续探索和摸索~

  • Filament : “Physically-based rendering engine”, 先 mark,有空深入分析下~

  • GetPublii/Publii : “Publii is a desktop-based CMS for Windows and Mac that makes creating static websites fast and hassle-free, even for beginners”, 好像是不过不错的 CMS 工具,先 mark,maybe 会用到

  • bash-oo-framework : “Bash Infinity is a modern boilerplate/framework/standard library for bash “, 一个基于 bash 的 lib 库, 但个人感觉方案有点重,最好有些轻量级的方案就好了~

  • Regex Dictionary by Lou Hevly : “The Regex Dictionary is a searchable online dictionary, based on The American Heritage Dictionary of the English Language, 4th edition, that returns matches based on strings”

Noise Protocol简介

最近在研究 VPN 相关的技术,会做个简单的总结,稍微系统地去介绍安全协议及其系统

0x00 何为Noise Protocol

官网的概述,其定义为:

Noise is a framework for building crypto protocols. Noise protocols support mutual and optional authentication, identity hiding, forward secrecy, zero round-trip encryption, and other advanced features.

太过含糊,仅仅强调其功能特性; 不过从作者在Schedule 34th Chaos Communication Congress的介绍,更具象一些,说明如下:

Noise is a framework that helps in creating secure channel protocols

所谓的secure channel,就是安全通道,比如TLS, IPsec, SSH都是; 也就是noise protocol就是用于快速方便去构建安全信道的一个框架。

0x01 WHY

一个自然而然的问题是,为什么需要这样的框架? 我个人的理解包括:

  • 从安全的角度来看,虽然我们拥有很多基础的密码原语支持,比如公钥体系,签名体系,对称加密,HASH 等, 但是在业务落地的时候这些东西如何组合也是个问题和风险点。如果组织不合理,非常有可能造成进一步的安全漏洞。基于此,形式化和清晰化的去描述和定义一个”安全协议”, 可以从更系统的层面去分析其安全问题,更全面的评估其可靠性,也避免了一些『自定义』方式带来的安全漏洞。 这个方面其实有点像我们开放我们的『加密算法』,可以让第三方去审计/评估,但是不影响加密算法的可用性。noise protocol同样也是,其明确定义了不同模式的交互方式和协议,我们可以更清晰的审计其健壮性

  • 从应用角度而言, two-parties的安全信道构建一直是有强烈诉求的, 但是不同的业务场景对于安全的要求也不尽相同,比如是否需要验证身份等, 所以去构建一个『框架』,让用户可以根据自己的业务场景方便构建自己安全信道的构建方式,是能满足更广大用户

0x02 Noise 的抽象和规范

介绍完了 Noise 的简单背景之后,我们来分析 Noise 是如何去解决这个问题的。

2.1 抽象

如下分析来自[1]的 PPT

一个安全信道的构建,可以划分为如下几个阶段 :

  1. 握手(Handshake)阶段,这阶段主要用于协议的协商和AKE(Authenticated Key Exchange) 相关事宜的完成,即包括:

    • Negotiation : 协议协商等
    • AKE : 身份验证&密钥交换
  2. 数据传输(Transport phase)阶段,根据握手阶段生成的对称密钥,加密传输相关数据

如下图所示 :

Secure Channel

但是从实践应用的角度,为了保障安全性(比如考虑前置安全性), 我们会根据情况,重新协商密钥(AKE), 所以整个交互过程,会变成如下模式:

[01] Negotiation + N (AKE + n * transport )

意味着:

  1. 一般就需要一次协商阶段/或者不需要
  2. 会有多次(AKE + trans)阶段,来保障传输的安全性

Noise protocol就是更关注后者的实现, 如下:

secure_channel_of_noise

如此一来,一个包含Noise Protocol的应用,其安全信道的组织方式大概如下:

noise_framework_overview

这个也是Wireguard的组织方式,后续的文章会再详细介绍

2.2 规范

有了上面的抽象之后,在具体实现上面, Noise 也做了相关的规范和约定;主要包括两个方面,其一是模式的约定,这里面主要说明了根据不同的业务场景(比如是否需要身份验证等),如何进行握手协议; 其二是密码算法的约定,主要约定了交互时候具体选择的密码协议等

2.2.1 模式约定

重点关注基础(fundamental),交互式(Interactive)的沟通模式

在一个交互式的场景下,比如Alice和Bob,我们假设他们需要完成握手协议,以便后续的数据传输。一个问题是,Alice 和 Bob 有多少种握手沟通方式? 这里, 我们其用两个字母表示,分别表示发送者(Alice)和 Bob(响应者)对于对方static key的诉求情况 :

对于Alice(请求初始化者)有如下四个情况 :

字母 spec说明 备注
N No static key for initiator 无需发送者的静态key(对发送者不进行身份验证)
K Static key for initiator Known to responder 已知(通过其他旁路方式)发送者的静态key, 可以进行身份验证
X Static key for initiator Xmitted (“transmitted”) to responder 通过握手传输交互发送者的静态key
I Static key for initiator Immediately transmitted to responder, despite reduced or absent identity hiding 通过握手传输,且离开发送静态 key, 这可能使其身份更容易暴露

对于Bob(请求响应者)有如下三个情况:

字母 spec说明 备注
N No static key for responder 无需响应者的静态key
K Static key for responder Known to initiator 已知响应者的静态key
X Static key for responder Xmitted (“transmitted”) to initiator 通过握手传输交互静态key

基于如上的自由组合,一共会有12种不同的模式; 再加上如下的规则 :

The fundamental handshake patterns perform DH operations for authentication (“es” and “se”) as early as possible

给定任意XY, 原则上面,我们都能推导其交互过程,比如XX,其具体如下:

A status B status 备注
s(x),e(x),rs(x),re(x) s(x),e(x),rs(x),re(x) 初始状态
s(x),e(y),rs(x),re(x) –> e s(x),e(x),rs(x),re(x) 生成e, 发送
s(x),e(y),rs(x),re(x) s(y),e(y),rs(x),re(y) B 知道e,s,re,起可以发送的 DH 有ee, es
s(x),e(y),rs(x),re(x) <–e,ee,s,es s(y),e(y),rs(x),re(y)
s(y),e(y),rs(y),re(y) s(y),e(y),rs(x),re(y) 发送 A.s, 其可以DH 有se,ss
s(y),e(y),rs(y),re(y) –> s,se s(y),e(y),rs(y),re(y)

2.2.2 基础算法约定

我们知道,密码学虽然定义了其有限的基本的『能力原语』,但是实现上面却各不相同,纷繁杂乱;一个 hash 方法,就有大几十种选择,不同的实现可能存在着或多或少的安全风险。为此,Noise框架直接规范了其依赖的函数的具体算法,对于这些规范内的函数,其安全性和可控性是经过严格审计的,具体如下:

类型 可选项 说明
密钥交互 DH25519 DHLEN = 32
密钥交互 DH448 DHLEN = 56
加密函数 ChaChaPoly 16 byte authentication data
加密函数 AESGCM 16 byte authentication data
哈希函数 SHA256 HASHLEN = 32, BLOCKLEN = 64
哈希函数 SHA512 HASHLEN = 64, BLOCKLEN = 128
哈希函数 BLAKE2s HASHLEN = 32, BLOCKLEN = 64
哈希函数 BLAKE2b HASHLEN = 64, BLOCKLEN = 128

如上,我们就可以约定Noise Protocol Name, 也可以通过其名字可以知道其协议过程,比如从Noise_NX_25519_AESGCM_SHA256, 可以得到:

  • NX = Pattern name
  • 25519 = DH name
  • AESGCM = Cipher name
  • SHA256 = Hash name

同时根据具体选择的算法,我们还可以估算一个Noise Protocol 交互时候其数据包的大小, 比如Noise_XX_25519_ChaChaPoly_SHA256模式,其不同的数据包大小如下:

1
2
3
4
5
6
7
8
9
XX:
-> e ## 1
<- e, ee, s, es ## 2
-> s, se ## 3
# 说明:
## 1. DH_25519的公钥为32字节,明文, 总长度为32
## 2. e为32字节, s为48字节(AEAD模式会增加16字节),payload(为空)加密后再增加16字节, 一共96字节
## 3. s加密后为48字节,加上payload为空的加密串,一共64字节

0x03 Noise Protocol 实践分析

为了助于我们对noise protocol加密协议有进一步的理解,对于给定的一个Noise Protocol Name, 我们要分析其包体格式, 比如上面的Noise_XX_25519_ChaChaPoly_SHA256, 其交互流程如下:

1
2
3
4
XX:
-> e
<- e, ee, s, es
-> s, se

那么在每个阶段,其具体发送的内容是什么呢?

其实在实现的时候, 发送方都维护了一个类似于状态机的内容,而e,s,ee之类的,则类似于action, 用于更新相关状态的内容;所以包的格式其实可以理解为就是对于action的一系列响应的结果;下面具体说明下:

首先是基础函数方面, 有如下几个能力 :

  • $DH(ee|es|se|ss)$ : 用于计算一个共享密钥, 例子用到的是DH25519
  • $hash(data)$ : 用于计算一个哈希值, 我们这边用的是SHA256
  • $encrypt(k,h,data)$ : AEAD 模式的加密函数
  • $HKDF(ck,data)$ : KDF 函数,用于生成新的key chain

其次是状态机方面,对于 Alice/Bob,其本地都维护了如下的信息:

关键字 说明
s,e 本地的临时/静态 DH组合对
rs,re 对方的临时/静态的 DH 组合对
h 当前的 hash 值,初始为空
ck chain key, 初始为空,用于通过 KDF 生成新key
k, n 加密k, 加密时候会通过(k,n,hash) 和 AEAD 模式进行对称加密
msg 当前要发送的消息内容
payload 当前要携带的额外数据

最后是 action 方面, 对于不同的 action,其定义如下:

  • action e :

    • $msg += e$
    • $h = hash(h||e)$
  • action s :

    • $s = k.empty() ? s : encrypt(s,k,h)$
    • $msg+=s$
    • $h = hash(h||s)$
  • action ee/es/ss/se :

    • $v = DH(ee|es|se|ss)$
    • $ck,k = HKDF(ck,v)$
    • $n = 0$
  • action token end(全部处理完 token) :

    • $payload = encrypt(payload,k,h) (if k!=0 )$

最后发送msg+payload

有了如上的说明,我们再详细看看Noise_XX_25519_ChaChaPoly_SHA256的交互情况 :

  1. -->e : 按照上面的分析, 会发送明文的e, 同时由于其不生成k, 因为也不会额外计算payload, 其发送包体只有32字节
  1. <--e,ee,s,es : 其有4个 token,拆分分析, 如下表格:
action msg h k ck
e e h1=hash(e) 0 0
ee e h1 k1 ck1,k1 = HKDF(ck,ee)
s e,enc(s) h2=hash(h1+enc(s)) k1 ck1
es e,enc(s) h2 k2 ck2,k2=HKDF(ck1,es)
end e,enc(s),enc(null)

具体格式如下:

noise_xx_demo

最后一个命令同理,不再赘述;

基于上面的分析,详细大家可以直接计算msg的包体大小,也就是上一节里面展示的;

如下是Wireshark抓包的示意图:

noise_wireshark_demo

具体实现代码方面,可以参考noiseprotocol:Noise Protocol Framework - Python 3 implementation

可以参看其README里面关于server/client的编写,实现一个快速简单的原型;

参考文档

weekly of 20180805

wiki:p_np_npc_problem

工具技巧

  • Detecting the use of “curl | bash” server side : 直接执行curl | bash的命令是存在比较大的安全风险的,因为可能执行一些未经确认的恶意代码; 那有没有办法可以去构造这种攻击吗?也就是对于普通的浏览器访问或者命令行下面直接的 curl 输出,我们返回正常的内容,但是如果是执行了curl | bash我们就返回还有恶意代码的内容呢?作者的回答是肯定的,其主要的思路是在返回内部的时候,初步先通过 chunk 机制返回带有sleep 5之类的能够检测时间/bash 的监测代码,在通过在服务端对于响应时间进行判断,就能知道我们输出的内容是否运行在 bash 环境下面(会不会感觉其实和检测一个代码是否跑在一个 JS 环境下面一样?);当然实现上面还有不少细节,比如说对于发送/接受/pipe 的 buffer 等控制和填充~

系统设计

  • Modern SAT solvers: fast, neat and underused (part 1 of N) — The Coding Nest : 本文的SAT是指Boolean satisfiability problem, 其实也就是个NP完全问题(NP-complete)。虽然是NP 问题,但是也有不少的 Lib 库存在,用于去解决这方面的问题,比如说文章提到的Minisat,DIMACS等; 从计算复杂性的理论,我们知道NPC 的问题是可以相互规约的,利用这个规则和现有的 Lib,我们可以认为,对于现实中存在的真正的 NP 问题,只要建立好正确对应的数学模型,将其规约到一个 NPC 问题,就能用这些基础库解决此类问题了。本文作者举了一个具体数独问题的例子,并演绎如何将其规约到基础的 NPC 问题,最终求解这类问题。对于,我们可以对计算复杂性的理论有进一步的了解和认识,也可以将其应用到其他的问题解决领域。先 mark 下,周末手动测试这些代码。如下的几个相关的参考资料,有助于了解整体文章:

  • Kafka 2.0重磅发布,新特性独家解读 : Kafka顺应了现在这个时代对于数据的规模化和流式化处理的需求,本身是发展也是非常迅猛;Maybe 可以其作为自己第二个侧重投入的重点工具链

  • How we scaled nginx and saved the world 54 years every day : 本文介绍了CloudFlare在 Nginx 部署和运维上面的一些先进经验,不少都是 Nginx 的性能深入优化点,比如SO_REUSEPORT, read thread pool, 算是真正有在规模化应用这些特性的大团队,经验等值得参考和采纳

  • 小米DevOps团队针对容器的Nginx优化 : 主要谈到容器化的时候,通过worker_processes auto的指令,并不能很好的控制 nginx 的进程数,基于此,可以通过旁路的脚本或者程序的升级来支持读取争取分配给他的CPU 线程数。其实,个人感觉,CPU 的限制是软限制,通过控制进程数并不合理,但是其深入分析问题的思路和精神值得佩服~

专题topic

本周主要关注P2P下载相关的技术

其他

weekly of 20180722

工具技巧

系统设计

专题topic

其他

weekly of 20180708

工具技巧

  • PDBs on Linux : PDB是windows下面的符号文件,其可以和bin文件拆分,某些时候会便于调试;默认情况下, linux的ELF的文件其调试信息的耦合在bin里面的,作者给出了自己的办法,具体为(可以收藏):
1
2
3
$ objcopy --only-keep-debug a.out a.out.pdb # extract symbols
$ strip a.out # strip away any debug information
$ objcopy --add-gnu-debuglink=a.out.pdb a.out # attach the symbols to the executable

系统设计

  • 谨防5个陷阱!数据科学家新手快速上道秘诀 : 最近AI前线给了不少实践落地的一些建议,这篇文章也是,有不少干货可以学习,具体包括:
    • 练习数据管理技能
    • 研究不同模型的优缺点
    • 尽可能简化模型
    • 检查你结论中的因果关系和相关性
    • 优化最有用的参数
  • Istio以及Service Mesh的未来 : 文章介绍Istio的简单机制,包括几个核心的组件:Envoy, Pilot,Mixer等,以及总结出来的其带来灵活性, 安全性, 可观测性等优点

专题topic

其他

nginx 1.15.2 版本说明

0x00 Feature List

主要包含如下两点:

  • Feature: the $ssl_preread_protocol variable in the ngx_stream_ssl_preread_module.

  • Feature: now when using the “reset_timedout_connection” directive nginx will reset connections being closed with the 444 code.

这里我们重点关注第二个特性(第一个特性,后续我们专门写篇介绍Nginx和SSL应用的文章再具体分析)

0x01 Reset Timeoutd Connection

第二个机制可以直译说明如下:

如果开启reset_timedout_connection配置后,Ngnix接收HTTP 444状态码后,会重置对应的连接

可以简单拆解为如下三个问题讨论:

  • HTTP 444状态码的作用和目的
  • 指令reset_timedout_connection的机制和作用
  • 整体这个feature的作用

1.1 HTTP 444状态码

其实444是Nginx内部的状态码,从nginx return的指令,看到说明如下:

…The non-standard code 444 closes a connection without sending a response header …

同时Http Status网站也有详细说明, 如下:

A non-standard status code used to instruct nginx to close the connection without sending a response to the client, most commonly used to deny malicious or malformed requests.

这样就比较明确, HTTP 444是Nginx内部的状态码, 其主要是作用是告诉 nginx可以直接关闭对应的链接,无需发送响应头部。这可以有效处理恶意或者畸形的流量。

1.2 reset_timedout_connection指令

该指令是由ngx_http_core_module提供的,如下是官方的说明:

1
2
3
4
5
* Syntax: reset_timedout_connection on | off;
* Default: reset_timedout_connection off;
* Context: http, server, location
Enables or disables resetting timed out connections. The reset is performed as follows. Before closing a socket, the SO_LINGER option is set on it with a timeout value of 0. When the socket is closed, TCP RST is sent to the client, and all memory occupied by this socket is released. This helps avoid keeping an already closed socket with filled buffers in a FIN_WAIT1 state for a long time.

简而言之, 当开启reset_timedout_connection后,Nginx 会通过设置SO_LINGER的选项快速关闭超时的连接, 其作用是保障系统和 Nginx 能够快速释放无用的连接的资源。 具体实现上面,就是通过控制SO_LINGER选项。

关于SO_LINGER其作用,可以简述为是控制close的行为, 参考Linux Socket SO_LINGER选项 (个人感觉解释的比较清晰明了)的说明,可以简述如下:

1
2
3
4
5
//SO_LINGER选项有如下结构:
struct linger {
int l_onoff; /* 0 = off, nozero = on */
int l_linger; /* linger time */
};

当设置 l_onoff为1,l_linger为0,则连接立即终止,TCP将丢弃残留在发送缓冲区中的数据并发送一个RST给对方,而不是通常的四分组终止序列,这避免了TIME_WAIT状态;在远端的recv()调用将以WSAECONNRESET出错。

这个也是Nginx的默认行为如下:

1
2
3
4
5
6
7
8
9
10
11
12
// file: ngx_http_request.c, 快速关闭请求
if (clcf->reset_timedout_connection) {
linger.l_onoff = 1;
linger.l_linger = 0;
if (setsockopt(r->connection->fd, SOL_SOCKET, SO_LINGER,
(const void *) &linger, sizeof(struct linger)) == -1)
{
ngx_log_error(NGX_LOG_ALERT, log, ngx_socket_errno,
"setsockopt(SO_LINGER) failed");
}
}

1.3 整体 Feature 作用

如上,整体的作用其实就是让 Nginx 能够快速释放相关的资源, 如下是前后的流程对比

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//before
A +---------> B +--------> C
1. return 444
<-----------+
2.normal close
<------------+
//now
A +---------> B +--------> C
2. reset socket 1. return 444
<-----------+ <-----------+

0x02 Nginx 模块的执行顺序

算是这次的补充说明, 是好久之前追查的一个问题,才发现之前对 Nginx 模块的执行顺序一直理解有误,这边记录说明下。

我们知道nginx的一个状态机驱动的模型, 那么在同一个阶段(状态)下面, 模块的执行顺序如何呢? 具体结论如下:

在非 filter 模块情况下, 在configure的时候, 越晚添加的模块就会越先执行(挂载在同一阶段的模块哈), 几个注意点如下:

  1. 上面描述的是在configure阶段越晚添加的越先执行, 指的是 Nginx 默认的configure,由于一些产品线的nginx 都会包一层自己的编译处理逻辑,这个时候就需要关注其是如何新增模块的(也就是新增模块和真正 configure)的关系

  2. Nginx扩展的自己config也会影响或者控制模块的执行顺序,默认的config如下:

1
HTTP_AUX_FILTER_MODULES="$HTTP_AUX_FILTER_MODULES ngx_http_baidu_problem_tracing_module"

但是如果改写成

1
HTTP_AUX_FILTER_MODULES="ngx_http_baidu_problem_tracing_module $HTTP_AUX_FILTER_MODULES "

原因具体下面说明;

现在我们来看一个模块/扩展是如何接入 Nginx 的状态机的;

1. configure阶段, 这个过程,Nginx 的脚本通过我们的编译控制,收集相关的信息,最终会生成一个 Shell 变量,这个变量包含了我们要编译进去的所有模块名称,如下:

1
2
3
4
5
6
7
8
9
10
if [ $HTTP = YES ]; then
modules="$modules $HTTP_MODULES $HTTP_FILTER_MODULES \
$HTTP_HEADERS_FILTER_MODULE \
$HTTP_AUX_FILTER_MODULES \
$HTTP_COPY_FILTER_MODULE \
$HTTP_RANGE_BODY_FILTER_MODULE \
$HTTP_NOT_MODIFIED_FILTER_MODULE"
NGX_ADDON_DEPS="$NGX_ADDON_DEPS \$(HTTP_DEPS)"
fi

这个里面需要说明的一点是module是直接引用了$HTTP_AUX_FILTER_MODULES之类的变量,而这些变量是每个扩展的config文件自己控制的,也就是说你把变量名字写在前后,直接影响你在modules里面的顺序

  1. 代码生成阶段,通过如上的环境变量, Nginx会生成类似于如下的文件,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
ngx_module_t *ngx_modules[] = {
&ngx_core_module,
&ngx_errlog_module,
&ngx_conf_module,
&ngx_events_module,
&ngx_event_core_module,
&ngx_epoll_module,
&ngx_openssl_module,
&ngx_regex_module,
&ngx_http_module,
&ngx_http_core_module,
&ngx_http_log_module,
&ngx_http_upstream_module,
&ngx_http_static_module,
&ngx_http_autoindex_module,
&ngx_http_index_module,
/* 这里说明 access 是 auth basic 在 access 阶段先执行的 */
&ngx_http_auth_basic_module,
&ngx_http_access_module,
...
}
  1. 启动初始化阶段, 其在http/ngx_http.c会有大概如下的伪代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//file: http/ngx_http.c
n += cmcf->phases[i].handlers.nelts;
// 从后面开始,追加到对于ph里面; 也就是越早在ngx modules里面被引用的,在会放在ph的越前面
for (j = cmcf->phases[i].handlers.nelts - 1; j >=0; j--) {
ph->checker = checker;
ph->handler = h[j];
ph->next = n;
ph++;
}
}
return NGX_OK;
}
  1. 执行阶段如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void ngx_http_core_run_phases(ngx_http_request_t *r)
{
ngx_int_t rc;
ngx_http_phase_handler_t *ph;
ngx_http_core_main_conf_t *cmcf;
cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
ph = cmcf->phase_engine.handlers;
// 顺序执行
while (ph[r->phase_handler].checker) {
rc = ph[r->phase_handler].checker(r, &ph[r->phase_handler]);
if (rc == NGX_OK) {
return;
}
}
}

如上,可以明确:

在非filter 模块情况下, 在configure的时候, 越晚添加的模块就会越先执行(挂载在同一阶段的模块哈)

0x03 Reference

nginx 1.15.1 版本说明

Feature List

主要包含如下两点:

  • Feature: the “random” directive inside the “upstream” block.

  • Feature: improved performance when using the “hash” and “ip_hash” directives with the “zone” directive.

一个是引入了random的upstream机制,还有一个是优化了zone区的hash性能; 下面我们详细拆解其功能

Upstream Zone机制

Nginx在1.9.0版本引入了zone的机制, 官方手册说明如下:

1
2
3
4
5
6
* Syntax: zone name [size];
* Default: —
* Context: upstream
> This directive appeared in version 1.9.0.
Defines the name and size of the shared memory zone that keeps the group’s configuration and run-time state that are shared between worker processes.

也就是其核心能力是使得upstream的相关配置以及状态,支持在进程间共享。这方面的优势显而易见,依照NGINX Load Balancing - HTTP Load Balancer 说明,我们可以总结为:

  • 其可以方便的支持健康检查,动态upstream配置等,而无需担心多进程数据同步的问题

  • 状态数据,比如max_fail等信息,可以被多个进程共享,这样是的业务避免无效的请求和重试, 否则每个进程都得去触发max_fail之类的阈值

关于upstream zone另外一个可以关注的点是其实现机制。直观来看的话,从单进程到多进程共享的数据结构,可能对源码会有较大的改动,尤其是有不少现存的upstream module, 不过nginx引入了一个相对巧妙的机制, 可以通过ngx_http_upstream_zone_module.c了解,其核心思路是:

  1. 将进程内的数据拷贝到共享内存里面
  2. 将所有的指针指向共享内存空间

这样几乎(主要有时候还需要解决多进程并发读写的问题)可以透明的将数据迁移到共享内存里面; 大致代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
static ngx_int_t
ngx_http_upstream_init_zone(ngx_shm_zone_t *shm_zone, void *data)
{
size_t len;
ngx_uint_t i;
ngx_slab_pool_t *shpool;
ngx_http_upstream_srv_conf_t *uscf, **uscfp;
ngx_http_upstream_main_conf_t *umcf;
/* 省去部分代码,指向共享内存区域 */
shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
/* copy peers to shared memory */
umcf = shm_zone->data;
uscfp = umcf->upstreams.elts;
for (i = 0; i < umcf->upstreams.nelts; i++) {
uscf = uscfp[i];
if (uscf->shm_zone != shm_zone) {
continue;
}
/* 讲每个upstream conf 拷贝到共享内存区域 */
if (ngx_http_upstream_zone_copy_peers(shpool, uscf) != NGX_OK) {
return NGX_ERROR;
}
}
return NGX_OK;
}

所以回到这次的feature, 对所谓的『ip_hash/hash』的性能优化,其实就是对zone里面读写锁的精细化控制。

Random Upstream

另外一个比较大的特性是引入random upstream的支持, 其官方手册说明如下:

1
2
3
4
5
* Syntax: random [two [method]];
* Default: —
* Context: upstream
The optional two parameter instructs nginx to randomly select two servers and then choose a server using the specified method. The default method is least_conn which passes a request to a server with the least number of active connections.

这里我们主要关注random two least_conn的情况; 什么意思呢?

用随机算法的时候,我们最担心的就是随机不均衡的问题, 所以对于我们upstream的random算法,其也要考虑这个方面的情况, 而two least_conn就是处理这个问题的有效手段,下面具体分析。

首先回到一个纯粹的数学问题,N个请求让N个服务器处理,则处理请求最多的服务器其会处理多少请求呢?

我们知道正常情况下,平均一个服务器会处理一个请求,但是事实上这个是低概率事件, 从Balanced Allocations的分析,其原话是:

the fullest box has, with high probability, ln $n$ / ln ln $n(1 + o(1))$ ball in it

也就是其实最大负载的机器其处理的请求数会远超于均值, 而如果用Two Random Choices(随机选两个,其次选择负载低的策略), 其概率大概为 $ln \ ln\ n$

如下是去概率函数分布图:

two-random-choice

总而言之, 双次选择会使得我们的负载均衡的表现更为优异。

如上公式的数学推导其实是比较复杂的(至少我没有看懂), 所以这里在简单分享自己对”N个请求让N个服务器处理,则处理请求最多的服务器其会处理多少请求呢?”这个问题的程序员解法;(Maybe有错误哈)

首先定义概率密度函数:

  • $f(k)$ : 表示N个球放N个盒子,处理请求最多的盒子其处理请求数量为k的概率
  • 则上面的问题,其实求的就是该分布的期望, 可以有:

$$E = \sum_{k=1}^{n}{f(k)*k}$$

其次,我们分析如何求解$f(k)$, 这里面我们通过传统概率算法计数法来计算对应的概率, 具体如下:

  • N个球放入N个盒子,不考虑球的差异性,其有 $\binom{2*n-1}{n}$ 可能性
  • 再考虑如果最多盒子对应的球的个数为k的放法, 我们定义为$c(n_1,n_2,k)$, 表明$n_1$个球放入$n_2$盒子,最多的盒子里面有$k$个球的方法数,则显然有:

    • $c(n,n,1)$ = 1
    • $c(n,n,n)$ = n

而对于$c(n,n,k)$我们则使用递归法统计, 我们可以估算其最多有$i$个盒子里面有$k$个球,基于次分析:

$$c(n_1,n_2,k) = \sum_{i=1}^{n} (cc(i) (\sum_{j=1}^{k-1}c(n_1-ik,n_2-i, j)))$$

其中$cc(i)$ 表明$i$个盒子有$k$个球,选取的办法有$\binom{n_2}{i}$
而公示后半部分的意思是剩余的盒子里面,最多为1,2,…,k-1个球的放法

按照如上的公示,可以得到代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
def s(ball,box,k) :
if ball > box and k == 1 :
return 0
if box > ball and k == 1 :
total = math.factorial(box) / math.factorial(ball) / math.factorial(box - ball)
return total
if ball == box and k == 1 :
return 1
if k <= 0 :
return 0
total = 0
for i in range(1,ball+1) :
if i * k > ball :
break
if i > box :
break
tmp1 = math.factorial(box) / math.factorial(i) / math.factorial(box - i)
tmp2 = 0
for j in range(1,k) :
tmp2 += s(ball- i*k, box-i, k-j)
total += tmp1 * tmp2
return total

依照如上公示,我们可以得到一些期望值如下:

1
2
3
ball_exp(10) = 3.7
ball_exp(20) = 4.7
ball_exp(40) = 5.6

和上面的数据推导数据不完全一致,但是还是符合相应的函数曲线的

参考

weekly of 20180624

香港

工具技巧

  • Jsonnet – A data templating language | Hacker News : Jsonnet是一个Json的模板语言,可以简单快速的构建基于Json的数据结构;PS: 其应用场景应该是作为中间工具,让业务用比较低的成本编写Json(支持计算/变量等),然后转换成最终的Json,降低业务json的维护和改造成本, 但是感觉其学习成本也不低

  • Changes in WebAssembly Could Render Meltdown and Spectre Browser Patches Useless : WebAssembly提供了一个能让程序更高效运行在浏览器的机制,但是也可能存在一些副作用,比如一旦引入类似于multi-thread的机制,就可能引入一些安全问题和风险, 比如之前CPU的Meltdown和Spectre漏洞

系统设计

  • 独家揭秘:腾讯千亿级参数分布式ML系统无量背后的秘密 : 信息量很大,先mark,本周消化下

  • Deep code search | the morning paper : 挺有意思的一个系统,通过深度学习的方法来了解代码的大概功能,基于此,可以解决一些语义检索的问题,比如说”read an object from xml”, 可能有一天会取代stack overflow

  • Structuring Deep Learning Projects : 本文介绍一个架构化的深度学习项目的开展方式,具体包括:

    1. Pick a cost function.
    2. Pick an initial network architecture.
    3. Fit the training set well on the cost function.
    4. Fit the validation set well on the cost function.
    5. Verify performance on a test set.
    6. Verify performance in the real world.

      其实这也是普适的机器学习方法

专题topic

About Perf Tool

其他