github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/pkg/sink/observer/observer.go (about)

     1  // Copyright 2023 PingCAP, Inc.
     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  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package observer
    15  
    16  import (
    17  	"context"
    18  	"net/url"
    19  	"strings"
    20  	"sync"
    21  
    22  	"github.com/pingcap/tiflow/cdc/model"
    23  	"github.com/pingcap/tiflow/pkg/config"
    24  	"github.com/pingcap/tiflow/pkg/errors"
    25  	cerror "github.com/pingcap/tiflow/pkg/errors"
    26  	"github.com/pingcap/tiflow/pkg/sink"
    27  	pmysql "github.com/pingcap/tiflow/pkg/sink/mysql"
    28  )
    29  
    30  // Observer defines an interface of downstream performance observer.
    31  type Observer interface {
    32  	// Tick is called periodically, Observer fetches performance metrics and
    33  	// records them in each Tick.
    34  	// Tick and Close must be concurrent safe.
    35  	Tick(ctx context.Context) error
    36  	Close() error
    37  }
    38  
    39  // NewObserverOpt represents available options when creating a new observer.
    40  type NewObserverOpt struct {
    41  	dbConnFactory pmysql.Factory
    42  }
    43  
    44  // NewObserverOption configures NewObserverOpt.
    45  type NewObserverOption func(*NewObserverOpt)
    46  
    47  // WithDBConnFactory specifies factory to create db connection.
    48  func WithDBConnFactory(factory pmysql.Factory) NewObserverOption {
    49  	return func(opt *NewObserverOpt) {
    50  		opt.dbConnFactory = factory
    51  	}
    52  }
    53  
    54  // NewObserver creates a new Observer
    55  func NewObserver(
    56  	ctx context.Context,
    57  	changefeedID model.ChangeFeedID,
    58  	sinkURIStr string,
    59  	replCfg *config.ReplicaConfig,
    60  	opts ...NewObserverOption,
    61  ) (Observer, error) {
    62  	creator := func() (Observer, error) {
    63  		options := &NewObserverOpt{dbConnFactory: pmysql.CreateMySQLDBConn}
    64  		for _, opt := range opts {
    65  			opt(options)
    66  		}
    67  
    68  		sinkURI, err := url.Parse(sinkURIStr)
    69  		if err != nil {
    70  			return nil, cerror.WrapError(cerror.ErrSinkURIInvalid, err)
    71  		}
    72  
    73  		scheme := strings.ToLower(sinkURI.Scheme)
    74  		if !sink.IsMySQLCompatibleScheme(scheme) {
    75  			return NewDummyObserver(), nil
    76  		}
    77  
    78  		cfg := pmysql.NewConfig()
    79  		err = cfg.Apply(config.GetGlobalServerConfig().TZ, changefeedID, sinkURI, replCfg)
    80  		if err != nil {
    81  			return nil, err
    82  		}
    83  
    84  		dsnStr, err := pmysql.GenerateDSN(ctx, sinkURI, cfg, options.dbConnFactory)
    85  		if err != nil {
    86  			return nil, err
    87  		}
    88  		db, err := options.dbConnFactory(ctx, dsnStr)
    89  		if err != nil {
    90  			return nil, err
    91  		}
    92  		db.SetMaxIdleConns(2)
    93  		db.SetMaxOpenConns(2)
    94  
    95  		isTiDB, err := pmysql.CheckIsTiDB(ctx, db)
    96  		if err != nil {
    97  			return nil, err
    98  		}
    99  		if isTiDB {
   100  			return NewTiDBObserver(db), nil
   101  		}
   102  		_ = db.Close()
   103  		return NewDummyObserver(), nil
   104  	}
   105  	return &observerAgent{creator: creator}, nil
   106  }
   107  
   108  type observerAgent struct {
   109  	creator func() (Observer, error)
   110  
   111  	mu struct {
   112  		sync.Mutex
   113  		inner  Observer
   114  		closed bool
   115  	}
   116  }
   117  
   118  // Tick implements Observer interface.
   119  func (o *observerAgent) Tick(ctx context.Context) error {
   120  	o.mu.Lock()
   121  	if o.mu.inner != nil {
   122  		defer o.mu.Unlock()
   123  		return o.mu.inner.Tick(ctx)
   124  	}
   125  	if o.mu.closed {
   126  		defer o.mu.Unlock()
   127  		return nil
   128  	}
   129  	o.mu.Unlock()
   130  
   131  	inner, err := o.creator()
   132  	if err != nil {
   133  		return errors.Trace(err)
   134  	}
   135  
   136  	o.mu.Lock()
   137  	defer o.mu.Unlock()
   138  	if !o.mu.closed {
   139  		o.mu.inner = inner
   140  		return o.mu.inner.Tick(ctx)
   141  	}
   142  	return nil
   143  }
   144  
   145  // Close implements Observer interface.
   146  func (o *observerAgent) Close() error {
   147  	o.mu.Lock()
   148  	defer o.mu.Unlock()
   149  	if o.mu.inner != nil {
   150  		o.mu.closed = true
   151  		return o.mu.inner.Close()
   152  	}
   153  	return nil
   154  }