¿Resulta que hay un ingenio sobre el límite de concurrencia oculto en 12306?

12306 ¿Tomar boletos, pensando en la concurrencia extrema?

Cada período de vacaciones, las personas que regresan a sus lugares de origen y salen en ciudades de primer y segundo nivel casi se enfrentan a un problema: ¡tomar boletos de tren! Aunque los boletos se pueden reservar en la mayoría de los casos, pero no hay boleto en el momento de la liberación. , Creo que todos Todos profundamente experimentados. Especialmente durante el Festival de Primavera, todos no solo usan 12306, sino que también consideran "Chixing" y otro software para obtener boletos. Cientos de millones de personas en todo el país están comprando boletos durante este tiempo. El "Servicio 12306" tiene un QPS que no puede ser superado por ningún sistema de eliminación instantánea en el mundo, ¡y millones de simultaneidad son normales! El autor estudió especialmente la arquitectura del lado del servidor de "12306”, y aprendió muchos puntos destacados en el diseño de su sistema. Aquí compartiré con ustedes y simularé un ejemplo: cómo proporcionar un sistema normal cuando 1 millón de personas toman 10,000 boletos de tren al mismo tiempo, servicio estable. dirección del código github

1. Arquitectura de sistema de alta concurrencia a gran escala

La arquitectura del sistema de alta concurrencia se implementará en clústeres distribuidos. La capa superior del servicio tiene equilibrio de carga capa por capa y proporciona varios métodos de recuperación ante desastres (sala de computadoras de doble fuego, tolerancia a fallas del nodo, recuperación ante desastres del servidor, etc.) .) para garantizar la alta disponibilidad del sistema, y ​​el tráfico también se basará en diferentes capacidades de carga y estrategias de configuración equilibradas para diferentes servidores. A continuación se muestra un diagrama simple:

1.1 Introducción al equilibrio de carga

La figura anterior describe que la solicitud del usuario al servidor ha pasado por tres niveles de balanceo de carga.Los tres tipos de balanceo de carga se presentan brevemente a continuación:

  • OSPF (Open Shortest Link First) es un protocolo de puerta de enlace interior (IGP). OSPF establece una base de datos de estado de enlace al anunciar el estado de las interfaces de red entre los enrutadores y genera el árbol de ruta más corto. OSPF calculará automáticamente el valor del costo en la interfaz de enrutamiento, pero el valor del costo de la interfaz también se puede especificar manualmente. valor calculado. El costo calculado por OSPF también es inversamente proporcional al ancho de banda de la interfaz. Cuanto mayor sea el ancho de banda, menor será el valor del costo. Las rutas que llegan al destino con el mismo valor de costo pueden realizar el equilibrio de carga y hasta 6 enlaces pueden realizar el equilibrio de carga al mismo tiempo.
  • LVS (Linux Virtual Server), que es una tecnología de clúster, adopta la tecnología de equilibrio de carga de IP y la tecnología de distribución de solicitudes basada en contenido. El programador tiene una buena tasa de rendimiento y transfiere solicitudes a diferentes servidores para su ejecución de manera equilibrada, y el programador protege automáticamente la falla del servidor, formando así un grupo de servidores en un servidor virtual de alto rendimiento y alta disponibilidad.
  • Nginx debe ser familiar para todos, es un servidor proxy inverso/proxy http de muy alto rendimiento y, a menudo, se usa para equilibrar la carga en el desarrollo de servicios. Hay tres formas principales para que Nginx logre el equilibrio de carga: sondeo, sondeo ponderado y sondeo hash de ip. A continuación, haremos una configuración y prueba especiales para el sondeo ponderado de Nginx.

1.2 Demostración del sondeo ponderado de Nginx

Nginx implementa el equilibrio de carga a través del módulo ascendente. La configuración del sondeo ponderado puede agregar un valor de peso al servicio relacionado. Al configurar, la carga correspondiente se puede establecer de acuerdo con el rendimiento y la capacidad de carga del servidor. La siguiente es una configuración de carga de sondeo ponderada. Escucharé en los puertos 3001-3004 localmente y configuraré los pesos de 1, 2, 3 y 4 respectivamente:

#配置负载均衡
    upstream load_rule {
       server 127.0.0.1:3001 weight=1;
       server 127.0.0.1:3002 weight=2;
       server 127.0.0.1:3003 weight=3;
       server 127.0.0.1:3004 weight=4;
    }
    ...
    server {
    listen       80;
    server_name  load_balance.com www.load_balance.com;
    location / {
       proxy_pass http://load_rule;
    }
}


复制代码

Configuré la dirección del nombre de dominio virtual de www.load\_balance.com en el directorio local /etc/hosts. Luego, usé el lenguaje Go para abrir cuatro servicios de monitoreo de puerto http. El siguiente es el programa Go escuchando en el puerto 3001. Necesitas modificar el puerto:

package main

import (
 "net/http"
 "os"
 "strings"
)

func main() {
 http.HandleFunc("/buy/ticket", handleReq)
 http.ListenAndServe(":3001", nil)
}

//处理请求函数,根据请求将响应结果信息写入日志
func handleReq(w http.ResponseWriter, r *http.Request) {
 failedMsg :=  "handle in port:"
 writeLog(failedMsg, "./stat.log")
}

//写入日志
func writeLog(msg string, logPath string) {
 fd, _ := os.OpenFile(logPath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
 defer fd.Close()
 content := strings.Join([]string{msg, "\r\n"}, "3001")
 buf := []byte(content)
 fd.Write(buf)
}


复制代码

Escribí la información de registro del puerto solicitada en el archivo ./stat.log y luego usé la herramienta de prueba de estrés ab para realizar la prueba de estrés:

ab -n 1000 -c 100 http://www.load_balance.com/buy/ticket


复制代码

De acuerdo con los resultados en el registro de estadísticas, los puertos 3001-3004 han recibido 100, 200, 300 y 400 solicitudes respectivamente, que están en buen acuerdo con la relación de peso que configuré en nginx, y el tráfico después de la carga es muy alto. uniforme y aleatoria. Para la implementación específica, puede consultar el código fuente de la implementación del módulo upstream de nginx.Aquí hay un artículo recomendado: Equilibrio de carga del mecanismo upstream en Nginx

2. Selección del tipo de sistema de compra de picos

Volviendo a la pregunta que comentábamos al principio: ¿cómo el sistema seckill de billetes de tren proporciona servicios normales y estables en condiciones de alta concurrencia?

De la introducción anterior, sabemos que el pico de tráfico del usuario se distribuye uniformemente a diferentes servidores a través del balanceo de carga capa por capa. Aun así, el QPS soportado por una sola máquina en el clúster es muy alto. ¿Cómo optimizar el rendimiento autónomo al extremo? Para resolver este problema, debemos entender una cosa: por lo general, el sistema de reserva de boletos tiene que lidiar con las tres etapas básicas de generar pedidos, deducir inventario y pagar a los usuarios. Lo que nuestro sistema debe hacer es garantizar que el pedido de boletos de tren no pasa de Vender, vender mucho, cada boleto vendido debe ser pagado para ser válido, y además el sistema debe soportar altísima concurrencia. ¿Cómo debería asignarse más razonablemente la secuencia de estas tres etapas? Analicemos:

2.1 Orden de reducción de inventario

Cuando la solicitud simultánea de un usuario llega al servidor, primero se crea un pedido, luego se descuenta el inventario y el usuario espera el pago. Este pedido es la primera solución que la mayoría de la gente piensa, en este caso también puede asegurar que el pedido no se sobrevenda, porque el inventario se reducirá después de que se cree el pedido, que es una operación atómica. Sin embargo, esto también causará algunos problemas. El primero es que, en el caso de una concurrencia extrema, los detalles de cualquier operación de memoria afectarán el rendimiento, especialmente la lógica de creación de una orden, que generalmente debe almacenarse en la base de datos del disco. lo cual ejerce presión sobre la base de datos, es concebible, la segunda es que si el usuario realiza un pedido maliciosamente, solo realizar el pedido sin pagar reducirá el inventario y venderá muchos pedidos, aunque el servidor puede limitar el número de IP y órdenes de compra del usuario, esta tampoco es una buena manera.

2.2 Pago menos inventario

Si esperas a que el usuario pague el pedido y luego reduces el inventario, la primera sensación es que no habrá menos ventas. Pero este es un gran tabú para la arquitectura concurrente, porque en el caso de concurrencia extrema, los usuarios pueden crear muchos pedidos. Cuando el inventario se reduce a cero, muchos usuarios descubren que los pedidos tomados no se pueden pagar, que es la razón. llamado "sobrevendido". También es imposible evitar la operación simultánea del disco de base de datos IO

2.3 Inventario de retención

De la consideración de los dos esquemas anteriores, podemos concluir que siempre que se cree un pedido, la base de datos IO debe operarse con frecuencia. Entonces, ¿hay una solución que no requiera la operación directa de la base de datos IO, que es la retención de inventario? Primero se descuenta el inventario para asegurar que no se sobrevenda, y luego se genera el pedido del usuario de forma asíncrona, para que la respuesta al usuario sea mucho más rápida, entonces ¿cómo garantizar muchas ventas? ¿Qué pasa si el usuario recibe el pedido y no paga? Todos sabemos que los pedidos tienen una fecha de caducidad. Por ejemplo, si el usuario no paga en cinco minutos, el pedido se invalidará. Una vez que el pedido caduque, se agregará un nuevo inventario. Esta es también la solución adoptada por muchos minoristas en línea. empresas para garantizar que se vendan muchos productos. La generación de pedidos es asíncrona y generalmente se procesan en colas de consumo en tiempo real como MQ y Kafka.Cuando el volumen de pedidos es relativamente pequeño, la generación de pedidos es muy rápida y los usuarios apenas necesitan hacer cola.

3. El arte de deducir inventario

Del análisis anterior, es obvio que el plan de retención de inventario es el más razonable. Analizamos más a fondo los detalles de la deducción del inventario. Todavía hay mucho espacio para la optimización. ¿Dónde existe el inventario? ¿Cómo garantizar la deducción correcta del inventario bajo alta concurrencia y responder rápidamente a las solicitudes de los usuarios?

En el caso de baja concurrencia de una sola máquina, generalmente deducimos el inventario así:

Para garantizar la atomicidad de la deducción del inventario y la generación de pedidos, es necesario usar el procesamiento de transacciones, luego tomar un juicio de inventario, reducir el inventario y finalmente enviar la transacción.Todo el proceso tiene mucho IO y el funcionamiento de la base de datos está bloqueado. Este método no es adecuado para un sistema de picos de alta concurrencia.

A continuación, optimizaremos el esquema de deducción de inventario para una sola máquina: deducción local de inventario. Asignamos una cierta cantidad de inventario a la máquina local, reducimos directamente el inventario en la memoria y luego creamos pedidos de forma asíncrona de acuerdo con la lógica anterior. El sistema independiente mejorado es el siguiente:

De esta manera, se evitan las operaciones de E/S frecuentes en la base de datos y las operaciones solo se realizan en la memoria, lo que mejora en gran medida la capacidad anticoncurrencia de una sola máquina. Sin embargo, una sola máquina con millones de solicitudes de usuarios no puede soportarlo de todos modos. Aunque nginx usa el modelo epoll para procesar solicitudes de red, el problema de c10k ya se ha resuelto en la industria. Sin embargo, en el sistema Linux, todos los recursos son archivos, y lo mismo ocurre con las solicitudes de red. Una gran cantidad de descriptores de archivos hará que el sistema operativo pierda respuesta en un instante. Anteriormente mencionamos la estrategia de equilibrio ponderado de nginx. También podríamos suponer que el volumen de solicitudes de usuario de 100 W se equilibra uniformemente con 100 servidores, de modo que la cantidad de concurrencia soportada por una sola máquina es mucho menor. Luego almacenamos 100 boletos de tren localmente en cada máquina, y el stock total en 100 servidores sigue siendo 10 000, lo que garantiza que los pedidos de stock no se vendan en exceso.La siguiente es la arquitectura de clúster que describimos:

Los problemas siguen. En el caso de alta concurrencia, no podemos garantizar la alta disponibilidad del sistema. Si dos o tres máquinas en los servidores 100 están inactivas porque no pueden manejar tráfico concurrente u otras razones. Entonces, los pedidos en estos servidores no se pueden vender, lo que da como resultado que se vendan menos pedidos. Para resolver este problema, necesitamos administrar el volumen total de pedidos de manera unificada, que es la próxima solución tolerante a fallas. El servidor no solo necesita reducir el inventario localmente, sino que también necesita reducir el inventario de forma remota. Con la operación de reducción de inventario unificada remota, podemos asignar un exceso de "inventario intermedio" a cada máquina de acuerdo con la carga de la máquina para evitar el tiempo de inactividad de la máquina. Analicémoslo en detalle con el siguiente diagrama de arquitectura:

Usamos Redis para almacenar un inventario unificado, porque el rendimiento de Redis es muy alto y se afirma que el QPS de una sola máquina puede resistir 10 W de simultaneidad. Después de la reducción del inventario local, si hay un pedido localmente, solicitaremos a Redis que reduzca el inventario de forma remota. Después de que tanto la reducción del inventario local como la reducción del inventario remoto sean exitosas, le devolveremos al usuario un aviso para obtener el ticket con éxito. que también puede garantizar de manera efectiva que el pedido no se reducirá.

Cuando una de las máquinas está inactiva, debido a que cada máquina tiene boletos restantes almacenados en el búfer, los boletos restantes en la máquina inactiva aún se pueden recuperar en otras máquinas, lo que garantiza muchas ventas. ¿Cuál es la configuración adecuada para el búfer de votos restantes? En teoría, cuantos más búferes se configuran, más máquinas puede tolerar el sistema el tiempo de inactividad. Sin embargo, si el búfer se establece demasiado grande, también tendrá un cierto impacto en redis. Aunque la capacidad anticoncurrencia de la base de datos en memoria de redis es muy alta, la solicitud aún pasará por una red IO. De hecho, la cantidad de solicitudes para redis durante el proceso de captura de tickets es la cantidad total de inventario local y búfer. inventario, porque cuando el inventario local es insuficiente, el sistema devuelve directamente al usuario el mensaje "Agotado", ya no se seguirá la lógica de deducción uniforme del inventario, lo que hasta cierto punto también evita la enorme cantidad de red solicitudes para abrumar a redis, por lo que cuánto se establece el valor del búfer depende de la carga del arquitecto en el sistema Capacidad para hacer una consideración seria.

4. Demostración de código

El lenguaje Go está diseñado de forma nativa para la concurrencia. Utilizo el lenguaje Go para demostrar el proceso específico de captura de boletos de una sola máquina.

4.1 Trabajo de inicialización

La función init en el paquete go se ejecuta antes que la función principal, y algunos trabajos preparatorios se realizan principalmente en esta etapa. Los preparativos que nuestro sistema necesita hacer son: inicializar el inventario local, inicializar el valor de la clave hash del inventario unificado de almacenamiento remoto de redis, inicializar el conjunto de conexiones de redis; además, necesitamos inicializar un canal de tipo int con un tamaño de 1 , el propósito es realizar la función de bloqueo distribuido, también puede usar bloqueos de lectura y escritura directamente o usar otros métodos como redis para evitar la competencia de recursos, pero es más eficiente usar canales. Esta es la filosofía del lenguaje go : no comunicarse a través de la memoria compartida, sino compartir la memoria a través de la comunicación. La biblioteca redis usa redigo, y la siguiente es la implementación del código:

...
//localSpike包结构体定义
package localSpike

type LocalSpike struct {
 LocalInStock     int64
 LocalSalesVolume int64
}
...
//remoteSpike对hash结构的定义和redis连接池
package remoteSpike
//远程订单存储健值
type RemoteSpikeKeys struct {
 SpikeOrderHashKey string //redis中秒杀订单hash结构key
 TotalInventoryKey string //hash结构中总订单库存key
 QuantityOfOrderKey string //hash结构中已有订单数量key
}

//初始化redis连接池
func NewPool() *redis.Pool {
 return &redis.Pool{
  MaxIdle:   10000,
  MaxActive: 12000, // max number of connections
  Dial: func() (redis.Conn, error) {
   c, err := redis.Dial("tcp", ":6379")
   if err != nil {
    panic(err.Error())
   }
   return c, err
  },
 }
}
...
func init() {
 localSpike = localSpike2.LocalSpike{
  LocalInStock:     150,
  LocalSalesVolume: 0,
 }
 remoteSpike = remoteSpike2.RemoteSpikeKeys{
  SpikeOrderHashKey:  "ticket_hash_key",
  TotalInventoryKey:  "ticket_total_nums",
  QuantityOfOrderKey: "ticket_sold_nums",
 }
 redisPool = remoteSpike2.NewPool()
 done = make(chan int, 1)
 done <- 1
}


复制代码

4.2 Deducción local de inventario y deducción unificada de inventario

La lógica de la deducción del inventario local es muy simple, el usuario lo solicita, agrega el volumen de ventas, luego compara si el volumen de ventas es mayor que el inventario local y devuelve el valor bool:

package localSpike
//本地扣库存,返回bool值
func (spike *LocalSpike) LocalDeductionStock() bool{
 spike.LocalSalesVolume = spike.LocalSalesVolume + 1
 return spike.LocalSalesVolume < spike.LocalInStock
}


复制代码

Tenga en cuenta que la operación de LocalSalesVolume de datos compartidos aquí se implementa mediante bloqueos, pero debido a que la deducción de inventario local y la deducción de inventario unificado son una operación atómica, el canal se usa en la capa superior para implementar, lo cual se analizará más adelante. La deducción unificada de la operación de inventario redis, porque redis es de subproceso único, y necesitamos realizar el proceso de obtener datos de él, escribir datos y calcular una serie de pasos, necesitamos cooperar con el script lua para empaquetar los comandos para asegurar la atomicidad de la operación:

package remoteSpike
......
const LuaScript = `
        local ticket_key = KEYS[1]
        local ticket_total_key = ARGV[1]
        local ticket_sold_key = ARGV[2]
        local ticket_total_nums = tonumber(redis.call('HGET', ticket_key, ticket_total_key))
        local ticket_sold_nums = tonumber(redis.call('HGET', ticket_key, ticket_sold_key))
  -- 查看是否还有余票,增加订单数量,返回结果值
       if(ticket_total_nums >= ticket_sold_nums) then
            return redis.call('HINCRBY', ticket_key, ticket_sold_key, 1)
        end
        return 0
`
//远端统一扣库存
func (RemoteSpikeKeys *RemoteSpikeKeys) RemoteDeductionStock(conn redis.Conn) bool {
 lua := redis.NewScript(1, LuaScript)
 result, err := redis.Int(lua.Do(conn, RemoteSpikeKeys.SpikeOrderHashKey, RemoteSpikeKeys.TotalInventoryKey, RemoteSpikeKeys.QuantityOfOrderKey))
 if err != nil {
  return false
 }
 return result != 0
}


复制代码

Usamos la estructura hash para almacenar la información del inventario total y las ventas totales. Cuando el usuario lo solicite, juzgará si las ventas totales son mayores que el inventario y luego devolverá el valor bool relevante. Antes de iniciar el servicio, necesitamos inicializar la información de inventario inicial de redis:

 hmset ticket_hash_key "ticket_total_nums" 10000 "ticket_sold_nums" 0


复制代码

4.3 Respondiendo a la información del usuario

Iniciamos un servicio http escuchando en un puerto:

package main
...
func main() {
 http.HandleFunc("/buy/ticket", handleReq)
 http.ListenAndServe(":3005", nil)
}


复制代码

Hemos hecho todo el trabajo de inicialización anterior. A continuación, la lógica de handleReq es muy clara. Es suficiente para juzgar si la obtención del ticket es exitosa y devolver la información al usuario.

package main
//处理请求函数,根据请求将响应结果信息写入日志
func handleReq(w http.ResponseWriter, r *http.Request) {
 redisConn := redisPool.Get()
 LogMsg := ""
 <-done
 //全局读写锁
 if localSpike.LocalDeductionStock() && remoteSpike.RemoteDeductionStock(redisConn) {
  util.RespJson(w, 1,  "抢票成功", nil)
  LogMsg = LogMsg + "result:1,localSales:" + strconv.FormatInt(localSpike.LocalSalesVolume, 10)
 } else {
  util.RespJson(w, -1, "已售罄", nil)
  LogMsg = LogMsg + "result:0,localSales:" + strconv.FormatInt(localSpike.LocalSalesVolume, 10)
 }
 done <- 1

 //将抢票状态写入到log中
 writeLog(LogMsg, "./stat.log")
}

func writeLog(msg string, logPath string) {
 fd, _ := os.OpenFile(logPath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
 defer fd.Close()
 content := strings.Join([]string{msg, "\r\n"}, "")
 buf := []byte(content)
 fd.Write(buf)
}


复制代码

Como se mencionó anteriormente, debemos tener en cuenta las condiciones de carrera al deducir el inventario. Aquí usamos canales para evitar lecturas y escrituras simultáneas, y garantizar una ejecución eficiente y secuencial de las solicitudes. Escribimos la información de retorno de la interfaz en el archivo ./stat.log para facilitar las estadísticas de medición de estrés.

4.4 Prueba de estrés del servicio autónomo

Para iniciar el servicio, usamos la herramienta de prueba de presión ab para probar:

ab -n 10000 -c 100 http://127.0.0.1:3005/buy/ticket


复制代码

La siguiente es la información de prueba de presión de mi mac local de gama baja

This is ApacheBench, Version 2.3 <$Revision: 1826891 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking 127.0.0.1 (be patient)
Completed 1000 requests
Completed 2000 requests
Completed 3000 requests
Completed 4000 requests
Completed 5000 requests
Completed 6000 requests
Completed 7000 requests
Completed 8000 requests
Completed 9000 requests
Completed 10000 requests
Finished 10000 requests


Server Software:
Server Hostname:        127.0.0.1
Server Port:            3005

Document Path:          /buy/ticket
Document Length:        29 bytes

Concurrency Level:      100
Time taken for tests:   2.339 seconds
Complete requests:      10000
Failed requests:        0
Total transferred:      1370000 bytes
HTML transferred:       290000 bytes
Requests per second:    4275.96 [#/sec] (mean)
Time per request:       23.387 [ms] (mean)
Time per request:       0.234 [ms] (mean, across all concurrent requests)
Transfer rate:          572.08 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    8  14.7      6     223
Processing:     2   15  17.6     11     232
Waiting:        1   11  13.5      8     225
Total:          7   23  22.8     18     239

Percentage of the requests served within a certain time (ms)
  50%     18
  66%     24
  75%     26
  80%     28
  90%     33
  95%     39
  98%     45
  99%     54
 100%    239 (longest request)


复制代码

De acuerdo con los indicadores, mi única máquina puede manejar más de 4000 solicitudes por segundo. Los servidores normales son todas configuraciones de múltiples núcleos, y no hay problema en el manejo de solicitudes de 1W+. Y mirando el registro, se encuentra que durante todo el proceso de servicio, las solicitudes son normales, el tráfico es uniforme y redis también es normal:

//stat.log
...
result:1,localSales:145
result:1,localSales:146
result:1,localSales:147
result:1,localSales:148
result:1,localSales:149
result:1,localSales:150
result:0,localSales:151
result:0,localSales:152
result:0,localSales:153
result:0,localSales:154
result:0,localSales:156
...


复制代码

5. Revisión resumida

En general, el sistema seckill es muy complicado. Aquí solo presentamos y simulamos brevemente cómo se puede optimizar una sola máquina para lograr un alto rendimiento, cómo un clúster puede evitar un único punto de falla y garantizar que los pedidos no se sobrevendan o se vendan mucho. Facturas e información de inventario del inventario total y mostrarlo al usuario, y el usuario no paga dentro del período de validez del pedido, libera el pedido, repone el inventario, etc.

Hemos implementado la lógica central de obtención de tickets de alta simultaneidad. Se puede decir que el diseño del sistema es muy inteligente. Evita hábilmente la operación de E/S de la base de datos de la base de datos y las solicitudes concurrentes altas para la E/S de la red Redis. Casi todos los cálculos están en la memoria. Se completa y garantiza efectivamente que no se sobrevenda o se venda mucho, y también puede tolerar el tiempo de inactividad de algunas máquinas. Creo que vale la pena aprender y resumir dos de ellos:

  • Equilibrio de carga, divide y vencerás. A través del equilibrio de carga, el tráfico diferente se divide en diferentes máquinas. Cada máquina maneja sus propias solicitudes y maximiza su rendimiento, de modo que el sistema en su conjunto puede soportar una concurrencia extremadamente alta, como si trabajara en equipo. Todos han aportado su valor al extremo, y el crecimiento del equipo es naturalmente grande.
  • Uso razonable de la concurrencia y la asincronía. Desde que el modelo de arquitectura de red epoll resolvió el problema c10k, la asincronía ha sido cada vez más aceptada por los desarrolladores de servidores. El trabajo que se puede hacer de forma asíncrona se puede hacer de forma asíncrona, lo que puede lograr resultados inesperados en el desensamblaje funcional. Se puede reflejar en nginx, node.js y redis El modelo epoll que utilizan para procesar las solicitudes de red nos ha demostrado que un solo subproceso aún puede ejercer un poder poderoso. El servidor ha entrado en la era multinúcleo. El lenguaje go, un lenguaje que nace para la concurrencia, aprovecha perfectamente las ventajas multinúcleo del servidor. Muchas tareas que pueden ser procesadas concurrentemente pueden ser resueltas por concurrencia. Por ejemplo, cuando go procesa las solicitudes http, cada solicitud se ejecutará en una rutina go, en resumen: cómo exprimir la CPU razonablemente y hacer que juegue su valor debido es la dirección que siempre debemos explorar y aprender.


Autor:
Official Account_IT Brother Enlace: https://juejin.cn/post/7020215813969805343
Fuente: Rare Earth Nuggets
Los derechos de autor pertenecen al autor. Para reimpresiones comerciales, comuníquese con el autor para obtener autorización, y para reimpresiones no comerciales, indique la fuente.

Supongo que te gusta

Origin blog.csdn.net/wdjnb/article/details/124363781
Recomendado
Clasificación