GoLang redis 串連池

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

最近被日誌是折騰得死去活來,寫檔案無疑效率是最高的,但是分布式又成問題,雖然稍微折騰一下配合NFS,還是可以搞一搞的,但是始終語言設計沒有那麼方便。

最終決定用redis,換了redis以為就好了,因為記憶體運行嘛,誰知道tcp串連開銷大得一塌糊塗,伺服器負載一下子高了許多,使用netstat -an 查看發現一堆的 TIME_WAIT,連ssh到伺服器都巨慢無比,所謂天下武功唯快不破,這麼慢80歲老太太跳一支廣場舞都能給滅了吧。

既然 tcp串連開銷這麼大,當然首要任務就是解決串連問題,明顯一個請求一次串連是很不靠譜的,還不如直接往硬碟寫日誌呢,當然寫日誌第一段也說了,不支援分布式,業務分配沒那麼好。

那麼,能不能先定只用一個串連呢,這顯然是不行的,一個串連多個php-fpm互掐也會造成瓶頸,那如果是一個php-fpm一個串連呢?顯然這是可取的,於是用了php的redis長串連,php-fpm.ini 配置如下:

pm = static pm.max_children = 400 pm.max_requests = 10240

php使用 pconnect 來代替 connect

$redis = new redis();$redis->pconnect('192.168.0.2', 6379); // 內網伺服器$redis->lpush('list', 'Just a test');

上面的配合意思是預設開啟400個php-fpm來處理nginx反向 Proxy過來的請求,一個php-fpm處理10240個請求,也就是說,處理10240個請求只需要串連redis一次,顯然對於一個請求串連一次redis的開銷要小N倍,壓測效果也如上所述,從原本的每秒處理幾千次請求一下子漲到了1W多次請求。

壓測指令也貼上吧,其實還是有許多人不知道的

ab -n 100000 -c 200 要壓測的url  // 並發200個用戶端請求100000次要壓測的url

當然要驗證nginx是否真的只開了400個php-fpm,可以用以下代碼驗證

$pid = getmypid();touch("pids/".$pid);

執行上面的壓測指令,看看pids目錄下是不是產生了400個以當前php-fpm進程號為名稱的檔案,當然不是剛好400個,因為還有一兩個是manager進程嘛,呵呵。

壓測效果很理想,那是不是問題就解決了呢,當然,PHP沒你想的那麼美好,舉個例子,Mysql的長串連預設是8小時,redis沒有瞭解過,我們就當他也8小時,那8小時一個php-fpm處理10240個請求肯定早就處理完啦,那這個長串連還不關閉,又不能重用,隨著時間的推移,串連數不是只增不減,總有一天會記憶體溢出?

有人可能會說,我測一下10240個請求需要多長時間,redis長串連的逾時時間設定接近的數字就好,問題是網路環境哪有想象那麼美好,假如網路不好造成10240個請求還沒處理完連線逾時了呢?加入網路太好處理了好幾輪10240次請求串連越來越多了呢?

不過redis好就好在他是單進程單線程IO多工,所以串連一直不關閉也不會有什麼大的影響,Mysql是多線程的,線程開多了不關,問題肯定還是比較嚴重的。

那有沒有辦法可以弄一個來管理這些長串連的,讓他一直不要關閉,用完就給另一個新開的php-fpm,答案就是串連池。

網上照抄一下原理:

    串連池基本的思想是在系統初始化的時候,將資料庫連接作為Object Storage Service在記憶體中,當使用者需要訪問資料庫時,並非建立一個新的串連,而是從串連池中取出一個已建立的空閑連線物件。使用完畢後,使用者也並非將串連關閉,而是將串連放回串連池中,以供下一個請求訪問使用。而串連的建立、斷開都由串連池自身來管理。同時,還可以通過設定串連池的參數來控制串連池中的初始串連數、串連的上下限數以及每個串連的最大使用次數、最大空閑時間等等。也可以通過其自身的管理機制來監視資料庫連接的數量、使用方式等。

按照上面的思想設計一個串連池顯然對PHP是莫大的挑戰,因為php-fpm之間記憶體是不共用的,於是我選擇了Go語言來幹這事,原因很簡單,Go的寫法和PHP一樣簡單,java太複雜配置環境也很麻煩,最後被我拋棄了,也不能說拋棄吧,太菜了用不來。

Go串連redis有很多庫,最終選擇了github.com/garyburd/redigo/redis,然後參考網上的文章寫了一個串連池的例子:

package mainimport (    "net/http"    "runtime"    "io"    "fmt"    "log"    "time"    "github.com/garyburd/redigo/redis")// 串連池大小var MAX_POOL_SIZE = 20var redisPoll chan redis.Connfunc putRedis(conn redis.Conn) {    // 基於函數和介面間互不信任原則,這裡再判斷一次,養成這個好習慣哦    if redisPoll == nil {        redisPoll = make(chan redis.Conn, MAX_POOL_SIZE)    }    if len(redisPoll) >= MAX_POOL_SIZE {        conn.Close()        return    }    redisPoll <- conn}func InitRedis(network, address string) redis.Conn {    // 緩衝機制,相當於訊息佇列    if len(redisPoll) == 0 {        // 如果長度為0,就定義一個redis.Conn類型長度為MAX_POOL_SIZE的channel        redisPoll = make(chan redis.Conn, MAX_POOL_SIZE)        go func() {            for i := 0; i < MAX_POOL_SIZE/2; i++ {                c, err := redis.Dial(network, address)                if err != nil {                    panic(err)                }                putRedis(c)            }        } ()    }    return <-redisPoll}func redisServer(w http.ResponseWriter, r *http.Request) {    startTime := time.Now()    c := InitRedis("tcp", "192.168.0.237:6379")    dbkey := "netgame:info"    if ok, err := redis.Bool(c.Do("LPUSH", dbkey, "yanetao")); ok {    } else {        log.Print(err)    }    msg := fmt.Sprintf("用時:%s", time.Now().Sub(startTime));    io.WriteString(w, msg+"\n\n");}func main() {    // 利用cpu多核來處理http請求,這個沒有用go預設就是單核處理http的,這個壓測過了,請一定要相信我    runtime.GOMAXPROCS(runtime.NumCPU());    http.HandleFunc("/", redisServer);    http.ListenAndServe(":9527", nil);}

上面看似實現了串連池,實際壓測效果很不理想,不但請求數低,還報錯。

請求數底是因為沒有解決串連開銷,上面是第一次來的時候開一個go協程去串連20/2就是10次redis,然後放到channel裡面去,實際上就是隊列了,channel就是訊息佇列,然後當這10個串連被請求用完了,就又產生10個,實際上tcp串連數一個沒少,多少個請求就多少個串連數,只是把連線時間片給移了一下,移給剛好10次串連過後那個倒黴蛋。

錯誤是因為我內網測試,請求太快了,第一波10個串連還沒串連好,第二波又來了,沒錯,又一大波殭屍,然後幾波的goroutine就開始互掐,導致串連錯誤了

還是沒有達到串連池的效果。

串連池的概念是先產生預設的串連,例如40個,那麼所有請求過來,都是用這40個串連來處理。

當40個串連被用完的時候,要麼排隊,要麼自增多幾個來處理。

這40個串連和新產生的串連,假如產生多5個,那麼這45個串連是不會關閉的,用完就放回池裡,其實也就是隊列裡,等待其他請求來使用他,達到串連複用的效果。

也就是說這些串連一定是長串連,一直連著不斷開。

上面明顯是短串連,我試過放到全域變數,每個串連處理六七個請求之後,就斷開了,程式提示串連不可用,go要如何達到長串連的效果呢,最後在老外的文章找到了這個庫的實現方式,代碼如下:

package mainimport (    "net/http"    "runtime"    "io"    "fmt"    "log"    "time"    "github.com/garyburd/redigo/redis")// 串連池大小var MAX_POOL_SIZE = 20var redisPoll chan redis.Connfunc putRedis(conn redis.Conn) {    // 基於函數和介面間互不信任原則,這裡再判斷一次,養成這個好習慣哦    if redisPoll == nil {        redisPoll = make(chan redis.Conn, MAX_POOL_SIZE)    }    if len(redisPoll) >= MAX_POOL_SIZE {        conn.Close()        return    }    redisPoll <- conn}func InitRedis(network, address string) redis.Conn {    // 緩衝機制,相當於訊息佇列    if len(redisPoll) == 0 {        // 如果長度為0,就定義一個redis.Conn類型長度為MAX_POOL_SIZE的channel        redisPoll = make(chan redis.Conn, MAX_POOL_SIZE)        go func() {            for i := 0; i < MAX_POOL_SIZE/2; i++ {                c, err := redis.Dial(network, address)                if err != nil {                    panic(err)                }                putRedis(c)            }        } ()    }    return <-redisPoll}func redisServer(w http.ResponseWriter, r *http.Request) {    startTime := time.Now()    c := InitRedis("tcp", "192.168.0.237:6379")    dbkey := "netgame:info"    if ok, err := redis.Bool(c.Do("LPUSH", dbkey, "yanetao")); ok {    } else {        log.Print(err)    }    msg := fmt.Sprintf("用時:%s", time.Now().Sub(startTime));    io.WriteString(w, msg+"\n\n");}func main() {    // 利用cpu多核來處理http請求,這個沒有用go預設就是單核處理http的,這個壓測過了,請一定要相信我    runtime.GOMAXPROCS(runtime.NumCPU());    http.HandleFunc("/", redisServer);    http.ListenAndServe(":9527", nil);}

壓測了一下,上面的代碼往redis裡面插資料跟只是輸出字串HelloWorld到用戶端幾乎一樣快,檢查了一下redis裡面netgame:info這個隊列資料,一條資料都沒丟,這才是我真正要的效果啊,好吧,一個牛逼哄哄的go+redis日誌系統真的要誕生了,哇哈哈。

參考:

一個老外問把串連放到全域變數搞不定,當然搞不定,短串連會逾時的嘛

官方的實現源碼

串連池概念


聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.