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 }