├── .gitignore ├── README.md ├── pool.go └── pool_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## GRPC连接池管理 2 | 3 | Golang客户端连接池实现,主要功能: 4 | - grpc连接复用 5 | - 连接自动创建管理 6 | - idle连接自动关闭 7 | - 连接max-life-time到期自动关闭 -------------------------------------------------------------------------------- /pool.go: -------------------------------------------------------------------------------- 1 | package pool 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "sync" 7 | "sync/atomic" 8 | "time" 9 | 10 | "google.golang.org/grpc" 11 | ) 12 | 13 | // Pool 连接池 14 | type Pool struct { 15 | clients chan *Client 16 | connCnt int32 17 | cap int32 18 | idleDur time.Duration 19 | maxLifeDur time.Duration 20 | timeout time.Duration //pool关闭时使用 21 | factor Factor 22 | lock sync.RWMutex 23 | mode int 24 | } 25 | 26 | // Client 封装的gprc.ClientConn 27 | type Client struct { 28 | *grpc.ClientConn 29 | timeUsed time.Time 30 | timeInit time.Time 31 | pool *Pool 32 | } 33 | 34 | // Factor grpc连接工厂方法 35 | type Factor func() (*grpc.ClientConn, error) 36 | 37 | var ( 38 | // ErrPoolInit 连接池初始化出错 39 | ErrPoolInit = errors.New("Pool init occurred error") 40 | // ErrGetTimeout 获取连接超时 41 | ErrGetTimeout = errors.New("Getting connection client timeout from pool") 42 | // ErrDialConn 创建连接发生错误 43 | ErrDialConn = errors.New("Dialing connection occurred error") 44 | // ErrPoolIsClosed 连接池已关闭 45 | ErrPoolIsClosed = errors.New("Pool is closed") 46 | ) 47 | 48 | const ( 49 | // PoolGetModeStrict 在实际创建连接数达上限后,池子中没有连接时不会新建连接 50 | PoolGetModeStrict = iota 51 | // PoolGetModeLoose 在实际创建连接数达上限后,池子中没有连接时会新建连接 52 | PoolGetModeLoose 53 | ) 54 | 55 | // Default 初始化默认连接池 56 | // idle: 10s 57 | // max life time: 60s 58 | // timeout: 10s 59 | // mode: PoolGetModeLoose 60 | func Default(factor Factor, init, cap int32) (*Pool, error) { 61 | return Init(factor, init, cap, 10*time.Second, 60*time.Second, 10*time.Second, PoolGetModeLoose) 62 | } 63 | 64 | // Init 初始化连接池 65 | func Init(factor Factor, init, cap int32, idleDur, maxLifeDur, timeout time.Duration, mode int) (*Pool, error) { 66 | // 参数验证 67 | if factor == nil { 68 | return nil, ErrPoolInit 69 | } 70 | if init < 0 || cap <= 0 || idleDur < 0 || maxLifeDur < 0 { 71 | return nil, ErrPoolInit 72 | } 73 | // init pool 74 | if init > cap { 75 | init = cap 76 | } 77 | pool := &Pool{ 78 | clients: make(chan *Client, cap), 79 | cap: cap, 80 | idleDur: idleDur, 81 | maxLifeDur: maxLifeDur, 82 | timeout: timeout, 83 | factor: factor, 84 | mode: mode, 85 | } 86 | // init client 87 | for i := int32(0); i < init; i++ { 88 | client, err := pool.createClient() 89 | if err != nil { 90 | return nil, ErrPoolInit 91 | } 92 | pool.clients <- client 93 | } 94 | return pool, nil 95 | } 96 | 97 | func (pool *Pool) createClient() (*Client, error) { 98 | conn, err := pool.factor() 99 | if err != nil { 100 | return nil, ErrPoolInit 101 | } 102 | now := time.Now() 103 | client := &Client{ 104 | ClientConn: conn, 105 | timeUsed: now, 106 | timeInit: now, 107 | pool: pool, 108 | } 109 | atomic.AddInt32(&pool.connCnt, 1) 110 | return client, nil 111 | } 112 | 113 | // Get 从连接池取出一个连接 114 | func (pool *Pool) Get(ctx context.Context) (*Client, error) { 115 | if pool.IsClose() { 116 | return nil, ErrPoolIsClosed 117 | } 118 | 119 | var client *Client 120 | now := time.Now() 121 | select { 122 | case <-ctx.Done(): 123 | if pool.mode == PoolGetModeStrict { 124 | pool.lock.Lock() 125 | defer pool.lock.Unlock() 126 | 127 | var err error 128 | if pool.connCnt >= int32(pool.cap) { 129 | err = ErrGetTimeout 130 | } else { 131 | client, err = pool.createClient() 132 | } 133 | return client, err 134 | } 135 | case client = <-pool.clients: 136 | if client != nil && pool.idleDur > 0 && client.timeUsed.Add(pool.idleDur).After(now) { 137 | client.timeUsed = now 138 | return client, nil 139 | } 140 | } 141 | // 如果连接已经是idle连接,或者是非严格模式下没有获取连接 142 | // 则新建一个连接同时销毁原有idle连接 143 | if client != nil { 144 | client.Destory() 145 | } 146 | client, err := pool.createClient() 147 | if err != nil { 148 | return nil, err 149 | } 150 | return client, nil 151 | } 152 | 153 | // Close 连接池关闭 154 | func (pool *Pool) Close() { 155 | pool.lock.Lock() 156 | defer pool.lock.Unlock() 157 | 158 | if pool.IsClose() { 159 | return 160 | } 161 | 162 | clients := pool.clients 163 | pool.clients = nil 164 | 165 | // 异步处理池子里的连接 166 | go func() { 167 | for { 168 | select { 169 | case client := <-clients: 170 | if client != nil { 171 | client.Destory() 172 | } 173 | case <-time.Tick(pool.timeout): 174 | if len(clients) <= 0 { 175 | close(clients) 176 | break 177 | } 178 | } 179 | } 180 | }() 181 | } 182 | 183 | // IsClose 连接池是否关闭 184 | func (pool *Pool) IsClose() bool { 185 | return pool == nil || pool.clients == nil 186 | } 187 | 188 | // Size 连接池中连接数 189 | func (pool *Pool) Size() int { 190 | pool.lock.RLock() 191 | defer pool.lock.RUnlock() 192 | 193 | return len(pool.clients) 194 | } 195 | 196 | // ConnCnt 实际连接数 197 | func (pool *Pool) ConnCnt() int32 { 198 | return pool.connCnt 199 | } 200 | 201 | // Close 连接关闭 202 | func (client *Client) Close() { 203 | go func() { 204 | pool := client.pool 205 | now := time.Now() 206 | // 连接池关闭了直接销毁 207 | if pool.IsClose() { 208 | client.Destory() 209 | return 210 | } 211 | // 如果连接存活时间超长也直接销毁连接 212 | if pool.maxLifeDur > 0 && client.timeInit.Add(pool.maxLifeDur).Before(now) { 213 | client.Destory() 214 | return 215 | } 216 | if client.ClientConn == nil { 217 | return 218 | } 219 | client.timeUsed = now 220 | client.pool.clients <- client 221 | }() 222 | } 223 | 224 | // Destory 销毁client 225 | func (client *Client) Destory() { 226 | if client.ClientConn != nil { 227 | client.ClientConn.Close() 228 | atomic.AddInt32(&client.pool.connCnt, -1) 229 | } 230 | client.ClientConn = nil 231 | client.pool = nil 232 | } 233 | 234 | // TimeInit 获取连接创建时间 235 | func (client *Client) TimeInit() time.Time { 236 | return client.timeInit 237 | } 238 | 239 | // TimeUsed 获取连接上一次使用时间 240 | func (client *Client) TimeUsed() time.Time { 241 | return client.timeUsed 242 | } 243 | -------------------------------------------------------------------------------- /pool_test.go: -------------------------------------------------------------------------------- 1 | package pool_test 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | "time" 7 | 8 | pool "github.com/xttty/client-pool" 9 | "google.golang.org/grpc" 10 | ) 11 | 12 | func TestPool(t *testing.T) { 13 | pool, err := pool.Init(factor, 5, 8, 3*time.Second, 10*time.Second, time.Second, pool.PoolGetModeLoose) 14 | if err != nil { 15 | t.Log(err) 16 | } 17 | now := time.Now() 18 | for i := 0; i < 20; i++ { 19 | time.Sleep(50 * time.Millisecond) 20 | go func(idx int) { 21 | ctx, cancel := context.WithTimeout(context.Background(), time.Second) 22 | defer cancel() 23 | conn, err := pool.Get(ctx) 24 | if err != nil { 25 | t.Logf("connection occurred error: %s, pool size: %d\n, pool conn cnt: %d", err.Error(), pool.Size(), pool.ConnCnt()) 26 | return 27 | } 28 | defer conn.Close() 29 | 30 | t.Logf("idx: %d, conn init: %d, conn last used: %d, cnt: %d, size: %d", idx, conn.TimeInit().Nanosecond()-now.Nanosecond(), conn.TimeUsed().Nanosecond()-now.Nanosecond(), pool.ConnCnt(), pool.Size()) 31 | time.Sleep(500 * time.Millisecond) 32 | }(i) 33 | } 34 | time.Sleep(5 * time.Second) 35 | t.Log("-------------------") 36 | for i := 0; i < 20; i++ { 37 | time.Sleep(50 * time.Millisecond) 38 | go func(idx int) { 39 | ctx, cancel := context.WithTimeout(context.Background(), time.Second) 40 | defer cancel() 41 | conn, err := pool.Get(ctx) 42 | if err != nil { 43 | t.Logf("connection occurred error: %s, pool size: %d\n, pool conn cnt: %d", err.Error(), pool.Size(), pool.ConnCnt()) 44 | return 45 | } 46 | defer conn.Close() 47 | 48 | t.Logf("idx: %d, conn init: %d, conn last used: %d, cnt: %d, size: %d", idx, conn.TimeInit().Nanosecond()-now.Nanosecond(), conn.TimeUsed().Nanosecond()-now.Nanosecond(), pool.ConnCnt(), pool.Size()) 49 | time.Sleep(500 * time.Millisecond) 50 | }(i) 51 | } 52 | time.Sleep(5 * time.Second) 53 | pool.Close() 54 | time.Sleep(time.Second) 55 | t.Logf("pool closed: %v, pool conn cnt: %d", pool.IsClose(), pool.ConnCnt()) 56 | } 57 | 58 | func factor() (*grpc.ClientConn, error) { 59 | return grpc.Dial("127.0.0.1:8080", grpc.WithInsecure()) 60 | } 61 | --------------------------------------------------------------------------------