Tabla de contenido
prefacio
Ha pasado mucho tiempo desde que publiqué un artículo sobre GO. No lo he jugado durante mucho tiempo y olvidé muchas cosas. Esta vez publiqué un artículo, usando Go+Nginx para construir un servidor que pueda lograr una alta concurrencia. y limitar las solicitudes.
1. Ir
Primero construyamos el entorno de la parte Go. Div el registrador para imprimir el registro por mí mismo. Puede imprimirlo como desee. Si no necesita su propio div, puede usarlo convenientemente.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()
}
2. Nginx
Para usar Nginx para equilibrar la carga y cambiar cuando la simultaneidad del puerto actual alcanza un número determinado, puede combinar el módulo ascendente de Nginx y las configuraciones relacionadas. Estos son los pasos generales:
-
Instale y configure Nginx: Primero, asegúrese de que se haya instalado Nginx y realice la configuración básica. Los pasos específicos de instalación y configuración varían según el sistema operativo. Una vez completada la instalación, puede editar el archivo de configuración de Nginx, que normalmente se encuentra en
nginx-1.25.1/conf/nginx.conf
. -
Defina el servidor back-end: en el archivo de configuración de Nginx, use
upstream
el comando para definir el grupo de servidores back-end y especifique la dirección y el número de puerto de cada servidor.Ejemplo:
upstream backend { server 127.0.0.1:8080; server 127.0.0.1:8081; # 添加更多的后端服务器... }
En el ejemplo anterior, definimos un
backend
grupo de servidores backend denominado , que contiene las direcciones y los números de puerto de varios servidores backend. -
Configure las reglas de equilibrio de carga: en la configuración del host virtual de Nginx, use
proxy_pass
el comando para reenviar la solicitud al grupo de servidores back-end y configure las reglas de equilibrio de carga.Ejemplo:
server { listen 80; server_name 127.0.0.1; location / { proxy_pass http://backend; } location /test { proxy_pass http://backend/test; } }
En el ejemplo anterior, definimos un host virtual que escucha en el puerto número 80,
server_name
sí127.0.0.1
. Todas las solicitudes seproxy_pass
reenviarán albackend
grupo de servidores back-end nombrado por la directiva. -
Configurar estrategia de balanceo de carga: Según tus necesidades, puedes configurar diferentes estrategias de balanceo de carga. Nginx proporciona una variedad de algoritmos de equilibrio de carga, como round robin (predeterminado), hash de IP, conexión mínima, etc. Puede elegir una estrategia de equilibrio de carga adecuada según la situación real. (Debido a que queremos probar aquí, elegí el algoritmo de conexión mínima para lograr)
Ejemplo:
upstream backend { least_conn; # 使用最少连接算法负载均衡策略 server 127.0.0.1:8080; server 127.0.0.1:8081; # 添加更多的后端服务器... }
En el ejemplo anterior, usamos el algoritmo de menor cantidad de conexiones para implementar la estrategia de equilibrio de carga, que envía la solicitud al servidor backend con la menor cantidad de conexiones actuales.
-
Configure el umbral concurrente: para cambiar según el número actual de conexiones simultáneas, puede usar el
limit_conn
módulo Nginx para establecer el umbral del número de conexiones simultáneas y cambiar según el umbral.Ejemplo:
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; } }
En el ejemplo anterior, usamos
limit_conn
el módulo para establecer un límite de 100 conexiones simultáneas por dirección IP. Cuando se alcanza este límite, Nginx devolverá un error 503. -
Vuelva a cargar la configuración e inicie Nginx: después de completar la configuración, guarde el archivo y use el comando para volver a cargar la configuración de Nginx. El comando suele ser
nginx -s reload
. Esto recargará el archivo de configuración, haciendo efectivos los cambios.
nginx-1.25.1/conf/nginx.conf
El código de configuración total (recuerde comentar el http original):
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;
}
}
}
El server 127.0.0.1:8080; server 127.0.0.1:8081;
seguimiento anterior se puede reemplazar con su propia dirección de nodo. Esto es solo una demostración, por lo que el puerto local se usa para simular múltiples nodos.
3. prueba
Dos códigos de prueba, elige según el idioma
Ir a la prueba de solicitud simultánea
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()
}
Prueba de solicitud concurrente de 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)
Los resultados de ejecución del servidor Go:
es muy intuitivo ver que nuestro algoritmo de conexión mínima anterior se usa para implementar la estrategia de equilibrio de carga.
Cuatro Resumen
No entré en detalles sobre la parte Go del código, pero todos deberían poder entenderlo. También usé un depósito de tokens para limitar la cantidad de conexiones que solicitan la API actual. Puede limitarlo nuevamente bajo la ip de Nginx límite. , puede lograr fácilmente las siguientes dos funciones
-
Proteja los recursos del servidor: al limitar la cantidad de solicitudes, puede evitar la sobrecarga del servidor. Cuando se recibe una gran cantidad de solicitudes al mismo tiempo, los recursos del servidor (como la CPU, la memoria y el ancho de banda de la red) pueden agotarse, lo que resulta en un bajo rendimiento o incluso fallas. Al limitar la cantidad de solicitudes, puede asegurarse de que el servidor pueda manejar las solicitudes de manera razonable y evitar el agotamiento de los recursos.
-
Mejorar la estabilidad del sistema: al limitar la cantidad de solicitudes, se puede evitar que algunos usuarios o programas maliciosos ataquen el servidor enviando una gran cantidad de solicitudes. Este tipo de ataque generalmente se denomina ataque de denegación de servicio (DDoS) Al limitar la cantidad de solicitudes, puede reducir el ataque de sobrecarga en el servidor y mejorar la estabilidad y confiabilidad del sistema.
aprende de
ChatGPT