You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
201 lines
5.3 KiB
201 lines
5.3 KiB
package rdb
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"encoding/binary"
|
|
"github.com/alibaba/RedisShake/internal/config"
|
|
"github.com/alibaba/RedisShake/internal/entry"
|
|
"github.com/alibaba/RedisShake/internal/log"
|
|
"github.com/alibaba/RedisShake/internal/rdb/structure"
|
|
"github.com/alibaba/RedisShake/internal/rdb/types"
|
|
"github.com/alibaba/RedisShake/internal/statistics"
|
|
"github.com/alibaba/RedisShake/internal/utils"
|
|
"io"
|
|
"os"
|
|
"strconv"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
kFlagFunction2 = 245 // function library data
|
|
kFlagFunction = 246 // old function library data for 7.0 rc1 and rc2
|
|
kFlagModuleAux = 247 // Module auxiliary data.
|
|
kFlagIdle = 0xf8 // LRU idle time.
|
|
kFlagFreq = 0xf9 // LFU frequency.
|
|
kFlagAUX = 0xfa // RDB aux field.
|
|
kFlagResizeDB = 0xfb // Hash table resize hint.
|
|
kFlagExpireMs = 0xfc // Expire time in milliseconds.
|
|
kFlagExpire = 0xfd // Old expire time in seconds.
|
|
kFlagSelect = 0xfe // DB number of the following keys.
|
|
kEOF = 0xff // End of the RDB file.
|
|
)
|
|
|
|
type Loader struct {
|
|
replStreamDbId int // https://github.com/alibaba/RedisShake/pull/430#issuecomment-1099014464
|
|
|
|
nowDBId int
|
|
expireAt uint64
|
|
idle int64
|
|
freq int64
|
|
|
|
filPath string
|
|
fp *os.File
|
|
|
|
ch chan *entry.Entry
|
|
dumpBuffer bytes.Buffer
|
|
}
|
|
|
|
func NewLoader(filPath string, ch chan *entry.Entry) *Loader {
|
|
ld := new(Loader)
|
|
ld.ch = ch
|
|
ld.filPath = filPath
|
|
return ld
|
|
}
|
|
|
|
func (ld *Loader) ParseRDB() int {
|
|
var err error
|
|
ld.fp, err = os.OpenFile(ld.filPath, os.O_RDONLY, 0666)
|
|
if err != nil {
|
|
log.Panicf("open file failed. file_path=[%s], error=[%s]", ld.filPath, err)
|
|
}
|
|
defer func() {
|
|
err = ld.fp.Close()
|
|
if err != nil {
|
|
log.Panicf("close file failed. file_path=[%s], error=[%s]", ld.filPath, err)
|
|
}
|
|
}()
|
|
rd := bufio.NewReader(ld.fp)
|
|
//magic + version
|
|
buf := make([]byte, 9)
|
|
_, err = io.ReadFull(rd, buf)
|
|
if err != nil {
|
|
log.PanicError(err)
|
|
}
|
|
if !bytes.Equal(buf[:5], []byte("REDIS")) {
|
|
log.Panicf("verify magic string, invalid file format. bytes=[%v]", buf[:5])
|
|
}
|
|
version, err := strconv.Atoi(string(buf[5:]))
|
|
if err != nil {
|
|
log.PanicError(err)
|
|
}
|
|
log.Infof("RDB version: %d", version)
|
|
|
|
// read entries
|
|
ld.parseRDBEntry(rd)
|
|
return ld.replStreamDbId
|
|
}
|
|
|
|
func (ld *Loader) parseRDBEntry(rd *bufio.Reader) {
|
|
// for stat
|
|
UpdateRDBSentSize := func() {
|
|
offset, err := ld.fp.Seek(0, io.SeekCurrent)
|
|
if err != nil {
|
|
log.PanicError(err)
|
|
}
|
|
statistics.UpdateRDBSentSize(offset)
|
|
}
|
|
defer UpdateRDBSentSize()
|
|
// read one entry
|
|
tick := time.Tick(time.Second * 1)
|
|
for true {
|
|
typeByte := structure.ReadByte(rd)
|
|
switch typeByte {
|
|
case kFlagIdle:
|
|
ld.idle = int64(structure.ReadLength(rd))
|
|
case kFlagFreq:
|
|
ld.freq = int64(structure.ReadByte(rd))
|
|
case kFlagAUX:
|
|
key := structure.ReadString(rd)
|
|
value := structure.ReadString(rd)
|
|
if key == "repl-stream-db" {
|
|
var err error
|
|
ld.replStreamDbId, err = strconv.Atoi(value)
|
|
if err != nil {
|
|
log.PanicError(err)
|
|
}
|
|
log.Infof("RDB repl-stream-db: %d", ld.replStreamDbId)
|
|
} else if key == "lua" {
|
|
e := entry.NewEntry()
|
|
e.Argv = []string{"script", "load", value}
|
|
e.IsBase = true
|
|
ld.ch <- e
|
|
log.Infof("LUA script: [%s]", value)
|
|
} else {
|
|
log.Infof("RDB AUX fields. key=[%s], value=[%s]", key, value)
|
|
}
|
|
case kFlagResizeDB:
|
|
dbSize := structure.ReadLength(rd)
|
|
expireSize := structure.ReadLength(rd)
|
|
log.Infof("RDB resize db. db_size=[%d], expire_size=[%d]", dbSize, expireSize)
|
|
case kFlagExpireMs:
|
|
ld.expireAt = structure.ReadUint64(rd)
|
|
case kFlagExpire:
|
|
ld.expireAt = uint64(structure.ReadUint32(rd)) * 1000
|
|
case kFlagSelect:
|
|
ld.nowDBId = int(structure.ReadLength(rd))
|
|
case kEOF:
|
|
return
|
|
default:
|
|
key := structure.ReadString(rd)
|
|
var value bytes.Buffer
|
|
anotherReader := io.TeeReader(rd, &value)
|
|
o := types.ParseObject(anotherReader, typeByte, key)
|
|
if uint64(value.Len()) > config.Config.Advanced.TargetRedisProtoMaxBulkLen {
|
|
cmds := o.Rewrite()
|
|
for _, cmd := range cmds {
|
|
e := entry.NewEntry()
|
|
e.IsBase = true
|
|
e.DbId = ld.nowDBId
|
|
e.Argv = cmd
|
|
ld.ch <- e
|
|
}
|
|
if ld.expireAt != 0 {
|
|
e := entry.NewEntry()
|
|
e.IsBase = true
|
|
e.DbId = ld.nowDBId
|
|
e.Argv = []string{"PEXPIREAT", key, strconv.FormatUint(ld.expireAt, 10)}
|
|
ld.ch <- e
|
|
}
|
|
} else {
|
|
e := entry.NewEntry()
|
|
e.IsBase = true
|
|
e.DbId = ld.nowDBId
|
|
v := ld.createValueDump(typeByte, value.Bytes())
|
|
e.Argv = []string{"restore", key, strconv.FormatUint(ld.expireAt, 10), v}
|
|
if config.Config.Advanced.RDBRestoreCommandBehavior == "rewrite" {
|
|
e.Argv = append(e.Argv, "replace")
|
|
}
|
|
if ld.expireAt != 0 {
|
|
e.Argv = append(e.Argv, "absttl")
|
|
}
|
|
if ld.idle != 0 {
|
|
e.Argv = append(e.Argv, "idletime", strconv.FormatInt(ld.idle, 10))
|
|
}
|
|
if ld.freq != 0 {
|
|
e.Argv = append(e.Argv, "freq", strconv.FormatInt(ld.freq, 10))
|
|
}
|
|
ld.ch <- e
|
|
}
|
|
ld.expireAt = 0
|
|
ld.idle = 0
|
|
ld.freq = 0
|
|
}
|
|
select {
|
|
case <-tick:
|
|
UpdateRDBSentSize()
|
|
default:
|
|
}
|
|
}
|
|
}
|
|
|
|
func (ld *Loader) createValueDump(typeByte byte, val []byte) string {
|
|
ld.dumpBuffer.Reset()
|
|
_, _ = ld.dumpBuffer.Write([]byte{typeByte})
|
|
_, _ = ld.dumpBuffer.Write(val)
|
|
_ = binary.Write(&ld.dumpBuffer, binary.LittleEndian, uint16(6))
|
|
// calc crc
|
|
sum64 := utils.CalcCRC64(ld.dumpBuffer.Bytes())
|
|
_ = binary.Write(&ld.dumpBuffer, binary.LittleEndian, sum64)
|
|
return ld.dumpBuffer.String()
|
|
}
|
|
|