github.com/matrixorigin/matrixone@v1.2.0/pkg/common/morpc/client.go (about)

     1  // Copyright 2021 - 2022 Matrix Origin
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package morpc
    16  
    17  import (
    18  	"context"
    19  	"sync"
    20  	"sync/atomic"
    21  	"time"
    22  
    23  	"github.com/matrixorigin/matrixone/pkg/common/moerr"
    24  	"github.com/matrixorigin/matrixone/pkg/common/stopper"
    25  	"github.com/matrixorigin/matrixone/pkg/logutil"
    26  	v2 "github.com/matrixorigin/matrixone/pkg/util/metric/v2"
    27  	"go.uber.org/zap"
    28  )
    29  
    30  // WithClientMaxBackendPerHost maximum number of connections per host
    31  func WithClientMaxBackendPerHost(maxBackendsPerHost int) ClientOption {
    32  	return func(c *client) {
    33  		c.options.maxBackendsPerHost = maxBackendsPerHost
    34  	}
    35  }
    36  
    37  // WithClientLogger set client logger
    38  func WithClientLogger(logger *zap.Logger) ClientOption {
    39  	return func(c *client) {
    40  		c.logger = logger
    41  	}
    42  }
    43  
    44  // WithClientInitBackends set the number of connections for the initialized backends.
    45  func WithClientInitBackends(backends []string, counts []int) ClientOption {
    46  	return func(c *client) {
    47  		if len(backends) != len(counts) {
    48  			panic("backend and count mismatch")
    49  		}
    50  
    51  		c.options.initBackends = backends
    52  		c.options.initBackendCounts = counts
    53  	}
    54  }
    55  
    56  // WithClientCreateTaskChanSize set the buffer size of the chan that creates the Backend Task.
    57  func WithClientCreateTaskChanSize(size int) ClientOption {
    58  	return func(c *client) {
    59  		c.createC = make(chan string, size)
    60  	}
    61  }
    62  
    63  // WithClientMaxBackendMaxIdleDuration set the maximum idle duration of the backend connection.
    64  // Backend connection that exceed this time will be automatically closed. 0 means no idle time
    65  // limit.
    66  func WithClientMaxBackendMaxIdleDuration(value time.Duration) ClientOption {
    67  	return func(c *client) {
    68  		c.options.maxIdleDuration = value
    69  	}
    70  }
    71  
    72  // WithClientEnableAutoCreateBackend enable client to automatically create a backend
    73  // in the background, when the links in the connection pool are used, if the pool has
    74  // not reached the maximum number of links, it will automatically create them in the
    75  // background to improve the latency of link creation.
    76  func WithClientEnableAutoCreateBackend() ClientOption {
    77  	return func(c *client) {
    78  		c.options.enableAutoCreate = true
    79  	}
    80  }
    81  
    82  type client struct {
    83  	name        string
    84  	metrics     *metrics
    85  	logger      *zap.Logger
    86  	stopper     *stopper.Stopper
    87  	factory     BackendFactory
    88  	createC     chan string
    89  	gcInactiveC chan string
    90  
    91  	mu struct {
    92  		sync.Mutex
    93  		closed   bool
    94  		backends map[string][]Backend
    95  		ops      map[string]*op
    96  	}
    97  
    98  	options struct {
    99  		maxBackendsPerHost int
   100  		maxIdleDuration    time.Duration
   101  		initBackends       []string
   102  		initBackendCounts  []int
   103  		enableAutoCreate   bool
   104  	}
   105  }
   106  
   107  // NewClient create rpc client with options
   108  func NewClient(
   109  	name string,
   110  	factory BackendFactory,
   111  	options ...ClientOption) (RPCClient, error) {
   112  	v2.RPCClientCreateCounter.WithLabelValues(name).Inc()
   113  	c := &client{
   114  		name:        name,
   115  		metrics:     newMetrics(name),
   116  		factory:     factory,
   117  		gcInactiveC: make(chan string),
   118  	}
   119  	c.mu.backends = make(map[string][]Backend)
   120  	c.mu.ops = make(map[string]*op)
   121  
   122  	for _, opt := range options {
   123  		opt(c)
   124  	}
   125  	c.adjust()
   126  	c.stopper = stopper.NewStopper(c.name, stopper.WithLogger(c.logger))
   127  
   128  	if err := c.maybeInitBackends(); err != nil {
   129  		c.Close()
   130  		return nil, err
   131  	}
   132  
   133  	if err := c.stopper.RunTask(c.createTask); err != nil {
   134  		return nil, err
   135  	}
   136  	if c.options.maxIdleDuration > 0 {
   137  		if err := c.stopper.RunTask(c.gcIdleTask); err != nil {
   138  			return nil, err
   139  		}
   140  	}
   141  	if err := c.stopper.RunTask(c.gcInactiveTask); err != nil {
   142  		return nil, err
   143  	}
   144  	return c, nil
   145  }
   146  
   147  func (c *client) adjust() {
   148  	c.logger = logutil.Adjust(c.logger).Named(c.name)
   149  	if c.createC == nil {
   150  		c.createC = make(chan string, 16)
   151  	}
   152  	if c.options.maxBackendsPerHost == 0 {
   153  		c.options.maxBackendsPerHost = 1
   154  	}
   155  	if len(c.options.initBackendCounts) > 0 {
   156  		for _, cnt := range c.options.initBackendCounts {
   157  			if cnt > c.options.maxBackendsPerHost {
   158  				c.options.maxBackendsPerHost = cnt
   159  			}
   160  		}
   161  	}
   162  	if c.options.maxIdleDuration == 0 {
   163  		c.options.maxIdleDuration = defaultMaxIdleDuration
   164  	}
   165  }
   166  
   167  func (c *client) maybeInitBackends() error {
   168  	c.mu.Lock()
   169  	defer c.mu.Unlock()
   170  	if len(c.options.initBackends) > 0 {
   171  		for idx, backend := range c.options.initBackends {
   172  			for i := 0; i < c.options.initBackendCounts[idx]; i++ {
   173  				_, err := c.createBackendLocked(backend)
   174  				if err != nil {
   175  					return err
   176  				}
   177  			}
   178  		}
   179  	}
   180  	return nil
   181  }
   182  
   183  func (c *client) Send(ctx context.Context, backend string, request Message) (*Future, error) {
   184  	if backend == "" {
   185  		return nil, moerr.NewBackendCannotConnectNoCtx()
   186  	}
   187  
   188  	if ctx == nil {
   189  		panic("client Send nil context")
   190  	}
   191  	for {
   192  		b, err := c.getBackend(backend, false)
   193  		if err != nil {
   194  			return nil, err
   195  		}
   196  
   197  		f, err := b.Send(ctx, request)
   198  		if err != nil && err == backendClosed {
   199  			continue
   200  		}
   201  		return f, err
   202  	}
   203  }
   204  
   205  func (c *client) NewStream(backend string, lock bool) (Stream, error) {
   206  	for {
   207  		b, err := c.getBackend(backend, lock)
   208  		if err != nil {
   209  			return nil, err
   210  		}
   211  
   212  		st, err := b.NewStream(lock)
   213  		if err != nil && err == backendClosed {
   214  			continue
   215  		}
   216  		return st, err
   217  	}
   218  }
   219  
   220  func (c *client) Ping(ctx context.Context, backend string) error {
   221  	if ctx == nil {
   222  		panic("client Ping nil context")
   223  	}
   224  	for {
   225  		b, err := c.getBackend(backend, false)
   226  		if err != nil {
   227  			return err
   228  		}
   229  
   230  		f, err := b.SendInternal(ctx, &flagOnlyMessage{flag: flagPing})
   231  		if err != nil {
   232  			if err == backendClosed {
   233  				continue
   234  			}
   235  			return err
   236  		}
   237  		_, err = f.Get()
   238  		f.Close()
   239  		return err
   240  	}
   241  }
   242  
   243  func (c *client) Close() error {
   244  	c.mu.Lock()
   245  	if c.mu.closed {
   246  		c.mu.Unlock()
   247  		return nil
   248  	}
   249  	c.mu.closed = true
   250  
   251  	for _, backends := range c.mu.backends {
   252  		for _, b := range backends {
   253  			b.Close()
   254  		}
   255  	}
   256  	c.mu.Unlock()
   257  
   258  	c.stopper.Stop()
   259  	close(c.createC)
   260  	return nil
   261  }
   262  
   263  func (c *client) CloseBackend() error {
   264  	c.mu.Lock()
   265  	defer c.mu.Unlock()
   266  	for _, backends := range c.mu.backends {
   267  		for _, b := range backends {
   268  			b.Close()
   269  		}
   270  	}
   271  	return nil
   272  }
   273  
   274  func (c *client) getBackend(backend string, lock bool) (Backend, error) {
   275  	c.mu.Lock()
   276  	b, err := c.getBackendLocked(backend, lock)
   277  	if err != nil {
   278  		c.mu.Unlock()
   279  		return nil, err
   280  	}
   281  	if b != nil {
   282  		c.mu.Unlock()
   283  		return b, nil
   284  	}
   285  	c.mu.Unlock()
   286  
   287  	return c.createBackend(backend, lock)
   288  }
   289  
   290  func (c *client) getBackendLocked(backend string, lock bool) (Backend, error) {
   291  	if c.mu.closed {
   292  		return nil, moerr.NewClientClosedNoCtx()
   293  	}
   294  	defer func() {
   295  		n := 0
   296  		for _, backends := range c.mu.backends {
   297  			n += len(backends)
   298  		}
   299  		c.metrics.poolSizeGauge.Set(float64(n))
   300  	}()
   301  
   302  	lockedCnt := 0
   303  	inactiveCnt := 0
   304  	if backends, ok := c.mu.backends[backend]; ok {
   305  		n := uint64(len(backends))
   306  		var b Backend
   307  		for i := uint64(0); i < n; i++ {
   308  			seq := c.mu.ops[backend].next()
   309  			b = backends[seq%n]
   310  			if !b.Locked() && b.LastActiveTime() != (time.Time{}) {
   311  				break
   312  			}
   313  
   314  			if b.Locked() {
   315  				lockedCnt++
   316  			}
   317  			if b.LastActiveTime() == (time.Time{}) {
   318  				inactiveCnt++
   319  			}
   320  			b = nil
   321  		}
   322  
   323  		// all backend inactived, trigger gc inactive.
   324  		if b == nil && n > 0 {
   325  			c.triggerGCInactive(backend)
   326  			c.logger.Debug("no available backends",
   327  				zap.String("backend", backend),
   328  				zap.Int("locked", lockedCnt),
   329  				zap.Int("inactive", inactiveCnt),
   330  				zap.Int("max", c.options.maxBackendsPerHost))
   331  			if !c.canCreateLocked(backend) {
   332  				return nil, moerr.NewNoAvailableBackendNoCtx()
   333  			}
   334  		}
   335  
   336  		if lock && b != nil {
   337  			b.Lock()
   338  		}
   339  		c.maybeCreateLocked(backend)
   340  		return b, nil
   341  	}
   342  	return nil, nil
   343  }
   344  
   345  func (c *client) maybeCreateLocked(backend string) bool {
   346  	if len(c.mu.backends[backend]) == 0 {
   347  		return c.tryCreate(backend)
   348  	}
   349  
   350  	if !c.canCreateLocked(backend) {
   351  		return false
   352  	}
   353  
   354  	for _, b := range c.mu.backends[backend] {
   355  		if b.Busy() || b.Locked() {
   356  			return c.tryCreate(backend)
   357  		}
   358  	}
   359  	return false
   360  }
   361  
   362  func (c *client) tryCreate(backend string) bool {
   363  	if !c.options.enableAutoCreate {
   364  		return false
   365  	}
   366  
   367  	select {
   368  	case c.createC <- backend:
   369  		return true
   370  	default:
   371  		return false
   372  	}
   373  }
   374  
   375  func (c *client) gcIdleTask(ctx context.Context) {
   376  	c.logger.Info("gc idle backends task started")
   377  	defer c.logger.Error("gc idle backends task stopped")
   378  
   379  	ticker := time.NewTicker(c.options.maxIdleDuration)
   380  	defer ticker.Stop()
   381  
   382  	for {
   383  		select {
   384  		case <-ctx.Done():
   385  			return
   386  		case <-ticker.C:
   387  			c.closeIdleBackends()
   388  		}
   389  	}
   390  }
   391  
   392  func (c *client) triggerGCInactive(remote string) {
   393  	select {
   394  	case c.gcInactiveC <- remote:
   395  		c.logger.Debug("try to remove all inactived backends",
   396  			zap.String("remote", remote))
   397  	default:
   398  	}
   399  }
   400  
   401  func (c *client) gcInactiveTask(ctx context.Context) {
   402  	c.logger.Debug("gc inactive backends task started")
   403  	defer c.logger.Error("gc inactive backends task stopped")
   404  
   405  	for {
   406  		select {
   407  		case <-ctx.Done():
   408  			return
   409  		case remote := <-c.gcInactiveC:
   410  			c.doRemoveInactive(remote)
   411  		}
   412  	}
   413  }
   414  
   415  func (c *client) doRemoveInactive(remote string) {
   416  	c.mu.Lock()
   417  	defer c.mu.Unlock()
   418  	backends, ok := c.mu.backends[remote]
   419  	if !ok {
   420  		return
   421  	}
   422  
   423  	newBackends := backends[:0]
   424  	for _, backend := range backends {
   425  		if backend.LastActiveTime() == (time.Time{}) {
   426  			backend.Close()
   427  			continue
   428  		}
   429  		newBackends = append(newBackends, backend)
   430  	}
   431  	c.mu.backends[remote] = newBackends
   432  }
   433  
   434  func (c *client) closeIdleBackends() {
   435  	var idleBackends []Backend
   436  	c.mu.Lock()
   437  	for k, backends := range c.mu.backends {
   438  		var newBackends []Backend
   439  		for _, b := range backends {
   440  			if !b.Locked() &&
   441  				time.Since(b.LastActiveTime()) > c.options.maxIdleDuration {
   442  				idleBackends = append(idleBackends, b)
   443  				continue
   444  			}
   445  			newBackends = append(newBackends, b)
   446  		}
   447  		c.mu.backends[k] = newBackends
   448  	}
   449  	c.mu.Unlock()
   450  
   451  	for _, b := range idleBackends {
   452  		b.Close()
   453  	}
   454  }
   455  
   456  func (c *client) createTask(ctx context.Context) {
   457  	for {
   458  		select {
   459  		case <-ctx.Done():
   460  			return
   461  		case backend, ok := <-c.createC:
   462  			if ok {
   463  				c.mu.Lock()
   464  				if _, err := c.createBackendLocked(backend); err != nil {
   465  					c.logger.Error("create backend failed",
   466  						zap.String("backend", backend),
   467  						zap.Error(err))
   468  				}
   469  				c.mu.Unlock()
   470  			}
   471  		}
   472  	}
   473  }
   474  
   475  func (c *client) createBackend(backend string, lock bool) (Backend, error) {
   476  	c.mu.Lock()
   477  	defer c.mu.Unlock()
   478  
   479  	b, err := c.getBackendLocked(backend, lock)
   480  	if err != nil {
   481  		return nil, err
   482  	}
   483  	if b != nil {
   484  		return b, nil
   485  	}
   486  
   487  	b, err = c.createBackendLocked(backend)
   488  	if err != nil {
   489  		return nil, err
   490  	}
   491  	if lock {
   492  		b.Lock()
   493  	}
   494  	return b, nil
   495  }
   496  
   497  func (c *client) createBackendLocked(backend string) (Backend, error) {
   498  	if !c.canCreateLocked(backend) {
   499  		return nil, moerr.NewNoAvailableBackendNoCtx()
   500  	}
   501  
   502  	b, err := c.doCreate(backend)
   503  	if err != nil {
   504  		return nil, err
   505  	}
   506  	c.mu.backends[backend] = append(c.mu.backends[backend], b)
   507  	if _, ok := c.mu.ops[backend]; !ok {
   508  		c.mu.ops[backend] = &op{}
   509  	}
   510  	return b, nil
   511  }
   512  
   513  func (c *client) doCreate(backend string) (Backend, error) {
   514  	b, err := c.factory.Create(backend, WithBackendMetrics(c.metrics))
   515  	if err != nil {
   516  		c.logger.Error("create backend failed",
   517  			zap.String("backend", backend),
   518  			zap.Error(err))
   519  		return nil, err
   520  	}
   521  	return b, nil
   522  }
   523  
   524  func (c *client) canCreateLocked(backend string) bool {
   525  	return len(c.mu.backends[backend]) < c.options.maxBackendsPerHost
   526  }
   527  
   528  type op struct {
   529  	seq uint64
   530  }
   531  
   532  func (o *op) next() uint64 {
   533  	return atomic.AddUint64(&o.seq, 1)
   534  }