gitbook/手把手带你搭建秒杀系统/docs/428142.md

232 lines
17 KiB
Markdown
Raw Permalink Normal View History

2022-09-03 22:05:03 +08:00
# 11高性能优化物理机极致优化
你好,我是志东,欢迎和我一起从零打造秒杀系统。
经过前面章节的学习,咱们逻辑代码层面的优化,基本都已经差不多了。这个时候,再制约系统性能的,往往是些逻辑代码之外的因素了,这些我们可能很少接触,但却非常重要。
所以今天我们将学习一下物理机相关的优化思路以及部署在物理机上的Nginx的配置优化。同时说明一下以下的优化都是在Linux平台下的优化方式在其他平台的话部分优化可能不支持。
## **物理机优化**
以下优化,在搭建生产环境时,你可以协同运维部门的同事一起完成,你可以将优化的思路告诉他们,由他们来完成操作会更加合适。
### CPU模式的优化
所谓CPU模式的调整就是调整CPU的工作频率使其呈现出不同的性能表现以满足特定的业务使用场景。我们一般使用的Linux系统也都有多种模式可供选择像PowerSave、OnDemand、Interactive、Performance等每个模式的调频方式都不同。
因为考虑到秒杀业务的特殊性,并且有时候活动非常的火热,但过段时间可能就降温了些,所以我们采用的模式也不相同。
像大促期间或者某段时间部分商品持续大力度营销这时的活动非常火热流量也高所以我们需要将CPU模式调整成Performance即高性能模式。这时CPU一直处于超频状态当然这种状态也是比较耗电的但是为了更好地开展活动还是需要打开的。
而当活动处于日常化时此时流量较大促有很大差异并且每天的流量相对稳定这时候我们就可以将CPU模式切回成PowerSave模式即节能模式或者是切回系统的默认模式OnDemand。这样的话可以兼顾性能与资源开销高性价比地支持秒杀活动。
### 网卡中断优化
那调完了CPU另一个需要优化的点就是网卡中断了。
“中断”是机器硬件与CPU交互的一种方式即硬件告诉CPU有事情要处理了。而网卡中断就是机器网卡告诉CPU要处理网络数据了。
前面我们就有说过秒杀的瞬时流量非常高带来的问题就是一下子会有非常多的网络请求进来。网卡在收到网络信号后会通知CPU来处理这时如果我们没有调整过相关配置那么很有可能处理网卡中断的CPU都集中在一个核上。
如果这个时候该CPU也在承担处理应用进程的任务那么就有可能出现单核CPU飙升的问题同时网络数据的处理也会受到影响导致大量TCP重传现象的发生。所以这个时候我们要做的就是合理分配多核CPU资源专门拿出一个核来处理网卡中断。
**操作的过程大致可以分成3步**
1. 查看在流量高峰时,是否处理网卡中断的工作都集中在同一个核上;
2. 找到网卡中断的IRQ硬件设备的一个编号让CPU知道是哪个硬件的中断信号
3. 将网卡的IRQ与一个特定的CPU核进行绑定。
我们这么做的目的其实就是在多核CPU下让一个进程在某个给定的CPU上尽量长时间地运行而不被迁移到其他处理器。这样做的好处就是一方面可以减少CPU调度产生的开销另一方面可以提高每个CPU核的缓存命中率。
同样地如果我们的Nginx服务和我们的Redis也安装在同一台物理机上那么我们也可以将Redis进程以及Nginx进程分别绑定到不同的核上。比如我们有16核那这个时候我们可以将其中的10核绑定Nginx进程2核用来绑定Redis实例1核用来绑定处理网卡中断剩下的可以选择再开Nginx实例或者给其他进程使用这样每个核都可以专注处理自己的进程任务不用切换拷贝内存等从而大大提高了整体的服务响应性能。
以上Redis的绑核操作可以通过taskset来完成而Nginx的绑核则可以通过Nginx的配置来直接绑定。那么接下来我们就一起看下Nginx配置方面的优化点。
## **Nginx配置优化**
前面在介绍Nginx的时候我有给你展示过这张Nginx配置的模块结构图每个模块都可以做相应的配置。
![图片](https://static001.geekbang.org/resource/image/af/e8/af0a2cd9ab08af631c5ab95ae91141e8.png?wh=1726x1656)
下面我们就按照顺序,依次介绍下各模块中重要配置的优化方式,并在最后附上一个最终建议优化配置。
### 全局模块配置
首先是全局模块配置 worker\_processes即用来处理网络请求的工作进程数。这个配置参数的设置非常关键它直接会影响到整个Nginx服务的吞吐量设置小了发挥不了服务器的硬件水平设置过大还会起到反效果。
那设置多少合适呢这个就要看我们机器的硬件配置了我们建议工作进程数和CPU核数保持一致这是种比较理想的状态。但就像上面提到的如果机器上还部署了其他应用像Redis实例等那这个时候就要考虑到其他应用对CPU资源的占用并且需要结合绑核操作尽量让一个CPU核能专门处理一个工作进程。所以我们下面结合绑核配置一起看下。
**worker\_cpu\_affinity**绑核的目的上面也已经介绍过了。当我们了解了机器的配置以及部署在机器的应用以后我们就可以合理地分配CPU资源以4核CPU绑核为例其绑定语法如下
```plain
worker_cpu_affinity   0001 0010 0100 1000;
```
针对语法的说明可参考下图:
![](https://static001.geekbang.org/resource/image/25/d7/25yy8caa109ebfc1f9b21d8af7164ed7.jpg?wh=1566x726)
通过合理地设置工作进程并将工作进程与CPU一一绑定可以有效利用机器资源提高服务的吐吞量与整体响应性能。
那说到吞吐量呢还得介绍一个关键的配置参数即工作进程可以打开的最大文件描述符数量其配置指令为worker\_rlimit\_nofile。
**worker\_rlimit\_nofile**我们都知道网络通信的底层是通过socket来建立连接的而每一个socket又会打开一个文件描述符所以文件描述符的数量设置会影响服务器处理网络连接的上限。如果设置过小那么超过文件描述符数量的连接都会被直接返回。而该配置又和单个工作进程可以建立的最大连接数量息息相关所以我们和下面的worker\_ connections一起说下。
### events模块配置
worker\_ connections指令是在events模块配置中使用的。
**worker\_connections**刚上面提到单个工作进程可以建立的最大连接数量是受worker\_rlimit\_nofile配置限制的理论上该值的设置应等于最大文件描述符数量除以工作进程数但因为工作进程处理请求并不是均匀的所以将该值的设置只要小于等于最大文件描述符数量即可。一般在线上使用时我们会将这两个的配置值都设置为65535。
那既然说到event模块的配置那我们就再说几个其他的常用配置。
**accept\_mutex**这个指令是用来配置工作进程接受新连接的方式。如果开启那么工作进程会轮流接受新的连接即采用互斥锁的方式。否则的话当有新连接进来之后所有的工作进程都会被唤醒但只有一个可以接受新连接并处理。其他进程如果没有连接处理则过段时间会继续进入等待状态而这个时间段内对CPU资源是有消耗的。由此可见如果我们流量较小时建议打开相反则建议关闭。如果该配置启用那么最好也设置一下以下配置。
**accept\_mutex\_delay**该指令是配合accept\_mutex来使用是设置工作进程取得互斥锁后接受新连接的超时时间。超过设置时间其他进程将可以获得互斥锁这样可以防止上个进程拿到锁后一直不释放导致处理请求受阻。
### HTTP模块配置
那events配置模块差不多就这些了下面我们开始介绍HTTP模块的配置。这块都是关于HTTP请求处理相关的设置比较多我们这里会总结出一些针对秒杀场景的常用优化项。
**sendfile这个是操作系统用来优化文件传输提供的一个函数。**
正常的文件传输,读时需要将数据文件从硬盘拷到内核空间,再从内核空间拷贝到用户空间,写时再依次拷贝出,同时也伴随着上下文的切换。
而sendfile的做法是省略掉了内核空间和用户空间的拷贝以及上下文切换操作这也叫做零拷贝节省资源的同时提高了效率具体如下图所示
![](https://static001.geekbang.org/resource/image/fa/48/fafd20fe607cbab6e021cecc9e865b48.jpg?wh=1956x672)
因为秒杀有文件传输的场景所以建议打开这个配置通过upstream的文件传输是不受该配置影响的。打开该配置后就可以继续使用另一个指令tcp\_nopush了。
**tcp\_nopush该配置只有在打开sendfile配置的情况下才能生效。**
简单来说该配置是关于TCP传输的配置。如果打开了nopush那么在Nginx响应客户端请求时会优化响应数据包的发送模式即将多个较小的数据包合并发送就像响应头和响应体。这样做的好处是可以减少网络拥堵优化网络传输当然也会牺牲稍许实时性的体验。
那么说到这就不得不说下另一个关于TCP传输的配置了。
**tcp\_nodelay顾名思义如果开启就是用来降低网络延时的。**
其做法和上面的tcp\_nopush相反追求数据包的实时传输对于秒杀这种网络负载较高的场景一般不推荐打开并且该配置只在长连接条件下才能生效而秒杀结算页的HTTP请求一般都使用短连接这里以及下文提到的长、短连接都是指TCP层面的。那在Nginx上也可以通过指令来配置客户端的连接处理方式即keepalive\_timeout。
**keepalive\_timeout一般HTTP请求是要使用长连接还是短连接需要客户端和服务端都支持。**
客户端在使用HTTP1.1之后的协议版本,默认都是长连接,如下图所示:
![](https://static001.geekbang.org/resource/image/ea/d9/ea7bc78ffbd0293d9f60babbd63515d9.jpg?wh=879x477)
协议版本是HTTP2.0的话如果没有特殊配置请求头里会有keep-alive的设置。但我们可以在Nginx通过配置空闲连接的存活时间为0来关闭长连接使其变成短连接。效果如下
![](https://static001.geekbang.org/resource/image/a2/cc/a2514a8292e2d42b06f6d25668b3b3cc.png?wh=764x696)
我们知道Nginx要连接的不仅有客户端还有下游的Web服务。那针对上游客户端以及下游服务端采用的连接方式会有什么不同呢我们看下这张图
![](https://static001.geekbang.org/resource/image/78/ff/78c7683c0c937d35934fd1d6abbb47ff.jpg?wh=1508x818)
Nginx之所以针对客户端采用短连接是因为客户端数量太多了且交互不频繁。如果都用长连接那么很快服务端连接将会被占完。
但对于下游Nginx相对就成了客户端了其数量只有几台到几十台之间不等且与后端Tomcat交互是十分频繁的如果不停地创建连接将会造成非常大的性能损耗所以最好采用长连接的方式。长连接配置的方式就放到最后的汇总配置里了。
**最后再说几个与下游Web服务相关的超时配置。**
它们分别是proxy\_connect\_timeout、proxy\_send\_timeout、proxy\_read\_timeout即连接建立超时时间、发送请求超时时间、读取响应超时时间。
如果不合理设置这些超时时间就会因为各种网络状况导致Nginx与Web服务之间数据传输受阻客户端将会一直等待体验极差。所以我们需要根据接口的正常响应时间设置一个合理的超时时间等待超过超时配置再将返回提示给到用户。具体的设置我也放到汇总配置里了。
## **总结**
这节课我们介绍了物理机与Nginx相关配置的优化优化的方向无非是对内存、CPU、IO磁盘IO和网络IO的优化。
所以对于物理机我们主要从调整CPU的工作模式来入手根据不同的秒杀业务场景我们切换不同的工作模式。像大促期间或者活动非常火热时我们将CPU模式切换成高性能模式以得到更好的响应性能当然代价就是耗电增加如果活动比较平稳我们可以考虑切换成省电模式这样可以节约成本。
另外在秒杀高峰期间网络负载重TCP重传现象频发我们也做了针对性的措施即通过绑定专门CPU来处理网卡中断。当然绑核的操作不只可以针对网卡中断还可以绑定Nginx进程以及部署的其他应用服务。这么做的目的一方面是为了减少CPU调度产生的开销另一方面也可以提高每个CPU核的缓存命中率。
之后我们又讲到了Nginx的优化分别针对客户端以及下游服务端从网络的连接、传输、超时等方面做了不同的配置讲解。具体的Nginx优化配置你可以参考以下示例。
nginx.conf配置如下
```plain
#工作进程根据CPU核数以及机器实际部署项目来定建议小于等于实际可使用CPU核数
worker_processes 2;
#绑核MacOS不支持。
#worker_cpu_affinity   01 10;
#工作进程可打开的最大文件描述符数量建议65535
worker_rlimit_nofile 65535;
#日志:路径与打印级别
error_log logs/error.log error;
events {
    #指定处理连接的方法可以不设置默认会根据平台选最高效的方法比如Linux是epoll
    #use epoll;
    #一个工作进程的最大连接数默认512建议小于等于worker_rlimit_nofile
    worker_connections 65535;
    #工作进程接受请求互斥默认值off,如果流量较低可以设置为on
    #accept_mutex off;
    #accept_mutex_delay 50ms;
}
http {
        #关闭非延时设置
        tcp_nodelay  off;
        #优化文件传输效率
        sendfile     on;
        #降低网络堵塞
        tcp_nopush   on;
        #与客户端使用短连接
        keepalive_timeout  0;
        #与下游服务使用长连接,指定HTTP协议版本并清除header中的Connection默认是close
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        #将客户端IP放在header里传给下游不然下游获取不到客户端真实IP
        proxy_set_header X-Real-IP $remote_addr;
        #与下游服务的连接建立超时时间
        proxy_connect_timeout 500ms;
        #向下游服务发送数据超时时间
        proxy_send_timeout 500ms;
        #从下游服务拿到响应结果的超时时间可以简单理解成Nginx多长时间内拿不到响应结果就算超时
#这个根据每个接口的响应性能不同可以在每个location单独设置
        proxy_read_timeout 3000ms;
        
        #开启响应结果的压缩
        gzip on;
        #压缩的最小长度,小于该配置的不压缩
        gzip_min_length  1k;
        #执行压缩的缓存区数量以及大小,可以使用默认配置,根据平台自动变化
        #gzip_buffers     4 8k;
        #执行压缩的HTTP请求的最低协议版本可以不设置默认就是1.1
        #gzip_http_version 1.1; 
        #哪些响应类型会执行压缩如果静态资源放到CDN了那这里只要配置文本和html即可
        gzip_types      text/plain;
         
        #acccess_log的日志格式
        log_format  access  '$remote_addr - $remote_user [$time_local] "$request" $status '
            '"$upstream_addr" "$upstream_status" "$upstream_response_time" userId:"$user_id"';
        #加载lua文件
        lua_package_path "/Users/~/Documents/seckillproject/demo-nginx/lua/?.lua;;";
        #导入其他文件
        include /Users/~/Documents/seckillproject/demo-nginx/domain/domain.com;
        include /Users/~/Documents/seckillproject/demo-nginx/domain/internal.com;
        include /Users/~/Documents/seckillproject/demo-nginx/config/upstream.conf;
        include /Users/~/Documents/seckillproject/demo-nginx/config/common.conf;
}
```
当然,今天说到的这些优化方向和优化点,考虑到机器配置、网络环境、业务功能等因素的差异,我们还需要依据实际压测效果来做灵活的调整,但总体优化思路是不变的。
## **思考题**
以上针对Nginx的优化配置有很多的指令这些指令可以在不同的模块进行配置比如像proxy\_set\_header这种既可以在HTTP全局模块配置也可以在server模块进行配置那类似这样的指令设置都会考虑哪些因素呢
期待你的思考,欢迎在评论区中和我讨论问题,交流经验!我们下节课再见。