github.com/whtcorpsinc/MilevaDB-Prod@v0.0.0-20211104133533-f57f4be3b597/causetstore/stochastikctx/binloginfo/binloginfo.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 binloginfo
    15  
    16  import (
    17  	"math"
    18  	"regexp"
    19  	"strings"
    20  	"sync"
    21  	"sync/atomic"
    22  	"time"
    23  
    24  	"github.com/whtcorpsinc/BerolinaSQL/terror"
    25  	"github.com/whtcorpsinc/errors"
    26  	"github.com/whtcorpsinc/fidelpb/go-binlog"
    27  	"github.com/whtcorpsinc/milevadb-tools/milevadb-binlog/node"
    28  	pumpcli "github.com/whtcorpsinc/milevadb-tools/milevadb-binlog/pump_client"
    29  	"github.com/whtcorpsinc/milevadb/config"
    30  	"github.com/whtcorpsinc/milevadb/ekv"
    31  	"github.com/whtcorpsinc/milevadb/metrics"
    32  	"github.com/whtcorpsinc/milevadb/soliton/logutil"
    33  	"github.com/whtcorpsinc/milevadb/stochastikctx"
    34  	driver "github.com/whtcorpsinc/milevadb/types/BerolinaSQL_driver"
    35  	"go.uber.org/zap"
    36  	"google.golang.org/grpc"
    37  )
    38  
    39  func init() {
    40  	grpc.EnableTracing = false
    41  }
    42  
    43  // pumpsClient is the client to write binlog, it is opened on server start and never close,
    44  // shared by all stochastik.
    45  var pumpsClient *pumpcli.PumpsClient
    46  var pumpsClientLock sync.RWMutex
    47  var shardPat = regexp.MustCompile(`SHARD_ROW_ID_BITS\s*=\s*\d+\s*`)
    48  var preSplitPat = regexp.MustCompile(`PRE_SPLIT_REGIONS\s*=\s*\d+\s*`)
    49  var autoRandomPat = regexp.MustCompile(`AUTO_RANDOM\s*\(\s*\d+\s*\)\s*`)
    50  
    51  // BinlogInfo contains binlog data and binlog client.
    52  type BinlogInfo struct {
    53  	Data   *binlog.Binlog
    54  	Client *pumpcli.PumpsClient
    55  }
    56  
    57  // BinlogStatus is the status of binlog
    58  type BinlogStatus int
    59  
    60  const (
    61  	//BinlogStatusUnknown stands for unknown binlog status
    62  	BinlogStatusUnknown BinlogStatus = iota
    63  	//BinlogStatusOn stands for the binlog is enabled
    64  	BinlogStatusOn
    65  	//BinlogStatusOff stands for the binlog is disabled
    66  	BinlogStatusOff
    67  	//BinlogStatusSkipping stands for the binlog status
    68  	BinlogStatusSkipping
    69  )
    70  
    71  // String implements String function in fmt.Stringer
    72  func (s BinlogStatus) String() string {
    73  	switch s {
    74  	case BinlogStatusOn:
    75  		return "On"
    76  	case BinlogStatusOff:
    77  		return "Off"
    78  	case BinlogStatusSkipping:
    79  		return "Skipping"
    80  	}
    81  	return "Unknown"
    82  }
    83  
    84  // GetPumpsClient gets the pumps client instance.
    85  func GetPumpsClient() *pumpcli.PumpsClient {
    86  	pumpsClientLock.RLock()
    87  	client := pumpsClient
    88  	pumpsClientLock.RUnlock()
    89  	return client
    90  }
    91  
    92  // SetPumpsClient sets the pumps client instance.
    93  func SetPumpsClient(client *pumpcli.PumpsClient) {
    94  	pumpsClientLock.Lock()
    95  	pumpsClient = client
    96  	pumpsClientLock.Unlock()
    97  }
    98  
    99  // GetPrewriteValue gets binlog prewrite value in the context.
   100  func GetPrewriteValue(ctx stochastikctx.Context, createIfNotExists bool) *binlog.PrewriteValue {
   101  	vars := ctx.GetStochastikVars()
   102  	v, ok := vars.TxnCtx.Binlog.(*binlog.PrewriteValue)
   103  	if !ok && createIfNotExists {
   104  		schemaVer := ctx.GetStochastikVars().TxnCtx.SchemaVersion
   105  		v = &binlog.PrewriteValue{SchemaVersion: schemaVer}
   106  		vars.TxnCtx.Binlog = v
   107  	}
   108  	return v
   109  }
   110  
   111  var skipBinlog uint32
   112  var ignoreError uint32
   113  var statusListener = func(_ BinlogStatus) error {
   114  	return nil
   115  }
   116  
   117  // EnableSkipBinlogFlag enables the skipBinlog flag.
   118  // NOTE: it is used *ONLY* for test.
   119  func EnableSkipBinlogFlag() {
   120  	atomic.StoreUint32(&skipBinlog, 1)
   121  	logutil.BgLogger().Warn("[binloginfo] enable the skipBinlog flag")
   122  }
   123  
   124  // DisableSkipBinlogFlag disable the skipBinlog flag.
   125  func DisableSkipBinlogFlag() {
   126  	atomic.StoreUint32(&skipBinlog, 0)
   127  	logutil.BgLogger().Warn("[binloginfo] disable the skipBinlog flag")
   128  }
   129  
   130  // IsBinlogSkipped gets the skipBinlog flag.
   131  func IsBinlogSkipped() bool {
   132  	return atomic.LoadUint32(&skipBinlog) > 0
   133  }
   134  
   135  // BinlogRecoverStatus is used for display the binlog recovered status after some operations.
   136  type BinlogRecoverStatus struct {
   137  	Skipped                 bool
   138  	SkippedCommitterCounter int32
   139  }
   140  
   141  // GetBinlogStatus returns the binlog recovered status.
   142  func GetBinlogStatus() *BinlogRecoverStatus {
   143  	return &BinlogRecoverStatus{
   144  		Skipped:                 IsBinlogSkipped(),
   145  		SkippedCommitterCounter: SkippedCommitterCount(),
   146  	}
   147  }
   148  
   149  var skippedCommitterCounter int32
   150  
   151  // WaitBinlogRecover returns when all committing transaction finished.
   152  func WaitBinlogRecover(timeout time.Duration) error {
   153  	logutil.BgLogger().Warn("[binloginfo] start waiting for binlog recovering")
   154  	ticker := time.NewTicker(500 * time.Millisecond)
   155  	defer ticker.Stop()
   156  	start := time.Now()
   157  	for {
   158  		select {
   159  		case <-ticker.C:
   160  			if atomic.LoadInt32(&skippedCommitterCounter) == 0 {
   161  				logutil.BgLogger().Warn("[binloginfo] binlog recovered")
   162  				return nil
   163  			}
   164  			if time.Since(start) > timeout {
   165  				logutil.BgLogger().Warn("[binloginfo] waiting for binlog recovering timed out",
   166  					zap.Duration("duration", timeout))
   167  				return errors.New("timeout")
   168  			}
   169  		}
   170  	}
   171  }
   172  
   173  // SkippedCommitterCount returns the number of alive committers whick skipped the binlog writing.
   174  func SkippedCommitterCount() int32 {
   175  	return atomic.LoadInt32(&skippedCommitterCounter)
   176  }
   177  
   178  // ResetSkippedCommitterCounter is used to reset the skippedCommitterCounter.
   179  func ResetSkippedCommitterCounter() {
   180  	atomic.StoreInt32(&skippedCommitterCounter, 0)
   181  	logutil.BgLogger().Warn("[binloginfo] skippedCommitterCounter is reset to 0")
   182  }
   183  
   184  // AddOneSkippedCommitter adds one committer to skippedCommitterCounter.
   185  func AddOneSkippedCommitter() {
   186  	atomic.AddInt32(&skippedCommitterCounter, 1)
   187  }
   188  
   189  // RemoveOneSkippedCommitter removes one committer from skippedCommitterCounter.
   190  func RemoveOneSkippedCommitter() {
   191  	atomic.AddInt32(&skippedCommitterCounter, -1)
   192  }
   193  
   194  // SetIgnoreError sets the ignoreError flag, this function called when MilevaDB start
   195  // up and find config.Binlog.IgnoreError is true.
   196  func SetIgnoreError(on bool) {
   197  	if on {
   198  		atomic.StoreUint32(&ignoreError, 1)
   199  	} else {
   200  		atomic.StoreUint32(&ignoreError, 0)
   201  	}
   202  }
   203  
   204  // GetStatus gets the status of binlog
   205  func GetStatus() BinlogStatus {
   206  	conf := config.GetGlobalConfig()
   207  	if !conf.Binlog.Enable {
   208  		return BinlogStatusOff
   209  	}
   210  	skip := atomic.LoadUint32(&skipBinlog)
   211  	if skip > 0 {
   212  		return BinlogStatusSkipping
   213  	}
   214  	return BinlogStatusOn
   215  }
   216  
   217  // RegisterStatusListener registers a listener function to watch binlog status
   218  func RegisterStatusListener(listener func(BinlogStatus) error) {
   219  	statusListener = listener
   220  }
   221  
   222  // WriteResult is used for the returned chan of WriteBinlog.
   223  type WriteResult struct {
   224  	skipped bool
   225  	err     error
   226  }
   227  
   228  // Skipped if true stands for the binlog writing is skipped.
   229  func (wr *WriteResult) Skipped() bool {
   230  	return wr.skipped
   231  }
   232  
   233  // GetError gets the error of WriteBinlog.
   234  func (wr *WriteResult) GetError() error {
   235  	return wr.err
   236  }
   237  
   238  // WriteBinlog writes a binlog to Pump.
   239  func (info *BinlogInfo) WriteBinlog(clusterID uint64) *WriteResult {
   240  	skip := atomic.LoadUint32(&skipBinlog)
   241  	if skip > 0 {
   242  		metrics.CriticalErrorCounter.Add(1)
   243  		return &WriteResult{true, nil}
   244  	}
   245  
   246  	if info.Client == nil {
   247  		return &WriteResult{false, errors.New("pumps client is nil")}
   248  	}
   249  
   250  	// it will retry in PumpsClient if write binlog fail.
   251  	err := info.Client.WriteBinlog(info.Data)
   252  	if err != nil {
   253  		logutil.BgLogger().Error("write binlog failed",
   254  			zap.String("binlog_type", info.Data.Tp.String()),
   255  			zap.Uint64("binlog_start_ts", uint64(info.Data.StartTs)),
   256  			zap.Uint64("binlog_commit_ts", uint64(info.Data.CommitTs)),
   257  			zap.Error(err))
   258  		if atomic.LoadUint32(&ignoreError) == 1 {
   259  			logutil.BgLogger().Error("write binlog fail but error ignored")
   260  			metrics.CriticalErrorCounter.Add(1)
   261  			// If error happens once, we'll stop writing binlog.
   262  			swapped := atomic.CompareAndSwapUint32(&skipBinlog, skip, skip+1)
   263  			if swapped && skip == 0 {
   264  				if err := statusListener(BinlogStatusSkipping); err != nil {
   265  					logutil.BgLogger().Warn("uFIDelate binlog status failed", zap.Error(err))
   266  				}
   267  			}
   268  			return &WriteResult{true, nil}
   269  		}
   270  
   271  		if strings.Contains(err.Error(), "received message larger than max") {
   272  			// This HoTT of error is not critical, return directly.
   273  			return &WriteResult{false, errors.Errorf("binlog data is too large (%s)", err.Error())}
   274  		}
   275  
   276  		return &WriteResult{false, terror.ErrCritical.GenWithStackByArgs(err)}
   277  	}
   278  
   279  	return &WriteResult{false, nil}
   280  }
   281  
   282  // SetDBSBinlog sets DBS binlog in the ekv.Transaction.
   283  func SetDBSBinlog(client *pumpcli.PumpsClient, txn ekv.Transaction, jobID int64, dbsSchemaState int32, dbsQuery string) {
   284  	if client == nil {
   285  		return
   286  	}
   287  
   288  	dbsQuery = AddSpecialComment(dbsQuery)
   289  	info := &BinlogInfo{
   290  		Data: &binlog.Binlog{
   291  			Tp:             binlog.BinlogType_Prewrite,
   292  			DdlJobId:       jobID,
   293  			DdlSchemaState: dbsSchemaState,
   294  			DdlQuery:       []byte(dbsQuery),
   295  		},
   296  		Client: client,
   297  	}
   298  	txn.SetOption(ekv.BinlogInfo, info)
   299  }
   300  
   301  const specialPrefix = `/*T! `
   302  
   303  // AddSpecialComment uses to add comment for causet option in DBS query.
   304  // Export for testing.
   305  func AddSpecialComment(dbsQuery string) string {
   306  	if strings.Contains(dbsQuery, specialPrefix) || strings.Contains(dbsQuery, driver.SpecialCommentVersionPrefix) {
   307  		return dbsQuery
   308  	}
   309  	dbsQuery = addSpecialCommentByRegexps(dbsQuery, specialPrefix, shardPat, preSplitPat)
   310  	for featureID, pattern := range driver.FeatureIDPatterns {
   311  		dbsQuery = addSpecialCommentByRegexps(dbsQuery, driver.BuildSpecialCommentPrefix(featureID), pattern)
   312  	}
   313  	return dbsQuery
   314  }
   315  
   316  // addSpecialCommentByRegexps uses to add special comment for the worlds in the dbsQuery with match the regexps.
   317  // addSpecialCommentByRegexps will merge multi pattern regs to one special comment.
   318  func addSpecialCommentByRegexps(dbsQuery string, prefix string, regs ...*regexp.Regexp) string {
   319  	upperQuery := strings.ToUpper(dbsQuery)
   320  	var specialComments []string
   321  	minIdx := math.MaxInt64
   322  	for i := 0; i < len(regs); {
   323  		reg := regs[i]
   324  		loc := reg.FindStringIndex(upperQuery)
   325  		if len(loc) < 2 {
   326  			i++
   327  			continue
   328  		}
   329  		specialComments = append(specialComments, dbsQuery[loc[0]:loc[1]])
   330  		if loc[0] < minIdx {
   331  			minIdx = loc[0]
   332  		}
   333  		dbsQuery = dbsQuery[:loc[0]] + dbsQuery[loc[1]:]
   334  		upperQuery = upperQuery[:loc[0]] + upperQuery[loc[1]:]
   335  	}
   336  	if minIdx != math.MaxInt64 {
   337  		query := dbsQuery[:minIdx] + prefix
   338  		for _, comment := range specialComments {
   339  			if query[len(query)-1] != ' ' {
   340  				query += " "
   341  			}
   342  			query += comment
   343  		}
   344  		if query[len(query)-1] != ' ' {
   345  			query += " "
   346  		}
   347  		query += "*/"
   348  		if len(dbsQuery[minIdx:]) > 0 {
   349  			return query + " " + dbsQuery[minIdx:]
   350  		}
   351  		return query
   352  	}
   353  	return dbsQuery
   354  }
   355  
   356  // MockPumpsClient creates a PumpsClient, used for test.
   357  func MockPumpsClient(client binlog.PumpClient) *pumpcli.PumpsClient {
   358  	nodeID := "pump-1"
   359  	pump := &pumpcli.PumpStatus{
   360  		Status: node.Status{
   361  			NodeID: nodeID,
   362  			State:  node.Online,
   363  		},
   364  		Client: client,
   365  	}
   366  
   367  	pumpInfos := &pumpcli.PumpInfos{
   368  		Pumps:            make(map[string]*pumpcli.PumpStatus),
   369  		AvaliablePumps:   make(map[string]*pumpcli.PumpStatus),
   370  		UnAvaliablePumps: make(map[string]*pumpcli.PumpStatus),
   371  	}
   372  	pumpInfos.Pumps[nodeID] = pump
   373  	pumpInfos.AvaliablePumps[nodeID] = pump
   374  
   375  	pCli := &pumpcli.PumpsClient{
   376  		ClusterID:          1,
   377  		Pumps:              pumpInfos,
   378  		Selector:           pumpcli.NewSelector(pumpcli.Range),
   379  		BinlogWriteTimeout: time.Second,
   380  	}
   381  	pCli.Selector.SetPumps([]*pumpcli.PumpStatus{pump})
   382  
   383  	return pCli
   384  }