github.com/cloudwego/kitex@v0.9.0/pkg/remote/trans/nphttp2/conn_pool.go (about) 1 /* 2 * Copyright 2021 CloudWeGo Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package nphttp2 18 19 import ( 20 "context" 21 "crypto/tls" 22 "net" 23 "runtime" 24 "sync" 25 "sync/atomic" 26 "time" 27 28 "golang.org/x/sync/singleflight" 29 30 "github.com/cloudwego/kitex/pkg/klog" 31 "github.com/cloudwego/kitex/pkg/remote" 32 "github.com/cloudwego/kitex/pkg/remote/trans/nphttp2/grpc" 33 ) 34 35 var _ remote.LongConnPool = &connPool{} 36 37 func poolSize() uint32 { 38 // One connection per processor, and need redundancy。 39 // Benchmark indicates this setting is the best performance. 40 numP := runtime.GOMAXPROCS(0) 41 return uint32(numP * 3 / 2) 42 } 43 44 // NewConnPool ... 45 func NewConnPool(remoteService string, size uint32, connOpts grpc.ConnectOptions) *connPool { 46 if size == 0 { 47 size = poolSize() 48 } 49 return &connPool{ 50 remoteService: remoteService, 51 size: size, 52 connOpts: connOpts, 53 } 54 } 55 56 // MuxPool manages a pool of long connections. 57 type connPool struct { 58 size uint32 59 sfg singleflight.Group 60 conns sync.Map // key: address, value: *transports 61 remoteService string // remote service name 62 connOpts grpc.ConnectOptions 63 } 64 65 type transports struct { 66 index uint32 67 size uint32 68 cliTransports []grpc.ClientTransport 69 } 70 71 // get connection from the pool, load balance with round-robin. 72 func (t *transports) get() grpc.ClientTransport { 73 idx := atomic.AddUint32(&t.index, 1) 74 return t.cliTransports[idx%t.size] 75 } 76 77 // put find the first empty position to put the connection to the pool. 78 func (t *transports) put(trans grpc.ClientTransport) { 79 for i := 0; i < int(t.size); i++ { 80 cliTransport := t.cliTransports[i] 81 if cliTransport == nil { 82 t.cliTransports[i] = trans 83 return 84 } 85 if !cliTransport.(grpc.IsActive).IsActive() { 86 t.cliTransports[i].GracefulClose() 87 t.cliTransports[i] = trans 88 return 89 } 90 } 91 } 92 93 // close all connections of the pool. 94 func (t *transports) close() { 95 for i := range t.cliTransports { 96 if c := t.cliTransports[i]; c != nil { 97 c.GracefulClose() 98 } 99 } 100 } 101 102 var _ remote.LongConnPool = (*connPool)(nil) 103 104 func (p *connPool) newTransport(ctx context.Context, dialer remote.Dialer, network, address string, 105 connectTimeout time.Duration, opts grpc.ConnectOptions, 106 ) (grpc.ClientTransport, error) { 107 conn, err := dialer.DialTimeout(network, address, connectTimeout) 108 if err != nil { 109 return nil, err 110 } 111 if opts.TLSConfig != nil { 112 tlsConn, err := newTLSConn(conn, opts.TLSConfig) 113 if err != nil { 114 return nil, err 115 } 116 conn = tlsConn 117 } 118 return grpc.NewClientTransport( 119 ctx, 120 conn, 121 opts, 122 p.remoteService, 123 func(grpc.GoAwayReason) { 124 // do nothing 125 }, 126 func() { 127 // do nothing 128 }, 129 ) 130 } 131 132 // Get pick or generate a net.Conn and return 133 func (p *connPool) Get(ctx context.Context, network, address string, opt remote.ConnOption) (net.Conn, error) { 134 if p.connOpts.ShortConn { 135 return p.createShortConn(ctx, network, address, opt) 136 } 137 138 var ( 139 trans *transports 140 conn *clientConn 141 err error 142 ) 143 144 v, ok := p.conns.Load(address) 145 if ok { 146 trans = v.(*transports) 147 if tr := trans.get(); tr != nil { 148 if tr.(grpc.IsActive).IsActive() { 149 // Actually new a stream, reuse the connection (grpc.ClientTransport) 150 conn, err = newClientConn(ctx, tr, address) 151 if err == nil { 152 return conn, nil 153 } 154 klog.CtxDebugf(ctx, "KITEX: New grpc stream failed, network=%s, address=%s, error=%s", network, address, err.Error()) 155 } 156 } 157 } 158 tr, err, _ := p.sfg.Do(address, func() (i interface{}, e error) { 159 // Notice: newTransport means new a connection, the timeout of connection cannot be set, 160 // so using context.Background() but not the ctx passed in as the parameter. 161 tr, err := p.newTransport(context.Background(), opt.Dialer, network, address, opt.ConnectTimeout, p.connOpts) 162 if err != nil { 163 return nil, err 164 } 165 if trans == nil { 166 trans = &transports{ 167 size: p.size, 168 cliTransports: make([]grpc.ClientTransport, p.size), 169 } 170 } 171 trans.put(tr) // the tr (connection) maybe not in the pool, but can be recycled by keepalive. 172 p.conns.Store(address, trans) 173 return tr, nil 174 }) 175 if err != nil { 176 klog.CtxErrorf(ctx, "KITEX: New grpc client connection failed, network=%s, address=%s, error=%s", network, address, err.Error()) 177 return nil, err 178 } 179 klog.CtxDebugf(ctx, "KITEX: New grpc client connection succeed, network=%s, address=%s", network, address) 180 return newClientConn(ctx, tr.(grpc.ClientTransport), address) 181 } 182 183 // Put implements the ConnPool interface. 184 func (p *connPool) Put(conn net.Conn) error { 185 if p.connOpts.ShortConn { 186 return p.release(conn) 187 } 188 return nil 189 } 190 191 func (p *connPool) release(conn net.Conn) error { 192 clientConn := conn.(*clientConn) 193 clientConn.tr.GracefulClose() 194 return nil 195 } 196 197 func (p *connPool) createShortConn(ctx context.Context, network, address string, opt remote.ConnOption) (net.Conn, error) { 198 // Notice: newTransport means new a connection, the timeout of connection cannot be set, 199 // so using context.Background() but not the ctx passed in as the parameter. 200 tr, err := p.newTransport(context.Background(), opt.Dialer, network, address, opt.ConnectTimeout, p.connOpts) 201 if err != nil { 202 return nil, err 203 } 204 return newClientConn(ctx, tr, address) 205 } 206 207 // Discard implements the ConnPool interface. 208 func (p *connPool) Discard(conn net.Conn) error { 209 return nil 210 } 211 212 // Clean implements the LongConnPool interface. 213 func (p *connPool) Clean(network, address string) { 214 if v, ok := p.conns.Load(address); ok { 215 p.conns.Delete(address) 216 v.(*transports).close() 217 } 218 } 219 220 // Close is to release resource of ConnPool, it is executed when client is closed. 221 func (p *connPool) Close() error { 222 p.conns.Range(func(addr, trans interface{}) bool { 223 p.conns.Delete(addr) 224 trans.(*transports).close() 225 return true 226 }) 227 return nil 228 } 229 230 // newTLSConn constructs a client-side TLS connection and performs handshake. 231 func newTLSConn(conn net.Conn, tlsCfg *tls.Config) (net.Conn, error) { 232 tlsConn := tls.Client(conn, tlsCfg) 233 if err := tlsConn.Handshake(); err != nil { 234 return nil, err 235 } 236 return tlsConn, nil 237 }