github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/dm/relay/meta.go (about) 1 // Copyright 2019 PingCAP, 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 relay 15 16 import ( 17 "bytes" 18 "fmt" 19 "os" 20 "path/filepath" 21 "sync" 22 23 "github.com/BurntSushi/toml" 24 "github.com/go-mysql-org/go-mysql/mysql" 25 "github.com/pingcap/tiflow/dm/pkg/gtid" 26 "github.com/pingcap/tiflow/dm/pkg/terror" 27 "github.com/pingcap/tiflow/dm/pkg/utils" 28 ) 29 30 var ( 31 minUUIDSuffix = 1 32 minCheckpoint = mysql.Position{Pos: 4} 33 ) 34 35 // Meta represents relay log meta information for sync source 36 // when re-syncing, we should reload meta info to guarantee continuous transmission 37 // in order to support master-slave switching, Meta should support switching binlog meta info to newer master 38 // should support the case, where switching from A to B, then switching from B back to A. 39 type Meta interface { 40 // Load loads meta information for the recently active server 41 Load() error 42 43 // AdjustWithStartPos adjusts current pos / GTID with start pos 44 // if current pos / GTID is meaningless, update to start pos or last pos when start pos is meaningless 45 // else do nothing 46 AdjustWithStartPos(binlogName string, binlogGTID string, enableGTID bool, latestBinlogName string, latestBinlogGTID string) (bool, error) 47 48 // Save saves meta information 49 Save(pos mysql.Position, gset mysql.GTIDSet) error 50 51 // Flush flushes meta information 52 Flush() error 53 54 // Dirty checks whether meta in memory is dirty (need to Flush) 55 Dirty() bool 56 57 // AddDir adds relay log subdirectory for a new server. The name of new subdirectory 58 // consists of the server_uuid of new server and a suffix. 59 // if suffix is not zero value, add sub relay directory with suffix (bound to a new source) 60 // otherwise the added sub relay directory's suffix is incremented (master/slave switch) 61 // after sub relay directory added, the internal binlog pos should be reset 62 // and binlog pos will be set again when new binlog events received 63 // if set @newPos / @newGTID, old value will be replaced 64 AddDir(serverUUID string, newPos *mysql.Position, newGTID mysql.GTIDSet, suffix int) error 65 66 // Pos returns current (UUID with suffix, Position) pair 67 Pos() (string, mysql.Position) 68 69 // GTID returns current (UUID with suffix, GTID) pair 70 GTID() (string, mysql.GTIDSet) 71 72 // SubDir returns the name of current relay log subdirectory. 73 SubDir() string 74 75 // TrimUUIDIndexFile trim invalid relay log subdirectories from memory and update the server-uuid.index file 76 // return trimmed result. 77 TrimUUIDIndexFile() ([]string, error) 78 79 // Dir returns the full path of relay log subdirectory. 80 Dir() string 81 82 // String returns string representation of current meta info 83 String() string 84 } 85 86 // LocalMeta implements Meta by save info in local. 87 type LocalMeta struct { 88 sync.RWMutex 89 flavor string 90 baseDir string 91 uuidIndexPath string 92 currentSubDir string 93 subDirs []string 94 gset mysql.GTIDSet 95 emptyGSet mysql.GTIDSet 96 dirty bool 97 98 BinLogName string `toml:"binlog-name" json:"binlog-name"` 99 BinLogPos uint32 `toml:"binlog-pos" json:"binlog-pos"` 100 BinlogGTID string `toml:"binlog-gtid" json:"binlog-gtid"` 101 } 102 103 // NewLocalMeta creates a new LocalMeta. 104 func NewLocalMeta(flavor, baseDir string) Meta { 105 lm := &LocalMeta{ 106 flavor: flavor, 107 baseDir: baseDir, 108 uuidIndexPath: filepath.Join(baseDir, utils.UUIDIndexFilename), 109 currentSubDir: "", 110 subDirs: make([]string, 0), 111 dirty: false, 112 BinLogName: minCheckpoint.Name, 113 BinLogPos: minCheckpoint.Pos, 114 BinlogGTID: "", 115 } 116 lm.emptyGSet, _ = gtid.ParserGTID(flavor, "") 117 return lm 118 } 119 120 // Load implements Meta.Load. 121 func (lm *LocalMeta) Load() error { 122 lm.Lock() 123 defer lm.Unlock() 124 125 subDirs, err := utils.ParseUUIDIndex(lm.uuidIndexPath) 126 if err != nil { 127 return err 128 } 129 130 err = lm.verifySubDirs(subDirs) 131 if err != nil { 132 return err 133 } 134 135 if len(subDirs) > 0 { 136 // update to the latest 137 err = lm.updateCurrentSubDir(subDirs[len(subDirs)-1]) 138 if err != nil { 139 return err 140 } 141 } 142 lm.subDirs = subDirs 143 144 err = lm.loadMetaData() 145 if err != nil { 146 return err 147 } 148 149 return nil 150 } 151 152 // AdjustWithStartPos implements Meta.AdjustWithStartPos, return whether adjusted. 153 func (lm *LocalMeta) AdjustWithStartPos(binlogName string, binlogGTID string, enableGTID bool, latestBinlogName string, latestBinlogGTID string) (bool, error) { 154 lm.Lock() 155 defer lm.Unlock() 156 157 // check whether already have meaningful pos 158 if len(lm.currentSubDir) > 0 { 159 _, suffix, err := utils.ParseRelaySubDir(lm.currentSubDir) 160 if err != nil { 161 return false, err 162 } 163 currPos := mysql.Position{Name: lm.BinLogName, Pos: lm.BinLogPos} 164 if suffix != minUUIDSuffix || currPos.Compare(minCheckpoint) > 0 || len(lm.BinlogGTID) > 0 { 165 return false, nil // current pos is meaningful, do nothing 166 } 167 } 168 169 gset := lm.emptyGSet.Clone() 170 var err error 171 172 if enableGTID { 173 if len(binlogGTID) == 0 { 174 binlogGTID = latestBinlogGTID 175 binlogName = latestBinlogName 176 } 177 gset, err = gtid.ParserGTID(lm.flavor, binlogGTID) 178 if err != nil { 179 return false, terror.Annotatef(err, "relay-binlog-gtid %s", binlogGTID) 180 } 181 } else { 182 if len(binlogName) == 0 { // no meaningful start pos specified 183 binlogGTID = latestBinlogGTID 184 binlogName = latestBinlogName 185 } else if !utils.VerifyFilename(binlogName) { 186 return false, terror.ErrRelayBinlogNameNotValid.Generate(binlogName) 187 } 188 } 189 190 lm.BinLogName = binlogName 191 lm.BinLogPos = minCheckpoint.Pos // always set pos to 4 192 lm.BinlogGTID = binlogGTID 193 lm.gset = gset 194 195 return true, lm.doFlush() 196 } 197 198 // Save implements Meta.Save. 199 func (lm *LocalMeta) Save(pos mysql.Position, gset mysql.GTIDSet) error { 200 lm.Lock() 201 defer lm.Unlock() 202 203 if len(lm.currentSubDir) == 0 { 204 return terror.ErrRelayNoCurrentUUID.Generate() 205 } 206 207 lm.BinLogName = pos.Name 208 lm.BinLogPos = pos.Pos 209 if gset == nil { 210 lm.BinlogGTID = "" 211 } else { 212 lm.BinlogGTID = gset.String() 213 lm.gset = gset.Clone() // need to clone and set, in order to avoid the local meta's gset and the input gset referencing the same object, causing contentions later 214 } 215 216 lm.dirty = true 217 218 return nil 219 } 220 221 // Flush implements Meta.Flush. 222 func (lm *LocalMeta) Flush() error { 223 lm.Lock() 224 defer lm.Unlock() 225 226 return lm.doFlush() 227 } 228 229 // doFlush does the real flushing. 230 func (lm *LocalMeta) doFlush() error { 231 if len(lm.currentSubDir) == 0 { 232 return terror.ErrRelayNoCurrentUUID.Generate() 233 } 234 235 var buf bytes.Buffer 236 enc := toml.NewEncoder(&buf) 237 err := enc.Encode(lm) 238 if err != nil { 239 return terror.ErrRelayFlushLocalMeta.Delegate(err) 240 } 241 242 filename := filepath.Join(lm.baseDir, lm.currentSubDir, utils.MetaFilename) 243 err = utils.WriteFileAtomic(filename, buf.Bytes(), 0o644) 244 if err != nil { 245 return terror.ErrRelayFlushLocalMeta.Delegate(err) 246 } 247 248 lm.dirty = false 249 250 return nil 251 } 252 253 // Dirty implements Meta.Dirty. 254 func (lm *LocalMeta) Dirty() bool { 255 lm.RLock() 256 defer lm.RUnlock() 257 258 return lm.dirty 259 } 260 261 // Dir implements Meta.Dir. 262 func (lm *LocalMeta) Dir() string { 263 lm.RLock() 264 defer lm.RUnlock() 265 266 return filepath.Join(lm.baseDir, lm.currentSubDir) 267 } 268 269 // AddDir implements Meta.AddDir. 270 func (lm *LocalMeta) AddDir(serverUUID string, newPos *mysql.Position, newGTID mysql.GTIDSet, uuidSuffix int) error { 271 lm.Lock() 272 defer lm.Unlock() 273 274 var newSubDir string 275 276 if len(lm.currentSubDir) == 0 { 277 // no UUID exists yet, simply add it 278 if uuidSuffix == 0 { 279 newSubDir = utils.AddSuffixForUUID(serverUUID, minUUIDSuffix) 280 } else { 281 newSubDir = utils.AddSuffixForUUID(serverUUID, uuidSuffix) 282 } 283 } else { 284 _, suffix, err := utils.ParseRelaySubDir(lm.currentSubDir) 285 if err != nil { 286 return err 287 } 288 // even newSubDir == currentSubDir, we still append it (for some cases, like `RESET MASTER`) 289 newSubDir = utils.AddSuffixForUUID(serverUUID, suffix+1) 290 } 291 292 // flush previous meta 293 if lm.dirty { 294 err := lm.doFlush() 295 if err != nil { 296 return err 297 } 298 } 299 300 // make sub dir for UUID 301 err := os.Mkdir(filepath.Join(lm.baseDir, newSubDir), 0o744) 302 if err != nil { 303 return terror.ErrRelayMkdir.Delegate(err) 304 } 305 306 // update UUID index file 307 uuids := lm.subDirs 308 uuids = append(uuids, newSubDir) 309 err = lm.updateIndexFile(uuids) 310 if err != nil { 311 return err 312 } 313 314 // update current UUID 315 lm.currentSubDir = newSubDir 316 lm.subDirs = uuids 317 318 if newPos != nil { 319 lm.BinLogName = newPos.Name 320 lm.BinLogPos = newPos.Pos 321 } else { 322 // reset binlog pos, will be set again when new binlog events received from master 323 // not reset GTID, it will be used to continue the syncing 324 lm.BinLogName = minCheckpoint.Name 325 lm.BinLogPos = minCheckpoint.Pos 326 } 327 328 if newGTID != nil { 329 lm.gset = newGTID.Clone() // need to clone and set, in order to avoid the local meta's gset and the input newGTID referencing the same object, causing contentions later 330 lm.BinlogGTID = newGTID.String() 331 } // if newGTID == nil, keep GTID not changed 332 333 // flush new meta to file 334 return lm.doFlush() 335 } 336 337 // Pos implements Meta.Pos. 338 func (lm *LocalMeta) Pos() (string, mysql.Position) { 339 lm.RLock() 340 defer lm.RUnlock() 341 342 return lm.currentSubDir, mysql.Position{Name: lm.BinLogName, Pos: lm.BinLogPos} 343 } 344 345 // GTID implements Meta.GTID. 346 func (lm *LocalMeta) GTID() (string, mysql.GTIDSet) { 347 lm.RLock() 348 defer lm.RUnlock() 349 350 if lm.gset != nil { 351 return lm.currentSubDir, lm.gset.Clone() 352 } 353 return lm.currentSubDir, nil 354 } 355 356 // SubDir implements Meta.SubDir. 357 func (lm *LocalMeta) SubDir() string { 358 lm.RLock() 359 defer lm.RUnlock() 360 return lm.currentSubDir 361 } 362 363 // TrimUUIDIndexFile implements Meta.TrimUUIDIndexFile. 364 func (lm *LocalMeta) TrimUUIDIndexFile() ([]string, error) { 365 lm.Lock() 366 defer lm.Unlock() 367 368 kept := make([]string, 0, len(lm.subDirs)) 369 trimmed := make([]string, 0) 370 for _, subDir := range lm.subDirs { 371 // now, only check if the sub dir exists 372 fp := filepath.Join(lm.baseDir, subDir) 373 if utils.IsDirExists(fp) { 374 kept = append(kept, subDir) 375 } else { 376 trimmed = append(trimmed, subDir) 377 } 378 } 379 380 if len(trimmed) == 0 { 381 return nil, nil 382 } 383 384 err := lm.updateIndexFile(kept) 385 if err != nil { 386 return nil, err 387 } 388 389 // currentSubDir should be not changed 390 lm.subDirs = kept 391 return trimmed, nil 392 } 393 394 // String implements Meta.String. 395 func (lm *LocalMeta) String() string { 396 uuid, pos := lm.Pos() 397 _, gs := lm.GTID() 398 return fmt.Sprintf("master-uuid = %s, relay-binlog = %v, relay-binlog-gtid = %v", uuid, pos, gs) 399 } 400 401 // updateIndexFile updates the content of server-uuid.index file. 402 func (lm *LocalMeta) updateIndexFile(uuids []string) error { 403 var buf bytes.Buffer 404 for _, uuid := range uuids { 405 buf.WriteString(uuid) 406 buf.WriteString("\n") 407 } 408 409 err := utils.WriteFileAtomic(lm.uuidIndexPath, buf.Bytes(), 0o644) 410 return terror.ErrRelayUpdateIndexFile.Delegate(err, lm.uuidIndexPath) 411 } 412 413 func (lm *LocalMeta) verifySubDirs(uuids []string) error { 414 previousSuffix := 0 415 for _, uuid := range uuids { 416 _, suffix, err := utils.ParseRelaySubDir(uuid) 417 if err != nil { 418 return terror.Annotatef(err, "UUID %s", uuid) 419 } 420 if previousSuffix > 0 { 421 if previousSuffix+1 != suffix { 422 return terror.ErrRelayUUIDSuffixNotValid.Generate(uuid, suffix, previousSuffix) 423 } 424 } 425 previousSuffix = suffix 426 } 427 428 return nil 429 } 430 431 // updateCurrentSubDir updates current relay log subdirectory. 432 func (lm *LocalMeta) updateCurrentSubDir(uuid string) error { 433 _, suffix, err := utils.ParseRelaySubDir(uuid) 434 if err != nil { 435 return err 436 } 437 438 if len(lm.currentSubDir) > 0 { 439 _, previousSuffix, err := utils.ParseRelaySubDir(lm.currentSubDir) 440 if err != nil { 441 return err // should not happen 442 } 443 if previousSuffix > suffix { 444 return terror.ErrRelayUUIDSuffixLessThanPrev.Generate(lm.currentSubDir, uuid) 445 } 446 } 447 448 lm.currentSubDir = uuid 449 return nil 450 } 451 452 // loadMetaData loads meta information from meta data file. 453 func (lm *LocalMeta) loadMetaData() error { 454 lm.gset = lm.emptyGSet.Clone() 455 456 if len(lm.currentSubDir) == 0 { 457 return nil 458 } 459 460 filename := filepath.Join(lm.baseDir, lm.currentSubDir, utils.MetaFilename) 461 462 fd, err := os.Open(filename) 463 if os.IsNotExist(err) { 464 return nil 465 } else if err != nil { 466 return terror.ErrRelayLoadMetaData.Delegate(err) 467 } 468 defer fd.Close() 469 470 _, err = toml.DecodeReader(fd, lm) 471 if err != nil { 472 return terror.ErrRelayLoadMetaData.Delegate(err) 473 } 474 475 if len(lm.BinlogGTID) != 0 { 476 gset, err := gtid.ParserGTID("", lm.BinlogGTID) 477 if err != nil { 478 return terror.ErrRelayLoadMetaData.Delegate(err) 479 } 480 lm.gset = gset 481 } 482 483 return nil 484 }