github.com/pingcap/br@v5.3.0-alpha.0.20220125034240-ec59c7b6ce30+incompatible/pkg/restore/split_client.go (about) 1 // Copyright 2020 PingCAP, Inc. Licensed under Apache-2.0. 2 3 package restore 4 5 import ( 6 "bytes" 7 "context" 8 "crypto/tls" 9 "encoding/json" 10 "fmt" 11 "io" 12 "net/http" 13 "path" 14 "strconv" 15 "strings" 16 "sync" 17 "time" 18 19 "github.com/pingcap/errors" 20 "github.com/pingcap/failpoint" 21 "github.com/pingcap/kvproto/pkg/errorpb" 22 "github.com/pingcap/kvproto/pkg/kvrpcpb" 23 "github.com/pingcap/kvproto/pkg/metapb" 24 "github.com/pingcap/kvproto/pkg/pdpb" 25 "github.com/pingcap/kvproto/pkg/tikvpb" 26 "github.com/pingcap/log" 27 pd "github.com/tikv/pd/client" 28 "github.com/tikv/pd/server/schedule/placement" 29 "go.uber.org/multierr" 30 "go.uber.org/zap" 31 "google.golang.org/grpc" 32 "google.golang.org/grpc/credentials" 33 "google.golang.org/grpc/status" 34 35 berrors "github.com/pingcap/br/pkg/errors" 36 "github.com/pingcap/br/pkg/httputil" 37 "github.com/pingcap/br/pkg/logutil" 38 ) 39 40 const ( 41 splitRegionMaxRetryTime = 4 42 ) 43 44 // SplitClient is an external client used by RegionSplitter. 45 type SplitClient interface { 46 // GetStore gets a store by a store id. 47 GetStore(ctx context.Context, storeID uint64) (*metapb.Store, error) 48 // GetRegion gets a region which includes a specified key. 49 GetRegion(ctx context.Context, key []byte) (*RegionInfo, error) 50 // GetRegionByID gets a region by a region id. 51 GetRegionByID(ctx context.Context, regionID uint64) (*RegionInfo, error) 52 // SplitRegion splits a region from a key, if key is not included in the region, it will return nil. 53 // note: the key should not be encoded 54 SplitRegion(ctx context.Context, regionInfo *RegionInfo, key []byte) (*RegionInfo, error) 55 // BatchSplitRegions splits a region from a batch of keys. 56 // note: the keys should not be encoded 57 BatchSplitRegions(ctx context.Context, regionInfo *RegionInfo, keys [][]byte) ([]*RegionInfo, error) 58 // BatchSplitRegionsWithOrigin splits a region from a batch of keys and return the original region and split new regions 59 BatchSplitRegionsWithOrigin(ctx context.Context, regionInfo *RegionInfo, keys [][]byte) (*RegionInfo, []*RegionInfo, error) 60 // ScatterRegion scatters a specified region. 61 ScatterRegion(ctx context.Context, regionInfo *RegionInfo) error 62 // GetOperator gets the status of operator of the specified region. 63 GetOperator(ctx context.Context, regionID uint64) (*pdpb.GetOperatorResponse, error) 64 // ScanRegion gets a list of regions, starts from the region that contains key. 65 // Limit limits the maximum number of regions returned. 66 ScanRegions(ctx context.Context, key, endKey []byte, limit int) ([]*RegionInfo, error) 67 // GetPlacementRule loads a placement rule from PD. 68 GetPlacementRule(ctx context.Context, groupID, ruleID string) (placement.Rule, error) 69 // SetPlacementRule insert or update a placement rule to PD. 70 SetPlacementRule(ctx context.Context, rule placement.Rule) error 71 // DeletePlacementRule removes a placement rule from PD. 72 DeletePlacementRule(ctx context.Context, groupID, ruleID string) error 73 // SetStoreLabel add or update specified label of stores. If labelValue 74 // is empty, it clears the label. 75 SetStoresLabel(ctx context.Context, stores []uint64, labelKey, labelValue string) error 76 } 77 78 // pdClient is a wrapper of pd client, can be used by RegionSplitter. 79 type pdClient struct { 80 mu sync.Mutex 81 client pd.Client 82 tlsConf *tls.Config 83 storeCache map[uint64]*metapb.Store 84 } 85 86 // NewSplitClient returns a client used by RegionSplitter. 87 func NewSplitClient(client pd.Client, tlsConf *tls.Config) SplitClient { 88 return &pdClient{ 89 client: client, 90 tlsConf: tlsConf, 91 storeCache: make(map[uint64]*metapb.Store), 92 } 93 } 94 95 func (c *pdClient) GetStore(ctx context.Context, storeID uint64) (*metapb.Store, error) { 96 c.mu.Lock() 97 defer c.mu.Unlock() 98 store, ok := c.storeCache[storeID] 99 if ok { 100 return store, nil 101 } 102 store, err := c.client.GetStore(ctx, storeID) 103 if err != nil { 104 return nil, errors.Trace(err) 105 } 106 c.storeCache[storeID] = store 107 return store, nil 108 } 109 110 func (c *pdClient) GetRegion(ctx context.Context, key []byte) (*RegionInfo, error) { 111 region, err := c.client.GetRegion(ctx, key) 112 if err != nil { 113 return nil, errors.Trace(err) 114 } 115 if region == nil { 116 return nil, nil 117 } 118 return &RegionInfo{ 119 Region: region.Meta, 120 Leader: region.Leader, 121 }, nil 122 } 123 124 func (c *pdClient) GetRegionByID(ctx context.Context, regionID uint64) (*RegionInfo, error) { 125 region, err := c.client.GetRegionByID(ctx, regionID) 126 if err != nil { 127 return nil, errors.Trace(err) 128 } 129 if region == nil { 130 return nil, nil 131 } 132 return &RegionInfo{ 133 Region: region.Meta, 134 Leader: region.Leader, 135 }, nil 136 } 137 138 func (c *pdClient) SplitRegion(ctx context.Context, regionInfo *RegionInfo, key []byte) (*RegionInfo, error) { 139 var peer *metapb.Peer 140 if regionInfo.Leader != nil { 141 peer = regionInfo.Leader 142 } else { 143 if len(regionInfo.Region.Peers) == 0 { 144 return nil, errors.Annotate(berrors.ErrRestoreNoPeer, "region does not have peer") 145 } 146 peer = regionInfo.Region.Peers[0] 147 } 148 storeID := peer.GetStoreId() 149 store, err := c.GetStore(ctx, storeID) 150 if err != nil { 151 return nil, errors.Trace(err) 152 } 153 conn, err := grpc.Dial(store.GetAddress(), grpc.WithInsecure()) 154 if err != nil { 155 return nil, errors.Trace(err) 156 } 157 defer conn.Close() 158 159 client := tikvpb.NewTikvClient(conn) 160 resp, err := client.SplitRegion(ctx, &kvrpcpb.SplitRegionRequest{ 161 Context: &kvrpcpb.Context{ 162 RegionId: regionInfo.Region.Id, 163 RegionEpoch: regionInfo.Region.RegionEpoch, 164 Peer: peer, 165 }, 166 SplitKey: key, 167 }) 168 if err != nil { 169 return nil, errors.Trace(err) 170 } 171 if resp.RegionError != nil { 172 log.Error("fail to split region", 173 logutil.Region(regionInfo.Region), 174 logutil.Key("key", key), 175 zap.Stringer("regionErr", resp.RegionError)) 176 return nil, errors.Annotatef(berrors.ErrRestoreSplitFailed, "err=%v", resp.RegionError) 177 } 178 179 // BUG: Left is deprecated, it may be nil even if split is succeed! 180 // Assume the new region is the left one. 181 newRegion := resp.GetLeft() 182 if newRegion == nil { 183 regions := resp.GetRegions() 184 for _, r := range regions { 185 if bytes.Equal(r.GetStartKey(), regionInfo.Region.GetStartKey()) { 186 newRegion = r 187 break 188 } 189 } 190 } 191 if newRegion == nil { 192 return nil, errors.Annotate(berrors.ErrRestoreSplitFailed, "new region is nil") 193 } 194 var leader *metapb.Peer 195 // Assume the leaders will be at the same store. 196 if regionInfo.Leader != nil { 197 for _, p := range newRegion.GetPeers() { 198 if p.GetStoreId() == regionInfo.Leader.GetStoreId() { 199 leader = p 200 break 201 } 202 } 203 } 204 return &RegionInfo{ 205 Region: newRegion, 206 Leader: leader, 207 }, nil 208 } 209 210 func splitRegionWithFailpoint( 211 ctx context.Context, 212 regionInfo *RegionInfo, 213 peer *metapb.Peer, 214 client tikvpb.TikvClient, 215 keys [][]byte, 216 ) (*kvrpcpb.SplitRegionResponse, error) { 217 failpoint.Inject("not-leader-error", func(injectNewLeader failpoint.Value) { 218 log.Debug("failpoint not-leader-error injected.") 219 resp := &kvrpcpb.SplitRegionResponse{ 220 RegionError: &errorpb.Error{ 221 NotLeader: &errorpb.NotLeader{ 222 RegionId: regionInfo.Region.Id, 223 }, 224 }, 225 } 226 if injectNewLeader.(bool) { 227 resp.RegionError.NotLeader.Leader = regionInfo.Leader 228 } 229 failpoint.Return(resp, nil) 230 }) 231 failpoint.Inject("somewhat-retryable-error", func() { 232 log.Debug("failpoint somewhat-retryable-error injected.") 233 failpoint.Return(&kvrpcpb.SplitRegionResponse{ 234 RegionError: &errorpb.Error{ 235 ServerIsBusy: &errorpb.ServerIsBusy{}, 236 }, 237 }, nil) 238 }) 239 return client.SplitRegion(ctx, &kvrpcpb.SplitRegionRequest{ 240 Context: &kvrpcpb.Context{ 241 RegionId: regionInfo.Region.Id, 242 RegionEpoch: regionInfo.Region.RegionEpoch, 243 Peer: peer, 244 }, 245 SplitKeys: keys, 246 }) 247 } 248 249 func (c *pdClient) sendSplitRegionRequest( 250 ctx context.Context, regionInfo *RegionInfo, keys [][]byte, 251 ) (*kvrpcpb.SplitRegionResponse, error) { 252 var splitErrors error 253 for i := 0; i < splitRegionMaxRetryTime; i++ { 254 var peer *metapb.Peer 255 // scanRegions may return empty Leader in https://github.com/tikv/pd/blob/v4.0.8/server/grpc_service.go#L524 256 // so wee also need check Leader.Id != 0 257 if regionInfo.Leader != nil && regionInfo.Leader.Id != 0 { 258 peer = regionInfo.Leader 259 } else { 260 if len(regionInfo.Region.Peers) == 0 { 261 return nil, multierr.Append(splitErrors, 262 errors.Annotatef(berrors.ErrRestoreNoPeer, "region[%d] doesn't have any peer", regionInfo.Region.GetId())) 263 } 264 peer = regionInfo.Region.Peers[0] 265 } 266 storeID := peer.GetStoreId() 267 store, err := c.GetStore(ctx, storeID) 268 if err != nil { 269 return nil, multierr.Append(splitErrors, err) 270 } 271 opt := grpc.WithInsecure() 272 if c.tlsConf != nil { 273 opt = grpc.WithTransportCredentials(credentials.NewTLS(c.tlsConf)) 274 } 275 conn, err := grpc.Dial(store.GetAddress(), opt) 276 if err != nil { 277 return nil, multierr.Append(splitErrors, err) 278 } 279 defer conn.Close() 280 client := tikvpb.NewTikvClient(conn) 281 resp, err := splitRegionWithFailpoint(ctx, regionInfo, peer, client, keys) 282 if err != nil { 283 return nil, multierr.Append(splitErrors, err) 284 } 285 if resp.RegionError != nil { 286 log.Error("fail to split region", 287 logutil.Region(regionInfo.Region), 288 zap.Stringer("regionErr", resp.RegionError)) 289 splitErrors = multierr.Append(splitErrors, 290 errors.Annotatef(berrors.ErrRestoreSplitFailed, "split region failed: err=%v", resp.RegionError)) 291 if nl := resp.RegionError.NotLeader; nl != nil { 292 if leader := nl.GetLeader(); leader != nil { 293 regionInfo.Leader = leader 294 } else { 295 newRegionInfo, findLeaderErr := c.GetRegionByID(ctx, nl.RegionId) 296 if findLeaderErr != nil { 297 return nil, multierr.Append(splitErrors, findLeaderErr) 298 } 299 if !checkRegionEpoch(newRegionInfo, regionInfo) { 300 return nil, multierr.Append(splitErrors, berrors.ErrKVEpochNotMatch) 301 } 302 log.Info("find new leader", zap.Uint64("new leader", newRegionInfo.Leader.Id)) 303 regionInfo = newRegionInfo 304 } 305 log.Info("split region meet not leader error, retrying", 306 zap.Int("retry times", i), 307 zap.Uint64("regionID", regionInfo.Region.Id), 308 zap.Any("new leader", regionInfo.Leader), 309 ) 310 continue 311 } 312 // TODO: we don't handle RegionNotMatch and RegionNotFound here, 313 // because I think we don't have enough information to retry. 314 // But maybe we can handle them here by some information the error itself provides. 315 if resp.RegionError.ServerIsBusy != nil || 316 resp.RegionError.StaleCommand != nil { 317 log.Warn("a error occurs on split region", 318 zap.Int("retry times", i), 319 zap.Uint64("regionID", regionInfo.Region.Id), 320 zap.String("error", resp.RegionError.Message), 321 zap.Any("error verbose", resp.RegionError), 322 ) 323 continue 324 } 325 return nil, errors.Trace(splitErrors) 326 } 327 return resp, nil 328 } 329 return nil, errors.Trace(splitErrors) 330 } 331 332 func (c *pdClient) BatchSplitRegionsWithOrigin( 333 ctx context.Context, regionInfo *RegionInfo, keys [][]byte, 334 ) (*RegionInfo, []*RegionInfo, error) { 335 resp, err := c.sendSplitRegionRequest(ctx, regionInfo, keys) 336 if err != nil { 337 return nil, nil, errors.Trace(err) 338 } 339 340 regions := resp.GetRegions() 341 newRegionInfos := make([]*RegionInfo, 0, len(regions)) 342 var originRegion *RegionInfo 343 for _, region := range regions { 344 var leader *metapb.Peer 345 346 // Assume the leaders will be at the same store. 347 if regionInfo.Leader != nil { 348 for _, p := range region.GetPeers() { 349 if p.GetStoreId() == regionInfo.Leader.GetStoreId() { 350 leader = p 351 break 352 } 353 } 354 } 355 // original region 356 if region.GetId() == regionInfo.Region.GetId() { 357 originRegion = &RegionInfo{ 358 Region: region, 359 Leader: leader, 360 } 361 continue 362 } 363 newRegionInfos = append(newRegionInfos, &RegionInfo{ 364 Region: region, 365 Leader: leader, 366 }) 367 } 368 return originRegion, newRegionInfos, nil 369 } 370 371 func (c *pdClient) BatchSplitRegions( 372 ctx context.Context, regionInfo *RegionInfo, keys [][]byte, 373 ) ([]*RegionInfo, error) { 374 _, newRegions, err := c.BatchSplitRegionsWithOrigin(ctx, regionInfo, keys) 375 return newRegions, err 376 } 377 378 func (c *pdClient) ScatterRegion(ctx context.Context, regionInfo *RegionInfo) error { 379 return c.client.ScatterRegion(ctx, regionInfo.Region.GetId()) 380 } 381 382 func (c *pdClient) GetOperator(ctx context.Context, regionID uint64) (*pdpb.GetOperatorResponse, error) { 383 return c.client.GetOperator(ctx, regionID) 384 } 385 386 func (c *pdClient) ScanRegions(ctx context.Context, key, endKey []byte, limit int) ([]*RegionInfo, error) { 387 regions, err := c.client.ScanRegions(ctx, key, endKey, limit) 388 if err != nil { 389 return nil, errors.Trace(err) 390 } 391 regionInfos := make([]*RegionInfo, 0, len(regions)) 392 for _, region := range regions { 393 regionInfos = append(regionInfos, &RegionInfo{ 394 Region: region.Meta, 395 Leader: region.Leader, 396 }) 397 } 398 return regionInfos, nil 399 } 400 401 func (c *pdClient) GetPlacementRule(ctx context.Context, groupID, ruleID string) (placement.Rule, error) { 402 var rule placement.Rule 403 addr := c.getPDAPIAddr() 404 if addr == "" { 405 return rule, errors.Annotate(berrors.ErrRestoreSplitFailed, "failed to add stores labels: no leader") 406 } 407 req, err := http.NewRequestWithContext(ctx, "GET", addr+path.Join("/pd/api/v1/config/rule", groupID, ruleID), nil) 408 if err != nil { 409 return rule, errors.Trace(err) 410 } 411 res, err := httputil.NewClient(c.tlsConf).Do(req) 412 if err != nil { 413 return rule, errors.Trace(err) 414 } 415 b, err := io.ReadAll(res.Body) 416 if err != nil { 417 return rule, errors.Trace(err) 418 } 419 res.Body.Close() 420 err = json.Unmarshal(b, &rule) 421 if err != nil { 422 return rule, errors.Trace(err) 423 } 424 return rule, nil 425 } 426 427 func (c *pdClient) SetPlacementRule(ctx context.Context, rule placement.Rule) error { 428 addr := c.getPDAPIAddr() 429 if addr == "" { 430 return errors.Annotate(berrors.ErrPDLeaderNotFound, "failed to add stores labels") 431 } 432 m, _ := json.Marshal(rule) 433 req, err := http.NewRequestWithContext(ctx, "POST", addr+path.Join("/pd/api/v1/config/rule"), bytes.NewReader(m)) 434 if err != nil { 435 return errors.Trace(err) 436 } 437 res, err := httputil.NewClient(c.tlsConf).Do(req) 438 if err != nil { 439 return errors.Trace(err) 440 } 441 return errors.Trace(res.Body.Close()) 442 } 443 444 func (c *pdClient) DeletePlacementRule(ctx context.Context, groupID, ruleID string) error { 445 addr := c.getPDAPIAddr() 446 if addr == "" { 447 return errors.Annotate(berrors.ErrPDLeaderNotFound, "failed to add stores labels") 448 } 449 req, err := http.NewRequestWithContext(ctx, "DELETE", addr+path.Join("/pd/api/v1/config/rule", groupID, ruleID), nil) 450 if err != nil { 451 return errors.Trace(err) 452 } 453 res, err := httputil.NewClient(c.tlsConf).Do(req) 454 if err != nil { 455 return errors.Trace(err) 456 } 457 return errors.Trace(res.Body.Close()) 458 } 459 460 func (c *pdClient) SetStoresLabel( 461 ctx context.Context, stores []uint64, labelKey, labelValue string, 462 ) error { 463 b := []byte(fmt.Sprintf(`{"%s": "%s"}`, labelKey, labelValue)) 464 addr := c.getPDAPIAddr() 465 if addr == "" { 466 return errors.Annotate(berrors.ErrPDLeaderNotFound, "failed to add stores labels") 467 } 468 httpCli := httputil.NewClient(c.tlsConf) 469 for _, id := range stores { 470 req, err := http.NewRequestWithContext( 471 ctx, "POST", 472 addr+path.Join("/pd/api/v1/store", strconv.FormatUint(id, 10), "label"), 473 bytes.NewReader(b), 474 ) 475 if err != nil { 476 return errors.Trace(err) 477 } 478 res, err := httpCli.Do(req) 479 if err != nil { 480 return errors.Trace(err) 481 } 482 err = res.Body.Close() 483 if err != nil { 484 return errors.Trace(err) 485 } 486 } 487 return nil 488 } 489 490 func (c *pdClient) getPDAPIAddr() string { 491 addr := c.client.GetLeaderAddr() 492 if addr != "" && !strings.HasPrefix(addr, "http") { 493 addr = "http://" + addr 494 } 495 return strings.TrimRight(addr, "/") 496 } 497 498 func checkRegionEpoch(new, old *RegionInfo) bool { 499 return new.Region.GetId() == old.Region.GetId() && 500 new.Region.GetRegionEpoch().GetVersion() == old.Region.GetRegionEpoch().GetVersion() && 501 new.Region.GetRegionEpoch().GetConfVer() == old.Region.GetRegionEpoch().GetConfVer() 502 } 503 504 type scatterBackoffer struct { 505 attempt int 506 baseBackoff time.Duration 507 } 508 509 func (b *scatterBackoffer) exponentialBackoff() time.Duration { 510 bo := b.baseBackoff 511 b.attempt-- 512 if b.attempt == 0 { 513 return 0 514 } 515 b.baseBackoff *= 2 516 return bo 517 } 518 519 func (b *scatterBackoffer) giveUp() time.Duration { 520 b.attempt = 0 521 return 0 522 } 523 524 // NextBackoff returns a duration to wait before retrying again 525 func (b *scatterBackoffer) NextBackoff(err error) time.Duration { 526 // There are 3 type of reason that PD would reject a `scatter` request: 527 // (1) region %d has no leader 528 // (2) region %d is hot 529 // (3) region %d is not fully replicated 530 // 531 // (2) shouldn't happen in a recently splitted region. 532 // (1) and (3) might happen, and should be retried. 533 grpcErr := status.Convert(err) 534 if grpcErr == nil { 535 return b.giveUp() 536 } 537 if strings.Contains(grpcErr.Message(), "is not fully replicated") { 538 log.Info("scatter region failed, retring", logutil.ShortError(err), zap.Int("attempt-remain", b.attempt)) 539 return b.exponentialBackoff() 540 } 541 if strings.Contains(grpcErr.Message(), "has no leader") { 542 log.Info("scatter region failed, retring", logutil.ShortError(err), zap.Int("attempt-remain", b.attempt)) 543 return b.exponentialBackoff() 544 } 545 return b.giveUp() 546 } 547 548 // Attempt returns the remain attempt times 549 func (b *scatterBackoffer) Attempt() int { 550 return b.attempt 551 }