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  }