真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

go語(yǔ)言內(nèi)存池總結(jié) go語(yǔ)言線程池

Go語(yǔ)言——sync.Map詳解

sync.Map是1.9才推薦的并發(fā)安全的map,除了互斥量以外,還運(yùn)用了原子操作,所以在這之前,有必要了解下 Go語(yǔ)言——原子操作

創(chuàng)新互聯(lián)咨詢電話:18982081108,為您提供成都網(wǎng)站建設(shè)網(wǎng)頁(yè)設(shè)計(jì)及定制高端網(wǎng)站建設(shè)服務(wù),創(chuàng)新互聯(lián)網(wǎng)頁(yè)制作領(lǐng)域10余年,包括成都建筑動(dòng)畫等多個(gè)行業(yè)擁有豐富建站經(jīng)驗(yàn),選擇創(chuàng)新互聯(lián),為企業(yè)保駕護(hù)航!

go1.10\src\sync\map.go

entry分為三種情況:

從read中讀取key,如果key存在就tryStore。

注意這里開(kāi)始需要加鎖,因?yàn)樾枰僮鱠irty。

條目在read中,首先取消標(biāo)記,然后將條目保存到dirty里。(因?yàn)闃?biāo)記的數(shù)據(jù)不在dirty里)

最后原子保存value到條目里面,這里注意read和dirty都有條目。

總結(jié)一下Store:

這里可以看到dirty保存了數(shù)據(jù)的修改,除非可以直接原子更新read,繼續(xù)保持read clean。

有了之前的經(jīng)驗(yàn),可以猜測(cè)下load流程:

與猜測(cè)的 區(qū)別 :

由于數(shù)據(jù)保存兩份,所以刪除考慮:

先看第二種情況。加鎖直接刪除dirty數(shù)據(jù)。思考下貌似沒(méi)什么問(wèn)題,本身就是臟數(shù)據(jù)。

第一種和第三種情況唯一的區(qū)別就是條目是否被標(biāo)記。標(biāo)記代表刪除,所以直接返回。否則CAS操作置為nil。這里總感覺(jué)少點(diǎn)什么,因?yàn)闂l目其實(shí)還是存在的,雖然指針nil。

看了一圈貌似沒(méi)找到標(biāo)記的邏輯,因?yàn)閯h除只是將他變成nil。

之前以為這個(gè)邏輯就是簡(jiǎn)單的將為標(biāo)記的條目拷貝給dirty,現(xiàn)在看來(lái)大有文章。

p == nil,說(shuō)明條目已經(jīng)被delete了,CAS將他置為標(biāo)記刪除。然后這個(gè)條目就不會(huì)保存在dirty里面。

這里其實(shí)就跟miss邏輯串起來(lái)了,因?yàn)閙iss達(dá)到閾值之后,dirty會(huì)全量變成read,也就是說(shuō)標(biāo)記刪除在這一步最終刪除。這個(gè)還是很巧妙的。

真正的刪除邏輯:

很繞。。。。

使用Go實(shí)現(xiàn)一個(gè)數(shù)據(jù)庫(kù)連接池

開(kāi)始本文之前,我們看一段Go連接數(shù)據(jù)庫(kù)的代碼:

本文內(nèi)容我們將解釋連接池背后是如何工作的,并 探索 如何配置數(shù)據(jù)庫(kù)能改變或優(yōu)化其性能。

轉(zhuǎn)自:

整理:地鼠文檔:

那么sql.DB連接池是如何工作的呢?

需要理解的最重要一點(diǎn)是,sql.DB池包含兩種類型的連接——“正在使用”連接和“空閑”連接。當(dāng)您使用連接執(zhí)行數(shù)據(jù)庫(kù)任務(wù)(例如執(zhí)行SQL語(yǔ)句或查詢行)時(shí),該連接被標(biāo)記為正在使用,任務(wù)完成后,該連接被標(biāo)記為空閑。

當(dāng)您使用Go執(zhí)行數(shù)據(jù)庫(kù)操作時(shí),它將首先檢查池中是否有可用的空閑連接。如果有可用的連接,那么Go將重用這個(gè)現(xiàn)有連接,并在任務(wù)期間將其標(biāo)記為正在使用。如果在您需要空閑連接時(shí)池中沒(méi)有空閑連接,那么Go將創(chuàng)建一個(gè)新的連接。

當(dāng)Go重用池中的空閑連接時(shí),與該連接有關(guān)的任何問(wèn)題都會(huì)被優(yōu)雅地處理。異常連接將在放棄之前自動(dòng)重試兩次,這時(shí)Go將從池中刪除異常連接并創(chuàng)建一個(gè)新的連接來(lái)執(zhí)行該任務(wù)。

連接池有四個(gè)方法,我們可以使用它們來(lái)配置連接池的行為。讓我們一個(gè)一個(gè)地來(lái)討論。

SetMaxOpenConns()方法允許您設(shè)置池中“打開(kāi)”連接(使用中+空閑連接)數(shù)量的上限。默認(rèn)情況下,打開(kāi)的連接數(shù)是無(wú)限的。

一般來(lái)說(shuō),MaxOpenConns設(shè)置得越大,可以并發(fā)執(zhí)行的數(shù)據(jù)庫(kù)查詢就越多,連接池本身成為應(yīng)用程序中的瓶頸的風(fēng)險(xiǎn)就越低。

但讓它無(wú)限并不是最好的選擇。默認(rèn)情況下,PostgreSQL最多100個(gè)打開(kāi)連接的硬限制,如果達(dá)到這個(gè)限制的話,它將導(dǎo)致pq驅(qū)動(dòng)返回”sorry, too many clients already”錯(cuò)誤。

為了避免這個(gè)錯(cuò)誤,將池中打開(kāi)的連接數(shù)量限制在100以下是有意義的,可以為其他需要使用PostgreSQL的應(yīng)用程序或會(huì)話留下足夠的空間。

設(shè)置MaxOpenConns限制的另一個(gè)好處是,它充當(dāng)一個(gè)非常基本的限流器,防止數(shù)據(jù)庫(kù)同時(shí)被大量任務(wù)壓垮。

但設(shè)定上限有一個(gè)重要的警告。如果達(dá)到MaxOpenConns限制,并且所有連接都在使用中,那么任何新的數(shù)據(jù)庫(kù)任務(wù)將被迫等待,直到有連接空閑。在我們的API上下文中,用戶的HTTP請(qǐng)求可能在等待空閑連接時(shí)無(wú)限期地“掛起”。因此,為了緩解這種情況,使用上下文為數(shù)據(jù)庫(kù)任務(wù)設(shè)置超時(shí)是很重要的。我們將在書的后面解釋如何處理。

SetMaxIdleConns()方法的作用是:設(shè)置池中空閑連接數(shù)的上限。缺省情況下,最大空閑連接數(shù)為2。

理論上,在池中允許更多的空閑連接將增加性能。因?yàn)樗鼫p少了從頭建立新連接發(fā)生概率—,因此有助于節(jié)省資源。

但要意識(shí)到保持空閑連接是有代價(jià)的。它占用了本來(lái)可以用于應(yīng)用程序和數(shù)據(jù)庫(kù)的內(nèi)存,而且如果一個(gè)連接空閑時(shí)間過(guò)長(zhǎng),它也可能變得不可用。例如,默認(rèn)情況下MySQL會(huì)自動(dòng)關(guān)閉任何8小時(shí)未使用的連接。

因此,與使用更小的空閑連接池相比,將MaxIdleConns設(shè)置得過(guò)高可能會(huì)導(dǎo)致更多的連接變得不可用,浪費(fèi)資源。因此保持適量的空閑連接是必要的。理想情況下,你只希望保持一個(gè)連接空閑,可以快速使用。

另一件要指出的事情是MaxIdleConns值應(yīng)該總是小于或等于MaxOpenConns。Go會(huì)強(qiáng)制保證這點(diǎn),并在必要時(shí)自動(dòng)減少M(fèi)axIdleConns值。

SetConnMaxLifetime()方法用于設(shè)置ConnMaxLifetime的極限值,表示一個(gè)連接保持可用的最長(zhǎng)時(shí)間。默認(rèn)連接的存活時(shí)間沒(méi)有限制,永久可用。

如果設(shè)置ConnMaxLifetime的值為1小時(shí),意味著所有的連接在創(chuàng)建后,經(jīng)過(guò)一個(gè)小時(shí)就會(huì)被標(biāo)記為失效連接,標(biāo)志后就不可復(fù)用。但需要注意:

理論上,ConnMaxLifetime為無(wú)限大(或設(shè)置為很長(zhǎng)生命周期)將提升性能,因?yàn)檫@樣可以減少新建連接。但是在某些情況下,設(shè)置短期存活時(shí)間有用。比如:

如果您決定對(duì)連接池設(shè)置ConnMaxLifetime,那么一定要記住連接過(guò)期(然后重新創(chuàng)建)的頻率。例如,如果連接池中有100個(gè)打開(kāi)的連接,而ConnMaxLifetime為1分鐘,那么您的應(yīng)用程序平均每秒可以殺死并重新創(chuàng)建多達(dá)1.67個(gè)連接。您不希望頻率太大而最終影響性能吧。

SetConnMaxIdleTime()方法在Go 1.15版本引入對(duì)ConnMaxIdleTime進(jìn)行配置。其效果和ConnMaxLifeTime類似,但這里設(shè)置的是:在被標(biāo)記為失效之前一個(gè)連接最長(zhǎng)空閑時(shí)間。例如,如果我們將ConnMaxIdleTime設(shè)置為1小時(shí),那么自上次使用以后在池中空閑了1小時(shí)的任何連接都將被標(biāo)記為過(guò)期并被后臺(tái)清理操作刪除。

這個(gè)配置非常有用,因?yàn)樗馕吨覀兛梢詫?duì)池中空閑連接的數(shù)量設(shè)置相對(duì)較高的限制,但可以通過(guò)刪除不再真正使用的空閑連接來(lái)周期性地釋放資源。

所以有很多信息要吸收。這在實(shí)踐中意味著什么?我們把以上所有的內(nèi)容總結(jié)成一些可行的要點(diǎn)。

1、根據(jù)經(jīng)驗(yàn),您應(yīng)該顯式地設(shè)置MaxOpenConns值。這個(gè)值應(yīng)該低于數(shù)據(jù)庫(kù)和操作系統(tǒng)對(duì)連接數(shù)量的硬性限制,您還可以考慮將其保持在相當(dāng)?shù)偷乃剑猿洚?dāng)基本的限流作用。

對(duì)于本書中的項(xiàng)目,我們將MaxOpenConns限制為25個(gè)連接。我發(fā)現(xiàn)這對(duì)于小型到中型的web應(yīng)用程序和API來(lái)說(shuō)是一個(gè)合理的初始值,但理想情況下,您應(yīng)該根據(jù)基準(zhǔn)測(cè)試和壓測(cè)結(jié)果調(diào)整這個(gè)值。

2、通常,更大的MaxOpenConns和MaxIdleConns值會(huì)帶來(lái)更好的性能。但是,效果是逐漸降低的,而且您應(yīng)該注意,太多的空閑連接(連接沒(méi)有被復(fù)用)實(shí)際上會(huì)導(dǎo)致性能下降和不必要的資源消耗。

因?yàn)镸axIdleConns應(yīng)該總是小于或等于MaxOpenConns,所以對(duì)于這個(gè)項(xiàng)目,我們還將MaxIdleConns限制為25個(gè)連接。

3、為了降低上面第2點(diǎn)的風(fēng)險(xiǎn),通常應(yīng)該設(shè)置ConnMaxIdleTime值來(lái)刪除長(zhǎng)時(shí)間未使用的空閑連接。在這個(gè)項(xiàng)目中,我們將設(shè)置ConnMaxIdleTime持續(xù)時(shí)間為15分鐘。

4、ConnMaxLifetime默認(rèn)設(shè)置為無(wú)限大是可以的,除非您的數(shù)據(jù)庫(kù)對(duì)連接生命周期施加了硬限制,或者您需要它協(xié)助一些操作,比如優(yōu)雅地交換數(shù)據(jù)庫(kù)。這些都不適用于本項(xiàng)目,所以我們將保留這個(gè)默認(rèn)的無(wú)限制配置。

與其硬編碼這些配置,不如更新cmd/api/main.go文件通過(guò)命令行參數(shù)讀取配置。

ConnMaxIdleTime值比較有意思,因?yàn)槲覀兿M鼈鬟f一段時(shí)間,最終需要將其轉(zhuǎn)換為Go的time.Duration類型。這里有幾個(gè)選擇:

1、我們可以使用一個(gè)整數(shù)來(lái)表示秒(或分鐘)的數(shù)量,并將其轉(zhuǎn)換為time.Duration。

2、我們可以使用一個(gè)表示持續(xù)時(shí)間的字符串——比如“5s”(5秒)或“10m”(10分鐘)——然后使用time.ParseDuration()函數(shù)解析它。

3、兩種方法都可以很好地工作,但是在這個(gè)項(xiàng)目中我們將使用選項(xiàng)2。繼續(xù)并更新cmd/api/main.go文件如下:

File: cmd/api/main.go

如何用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)銷,軟件架構(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ù)性主要依賴于你如何把解決方案設(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ù)需要寫入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,然后處理它們。我們使用類似于下面的代碼:

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

在我的書中,簡(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)的寫入到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)大的替代方案。

golang sync.Pool的用法及實(shí)現(xiàn)

正如sycn.Pool的名字所示,這是go中實(shí)現(xiàn)的一個(gè)對(duì)象池,為什么要有這個(gè)池呢?首先go是自帶垃圾回收機(jī)制(也就是通常所說(shuō)的gc)。gc會(huì)帶來(lái)運(yùn)行時(shí)的開(kāi)銷,對(duì)于高頻的內(nèi)存申請(qǐng)與釋放,如果將不用的對(duì)象存放在一個(gè)池子中,用的時(shí)候從池子中取出一個(gè)對(duì)象,用完了再還回去,這樣就能減輕gc的壓力。

對(duì)于池這個(gè)概念,之前可能聽(tīng)說(shuō)過(guò)連接池。能否用sync.Pool實(shí)現(xiàn)一個(gè)連接池呢?答案是不能的。因?yàn)閷?duì)于sync.Pool而言,我們無(wú)法保證每次放回去再取出來(lái)的對(duì)象是與之前一致的,對(duì)象的內(nèi)存存在著唄銷毀的可能。因此,這個(gè)sync.Pool的存在僅僅是為了減緩gc的壓力而生的。

定義sync.Pool的時(shí)候只需要設(shè)置一個(gè)New成員,它是一個(gè)函數(shù),類型為func() interface{},當(dāng)池子中沒(méi)有空閑的對(duì)象時(shí)就會(huì)調(diào)用New函數(shù)生成一個(gè)。由于pool中對(duì)象的數(shù)量不可控,因此并沒(méi)有傳遞任何與對(duì)象數(shù)量有關(guān)的參數(shù)。

然后,調(diào)用調(diào)用Get函數(shù)就可以取出一個(gè)對(duì)象,調(diào)用Put函數(shù)就可以將對(duì)象歸還到池子中。

golang sync.pool對(duì)象復(fù)用 并發(fā)原理 緩存池

在go http每一次go serve(l)都會(huì)構(gòu)建Request數(shù)據(jù)結(jié)構(gòu)。在大量數(shù)據(jù)請(qǐng)求或高并發(fā)的場(chǎng)景中,頻繁創(chuàng)建銷毀對(duì)象,會(huì)導(dǎo)致GC壓力。解決辦法之一就是使用對(duì)象復(fù)用技術(shù)。在http協(xié)議層之下,使用對(duì)象復(fù)用技術(shù)創(chuàng)建Request數(shù)據(jù)結(jié)構(gòu)。在http協(xié)議層之上,可以使用對(duì)象復(fù)用技術(shù)創(chuàng)建(w,*r,ctx)數(shù)據(jù)結(jié)構(gòu)。這樣即可以回快TCP層讀包之后的解析速度,也可也加快請(qǐng)求處理的速度。

先上一個(gè)測(cè)試:

結(jié)論是這樣的:

貌似使用池化,性能弱爆了???這似乎與net/http使用sync.pool池化Request來(lái)優(yōu)化性能的選擇相違背。這同時(shí)也說(shuō)明了一個(gè)問(wèn)題,好的東西,如果濫用反而造成了性能成倍的下降。在看過(guò)pool原理之后,結(jié)合實(shí)例,將給出正確的使用方法,并給出預(yù)期的效果。

sync.Pool是一個(gè) 協(xié)程安全 的 臨時(shí)對(duì)象池 。數(shù)據(jù)結(jié)構(gòu)如下:

local 成員的真實(shí)類型是一個(gè) poolLocal 數(shù)組,localSize 是數(shù)組長(zhǎng)度。這涉及到Pool實(shí)現(xiàn),pool為每個(gè)P分配了一個(gè)對(duì)象,P數(shù)量設(shè)置為runtime.GOMAXPROCS(0)。在并發(fā)讀寫時(shí),goroutine綁定的P有對(duì)象,先用自己的,沒(méi)有去偷其它P的。go語(yǔ)言將數(shù)據(jù)分散在了各個(gè)真正運(yùn)行的P中,降低了鎖競(jìng)爭(zhēng),提高了并發(fā)能力。

不要習(xí)慣性地誤認(rèn)為New是一個(gè)關(guān)鍵字,這里的New是Pool的一個(gè)字段,也是一個(gè)閉包名稱。其API:

如果不指定New字段,對(duì)象池為空時(shí)會(huì)返回nil,而不是一個(gè)新構(gòu)建的對(duì)象。Get()到的對(duì)象是隨機(jī)的。

原生sync.Pool的問(wèn)題是,Pool中的對(duì)象會(huì)被GC清理掉,這使得sync.Pool只適合做簡(jiǎn)單地對(duì)象池,不適合作連接池。

pool創(chuàng)建時(shí)不能指定大小,沒(méi)有數(shù)量限制。pool中對(duì)象會(huì)被GC清掉,只存在于兩次GC之間。實(shí)現(xiàn)是pool的init方法注冊(cè)了一個(gè)poolCleanup()函數(shù),這個(gè)方法在GC之前執(zhí)行,清空pool中的所有緩存對(duì)象。

為使多協(xié)程使用同一個(gè)POOL。最基本的想法就是每個(gè)協(xié)程,加鎖去操作共享的POOL,這顯然是低效的。而進(jìn)一步改進(jìn),類似于ConcurrentHashMap(JDK7)的分Segment,提高其并發(fā)性可以一定程度性緩解。

注意到pool中的對(duì)象是無(wú)差異性的,加鎖或者分段加鎖都不是較好的做法。go的做法是為每一個(gè)綁定協(xié)程的P都分配一個(gè)子池。每個(gè)子池又分為私有池和共享列表。共享列表是分別存放在各個(gè)P之上的共享區(qū)域,而不是各個(gè)P共享的一塊內(nèi)存。協(xié)程拿自己P里的子池對(duì)象不需要加鎖,拿共享列表中的就需要加鎖了。

Get對(duì)象過(guò)程:

Put過(guò)程:

如何解決Get最壞情況遍歷所有P才獲取得對(duì)象呢:

方法1止前sync.pool并沒(méi)有這樣的設(shè)置。方法2由于goroutine被分配到哪個(gè)P由調(diào)度器調(diào)度不可控,無(wú)法確保其平衡。

由于不可控的GC導(dǎo)致生命周期過(guò)短,且池大小不可控,因而不適合作連接池。僅適用于增加對(duì)象重用機(jī)率,減少GC負(fù)擔(dān)。2

執(zhí)行結(jié)果:

單線程情況下,遍歷其它無(wú)元素的P,長(zhǎng)時(shí)間加鎖性能低下。啟用協(xié)程改善。

結(jié)果:

測(cè)試場(chǎng)景在goroutines遠(yuǎn)大于GOMAXPROCS情況下,與非池化性能差異巨大。

測(cè)試結(jié)果

可以看到同樣使用*sync.pool,較大池大小的命中率較高,性能遠(yuǎn)高于空池。

結(jié)論:pool在一定的使用條件下提高并發(fā)性能,條件1是協(xié)程數(shù)遠(yuǎn)大于GOMAXPROCS,條件2是池中對(duì)象遠(yuǎn)大于GOMAXPROCS。歸結(jié)成一個(gè)原因就是使對(duì)象在各個(gè)P中均勻分布。

池pool和緩存cache的區(qū)別。池的意思是,池內(nèi)對(duì)象是可以互換的,不關(guān)心具體值,甚至不需要區(qū)分是新建的還是從池中拿出的。緩存指的是KV映射,緩存里的值互不相同,清除機(jī)制更為復(fù)雜。緩存清除算法如LRU、LIRS緩存算法。

池空間回收的幾種方式。一些是GC前回收,一些是基于時(shí)鐘或弱引用回收。最終確定在GC時(shí)回收Pool內(nèi)對(duì)象,即不回避GC。用java的GC解釋弱引用。GC的四種引用:強(qiáng)引用、弱引用、軟引用、虛引用。虛引用即沒(méi)有引用,弱引用GC但有空間則保留,軟引用GC即清除。ThreadLocal的值為弱引用的例子。

regexp 包為了保證并發(fā)時(shí)使用同一個(gè)正則,而維護(hù)了一組狀態(tài)機(jī)。

fmt包做字串拼接,從sync.pool拿[]byte對(duì)象。避免頻繁構(gòu)建再GC效率高很多。


網(wǎng)頁(yè)題目:go語(yǔ)言內(nèi)存池總結(jié) go語(yǔ)言線程池
網(wǎng)站網(wǎng)址:http://weahome.cn/article/hgesjh.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部