初始化项目
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
package sqlSync
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"os"
|
||||
|
||||
"goutil/fileUtil"
|
||||
"goutil/intAndBytesUtil"
|
||||
)
|
||||
|
||||
const (
|
||||
// 头部字节长度
|
||||
con_Header_Length = 4
|
||||
)
|
||||
|
||||
var (
|
||||
// 字节的大小端顺序
|
||||
byteOrder = binary.LittleEndian
|
||||
)
|
||||
|
||||
// 按照指定方式读取文本内容
|
||||
// fileObj:大文件对象
|
||||
// data:待写入的数据
|
||||
// 返回值:
|
||||
// error:写入是否存在异常
|
||||
func Write(fileObj *fileUtil.BigFile, data string) error {
|
||||
// 获得数据内容的长度
|
||||
dataLength := len(data)
|
||||
|
||||
// 将长度转化为字节数组
|
||||
header := intAndBytesUtil.Int32ToBytes(int32(dataLength), byteOrder)
|
||||
|
||||
// 将头部与内容组合在一起
|
||||
message := append(header, data...)
|
||||
|
||||
// 写入数据
|
||||
return fileObj.WriteMessage(message)
|
||||
}
|
||||
|
||||
// 从文件读取一条数据
|
||||
// fileObj:文件对象
|
||||
// 返回值:
|
||||
// result:读取到的字符串
|
||||
// err:错误信息
|
||||
func Read(fileObj *os.File) (result string, readLen int64, err error) {
|
||||
// 1. 读取头部内容
|
||||
header := make([]byte, 4)
|
||||
var n int
|
||||
n, err = fileObj.Read(header)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if n < con_Header_Length {
|
||||
err = errors.New("can not read 4 byte for read len")
|
||||
readLen = int64(n)
|
||||
return
|
||||
}
|
||||
|
||||
dataLength := intAndBytesUtil.BytesToInt32(header, byteOrder)
|
||||
|
||||
// 2. 读取指定长度的内容
|
||||
data := make([]byte, dataLength)
|
||||
n, err = fileObj.Read(data)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
readLen = int64(len(header) + int(dataLength))
|
||||
result = string(data)
|
||||
return
|
||||
}
|
||||
139
trunk/framework/dataSyncMgr/mysqlSync/sqlSync/sqlFile.go
Normal file
139
trunk/framework/dataSyncMgr/mysqlSync/sqlSync/sqlFile.go
Normal file
@@ -0,0 +1,139 @@
|
||||
package sqlSync
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"goutil/fileUtil"
|
||||
"goutil/logUtil"
|
||||
)
|
||||
|
||||
const (
|
||||
// 第一个文件名
|
||||
con_Default_FileName = "00000000"
|
||||
|
||||
// 文件名后缀
|
||||
con_FileName_Suffix = "data"
|
||||
)
|
||||
|
||||
// 同步数据对象(用于往文件中写入sql语句)
|
||||
type SqlFile struct {
|
||||
// 存放同步数据的文件夹路径
|
||||
dirPath string
|
||||
|
||||
// 同步数据对象的唯一标识,用于进行重复判断
|
||||
identifier string
|
||||
|
||||
// 保存数据的大文件对象
|
||||
bigFileObj *fileUtil.BigFile
|
||||
|
||||
// 数据同步对象
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
// 将数据写入同步数据对象
|
||||
// data:待写入的数据
|
||||
func (this *SqlFile) Write(data string) {
|
||||
this.mutex.Lock()
|
||||
defer this.mutex.Unlock()
|
||||
|
||||
// 写入数据
|
||||
err := Write(this.bigFileObj, data)
|
||||
if err != nil {
|
||||
prefix := fmt.Sprintf("%s-%s", this.identifier, "SqlFile.write.bigFileObj.WriteMessage")
|
||||
err = fmt.Errorf("%s-Write message to big file object failed:%s", prefix, err)
|
||||
logUtil.ErrorLog(err.Error())
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// 获取大文件对象的文件绝对路径
|
||||
func (this *SqlFile) FileFullName() string {
|
||||
return filepath.Join(this.dirPath, this.bigFileObj.FileName())
|
||||
}
|
||||
|
||||
// 当前读写的文件名
|
||||
func (this *SqlFile) FileName() string {
|
||||
return this.bigFileObj.FileName()
|
||||
}
|
||||
|
||||
// 创建同步数据对象
|
||||
// _dirPath:目录的路径
|
||||
// _identifier:当前数据的唯一标识(可以使用数据库表名)
|
||||
// _maxFileSize:每个大文件的最大写入值(单位:Byte)
|
||||
// 返回值:
|
||||
// 同步数据对象
|
||||
func NewSqlFile(dirPath, identifier, fileName string, maxFileSize int) *SqlFile {
|
||||
result := &SqlFile{
|
||||
dirPath: dirPath,
|
||||
identifier: identifier,
|
||||
}
|
||||
|
||||
// 初始化大文件对象
|
||||
if fileName == "" {
|
||||
fileName = con_Default_FileName
|
||||
}
|
||||
bigFileObj, err := fileUtil.NewBigFileWithNewFileNameFunc2(dirPath, "", fileName, maxFileSize, NewFileName)
|
||||
if err != nil {
|
||||
prefix := fmt.Sprintf("%s-%s", result.identifier, "SqlFile.newSqlFile.fileUtil.NewBigFileWithNewFileNameFunc")
|
||||
err = fmt.Errorf("%s-Create big file object failed:%s", prefix, err)
|
||||
logUtil.ErrorLog(err.Error())
|
||||
panic(err)
|
||||
}
|
||||
result.bigFileObj = bigFileObj
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// 根据当前文件名生成下一个sql文件名
|
||||
// prefix:文件名前缀
|
||||
// path:当前文件的路径
|
||||
// 返回值:
|
||||
// string:下一个文件的完整路径
|
||||
func NewFileName(prefix, path string) string {
|
||||
fullName := filepath.Base(path)
|
||||
curFileName := strings.Split(fullName, ".")[0]
|
||||
curFileId, err := strconv.Atoi(curFileName)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("%s-Convert newFileName:%s to int failed:%s", prefix, curFileName, err)
|
||||
logUtil.ErrorLog(err.Error())
|
||||
panic(err)
|
||||
}
|
||||
|
||||
newFileId := curFileId + 1
|
||||
newFileName := fmt.Sprintf("%08d", newFileId)
|
||||
|
||||
// 加上文件后缀
|
||||
newFileName = fmt.Sprintf("%s.%s", newFileName, con_FileName_Suffix)
|
||||
|
||||
return newFileName
|
||||
}
|
||||
|
||||
// 获取文件夹下所有的sql文件
|
||||
// dirPath:指定要获取的文件夹路径
|
||||
// 返回值:
|
||||
// []string:sql文件列表
|
||||
func GetDataFileList(dirPath string) []string {
|
||||
// 获取当前目录中所有的数据文件列表
|
||||
fileList, err := fileUtil.GetFileList2(dirPath, "", con_FileName_Suffix)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
} else {
|
||||
err = fmt.Errorf("%s/*.%s-Get file list failed:%s", dirPath, con_FileName_Suffix, err)
|
||||
logUtil.ErrorLog(err.Error())
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// 如果文件数量大于1,则进行排序,以便于后续处理
|
||||
if len(fileList) > 1 {
|
||||
sort.Strings(fileList)
|
||||
}
|
||||
|
||||
return fileList
|
||||
}
|
||||
271
trunk/framework/dataSyncMgr/mysqlSync/sqlSync/syncObject.go
Normal file
271
trunk/framework/dataSyncMgr/mysqlSync/sqlSync/syncObject.go
Normal file
@@ -0,0 +1,271 @@
|
||||
package sqlSync
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"goutil/debugUtil"
|
||||
"goutil/fileUtil"
|
||||
"goutil/logUtil"
|
||||
)
|
||||
|
||||
// 同步对象定义
|
||||
type SyncObject struct {
|
||||
// 同步数据的存储路径
|
||||
dirPath string
|
||||
|
||||
// 同步数据对象的唯一标识,用于进行重复判断
|
||||
identifier string
|
||||
|
||||
// 数据库对象
|
||||
dbObj *sql.DB
|
||||
|
||||
// 处理数据写入的文件
|
||||
sqlFileObj *SqlFile
|
||||
|
||||
// 同步处理对象
|
||||
syncHandleObj SyncHandler
|
||||
}
|
||||
|
||||
// 进行同步对象初始化
|
||||
// maxFileSize:每个大文件的最大写入值(单位:Byte)
|
||||
func (this *SyncObject) Init(maxFileSize int) {
|
||||
// 启动时同步所有数据(然后才能从数据库中查询数据,以免数据丢失)
|
||||
this.syncHandleObj.Init(this)
|
||||
|
||||
// 构造同步数据对象
|
||||
fileName, _ := this.syncHandleObj.GetSyncingInfo()
|
||||
this.sqlFileObj = NewSqlFile(this.dirPath, this.identifier, fileName, maxFileSize)
|
||||
|
||||
// 当前没有正在同步的文件,则指向当前正在写的文件
|
||||
if len(fileName) <= 0 {
|
||||
this.syncHandleObj.Update(this.sqlFileObj.FileFullName(), 0, nil)
|
||||
}
|
||||
|
||||
// 启动一个新goroutine来负责同步数据
|
||||
go func() {
|
||||
/* 此处不使用goroutineMgr.Monitor/ReleaseMonitor,因为此处不能捕获panic,需要让外部进程终止执行,
|
||||
因为此模块的文件读写为核心逻辑,一旦出现问题必须停止进程,否则会造成脏数据
|
||||
*/
|
||||
this.Sync()
|
||||
}()
|
||||
}
|
||||
|
||||
// 保存数据到本地文件
|
||||
// command:待保存的指令
|
||||
func (this *SyncObject) Save(command string) {
|
||||
this.sqlFileObj.Write(command)
|
||||
}
|
||||
|
||||
// 循环同步多个文件
|
||||
func (this *SyncObject) Sync() {
|
||||
// 开始循环同步
|
||||
for {
|
||||
// 同步当前文件
|
||||
this.syncOneFile()
|
||||
|
||||
// 当前文件同步完成,记录同步日志
|
||||
nowFilePath, _ := this.syncHandleObj.GetSyncingInfo()
|
||||
|
||||
// 删除已同步完成的文件
|
||||
WaitForOk(func() bool {
|
||||
fileExist, err := fileUtil.IsFileExists(nowFilePath)
|
||||
if err != nil {
|
||||
logUtil.ErrorLog(fmt.Sprintf("mysqlSync/syncObject IsFileExists error:%s", err.Error()))
|
||||
return false
|
||||
}
|
||||
if fileExist == false {
|
||||
return true
|
||||
}
|
||||
|
||||
err = fileUtil.DeleteFile(nowFilePath)
|
||||
if err != nil {
|
||||
logUtil.ErrorLog(fmt.Sprintf("mysqlSync/syncObject delete file error:%s", err.Error()))
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}, 10*time.Second)
|
||||
|
||||
// 当前文件同步完成,获取下个文件
|
||||
nextFileName := NewFileName("", nowFilePath)
|
||||
filePath := filepath.Join(this.dirPath, nextFileName)
|
||||
exist, err := fileUtil.IsFileExists(filePath)
|
||||
if err != nil {
|
||||
logUtil.ErrorLog(fmt.Sprintf("mysqlSync/syncObject IsFileExists error:%s", err.Error()))
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// 如果文件不存在,退出
|
||||
if !exist {
|
||||
// fmt.Println("协程退出了")
|
||||
return
|
||||
}
|
||||
|
||||
// 更新同步的位置信息 此处忽略错误是因为,哪怕是出错了,也不会影响整体逻辑
|
||||
this.syncHandleObj.Update(filePath, 0, nil)
|
||||
}
|
||||
}
|
||||
|
||||
// 同步单个文件
|
||||
func (this *SyncObject) syncOneFile() {
|
||||
// 获取信息同步项对象
|
||||
filePath, offset := this.syncHandleObj.GetSyncingInfo()
|
||||
|
||||
// 打开待读取的文件
|
||||
f, exist := this.openFile(filePath)
|
||||
if exist == false {
|
||||
// logUtil.WarnLog(fmt.Sprintf("待同步的文件不存在,跳过此文件:%s", filePath),"")
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
for {
|
||||
// 移动到需要读取的位置
|
||||
if _, err := f.Seek(offset, io.SeekStart); err != nil {
|
||||
prefix := fmt.Sprintf("%s-%s", this.identifier, "SyncObject.Seek")
|
||||
err = fmt.Errorf("%s-Seek offset for header failed:%s", prefix, err)
|
||||
logUtil.ErrorLog(err.Error())
|
||||
panic(err)
|
||||
}
|
||||
|
||||
command, readLen, err := Read(f)
|
||||
if err != nil {
|
||||
// 如果读取到文件末尾,判断是否等待
|
||||
if err == io.EOF {
|
||||
if this.sqlFileObj != nil && strings.Contains(filePath, this.sqlFileObj.FileName()) {
|
||||
time.Sleep(20 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
|
||||
// 如果该文件是空文件,同步更新信息
|
||||
return
|
||||
}
|
||||
|
||||
prefix := fmt.Sprintf("%s-%s", this.identifier, "SyncObject.syncOneFile.f.Read")
|
||||
err = fmt.Errorf("%s-Read header failed:%s", prefix, err)
|
||||
logUtil.ErrorLog(err.Error())
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// 3. 同步到mysql中,并更新同步位置
|
||||
this.syncHandleObj.SyncOneSql(command, filePath, offset+readLen)
|
||||
|
||||
// 4. 更新内存中的同步位置
|
||||
offset += readLen
|
||||
}
|
||||
}
|
||||
|
||||
// 打开待读取的文件
|
||||
// filePath:待打开的文件
|
||||
// 返回值:
|
||||
// *os.File:文件句柄,
|
||||
func (this *SyncObject) openFile(filePath string) (f *os.File, exist bool) {
|
||||
var err error
|
||||
for {
|
||||
exist, err = fileUtil.IsFileExists(filePath)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("check file error,filePath:%v error:%v", filePath, err.Error())
|
||||
logUtil.ErrorLog(err.Error())
|
||||
time.Sleep(time.Second * 5)
|
||||
continue
|
||||
}
|
||||
if exist == false {
|
||||
// 如果文件不存在,则跳过此文件
|
||||
logUtil.WarnLog(fmt.Sprintf("file no exist, skip file:%v", filePath))
|
||||
exist = false
|
||||
return
|
||||
}
|
||||
exist = true
|
||||
|
||||
// 打开当前处理文件
|
||||
f, err = os.OpenFile(filePath, os.O_RDONLY, os.ModePerm|os.ModeTemporary)
|
||||
if err != nil {
|
||||
prefix := fmt.Sprintf("%s-%s", this.identifier, "SyncObject.syncOneFile.os.OpenFile")
|
||||
err = fmt.Errorf("%s-Open file:%s failed:%s", prefix, filePath, err)
|
||||
logUtil.ErrorLog(err.Error())
|
||||
|
||||
time.Sleep(time.Second * 5)
|
||||
continue
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 同步数据到mysql中
|
||||
// command:sql语句
|
||||
// tx:事务处理对象
|
||||
// 返回值:
|
||||
// error:错误信息
|
||||
func (this *SyncObject) syncToMysql(command string, tx *sql.Tx) error {
|
||||
_, err := tx.Exec(command)
|
||||
if err != nil {
|
||||
prefix := fmt.Sprintf("%s-%s", this.identifier, "SyncObject.syncToMysql")
|
||||
err = fmt.Errorf("%s-%s Update to mysql failed:%s", prefix, command, err)
|
||||
logUtil.ErrorLog(err.Error())
|
||||
debugUtil.Printf("fatal Error:%v", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 创新新的mysql同步对象
|
||||
// dirPath:存放数据的目录
|
||||
// identifier:当前数据的唯一标识(可以使用数据库表名)
|
||||
// dbObj:数据库对象
|
||||
// _syncHandleObj:同步处理对象
|
||||
// 返回值:
|
||||
// mysql同步对象
|
||||
func NewSyncObject(dirPath, identifier string, dbObj *sql.DB, _syncHandleObj SyncHandler) *SyncObject {
|
||||
dirPath = filepath.Join(dirPath, identifier)
|
||||
|
||||
// 创建更新目录
|
||||
err := os.MkdirAll(dirPath, os.ModePerm|os.ModeTemporary)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("%s-%s-make dir failed:%s", identifier, "SyncObject.newSyncObject.os.MkdirAll", err)
|
||||
logUtil.ErrorLog(err.Error())
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// 构造同步信息对象
|
||||
result := &SyncObject{
|
||||
dirPath: dirPath,
|
||||
identifier: identifier,
|
||||
dbObj: dbObj,
|
||||
syncHandleObj: _syncHandleObj,
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// 同步处理接口
|
||||
type SyncHandler interface {
|
||||
// 初始化
|
||||
Init(baseObj *SyncObject)
|
||||
|
||||
// 获取正在同步的信息
|
||||
// filePath:文件路径
|
||||
// offset:文件偏移量
|
||||
GetSyncingInfo() (filePath string, offset int64)
|
||||
|
||||
// 更新
|
||||
// filePath:文件路径
|
||||
// offset:文件偏移量
|
||||
// tran:事务对象
|
||||
// 返回值:
|
||||
// error:错误对象
|
||||
Update(filePath string, offset int64, tran *sql.Tx) error
|
||||
|
||||
// 同步一条sql
|
||||
// command:指令数据
|
||||
// filePath:文件路径
|
||||
// offset:文件偏移量
|
||||
SyncOneSql(command string, filePath string, offset int64)
|
||||
}
|
||||
99
trunk/framework/dataSyncMgr/mysqlSync/sqlSync/utility.go
Normal file
99
trunk/framework/dataSyncMgr/mysqlSync/sqlSync/utility.go
Normal file
@@ -0,0 +1,99 @@
|
||||
package sqlSync
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"goutil/logUtil"
|
||||
)
|
||||
|
||||
// 以事务的方式执行
|
||||
// db:数据库对象
|
||||
// funcObj:对应的具体处理函数
|
||||
// 返回值:
|
||||
// error:处理是否存在错误
|
||||
func ExecuteByTran(db *sql.DB, funcObj func(tran *sql.Tx) (isCommit bool, err error)) error {
|
||||
tran, err := db.Begin()
|
||||
if err != nil {
|
||||
logUtil.ErrorLog(fmt.Sprintf("start transaction error:%v", err.Error()))
|
||||
return err
|
||||
}
|
||||
|
||||
// 事务处理
|
||||
isCommit := false
|
||||
defer func() {
|
||||
if isCommit {
|
||||
err = tran.Commit()
|
||||
} else {
|
||||
err = tran.Rollback()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
logUtil.ErrorLog(fmt.Sprintf("transaction end error:%v", err.Error()))
|
||||
}
|
||||
}()
|
||||
|
||||
isCommit, err = funcObj(tran)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// 循环执行知道返回成功为止
|
||||
// funcObj:待执行的函数
|
||||
// interval:执行间隔时间
|
||||
func WaitForOk(funcObj func() bool, interval time.Duration) {
|
||||
for {
|
||||
if funcObj() == false {
|
||||
time.Sleep(interval)
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否是连接错误
|
||||
// errMsg:错误信息
|
||||
// 返回值:
|
||||
// bool:true:连接错误 false:其他异常
|
||||
func CheckIfConnectionError(errMsg string) bool {
|
||||
//// 连接被关闭
|
||||
ifConnectionClose := strings.Contains(errMsg, "A connection attempt failed because the connected party did not properly respond")
|
||||
if ifConnectionClose {
|
||||
return true
|
||||
}
|
||||
|
||||
// 使用过程中连接断开
|
||||
ifConnectionClose = strings.Contains(errMsg, "No connection could be made")
|
||||
if ifConnectionClose {
|
||||
return true
|
||||
}
|
||||
|
||||
// 事务处理过程中连接断开的提示
|
||||
ifConnectionClose = strings.Contains(errMsg, "bad connection")
|
||||
if ifConnectionClose {
|
||||
return true
|
||||
}
|
||||
|
||||
// socket压根儿连不上的处理
|
||||
ifConnectionClose = strings.Contains(errMsg, "A socket operation was attempted to an unreachable network")
|
||||
if ifConnectionClose {
|
||||
return true
|
||||
}
|
||||
|
||||
// 用户无法访问
|
||||
return strings.Contains(errMsg, "Access denied for user")
|
||||
}
|
||||
|
||||
// 获取比较简洁的错误信息
|
||||
// errMsg:错误信息
|
||||
// 返回值:
|
||||
// string:比较简洁的错误信息
|
||||
func GetSimpleErrorMessage(errMsg string) string {
|
||||
if strings.Contains(errMsg, "Error 1064: You have an error in your SQL syntax") {
|
||||
return "SqlError"
|
||||
}
|
||||
|
||||
return errMsg
|
||||
}
|
||||
Reference in New Issue
Block a user