在Ubuntu AWS EC2实例上优化Golang net/http服务器性能

在Ubuntu AWS EC2实例上优化Golang net/http服务器性能 我已将Go服务器部署在AWS EC2上。目前使用的是t2.micro实例,配备Ubuntu Server 18.04 LTS (HVM)、SSD卷类型和8GB硬盘。

Go代码:

我使用的代码如下:

package main

import (
    "fmt"
    "log"
    "net/http"
    "strings"
)

var (
    chars_10 string
)

func main() {
    chars_10 = strings.Repeat("a", 10)
    http.HandleFunc("/experiment-1/10-characters", handler_10_chars)
    log.Fatal(http.ListenAndServe(":8888", nil))
}

func handler_10_chars(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, chars_10)
}

基准测试代码:

我使用的基准测试工具是K6,基准测试代码如下:

import http from "k6/http";
import { check } from "k6";

export let options = {
   vus: 2048,
   duration: "60s"
};

let url = ""; // 这里我定义AWS EC2实例端点

export default function() {
    let res = http.get(url);
    check(res, {
        "status was 200": (r) => r.status == 200,
    });
};

这段代码将在我的笔记本上执行,访问AWS EC2端点。

实验过程:

首先,我通过终端命令go build server.go将Go代码编译为单个二进制文件。然后,在终端运行**./server**启动服务器。

当我首次对服务器进行压力测试时,k6基准测试中抛出了一个错误:

WARN[0005] Request Failed error="Get http://aws-url:8888/: read tcp [::1]:60816->[::1]:8888: read: connection reset by peer"

其中aws-url是AWS自动生成的URL,8888是我启用的端口(并映射到服务器监听传入请求的端口)。

在网上搜索后,我发现这是因为SOMAXCONN变量的值为128。所以我的做法是将这个数字改为65535。具体方法是编辑/etc/sysctl.conf,在文件末尾添加以下行:

net.core.somaxconn=65535

然后,我再次运行K6基准测试。我注意到Connection reset by peer错误消失了。但这次服务器端出现了新的错误:

Accept error: too many open files.

我再次搜索如何解决这个新错误,发现这是因为系统文件描述符数量限制。所以我的做法是再次编辑/etc/sysctl.conf,在文件末尾添加以下行:

fs.file-max = 1000000

此外,我在终端运行了以下命令:

ulimit -n 99999

我再次运行K6基准测试。这次没有错误了。但我注意到一些问题:在每次基准测试中,CPU使用率和内存使用率始终低于20%(我无法让服务器使用100%的CPU),而TTFB最高增加到平均2秒。经过数小时尝试不同的Ubuntu配置后,我发现了这篇文章:Slow Server? This is the Flow Chart You’re Looking For | Scout APM Blog。我再次运行基准测试,注意到我的CPU表现符合第3步:

第3步:IO等待较低且空闲时间较高

因此我得出结论,Go没有使用全部CPU容量来处理请求。我的问题是:如何让Go使用100%的CPU容量,以便我能处理每秒最大请求数?您认为还有哪些其他瓶颈可能限制服务器达到100%的CPU使用率?


更多关于在Ubuntu AWS EC2实例上优化Golang net/http服务器性能的实战教程也可以访问 https://www.itying.com/category-94-b0.html

2 回复

如果你使用另一台EC2实例来运行负载测试,而不是用笔记本电脑,会发生什么情况?你确定没有超出实例的带宽或CPU限制吗?我不确定AWS限制你的资源时,是否会显示为应用程序使用的CPU。

如果选择更大的EC2节点(一个作为服务器,一个用于生成负载)会怎样?或许使用更大的实例来生成负载会更好,这样你就有更多机会了解主节点的性能极限?

更多关于在Ubuntu AWS EC2实例上优化Golang net/http服务器性能的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


要优化Go net/http服务器在AWS EC2实例上的性能并充分利用CPU资源,需要从多个层面进行调优。以下是具体的优化方案:

1. 优化Go HTTP服务器配置

package main

import (
    "fmt"
    "log"
    "net/http"
    "runtime"
    "strings"
    "time"
)

var (
    chars_10 string
)

func main() {
    // 设置最大CPU核心数
    runtime.GOMAXPROCS(runtime.NumCPU())
    
    chars_10 = strings.Repeat("a", 10)
    
    // 创建自定义HTTP服务器配置
    server := &http.Server{
        Addr: ":8888",
        Handler: http.HandlerFunc(handler_10_chars),
        ReadTimeout:    10 * time.Second,
        WriteTimeout:   10 * time.Second,
        IdleTimeout:    30 * time.Second,
        MaxHeaderBytes: 1 << 20, // 1MB
    }
    
    log.Fatal(server.ListenAndServe())
}

func handler_10_chars(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, chars_10)
}

2. 使用更高效的HTTP路由器

package main

import (
    "fmt"
    "log"
    "net/http"
    "runtime"
    "strings"
    "time"
    
    "github.com/gorilla/mux"
)

var (
    chars_10 string
)

func main() {
    runtime.GOMAXPROCS(runtime.NumCPU())
    
    chars_10 = strings.Repeat("a", 10)
    
    router := mux.NewRouter()
    router.HandleFunc("/experiment-1/10-characters", handler_10_chars)
    
    server := &http.Server{
        Addr:         ":8888",
        Handler:      router,
        ReadTimeout:  5 * time.Second,
        WriteTimeout: 10 * time.Second,
        IdleTimeout:  120 * time.Second,
    }
    
    log.Fatal(server.ListenAndServe())
}

func handler_10_chars(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "text/plain")
    fmt.Fprintf(w, chars_10)
}

3. 优化系统级别的配置

在Ubuntu上创建systemd服务文件 /etc/systemd/system/go-server.service

[Unit]
Description=Go HTTP Server
After=network.target

[Service]
Type=simple
User=ubuntu
Group=ubuntu
WorkingDirectory=/path/to/your/app
ExecStart=/path/to/your/server
Restart=always
LimitNOFILE=1000000
LimitNPROC=1000000

[Install]
WantedBy=multi-user.target

4. 使用连接池优化客户端请求

// 在K6测试中优化连接重用
import http from "k6/http";
import { check } from "k6";

export let options = {
   vus: 2048,
   duration: "60s",
};

// 创建HTTP客户端实例以重用连接
const client = new http.Client({
    timeout: '30s',
});

let url = "http://your-aws-url:8888/experiment-1/10-characters";

export default function() {
    let res = client.get(url);
    check(res, {
        "status was 200": (r) => r.status == 200,
    });
};

5. 启用HTTP/2和优化TLS配置(如果使用HTTPS)

package main

import (
    "fmt"
    "log"
    "net/http"
    "runtime"
    "strings"
    "time"
)

func main() {
    runtime.GOMAXPROCS(runtime.NumCPU())
    
    chars_10 := strings.Repeat("a", 10)
    
    server := &http.Server{
        Addr: ":8888",
        Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            fmt.Fprintf(w, chars_10)
        }),
        ReadTimeout:       5 * time.Second,
        WriteTimeout:      10 * time.Second,
        IdleTimeout:       120 * time.Second,
        ReadHeaderTimeout: 2 * time.Second,
    }
    
    // 启用HTTP/2
    log.Fatal(server.ListenAndServe())
}

6. 监控和性能分析

添加性能监控端点:

package main

import (
    "fmt"
    "log"
    "net/http"
    _ "net/http/pprof"
    "runtime"
    "strings"
    "time"
)

func main() {
    runtime.GOMAXPROCS(runtime.NumCPU())
    
    chars_10 := strings.Repeat("a", 10)
    
    // 启用pprof性能分析
    go func() {
        log.Println(http.ListenAndServe("localhost:6060", nil))
    }()
    
    http.HandleFunc("/experiment-1/10-characters", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, chars_10)
    })
    
    server := &http.Server{
        Addr: ":8888",
        ReadTimeout:  5 * time.Second,
        WriteTimeout: 10 * time.Second,
        IdleTimeout:  120 * time.Second,
    }
    
    log.Fatal(server.ListenAndServe())
}

潜在瓶颈分析:

  1. 网络带宽限制:t2.micro实例的网络性能有限
  2. 实例类型限制:t2.micro是突发性能实例,持续高负载时CPU积分会耗尽
  3. 系统调用开销:大量的小HTTP请求会产生显著的系统调用开销
  4. 内存分配:频繁的字符串操作和HTTP响应构建可能产生GC压力

要验证这些优化效果,可以使用以下命令监控服务器性能:

# 监控CPU使用率
htop

# 监控网络连接
netstat -an | grep :8888 | wc -l

# 监控文件描述符使用
lsof -p $(pgrep server) | wc -l

这些优化应该能够帮助Go服务器更有效地利用CPU资源,但需要注意t2.micro实例本身的性能限制可能仍然是主要瓶颈。

回到顶部