github.com/whtcorpsinc/MilevaDB-Prod@v0.0.0-20211104133533-f57f4be3b597/causetstore/milevadb-server/einsteindb/kv.go (about)

     1  // Copyright 2020 WHTCORPS INC, 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 einsteindb
    15  
    16  import (
    17  	"context"
    18  	"crypto/tls"
    19  	"fmt"
    20  	"math/rand"
    21  	"net/url"
    22  	"sync"
    23  	"sync/atomic"
    24  	"time"
    25  
    26  	fidel "github.com/einsteindb/fidel/client"
    27  	"github.com/opentracing/opentracing-go"
    28  	"github.com/whtcorpsinc/errors"
    29  	"github.com/whtcorpsinc/failpoint"
    30  	"github.com/whtcorpsinc/milevadb/causetstore/einsteindb/einsteindbrpc"
    31  	"github.com/whtcorpsinc/milevadb/causetstore/einsteindb/latch"
    32  	"github.com/whtcorpsinc/milevadb/causetstore/einsteindb/oracle"
    33  	"github.com/whtcorpsinc/milevadb/causetstore/einsteindb/oracle/oracles"
    34  	"github.com/whtcorpsinc/milevadb/config"
    35  	"github.com/whtcorpsinc/milevadb/ekv"
    36  	"github.com/whtcorpsinc/milevadb/metrics"
    37  	"github.com/whtcorpsinc/milevadb/soliton/execdetails"
    38  	"github.com/whtcorpsinc/milevadb/soliton/fastrand"
    39  	"github.com/whtcorpsinc/milevadb/soliton/logutil"
    40  	"go.etcd.io/etcd/clientv3"
    41  	"go.uber.org/zap"
    42  	"google.golang.org/grpc"
    43  	"google.golang.org/grpc/keepalive"
    44  )
    45  
    46  type storeCache struct {
    47  	sync.Mutex
    48  	cache map[string]*einsteindbStore
    49  }
    50  
    51  var mc storeCache
    52  
    53  // Driver implements engine Driver.
    54  type Driver struct {
    55  }
    56  
    57  func createEtcdKV(addrs []string, tlsConfig *tls.Config) (*clientv3.Client, error) {
    58  	cfg := config.GetGlobalConfig()
    59  	cli, err := clientv3.New(clientv3.Config{
    60  		Endpoints:            addrs,
    61  		AutoSyncInterval:     30 * time.Second,
    62  		DialTimeout:          5 * time.Second,
    63  		TLS:                  tlsConfig,
    64  		DialKeepAliveTime:    time.Second * time.Duration(cfg.EinsteinDBClient.GrpcKeepAliveTime),
    65  		DialKeepAliveTimeout: time.Second * time.Duration(cfg.EinsteinDBClient.GrpcKeepAliveTimeout),
    66  	})
    67  	if err != nil {
    68  		return nil, errors.Trace(err)
    69  	}
    70  	return cli, nil
    71  }
    72  
    73  // Open opens or creates an EinsteinDB storage with given path.
    74  
    75  func (d Driver) Open(path string) (ekv.CausetStorage, error) {
    76  	mc.Lock()
    77  	defer mc.Unlock()
    78  
    79  	security := config.GetGlobalConfig().Security
    80  	einsteindbConfig := config.GetGlobalConfig().EinsteinDBClient
    81  	txnLocalLatches := config.GetGlobalConfig().TxnLocalLatches
    82  	etcdAddrs, disableGC, err := config.ParsePath(path)
    83  	if err != nil {
    84  		return nil, errors.Trace(err)
    85  	}
    86  
    87  	FIDelCli, err := fidel.NewClient(etcdAddrs, fidel.SecurityOption{
    88  		CAPath:   security.ClusterSSLCA,
    89  		CertPath: security.ClusterSSLCert,
    90  		KeyPath:  security.ClusterSSLKey,
    91  	}, fidel.WithGRPCDialOptions(
    92  		grpc.WithKeepaliveParams(keepalive.ClientParameters{
    93  			Time:    time.Duration(einsteindbConfig.GrpcKeepAliveTime) * time.Second,
    94  			Timeout: time.Duration(einsteindbConfig.GrpcKeepAliveTimeout) * time.Second,
    95  		}),
    96  	))
    97  	FIDelCli = execdetails.InterceptedFIDelClient{Client: FIDelCli}
    98  
    99  	if err != nil {
   100  		return nil, errors.Trace(err)
   101  	}
   102  
   103  	// FIXME: uuid will be a very long and ugly string, simplify it.
   104  	uuid := fmt.Sprintf("einsteindb-%v", FIDelCli.GetClusterID(context.TODO()))
   105  	if causetstore, ok := mc.cache[uuid]; ok {
   106  		return causetstore, nil
   107  	}
   108  
   109  	tlsConfig, err := security.ToTLSConfig()
   110  	if err != nil {
   111  		return nil, errors.Trace(err)
   112  	}
   113  
   114  	spekv, err := NewEtcdSafePointKV(etcdAddrs, tlsConfig)
   115  	if err != nil {
   116  		return nil, errors.Trace(err)
   117  	}
   118  
   119  	coprCacheConfig := &config.GetGlobalConfig().EinsteinDBClient.CoprCache
   120  	s, err := newEinsteinDBStore(uuid, &codecFIDelClient{FIDelCli}, spekv, newRPCClient(security), !disableGC, coprCacheConfig)
   121  	if err != nil {
   122  		return nil, errors.Trace(err)
   123  	}
   124  	if txnLocalLatches.Enabled {
   125  		s.EnableTxnLocalLatches(txnLocalLatches.Capacity)
   126  	}
   127  	s.etcdAddrs = etcdAddrs
   128  	s.tlsConfig = tlsConfig
   129  
   130  	mc.cache[uuid] = s
   131  	return s, nil
   132  }
   133  
   134  // EtcdBackend is used for judging a storage is a real EinsteinDB.
   135  type EtcdBackend interface {
   136  	EtcdAddrs() ([]string, error)
   137  	TLSConfig() *tls.Config
   138  	StartGCWorker() error
   139  }
   140  
   141  // uFIDelate oracle's lastTS every 2000ms.
   142  var oracleUFIDelateInterval = 2000
   143  
   144  type einsteindbStore struct {
   145  	clusterID    uint64
   146  	uuid         string
   147  	oracle       oracle.Oracle
   148  	client       Client
   149  	FIDelClient  fidel.Client
   150  	regionCache  *RegionCache
   151  	coprCache    *coprCache
   152  	lockResolver *LockResolver
   153  	txnLatches   *latch.LatchesScheduler
   154  	gcWorker     GCHandler
   155  	etcdAddrs    []string
   156  	tlsConfig    *tls.Config
   157  	mock         bool
   158  	enableGC     bool
   159  
   160  	ekv       SafePointKV
   161  	safePoint uint64
   162  	spTime    time.Time
   163  	spMutex   sync.RWMutex  // this is used to uFIDelate safePoint and spTime
   164  	closed    chan struct{} // this is used to nofity when the causetstore is closed
   165  
   166  	replicaReadSeed uint32 // this is used to load balance followers / learners when replica read is enabled
   167  }
   168  
   169  func (s *einsteindbStore) UFIDelateSPCache(cachedSP uint64, cachedTime time.Time) {
   170  	s.spMutex.Lock()
   171  	s.safePoint = cachedSP
   172  	s.spTime = cachedTime
   173  	s.spMutex.Unlock()
   174  }
   175  
   176  func (s *einsteindbStore) CheckVisibility(startTime uint64) error {
   177  	s.spMutex.RLock()
   178  	cachedSafePoint := s.safePoint
   179  	cachedTime := s.spTime
   180  	s.spMutex.RUnlock()
   181  	diff := time.Since(cachedTime)
   182  
   183  	if diff > (GcSafePointCacheInterval - gcCPUTimeInaccuracyBound) {
   184  		return ErrFIDelServerTimeout.GenWithStackByArgs("start timestamp may fall behind safe point")
   185  	}
   186  
   187  	if startTime < cachedSafePoint {
   188  		t1 := oracle.GetTimeFromTS(startTime)
   189  		t2 := oracle.GetTimeFromTS(cachedSafePoint)
   190  		return ErrGCTooEarly.GenWithStackByArgs(t1, t2)
   191  	}
   192  
   193  	return nil
   194  }
   195  
   196  func newEinsteinDBStore(uuid string, FIDelClient fidel.Client, spekv SafePointKV, client Client, enableGC bool, coprCacheConfig *config.CoprocessorCache) (*einsteindbStore, error) {
   197  	o, err := oracles.NewFIDelOracle(FIDelClient, time.Duration(oracleUFIDelateInterval)*time.Millisecond)
   198  	if err != nil {
   199  		return nil, errors.Trace(err)
   200  	}
   201  	causetstore := &einsteindbStore{
   202  		clusterID:       FIDelClient.GetClusterID(context.TODO()),
   203  		uuid:            uuid,
   204  		oracle:          o,
   205  		client:          reqDefCauslapse{client},
   206  		FIDelClient:     FIDelClient,
   207  		regionCache:     NewRegionCache(FIDelClient),
   208  		coprCache:       nil,
   209  		ekv:             spekv,
   210  		safePoint:       0,
   211  		spTime:          time.Now(),
   212  		closed:          make(chan struct{}),
   213  		replicaReadSeed: fastrand.Uint32(),
   214  	}
   215  	causetstore.lockResolver = newLockResolver(causetstore)
   216  	causetstore.enableGC = enableGC
   217  
   218  	coprCache, err := newCoprCache(coprCacheConfig)
   219  	if err != nil {
   220  		return nil, errors.Trace(err)
   221  	}
   222  	causetstore.coprCache = coprCache
   223  
   224  	go causetstore.runSafePointChecker()
   225  
   226  	return causetstore, nil
   227  }
   228  
   229  func (s *einsteindbStore) EnableTxnLocalLatches(size uint) {
   230  	s.txnLatches = latch.NewScheduler(size)
   231  }
   232  
   233  // IsLatchEnabled is used by mockstore.TestConfig.
   234  func (s *einsteindbStore) IsLatchEnabled() bool {
   235  	return s.txnLatches != nil
   236  }
   237  
   238  func (s *einsteindbStore) EtcdAddrs() ([]string, error) {
   239  	if s.etcdAddrs == nil {
   240  		return nil, nil
   241  	}
   242  	ctx := context.Background()
   243  	bo := NewBackoffer(ctx, GetMemberInfoBackoff)
   244  	etcdAddrs := make([]string, 0)
   245  	FIDelClient := s.FIDelClient
   246  	if FIDelClient == nil {
   247  		return nil, errors.New("Etcd client not found")
   248  	}
   249  	for {
   250  		members, err := FIDelClient.GetMemberInfo(ctx)
   251  		if err != nil {
   252  			err := bo.Backoff(BoRegionMiss, err)
   253  			if err != nil {
   254  				return nil, err
   255  			}
   256  			continue
   257  		}
   258  		for _, member := range members {
   259  			if len(member.ClientUrls) > 0 {
   260  				u, err := url.Parse(member.ClientUrls[0])
   261  				if err != nil {
   262  					logutil.BgLogger().Error("fail to parse client url from fidel members", zap.String("client_url", member.ClientUrls[0]), zap.Error(err))
   263  					return nil, err
   264  				}
   265  				etcdAddrs = append(etcdAddrs, u.Host)
   266  			}
   267  		}
   268  		return etcdAddrs, nil
   269  	}
   270  }
   271  
   272  func (s *einsteindbStore) TLSConfig() *tls.Config {
   273  	return s.tlsConfig
   274  }
   275  
   276  // StartGCWorker starts GC worker, it's called in BootstrapStochastik, don't call this function more than once.
   277  func (s *einsteindbStore) StartGCWorker() error {
   278  	if !s.enableGC || NewGCHandlerFunc == nil {
   279  		return nil
   280  	}
   281  
   282  	gcWorker, err := NewGCHandlerFunc(s, s.FIDelClient)
   283  	if err != nil {
   284  		return errors.Trace(err)
   285  	}
   286  	gcWorker.Start()
   287  	s.gcWorker = gcWorker
   288  	return nil
   289  }
   290  
   291  func (s *einsteindbStore) runSafePointChecker() {
   292  	d := gcSafePointUFIDelateInterval
   293  	for {
   294  		select {
   295  		case spCachedTime := <-time.After(d):
   296  			cachedSafePoint, err := loadSafePoint(s.GetSafePointKV())
   297  			if err == nil {
   298  				metrics.EinsteinDBLoadSafepointCounter.WithLabelValues("ok").Inc()
   299  				s.UFIDelateSPCache(cachedSafePoint, spCachedTime)
   300  				d = gcSafePointUFIDelateInterval
   301  			} else {
   302  				metrics.EinsteinDBLoadSafepointCounter.WithLabelValues("fail").Inc()
   303  				logutil.BgLogger().Error("fail to load safepoint from fidel", zap.Error(err))
   304  				d = gcSafePointQuickRepeatInterval
   305  			}
   306  		case <-s.Closed():
   307  			return
   308  		}
   309  	}
   310  }
   311  
   312  func (s *einsteindbStore) Begin() (ekv.Transaction, error) {
   313  	txn, err := newEinsteinDBTxn(s)
   314  	if err != nil {
   315  		return nil, errors.Trace(err)
   316  	}
   317  	return txn, nil
   318  }
   319  
   320  // BeginWithStartTS begins a transaction with startTS.
   321  func (s *einsteindbStore) BeginWithStartTS(startTS uint64) (ekv.Transaction, error) {
   322  	txn, err := newEinsteinDBTxnWithStartTS(s, startTS, s.nextReplicaReadSeed())
   323  	if err != nil {
   324  		return nil, errors.Trace(err)
   325  	}
   326  	return txn, nil
   327  }
   328  
   329  func (s *einsteindbStore) GetSnapshot(ver ekv.Version) (ekv.Snapshot, error) {
   330  	snapshot := newEinsteinDBSnapshot(s, ver, s.nextReplicaReadSeed())
   331  	return snapshot, nil
   332  }
   333  
   334  func (s *einsteindbStore) Close() error {
   335  	mc.Lock()
   336  	defer mc.Unlock()
   337  
   338  	delete(mc.cache, s.uuid)
   339  	s.oracle.Close()
   340  	s.FIDelClient.Close()
   341  	if s.gcWorker != nil {
   342  		s.gcWorker.Close()
   343  	}
   344  
   345  	close(s.closed)
   346  	if err := s.client.Close(); err != nil {
   347  		return errors.Trace(err)
   348  	}
   349  
   350  	if s.txnLatches != nil {
   351  		s.txnLatches.Close()
   352  	}
   353  	s.regionCache.Close()
   354  	return nil
   355  }
   356  
   357  func (s *einsteindbStore) UUID() string {
   358  	return s.uuid
   359  }
   360  
   361  func (s *einsteindbStore) CurrentVersion() (ekv.Version, error) {
   362  	bo := NewBackofferWithVars(context.Background(), tsoMaxBackoff, nil)
   363  	startTS, err := s.getTimestampWithRetry(bo)
   364  	if err != nil {
   365  		return ekv.NewVersion(0), errors.Trace(err)
   366  	}
   367  	return ekv.NewVersion(startTS), nil
   368  }
   369  
   370  func (s *einsteindbStore) getTimestampWithRetry(bo *Backoffer) (uint64, error) {
   371  	if span := opentracing.SpanFromContext(bo.ctx); span != nil && span.Tracer() != nil {
   372  		span1 := span.Tracer().StartSpan("einsteindbStore.getTimestampWithRetry", opentracing.ChildOf(span.Context()))
   373  		defer span1.Finish()
   374  		bo.ctx = opentracing.ContextWithSpan(bo.ctx, span1)
   375  	}
   376  
   377  	for {
   378  		startTS, err := s.oracle.GetTimestamp(bo.ctx)
   379  		// mockGetTSErrorInRetry should wait MockCommitErrorOnce first, then will run into retry() logic.
   380  		// Then mockGetTSErrorInRetry will return retryable error when first retry.
   381  		// Before PR #8743, we don't cleanup txn after meet error such as error like: FIDel server timeout
   382  		// This may cause duplicate data to be written.
   383  		failpoint.Inject("mockGetTSErrorInRetry", func(val failpoint.Value) {
   384  			if val.(bool) && !ekv.IsMockCommitErrorEnable() {
   385  				err = ErrFIDelServerTimeout.GenWithStackByArgs("mock FIDel timeout")
   386  			}
   387  		})
   388  
   389  		if err == nil {
   390  			return startTS, nil
   391  		}
   392  		err = bo.Backoff(BoFIDelRPC, errors.Errorf("get timestamp failed: %v", err))
   393  		if err != nil {
   394  			return 0, errors.Trace(err)
   395  		}
   396  	}
   397  }
   398  
   399  func (s *einsteindbStore) nextReplicaReadSeed() uint32 {
   400  	return atomic.AddUint32(&s.replicaReadSeed, 1)
   401  }
   402  
   403  func (s *einsteindbStore) GetClient() ekv.Client {
   404  	return &CopClient{
   405  		causetstore:     s,
   406  		replicaReadSeed: s.nextReplicaReadSeed(),
   407  	}
   408  }
   409  
   410  func (s *einsteindbStore) GetOracle() oracle.Oracle {
   411  	return s.oracle
   412  }
   413  
   414  func (s *einsteindbStore) Name() string {
   415  	return "EinsteinDB"
   416  }
   417  
   418  func (s *einsteindbStore) Describe() string {
   419  	return "EinsteinDB is a distributed transactional key-value database"
   420  }
   421  
   422  func (s *einsteindbStore) ShowStatus(ctx context.Context, key string) (interface{}, error) {
   423  	return nil, ekv.ErrNotImplemented
   424  }
   425  
   426  func (s *einsteindbStore) SupportDeleteRange() (supported bool) {
   427  	return !s.mock
   428  }
   429  
   430  func (s *einsteindbStore) SendReq(bo *Backoffer, req *einsteindbrpc.Request, regionID RegionVerID, timeout time.Duration) (*einsteindbrpc.Response, error) {
   431  	sender := NewRegionRequestSender(s.regionCache, s.client)
   432  	return sender.SendReq(bo, req, regionID, timeout)
   433  }
   434  
   435  func (s *einsteindbStore) GetRegionCache() *RegionCache {
   436  	return s.regionCache
   437  }
   438  
   439  func (s *einsteindbStore) GetLockResolver() *LockResolver {
   440  	return s.lockResolver
   441  }
   442  
   443  func (s *einsteindbStore) GetGCHandler() GCHandler {
   444  	return s.gcWorker
   445  }
   446  
   447  func (s *einsteindbStore) Closed() <-chan struct{} {
   448  	return s.closed
   449  }
   450  
   451  func (s *einsteindbStore) GetSafePointKV() SafePointKV {
   452  	return s.ekv
   453  }
   454  
   455  func (s *einsteindbStore) SetOracle(oracle oracle.Oracle) {
   456  	s.oracle = oracle
   457  }
   458  
   459  func (s *einsteindbStore) SetEinsteinDBClient(client Client) {
   460  	s.client = client
   461  }
   462  
   463  func (s *einsteindbStore) GetEinsteinDBClient() (client Client) {
   464  	return s.client
   465  }
   466  
   467  func init() {
   468  	mc.cache = make(map[string]*einsteindbStore)
   469  	rand.Seed(time.Now().UnixNano())
   470  }