前言
很久都没出GO方面的文章了,自己都很久没玩了,很多东西都忘了,这次出个文章,使用Go+Nginx搭建一个可以实现高并发并且对请求做出限制功能的服务器。
一、Go
这里先搭建Go部分的环境,这个代码我自己div了一下Logger打印日志,可以自己随心所欲搭配打印,如果不需要自己div,图方便的可以使用gin.Default()
package main
import (
"fmt"
"github.com/fatih/color"
"github.com/gin-gonic/gin"
"golang.org/x/time/rate"
"log"
"net/http"
"sync"
"time"
)
func Logger(addr string) gin.HandlerFunc {
return func(context *gin.Context) {
t := time.Now()
// 继续处理请求
context.Next()
// 记录响应时间
latency := time.Since(t).Milliseconds()
ginLog := "[GIN-LOG] NowTime--> %s | Method--> %s | Path--> %s | SpendTime--> %dms | Ip--> %s | Port--> %s"
info := fmt.Sprintf(
ginLog,
time.Now().Format("2006/01/02 15:04:05"),
context.Request.Method,
context.Request.URL.Path,
latency,
context.ClientIP(),
addr)
color.Green(info)
}
}
// responseData 返回信息
func responseData(context *gin.Context, code int, message string, status string, result interface{}) {
context.JSON(http.StatusOK, gin.H{"code": code, "message": message, "success": status, "result": result})
}
func route1Handler(addr string) http.Handler {
// 创建令牌桶,每秒产生100个令牌,桶的容量为100
bucket := rate.NewLimiter(100, 100)
// 处理 route1 的逻辑
e := gin.New()
e.Use(gin.Recovery(), Logger(addr))
e.GET("/test", func(context *gin.Context) {
if !bucket.Allow() {
responseData(context, 300, "过载", "fail", "")
return
}
name := context.Query("name")
if name == "" {
responseData(context, 200, "成功返回数据", "success", "")
return
}
})
return e
}
func startServer(wg *sync.WaitGroup, port int) {
defer wg.Done()
addr := fmt.Sprintf(":%d", port)
server := &http.Server{
Addr: addr,
Handler: route1Handler(addr),
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
}
err := server.ListenAndServe()
if err != nil && err != http.ErrServerClosed {
log.Fatal(err)
}
}
func main() {
// 端口数量(节点数量)
concurrency := 2
var wg sync.WaitGroup
wg.Add(concurrency)
// 启动端口(模拟多个节点操作)
for i := 0; i < concurrency; i++ {
go startServer(&wg, 8080+i)
}
// 等待所有并发请求完成
wg.Wait()
}
二、Nginx
要使用 Nginx 进行负载均衡,并在当前端口的并发达到一定数量时进行切换,可以结合 Nginx 的 upstream 模块和相关配置实现。以下是一般的步骤:
-
安装和配置 Nginx:首先,确保已经安装了 Nginx,并进行基本的配置。具体的安装和配置步骤会因操作系统而异。安装完成后,可以编辑 Nginx 的配置文件,通常是位于
nginx-1.25.1/conf/nginx.conf
。 -
定义后端服务器:在 Nginx 配置文件中,使用
upstream
指令定义后端服务器组,并指定各个服务器的地址和端口号。示例:
upstream backend { server 127.0.0.1:8080; server 127.0.0.1:8081; # 添加更多的后端服务器... }
在上述示例中,我们定义了一个名为
backend
的后端服务器组,其中包含多个后端服务器的地址和端口号。 -
配置负载均衡规则:在 Nginx 的虚拟主机配置中,使用
proxy_pass
指令将请求转发到后端服务器组,并配置负载均衡规则。示例:
server { listen 80; server_name 127.0.0.1; location / { proxy_pass http://backend; } location /test { proxy_pass http://backend/test; } }
在上述示例中,我们定义了一个虚拟主机,它监听端口号为 80,
server_name
是127.0.0.1
。所有的请求将通过proxy_pass
指令转发到名为backend
的后端服务器组。 -
配置负载均衡策略:根据您的需求,可以配置不同的负载均衡策略。Nginx 提供了多种负载均衡算法,如轮询(默认)、IP 哈希、最少连接等。可以根据实际情况选择适合的负载均衡策略。(因为我们这里要测试,我就选择了最少连接算法来实现)
示例:
upstream backend { least_conn; # 使用最少连接算法负载均衡策略 server 127.0.0.1:8080; server 127.0.0.1:8081; # 添加更多的后端服务器... }
在上述示例中,我们使用了最少连接算法来实现负载均衡策略,该策略将请求发送到当前连接数最少的后端服务器。
-
配置并发阈值:要根据当前端口的并发数量进行切换,您可以使用 Nginx 的
limit_conn
模块来设置并发连接数的阈值,并根据阈值进行切换。示例:
server { listen 80; server_name 127.0.0.1; location / { limit_conn conn_limit_per_ip 100; proxy_pass http://backend; } location /test { limit_conn conn_limit_per_ip 100; proxy_pass http://backend/test; } }
在上述示例中,我们使用
limit_conn
模块设置每个 IP 地址的并发连接数限制为 100。当达到这个限制时,Nginx 将会返回503错误。 -
重新加载配置和启动 Nginx:完成配置后,保存文件并使用命令重新加载 Nginx 配置。命令通常是
nginx -s reload
。这将重新加载配置文件,使更改生效。
nginx-1.25.1/conf/nginx.conf
总的配置代码(原来那个http记得注释掉):
http {
limit_conn_zone $binary_remote_addr zone=conn_limit_per_ip:20m;
upstream backend {
least_conn;
server 127.0.0.1:8080;
server 127.0.0.1:8081;
}
server {
listen 80;
server_name 127.0.0.1;
location / {
limit_conn conn_limit_per_ip 100;
proxy_pass http://backend;
}
location /test {
limit_conn conn_limit_per_ip 100;
proxy_pass http://backend/test;
}
}
}
上面的 server 127.0.0.1:8080; server 127.0.0.1:8081;
后续可以换成自己的节点地址,这里只是演示所以都用本地的端口来模拟多节点。
三、测试
两种测试代码,自己根据语言选择
Go并发请求测试
package main
import (
"fmt"
"io/ioutil"
"net/http"
"sync"
)
func sendRequest(url string, wg *sync.WaitGroup) {
defer wg.Done()
response, err := http.Get(url)
if err != nil {
fmt.Printf("Error connecting to %s: %s\n", url, err)
return
}
defer response.Body.Close()
//处理响应
responseBody, err := ioutil.ReadAll(response.Body)
if err != nil {
fmt.Printf("Error reading response body from %s: %s\n", url, err)
return
}
fmt.Printf("Response from %s: %s\n", url, string(responseBody))
//fmt.Printf("Response from %s: %s\n", url, response.Status)
}
func main() {
// 设置并发请求数
concurrency := 300
// 设置要请求的URL
url := "http://127.0.0.1/test"
var wg sync.WaitGroup
wg.Add(concurrency)
// 发送并发请求
for i := 0; i < concurrency; i++ {
go sendRequest(url, &wg)
}
wg.Wait()
}
Python并发请求测试
import requests
from concurrent.futures import ThreadPoolExecutor
def send_request(url):
try:
response = requests.get(url)
print(response.text)
# print(f"Response from {url}: {response.status_code}")
except requests.exceptions.RequestException as e:
print(f"Error connecting to {url}: {e}")
# 设置并发请求数
concurrency = 500
# 设置要请求的URL
url = "http://127.0.0.1/test"
# 创建线程池执行器
executor = ThreadPoolExecutor(max_workers=concurrency)
# 发送并发请求
for _ in range(concurrency):
executor.submit(send_request, url)
Go服务器的运行结果:
很直观的看到了,采用了我们之前的最少连接算法来实现负载均衡策略。
四、总结
Go部分的代码,我没怎么详细讲,大概意思大家应该能看懂,里面我还使用了令牌桶来对请求当前api的连接数做了限制,可以在Nginx的ip限制下再做一次限制,可以简单达到下面的两个功能
-
保护服务器资源:通过限制请求数,可以防止服务器过载。当同时接收到大量的请求时,服务器的资源(如 CPU、内存和网络带宽)可能会耗尽,导致性能下降甚至崩溃。通过限制请求数,可以确保服务器能够合理地处理请求,避免资源耗尽。
-
提高系统稳定性:通过限制请求数,可以防止某些恶意用户或恶意程序通过发送大量请求来攻击服务器。这种攻击通常被称为拒绝服务攻击(DDoS),通过限制请求数,可以减轻对服务器的过载攻击,提高系统的稳定性和可靠性。
借鉴
ChatGPT