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  }