数据库丢失日志,依靠失败日志进行恢复

问题

很多情况下,由于当时创建数据库表字段时,没有准确的确定日志表的自增ID类型,或者说其他值的类型,有可能插入失败

  1. 主键超出范围
  2. varchar类型超出范围等

因此,通过错误日志信息,将数据导出,重新将丢失的数据写入数据库中

数据库使用sqlx
将日志数据上传到服务器上,通过定时任务,在数据库接收数据量少的时候进行脚本数据迁移,每次限定次数

import (
	"bufio"
	"encoding/json"
	"fmt"
	"github.com/jmoiron/sqlx"
	"io/ioutil"
	"math"
	"os"
	"recoverlog/config"
	"recoverlog/db"
	"strings"
	"time"
	"zonst/logging"
)

// 从日志中获取执行失败的SQL
type SQLQuery struct {
	Query   string                 `json:"query"`
	MapArgs map[string]interface{} `json:"mapargs"`
}


var (
	// 执行过程中,的日志文件信息
	logfile = "../data/log/xxx.log"
	// 需要恢复的日志文件位置
	dir     = "../data/log/xxxx/"
)

func RecoverCoinLog() {

	if config.Node.Mode == "local" || config.Node.Mode == "dev" {
		dir = "../log"
		logfile = "../xxxlog.log"
	}

	if err := logging.SetLogFile(logfile); err != nil {
		logging.Errorf("打开日志文件失败err:%v", err)
		return
	}

	// 获取当前目录下所有文件
	files, e := GetAllFile(dir)
	if e != nil {
		logging.Errorf("err:%v 获取目录信息失败\n", e)
		return
	}
	for _, fileName := range files {
		xxxLogs := make([]map[string]interface{}, 0, 100)

		logging.Infoln("读取的文件名", fileName)
		file, err := os.Open(fileName)
		if err != nil {
			logging.Errorf("open file err:%v\n", err)
			return
		}
		buf := bufio.NewScanner(file)
		for {
			var sql SQLQuery
			if !buf.Scan() {
				break
			}
			line := buf.Text()
			line = strings.TrimSpace(line)
			// 此处根据实际情况,将日志进行拆分
			if strings.Contains(line, "xxx") && (strings.Contains(line, "INSERT INTO") || strings.Contains(line, "insert into")) {
				strs := strings.Split(line, "worker.go:139:")

				if len(strs) > 1 {
					jsonData := strs[1]
					json.Unmarshal([]byte(jsonData), &sql)
					if strings.Contains(sql.Query, "表名称") {
						// 金币场日志信息
						xxxLogs = append(xxxLogs, sql.MapArgs)
					}
				}

				if len(strs) < 1 {
					logging.Errorf("log err:line:%v\n", line)
				}
			}

		}

		// 批量插入
		logging.Infof("插入文件:%v的数据\n", fileName)
		logging.Infoln("len(xxxLogs)", len(xxxLogs))

		if len(xxxLogs) != 0 {
			e = BatchInsertxxxLog(db.DB, xxxLogs)
			if e != nil {
				logging.Errorf("InsertxxxxLog err:%v fileName:%v\n", e, fileName)
			}
		}

	}
}

// 批量插入数据
func BatchInsertxxxLog(db *sqlx.DB, xxxLogs []map[string]interface{}) error {
	query := "INSERT INTO xxxx(user_id, game_id, update_coin, start_coin, end_coin, op_type, playback_id, log_date, log_time, is_robot,start_coin64,end_coin64) VALUES (:userID, :gameID, :updateCoin, :startCoin, :endCoin, :opType, :playbackID, :logDate, :logTime, :isRobot,:startCoin64,:endCoin64)"
	nums := len(xxxLogs)

	//判断一共有多少数据,需要分多少次插入
	ids := nums / 1000
	fmt.Println(nums)
	fmt.Println(time.Now())
	// 一次批量插入1000 条数据
	for i := 1; i <= ids; i++ {
		start, end := SlicePage(i, 1000, nums)
		_, e := db.NamedExec(query, xxxLogs[start:end])
		if e != nil {
			logging.Errorf("执行插入InsertUserCoinLog出错,err:%v", e)
			// 保存执行失败的数据,以便下次执行
			for _,v := range userCoinLogs[start:end]{
				logging.Infoln(v)
			}
		}
	
		time.Sleep(30 * time.Millisecond)
	}
	fmt.Println(time.Now())
	return nil

}

// 切片分段(分页)
func SlicePage(page, pageSize, nums int) (sliceStart, sliceEnd int) {
	if page < 0 {
		page = 1
	}

	if pageSize < 0 {
		pageSize = 20
	}

	if pageSize > nums {
		return 0, nums
	}

	// 总页数
	pageCount := int(math.Ceil(float64(nums) / float64(pageSize)))
	if page > pageCount {
		return 0, 0
	}
	sliceStart = (page - 1) * pageSize
	sliceEnd = sliceStart + pageSize

	if sliceEnd > nums {
		sliceEnd = nums
	}
	return sliceStart, sliceEnd
}

// 读取文件夹下的所有文件
func GetAllFile(pathname string) ([]string, error) {
	s := make([]string, 0)
	rd, err := ioutil.ReadDir(pathname)
	if err != nil {
		fmt.Println("read dir fail:", err)
		return s, err
	}

	for _, fi := range rd {
		if !fi.IsDir() {
			fullName := pathname + "/" + fi.Name()
			s = append(s, fullName)
		}
	}
	return s, nil
}


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值