新聞中心
如何在 Go 語言中使用 Redis 連接池
一、關(guān)于連接池
創(chuàng)新互聯(lián)建站專注于雙灤網(wǎng)站建設(shè)服務(wù)及定制,我們擁有豐富的企業(yè)做網(wǎng)站經(jīng)驗(yàn)。 熱誠為您提供雙灤營銷型網(wǎng)站建設(shè),雙灤網(wǎng)站制作、雙灤網(wǎng)頁設(shè)計(jì)、雙灤網(wǎng)站官網(wǎng)定制、微信小程序開發(fā)服務(wù),打造雙灤網(wǎng)絡(luò)公司原創(chuàng)品牌,更為您提供雙灤網(wǎng)站排名全網(wǎng)營銷落地服務(wù)。
一個數(shù)據(jù)庫服務(wù)器只擁有有限的資源,并且如果你沒有充分使用這些資源,你可以通過使用更多的連接來提高吞吐量。一旦所有的資源都在使用,那么你就不 能通過增加更多的連接來提高吞吐量。事實(shí)上,吞吐量在連接負(fù)載較大時就開始下降了。通??梢酝ㄟ^限制與可用的資源相匹配的數(shù)據(jù)庫連接的數(shù)量來提高延遲和吞 吐量。
如何在Go語言中使用Redis連接池
如果不使用連接池,那么,每次傳輸數(shù)據(jù),我們都需要進(jìn)行創(chuàng)建連接,收發(fā)數(shù)據(jù),關(guān)閉連接。在并發(fā)量不高的場景,基本上不會有什么問題,一旦并發(fā)量上去了,那么,一般就會遇到下面幾個常見問題:
性能普遍上不去
CPU 大量資源被系統(tǒng)消耗
網(wǎng)絡(luò)一旦抖動,會有大量 TIME_WAIT 產(chǎn)生,不得不定期重啟服務(wù)或定期重啟機(jī)器
服務(wù)器工作不穩(wěn)定,QPS 忽高忽低
要想解決這些問題,我們就要用到連接池了。連接池的思路很簡單,在初始化時,創(chuàng)建一定數(shù)量的連接,先把所有長連接存起來,然后,誰需要使用,從這里取走,干完活立馬放回來。 如果請求數(shù)超出連接池容量,那么就排隊(duì)等待、退化成短連接或者直接丟棄掉。
二、使用連接池遇到的坑
最近在一個項(xiàng)目中,需要實(shí)現(xiàn)一個簡單的 Web Server 提供 Redis 的 HTTP interface,提供 JSON 形式的返回結(jié)果??紤]用 Go 來實(shí)現(xiàn)。
首先,去看一下 Redis 官方推薦的 Go Redis driver。官方 Star 的項(xiàng)目有兩個:Radix.v2 和 Redigo。經(jīng)過簡單的比較后,選擇了更加輕量級和實(shí)現(xiàn)更加優(yōu)雅的 Radix.v2。
Radix.v2 包是根據(jù)功能劃分成一個個的 sub package,每一個 sub package 在一個獨(dú)立的子目錄中,結(jié)構(gòu)非常清晰。我的項(xiàng)目中會用到的 sub package 有 redis 和 pool。
由于我想讓這種被 fork 的進(jìn)程最好簡單點(diǎn),做的事情單一一些,所以,在沒有深入去看 Radix.v2 的 pool 的實(shí)現(xiàn)之前,我選擇了自己實(shí)現(xiàn)一個 Redis pool。(這里,就不貼代碼了。后來發(fā)現(xiàn)自己實(shí)現(xiàn)的 Redis pool 與 Radix.v2 實(shí)現(xiàn)的 Redis pool 的原理是一樣的,都是基于 channel 實(shí)現(xiàn)的, 遇到的問題也是一樣的。)
不過在測試過程中,發(fā)現(xiàn)了一個詭異的問題。在請求過程中經(jīng)常會報(bào) EOF 錯誤。而且是概率性出現(xiàn),一會有問題,一會又好了。通過反復(fù)的測試,發(fā)現(xiàn) bug 是有規(guī)律的,當(dāng)程序空閑一會后,再進(jìn)行連續(xù)請求,會發(fā)生3次失敗,然后之后的請求都能成功,而我的連接池大小設(shè)置的是3。再進(jìn)一步分析,程序空閑300秒 后,再請求就會失敗,發(fā)現(xiàn)我的 Redis server 配置了 timeout 300,至此,問題就清楚了。是連接超時 Redis server 主動斷開了連接??蛻舳诉@邊從一個超時的連接請求就會得到 EOF 錯誤。
然后我看了一下 Radix.v2 的 pool 包的源碼,發(fā)現(xiàn)這個庫本身并沒有檢測壞的連接,并替換為新server{location/pool{content_by_lua_block{localredis=require"resty.redis"localred=redis:new()localok,err=red:connect("127.0.0.1",6379)ifnotokthenngx.say("failedtoconnect:",err)returnendok,err=red:set("hello","world")ifnotokthenreturnendred:set_keepalive(10000,100)}}}
發(fā)現(xiàn)有個 set_keepalive 的方法,查了一下官方文檔,方法的原型是 syntax: ok, err = red:set_keepalive(max_idle_timeout, pool_size) 貌似 max_idle_timeout 這個參數(shù),就是我們所缺少的東西,然后進(jìn)一步跟蹤源碼,看看里面是怎么保證連接有效的。
function_M.set_keepalive(self,...)localsock=self.sockifnotsockthenreturnnil,"notinitialized"endifself.subscribedthenreturnnil,"subscribedstate"endreturnsock:setkeepalive(...)end
至此,已經(jīng)清楚了,使用了 tcp 的 keepalive 心跳機(jī)制。
于是,通過與 Radix.v2 的作者一些討論,選擇自己在 redis 這層使用心跳機(jī)制,來解決這個問題。
四、最后的解決方案
在創(chuàng)建連接池之后,起一個 goroutine,每隔一段 idleTime 發(fā)送一個 PING 到 Redis server。其中,idleTime 略小于 Redis server 的 timeout 配置。連接池初始化部分代碼如下:
p,err:=pool.New("tcp",u.Host,concurrency)errHndlr(err)gofunc(){for{p.Cmd("PING")time.Sleep(idelTime*time.Second)}}()
使用 redis 傳輸數(shù)據(jù)部分代碼如下:
funcredisDo(p*pool.Pool,cmdstring,args...interface{})(reply*redis.Resp,errerror){reply=p.Cmd(cmd,args...)iferr=reply.Err;err!=nil{iferr!=io.EOF{Fatal.Println("redis",cmd,args,"erris",err)}}return}
其中,Radix.v2 連接池內(nèi)部進(jìn)行了連接池內(nèi)連接的獲取和放回,代碼如下:
//Cmdautomaticallygetsoneclientfromthepool,executesthegivencommand//(returningitsresult),andputstheclientbackinthepoolfunc(p*Pool)Cmd(cmdstring,args...interface{})*redis.Resp{c,err:=p.Get()iferr!=nil{returnredis.NewResp(err)}deferp.Put(c)returnc.Cmd(cmd,args...)}
這樣,我們就有了 keepalive 的機(jī)制,不會出現(xiàn) timeout 的連接了,從 redis 連接池里面取出的連接都是可用的連接了。看似簡單的代碼,卻完美的解決了連接池里面超時連接的問題。同時,就算 Redis server 重啟等情況,也能保證連接自動重連。
如何安裝Go語言安裝包
go語言作為google的一個主推語言,最近很多人都在研究,也花了一點(diǎn)時間對他的安裝進(jìn)行了測試,本人使用Sublime Text 2 + GoSublime + gocode
顧名思義首先是安裝Go,這里有很詳細(xì)的安裝說明, 或者(golang.org自己去找hosts),官方已經(jīng)支持Windows版本
下載解壓配置環(huán)境變量
“環(huán)境變量”(我的電腦-高級系統(tǒng)設(shè)置-環(huán)境變量),在系統(tǒng)變量的標(biāo)簽下,依次新建編輯如下幾個鍵值對:
(1). 新建 變量名:GOBIN 變量值 :c:\go\bin
(2). 新建 變量名:GOARCH 變量值:386
(3). 新建 變量名:GOOS 變量值:windows
(4). 新建 變量名: GOROOT 變量值:c:\go
(5). 編輯 Path 在Path的變量值的最后加上 %GOBIN%
1. 下載 Sublime Text 2,地址如下:
2. 解壓以后,雙擊 sublime_text,就可以使用 Sublime Text 2 了。
破解:
用 WinHex 編輯 sublime_text_backup.exe 文件, 跳到 000CBB70 那一行,將該行的 8A C3 修改為 B0 01 然后保存
破解注冊成功
3. 安裝 Package Control,在打開 Sublime Text 2以后,按下快捷鍵 Ctrl + `,打開命令窗行(具體在view——show Console),并回車:
import urllib2,os; pf=’Package Control.sublime-package’; ipp=sublime.installed_packages_path(); os.makedirs(ipp) if not os.path.exists(ipp) else None; urllib2.install_opener(urllib2.build_opener(urllib2.ProxyHandler())); open(os.path.join(ipp,pf),’wb’).write(urllib2.urlopen(‘’+pf.replace(‘ ‘,’%20′)).read()); print ‘Please restart Sublime Text to finish installation’
4. 重啟Sublime Text 2后,就可以發(fā)現(xiàn)在 Preferences菜單下,多出一個菜單項(xiàng) Package Control。
5.現(xiàn)在安裝GoSublime插件了,按住Ctrl+Shilft+p會彈出一個對話框輸入install回車彈出一個安裝包的對話框
同上輸入GoSublime選擇GoSublime回車
本機(jī)已經(jīng)安裝所以沒有出現(xiàn)選項(xiàng),輸入Go build選中回車(這個屬于可選)
到此GoSublime安裝成功
6.下面安裝gocode,
首安裝 Git-1.7.11-preview20120710。
打開控制臺,輸入以下內(nèi)容:
go get github.com/nsf/gocode
go install github.com/nsf/gocode
go get github.com/DisposaBoy/MarGo
go install github.com/DisposaBoy/MarGo
也可以去github下載(要安裝google的git版本管理工具)
安裝完成后,我們可以在 go/bin 目錄下,發(fā)現(xiàn)多出了個 gocode 文件。(一定要放在bin目錄下)
7. 修改GoSublime配置:在 Preferences菜單下,找到Package Settings,然后找到 GoSublime,再往下找到 Settings – Default。再打開的文件中,添加如下配置,并保存:
"env": {"path":"c:/go/bin;" },
好了,到目前為止,開發(fā)環(huán)境搭建完成。
下面可以自由編程了。呵呵。
按下快捷鍵 Ctrl + b 界面下方會出現(xiàn)如下界面:
好了,到現(xiàn)在,開發(fā)環(huán)境就搭建完畢了。
如下是內(nèi)容我這邊沒有使用照樣可以使用:
sublime Text 2 編譯配置設(shè)置方法
tools-build system-new build system 新建一個配置文件 設(shè)置為
{
“cmd”: ["go", "run", "$file_name"],
“file_regex”: “^[ ]*File \”(…*?)\”, line ([0-9]*)”,
“working_dir”: “$file_path”,
“selector”: “source.go”
}
然后就可以用ctrl+b 編譯了
保存
go語言TCP連接池rocket049/connpool使用
安裝:
go get -v -u github.com/rocket049/connpool
go get -v -u gitee.com/rocket049/connpool
rocket049/connpool 包是本人用go語言開發(fā)的,提供一個通用的TCP連接池,初始化參數(shù)包括最高連接數(shù)、超時秒數(shù)、連接函數(shù),放回連接池的連接被重新取出時,如果已經(jīng)超時,將會自動重新連接;如果沒有超時,連接將被復(fù)用。
可調(diào)用的函數(shù):
調(diào)用示例:
golang sync.pool對象復(fù)用 并發(fā)原理 緩存池
在go http每一次go serve(l)都會構(gòu)建Request數(shù)據(jù)結(jié)構(gòu)。在大量數(shù)據(jù)請求或高并發(fā)的場景中,頻繁創(chuàng)建銷毀對象,會導(dǎo)致GC壓力。解決辦法之一就是使用對象復(fù)用技術(shù)。在http協(xié)議層之下,使用對象復(fù)用技術(shù)創(chuàng)建Request數(shù)據(jù)結(jié)構(gòu)。在http協(xié)議層之上,可以使用對象復(fù)用技術(shù)創(chuàng)建(w,*r,ctx)數(shù)據(jù)結(jié)構(gòu)。這樣即可以回快TCP層讀包之后的解析速度,也可也加快請求處理的速度。
先上一個測試:
結(jié)論是這樣的:
貌似使用池化,性能弱爆了???這似乎與net/http使用sync.pool池化Request來優(yōu)化性能的選擇相違背。這同時也說明了一個問題,好的東西,如果濫用反而造成了性能成倍的下降。在看過pool原理之后,結(jié)合實(shí)例,將給出正確的使用方法,并給出預(yù)期的效果。
sync.Pool是一個 協(xié)程安全 的 臨時對象池 。數(shù)據(jù)結(jié)構(gòu)如下:
local 成員的真實(shí)類型是一個 poolLocal 數(shù)組,localSize 是數(shù)組長度。這涉及到Pool實(shí)現(xiàn),pool為每個P分配了一個對象,P數(shù)量設(shè)置為runtime.GOMAXPROCS(0)。在并發(fā)讀寫時,goroutine綁定的P有對象,先用自己的,沒有去偷其它P的。go語言將數(shù)據(jù)分散在了各個真正運(yùn)行的P中,降低了鎖競爭,提高了并發(fā)能力。
不要習(xí)慣性地誤認(rèn)為New是一個關(guān)鍵字,這里的New是Pool的一個字段,也是一個閉包名稱。其API:
如果不指定New字段,對象池為空時會返回nil,而不是一個新構(gòu)建的對象。Get()到的對象是隨機(jī)的。
原生sync.Pool的問題是,Pool中的對象會被GC清理掉,這使得sync.Pool只適合做簡單地對象池,不適合作連接池。
pool創(chuàng)建時不能指定大小,沒有數(shù)量限制。pool中對象會被GC清掉,只存在于兩次GC之間。實(shí)現(xiàn)是pool的init方法注冊了一個poolCleanup()函數(shù),這個方法在GC之前執(zhí)行,清空pool中的所有緩存對象。
為使多協(xié)程使用同一個POOL。最基本的想法就是每個協(xié)程,加鎖去操作共享的POOL,這顯然是低效的。而進(jìn)一步改進(jìn),類似于ConcurrentHashMap(JDK7)的分Segment,提高其并發(fā)性可以一定程度性緩解。
注意到pool中的對象是無差異性的,加鎖或者分段加鎖都不是較好的做法。go的做法是為每一個綁定協(xié)程的P都分配一個子池。每個子池又分為私有池和共享列表。共享列表是分別存放在各個P之上的共享區(qū)域,而不是各個P共享的一塊內(nèi)存。協(xié)程拿自己P里的子池對象不需要加鎖,拿共享列表中的就需要加鎖了。
Get對象過程:
Put過程:
如何解決Get最壞情況遍歷所有P才獲取得對象呢:
方法1止前sync.pool并沒有這樣的設(shè)置。方法2由于goroutine被分配到哪個P由調(diào)度器調(diào)度不可控,無法確保其平衡。
由于不可控的GC導(dǎo)致生命周期過短,且池大小不可控,因而不適合作連接池。僅適用于增加對象重用機(jī)率,減少GC負(fù)擔(dān)。2
執(zhí)行結(jié)果:
單線程情況下,遍歷其它無元素的P,長時間加鎖性能低下。啟用協(xié)程改善。
結(jié)果:
測試場景在goroutines遠(yuǎn)大于GOMAXPROCS情況下,與非池化性能差異巨大。
測試結(jié)果
可以看到同樣使用*sync.pool,較大池大小的命中率較高,性能遠(yuǎn)高于空池。
結(jié)論:pool在一定的使用條件下提高并發(fā)性能,條件1是協(xié)程數(shù)遠(yuǎn)大于GOMAXPROCS,條件2是池中對象遠(yuǎn)大于GOMAXPROCS。歸結(jié)成一個原因就是使對象在各個P中均勻分布。
池pool和緩存cache的區(qū)別。池的意思是,池內(nèi)對象是可以互換的,不關(guān)心具體值,甚至不需要區(qū)分是新建的還是從池中拿出的。緩存指的是KV映射,緩存里的值互不相同,清除機(jī)制更為復(fù)雜。緩存清除算法如LRU、LIRS緩存算法。
池空間回收的幾種方式。一些是GC前回收,一些是基于時鐘或弱引用回收。最終確定在GC時回收Pool內(nèi)對象,即不回避GC。用java的GC解釋弱引用。GC的四種引用:強(qiáng)引用、弱引用、軟引用、虛引用。虛引用即沒有引用,弱引用GC但有空間則保留,軟引用GC即清除。ThreadLocal的值為弱引用的例子。
regexp 包為了保證并發(fā)時使用同一個正則,而維護(hù)了一組狀態(tài)機(jī)。
fmt包做字串拼接,從sync.pool拿[]byte對象。避免頻繁構(gòu)建再GC效率高很多。
golang sync.Pool的用法及實(shí)現(xiàn)
正如sycn.Pool的名字所示,這是go中實(shí)現(xiàn)的一個對象池,為什么要有這個池呢?首先go是自帶垃圾回收機(jī)制(也就是通常所說的gc)。gc會帶來運(yùn)行時的開銷,對于高頻的內(nèi)存申請與釋放,如果將不用的對象存放在一個池子中,用的時候從池子中取出一個對象,用完了再還回去,這樣就能減輕gc的壓力。
對于池這個概念,之前可能聽說過連接池。能否用sync.Pool實(shí)現(xiàn)一個連接池呢?答案是不能的。因?yàn)閷τ趕ync.Pool而言,我們無法保證每次放回去再取出來的對象是與之前一致的,對象的內(nèi)存存在著唄銷毀的可能。因此,這個sync.Pool的存在僅僅是為了減緩gc的壓力而生的。
定義sync.Pool的時候只需要設(shè)置一個New成員,它是一個函數(shù),類型為func() interface{},當(dāng)池子中沒有空閑的對象時就會調(diào)用New函數(shù)生成一個。由于pool中對象的數(shù)量不可控,因此并沒有傳遞任何與對象數(shù)量有關(guān)的參數(shù)。
然后,調(diào)用調(diào)用Get函數(shù)就可以取出一個對象,調(diào)用Put函數(shù)就可以將對象歸還到池子中。
文章名稱:go語言pool包,golang pool
網(wǎng)站路徑:http://fisionsoft.com.cn/article/dsissgs.html