本文使用「署名 4.0 国际 (CC BY 4.0)」许可协议,欢迎转载、或重新修改使用,但需要注明来源。 [署名 4.0 国际 (CC BY 4.0)](https://creativecommons.org/licenses/by/4.0/deed.zh) 本文作者: 苏洋 创建时间: 2020年11月16日 统计字数: 29252字 阅读时间: 59分钟阅读 本文链接: https://soulteary.com/2020/11/16/nine-redirection-schemes-and-performance-comparison.html ----- # 重定向的九种方案及性能比较 说到重定向,大家必然不会陌生,最常见的场景之一便是各种文章、社交媒体上的短链接。 最近计划对于之前的短链接服务进行升级改造。在改造前,对于常见 Web 语言,如 Java、PHP、Python、Node、Ruby、Go和服务工具 Nginx、Caddy、Traefik 做了简单的对比分析。 希望这篇文章能够帮你在这个技术场景下,更立体的了解到各种语言/工具的基础性能差异。 ## 写在前面 这里我使用了两台按量付费的云主机进行测试,一台作为测试服务器,用于运行不同语言/工具的代码,另外一台作为测试发起方,对测试服务器模拟发起大量用户请求。 两台服务器的系统配置完全一致,均为 4c4g 的阿里云密集计算型服务器,型号名称“`ecs.ic5.xlarge`”,主频 2.5 GHz/2.7 GHz,采用“天湖”架构的 CPU,系统选择容器时代的宠儿:Ubuntu Server。 在进行测试分析前,进行以下约定: - 所有语言和工具的测试均使用容器环境,编排执行均使用 compose 执行对应的 docker-compose.yml 来完成,镜像均使用 Alpine 系统版本。 - 所有语言均不使用框架完成转发逻辑,尽量使用“原生模块”处理请求。 - 所有语言的代码实现和工具配置,代码量和代码复杂度要尽可能少。 - 所有语言和工具都进行100并发和1000并发下的10万次请求测试。 ## 配置测试环境 两台机器默认的环境不需要很复杂,仅安装 **docker** 和 **compose** 两个工具足以,可以参考下面的命令,完成初始系统的安装: ```bash apt update && apt upgrade -y apt remove docker docker-engine docker.io apt install -y apt-transport-https ca-certificates curl software-properties-common curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - apt-key fingerprint 0EBFCD88 add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" apt install -y docker-ce curl -L https://github.com/docker/compose/releases/download/1.27.4/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose chmod +x /usr/local/bin/docker-compose ``` 安装完毕后,先验证软件版本,均为以下结果,顺便检查 ulimit 限制: ```bash # docker -v Docker version 19.03.13, build 4484c46d9d # docker-compose -v docker-compose version 1.27.4, build 40524192 # ulimit unlimited ``` 查看到 `ulimit` 结果为 `unlimited`,说明未对系统资源进行限制。 因为需要在一台服务器上发起测试请求,所以在测试服务器上执行 `apt install apache2-utils` ,获取通用测试工具 `ab`,输入下面的命令,可以查看到工具的具体版本等信息: ```bash # apt show apache2-utils Package: apache2-utils Version: 2.4.41-4ubuntu3.1 ... ``` ### 安装 Linux Dash 监控面板 为了更直观的获取测试过程服务器的整体性能和资源使用,我们使用 `linux-dash` 这个开源工具,并使用 Golang 版本执行,尽可能减少对系统资源的占用,影响各种测试结果。 在 Ubuntu 环境下安装 Golang 极其简单,下载,解压,设置环境变量即可: ```bash wget https://golang.org/dl/go1.15.5.linux-amd64.tar.gz tar -C /usr/local -xzf go1.15.5.linux-amd64.tar.gz export PATH=$PATH:/usr/local/go/bin ``` 安装完毕,验证 Golang 版本如下: ```bash go version go version go1.15.5 linux/amd64 ``` 然后是下载 Linux Dash 的源码,并进行简单的构建和运行。 ```bash git clone --depth 1 https://github.com/afaqurk/linux-dash.git cd linux-dash/app/server go build index.go ``` 执行 `./index` 之后,你会看到运行的日志:“Starting http server at: 0.0.0.0:80”。接着打开浏览器,就能看到实时的性能监控面板了。 ### 测试命令 我们使用的测试命令十分简单,只需要三条即可: - 使用 `curl -I -X "GET" http://192.168.23.55:8080/` 来验证服务联通性以及查看服务端响应结果。 - 使用 `ab -n 100000 -c 100 http://192.168.23.55:8080/` 来测试100并发数情况下的服务能力。 - 使用 `ab -n 100000 -c 1000 http://192.168.23.55:8080/` 来测试1000并发数情况下的服务能力。 ## PHP 江湖流传 PHP 是最好的语言,所以先对 PHP 进行测试。 ### PHP - 官方通用镜像 考虑到 PHP 每代版本都有较大性能提升,所以我们采用官方最新的 `php:8.0.0RC4-apache` 镜像来进行测试。 测试使用的 compose 配置如下: ```yaml version: "3" services: php-apache: image: php:8.0.0RC4-apache volumes: - ./src:/var/www/html/ ports: - 8080:80 ``` 将上面的内容保存为 `docker-compose.yml` 后,在同级目录中创建一个 `index.php` 即可开始我们的测试,在 PHP 中完成重定向操作非常简单,只需要一句话: ```php ``` 先执行一次请求,可以看到服务器的响应如下: ```TeXT HTTP/1.1 301 Moved Permanently Date: Mon, 16 Nov 2020 02:17:21 GMT Server: Apache/2.4.38 (Debian) X-Powered-By: PHP/8.0.0RC4 Location: http://localhost:1024 Content-Type: text/html; charset=UTF-8 ``` 接着进行 100 并发数下 10万次请求: ```TeXT Server Software: Apache/2.4.38 Server Hostname: 192.168.23.55 Server Port: 8080 Document Path: / Document Length: 0 bytes Concurrency Level: 100 Time taken for tests: 11.683 seconds Complete requests: 100000 Failed requests: 0 Non-2xx responses: 100000 Total transferred: 24200000 bytes HTML transferred: 0 bytes Requests per second: 8559.09 [#/sec] (mean) Time per request: 11.683 [ms] (mean) Time per request: 0.117 [ms] (mean, across all concurrent requests) Transfer rate: 2022.75 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 0 0.1 0 6 Processing: 0 12 5.1 11 118 Waiting: 0 11 3.7 11 102 Total: 1 12 5.1 11 119 Percentage of the requests served within a certain time (ms) 50% 11 66% 11 75% 12 80% 12 90% 14 95% 17 98% 26 99% 35 100% 119 (longest request) ``` 再继续进行 1000 并发下10万次请求测试: ```TeXT Server Software: Apache/2.4.38 Server Hostname: 192.168.23.55 Server Port: 8080 Document Path: / Document Length: 0 bytes Concurrency Level: 1000 Time taken for tests: 13.549 seconds Complete requests: 100000 Failed requests: 0 Non-2xx responses: 100000 Total transferred: 24200000 bytes HTML transferred: 0 bytes Requests per second: 7380.66 [#/sec] (mean) Time per request: 135.489 [ms] (mean) Time per request: 0.135 [ms] (mean, across all concurrent requests) Transfer rate: 1744.26 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 7 91.3 0 3043 Processing: 6 115 825.7 59 13519 Waiting: 1 115 825.7 59 13519 Total: 23 122 832.2 59 13544 Percentage of the requests served within a certain time (ms) 50% 59 66% 61 75% 62 80% 64 90% 67 95% 73 98% 95 99% 1064 100% 13544 (longest request) ``` 观察控制台,可以看到 CPU 使用率在 70%,系统负载飙升至 49,内存用量从9%升到了12%。 ### PHP - 官方内置 Server 前文提到,我们尽量使用原生的服务能力,PHP 其实很早就内置了一个 Web Server,所以我们可以使用其内置的 Server 来尝试提供 Web 服务。 测试使用的 compose 配置和之前区别不大,调整一下 `command` 指令即可: ```php version: "3.6" services: php8: image: php:8.0.0RC4-apache volumes: - ./src/:/var/www/html/ ports: - 8080:80 command: php -S 0.0.0.0:80 -t /var/www/html/ ``` 查看服务响应,相比较之前多了一行 `Connection` 响应信息,其余没有太大变化。 ```TeXT HTTP/1.1 301 Moved Permanently Host: 192.168.23.55:8080 Date: Mon, 16 Nov 2020 02:34:39 GMT Connection: close X-Powered-By: PHP/8.0.0RC4 Location: http://localhost:1024 Content-type: text/html; charset=UTF-8 ``` 接着进行 100 并发数下 10万次请求: ```TeXT Server Software: Server Hostname: 192.168.23.55 Server Port: 8080 Document Path: / Document Length: 0 bytes Concurrency Level: 100 Time taken for tests: 15.348 seconds Complete requests: 100000 Failed requests: 0 Non-2xx responses: 100000 Total transferred: 21700000 bytes HTML transferred: 0 bytes Requests per second: 6515.38 [#/sec] (mean) Time per request: 15.348 [ms] (mean) Time per request: 0.153 [ms] (mean, across all concurrent requests) Transfer rate: 1380.70 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 0 0.2 0 13 Processing: 2 15 1.5 15 30 Waiting: 0 15 1.4 15 30 Total: 2 15 1.4 15 31 Percentage of the requests served within a certain time (ms) 50% 15 66% 15 75% 15 80% 15 90% 18 95% 18 98% 19 99% 19 100% 31 (longest request) ``` 再继续进行 1000 并发下10万次请求测试: ```TeXT ab -n 100000 -c 1000 http://192.168.23.55:8080/ This is ApacheBench, Version 2.3 <$Revision: 1843412 $> Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Licensed to The Apache Software Foundation, http://www.apache.org/ Benchmarking 192.168.23.55 (be patient) Completed 10000 requests Completed 20000 requests Completed 30000 requests Completed 40000 requests Completed 50000 requests Completed 60000 requests Completed 70000 requests Completed 80000 requests Completed 90000 requests apr_socket_recv: Connection reset by peer (104) Total of 99594 requests completed ``` 观察控制台,可以看到 CPU 使用率在 49%,系统负载飙升至 44,内存用量几乎没有变化。 这里 PHP 内置服务的性能有一些跟不上了,有一些请求因为超时中断,未能完成测试。虽然这个测试没有完成,但是结合100并发场景的测试结果,足够说明这个模式用于简单测试使用、或者个人场景来说是满足的,但是慎用于生产环境。 ### PHP 启用 OPCache 在 PHP 使用场景下,为了获取更好的语言性能,我们一般会开启 OPCache 。 在容器环境中,默认 PHP 配置是以模版形式保存在 `/usr/local/etc/php/php.ini-production` 路径,我们在配置文件中找到 `OPCache` 配置小节。 ```TeXT [opcache] ; Determines if Zend OPCache is enabled ;opcache.enable=1 ; Determines if Zend OPCache is enabled for the CLI version of PHP ;opcache.enable_cli=0 ; The OPcache shared memory storage size. ;opcache.memory_consumption=128 ... ``` 将和 OPCache 配置有关的项目前的注释分号全部去掉,然后将 `opcache.enable_cli` 设置为 `1`,再将 `;zend_extension=opcache` 改为 `zend_extension=opcache`,最后将内容保存为 `php.ini`。 接着编写 compose 配置文件: ```yml version: "3" services: php-apache: image: php:8.0.0RC4-apache volumes: - ./src:/var/www/html/ - ./php.ini:/usr/local/etc/php/php.ini ports: - 8080:80 ``` 启动服务后,我们将会收到类似下面的服务器响应。 ```TeXT HTTP/1.1 301 Moved Permanently Date: Mon, 16 Nov 2020 02:51:44 GMT Server: Apache/2.4.38 (Debian) X-Powered-By: PHP/8.0.0RC4 Location: http://localhost:1024 Content-Type: text/html; charset=UTF-8 ``` 接着进行 100 并发数下 10万次请求: ```TeXT Server Software: Apache/2.4.38 Server Hostname: 192.168.23.55 Server Port: 8080 Document Path: / Document Length: 0 bytes Concurrency Level: 100 Time taken for tests: 10.885 seconds Complete requests: 100000 Failed requests: 0 Non-2xx responses: 100000 Total transferred: 24200000 bytes HTML transferred: 0 bytes Requests per second: 9186.73 [#/sec] (mean) Time per request: 10.885 [ms] (mean) Time per request: 0.109 [ms] (mean, across all concurrent requests) Transfer rate: 2171.08 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 0 0.1 0 7 Processing: 0 11 8.9 9 211 Waiting: 0 10 6.6 9 209 Total: 0 11 8.9 9 212 Percentage of the requests served within a certain time (ms) 50% 9 66% 10 75% 10 80% 11 90% 17 95% 27 98% 41 99% 50 100% 212 (longest request) ``` 再继续进行 1000 并发下10万次请求测试: ```TeXT Server Software: Apache/2.4.38 Server Hostname: 192.168.23.55 Server Port: 8080 Document Path: / Document Length: 0 bytes Concurrency Level: 1000 Time taken for tests: 13.379 seconds Complete requests: 100000 Failed requests: 0 Non-2xx responses: 100000 Total transferred: 24200000 bytes HTML transferred: 0 bytes Requests per second: 7474.21 [#/sec] (mean) Time per request: 133.793 [ms] (mean) Time per request: 0.134 [ms] (mean, across all concurrent requests) Transfer rate: 1766.37 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 17 153.6 0 7112 Processing: 4 96 633.4 55 13352 Waiting: 0 94 633.4 55 13352 Total: 20 112 656.0 55 13373 Percentage of the requests served within a certain time (ms) 50% 55 66% 57 75% 60 80% 62 90% 71 95% 89 98% 290 99% 1086 100% 13373 (longest request) ``` 可以看到相比较第一个小节中,在不改动代码,仅调整配置的前提下,使用默认的官方镜像的模式,在 100 并发和 1000 并发的情况下,相比较原来,分别提升了 7% 和 1%,响应时间也有了一些优化和提升。 观察控制台,可以看到 CPU 使用率在 67%,系统负载飙升至 40,内存用量从9%升到了12%。 ## Java Java 是目前虚拟机最强语言,也是 Web 领域通用性最好的语言之一。 ### Java - OpenJDK 镜像未编译 那么来看看 Java 在内置 Web Server 模块下未编译时的表现吧。 依旧是先给出 compose 配置文件。 ```yaml version: "3" services: java: image: openjdk:16-jdk-alpine3.12 ports: - 8080:80 volumes: - ./main.java:/app/main.java command: java /app/main.java ``` 然后我们需要编写不到三十行的脚本,并将它保存为 `main.java`: ```java package com.soulteary.test; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpServer; import java.io.IOException; import java.net.InetSocketAddress; public class Main { public static void main(String[] args) throws Exception { HttpServer httpServer = HttpServer.create(new InetSocketAddress(80), 0); httpServer.createContext("/", new HttpHandler() { @Override public void handle(HttpExchange httpExchange) throws IOException { httpExchange.getResponseHeaders().add("Content-Type", "text/html; charset=UTF-8"); httpExchange.getResponseHeaders().add("Location", "http://localhost:1024"); httpExchange.sendResponseHeaders(301, 0); httpExchange.close(); } }); httpServer.start(); } } ``` 将服务运行起来后,我们能够看到 Java 的响应除了我们代码中声明的两行外,默认比 PHP 要少一些。 ```TeXT HTTP/1.1 301 Moved Permanently Date: Mon, 16 Nov 2020 05:07:37 GMT Content-type: text/html; charset=UTF-8 Location: http://localhost:1024 ``` 进行 100 并发数下 10万次请求: ```TeXT Server Software: Server Hostname: 192.168.23.55 Server Port: 8080 Document Path: / Document Length: 0 bytes Concurrency Level: 100 Time taken for tests: 10.501 seconds Complete requests: 100000 Failed requests: 0 Non-2xx responses: 100000 Total transferred: 16300000 bytes HTML transferred: 0 bytes Requests per second: 9522.61 [#/sec] (mean) Time per request: 10.501 [ms] (mean) Time per request: 0.105 [ms] (mean, across all concurrent requests) Transfer rate: 1515.81 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 4 61.9 0 3046 Processing: 1 6 39.2 5 3304 Waiting: 0 6 39.2 5 3304 Total: 2 10 83.2 5 4324 Percentage of the requests served within a certain time (ms) 50% 5 66% 6 75% 6 80% 6 90% 6 95% 7 98% 8 99% 10 100% 4324 (longest request) ``` 再进行 1000 并发数下 10万次请求: ```TeXT Server Software: Server Hostname: 192.168.23.55 Server Port: 8080 Document Path: / Document Length: 0 bytes Concurrency Level: 1000 Time taken for tests: 16.461 seconds Complete requests: 100000 Failed requests: 0 Non-2xx responses: 100000 Total transferred: 16300000 bytes HTML transferred: 0 bytes Requests per second: 6074.99 [#/sec] (mean) Time per request: 164.609 [ms] (mean) Time per request: 0.165 [ms] (mean, across all concurrent requests) Transfer rate: 967.02 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 32 367.2 0 7205 Processing: 1 37 547.7 6 13379 Waiting: 0 36 547.7 6 13379 Total: 1 68 761.1 6 16418 Percentage of the requests served within a certain time (ms) 50% 6 66% 6 75% 6 80% 6 90% 6 95% 7 98% 76 99% 1217 100% 16418 (longest request) ``` 观察控制台,可以看到 CPU 使用率在 24%,系统负载为 12.56,内存用量从9%升到了13%。 对比之前可以看到,Java 的执行效率很高, 50% 的请求的处理时间在 5ms 内,98 的请求处理速度在 8ms 内。 虽然处理速度很快,但是 100 并发下,仅比开了 OPCache 的 PHP 高 3.6%,而 1000 并发场景下,因为 2% 的请求处理时间太长,导致测试结果反而不是很好看。不过也足以得出类似 PHP 内置 Server 的结论,简单测试和个人使用完全没有问题,线上使用要慎重,最好配合其他服务软件使用。 ### Java - 编译后的程序 在不改动代码的前提下,我们测试下编译后的 Java 程序的执行效率。 先调整 compose 配置: ```yaml version: "3" services: java: image: openjdk:16-jdk-alpine3.12 ports: - 8080:80 volumes: - ./Main.java:/app/Main.java command: sh -c "javac -encoding UTF-8 -nowarn -source 1.8 -target 1.8 -d . Main.java && java com/soulteary/test/Main" ``` 服务器响应和之前除了时间外一样。 ```TeXT HTTP/1.1 301 Moved Permanently Date: Mon, 16 Nov 2020 08:48:17 GMT Content-type: text/html; charset=UTF-8 Location: http://localhost:1024 ``` 进行 100 并发数下 10万次请求: ```TeXT Server Software: Server Hostname: 192.168.23.55 Server Port: 8080 Document Path: / Document Length: 0 bytes Concurrency Level: 100 Time taken for tests: 9.904 seconds Complete requests: 100000 Failed requests: 0 Non-2xx responses: 100000 Total transferred: 16300000 bytes HTML transferred: 0 bytes Requests per second: 10097.39 [#/sec] (mean) Time per request: 9.904 [ms] (mean) Time per request: 0.099 [ms] (mean, across all concurrent requests) Transfer rate: 1607.30 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 4 63.7 0 3044 Processing: 1 6 30.0 5 3351 Waiting: 0 6 30.0 5 3351 Total: 2 10 72.9 5 3353 Percentage of the requests served within a certain time (ms) 50% 5 66% 6 75% 6 80% 6 90% 6 95% 6 98% 7 99% 7 100% 3353 (longest request) ``` 进行 1000 并发数下 10万次请求。 ```TeXT Server Software: Server Hostname: 192.168.23.55 Server Port: 8080 Document Path: / Document Length: 0 bytes Concurrency Level: 1000 Time taken for tests: 27.707 seconds Complete requests: 100000 Failed requests: 0 Non-2xx responses: 100000 Total transferred: 16300000 bytes HTML transferred: 0 bytes Requests per second: 3609.15 [#/sec] (mean) Time per request: 277.074 [ms] (mean) Time per request: 0.277 [ms] (mean, across all concurrent requests) Transfer rate: 574.50 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 22 240.5 0 7212 Processing: 1 58 970.8 5 26658 Waiting: 0 58 970.8 5 26658 Total: 2 81 1044.2 5 27691 Percentage of the requests served within a certain time (ms) 50% 5 66% 6 75% 6 80% 6 90% 6 95% 6 98% 209 99% 1226 100% 27691 (longest request) ``` 观察控制台,可以看到 CPU 使用率在 26%,系统负载为 2.5,内存用量从9%升到了12%。 相比较未编译的程序,可以看到性能提升还是比较大的,以100并发情况为例,性能提升 5%,99%都能在7ms内;而1000并发情况下,95%的请求也能够在 6ms 内完成。不过使用场景和结论依旧不会有变化,内置服务适用于简单测试和个人使用,生产环境使用需要慎重。 ## Go 继续来了解下云原生领域当红炸子鸡:Go 。 ### Go - 未编译情况 先来看看 Golang 在未编译情况下的表现。 依旧是先给出 compose 配置文件: ```yaml version: "3" services: golang: image: golang:1.15.5-alpine3.12 ports: - 8080:80 command: go run /app/main.go volumes: - ./main.go:/app/main.go ``` Go 的代码实现更精简一些,只需要写不到二十行代码。 ```go package main import ( "log" "net/http" ) func redirect(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "http://localhost:1024", 301) } func main() { http.HandleFunc("/", redirect) err := http.ListenAndServe(":80", nil) if err != nil { log.Fatal("ListenAndServe: ", err) } } ``` 将上面的代码保存为 `main.go`,然后启动服务,可以看到响应结果也很精简。 ```TeXT HTTP/1.1 301 Moved Permanently Content-Type: text/html; charset=utf-8 Location: http://localhost:1024 Date: Mon, 16 Nov 2020 04:09:09 GMT ``` 进行 100 并发数下 10万次请求: ```TeXT Server Software: Server Hostname: 192.168.23.55 Server Port: 8080 Document Path: / Document Length: 56 bytes Concurrency Level: 100 Time taken for tests: 5.245 seconds Complete requests: 100000 Failed requests: 0 Non-2xx responses: 100000 Total transferred: 22000000 bytes HTML transferred: 5600000 bytes Requests per second: 19064.01 [#/sec] (mean) Time per request: 5.245 [ms] (mean) Time per request: 0.052 [ms] (mean, across all concurrent requests) Transfer rate: 4095.78 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 1 0.7 1 7 Processing: 0 4 1.9 4 25 Waiting: 0 4 2.0 3 25 Total: 0 5 1.8 5 26 Percentage of the requests served within a certain time (ms) 50% 5 66% 5 75% 6 80% 6 90% 7 95% 8 98% 10 99% 12 100% 26 (longest request) ``` 进行 1000 并发数下 10万次请求: ```TeXT Server Software: Server Hostname: 192.168.23.55 Server Port: 8080 Document Path: / Document Length: 56 bytes Concurrency Level: 1000 Time taken for tests: 5.379 seconds Complete requests: 100000 Failed requests: 0 Non-2xx responses: 100000 Total transferred: 22000000 bytes HTML transferred: 5600000 bytes Requests per second: 18591.49 [#/sec] (mean) Time per request: 53.788 [ms] (mean) Time per request: 0.054 [ms] (mean, across all concurrent requests) Transfer rate: 3994.26 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 17 8.5 19 34 Processing: 8 36 11.5 34 200 Waiting: 0 30 12.9 27 192 Total: 8 53 10.4 53 200 Percentage of the requests served within a certain time (ms) 50% 53 66% 56 75% 59 80% 61 90% 65 95% 69 98% 78 99% 85 100% 200 (longest request) ``` 观察控制台,可以看到 CPU 使用率在 36%,系统负载为 1.29,内存用量从9%升到了10%。 可以看到未编译的 Go 的程序不论是在 100 并发还是 1000 并发下的表现都是非常不错的。动辄1万8到1万9的响应能力(执行效率),侧面说明了为什么开源工具越来越多的由 Go 编写而成。 ### Go - 编译后的程序 编译前的程序的结果已经非常棒了,接着来看看编译后的程序。 在代码不需变化的前提下,调整 compose 配置文件中的 `command` 指令即可: ```yaml version: "3" services: golang: image: golang:1.15.5-alpine3.12 ports: - 8080:80 command: sh -c "go build /app/main.go && ./main" volumes: - ./main.go:/app/main.go ``` 进行 100 并发数下 10万次请求: ```TeXT Server Software: Server Hostname: 192.168.23.55 Server Port: 8080 Document Path: / Document Length: 56 bytes Concurrency Level: 100 Time taken for tests: 5.271 seconds Complete requests: 100000 Failed requests: 0 Non-2xx responses: 100000 Total transferred: 22000000 bytes HTML transferred: 5600000 bytes Requests per second: 18972.32 [#/sec] (mean) Time per request: 5.271 [ms] (mean) Time per request: 0.053 [ms] (mean, across all concurrent requests) Transfer rate: 4076.08 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 1 0.7 1 7 Processing: 0 4 2.1 4 28 Waiting: 0 4 2.1 4 27 Total: 0 5 2.0 5 28 Percentage of the requests served within a certain time (ms) 50% 5 66% 5 75% 6 80% 6 90% 7 95% 9 98% 11 99% 13 100% 28 (longest request) ``` 进行 1000 并发数下 10万次请求: ```TeXT Server Software: Server Hostname: 192.168.23.55 Server Port: 8080 Document Path: / Document Length: 56 bytes Concurrency Level: 1000 Time taken for tests: 5.405 seconds Complete requests: 100000 Failed requests: 0 Non-2xx responses: 100000 Total transferred: 22000000 bytes HTML transferred: 5600000 bytes Requests per second: 18499.78 [#/sec] (mean) Time per request: 54.055 [ms] (mean) Time per request: 0.054 [ms] (mean, across all concurrent requests) Transfer rate: 3974.56 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 13 10.7 16 33 Processing: 0 40 16.5 38 214 Waiting: 0 35 18.1 31 209 Total: 0 54 15.1 54 219 Percentage of the requests served within a certain time (ms) 50% 54 66% 57 75% 60 80% 61 90% 67 95% 77 98% 92 99% 106 100% 219 (longest request) ``` 观察控制台,可以看到 CPU 使用率在 35%,系统负载为 1.3,内存用量始终为9%。 测试结果发现编译前后区别并不大,甚至还略有轻微下降,不过不影响可用可信赖的判断。这里性能轻微下降,有可能是 alpine 版本编译结果的执行效率的问题,后面有时间再进行进一步测试吧。 ## Node.js 接下来试试我最喜欢的语言之一,Node.js。 compose 配置内容如下: ```yaml version: "3" services: nginx: image: node:15.2.0-alpine3.12 volumes: - ./index.js:/app/index.js ports: - 8080:80 command: node /app/index.js ``` 相比较前面的程序(除PHP外),示例程序可以写的更简洁一些: ```javascript var http = require('http'); http.createServer(function (request, response) { response.writeHead(301, { 'Location': 'http://localhost:1024/' }).end(); }).listen(80); ``` 服务运行之后,默认响应内容如下: ```TeXT HTTP/1.1 301 Moved Permanently Location: http://localhost:1024/ Date: Mon, 16 Nov 2020 03:41:15 GMT Connection: keep-alive Keep-Alive: timeout=5 ``` 进行 100 并发数下 10万次请求: ```TeXT Server Software: Server Hostname: 192.168.23.55 Server Port: 8080 Document Path: / Document Length: 0 bytes Concurrency Level: 100 Time taken for tests: 11.449 seconds Complete requests: 100000 Failed requests: 0 Non-2xx responses: 100000 Total transferred: 12400000 bytes HTML transferred: 0 bytes Requests per second: 8734.66 [#/sec] (mean) Time per request: 11.449 [ms] (mean) Time per request: 0.114 [ms] (mean, across all concurrent requests) Transfer rate: 1057.71 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 0 0.4 0 8 Processing: 2 11 2.6 11 43 Waiting: 1 7 1.9 7 35 Total: 3 11 2.5 11 48 Percentage of the requests served within a certain time (ms) 50% 11 66% 12 75% 12 80% 12 90% 13 95% 14 98% 18 99% 23 100% 48 (longest request) ``` 进行 1000 并发数下 10万次请求: ```TeXT Server Software: Server Hostname: 192.168.23.55 Server Port: 8080 Document Path: / Document Length: 0 bytes Concurrency Level: 1000 Time taken for tests: 13.628 seconds Complete requests: 100000 Failed requests: 0 Non-2xx responses: 100000 Total transferred: 12400000 bytes HTML transferred: 0 bytes Requests per second: 7337.80 [#/sec] (mean) Time per request: 136.281 [ms] (mean) Time per request: 0.136 [ms] (mean, across all concurrent requests) Transfer rate: 888.56 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 29 183.8 1 3035 Processing: 29 107 31.6 110 507 Waiting: 4 80 30.7 87 475 Total: 29 136 184.8 113 3105 Percentage of the requests served within a certain time (ms) 50% 113 66% 124 75% 128 80% 130 90% 146 95% 153 98% 1089 99% 1129 100% 3105 (longest request) ``` 观察控制台,可以看到 CPU 使用率在 22%,系统负载为 0.59,内存用量从9%升到10%。 可以看到执行还算及格,虽然性能看起来不如 Go 和 Java,但是在 1000 并发的场景下,完全没有丢失或者发生特别长时间的响应超时。 ## Python - 内置 Web 服务 Python 的默认 compose 如下: ```python version: "3" services: python: image: python:3.9.0-alpine3.12 volumes: - ./main.py:/main.py command: python /main.py ports: - 8080:80 ``` 实现代码相比其他语言,也比较简洁,十行以内就能满足功能需求: ```python from http.server import HTTPServer, BaseHTTPRequestHandler class Redirect(BaseHTTPRequestHandler): def do_GET(self): self.send_response(301) self.send_header('Location', 'http://localhost:1024') self.end_headers() HTTPServer(("0.0.0.0", 80), Redirect).serve_forever() ``` 服务启动之后,查看服务相应内容的脚本需要额外指定一个参数 “`-X GET`”来强制声明 ```bash curl -I -X "GET" http://192.168.23.55:8080/ ``` ```TeXT HTTP/1.0 301 Moved Permanently Server: BaseHTTP/0.6 Python/3.9.0 Date: Mon, 16 Nov 2020 03:48:32 GMT Location: http://localhost:1024 ``` 测试 100 并发时候,便会出现下面这样的问题,所以对于 Python 来说,更多的测试可以跳过了。 ```TeXT Benchmarking 192.168.23.55 (be patient) Completed 10000 requests Completed 20000 requests Completed 30000 requests Completed 40000 requests Completed 50000 requests Completed 60000 requests Completed 70000 requests Completed 80000 requests Completed 90000 requests apr_pollset_poll: The timeout specified has expired (70007) Total of 99936 requests completed ... apr_pollset_poll: The timeout specified has expired (70007) Total of 99954 requests completed ``` 观察控制台,可以看到 CPU 使用率在 29%,系统负载为 0.72,内存用量始终为9%。 Python 的内置模块相比较 PHP 和 Java 的模块略差一些,但是对于系统资源的使用其实还好,不过这和没有完成测试应该也有很大关系,客观减少了许多计算。 ## Ruby 最后一个测试的语言类的选手是 Ruby。 compose 配置如下: ```yaml version: "3" services: ruby: image: ruby:3.0.0-preview1-alpine3.12 ports: - 8080:80 command: ruby /main.rb volumes: - ./main.rb:/main.rb ``` 和其他语言一样,需要写一段脚本,不到二十行: ```ruby require 'socket' server = TCPServer.new 80 while session = server.accept request = session.gets puts request session.print "HTTP/1.1 301\r\n" session.print "Content-Type: text/html\r\n" session.print "Location: http://localhost:9000\r\n" session.print "\r\n" # session.print "" session.close end ``` 将代码保存为 `main.rb` 后,启动服务,得到的响应结果为: ```TeXT HTTP/1.1 301 Content-Type: text/html Location: http://localhost:1024 ``` 进行 100 并发数下 10万次请求: ```TeXT Server Software: Server Hostname: test-server Server Port: 8080 Document Path: / Document Length: 0 bytes Concurrency Level: 100 Time taken for tests: 5.528 seconds Complete requests: 100000 Failed requests: 0 Non-2xx responses: 100000 Total transferred: 7400000 bytes HTML transferred: 0 bytes Requests per second: 18088.15 [#/sec] (mean) Time per request: 5.528 [ms] (mean) Time per request: 0.055 [ms] (mean, across all concurrent requests) Transfer rate: 1307.15 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 1 0.6 0 7 Processing: 1 5 1.0 5 10 Waiting: 0 5 1.1 5 10 Total: 2 6 0.8 5 10 WARNING: The median and mean for the initial connection time are not within a normal deviation These results are probably not that reliable. WARNING: The median and mean for the total time are not within a normal deviation These results are probably not that reliable. Percentage of the requests served within a certain time (ms) 50% 5 66% 5 75% 5 80% 6 90% 7 95% 8 98% 8 99% 8 100% 10 (longest request) ``` 而进行 1000 并发数下 10万次请求,则出现了和 Python 类似的情况: ```TeXT Benchmarking test-server (be patient) Completed 10000 requests Completed 20000 requests Completed 30000 requests Completed 40000 requests Completed 50000 requests Completed 60000 requests Completed 70000 requests Completed 80000 requests Completed 90000 requests apr_pollset_poll: The timeout specified has expired (70007) Total of 99822 requests completed ``` 观察控制台,可以看到 CPU 使用率在 14%,系统负载为 0.1,内存用量始终为9%。 小规模使用的情况下,Ruby 简直超出预期。 ## 服务器软件 测试完各种可以用“短代码”实现重定向的语言后,来看看专门为 Web 请求进行过优化的“正规军”吧。 ### Nginx Nginx 从 1.19 开始支持配置模版,可以用下面的写法将配置文件在应用启动后注入到模版中: ```yaml version: "3" services: nginx: image: nginx:1.19.4-alpine volumes: - ./templates:/etc/nginx/templates ports: - 8080:80 environment: - NGINX_HOST=localhost - NGINX_PORT=80 ``` 接着创建一个名为 `default.conf.template` 的文件: ```TeXT server { listen ${NGINX_PORT}; server_name ${NGINX_HOST}; location / { rewrite .* http://localhost:1024 permanent; } } ``` 然后启动应用,会看到一个响应内容显得略多的结果: ```TeXT HTTP/1.1 301 Moved Permanently Server: nginx/1.19.4 Date: Mon, 16 Nov 2020 03:13:32 GMT Content-Type: text/html Content-Length: 169 Connection: keep-alive Location: http://localhost:1024 ``` 测试 100并发10万次请求的结果如下: ```TeXT Server Software: nginx/1.19.4 Server Hostname: test-server Server Port: 8080 Document Path: / Document Length: 169 bytes Concurrency Level: 100 Time taken for tests: 5.075 seconds Complete requests: 100000 Failed requests: 0 Non-2xx responses: 100000 Total transferred: 36000000 bytes HTML transferred: 16900000 bytes Requests per second: 19705.04 [#/sec] (mean) Time per request: 5.075 [ms] (mean) Time per request: 0.051 [ms] (mean, across all concurrent requests) Transfer rate: 6927.55 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 2 0.6 2 6 Processing: 0 3 1.4 3 26 Waiting: 0 3 1.4 3 26 Total: 0 5 1.4 5 26 Percentage of the requests served within a certain time (ms) 50% 5 66% 5 75% 5 80% 5 90% 6 95% 7 98% 10 99% 11 100% 26 (longest request) ``` 测试1000并发10万次请求的结果如下: ```TeXT Server Software: nginx/1.19.4 Server Hostname: test-server Server Port: 8080 Document Path: / Document Length: 169 bytes Concurrency Level: 1000 Time taken for tests: 4.839 seconds Complete requests: 100000 Failed requests: 0 Non-2xx responses: 100000 Total transferred: 36000000 bytes HTML transferred: 16900000 bytes Requests per second: 20663.50 [#/sec] (mean) Time per request: 48.395 [ms] (mean) Time per request: 0.048 [ms] (mean, across all concurrent requests) Transfer rate: 7264.51 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 21 79.6 16 1052 Processing: 6 27 11.9 25 429 Waiting: 0 22 11.6 20 425 Total: 15 48 80.3 41 1153 Percentage of the requests served within a certain time (ms) 50% 41 66% 45 75% 48 80% 50 90% 57 95% 65 98% 84 99% 109 100% 1153 (longest request) ``` 观察控制台,可以看到 CPU 使用率在 3%,系统负载为 0.1,内存用量始终为9%。 可以看到带有复杂 Web 业务逻辑的 Nginx 毫不意外的轻松秒杀了上述所有语言实现。但是相比较语言实现,编写 Nginx Conf 很多时候不够灵活。 ### Caddy 接着来试试使用优秀的 Golang 框架 Iris 实现的新服务软件,Caddy。 compose 配置类似 Nginx ,需要将配置文件映射到容器内: ```yaml version: "3" services: caddy: image: caddy:2.1.1-alpine volumes: - ./Caddyfile:/etc/caddy/Caddyfile ports: - 8080:80 ``` 然后创建一个名为 `Caddyfile` 的文件,相比较传统的 Nginx.conf ,内容简单到只需要一句话: ```TeXT redir http://localhost:1024/ permanent ``` 服务启动之后,我们会收到一个简单的响应,不过有个细节要注意,Caddy 没有返回 “301”: ```TeXT HTTP/1.1 308 Permanent Redirect Connection: close Location: https://192.168.23.55/ Server: Caddy Date: Mon, 16 Nov 2020 03:33:25 GMT ``` 进行 100 并发 10万次请求: ```TeXT Server Software: Caddy Server Hostname: test-server Server Port: 8080 Document Path: / Document Length: 0 bytes Concurrency Level: 100 Time taken for tests: 6.194 seconds Complete requests: 100000 Failed requests: 0 Non-2xx responses: 100000 Total transferred: 15700000 bytes HTML transferred: 0 bytes Requests per second: 16143.41 [#/sec] (mean) Time per request: 6.194 [ms] (mean) Time per request: 0.062 [ms] (mean, across all concurrent requests) Transfer rate: 2475.11 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 0 0.2 0 5 Processing: 0 6 3.5 5 44 Waiting: 0 6 3.4 5 44 Total: 0 6 3.5 6 44 Percentage of the requests served within a certain time (ms) 50% 6 66% 6 75% 7 80% 8 90% 10 95% 13 98% 17 99% 20 100% 44 (longest request) ``` 进行 1000 并发 10万次请求: ```TeXT Server Software: Caddy Server Hostname: test-server Server Port: 8080 Document Path: / Document Length: 0 bytes Concurrency Level: 1000 Time taken for tests: 6.185 seconds Complete requests: 100000 Failed requests: 0 Non-2xx responses: 100000 Total transferred: 15700000 bytes HTML transferred: 0 bytes Requests per second: 16168.60 [#/sec] (mean) Time per request: 61.848 [ms] (mean) Time per request: 0.062 [ms] (mean, across all concurrent requests) Transfer rate: 2478.97 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 1 2.5 0 29 Processing: 0 60 21.1 60 277 Waiting: 0 60 21.0 59 273 Total: 0 61 20.9 61 277 Percentage of the requests served within a certain time (ms) 50% 61 66% 66 75% 70 80% 74 90% 85 95% 97 98% 113 99% 124 100% 277 (longest request) ``` 观察控制台,可以看到 CPU 使用率在 23%,系统负载为 0.1,内存用量从9%升到了10%。 可以看到相比较原生的 Go 的编译后的程序的性能提升都十分明显,加上 Caddy 的配置灵活,应该未来还是有不少“出场机会”的。 ### Traefik 最后来看看我们一直深度使用的 Traefik,和其他的程序和脚本略有差异,云原生的软件,默认特点之一便是完备的启动参数和从环境变量、容器变量中获取相关的信息,所以只需要准备一个 `docker-compose.yml` 就可以开始测试了: ```yaml version: '3' services: traefik: image: traefik:v2.3.2 ports: - 8080:80 command: - "--log.level=ERROR" - "--providers.docker=true" - "--providers.docker.exposedbydefault=true" - "--entrypoints.http.address=:80" labels: - "traefik.http.middlewares.redir.redirectregex.regex=^.*" - "traefik.http.middlewares.redir.redirectregex.replacement=http://localhost:1024" - "traefik.http.routers.redir-test.rule=HostRegexp(`{any:.*}`)" - "traefik.http.routers.redir-test.entrypoints=http" - "traefik.http.routers.redir-test.middlewares=redir" - "traefik.http.services.backend.loadbalancer.server.scheme=http" - "traefik.http.services.backend.loadbalancer.server.port=80" volumes: - /var/run/docker.sock:/var/run/docker.sock:ro ``` 服务启动后,获得的响应头和 Caddy 类似,并非我们预期的 “301”,还好当前的浏览器主要以解析 `Location` 这个响应头,和判断响应码是否为 “300 系列”,所以实际使用过程中,不会出现预期之外的问题。 ```TeXT HTTP/1.1 307 Temporary Redirect Location: http://localhost:1024 Date: Mon, 16 Nov 2020 07:26:18 GMT Content-Length: 18 Content-Type: text/plain; charset=utf-8 ``` 进行 100 并发10万次请求: ```TeXT Server Software: Server Hostname: test-server Server Port: 8080 Document Path: / Document Length: 5 bytes Concurrency Level: 100 Time taken for tests: 11.258 seconds Complete requests: 100000 Failed requests: 0 Non-2xx responses: 100000 Total transferred: 15700000 bytes HTML transferred: 500000 bytes Requests per second: 8882.85 [#/sec] (mean) Time per request: 11.258 [ms] (mean) Time per request: 0.113 [ms] (mean, across all concurrent requests) Transfer rate: 1361.92 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 0 0.2 0 6 Processing: 0 11 6.2 10 63 Waiting: 0 8 5.3 7 63 Total: 0 11 6.3 10 64 Percentage of the requests served within a certain time (ms) 50% 10 66% 13 75% 15 80% 16 90% 19 95% 23 98% 27 99% 30 100% 64 (longest request) ``` 进行 1000 并发10万次请求: ```TeXT Server Software: Server Hostname: test-server Server Port: 8080 Document Path: / Document Length: 5 bytes Concurrency Level: 1000 Time taken for tests: 11.975 seconds Complete requests: 100000 Failed requests: 0 Non-2xx responses: 100000 Total transferred: 15700000 bytes HTML transferred: 500000 bytes Requests per second: 8350.87 [#/sec] (mean) Time per request: 119.748 [ms] (mean) Time per request: 0.120 [ms] (mean, across all concurrent requests) Transfer rate: 1280.36 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 1 2.4 0 25 Processing: 1 118 48.5 113 491 Waiting: 0 103 46.5 101 452 Total: 1 119 48.3 114 491 Percentage of the requests served within a certain time (ms) 50% 114 66% 130 75% 142 80% 152 90% 182 95% 208 98% 241 99% 268 100% 491 (longest request) ``` 观察控制台,可以看到 CPU 使用率在 57%,系统负载为 1.5,内存用量从9%升到了10%。 这里可以看到 Traefik 并不是性能最好的工具,相比较 Nginx 而言,有很长的路要走。但是相比较我们直接使用高级语言去写,稳定性和可靠性高了不少,况且软件还支持大量通用 Web 处理任务。 ## 最后 示例代码已开源在 [https://github.com/soulteary/redirect-test](https://github.com/soulteary/redirect-test),欢迎自行取用,或者补充。 行文仓促,欢迎提建议、一起讨论。 --EOF