Golang?Redis連接池實現(xiàn)原理及示例探究
引言
用11篇文章實現(xiàn)一個可用的Redis服務(wù),姑且叫EasyRedis吧,希望通過文章將Redis掰開撕碎了呈現(xiàn)給大家,而不是僅僅停留在八股文的層面,并且有非常爽的感覺,歡迎持續(xù)關(guān)注學(xué)習(xí)。
項目代碼地址: https://github.com/gofish2020/easyredis
- [x] easyredis之TCP服務(wù)
- [x] easyredis之網(wǎng)絡(luò)請求序列化協(xié)議(RESP)
- [x] easyredis之內(nèi)存數(shù)據(jù)庫
- [x] easyredis之過期時間 (時間輪實現(xiàn))
- [x] easyredis之持久化 (AOF實現(xiàn))
- [x] easyredis之發(fā)布訂閱功能
- [x] easyredis之有序集合(跳表實現(xiàn))
- [x] easyredis之 pipeline 客戶端實現(xiàn)
- [x] easyredis之事務(wù)(原子性/回滾)
- [x] easyredis之連接池
- [ ] easyredis之分布式集群存儲
Redis之連接池
通過本篇可以學(xué)到什么?
通道的應(yīng)用
連接池的封裝
從本篇開始,實現(xiàn)分布式相關(guān)的代碼。既然是分布式,那么redis key就會分布(分散)在不同的集群節(jié)點上。

當客戶端發(fā)送set key value命令給Redis0服務(wù),通過hash計算如果該key應(yīng)該保存在Redis2服務(wù),那么Redis0就要連接Redis2服務(wù),并將命令轉(zhuǎn)發(fā)給Redis2進行處理。
在命令的轉(zhuǎn)發(fā)的過程中,需要頻繁的連接分布式節(jié)點,所以我們需要先實現(xiàn)連接池的基本功能,復(fù)用連接。
在第八篇pipeline客戶端我們已經(jīng)實現(xiàn)了客戶端連接,本篇需要實現(xiàn)一個池子的功能將已經(jīng)使用完的連接緩存起來,等到需要使用的時候,再取出來繼續(xù)使用。
代碼路徑tool/pool/pool.go,代碼量160行
池子結(jié)構(gòu)體定義
既然是池子,那定義的數(shù)據(jù)結(jié)構(gòu)里面肯定要有個緩沖的變量,這里就是
idles chan any一開始池子中肯定是沒有對象的,所以需要有個能夠創(chuàng)建對象的函數(shù)
newObject配套有個釋放對象的函數(shù)
freeObject池子中的對象不可能讓他無限的增多,當達到
activeCount個對象的時候,就不再繼續(xù)用newObject生成新對象,需要等之前的對象回收以后,才能獲取到對象(這里不理解往下繼續(xù)看)
type Pool struct {
Config
// 創(chuàng)建對象
newObject func() (any, error)
// 釋放對象
freeObject func(x any)
// 空閑對象池
idles chan any
mu sync.Mutex
activeCount int// 已經(jīng)創(chuàng)建的對象個數(shù)
waiting []chan any // 阻塞等待
closed bool// 是否已關(guān)閉
}
func NewPool(new func() (any, error), free func(x any), conf Config) *Pool {
ifnew == nil {
logger.Error("NewPool argument new func is nil")
returnnil
}
if free == nil {
free = func(x any) {}
}
p := Pool{
Config: conf,
newObject: new,
freeObject: free,
activeCount: 0,
closed: false,
}
p.idles = make(chan any, p.MaxIdles)
return &p
}從池子中獲取對象
p.mu.Lock()加鎖(race condition)- 從空閑緩沖
idles中獲取一個之前緩沖的對象 - 如果沒有獲取到就調(diào)用
p.getOne()新創(chuàng)建一個 - 在
func (p *Pool) getOne() (any, error)函數(shù)中,會判斷當前池子中是否(歷史上)已經(jīng)創(chuàng)建了足夠多的對象p.activeCount >= p.Config.MaxActive,那就不創(chuàng)建新對象,阻塞等待回收;否則調(diào)用newObject函數(shù)創(chuàng)建新對象
func (p *Pool) Get() (any, error) {
p.mu.Lock()
if p.closed {
p.mu.Unlock()
returnnil, ErrClosed
}
select {
case x := <-p.idles: // 從空閑中獲取
p.mu.Unlock() // 解鎖
return x, nil
default:
return p.getOne() // 獲取一個新的
}
}
func (p *Pool) getOne() (any, error) {
// 說明已經(jīng)創(chuàng)建了太多對象
if p.activeCount >= p.Config.MaxActive {
wait := make(chan any, 1)
p.waiting = append(p.waiting, wait)
p.mu.Unlock()
// 阻塞等待
x, ok := <-wait
if !ok {
returnnil, ErrClosed
}
return x, nil
}
p.activeCount++
p.mu.Unlock()
// 創(chuàng)建新對象
x, err := p.newObject()
if err != nil {
p.mu.Lock()
p.activeCount--
p.mu.Unlock()
returnnil, err
}
return x, nil
}池子對象回收
回收的過程就是對象緩存的過程,當然也要有個“度”
先加鎖
回收前先判斷是否有阻塞等待回收len(p.waiting) > 0,這里的邏輯和上面的等待阻塞邏輯對應(yīng)起來了
如果沒有阻塞等待的,那就直接將對象保存到緩沖中idles中
- 這里還有一個邏輯,緩沖有個大小限制(不可能無限的緩沖,多余不使用的對象,我們將它釋放了,占用內(nèi)存也沒啥意義)
func (p *Pool) Put(x any) {
p.mu.Lock()
if p.closed {
p.mu.Unlock()
p.freeObject(x) // 直接釋放
return
}
//1.先判斷等待中
iflen(p.waiting) > 0 {
// 彈出一個(從頭部)
wait := p.waiting[0]
temp := make([]chan any, len(p.waiting)-1)
copy(temp, p.waiting[1:])
p.waiting = temp
wait <- x // 取消阻塞
p.mu.Unlock()
return
}
// 2.直接放回空閑緩沖
select {
case p.idles <- x:
p.mu.Unlock()
default: // 說明空閑已滿
p.activeCount-- // 對象個數(shù)-1
p.mu.Unlock()
p.freeObject(x) // 釋放
}
}
再次封裝(socket連接池)

上面的代碼已經(jīng)完全實現(xiàn)了一個池子的功能;但是我們在實際使用的時候,每個ip地址對應(yīng)一個連接池,所以這里又增加了一個結(jié)構(gòu)體RedisConnPool,結(jié)合上面的池子功能,再配合之前的pipleline客戶端的功能,實現(xiàn)socket連接池。
代碼路徑:cluster/conn_pool.go代碼邏輯:
用一個字典key表示ip地址,value表示上面實現(xiàn)的池對象
GetConn獲取一個ip地址對應(yīng)的連接ReturnConn歸還連接到連接池中
type RedisConnPool struct {
connDict *dict.ConcurrentDict // addr -> *pool.Pool
}
func NewRedisConnPool() *RedisConnPool {
return &RedisConnPool{
connDict: dict.NewConcurrentDict(16),
}
}
func (r *RedisConnPool) GetConn(addr string) (*client.RedisClent, error) {
var connectionPool *pool.Pool // 對象池
// 通過不同的地址addr,獲取不同的對象池
raw, ok := r.connDict.Get(addr)
if ok {
connectionPool = raw.(*pool.Pool)
} else {
// 創(chuàng)建對象函數(shù)
newClient := func() (any, error) {
// redis的客戶端連接
cli, err := client.NewRedisClient(addr)
if err != nil {
returnnil, err
}
// 啟動
cli.Start()
if conf.GlobalConfig.RequirePass != "" { // 說明服務(wù)需要密碼
reply, err := cli.Send(aof.Auth([]byte(conf.GlobalConfig.RequirePass)))
if err != nil {
returnnil, err
}
if !protocol.IsOKReply(reply) {
returnnil, errors.New("auth failed:" + string(reply.ToBytes()))
}
return cli, nil
}
return cli, nil
}
// 釋放對象函數(shù)
freeClient := func(x any) {
cli, ok := x.(*client.RedisClent)
if ok {
cli.Stop() // 釋放
}
}
// 針對addr地址,創(chuàng)建一個新的對象池
connectionPool = pool.NewPool(newClient, freeClient, pool.Config{
MaxIdles: 1,
MaxActive: 20,
})
// addr -> *pool.Pool
r.connDict.Put(addr, connectionPool)
}
// 從對象池中獲取一個對象
raw, err := connectionPool.Get()
if err != nil {
returnnil, err
}
conn, ok := raw.(*client.RedisClent)
if !ok {
returnnil, errors.New("connection pool make wrong type")
}
return conn, nil
}
func (r *RedisConnPool) ReturnConn(peer string, cli *client.RedisClent) error {
raw, ok := r.connDict.Get(peer)
if !ok {
return errors.New("connection pool not found")
}
raw.(*pool.Pool).Put(cli)
returnnil
}以上就是Golang實現(xiàn)Redis之連接池的詳細內(nèi)容,更多關(guān)于Golang Redis連接池的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Go實現(xiàn)替換(覆蓋)文件某一行內(nèi)容的示例代碼
本文主要介紹了Go實現(xiàn)替換(覆蓋)文件某一行內(nèi)容的示例代碼,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07
Go語言for range(按照鍵值循環(huán))遍歷操作
這篇文章主要介紹了Go語言for range(按照鍵值循環(huán))遍歷操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-12-12
詳解Go開發(fā)Struct轉(zhuǎn)換成map兩種方式比較
本篇文章主要介紹了詳解Go開發(fā)Struct轉(zhuǎn)換成map兩種方式比較,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-07-07
Go結(jié)合反射將結(jié)構(gòu)體轉(zhuǎn)換成Excel的過程詳解
這篇文章主要介紹了Go結(jié)合反射將結(jié)構(gòu)體轉(zhuǎn)換成Excel的過程詳解,大概思路是在Go的結(jié)構(gòu)體中每個屬性打上一個excel標簽,利用反射獲取標簽中的內(nèi)容,作為表格的Header,需要的朋友可以參考下2022-06-06
一文教你如何快速學(xué)會Go的struct數(shù)據(jù)類型
結(jié)構(gòu)是表示字段集合的用戶定義類型。它可以用于將數(shù)據(jù)分組為單個單元而不是將每個數(shù)據(jù)作為單獨的值的地方。本文就來和大家聊聊Go中struct數(shù)據(jù)類型的使用,需要的可以參考一下2023-03-03

