go語(yǔ)言多進(jìn)程io go語(yǔ)言如何利用多核

Go語(yǔ)言——goroutine并發(fā)模型

參考:

創(chuàng)新互聯(lián)公司長(zhǎng)期為1000+客戶提供的網(wǎng)站建設(shè)服務(wù),團(tuán)隊(duì)從業(yè)經(jīng)驗(yàn)10年,關(guān)注不同地域、不同群體,并針對(duì)不同對(duì)象提供差異化的產(chǎn)品和服務(wù);打造開(kāi)放共贏平臺(tái),與合作伙伴共同營(yíng)造健康的互聯(lián)網(wǎng)生態(tài)環(huán)境。為偃師企業(yè)提供專(zhuān)業(yè)的成都網(wǎng)站建設(shè)、成都網(wǎng)站制作,偃師網(wǎng)站改版等技術(shù)服務(wù)。擁有十載豐富建站經(jīng)驗(yàn)和眾多成功案例,為您定制開(kāi)發(fā)。

Goroutine并發(fā)調(diào)度模型深度解析手?jǐn)]一個(gè)協(xié)程池

Golang 的 goroutine 是如何實(shí)現(xiàn)的?

Golang - 調(diào)度剖析【第二部分】

OS線程初始棧為2MB。Go語(yǔ)言中,每個(gè)goroutine采用動(dòng)態(tài)擴(kuò)容方式,初始2KB,按需增長(zhǎng),最大1G。此外GC會(huì)收縮棧空間。

BTW,增長(zhǎng)擴(kuò)容都是有代價(jià)的,需要copy數(shù)據(jù)到新的stack,所以初始2KB可能有些性能問(wèn)題。

更多關(guān)于stack的內(nèi)容,可以參見(jiàn)大佬的文章。 聊一聊goroutine stack

用戶線程的調(diào)度以及生命周期管理都是用戶層面,Go語(yǔ)言自己實(shí)現(xiàn)的,不借助OS系統(tǒng)調(diào)用,減少系統(tǒng)資源消耗。

Go語(yǔ)言采用兩級(jí)線程模型,即用戶線程與內(nèi)核線程KSE(kernel scheduling entity)是M:N的。最終goroutine還是會(huì)交給OS線程執(zhí)行,但是需要一個(gè)中介,提供上下文。這就是G-M-P模型

Go調(diào)度器有兩個(gè)不同的運(yùn)行隊(duì)列:

go1.10\src\runtime\runtime2.go

Go調(diào)度器根據(jù)事件進(jìn)行上下文切換。

調(diào)度的目的就是防止M堵塞,空閑,系統(tǒng)進(jìn)程切換。

詳見(jiàn) Golang - 調(diào)度剖析【第二部分】

Linux可以通過(guò)epoll實(shí)現(xiàn)網(wǎng)絡(luò)調(diào)用,統(tǒng)稱(chēng)網(wǎng)絡(luò)輪詢器N(Net Poller)。

文件IO操作

上面都是防止M堵塞,任務(wù)竊取是防止M空閑

每個(gè)M都有一個(gè)特殊的G,g0。用于執(zhí)行調(diào)度,gc,棧管理等任務(wù),所以g0的棧稱(chēng)為調(diào)度棧。g0的棧不會(huì)自動(dòng)增長(zhǎng),不會(huì)被gc,來(lái)自os線程的棧。

go1.10\src\runtime\proc.go

G沒(méi)辦法自己運(yùn)行,必須通過(guò)M運(yùn)行

M通過(guò)通過(guò)調(diào)度,執(zhí)行G

從M掛載P的runq中找到G,執(zhí)行G

如何用go語(yǔ)言每分鐘處理100萬(wàn)個(gè)請(qǐng)求

在Malwarebytes 我們經(jīng)歷了顯著的增長(zhǎng),自從我一年前加入了硅谷的公司,一個(gè)主要的職責(zé)成了設(shè)計(jì)架構(gòu)和開(kāi)發(fā)一些系統(tǒng)來(lái)支持一個(gè)快速增長(zhǎng)的信息安全公司和所有需要的設(shè)施來(lái)支持一個(gè)每天百萬(wàn)用戶使用的產(chǎn)品。我在反病毒和反惡意軟件行業(yè)的不同公司工作了12年,從而我知道由于我們每天處理大量的數(shù)據(jù),這些系統(tǒng)是多么復(fù)雜。

有趣的是,在過(guò)去的大約9年間,我參與的所有的web后端的開(kāi)發(fā)通常是通過(guò)Ruby on Rails技術(shù)實(shí)現(xiàn)的。不要錯(cuò)怪我。我喜歡Ruby on Rails,并且我相信它是個(gè)令人驚訝的環(huán)境。但是一段時(shí)間后,你會(huì)開(kāi)始以ruby的方式開(kāi)始思考和設(shè)計(jì)系統(tǒng),你會(huì)忘記,如果你可以利用多線程、并行、快速執(zhí)行和小內(nèi)存開(kāi)銷(xiāo),軟件架構(gòu)本來(lái)應(yīng)該是多么高效和簡(jiǎn)單。很多年期間,我是一個(gè)c/c++、Delphi和c#開(kāi)發(fā)者,我剛開(kāi)始意識(shí)到使用正確的工具可以把復(fù)雜的事情變得簡(jiǎn)單些。

作為首席架構(gòu)師,我不會(huì)很關(guān)心在互聯(lián)網(wǎng)上的語(yǔ)言和框架戰(zhàn)爭(zhēng)。我相信效率、生產(chǎn)力。代碼可維護(hù)性主要依賴(lài)于你如何把解決方案設(shè)計(jì)得很簡(jiǎn)單。

問(wèn)題

當(dāng)工作在我們的匿名遙測(cè)和分析系統(tǒng)中,我們的目標(biāo)是可以處理來(lái)自于百萬(wàn)級(jí)別的終端的大量的POST請(qǐng)求。web處理服務(wù)可以接收包含了很多payload的集合的JSON數(shù)據(jù),這些數(shù)據(jù)需要寫(xiě)入Amazon S3中。接下來(lái),map-reduce系統(tǒng)可以操作這些數(shù)據(jù)。

按照習(xí)慣,我們會(huì)調(diào)研服務(wù)層級(jí)架構(gòu),涉及的軟件如下:

Sidekiq

Resque

DelayedJob

Elasticbeanstalk Worker Tier

RabbitMQ

and so on…

搭建了2個(gè)不同的集群,一個(gè)提供web前端,另外一個(gè)提供后端處理,這樣我們可以橫向擴(kuò)展后端服務(wù)的數(shù)量。

但是,從剛開(kāi)始,在 討論階段我們的團(tuán)隊(duì)就知道我們應(yīng)該使用Go,因?yàn)槲覀兛吹竭@會(huì)潛在性地成為一個(gè)非常龐大( large traffic)的系統(tǒng)。我已經(jīng)使用了Go語(yǔ)言大約2年時(shí)間,我們開(kāi)發(fā)了幾個(gè)系統(tǒng),但是很少會(huì)達(dá)到這樣的負(fù)載(amount of load)。

我們開(kāi)始創(chuàng)建一些結(jié)構(gòu),定義從POST調(diào)用得到的web請(qǐng)求負(fù)載,還有一個(gè)上傳到S3 budket的函數(shù)。

type PayloadCollection struct {

WindowsVersion string `json:"version"`

Token string `json:"token"`

Payloads []Payload `json:"data"`

}

type Payload struct {

// [redacted]

}

func (p *Payload) UploadToS3() error {

// the storageFolder method ensures that there are no name collision in

// case we get same timestamp in the key name

storage_path := fmt.Sprintf("%v/%v", p.storageFolder, time.Now().UnixNano())

bucket := S3Bucket

b := new(bytes.Buffer)

encodeErr := json.NewEncoder(b).Encode(payload)

if encodeErr != nil {

return encodeErr

}

// Everything we post to the S3 bucket should be marked 'private'

var acl = s3.Private

var contentType = "application/octet-stream"

return bucket.PutReader(storage_path, b, int64(b.Len()), contentType, acl, s3.Options{})

}

本地Go routines方法

剛開(kāi)始,我們采用了一個(gè)非常本地化的POST處理實(shí)現(xiàn),僅僅嘗試把發(fā)到簡(jiǎn)單go routine的job并行化:

func payloadHandler(w http.ResponseWriter, r *http.Request) {

if r.Method != "POST" {

w.WriteHeader(http.StatusMethodNotAllowed)

return

}

// Read the body into a string for json decoding

var content = PayloadCollection{}

err := json.NewDecoder(io.LimitReader(r.Body, MaxLength)).Decode(content)

if err != nil {

w.Header().Set("Content-Type", "application/json; charset=UTF-8")

w.WriteHeader(http.StatusBadRequest)

return

}

// Go through each payload and queue items individually to be posted to S3

for _, payload := range content.Payloads {

go payload.UploadToS3() // ----- DON'T DO THIS

}

w.WriteHeader(http.StatusOK)

}

對(duì)于中小負(fù)載,這會(huì)對(duì)大多數(shù)的人適用,但是大規(guī)模下,這個(gè)方案會(huì)很快被證明不是很好用。我們期望的請(qǐng)求數(shù),不在我們剛開(kāi)始計(jì)劃的數(shù)量級(jí),當(dāng)我們把第一個(gè)版本部署到生產(chǎn)環(huán)境上。我們完全低估了流量。

上面的方案在很多地方很不好。沒(méi)有辦法控制我們產(chǎn)生的go routine的數(shù)量。由于我們收到了每分鐘1百萬(wàn)的POST請(qǐng)求,這段代碼很快就崩潰了。

再次嘗試

我們需要找一個(gè)不同的方式。自開(kāi)始我們就討論過(guò), 我們需要保持請(qǐng)求處理程序的生命周期很短,并且進(jìn)程在后臺(tái)產(chǎn)生。當(dāng)然,這是你在Ruby on Rails的世界里必須要做的事情,否則你會(huì)阻塞在所有可用的工作 web處理器上,不管你是使用puma、unicore還是passenger(我們不要討論JRuby這個(gè)話題)。然后我們需要利用常用的處理方案來(lái)做這些,比如Resque、 Sidekiq、 SQS等。這個(gè)列表會(huì)繼續(xù)保留,因?yàn)橛泻芏嗟姆桨缚梢詫?shí)現(xiàn)這些。

所以,第二次迭代,我們創(chuàng)建了一個(gè)緩沖channel,我們可以把job排隊(duì),然后把它們上傳到S3。因?yàn)槲覀兛梢钥刂莆覀冴?duì)列中的item最大值,我們有大量的內(nèi)存來(lái)排列job,我們認(rèn)為只要把job在channel里面緩沖就可以了。

var Queue chan Payload

func init() {

Queue = make(chan Payload, MAX_QUEUE)

}

func payloadHandler(w http.ResponseWriter, r *http.Request) {

...

// Go through each payload and queue items individually to be posted to S3

for _, payload := range content.Payloads {

Queue - payload

}

...

}

接下來(lái),我們?cè)購(gòu)年?duì)列中取job,然后處理它們。我們使用類(lèi)似于下面的代碼:

func StartProcessor() {

for {

select {

case job := -Queue:

job.payload.UploadToS3() // -- STILL NOT GOOD

}

}

}

說(shuō)實(shí)話,我不知道我們?cè)谙胧裁础_@肯定是一個(gè)滿是Red-Bulls的夜晚。這個(gè)方法不會(huì)帶來(lái)什么改善,我們用了一個(gè) 有缺陷的緩沖隊(duì)列并發(fā),僅僅是把問(wèn)題推遲了。我們的同步處理器同時(shí)僅僅會(huì)上傳一個(gè)數(shù)據(jù)到S3,因?yàn)閬?lái)到的請(qǐng)求遠(yuǎn)遠(yuǎn)大于單核處理器上傳到S3的能力,我們的帶緩沖channel很快達(dá)到了它的極限,然后阻塞了請(qǐng)求處理邏輯的queue更多item的能力。

我們僅僅避免了問(wèn)題,同時(shí)開(kāi)始了我們的系統(tǒng)掛掉的倒計(jì)時(shí)。當(dāng)部署了這個(gè)有缺陷的版本后,我們的延時(shí)保持在每分鐘以常量增長(zhǎng)。

最好的解決方案

我們討論過(guò)在使用用Go channel時(shí)利用一種常用的模式,來(lái)創(chuàng)建一個(gè)二級(jí)channel系統(tǒng),一個(gè)來(lái)queue job,另外一個(gè)來(lái)控制使用多少個(gè)worker來(lái)并發(fā)操作JobQueue。

想法是,以一個(gè)恒定速率并行上傳到S3,既不會(huì)導(dǎo)致機(jī)器崩潰也不好產(chǎn)生S3的連接錯(cuò)誤。這樣我們選擇了創(chuàng)建一個(gè)Job/Worker模式。對(duì)于那些熟悉Java、C#等語(yǔ)言的開(kāi)發(fā)者,可以把這種模式想象成利用channel以golang的方式來(lái)實(shí)現(xiàn)了一個(gè)worker線程池,作為一種替代。

var (

MaxWorker = os.Getenv("MAX_WORKERS")

MaxQueue = os.Getenv("MAX_QUEUE")

)

// Job represents the job to be run

type Job struct {

Payload Payload

}

// A buffered channel that we can send work requests on.

var JobQueue chan Job

// Worker represents the worker that executes the job

type Worker struct {

WorkerPool chan chan Job

JobChannel chan Job

quit chan bool

}

func NewWorker(workerPool chan chan Job) Worker {

return Worker{

WorkerPool: workerPool,

JobChannel: make(chan Job),

quit: make(chan bool)}

}

// Start method starts the run loop for the worker, listening for a quit channel in

// case we need to stop it

func (w Worker) Start() {

go func() {

for {

// register the current worker into the worker queue.

w.WorkerPool - w.JobChannel

select {

case job := -w.JobChannel:

// we have received a work request.

if err := job.Payload.UploadToS3(); err != nil {

log.Errorf("Error uploading to S3: %s", err.Error())

}

case -w.quit:

// we have received a signal to stop

return

}

}

}()

}

// Stop signals the worker to stop listening for work requests.

func (w Worker) Stop() {

go func() {

w.quit - true

}()

}

我們已經(jīng)修改了我們的web請(qǐng)求handler,用payload創(chuàng)建一個(gè)Job實(shí)例,然后發(fā)到JobQueue channel,以便于worker來(lái)獲取。

func payloadHandler(w http.ResponseWriter, r *http.Request) {

if r.Method != "POST" {

w.WriteHeader(http.StatusMethodNotAllowed)

return

}

// Read the body into a string for json decoding

var content = PayloadCollection{}

err := json.NewDecoder(io.LimitReader(r.Body, MaxLength)).Decode(content)

if err != nil {

w.Header().Set("Content-Type", "application/json; charset=UTF-8")

w.WriteHeader(http.StatusBadRequest)

return

}

// Go through each payload and queue items individually to be posted to S3

for _, payload := range content.Payloads {

// let's create a job with the payload

work := Job{Payload: payload}

// Push the work onto the queue.

JobQueue - work

}

w.WriteHeader(http.StatusOK)

}

在web server初始化時(shí),我們創(chuàng)建一個(gè)Dispatcher,然后調(diào)用Run()函數(shù)創(chuàng)建一個(gè)worker池子,然后開(kāi)始監(jiān)聽(tīng)JobQueue中的job。

dispatcher := NewDispatcher(MaxWorker)

dispatcher.Run()

下面是dispatcher的實(shí)現(xiàn)代碼:

type Dispatcher struct {

// A pool of workers channels that are registered with the dispatcher

WorkerPool chan chan Job

}

func NewDispatcher(maxWorkers int) *Dispatcher {

pool := make(chan chan Job, maxWorkers)

return Dispatcher{WorkerPool: pool}

}

func (d *Dispatcher) Run() {

// starting n number of workers

for i := 0; i d.maxWorkers; i++ {

worker := NewWorker(d.pool)

worker.Start()

}

go d.dispatch()

}

func (d *Dispatcher) dispatch() {

for {

select {

case job := -JobQueue:

// a job request has been received

go func(job Job) {

// try to obtain a worker job channel that is available.

// this will block until a worker is idle

jobChannel := -d.WorkerPool

// dispatch the job to the worker job channel

jobChannel - job

}(job)

}

}

}

注意到,我們提供了初始化并加入到池子的worker的最大數(shù)量。因?yàn)檫@個(gè)工程我們利用了Amazon Elasticbeanstalk帶有的docker化的Go環(huán)境,所以我們常常會(huì)遵守12-factor方法論來(lái)配置我們的生成環(huán)境中的系統(tǒng),我們從環(huán)境變了讀取這些值。這種方式,我們控制worker的數(shù)量和JobQueue的大小,所以我們可以很快的改變這些值,而不需要重新部署集群。

var (

MaxWorker = os.Getenv("MAX_WORKERS")

MaxQueue = os.Getenv("MAX_QUEUE")

)

直接結(jié)果

我們部署了之后,立馬看到了延時(shí)降到微乎其微的數(shù)值,并未我們處理請(qǐng)求的能力提升很大。

Elastic Load Balancers完全啟動(dòng)后,我們看到ElasticBeanstalk 應(yīng)用服務(wù)于每分鐘1百萬(wàn)請(qǐng)求。通常情況下在上午時(shí)間有幾個(gè)小時(shí),流量峰值超過(guò)每分鐘一百萬(wàn)次。

我們一旦部署了新的代碼,服務(wù)器的數(shù)量從100臺(tái)大幅 下降到大約20臺(tái)。

我們合理配置了我們的集群和自動(dòng)均衡配置之后,我們可以把服務(wù)器的數(shù)量降至4x EC2 c4.Large實(shí)例,并且Elastic Auto-Scaling設(shè)置為如果CPU達(dá)到5分鐘的90%利用率,我們就會(huì)產(chǎn)生新的實(shí)例。

總結(jié)

在我的書(shū)中,簡(jiǎn)單總是獲勝。我們可以使用多隊(duì)列、后臺(tái)worker、復(fù)雜的部署設(shè)計(jì)一個(gè)復(fù)雜的系統(tǒng),但是我們決定利用Elasticbeanstalk 的auto-scaling的能力和Go語(yǔ)言開(kāi)箱即用的特性簡(jiǎn)化并發(fā)。

我們僅僅用了4臺(tái)機(jī)器,這并不是什么新鮮事了??赡芩鼈冞€不如我的MacBook能力強(qiáng)大,但是卻處理了每分鐘1百萬(wàn)的寫(xiě)入到S3的請(qǐng)求。

處理問(wèn)題有正確的工具。當(dāng)你的 Ruby on Rails 系統(tǒng)需要更強(qiáng)大的web handler時(shí),可以考慮下ruby生態(tài)系統(tǒng)之外的技術(shù),或許可以得到更簡(jiǎn)單但更強(qiáng)大的替代方案。

如何實(shí)現(xiàn)支持?jǐn)?shù)億用戶的長(zhǎng)連消息系統(tǒng)

此文是根據(jù)周洋在【高可用架構(gòu)群】中的分享內(nèi)容整理而成,轉(zhuǎn)發(fā)請(qǐng)注明出處。

周洋,360手機(jī)助手技術(shù)經(jīng)理及架構(gòu)師,負(fù)責(zé)360長(zhǎng)連接消息系統(tǒng),360手機(jī)助手架構(gòu)的開(kāi)發(fā)與維護(hù)。

不知道咱們?nèi)好裁磿r(shí)候改為“Python高可用架構(gòu)群”了,所以不得不說(shuō),很榮幸能在接下來(lái)的一個(gè)小時(shí)里在Python群里討論golang....

360消息系統(tǒng)介紹

360消息系統(tǒng)更確切的說(shuō)是長(zhǎng)連接push系統(tǒng),目前服務(wù)于360內(nèi)部多個(gè)產(chǎn)品,開(kāi)發(fā)平臺(tái)數(shù)千款app,也支持部分聊天業(yè)務(wù)場(chǎng)景,單通道多app復(fù)用,支持上行數(shù)據(jù),提供接入方不同粒度的上行數(shù)據(jù)和用戶狀態(tài)回調(diào)服務(wù)。

目前整個(gè)系統(tǒng)按不同業(yè)務(wù)分成9個(gè)功能完整的集群,部署在多個(gè)idc上(每個(gè)集群覆蓋不同的idc),實(shí)時(shí)在線數(shù)億量級(jí)。通常情況下,pc,手機(jī),甚至是智能硬件上的360產(chǎn)品的push消息,基本上是從我們系統(tǒng)發(fā)出的。

關(guān)于push系統(tǒng)對(duì)比與性能指標(biāo)的討論

很多同行比較關(guān)心go語(yǔ)言在實(shí)現(xiàn)push系統(tǒng)上的性能問(wèn)題,單機(jī)性能究竟如何,能否和其他語(yǔ)言實(shí)現(xiàn)的類(lèi)似系統(tǒng)做對(duì)比么?甚至問(wèn)如果是創(chuàng)業(yè),第三方云推送平臺(tái),推薦哪個(gè)?

其實(shí)各大廠都有類(lèi)似的push系統(tǒng),市場(chǎng)上也有類(lèi)似功能的云服務(wù)。包括我們公司早期也有erlang,nodejs實(shí)現(xiàn)的類(lèi)似系統(tǒng),也一度被公司要求做類(lèi)似的對(duì)比測(cè)試。我感覺(jué)在討論對(duì)比數(shù)據(jù)的時(shí)候,很難保證大家環(huán)境和需求的統(tǒng)一,我只能說(shuō)下我這里的體會(huì),數(shù)據(jù)是有的,但這個(gè)數(shù)據(jù)前面估計(jì)會(huì)有很多定語(yǔ)~

第一個(gè)重要指標(biāo):?jiǎn)螜C(jī)的連接數(shù)指標(biāo)

做過(guò)長(zhǎng)連接的同行,應(yīng)該有體會(huì),如果在穩(wěn)定連接情況下,連接數(shù)這個(gè)指標(biāo),在沒(méi)有網(wǎng)絡(luò)吞吐情況下對(duì)比,其實(shí)意義往往不大,維持連接消耗cpu資源很小,每條連接tcp協(xié)議棧會(huì)占約4k的內(nèi)存開(kāi)銷(xiāo),系統(tǒng)參數(shù)調(diào)整后,我們單機(jī)測(cè)試數(shù)據(jù),最高也是可以達(dá)到單實(shí)例300w長(zhǎng)連接。但做更高的測(cè)試,我個(gè)人感覺(jué)意義不大。

因?yàn)閷?shí)際網(wǎng)絡(luò)環(huán)境下,單實(shí)例300w長(zhǎng)連接,從理論上算壓力就很大:實(shí)際弱網(wǎng)絡(luò)環(huán)境下,移動(dòng)客戶端的斷線率很高,假設(shè)每秒有1000分之一的用戶斷線重連。300w長(zhǎng)連接,每秒新建連接達(dá)到3w,這同時(shí)連入的3w用戶,要進(jìn)行注冊(cè),加載離線存儲(chǔ)等對(duì)內(nèi)rpc調(diào)用,另外300w長(zhǎng)連接的用戶心跳需要維持,假設(shè)心跳300s一次,心跳包每秒需要1w tps。單播和多播數(shù)據(jù)的轉(zhuǎn)發(fā),廣播數(shù)據(jù)的轉(zhuǎn)發(fā),本身也要響應(yīng)內(nèi)部的rpc調(diào)用,300w長(zhǎng)連接情況下,gc帶來(lái)的壓力,內(nèi)部接口的響應(yīng)延遲能否穩(wěn)定保障。這些集中在一個(gè)實(shí)例中,可用性是一個(gè)挑戰(zhàn)。所以線上單實(shí)例不會(huì)hold很高的長(zhǎng)連接,實(shí)際情況也要根據(jù)接入客戶端網(wǎng)絡(luò)狀況來(lái)決定。

第二個(gè)重要指標(biāo):消息系統(tǒng)的內(nèi)存使用量指標(biāo)

這一點(diǎn)上,使用go語(yǔ)言情況下,由于協(xié)程的原因,會(huì)有一部分額外開(kāi)銷(xiāo)。但是要做兩個(gè)推送系統(tǒng)的對(duì)比,也有些需要確定問(wèn)題。比如系統(tǒng)從設(shè)計(jì)上是否需要全雙工(即讀寫(xiě)是否需要同時(shí)進(jìn)行)如果半雙工,理論上對(duì)一個(gè)用戶的連接只需要使用一個(gè)協(xié)程即可(這種情況下,對(duì)用戶的斷線檢測(cè)可能會(huì)有延時(shí)),如果是全雙工,那讀/寫(xiě)各一個(gè)協(xié)程。兩種場(chǎng)景內(nèi)存開(kāi)銷(xiāo)是有區(qū)別的。

另外測(cè)試數(shù)據(jù)的大小往往決定我們對(duì)連接上設(shè)置的讀寫(xiě)buffer是多大,是全局復(fù)用的,還是每個(gè)連接上獨(dú)享的,還是動(dòng)態(tài)申請(qǐng)的。另外是否全雙工也決定buffer怎么開(kāi)。不同的策略,可能在不同情況的測(cè)試中表現(xiàn)不一樣。

第三個(gè)重要指標(biāo):每秒消息下發(fā)量

這一點(diǎn)上,也要看我們對(duì)消息到達(dá)的QoS級(jí)別(回復(fù)ack策略區(qū)別),另外看架構(gòu)策略,每種策略有其更適用的場(chǎng)景,是純粹推?還是推拉結(jié)合?甚至是否開(kāi)啟了消息日志?日志庫(kù)的實(shí)現(xiàn)機(jī)制、以及緩沖開(kāi)多大?flush策略……這些都影響整個(gè)系統(tǒng)的吞吐量。

另外為了HA,增加了內(nèi)部通信成本,為了避免一些小概率事件,提供閃斷補(bǔ)償策略,這些都要考慮進(jìn)去。如果所有的都去掉,那就是比較基礎(chǔ)庫(kù)的性能了。

所以我只能給出大概數(shù)據(jù),24核,64G的服務(wù)器上,在QoS為message at least,純粹推,消息體256B~1kB情況下,單個(gè)實(shí)例100w實(shí)際用戶(200w+)協(xié)程,峰值可以達(dá)到2~5w的QPS...內(nèi)存可以穩(wěn)定在25G左右,gc時(shí)間在200~800ms左右(還有優(yōu)化空間)。

我們正常線上單實(shí)例用戶控制在80w以內(nèi),單機(jī)最多兩個(gè)實(shí)例。事實(shí)上,整個(gè)系統(tǒng)在推送的需求上,對(duì)高峰的輸出不是提速,往往是進(jìn)行限速,以防push系統(tǒng)瞬時(shí)的高吞吐量,轉(zhuǎn)化成對(duì)接入方業(yè)務(wù)服務(wù)器的ddos攻擊所以對(duì)于性能上,我感覺(jué)大家可以放心使用,至少在我們這個(gè)量級(jí)上,經(jīng)受過(guò)考驗(yàn),go1.5到來(lái)后,確實(shí)有之前投資又增值了的感覺(jué)。

消息系統(tǒng)架構(gòu)介紹

下面是對(duì)消息系統(tǒng)的大概介紹,之前一些同學(xué)可能在gopher china上可以看到分享,這里簡(jiǎn)單講解下架構(gòu)和各個(gè)組件功能,額外補(bǔ)充一些當(dāng)時(shí)遺漏的信息:

架構(gòu)圖如下,所有的service都 written by golang.

幾個(gè)大概重要組件介紹如下:

dispatcher service根據(jù)客戶端請(qǐng)求信息,將應(yīng)網(wǎng)絡(luò)和區(qū)域的長(zhǎng)連接服務(wù)器的,一組IP傳送給客戶端??蛻舳烁鶕?jù)返回的IP,建立長(zhǎng)連接,連接Room service.

room Service,長(zhǎng)連接網(wǎng)關(guān),hold用戶連接,并將用戶注冊(cè)進(jìn)register service,本身也做一些接入安全策略、白名單、IP限制等。

register service是我們?nèi)謘ession存儲(chǔ)組件,存儲(chǔ)和索引用戶的相關(guān)信息,以供獲取和查詢。

coordinator service用來(lái)轉(zhuǎn)發(fā)用戶的上行數(shù)據(jù),包括接入方訂閱的用戶狀態(tài)信息的回調(diào),另外做需要協(xié)調(diào)各個(gè)組件的異步操作,比如kick用戶操作,需要從register拿出其他用戶做異步操作.

saver service是存儲(chǔ)訪問(wèn)層,承擔(dān)了對(duì)redis和mysql的操作,另外也提供部分業(yè)務(wù)邏輯相關(guān)的內(nèi)存緩存,比如廣播信息的加載可以在saver中進(jìn)行緩存。另外一些策略,比如客戶端sdk由于被惡意或者意外修改,每次加載了消息,不回復(fù)ack,那服務(wù)端就不會(huì)刪除消息,消息就會(huì)被反復(fù)加載,形成死循環(huán),可以通過(guò)在saver中做策略和判斷。(客戶端總是不可信的)。

center service提供給接入方的內(nèi)部api服務(wù)器,比如單播或者廣播接口,狀態(tài)查詢接口等一系列api,包括運(yùn)維和管理的api。

舉兩個(gè)常見(jiàn)例子,了解工作機(jī)制:比如發(fā)一條單播給一個(gè)用戶,center先請(qǐng)求Register獲取這個(gè)用戶之前注冊(cè)的連接通道標(biāo)識(shí)、room實(shí)例地址,通過(guò)room service下發(fā)給長(zhǎng)連接 Center Service比較重的工作如全網(wǎng)廣播,需要把所有的任務(wù)分解成一系列的子任務(wù),分發(fā)給所有center,然后在所有的子任務(wù)里,分別獲取在線和離線的所有用戶,再批量推到Room Service。通常整個(gè)集群在那一瞬間壓力很大。

deployd/agent service用于部署管理各個(gè)進(jìn)程,收集各組件的狀態(tài)和信息,zookeeper和keeper用于整個(gè)系統(tǒng)的配置文件管理和簡(jiǎn)單調(diào)度

關(guān)于推送的服務(wù)端架構(gòu)

常見(jiàn)的推送模型有長(zhǎng)輪訓(xùn)拉取,服務(wù)端直接推送(360消息系統(tǒng)目前主要是這種),推拉結(jié)合(推送只發(fā)通知,推送后根據(jù)通知去拉取消息).

拉取的方式不說(shuō)了,現(xiàn)在并不常用了,早期很多是nginx+lua+redis,長(zhǎng)輪訓(xùn),主要問(wèn)題是開(kāi)銷(xiāo)比較大,時(shí)效性也不好,能做的優(yōu)化策略不多。

直接推送的系統(tǒng),目前就是360消息系統(tǒng)這種,消息類(lèi)型是消耗型的,并且對(duì)于同一個(gè)用戶并不允許重復(fù)消耗,如果需要多終端重復(fù)消耗,需要抽象成不同用戶。

推的好處是實(shí)時(shí)性好,開(kāi)銷(xiāo)小,直接將消息下發(fā)給客戶端,不需要客戶端走從接入層到存儲(chǔ)層主動(dòng)拉取.

但純推送模型,有個(gè)很大問(wèn)題,由于系統(tǒng)是異步的,他的時(shí)序性無(wú)法精確保證。這對(duì)于push需求來(lái)說(shuō)是夠用的,但如果復(fù)用推送系統(tǒng)做im類(lèi)型通信,可能并不合適。

對(duì)于嚴(yán)格要求時(shí)序性,消息可以重復(fù)消耗的系統(tǒng),目前也都是走推拉結(jié)合的模型,就是只使用我們的推送系統(tǒng)發(fā)通知,并附帶id等給客戶端做拉取的判斷策略,客戶端根據(jù)推送的key,主動(dòng)從業(yè)務(wù)服務(wù)器拉取消息。并且當(dāng)主從同步延遲的時(shí)候,跟進(jìn)推送的key做延遲拉取策略。同時(shí)也可以通過(guò)消息本身的QoS,做純粹的推送策略,比如一些“正在打字的”低優(yōu)先級(jí)消息,不需要主動(dòng)拉取了,通過(guò)推送直接消耗掉。

哪些因素決定推送系統(tǒng)的效果?

首先是sdk的完善程度,sdk策略和細(xì)節(jié)完善度,往往決定了弱網(wǎng)絡(luò)環(huán)境下最終推送質(zhì)量.

SDK選路策略,最基本的一些策略如下:有些開(kāi)源服務(wù)可能會(huì)針對(duì)用戶hash一個(gè)該接入?yún)^(qū)域的固定ip,實(shí)際上在國(guó)內(nèi)環(huán)境下不可行,最好分配器(dispatcher)是返回散列的一組,而且端口也要參開(kāi),必要時(shí)候,客戶端告知是retry多組都連不上,返回不同idc的服務(wù)器。因?yàn)槲覀儠?huì)經(jīng)常檢測(cè)到一些case,同一地區(qū)的不同用戶,可能對(duì)同一idc內(nèi)的不同ip連通性都不一樣,也出現(xiàn)過(guò)同一ip不同端口連通性不同,所以用戶的選路策略一定要靈活,策略要足夠完善.另外在選路過(guò)程中,客戶端要對(duì)不同網(wǎng)絡(luò)情況下的長(zhǎng)連接ip做緩存,當(dāng)網(wǎng)絡(luò)環(huán)境切換時(shí)候(wifi、2G、3G),重新請(qǐng)求分配器,緩存不同網(wǎng)絡(luò)環(huán)境的長(zhǎng)連接ip。

客戶端對(duì)于數(shù)據(jù)心跳和讀寫(xiě)超時(shí)設(shè)置,完善斷線檢測(cè)重連機(jī)制

針對(duì)不同網(wǎng)絡(luò)環(huán)境,或者客戶端本身消息的活躍程度,心跳要自適應(yīng)的進(jìn)行調(diào)整并與服務(wù)端協(xié)商,來(lái)保證鏈路的連通性。并且在弱網(wǎng)絡(luò)環(huán)境下,除了網(wǎng)絡(luò)切換(wifi切3G)或者讀寫(xiě)出錯(cuò)情況,什么時(shí)候重新建立鏈路也是一個(gè)問(wèn)題??蛻舳税l(fā)出的ping包,不同網(wǎng)絡(luò)下,多久沒(méi)有得到響應(yīng),認(rèn)為網(wǎng)絡(luò)出現(xiàn)問(wèn)題,重新建立鏈路需要有個(gè)權(quán)衡。另外對(duì)于不同網(wǎng)絡(luò)環(huán)境下,讀取不同的消息長(zhǎng)度,也要有不同的容忍時(shí)間,不能一刀切。好的心跳和讀寫(xiě)超時(shí)設(shè)置,可以讓客戶端最快的檢測(cè)到網(wǎng)絡(luò)問(wèn)題,重新建立鏈路,同時(shí)在網(wǎng)絡(luò)抖動(dòng)情況下也能完成大數(shù)據(jù)傳輸。

結(jié)合服務(wù)端做策略

另外系統(tǒng)可能結(jié)合服務(wù)端做一些特殊的策略,比如我們?cè)谶x路時(shí)候,我們會(huì)將同一個(gè)用戶盡量映射到同一個(gè)room service實(shí)例上。斷線時(shí),客戶端盡量對(duì)上次連接成功的地址進(jìn)行重試。主要是方便服務(wù)端做閃斷情況下策略,會(huì)暫存用戶閃斷時(shí)實(shí)例上的信息,重新連入的 時(shí)候,做單實(shí)例內(nèi)的遷移,減少延時(shí)與加載開(kāi)銷(xiāo).

客戶端?;畈呗?/p>

很多創(chuàng)業(yè)公司愿意重新搭建一套push系統(tǒng),確實(shí)不難實(shí)現(xiàn),其實(shí)在協(xié)議完備情況下(最簡(jiǎn)單就是客戶端不回ack不清數(shù)據(jù)),服務(wù)端會(huì)保證消息是不丟的。但問(wèn)題是為什么在消息有效期內(nèi),到達(dá)率上不去?往往因?yàn)樽约篴pp的push service存活能力不高。選用云平臺(tái)或者大廠的,往往sdk會(huì)做一些?;畈呗裕热绾推渌鸻pp共生,互相喚醒,這也是云平臺(tái)的push service更有保障原因。我相信很多云平臺(tái)旗下的sdk,多個(gè)使用同樣sdk的app,為了實(shí)現(xiàn)服務(wù)存活,是可以互相喚醒和保證活躍的。另外現(xiàn)在push sdk本身是單連接,多app復(fù)用的,這為sdk實(shí)現(xiàn),增加了新的挑戰(zhàn)。

綜上,對(duì)我來(lái)說(shuō),選擇推送平臺(tái),優(yōu)先會(huì)考慮客戶端sdk的完善程度。對(duì)于服務(wù)端,選擇條件稍微簡(jiǎn)單,要求部署接入點(diǎn)(IDC)越要多,配合精細(xì)的選路策略,效果越有保證,至于想知道哪些云服務(wù)有多少點(diǎn),這個(gè)群里來(lái)自各地的小伙伴們,可以合伙測(cè)測(cè)。

go語(yǔ)言開(kāi)發(fā)問(wèn)題與解決方案

下面講下,go開(kāi)發(fā)過(guò)程中遇到挑戰(zhàn)和優(yōu)化策略,給大家看下當(dāng)年的一張圖,在第一版優(yōu)化方案上線前一天截圖~

可以看到,內(nèi)存最高占用69G,GC時(shí)間單實(shí)例最高時(shí)候高達(dá)3~6s.這種情況下,試想一次悲劇的請(qǐng)求,經(jīng)過(guò)了幾個(gè)正在執(zhí)行g(shù)c的組件,后果必然是超時(shí)... gc照成的接入方重試,又加重了系統(tǒng)的負(fù)擔(dān)。遇到這種情況當(dāng)時(shí)整個(gè)系統(tǒng)最差情況每隔2,3天就需要重啟一次~

當(dāng)時(shí)出現(xiàn)問(wèn)題,現(xiàn)在總結(jié)起來(lái),大概以下幾點(diǎn)

1.散落在協(xié)程里的I/O,Buffer和對(duì)象不復(fù)用。

當(dāng)時(shí)(12年)由于對(duì)go的gc效率理解有限,比較奔放,程序里大量short live的協(xié)程,對(duì)內(nèi)通信的很多io操作,由于不想阻塞主循環(huán)邏輯或者需要及時(shí)響應(yīng)的邏輯,通過(guò)單獨(dú)go協(xié)程來(lái)實(shí)現(xiàn)異步。這回會(huì)gc帶來(lái)很多負(fù)擔(dān)。

針對(duì)這個(gè)問(wèn)題,應(yīng)盡量控制協(xié)程創(chuàng)建,對(duì)于長(zhǎng)連接這種應(yīng)用,本身已經(jīng)有幾百萬(wàn)并發(fā)協(xié)程情況下,很多情況沒(méi)必要在各個(gè)并發(fā)協(xié)程內(nèi)部做異步io,因?yàn)槌绦虻牟⑿卸仁怯邢?,理論上做協(xié)程內(nèi)做阻塞操作是沒(méi)問(wèn)題。

如果有些需要異步執(zhí)行,比如如果不異步執(zhí)行,影響對(duì)用戶心跳或者等待response無(wú)法響應(yīng),最好通過(guò)一個(gè)任務(wù)池,和一組常駐協(xié)程,來(lái)消耗,處理結(jié)果,通過(guò)channel再傳回調(diào)用方。使用任務(wù)池還有額外的好處,可以對(duì)請(qǐng)求進(jìn)行打包處理,提高吞吐量,并且可以加入控量策略.

2.網(wǎng)絡(luò)環(huán)境不好引起激增

go協(xié)程相比較以往高并發(fā)程序,如果做不好流控,會(huì)引起協(xié)程數(shù)量激增。早期的時(shí)候也會(huì)發(fā)現(xiàn),時(shí)不時(shí)有部分主機(jī)內(nèi)存會(huì)遠(yuǎn)遠(yuǎn)大于其他服務(wù)器,但發(fā)現(xiàn)時(shí)候,所有主要profiling參數(shù)都正常了。

后來(lái)發(fā)現(xiàn),通信較多系統(tǒng)中,網(wǎng)絡(luò)抖動(dòng)阻塞是不可免的(即使是內(nèi)網(wǎng)),對(duì)外不停accept接受新請(qǐng)求,但執(zhí)行過(guò)程中,由于對(duì)內(nèi)通信阻塞,大量協(xié)程被 創(chuàng)建,業(yè)務(wù)協(xié)程等待通信結(jié)果沒(méi)有釋放,往往瞬時(shí)會(huì)迎來(lái)協(xié)程暴漲。但這些內(nèi)存在系統(tǒng)穩(wěn)定后,virt和res都并沒(méi)能徹底釋放,下降后,維持高位。

處理這種情況,需要增加一些流控策略,流控策略可以選擇在rpc庫(kù)來(lái)做,或者上面說(shuō)的任務(wù)池來(lái)做,其實(shí)我感覺(jué)放在任務(wù)池里做更合理些,畢竟rpc通信庫(kù)可以做讀寫(xiě)數(shù)據(jù)的限流,但它并不清楚具體的限流策略,到底是重試還是日志還是緩存到指定隊(duì)列。任務(wù)池本身就是業(yè)務(wù)邏輯相關(guān)的,它清楚針對(duì)不同的接口需要的流控限制策略。

3.低效和開(kāi)銷(xiāo)大的rpc框架

早期rpc通信框架比較簡(jiǎn)單,對(duì)內(nèi)通信時(shí)候使用的也是短連接。這本來(lái)短連接開(kāi)銷(xiāo)和性能瓶頸超出我們預(yù)期,短連接io效率是低一些,但端口資源夠,本身吞吐可以滿足需要,用是沒(méi)問(wèn)題的,很多分層的系統(tǒng),也有http短連接對(duì)內(nèi)進(jìn)行請(qǐng)求的

但早期go版本,這樣寫(xiě)程序,在一定量級(jí)情況,是支撐不住的。短連接大量臨時(shí)對(duì)象和臨時(shí)buffer創(chuàng)建,在本已經(jīng)百萬(wàn)協(xié)程的程序中,是無(wú)法承受的。所以后續(xù)我們對(duì)我們的rpc框架作了兩次調(diào)整。

第二版的rpc框架,使用了連接池,通過(guò)長(zhǎng)連接對(duì)內(nèi)進(jìn)行通信(復(fù)用的資源包括client和server的:編解碼Buffer、Request/response),大大改善了性能。

但這種在一次request和response還是占用連接的,如果網(wǎng)絡(luò)狀況ok情況下,這不是問(wèn)題,足夠滿足需要了,但試想一個(gè)room實(shí)例要與后面的數(shù)百個(gè)的register,coordinator,saver,center,keeper實(shí)例進(jìn)行通信,需要建立大量的常駐連接,每個(gè)目標(biāo)機(jī)幾十個(gè)連接,也有數(shù)千個(gè)連接被占用。

非持續(xù)抖動(dòng)時(shí)候(持續(xù)逗開(kāi)多少無(wú)解),或者有延遲較高的請(qǐng)求時(shí)候,如果針對(duì)目標(biāo)ip連接開(kāi)少了,會(huì)有瞬時(shí)大量請(qǐng)求阻塞,連接無(wú)法得到充分利用。第三版增加了Pipeline操作,Pipeline會(huì)帶來(lái)一些額外的開(kāi)銷(xiāo),利用tcp的全雙特性,以盡量少的連接完成對(duì)各個(gè)服務(wù)集群的rpc調(diào)用。

4.Gc時(shí)間過(guò)長(zhǎng)

Go的Gc仍舊在持續(xù)改善中,大量對(duì)象和buffer創(chuàng)建,仍舊會(huì)給gc帶來(lái)很大負(fù)擔(dān),尤其一個(gè)占用了25G左右的程序。之前go team的大咖郵件也告知我們,未來(lái)會(huì)讓使用協(xié)程的成本更低,理論上不需要在應(yīng)用層做更多的策略來(lái)緩解gc.

改善方式,一種是多實(shí)例的拆分,如果公司沒(méi)有端口限制,可以很快部署大量實(shí)例,減少gc時(shí)長(zhǎng),最直接方法。不過(guò)對(duì)于360來(lái)說(shuō),外網(wǎng)通常只能使用80和433。因此常規(guī)上只能開(kāi)啟兩個(gè)實(shí)例。當(dāng)然很多人給我建議能否使用SO_REUSEPORT,不過(guò)我們內(nèi)核版本確實(shí)比較低,并沒(méi)有實(shí)踐過(guò)。

另外能否模仿nginx,fork多個(gè)進(jìn)程監(jiān)控同樣端口,至少我們目前沒(méi)有這樣做,主要對(duì)于我們目前進(jìn)程管理上,還是獨(dú)立的運(yùn)行的,對(duì)外監(jiān)聽(tīng)不同端口程序,還有配套的內(nèi)部通信和管理端口,實(shí)例管理和升級(jí)上要做調(diào)整。

解決gc的另兩個(gè)手段,是內(nèi)存池和對(duì)象池,不過(guò)最好做仔細(xì)評(píng)估和測(cè)試,內(nèi)存池、對(duì)象池使用,也需要對(duì)于代碼可讀性與整體效率進(jìn)行權(quán)衡。

這種程序一定情況下會(huì)降低并行度,因?yàn)橛贸貎?nèi)資源一定要加互斥鎖或者原子操作做CAS,通常原子操作實(shí)測(cè)要更快一些。CAS可以理解為可操作的更細(xì)行為粒度的鎖(可以做更多CAS策略,放棄運(yùn)行,防止忙等)。這種方式帶來(lái)的問(wèn)題是,程序的可讀性會(huì)越來(lái)越像C語(yǔ)言,每次要malloc,各地方用完后要free,對(duì)于對(duì)象池free之前要reset,我曾經(jīng)在應(yīng)用層嘗試做了一個(gè)分層次結(jié)構(gòu)的“無(wú)鎖隊(duì)列”

上圖左邊的數(shù)組實(shí)際上是一個(gè)列表,這個(gè)列表按大小將內(nèi)存分塊,然后使用atomic操作進(jìn)行CAS。但實(shí)際要看測(cè)試數(shù)據(jù)了,池技術(shù)可以明顯減少臨時(shí)對(duì)象和內(nèi)存的申請(qǐng)和釋放,gc時(shí)間會(huì)減少,但加鎖帶來(lái)的并行度的降低,是否能給一段時(shí)間內(nèi)的整體吞吐量帶來(lái)提升,要做測(cè)試和權(quán)衡…

在我們消息系統(tǒng),實(shí)際上后續(xù)去除了部分這種黑科技,試想在百萬(wàn)個(gè)協(xié)程里面做自旋操作申請(qǐng)復(fù)用的buffer和對(duì)象,開(kāi)銷(xiāo)會(huì)很大,尤其在協(xié)程對(duì)線程多對(duì)多模型情況下,更依賴(lài)于golang本身調(diào)度策略,除非我對(duì)池增加更多的策略處理,減少忙等,感覺(jué)是在把runtime做的事情,在應(yīng)用層非常不優(yōu)雅的實(shí)現(xiàn)。普遍使用開(kāi)銷(xiāo)理論就大于收益。

但對(duì)于rpc庫(kù)或者codec庫(kù),任務(wù)池內(nèi)部,這些開(kāi)定量協(xié)程,集中處理數(shù)據(jù)的區(qū)域,可以嘗試改造~

對(duì)于有些固定對(duì)象復(fù)用,比如固定的心跳包什么的,可以考慮使用全局一些對(duì)象,進(jìn)行復(fù)用,針對(duì)應(yīng)用層數(shù)據(jù),具體設(shè)計(jì)對(duì)象池,在部分環(huán)節(jié)去復(fù)用,可能比這種無(wú)差別的設(shè)計(jì)一個(gè)通用池更能進(jìn)行效果評(píng)估.

消息系統(tǒng)的運(yùn)維及測(cè)試

下面介紹消息系統(tǒng)的架構(gòu)迭代和一些迭代經(jīng)驗(yàn),由于之前在其他地方有過(guò)分享,后面的會(huì)給出相關(guān)鏈接,下面實(shí)際做個(gè)簡(jiǎn)單介紹,感興趣可以去鏈接里面看

架構(gòu)迭代~根據(jù)業(yè)務(wù)和集群的拆分,能解決部分灰度部署上線測(cè)試,減少點(diǎn)對(duì)點(diǎn)通信和廣播通信不同產(chǎn)品的相互影響,針對(duì)特定的功能做獨(dú)立的優(yōu)化.

消息系統(tǒng)架構(gòu)和集群拆分,最基本的是拆分多實(shí)例,其次是按照業(yè)務(wù)類(lèi)型對(duì)資源占用情況分類(lèi),按用戶接入網(wǎng)絡(luò)和對(duì)idc布點(diǎn)要求分類(lèi)(目前沒(méi)有條件,所有的產(chǎn)品都部署到全部idc)

系統(tǒng)的測(cè)試go語(yǔ)言在并發(fā)測(cè)試上有獨(dú)特優(yōu)勢(shì)。

對(duì)于壓力測(cè)試,目前主要針對(duì)指定的服務(wù)器,選定線上空閑的服務(wù)器做長(zhǎng)連接壓測(cè)。然后結(jié)合可視化,分析壓測(cè)過(guò)程中的系統(tǒng)狀態(tài)。但壓測(cè)早期用的比較多,但實(shí)現(xiàn)的統(tǒng)計(jì)報(bào)表功能和我理想有一定差距。我覺(jué)得最近出的golang開(kāi)源產(chǎn)品都符合這種場(chǎng)景,go寫(xiě)網(wǎng)絡(luò)并發(fā)程序給大家?guī)?lái)的便利,讓大家把以往為了降低復(fù)雜度,拆解或者分層協(xié)作的組件,又組合在了一起。

QA

Q1:協(xié)議棧大小,超時(shí)時(shí)間定制原則?

移動(dòng)網(wǎng)絡(luò)下超時(shí)時(shí)間按產(chǎn)品需求通常2g,3G情況下是5分鐘,wifi情況下5~8分鐘。但對(duì)于個(gè)別場(chǎng)景,要求響應(yīng)非常迅速的場(chǎng)景,如果連接idle超過(guò)1分鐘,都會(huì)有ping,pong,來(lái)校驗(yàn)是否斷線檢測(cè),盡快做到重新連接。

Q2:消息是否持久化?

消息持久化,通常是先存后發(fā),存儲(chǔ)用的redis,但落地用的mysql。mysql只做故障恢復(fù)使用。

Q3:消息風(fēng)暴怎么解決的?

如果是發(fā)送情況下,普通產(chǎn)品是不需要限速的,對(duì)于較大產(chǎn)品是有發(fā)送隊(duì)列做控速度,按人數(shù),按秒進(jìn)行控速度發(fā)放,發(fā)送成功再發(fā)送下一條。

Q4:golang的工具鏈支持怎么樣?我自己寫(xiě)過(guò)一些小程序千把行之內(nèi),確實(shí)很不錯(cuò),但不知道代碼量上去之后,配套的debug工具和profiling工具如何,我看上邊有分享說(shuō)golang自帶的profiling工具還不錯(cuò),那debug呢怎么樣呢,官方一直沒(méi)有出debug工具,gdb支持也不完善,不知你們用的什么?

是這樣的,我們正常就是println,我感覺(jué)基本上可以定位我所有問(wèn)題,但也不排除由于并行性通過(guò)println無(wú)法復(fù)現(xiàn)的問(wèn)題,目前來(lái)看只能靠經(jīng)驗(yàn)了。只要常見(jiàn)并發(fā)嘗試,經(jīng)過(guò)分析是可以找到的。go很快會(huì)推出調(diào)試工具的~

Q5:協(xié)議棧是基于tcp嗎?

是否有協(xié)議拓展功能?協(xié)議棧是tcp,整個(gè)系統(tǒng)tcp長(zhǎng)連接,沒(méi)有考慮擴(kuò)展其功能~如果有好的經(jīng)驗(yàn),可以分享~

Q6:問(wèn)個(gè)問(wèn)題,這個(gè)系統(tǒng)是接收上行數(shù)據(jù)的吧,系統(tǒng)接收上行數(shù)據(jù)后是轉(zhuǎn)發(fā)給相應(yīng)系統(tǒng)做處理么,是怎么轉(zhuǎn)發(fā)呢,如果需要給客戶端返回調(diào)用結(jié)果又是怎么處理呢?

系統(tǒng)上行數(shù)據(jù)是根據(jù)協(xié)議頭進(jìn)行轉(zhuǎn)發(fā),協(xié)議頭里面標(biāo)記了產(chǎn)品和轉(zhuǎn)發(fā)類(lèi)型,在coordinator里面跟進(jìn)產(chǎn)品和轉(zhuǎn)發(fā)類(lèi)型,回調(diào)用戶,如果用戶需要阻塞等待回復(fù)才能后續(xù)操作,那通過(guò)再發(fā)送消息,路由回用戶。因?yàn)檎麄€(gè)系統(tǒng)是全異步的。

Q7:問(wèn)個(gè)pushsdk的問(wèn)題。pushsdk的單連接,多app復(fù)用方式,這樣的情況下以下幾個(gè)問(wèn)題是如何解決的:1)系統(tǒng)流量統(tǒng)計(jì)會(huì)把所有流量都算到啟動(dòng)連接的應(yīng)用吧?而啟動(dòng)應(yīng)用的連接是不固定的吧?2)同一個(gè)pushsdk在不同的應(yīng)用中的版本號(hào)可能不一樣,這樣暴露出來(lái)的接口可能有版本問(wèn)題,如果用單連接模式怎么解決?

流量只能算在啟動(dòng)的app上了,但一般這種安裝率很高的app承擔(dān)可能性大,常用app本身被檢測(cè)和殺死可能性較少,另外消息下發(fā)量是有嚴(yán)格控制 的。整體上用戶還是省電和省流量的。我們pushsdk盡量向上兼容,出于這個(gè)目的,push sdk本身做的工作非常有限,抽象出來(lái)一些常見(jiàn)的功能,純推的系統(tǒng),客戶端策略目前做的很少,也有這個(gè)原因。

Q8:生產(chǎn)系統(tǒng)的profiling是一直打開(kāi)的么?

不是一直打開(kāi),每個(gè)集群都有采樣,但需要開(kāi)啟哪個(gè)可以后臺(tái)控制。這個(gè)profling是通過(guò)接口調(diào)用。

Q9:面前系統(tǒng)中的消息消費(fèi)者可不可以分組?類(lèi)似于Kafka。

客戶端可以訂閱不同產(chǎn)品的消息,接受不同的分組。接入的時(shí)候進(jìn)行bind或者unbind操作

Q10:為什么放棄erlang,而選擇go,有什么特別原因嗎?我們現(xiàn)在用的erlang?

erlang沒(méi)有問(wèn)題,原因是我們上線后,其他團(tuán)隊(duì)才做出來(lái),經(jīng)過(guò)qa一個(gè)部門(mén)對(duì)比測(cè)試,在沒(méi)有顯著性能提升下,選擇繼續(xù)使用go版本的push,作為公司基礎(chǔ)服務(wù)。

Q11:流控問(wèn)題有排查過(guò)網(wǎng)卡配置導(dǎo)致的idle問(wèn)題嗎?

流控是業(yè)務(wù)級(jí)別的流控,我們上線前對(duì)于內(nèi)網(wǎng)的極限通信量做了測(cè)試,后續(xù)將請(qǐng)求在rpc庫(kù)內(nèi),控制在小于內(nèi)部通信開(kāi)銷(xiāo)的上限以下.在到達(dá)上限前作流控。

Q12:服務(wù)的協(xié)調(diào)調(diào)度為什么選擇zk有考慮過(guò)raft實(shí)現(xiàn)嗎?golang的raft實(shí)現(xiàn)很多啊,比如Consul和ectd之類(lèi)的。

3年前,還沒(méi)有后兩者或者后兩者沒(méi)聽(tīng)過(guò)應(yīng)該。zk當(dāng)時(shí)公司內(nèi)部成熟方案,不過(guò)目前來(lái)看,我們不準(zhǔn)備用zk作結(jié)合系統(tǒng)的定制開(kāi)發(fā),準(zhǔn)備用自己寫(xiě)的keeper代替zk,完成配置文件自動(dòng)轉(zhuǎn)數(shù)據(jù)結(jié)構(gòu),數(shù)據(jù)結(jié)構(gòu)自動(dòng)同步指定進(jìn)程,同時(shí)里面可以完成很多自定義的發(fā)現(xiàn)和控制策略,客戶端包含keeper的sdk就可以實(shí)現(xiàn)以上的所有監(jiān)控?cái)?shù)據(jù),profling數(shù)據(jù)收集,配置文件更新,啟動(dòng)關(guān)閉等回調(diào)。完全抽象成語(yǔ)keeper通信sdk,keeper之間考慮用raft。

Q13:負(fù)載策略是否同時(shí)在服務(wù)側(cè)與CLIENT側(cè)同時(shí)做的 (DISPATCHER 會(huì)返回一組IP)?另外,ROOM SERVER/REGISTER SERVER連接狀態(tài)的一致性|可用性如何保證? 服務(wù)側(cè)?;钣袩o(wú)特別關(guān)注的地方? 安全性方面是基于TLS再加上應(yīng)用層加密?

會(huì)在server端做,比如重啟操作前,會(huì)下發(fā)指令類(lèi)型消息,讓客戶端進(jìn)行主動(dòng)行為。部分消息使用了加密策略,自定義的rsa+des,另外滿足我們安全公司的需要,也定制開(kāi)發(fā)很多安全加密策略。一致性是通過(guò)冷備解決的,早期考慮雙寫(xiě),但實(shí)時(shí)狀態(tài)雙寫(xiě)同步代價(jià)太高而且容易有臟數(shù)據(jù),比如register掛了,調(diào)用所有room,通過(guò)重新刷入指定register來(lái)解決。

Q14:這個(gè)keeper有開(kāi)源打算嗎?

還在寫(xiě),如果沒(méi)耦合我們系統(tǒng)太多功能,一定會(huì)開(kāi)源的,主要這意味著,我們所有的bind在sdk的庫(kù)也需要開(kāi)源~

Q15:比較好奇lisence是哪個(gè)如果開(kāi)源?

分享題目:go語(yǔ)言多進(jìn)程io go語(yǔ)言如何利用多核
新聞來(lái)源:http://bm7419.com/article32/ddehcpc.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供ChatGPT、網(wǎng)站設(shè)計(jì)、企業(yè)網(wǎng)站制作、定制開(kāi)發(fā)虛擬主機(jī)、用戶體驗(yàn)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

成都網(wǎng)站建設(shè)