>  기사  >  백엔드 개발  >  “12306”의 건축물은 얼마나 멋진가요?

“12306”의 건축물은 얼마나 멋진가요?

angryTom
angryTom앞으로
2019-11-05 18:15:572903검색

휴일마다 집에 돌아가거나 1, 2선 도시에 놀러 나가는 사람들은 거의 항상 한 가지 문제에 직면합니다. 바로 기차표를 구하는 것입니다!

12306 티켓 잡기, 극도의 동시성이 불러일으키는 생각

지금은 대부분의 경우 티켓 예매가 가능하지만, 하지만 저는 티켓이 오픈되자마자 티켓이 없는 상황을 다들 깊이 이해하고 계시리라 믿습니다.
특히 춘절 기간 동안 사람들은 12306을 사용할 뿐만 아니라 "Zhixing" 및 기타 티켓 예매 소프트웨어도 고려합니다.
"12306 서비스"는 세상 그 어떤 인스턴트 킬링 시스템도 능가할 수 없는 QPS를 가지고 있습니다.
저자는 "12306"의 서버 아키텍처를 특별히 연구했으며 시스템 설계의 많은 하이라이트를 배웠습니다. 여기서는 100만 명이 동시에 기차표 10,000장을 구하는 방법, 시스템을 시뮬레이션해 보겠습니다. 정상적이고 안정적인 서비스를 제공합니다.

Github 코드 주소:

https://github.com/GuoZhaoran/spikeSystem

#🎜🎜 #

대규모 동시성 시스템 아키텍처

고동시성 시스템 아키텍처는 서비스의 상위 계층에 계층별 로드 밸런싱이 적용됩니다. 다양한 재해 복구 방법(이중 화재 컴퓨터실, 노드 내결함성, 서버 재해 복구 등)을 제공하여 시스템의 고가용성을 보장하고 다양한 로드 기능 및 구성 전략을 기반으로 트래픽을 다양한 서버로 분산시킵니다.

다음은 간단한 다이어그램입니다.

“12306”의 건축물은 얼마나 멋진가요?

Load Balancing Simple

위 그림은 사용자 요청에 대해 설명합니다. 서버는 세 가지 계층의 로드 밸런싱을 경험했습니다. 다음은 이 세 가지 유형의 로드 밸런싱에 대해 간략하게 소개합니다.

①OSPF(Open Shortest Link First)는 내부 게이트웨이 프로토콜(IGP)입니다.

OSPF는 라우터를 통과하여 상태를 광고합니다. OSPF는 라우팅 인터페이스의 비용 값을 자동으로 계산하지만 인터페이스의 비용 값을 수동으로 지정할 수도 있습니다. 수동으로 지정한 값이 자동 값보다 우선합니다. 계산된 값.

OSPF에서 계산한 Cost는 인터페이스 대역폭에 반비례합니다. 대역폭이 높을수록 Cost 값은 작아집니다. 타겟까지 비용 값이 동일한 경로는 로드 밸런싱을 수행할 수 있으며, 최대 6개의 링크까지 동시에 로드 밸런싱을 수행할 수 있습니다.

②LVS (Linux Virtual Server)

IP 로드 밸런싱 기술과 컨텐츠 기반 요청 분산을 이용한 클러스터(Cluster) 기술입니다. 기술.

스케줄러는 처리 속도가 매우 뛰어나며 실행을 위해 요청을 다른 서버로 균등하게 전송합니다. 스케줄러는 서버 장애를 자동으로 보호하여 서버 그룹을 고성능, 고가용성 가상 서버로 구성합니다.

3Nginx

매우 고성능의 HTTP 프록시/리버스 프록시 서버이며, 서비스도 잘 알고 계시리라 믿습니다. 개발 중입니다. 로드 밸런싱에도 자주 사용됩니다.

Nginx가 로드 밸런싱을 달성하는 세 가지 주요 방법이 있습니다: 폴링 가중 폴링 IP 해시 폴링

아래에서는 Nginx의 가중 폴링에 대한 특수 구성 및 테스트를 수행합니다.

Nginx 가중치 폴링 시연

Nginx는 Upstream 모듈을 통해 로드 밸런싱을 구현하며, 가중치 폴링 구성을 연관시킬 수 있는 서비스입니다. 가중치 값을 추가하며, 구성 시 서버의 성능 및 부하 용량에 따라 해당 부하를 설정할 수 있습니다.

다음은 가중 폴링 로드 구성입니다. 포트 3001-3004를 로컬로 수신하고 각각 1, 2, 3, 4의 가중치를 구성합니다.

#配置负载均衡
    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;
    }
}

로컬 /etc/hosts 디렉터리에 www.load_balance.com이라는 가상 도메인 이름 주소를 구성했습니다.

다음으로 Go 언어를 사용하여 4개의 HTTP 포트 수신 서비스를 엽니다. 다음은 포트 3001에서 수신하는 Go 프로그램입니다. 나머지 몇 개는 포트만 수정하면 됩니다.

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)
}
#🎜🎜 # 요청한 포트 로그 정보를 ./stat.log 파일에 기록한 다음 AB 스트레스 테스트 도구를 사용하여 스트레스 테스트를 수행했습니다. ab -n 1000 -

c 100 http://www.load_balance.com/buy/ticket

결과는 통계에 나와 있습니다. 로그, 3001-3004 포트는 각각 100, 200, 300 및 400개의 요청을 수신했습니다.

이것은 Nginx에서 구성한 가중치 비율과 잘 일치하며 로드 후 트래픽은 매우 균일하고 무작위입니다.

구체적인 구현에 대해서는 Nginx의 Upsteam 모듈 구현 소스 코드를 참조할 수 있습니다. 다음은 추천 문서 "Nginx의 업스트림 메커니즘 부하 분산"입니다: https://www.kancloud.cn/digest/ Understandingnginx/ 202607


깜짝 세일 시스템 선택

원래 언급했던 질문으로 돌아가서: 기차표 깜짝 세일은 어떻습니까? 높은 동시성 조건에서 정상적이고 안정적인 서비스를 제공하는 방법은 무엇입니까?

위의 소개에서 우리는 사용자 플래시 세일 트래픽이 여러 계층의 로드 밸런싱을 통해 여러 서버에 고르게 분산된다는 것을 알고 있습니다. 그럼에도 불구하고 클러스터의 단일 머신이 견뎌내는 QPS도 매우 높습니다. 독립 실행형 성능을 극한까지 최적화하는 방법은 무엇입니까?

이 문제를 해결하려면 한 가지를 이해해야 합니다. 일반적으로 예약 시스템은 주문 생성, 재고 감소, 사용자 결제의 세 가지 기본 단계를 처리해야 합니다.

우리 시스템이 해야 할 일은 기차표 주문이 과매도되거나 과매도되지 않도록 하는 것입니다. 판매된 모든 티켓이 유효하려면 시스템이 매우 높은 동시성을 견딜 수 있는지 확인해야 합니다.

이 세 단계의 순서를 어떻게 더 합리적으로 할당해야 할까요? 분석해 보겠습니다.

재고를 줄이기 위해 주문하세요

“12306”의 건축물은 얼마나 멋진가요?
사용자의 동시 요청이 서버에 도달하면 먼저 주문이 생성된 다음 재고가 차감되고 사용자가 지불합니다.

이 주문은 우리 대부분이 생각할 첫 번째 솔루션입니다. 이 경우 원자적 작업인 주문이 생성된 후 재고가 줄어들기 때문에 주문이 과매도되지 않도록 보장할 수도 있습니다.

그러나 이로 인해 몇 가지 문제도 발생할 수 있습니다.

극단적인 동시성의 경우 메모리 작업의 세부 사항, 특히 일반적으로 디스크 데이터베이스에 저장해야 하는 주문 생성과 같은 논리가 성능에 큰 영향을 미칩니다. . 데이터베이스에 대한 압박이 있을 수 있습니다.

사용자가 악의적으로 주문하고 결제하지 않고 주문만 하면 재고가 줄어들고, 서버에서 IP와 사용자 구매 주문 수를 제한할 수 있지만 이는 그렇지 않습니다. 좋은 방법이다.

결제하여 재고 줄이기

사용자가 주문 대금을 지불하고 재고를 줄이길 기다리면 첫 번째 느낌은 매출이 줄어들지 않을 것이라는 것입니다. 그러나 이는 동시 아키텍처의 금기 사항입니다. 왜냐하면 극단적인 동시성에서는 사용자가 많은 주문을 생성할 수 있기 때문입니다.

“12306”의 건축물은 얼마나 멋진가요?

재고가 0으로 줄어들면 많은 사용자는 자신이 잡은 주문에 대해 지불할 수 없다는 것을 알게 됩니다. 이를 "과매도"라고도 합니다. 동시 데이터베이스 디스크 IO 작업도 피할 수 없습니다.
재고 보류

위의 두 가지 솔루션을 고려하여 주문이 생성되는 한 데이터베이스 IO가 자주 작동되어야 한다는 결론을 내릴 수 있습니다.

그렇다면 데이터베이스 IO를 직접 운영하지 않아도 되는 솔루션이 있을까요? 이것이 바로 재고 원천징수입니다. 먼저, 과매도되지 않도록 재고를 차감한 다음, 사용자 주문이 비동기적으로 생성되어 사용자에 대한 응답이 훨씬 빨라질 것입니다. 그러면 판매량이 많이 발생하도록 하려면 어떻게 해야 할까요? 주문을 받은 후 결제를 하지 않으면 사용자는 어떻게 해야 하나요?

우리 모두는 주문에 유효 기간이 있다는 것을 알고 있습니다. 예를 들어, 사용자가 5분 이내에 결제하지 않으면 주문이 만료되고, 이는 많은 온라인 소매업체에서도 마찬가지입니다. 이제 그들이 많은 상품을 판매하는지 확인하는 데 사용합니다.

주문은 비동기식으로 생성되며 일반적으로 MQ 및 Kafka와 같은 인스턴트 소비 대기열에서 처리됩니다. 주문량이 상대적으로 적을 경우 주문이 매우 빠르게 생성되며 사용자가 대기열에 들어갈 필요가 거의 없습니다.

재고 원천징수의 기술

위의 분석에서 재고 원천징수 솔루션이 가장 합리적인 것은 분명합니다. 재고 공제 세부 사항을 더 자세히 분석해 보겠습니다. 아직 최적화할 여지가 많이 남아 있습니다. 높은 동시성과 사용자 요청에 대한 신속한 응답 하에서 올바른 재고 공제를 보장하는 방법은 무엇입니까?

단일 머신에서 동시성이 낮은 경우 일반적으로 재고 공제 구현은 다음과 같습니다.

“12306”의 건축물은 얼마나 멋진가요?


인벤토리 공제 및 주문 생성의 원자성을 보장하려면 트랜잭션 처리를 사용해야 합니다. 그런 다음 재고를 판단하고 재고를 줄이고 마지막으로 트랜잭션을 커밋하려면 전체 프로세스에 많은 IO가 포함되고 데이터베이스 작업이 차단됩니다.

이 방법은 동시성이 높은 플래시 세일 시스템에는 전혀 적합하지 않습니다. 다음으로 단일 기계 재고 공제 계획인 현지 재고 공제를 최적화합니다.

일정량의 재고를 로컬 머신에 할당하고 메모리에서 직접 재고를 줄인 다음 이전 로직에 따라 비동기적으로 주문을 생성합니다.

향상된 독립 실행형 시스템은 다음과 같습니다.

“12306”의 건축물은 얼마나 멋진가요?

이는 데이터베이스에서 빈번한 IO 작업을 방지하고 메모리에서만 계산을 수행하므로 독립 실행형 시스템의 동시성에 저항하는 기능이 크게 향상됩니다.

그러나 단일 시스템은 어떤 경우에도 수백만 명의 사용자 요청을 견딜 수 없습니다. Nginx는 Epoll 모델을 사용하여 네트워크 요청을 처리하지만 c10k 문제는 업계에서 오랫동안 해결되었습니다.

그러나 Linux 시스템에서는 모든 리소스가 파일이며 네트워크 요청의 경우에도 마찬가지입니다. 파일 설명자가 많으면 운영 체제가 즉시 응답하지 않게 됩니다.
위에서 Nginx의 가중치 밸런싱 전략을 언급했습니다. 100만 개의 사용자 요청이 평균 100개의 서버로 밸런싱되어 단일 시스템이 견디는 동시성의 양이 훨씬 적다고 가정하는 것이 좋습니다.

그런 다음 각 기계에 로컬로 100장의 기차표를 저장하고 100개 서버의 총 재고는 여전히 10,000입니다. 이를 통해 재고 주문이 초과 판매되지 않도록 보장합니다.

“12306”의 건축물은 얼마나 멋진가요?

问题接踵而至,在高并发情况下,现在我们还无法保证系统的高可用,假如这 100 台服务器上有两三台机器因为扛不住并发的流量或者其他的原因宕机了。那么这些服务器上的订单就卖不出去了,这就造成了订单的少卖。

要解决这个问题,我们需要对总订单量做统一的管理,这就是接下来的容错方案。服务器不仅要在本地减库存,另外要远程统一减库存。
有了远程统一减库存的操作,我们就可以根据机器负载情况,为每台机器分配一些多余的“Buffer 库存”用来防止机器中有机器宕机的情况。

我们结合下面架构图具体分析一下:

“12306”의 건축물은 얼마나 멋진가요?


我们采用 Redis 存储统一库存,因为 Redis 的性能非常高,号称单机 QPS 能抗 10W 的并发。

在本地减库存以后,如果本地有订单,我们再去请求 Redis 远程减库存,本地减库存和远程减库存都成功了,才返回给用户抢票成功的提示,这样也能有效的保证订单不会超卖。

当机器中有机器宕机时,因为每个机器上有预留的 Buffer 余票,所以宕机机器上的余票依然能够在其他机器上得到弥补,保证了不少卖。
Buffer 余票设置多少合适呢,理论上 Buffer 设置的越多,系统容忍宕机的机器数量就越多,但是 Buffer 设置的太大也会对 Redis 造成一定的影响。

虽然 Redis 内存数据库抗并发能力非常高,请求依然会走一次网络 IO,其实抢票过程中对 Redis 的请求次数是本地库存和 Buffer 库存的总量。

因为当本地库存不足时,系统直接返回用户“已售罄”的信息提示,就不会再走统一扣库存的逻辑。

这在一定程度上也避免了巨大的网络请求量把 Redis 压跨,所以 Buffer 值设置多少,需要架构师对系统的负载能力做认真的考量。

代码演示

Go 语言原生为并发设计,我采用 Go 语言给大家演示一下单机抢票的具体流程。

初始化工作

Go 包中的 Init 函数先于 Main 函数执行,在这个阶段主要做一些准备性工作。
我们系统需要做的准备工作有:初始化本地库存、初始化远程 Redis 存储统一库存的 Hash 键值、初始化 Redis 连接池。

另外还需要初始化一个大小为 1 的 Int 类型 Chan,目的是实现分布式锁的功能。

也可以直接使用读写锁或者使用 Redis 等其他的方式避免资源竞争,但使用 Channel 更加高效,这就是 Go 语言的哲学:不要通过共享内存来通信,而要通过通信来共享内存。
Redis 库使用的是 Redigo,下面是代码实现:...

//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
}

本地扣库存和统一扣库存

本地扣库存逻辑非常简单,用户请求过来,添加销量,然后对比销量是否大于本地库存,返回 Bool 值:package localSpike

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

注意这里对共享数据 LocalSalesVolume 的操作是要使用锁来实现的,但是因为本地扣库存和统一扣库存是一个原子性操作,所以在最上层使用 Channel 来实现,这块后边会讲。

统一扣库存操作 Redis,因为 Redis 是单线程的,而我们要实现从中取数据,写数据并计算一些列步骤,我们要配合 Lua 脚本打包命令,保证操作的原子性:

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(&#39;HGET&#39;, ticket_key, ticket_total_key))
        local ticket_sold_nums = tonumber(redis.call(&#39;HGET&#39;, ticket_key, ticket_sold_key))
        -- 查看是否还有余票,增加订单数量,返回结果值
       if(ticket_total_nums >= ticket_sold_nums) then
            return redis.call(&#39;HINCRBY&#39;, 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
}

我们使用 Hash 结构存储总库存和总销量的信息,用户请求过来时,判断总销量是否大于库存,然后返回相关的 Bool 值。

在启动服务之前,我们需要初始化 Redis 的初始库存信息:

hmset ticket_hash_key "ticket_total_nums" 10000 "ticket_sold_nums" 0

响应用户信息

我们开启一个 HTTP 服务,监听在一个端口上:

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

上面我们做完了所有的初始化工作,接下来 handleReq 的逻辑非常清晰,判断是否抢票成功,返回给用户信息就可以了。

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)
}

前边提到我们扣库存时要考虑竞态条件,我们这里是使用 Channel 避免并发的读写,保证了请求的高效顺序执行。我们将接口的返回信息写入到了 ./stat.log 文件方便做压测统计。

单机服务压测

开启服务,我们使用 AB 压测工具进行测试:

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


下面是我本地低配 Mac 的压测信息:

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)

根据指标显示,我单机每秒就能处理 4000+ 的请求,正常服务器都是多核配置,处理 1W+ 的请求根本没有问题。

而且查看日志发现整个服务过程中,请求都很正常,流量均匀,Redis 也很正常://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
...

总结回顾

总体来说,秒杀系统是非常复杂的。我们这里只是简单介绍模拟了一下单机如何优化到高性能,集群如何避免单点故障,保证订单不超卖、不少卖的一些策略

完整的订单系统还有订单进度的查看,每台服务器上都有一个任务,定时的从总库存同步余票和库存信息展示给用户,还有用户在订单有效期内不支付,释放订单,补充到库存等等。
我们实现了高并发抢票的核心逻辑,可以说系统设计的非常的巧妙,巧妙的避开了对 DB 数据库 IO 的操作。
对 Redis 网络 IO 的高并发请求,几乎所有的计算都是在内存中完成的,而且有效的保证了不超卖、不少卖,还能够容忍部分机器的宕机。

我觉得其中有两点特别值得学习总结:
①负载均衡,分而治之

通过负载均衡,将不同的流量划分到不同的机器上,每台机器处理好自己的请求,将自己的性能发挥到极致。

这样系统的整体也就能承受极高的并发了,就像工作的一个团队,每个人都将自己的价值发挥到了极致,团队成长自然是很大的。

②合理的使用并发和异步

自 Epoll 网络架构模型解决了 c10k 问题以来,异步越来越被服务端开发人员所接受,能够用异步来做的工作,就用异步来做,在功能拆解上能达到意想不到的效果。

这点在 Nginx、Node.JS、Redis 上都能体现,他们处理网络请求使用的 Epoll 模型,用实践告诉了我们单线程依然可以发挥强大的威力。
服务器已经进入了多核时代,Go 语言这种天生为并发而生的语言,完美的发挥了服务器多核优势,很多可以并发处理的任务都可以使用并发来解决,比如 Go 处理 HTTP 请求时每个请求都会在一个 Goroutine 中执行。

总之,怎样合理的压榨 CPU,让其发挥出应有的价值,是我们一直需要探索学习的方向。

위 내용은 “12306”의 건축물은 얼마나 멋진가요?의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 微信에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제