源码 连接池 设计
问题:GRPC应用中是否必要建立连接池
实践:
import ( "LabGO/utils" "fmt" "runtime" "sync" "time" "google.golang.org/grpc" "google.golang.org/grpc/encoding/gzip" ) var ConnPoolSize *int // 连接池最大可用连接数 var ConnPool []*grpc.ClientConn // 连接池 var ConnPoolThisInUse []bool // 连接当前使用状态 var ConnPoolLock sync.Mutex // 锁 // 在连接池中填入可用连接 func InitConnPool() { ConnPoolSize = &conf.Config.GrpcConnPoolSize // 从环境配置中读取连接池大小 ConnPool = make([]*grpc.ClientConn, *ConnPoolSize) ConnPoolThisInUse = make([]bool, *ConnPoolSize) for i := 0; i < *ConnPoolSize; i++ { c, err := NewConn() if err != nil { // TODO defer func() { log.SRELog.Error(err) }() continue } ConnPool[i] = c } } // 检查指定连接是否可用 func chekcConnState(c *grpc.ClientConn) bool { defer func() { if e := recover(); e != nil { var buf = make([]byte, 8192) runtime.Stack(buf, true) err := fmt.Errorf("recover: %v Stack:%v", e, string(buf)) log.SRELog.Error(err) } log.SRELog.Info("Leave chekcConnState") }() if c == nil { return false } switch c.GetState().String() { case "IDLE", "CONNECTING", "READY": return true case "TRANSIENT_FAILURE", "SHUTDOWN": err := c.Close() if err != nil { log.SRELog.Error(err) } return false default: err := c.Close() if err != nil { log.SRELog.Error(err) } return false } } // 维持连接池中的连接可用 func CheckConnPool() { log.SRELog.Info("IN CheckConnPool", " ", len(ConnPool)) for i, c := range ConnPool { if !chekcConnState(c) { ConnPoolLock.Lock() c1, err := NewConn() if err != nil { // TODO defer func() { log.SRELog.Error(err) }() ConnPoolLock.Unlock() continue } ConnPool[i] = c1 ConnPoolLock.Unlock() } } } // 阻塞式获取可用连接 func GetAConnWithBlock() (*grpc.ClientConn, int) { defer func() { if e := recover(); e != nil { var buf = make([]byte, 8192) runtime.Stack(buf, true) err := fmt.Errorf("recover: %v Stack:%v", e, string(buf)) log.SRELog.Error(err) } log.SRELog.Info("Leave GetAConnWithBlock") }() log.SRELog.Info("IN GetAConnWithBlock ", len(ConnPool)) for { for i, c := range ConnPool { if chekcConnState(c) && !ConnPoolThisInUse[i] { ConnPoolLock.Lock() ConnPoolThisInUse[i] = true ConnPoolLock.Unlock() return c, i } } time.Sleep(time.Second) } } // 获取可用连接,返回grpc客户端 func NewCli() (pb.CISClient, int) { c, i := GetAConnWithBlock() log.SRELog.Info("NewCli", c) return NewClient(c), i } // 生成连接 func NewConn() (*grpc.ClientConn, error) { // TODO var opts = func() []grpc.DialOption { return opts }() var target = utils.ConcatStr(conf.Config.SrvAddr, ":50053") grpc.UseCompressor(gzip.Name) return grpc.Dial(target, opts...) } // 生成客户端 func NewClient(conn *grpc.ClientConn) pb.CISClient { return pb.NewCISClient(conn) } // 调用,获取可用客户端 gCli, idC := server.NewCli() defer func() { server.ConnPoolLock.Lock() server.ConnPoolThisInUse[idC] = false server.ConnPoolLock.Unlock() }() req, err := gCli.TestMethod(ctx, &pb.TestMethodReq{...})
从可用连接池中取出最后一个,设置过期时间、标记为使用状态、连接吃减少一个连接;
freeConn []*driverConn // free connections ordered by returnedAt oldest to newest 最旧的
如果其中没有
// Prefer a free connection, if possible.
last := len(db.freeConn) - 1
if strategy == cachedOrNewConn && last >= 0 {
// Reuse the lowest idle time connection so we can close
// connections which remain idle as soon as possible.
conn := db.freeConn[last]
db.freeConn = db.freeConn[:last]
conn.inUse = true
if conn.expired(lifetime) {
db.maxLifetimeClosed++
db.mu.Unlock()
conn.Close()
return nil, driver.ErrBadConn
}
db.mu.Unlock()
Go\src\database\sql\sql.go
// conn returns a newly-opened or cached *driverConn. func (db *DB) conn(ctx context.Context, strategy connReuseStrategy) (*driverConn, error) { db.mu.Lock() if db.closed { db.mu.Unlock() return nil, errDBClosed } // Check if the context is expired. select { default: case <-ctx.Done(): db.mu.Unlock() return nil, ctx.Err() } lifetime := db.maxLifetime // Prefer a free connection, if possible. last := len(db.freeConn) - 1 if strategy == cachedOrNewConn && last >= 0 { // Reuse the lowest idle time connection so we can close // connections which remain idle as soon as possible. conn := db.freeConn[last] db.freeConn = db.freeConn[:last] conn.inUse = true if conn.expired(lifetime) { db.maxLifetimeClosed++ db.mu.Unlock() conn.Close() return nil, driver.ErrBadConn } db.mu.Unlock() // Reset the session if required. if err := conn.resetSession(ctx); errors.Is(err, driver.ErrBadConn) { conn.Close() return nil, err } return conn, nil } // Out of free connections or we were asked not to use one. If we're not // allowed to open any more connections, make a request and wait. if db.maxOpen > 0 && db.numOpen >= db.maxOpen { // Make the connRequest channel. It's buffered so that the // connectionOpener doesn't block while waiting for the req to be read. req := make(chan connRequest, 1) reqKey := db.nextRequestKeyLocked() db.connRequests[reqKey] = req db.waitCount++ db.mu.Unlock() waitStart := nowFunc() // Timeout the connection request with the context. select { case <-ctx.Done(): // Remove the connection request and ensure no value has been sent // on it after removing. db.mu.Lock() delete(db.connRequests, reqKey) db.mu.Unlock() atomic.AddInt64(&db.waitDuration, int64(time.Since(waitStart))) select { default: case ret, ok := <-req: if ok && ret.conn != nil { db.putConn(ret.conn, ret.err, false) } } return nil, ctx.Err() case ret, ok := <-req: atomic.AddInt64(&db.waitDuration, int64(time.Since(waitStart))) if !ok { return nil, errDBClosed } // Only check if the connection is expired if the strategy is cachedOrNewConns. // If we require a new connection, just re-use the connection without looking // at the expiry time. If it is expired, it will be checked when it is placed // back into the connection pool. // This prioritizes giving a valid connection to a client over the exact connection // lifetime, which could expire exactly after this point anyway. if strategy == cachedOrNewConn && ret.err == nil && ret.conn.expired(lifetime) { db.mu.Lock() db.maxLifetimeClosed++ db.mu.Unlock() ret.conn.Close() return nil, driver.ErrBadConn } if ret.conn == nil { return nil, ret.err } // Reset the session if required. if err := ret.conn.resetSession(ctx); errors.Is(err, driver.ErrBadConn) { ret.conn.Close() return nil, err } return ret.conn, ret.err } } db.numOpen++ // optimistically db.mu.Unlock() ci, err := db.connector.Connect(ctx) if err != nil { db.mu.Lock() db.numOpen-- // correct for earlier optimism db.maybeOpenNewConnections() db.mu.Unlock() return nil, err } db.mu.Lock() dc := &driverConn{ db: db, createdAt: nowFunc(), returnedAt: nowFunc(), ci: ci, inUse: true, } db.addDepLocked(dc, dc) db.mu.Unlock() return dc, nil }
// driverConn wraps a driver.Conn with a mutex, to // be held during all calls into the Conn. (including any calls onto // interfaces returned via that Conn, such as calls on Tx, Stmt, // Result, Rows) type driverConn struct { db *DB createdAt time.Time sync.Mutex // guards following ci driver.Conn needReset bool // The connection session should be reset before use if true. closed bool finalClosed bool // ci.Close has been called openStmt map[*driverStmt]bool // guarded by db.mu inUse bool returnedAt time.Time // Time the connection was created or returned. onPut []func() // code (with db.mu held) run when conn is next returned dbmuClosed bool // same as closed, but guarded by db.mu, for removeClosedStmtLocked }
// putConn adds a connection to the db's free pool. // err is optionally the last error that occurred on this connection. func (db *DB) putConn(dc *driverConn, err error, resetSession bool) { if !errors.Is(err, driver.ErrBadConn) { if !dc.validateConnection(resetSession) { err = driver.ErrBadConn } } db.mu.Lock() if !dc.inUse { db.mu.Unlock() if debugGetPut { fmt.Printf("putConn(%v) DUPLICATE was: %s\n\nPREVIOUS was: %s", dc, stack(), db.lastPut[dc]) } panic("sql: connection returned that was never out") } if !errors.Is(err, driver.ErrBadConn) && dc.expired(db.maxLifetime) { db.maxLifetimeClosed++ err = driver.ErrBadConn } if debugGetPut { db.lastPut[dc] = stack() } dc.inUse = false dc.returnedAt = nowFunc() for _, fn := range dc.onPut { fn() } dc.onPut = nil if errors.Is(err, driver.ErrBadConn) { // Don't reuse bad connections. // Since the conn is considered bad and is being discarded, treat it // as closed. Don't decrement the open count here, finalClose will // take care of that. db.maybeOpenNewConnections() db.mu.Unlock() dc.Close() return } if putConnHook != nil { putConnHook(db, dc) } added := db.putConnDBLocked(dc, nil) db.mu.Unlock() if !added { dc.Close() return } } // Satisfy a connRequest or put the driverConn in the idle pool and return true // or return false. // putConnDBLocked will satisfy a connRequest if there is one, or it will // return the *driverConn to the freeConn list if err == nil and the idle // connection limit will not be exceeded. // If err != nil, the value of dc is ignored. // If err == nil, then dc must not equal nil. // If a connRequest was fulfilled or the *driverConn was placed in the // freeConn list, then true is returned, otherwise false is returned. func (db *DB) putConnDBLocked(dc *driverConn, err error) bool { if db.closed { return false } if db.maxOpen > 0 && db.numOpen > db.maxOpen { return false } if c := len(db.connRequests); c > 0 { var req chan connRequest var reqKey uint64 for reqKey, req = range db.connRequests { break } delete(db.connRequests, reqKey) // Remove from pending requests. if err == nil { dc.inUse = true } req <- connRequest{ conn: dc, err: err, } return true } else if err == nil && !db.closed { if db.maxIdleConnsLocked() > len(db.freeConn) { db.freeConn = append(db.freeConn, dc) db.startCleanerLocked() return true } db.maxIdleClosed++ } return false }