GTID (global transaction IDentifier) 是全局事务标识。具有全局唯一性,一个 GTID 在一个服务器上只执行一次,从而避免重复执行导致数据混乱或主从不一致。
- GTID 怎么保证的全局唯一性?
GTID = source_id(数据库uuid即server_uuid) : transaction_id(从1开始自增长的序列号) - 什么是 server_uuid ?
MySQL5.6 以后采用128位的 server_uuid 代替了32位的 server_id,server_id 依赖于my.cnf的手工配置,可能会有冲突,128位的uuid算法可以保证所有的 MySQL_uuid 不发生冲突。 - uuid算法怎么保证不冲突的?
uuid=时间戳(当前时间戳,格林威治标准时间以来的纳秒数)+节点(机器Mac地址哈希)+时钟序列(随机生成两个字节) - GTID 的优势
在传统的复制里面,当发生故障需要主从切换时,服务器需要找到 binlog 和 pos 点,然后将其设为新的主节点开启复制,相对来说比较麻烦,也容易出错。在MySQL5.6里面,MySQL会通过内部机制自动匹配GTID断点,不再寻找 binlog 和 pos 点。 - MySQL 怎么自动匹配 GTID 断点?
- 在每个事务的开头,GTID 被获取并保存在内存中。事务完成后,GTID被记录到二进制日志中,并标记事务已提交。
- GTID EXCUTED 是从服务器中已经执行的 GTID 集合,会在内存和磁盘中同步保存,每次从服务器同步完成一个事务后,都会更新这些集合状态。
- 在服务器恢复时,MySQL 会读取 GTID 集合并恢复状态。通过对比 GTID 集合,确保恢复后的数据一致性
模拟GTID复制
go
package main
import (
"fmt"
"io/ioutil"
"os"
"strings"
"sync"
"time"
)
func initializeFile(filePath string, content string) {
ioutil.WriteFile(filePath, []byte(content), 0644)
}
func appendToFile(filePath, content string) {
f, _ := os.OpenFile(filePath, os.O_APPEND|os.O_WRONLY, 0644)
defer f.Close()
f.WriteString(content)
}
func readFile(filePath string) string {
content, _ := ioutil.ReadFile(filePath)
return string(content)
}
func monitorSourceFileAndUpdateBinlog(sourceFile, binlogFile string, stopCh chan bool) {
ticker := time.NewTicker(2 * time.Second)
defer ticker.Stop()
var lastContent string
for {
select {
case <-stopCh:
return
case <-ticker.C:
currentContent := readFile(sourceFile)
if currentContent != lastContent {
gtid := fmt.Sprintf("GTID=%d\n", time.Now().UnixNano())
appendToFile(binlogFile, gtid)
appendToFile(binlogFile, currentContent[len(lastContent):])
lastContent = currentContent
}
}
}
}
func monitorBinlogAndUpdateRelaylog(binlogFile, relaylogFile string, stopCh chan bool) {
ticker := time.NewTicker(2 * time.Second)
defer ticker.Stop()
var lastContent string
for {
select {
case <-stopCh:
return
case <-ticker.C:
currentContent := readFile(binlogFile)
if currentContent != lastContent {
appendToFile(relaylogFile, currentContent[len(lastContent):])
lastContent = currentContent
}
}
}
}
func monitorRelaylogAndUpdateTarget(relaylogFile, targetFile string, stopCh chan bool) {
ticker := time.NewTicker(2 * time.Second)
defer ticker.Stop()
var lastContent string
for {
select {
case <-stopCh:
return
case <-ticker.C:
currentContent := readFile(relaylogFile)
if currentContent != lastContent {
// Remove GTID lines
lines := strings.Split(currentContent[len(lastContent):], "\n")
for _, line := range lines {
if !strings.HasPrefix(line, "GTID=") && line != "" {
appendToFile(targetFile, line+"\n")
}
}
lastContent = currentContent
}
}
}
}
func main() {
var wg sync.WaitGroup
stopCh := make(chan bool)
// 文件路径与初始内容设置
sourceFile := "txt1"
targetFile := "txt2"
binlogFile := "binlog"
relaylogFile := "relaylog"
// 初始化文件
initializeFile(sourceFile, "Initial content\n")
initializeFile(targetFile, "")
initializeFile(binlogFile, "")
initializeFile(relaylogFile, "")
// 启动监控协程
wg.Add(3)
go func() {
defer wg.Done()
monitorSourceFileAndUpdateBinlog(sourceFile, binlogFile, stopCh)
}()
go func() {
defer wg.Done()
monitorBinlogAndUpdateRelaylog(binlogFile, relaylogFile, stopCh)
}()
go func() {
defer wg.Done()
monitorRelaylogAndUpdateTarget(relaylogFile, targetFile, stopCh)
}()
// 模拟用户输入以停止监控
fmt.Println("Press Enter to stop monitoring...")
fmt.Scanln()
// 通知监控协程停止
stopCh <- true
// 等待所有协程结束
wg.Wait()
fmt.Println("Replication completed.")
}