概念

我们已经知道什么是SNI,以及如何为用户配置SNI。

[nginx] nginx使用SNI功能的方法

问题

通过观察配置文件,可以发现,针对每一个SSL/TLS链接, nginx都会动态的查找(加载),找到正确的证书。

那么在这个过程中,与没有SNI配置的情况下,有什么性能异同呢?

通过对nginx相关部分的源码分析,将给出这个问题的答案。

(不关注源码的话,可以直接翻到后文查看“结论”章节。)

[classic_tong @ 20191023]

有图有真相

分析

如上图所示

1 模块

nginx使用两个模块(这里只针对http进行分析,stream请自行类推)来完成tls的处理,ngx_openssl_module 与

ngx_http_ssl_module。其中前者为核心模块。后者为http模块。

核心模块会被master进程调用ngx_ssl_init()函数进行加载,完成全局处理,包括对openssl的初始化。

http模块将由http框架完成处理,可以分为配置阶段与connection的解析阶段。

2  配置阶段

见图中的红框“流程二”。这个阶段就是逐行处理配置文件的阶段。在这个阶段里,merge ssl server config的时候,

http_ssl_module将判断是否配置了SNI功能。

如果没有配置,将调用ngx_ssl_certificate()函数加载证书链里边的所有证书

,将其读进内存并保存在全局配置文件的ctx结构体呢。ctx结构体是openssl api中的全局上下文,会作为参数传入

给openssl的api,openssl在处理连接时,会将ctx里的证书链拷贝(由SSL_new接口完成)到connect结构里,

openssl的connect结构的作用域是tls连接级别。

如果配置了SNI,http_ssl_module将不会加载证书链,而是将配置中的变量解析编译好备用,为运行时做好准备。

同时,还会通过openssl的SSL_CTX_set_cert_cb()函数设置一个回调函数 ngx_http_ssl_certificate(), 该函数会在

ssl握手之前进入,给用户一个修改证书信息的机会。该回调是在SSL_do_handshake()中完成的。

  1. # man SSL_CTX_set_cert_cb
  2. cert_cb() is the application defined callback. It is called before a certificate will be used by a client or server.
    The callback can then inspect the passed ssl structure and set or clear any appropriate certificates.

[classic_tong @ 20191023]

3 请求阶段

如图红框“流程三”

在请求阶段,“是否启用了SNI功能”是不被nginx感知的。所有与SNI相关的逻辑都会在前文设置的回调函数 ngx_http_ssl_certificate()

中完成。nginx框架与ssl模块只是正常的完成业务逻辑处理。

在回调函数中,首先会申请一个request结构体,作为中间存储。然后通过openssl的BIO接口完成证书链的加载,并关联至

openssl的connect结构里。然后,刚刚申请的request会被释放(这里边新建了一个pool,这个pool是从操作系统申请的内存)。

该过程与配置阶段共同复用了函数 ngx_ssl_load_certificate(), 整个过程都没有全局上下文ctx的参与,链接结束后证书链会被释放掉。

下次新的链接会重新再加载一次证书链。

4 openssl

分别观察有SNI和没有SNI的两个流程,将发现api的调用序列上有如下区别,关键在于是否使用ctx上下文。

无sni的时候,使用SSL_CTX_use_certificate()函数,有sni的时候,使用SSL_use_certificate()函数。

查找文档,对比一下区别:

  1. # man SSL_CTX_use_certificate
  2. The SSL_CTX_* class of functions loads the certificates and keys into the SSL_CTX object ctx. The information is passed
    to SSL objects ssl created from ctx with SSL_new() by copying, so that changes applied to ctx do not propagate to already existing SSL objects.
  3.  
  4. The SSL_* class of functions only loads certificates and keys into a specific SSL object. The specific
    information is kept, when SSL_clear() is called for this SSL object.

从侧面印证我们的分析结果,感兴趣的可以完整的阅读手册,这里不在赘述。

5 实验

截止到目前所有的分析都是基于nginx的,将openssl作为黑盒。并不能严谨的排除openssl为此做了特别优化,去防止多次加载

证书链的可能。 为了印证分析,做如下实验,配置SNI后,使用相同的证书多次请求。通过gdb观察,是否每次都进入回调函数并重新加载

证书链。

  1. if (ngx_strncmp(cert->data, "data:", sizeof("data:") - ) == ) {
  2. (gdb) n
  3. if (ngx_get_full_name(pool, (ngx_str_t *) &ngx_cycle->conf_prefix, cert)
  4. (gdb)
  5. bio = BIO_new_file((char *) cert->data, "r");
  6. (gdb)
  7. if (bio == NULL) {
  8. (gdb)
  9. x509 = PEM_read_bio_X509_AUX(bio, NULL, NULL, NULL);
  10. (gdb) bt
  11. # ngx_ssl_load_certificate (pool=0x55825f636240, err=0x7ffd32750868, cert=0x7ffd32750900, chain=0x7ffd32750870) at src/event/ngx_event_openssl.c:
  12. # 0x000055825ed31d79 in ngx_ssl_connection_certificate (c=0x55825f64c0c0, pool=0x55825f636240, cert=0x7ffd32750900, key=0x7ffd32750910, passwords=0x55825f0df860 <empty_passwords.>) at src/event/ngx_event_openssl.c:
  13. # 0x000055825ee378ef in ngx_stream_ssl_certificate (ssl_conn=0x55825f632c70, arg=0x55825f63def0) at src/stream/ngx_stream_ssl_module.c:
  14. # 0x00007efed0277b3c in tls_post_process_client_hello () from /opt/openssl/lib/libssl.so.1.1
  15. # 0x00007efed026d362 in state_machine () from /opt/openssl/lib/libssl.so.1.1
  16. # 0x00007efed0265991 in SSL_do_handshake () from /opt/openssl/lib/libssl.so.1.1
  17. # 0x000055825ed33f27 in ngx_ssl_handshake (c=0x55825f64c0c0) at src/event/ngx_event_openssl.c:
  18. # 0x000055825ed34671 in ngx_ssl_handshake_handler (ev=0x55825f66dec0) at src/event/ngx_event_openssl.c:
  19. # 0x000055825ed2dae8 in ngx_epoll_process_events (cycle=0x55825f62d4a0, timer=, flags=) at src/event/modules/ngx_epoll_module.c:
  20. # 0x000055825ed185cf in ngx_process_events_and_timers (cycle=0x55825f62d4a0) at src/event/ngx_event.c:
  21. # 0x000055825ed2a4d4 in ngx_worker_process_cycle (cycle=0x55825f62d4a0, data=0x0) at src/os/unix/ngx_process_cycle.c:
  22. # 0x000055825ed26305 in ngx_spawn_process (cycle=0x55825f62d4a0, proc=0x55825ed2a3d4 <ngx_worker_process_cycle>, data=0x0, name=0x55825ee6e4fb "worker process", respawn=-) at src/os/unix/ngx_process.c:
  23. # 0x000055825ed29018 in ngx_start_worker_processes (cycle=0x55825f62d4a0, n=, type=-) at src/os/unix/ngx_process_cycle.c:
  24. # 0x000055825ed284fb in ngx_master_process_cycle (cycle=0x55825f62d4a0) at src/os/unix/ngx_process_cycle.c:
  25. # 0x000055825ecdf1d9 in main (argc=, argv=0x7ffd327511a8) at src/core/nginx.c:
  26. (gdb)

(留个调用栈,方便以后看。)

通过实验,证书每次都会通过api BIO_new_file 进行重新加载,除非该函数做了优化,否则每次链接的每个证书都将发生磁盘IO操作。

结论

配置了SNI功能之后,nginx对TLS链接请求的处理将产生性能损失。损失的粒度为每链接级别。

每个ssl/tls链接都将额外发生:一次内存申请与释放;一组加载证书链上全部证书的磁盘IO。

改进方案

我认为内存的申请可以忽略,因为原本处理request的时候也是需要向OS申请内存的。比较关键的在于磁盘IO。

第一个方案,可以将所有证书放置在内存文件系统中,此方案不需要调整代码。

第二个方案,重写ngx_http_ssl_certificate(), 虽然证书是动态选择的,但是只要在备选集合确定的情况下,我们依然

可以进行预加载,去除掉运行时的IO。引入的限制为:1备选集合不能实时变更,2证书集合元素数量需要设置上限。

[classic_tong @ 20191023]

[nginx] nginx源码分析--SNI性能分析的更多相关文章

  1. HashMap的源码学习以及性能分析

    HashMap的源码学习以及性能分析 一).Map接口的实现类 HashTable.HashMap.LinkedHashMap.TreeMap 二).HashMap和HashTable的区别 1).H ...

  2. lesson8:AtomicInteger源码解析及性能分析

    AtomicInteger等对象出现的目的主要是为了解决在多线程环境下变量计数的问题,例如常用的i++,i--操作,它们不是线程安全的,AtomicInteger引入后,就不必在进行i++和i--操作 ...

  3. ThreadLocal源码及相关问题分析

    前言 在高并发的环境下,当我们使用一个公共的变量时如果不加锁会出现并发问题,例如SimpleDateFormat,但是加锁的话会影响性能,对于这种情况我们可以使用ThreadLocal.ThreadL ...

  4. 编译nginx的源码安装subs_filter模块

    使用nginx的反向代理功能搭建nuget镜像服务器时,需要针对官方nuget服务器的响应内容进行字符串替换,比如将www.nuget.org替换为镜像服务器的主机名,将https://替换为http ...

  5. ArrayList源码和多线程安全问题分析

    1.ArrayList源码和多线程安全问题分析 在分析ArrayList线程安全问题之前,我们线对此类的源码进行分析,找出可能出现线程安全问题的地方,然后代码进行验证和分析. 1.1 数据结构 Arr ...

  6. Nginx unit 源码安装初体验

    Nginx unit 源码安装初体验 上次介绍了从yum的安装方法(https://www.cnblogs.com/wang-li/p/9684040.html),这次将介绍源码安装,目前最新版为1. ...

  7. Okhttp3源码解析(3)-Call分析(整体流程)

    ### 前言 前面我们讲了 [Okhttp的基本用法](https://www.jianshu.com/p/8e404d9c160f) [Okhttp3源码解析(1)-OkHttpClient分析]( ...

  8. Okhttp3源码解析(2)-Request分析

    ### 前言 前面我们讲了 [Okhttp的基本用法](https://www.jianshu.com/p/8e404d9c160f) [Okhttp3源码解析(1)-OkHttpClient分析]( ...

  9. Spring mvc之源码 handlerMapping和handlerAdapter分析

    Spring mvc之源码 handlerMapping和handlerAdapter分析 本篇并不是具体分析Spring mvc,所以好多细节都是一笔带过,主要是带大家梳理一下整个Spring mv ...

随机推荐

  1. redis-查看日志

    转: redis-查看日志 redis在默认情况下,是不会生成日志文件的,所以需要配置 配置方法: 1.首先找到redis的配置文件 2.打开配置文件,找到logfile(可能有多个logfile,认 ...

  2. 深入分析GCC

    深入分析GCC 目录 前言章 GCC概述 11.1 GCC的产生与发展 11.2 GCC的特点 21.3 GCC代码分析 3第2章 GCC源代码分析工具 42.1 vim ctags代码阅读工具 42 ...

  3. [转]彻底解决deepin linux的无线网络问题

    链接地址:https://bbs.deepin.org/forum.php?mod=viewthread&tid=153154

  4. c-lodop回调函数简短问答及相关博文

    回调函数相关博文:C-Lodop回调函数的触发.LODOP.FORMAT格式转换[回调和直接返回值].Lodop导出excel及提示成功[回调和直接返回值].c-lodop获取任务页数-回调里给全局变 ...

  5. 04 javascirpt基础知识---听课笔记

    1.JavaScript概念 一门客户端脚本语言运行在客户端浏览器中的.每一个浏览器都有JavaScript的解析引擎脚本语言:不需要编译,直接就可以被浏览器解析执行了 * 功能:可以来增强用户和ht ...

  6. handy网络库源码阅读

    简洁易用的C++11网络库,From:https://github.com/yedf/handy 在整理过去的资料过程中,发现过去有关注过这一个网络库,简单看了一下属于轻量级的实现,因此本文将对该库进 ...

  7. [HAOI2008]硬币购物-题解

    传送门 解答 根据容斥原理 \[ \left|\bigcap_{i=1}^n \overline{S_i}\right| = |U| - \left|\bigcup_{i=1}^n S_i\right ...

  8. zap+日志分级分文件+按时间切割日志整合demo

    实现功能     info debug 级别的日志输出到 /path/log/demo.log     warn error .... 级别的日志输出到 /path/log/demo_error.lo ...

  9. RabbitMQ学习记录1

    前言 我是在解决分布式事务的一致性问题时了解到RabbitMQ的,当时主要是要基于RabbitMQ来实现我们分布式系统之间对有事务可靠性要求的系统间通信的.关于分布式事务一致性问题及其常见的解决方案, ...

  10. 解析:让你弄懂redux原理

    作者: HerryLo 本文永久有效链接: https://github.com/AttemptWeb...... Redux是JavaScript状态容器,提供可预测化的状态管理. 在实际开发中,常 ...