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 }