说到重定向,大家必然不会陌生,最常见的场景之一便是各种文章、社交媒体上的短链接。

最近计划对于之前的短链接服务进行升级改造。在改造前,对于常见 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万次请求测试。

配置测试环境

两台机器默认的环境不需要很复杂,仅安装 dockercompose 两个工具足以,可以参考下面的命令,完成初始系统的安装:

安装完毕后,先验证软件版本,均为以下结果,顺便检查 ulimit 限制:

查看到 ulimit 结果为 unlimited,说明未对系统资源进行限制。

因为需要在一台服务器上发起测试请求,所以在测试服务器上执行 apt install apache2-utils ,获取通用测试工具 ab,输入下面的命令,可以查看到工具的具体版本等信息:

安装 Linux Dash 监控面板

为了更直观的获取测试过程服务器的整体性能和资源使用,我们使用 linux-dash 这个开源工具,并使用 Golang 版本执行,尽可能减少对系统资源的占用,影响各种测试结果。

在 Ubuntu 环境下安装 Golang 极其简单,下载,解压,设置环境变量即可:

安装完毕,验证 Golang 版本如下:

然后是下载 Linux Dash 的源码,并进行简单的构建和运行。

执行 ./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 配置如下:

将上面的内容保存为 docker-compose.yml 后,在同级目录中创建一个 index.php 即可开始我们的测试,在 PHP 中完成重定向操作非常简单,只需要一句话:

先执行一次请求,可以看到服务器的响应如下:

接着进行 100 并发数下 10万次请求:

再继续进行 1000 并发下10万次请求测试:

观察控制台,可以看到 CPU 使用率在 70%,系统负载飙升至 49,内存用量从9%升到了12%。

PHP - 官方内置 Server

前文提到,我们尽量使用原生的服务能力,PHP 其实很早就内置了一个 Web Server,所以我们可以使用其内置的 Server 来尝试提供 Web 服务。

测试使用的 compose 配置和之前区别不大,调整一下 command 指令即可:

查看服务响应,相比较之前多了一行 Connection 响应信息,其余没有太大变化。

接着进行 100 并发数下 10万次请求:

再继续进行 1000 并发下10万次请求测试:

观察控制台,可以看到 CPU 使用率在 49%,系统负载飙升至 44,内存用量几乎没有变化。

这里 PHP 内置服务的性能有一些跟不上了,有一些请求因为超时中断,未能完成测试。虽然这个测试没有完成,但是结合100并发场景的测试结果,足够说明这个模式用于简单测试使用、或者个人场景来说是满足的,但是慎用于生产环境。

PHP 启用 OPCache

在 PHP 使用场景下,为了获取更好的语言性能,我们一般会开启 OPCache 。

在容器环境中,默认 PHP 配置是以模版形式保存在 /usr/local/etc/php/php.ini-production 路径,我们在配置文件中找到 OPCache 配置小节。

将和 OPCache 配置有关的项目前的注释分号全部去掉,然后将 opcache.enable_cli 设置为 1,再将 ;zend_extension=opcache 改为 zend_extension=opcache,最后将内容保存为 php.ini

接着编写 compose 配置文件:

启动服务后,我们将会收到类似下面的服务器响应。

接着进行 100 并发数下 10万次请求:

再继续进行 1000 并发下10万次请求测试:

可以看到相比较第一个小节中,在不改动代码,仅调整配置的前提下,使用默认的官方镜像的模式,在 100 并发和 1000 并发的情况下,相比较原来,分别提升了 7% 和 1%,响应时间也有了一些优化和提升。

观察控制台,可以看到 CPU 使用率在 67%,系统负载飙升至 40,内存用量从9%升到了12%。

Java

Java 是目前虚拟机最强语言,也是 Web 领域通用性最好的语言之一。

Java - OpenJDK 镜像未编译

那么来看看 Java 在内置 Web Server 模块下未编译时的表现吧。

依旧是先给出 compose 配置文件。

然后我们需要编写不到三十行的脚本,并将它保存为 main.java

将服务运行起来后,我们能够看到 Java 的响应除了我们代码中声明的两行外,默认比 PHP 要少一些。

进行 100 并发数下 10万次请求:

再进行 1000 并发数下 10万次请求:

观察控制台,可以看到 CPU 使用率在 24%,系统负载为 12.56,内存用量从9%升到了13%。

对比之前可以看到,Java 的执行效率很高, 50% 的请求的处理时间在 5ms 内,98 的请求处理速度在 8ms 内。

虽然处理速度很快,但是 100 并发下,仅比开了 OPCache 的 PHP 高 3.6%,而 1000 并发场景下,因为 2% 的请求处理时间太长,导致测试结果反而不是很好看。不过也足以得出类似 PHP 内置 Server 的结论,简单测试和个人使用完全没有问题,线上使用要慎重,最好配合其他服务软件使用。

Java - 编译后的程序

在不改动代码的前提下,我们测试下编译后的 Java 程序的执行效率。

先调整 compose 配置:

服务器响应和之前除了时间外一样。

进行 100 并发数下 10万次请求:

进行 1000 并发数下 10万次请求。

观察控制台,可以看到 CPU 使用率在 26%,系统负载为 2.5,内存用量从9%升到了12%。

相比较未编译的程序,可以看到性能提升还是比较大的,以100并发情况为例,性能提升 5%,99%都能在7ms内;而1000并发情况下,95%的请求也能够在 6ms 内完成。不过使用场景和结论依旧不会有变化,内置服务适用于简单测试和个人使用,生产环境使用需要慎重。

Go

继续来了解下云原生领域当红炸子鸡:Go 。

Go - 未编译情况

先来看看 Golang 在未编译情况下的表现。

依旧是先给出 compose 配置文件:

Go 的代码实现更精简一些,只需要写不到二十行代码。

将上面的代码保存为 main.go,然后启动服务,可以看到响应结果也很精简。

进行 100 并发数下 10万次请求:

进行 1000 并发数下 10万次请求:

观察控制台,可以看到 CPU 使用率在 36%,系统负载为 1.29,内存用量从9%升到了10%。

可以看到未编译的 Go 的程序不论是在 100 并发还是 1000 并发下的表现都是非常不错的。动辄1万8到1万9的响应能力(执行效率),侧面说明了为什么开源工具越来越多的由 Go 编写而成。

Go - 编译后的程序

编译前的程序的结果已经非常棒了,接着来看看编译后的程序。

在代码不需变化的前提下,调整 compose 配置文件中的 command 指令即可:

进行 100 并发数下 10万次请求:

进行 1000 并发数下 10万次请求:

观察控制台,可以看到 CPU 使用率在 35%,系统负载为 1.3,内存用量始终为9%。

测试结果发现编译前后区别并不大,甚至还略有轻微下降,不过不影响可用可信赖的判断。这里性能轻微下降,有可能是 alpine 版本编译结果的执行效率的问题,后面有时间再进行进一步测试吧。

Node.js

接下来试试我最喜欢的语言之一,Node.js。

compose 配置内容如下:

相比较前面的程序(除PHP外),示例程序可以写的更简洁一些:

服务运行之后,默认响应内容如下:

进行 100 并发数下 10万次请求:

进行 1000 并发数下 10万次请求:

观察控制台,可以看到 CPU 使用率在 22%,系统负载为 0.59,内存用量从9%升到10%。

可以看到执行还算及格,虽然性能看起来不如 Go 和 Java,但是在 1000 并发的场景下,完全没有丢失或者发生特别长时间的响应超时。

Python - 内置 Web 服务

Python 的默认 compose 如下:

实现代码相比其他语言,也比较简洁,十行以内就能满足功能需求:

服务启动之后,查看服务相应内容的脚本需要额外指定一个参数 “-X GET”来强制声明

测试 100 并发时候,便会出现下面这样的问题,所以对于 Python 来说,更多的测试可以跳过了。

观察控制台,可以看到 CPU 使用率在 29%,系统负载为 0.72,内存用量始终为9%。

Python 的内置模块相比较 PHP 和 Java 的模块略差一些,但是对于系统资源的使用其实还好,不过这和没有完成测试应该也有很大关系,客观减少了许多计算。

Ruby

最后一个测试的语言类的选手是 Ruby。

compose 配置如下:

和其他语言一样,需要写一段脚本,不到二十行:

将代码保存为 main.rb 后,启动服务,得到的响应结果为:

进行 100 并发数下 10万次请求:

而进行 1000 并发数下 10万次请求,则出现了和 Python 类似的情况:

观察控制台,可以看到 CPU 使用率在 14%,系统负载为 0.1,内存用量始终为9%。

小规模使用的情况下,Ruby 简直超出预期。

服务器软件

测试完各种可以用“短代码”实现重定向的语言后,来看看专门为 Web 请求进行过优化的“正规军”吧。

Nginx

Nginx 从 1.19 开始支持配置模版,可以用下面的写法将配置文件在应用启动后注入到模版中:

接着创建一个名为 default.conf.template 的文件:

然后启动应用,会看到一个响应内容显得略多的结果:

测试 100并发10万次请求的结果如下:

测试1000并发10万次请求的结果如下:

观察控制台,可以看到 CPU 使用率在 3%,系统负载为 0.1,内存用量始终为9%。

可以看到带有复杂 Web 业务逻辑的 Nginx 毫不意外的轻松秒杀了上述所有语言实现。但是相比较语言实现,编写 Nginx Conf 很多时候不够灵活。

Caddy

接着来试试使用优秀的 Golang 框架 Iris 实现的新服务软件,Caddy。

compose 配置类似 Nginx ,需要将配置文件映射到容器内:

然后创建一个名为 Caddyfile 的文件,相比较传统的 Nginx.conf ,内容简单到只需要一句话:

服务启动之后,我们会收到一个简单的响应,不过有个细节要注意,Caddy 没有返回 “301”:

进行 100 并发 10万次请求:

进行 1000 并发 10万次请求:

观察控制台,可以看到 CPU 使用率在 23%,系统负载为 0.1,内存用量从9%升到了10%。

可以看到相比较原生的 Go 的编译后的程序的性能提升都十分明显,加上 Caddy 的配置灵活,应该未来还是有不少“出场机会”的。

Traefik

最后来看看我们一直深度使用的 Traefik,和其他的程序和脚本略有差异,云原生的软件,默认特点之一便是完备的启动参数和从环境变量、容器变量中获取相关的信息,所以只需要准备一个 docker-compose.yml 就可以开始测试了:

服务启动后,获得的响应头和 Caddy 类似,并非我们预期的 “301”,还好当前的浏览器主要以解析 Location 这个响应头,和判断响应码是否为 “300 系列”,所以实际使用过程中,不会出现预期之外的问题。

进行 100 并发10万次请求:

进行 1000 并发10万次请求:

观察控制台,可以看到 CPU 使用率在 57%,系统负载为 1.5,内存用量从9%升到了10%。

这里可以看到 Traefik 并不是性能最好的工具,相比较 Nginx 而言,有很长的路要走。但是相比较我们直接使用高级语言去写,稳定性和可靠性高了不少,况且软件还支持大量通用 Web 处理任务。

最后

示例代码已开源在 https://github.com/soulteary/redirect-test,欢迎自行取用,或者补充。

行文仓促,欢迎提建议、一起讨论。

–EOF