redis-shake工具
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.
 
 
 

237 lines
5.7 KiB

package reader
import (
"bufio"
"github.com/alibaba/RedisShake/internal/client"
"github.com/alibaba/RedisShake/internal/entry"
"github.com/alibaba/RedisShake/internal/log"
"github.com/alibaba/RedisShake/internal/rdb"
"github.com/alibaba/RedisShake/internal/reader/rotate"
"github.com/alibaba/RedisShake/internal/statistics"
"io"
"io/ioutil"
"os"
"strconv"
"strings"
"time"
)
type psyncReader struct {
client *client.Redis
address string
ch chan *entry.Entry
DbId int
rd *bufio.Reader
receivedOffset int64
}
func NewPSyncReader(address string, password string, isTls bool) Reader {
r := new(psyncReader)
r.init(address, password, isTls)
return r
}
func (r *psyncReader) init(address string, password string, isTls bool) {
r.address = address
standalone := client.NewRedisClient(address, password, isTls)
r.client = standalone
r.rd = r.client.BufioReader()
log.Infof("psyncReader connected to redis successful. address=[%s]", address)
}
func (r *psyncReader) StartRead() chan *entry.Entry {
r.ch = make(chan *entry.Entry, 1024)
go func() {
r.clearDir()
go r.sendReplconfAck()
r.saveRDB()
startOffset := r.receivedOffset
go r.saveAOF(r.rd)
r.sendRDB()
time.Sleep(1 * time.Second) // wait for saveAOF create aof file
r.sendAOF(startOffset)
}()
return r.ch
}
func (r *psyncReader) clearDir() {
files, err := ioutil.ReadDir("./")
if err != nil {
log.PanicError(err)
}
for _, f := range files {
if strings.HasSuffix(f.Name(), ".rdb") || strings.HasSuffix(f.Name(), ".aof") {
err = os.Remove(f.Name())
if err != nil {
log.PanicError(err)
}
log.Warnf("remove file. filename=[%s]", f.Name())
}
}
}
func (r *psyncReader) saveRDB() {
log.Infof("start save RDB. address=[%s]", r.address)
argv := []string{"replconf", "listening-port", "10007"} // 10007 is magic number
log.Infof("send %v", argv)
reply := r.client.DoWithStringReply(argv...)
if reply != "OK" {
log.Warnf("send replconf command to redis server failed. address=[%s], reply=[%s], error=[]", r.address, reply)
}
// send psync
argv = []string{"PSYNC", "?", "-1"}
r.client.Send(argv...)
log.Infof("send %v", argv)
// format: \n\n\n$<reply>\r\n
for true {
// \n\n\n$
b, err := r.rd.ReadByte()
if err != nil {
log.PanicError(err)
}
if b == '\n' {
continue
}
if b != '+' {
log.Panicf("invalid rdb format. address=[%s], b=[%s]", r.address, string(b))
}
break
}
reply, err := r.rd.ReadString('\n')
if err != nil {
log.PanicError(err)
}
reply = strings.TrimSpace(reply)
log.Infof("receive [%s]", reply)
masterOffset, err := strconv.Atoi(strings.Split(reply, " ")[2])
if err != nil {
log.PanicError(err)
}
r.receivedOffset = int64(masterOffset)
log.Infof("source db is doing bgsave. address=[%s]", r.address)
timeStart := time.Now()
// format: \n\n\n$<length>\r\n<rdb>
for true {
// \n\n\n$
b, err := r.rd.ReadByte()
if err != nil {
log.PanicError(err)
}
if b == '\n' {
continue
}
if b != '$' {
log.Panicf("invalid rdb format. address=[%s], b=[%s]", r.address, string(b))
}
break
}
log.Infof("source db bgsave finished. timeUsed=[%.2f]s, address=[%s]", time.Since(timeStart).Seconds(), r.address)
lengthStr, err := r.rd.ReadString('\n')
if err != nil {
log.PanicError(err)
}
lengthStr = strings.TrimSpace(lengthStr)
length, err := strconv.ParseInt(lengthStr, 10, 64)
if err != nil {
log.PanicError(err)
}
log.Infof("received rdb length. length=[%d]", length)
statistics.SetRDBFileSize(length)
// create rdb file
rdbFilePath := "dump.rdb"
log.Infof("create dump.rdb file. filename_path=[%s]", rdbFilePath)
rdbFileHandle, err := os.OpenFile(rdbFilePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
log.PanicError(err)
}
// read rdb
remainder := length
const bufSize int64 = 32 * 1024 * 1024 // 32MB
buf := make([]byte, bufSize)
for remainder != 0 {
readOnce := bufSize
if remainder < readOnce {
readOnce = remainder
}
n, err := r.rd.Read(buf[:readOnce])
if err != nil {
log.PanicError(err)
}
remainder -= int64(n)
statistics.UpdateRDBReceivedSize(length - remainder)
_, err = rdbFileHandle.Write(buf[:n])
if err != nil {
log.PanicError(err)
}
}
err = rdbFileHandle.Close()
if err != nil {
log.PanicError(err)
}
log.Infof("save RDB finished. address=[%s], total_bytes=[%d]", r.address, length)
}
func (r *psyncReader) saveAOF(rd io.Reader) {
log.Infof("start save AOF. address=[%s]", r.address)
// create aof file
aofWriter := rotate.NewAOFWriter(r.receivedOffset)
defer aofWriter.Close()
buf := make([]byte, 16*1024) // 16KB is enough for writing file
for {
n, err := rd.Read(buf)
if err != nil {
log.PanicError(err)
}
r.receivedOffset += int64(n)
statistics.UpdateAOFReceivedOffset(r.receivedOffset)
aofWriter.Write(buf[:n])
}
}
func (r *psyncReader) sendRDB() {
// start parse rdb
log.Infof("start send RDB. address=[%s]", r.address)
rdbLoader := rdb.NewLoader("dump.rdb", r.ch)
r.DbId = rdbLoader.ParseRDB()
log.Infof("send RDB finished. address=[%s], repl-stream-db=[%d]", r.address, r.DbId)
}
func (r *psyncReader) sendAOF(offset int64) {
aofReader := rotate.NewAOFReader(offset)
defer aofReader.Close()
r.client.SetBufioReader(bufio.NewReader(aofReader))
for {
argv := client.ArrayString(r.client.Receive())
// select
if strings.EqualFold(argv[0], "select") {
DbId, err := strconv.Atoi(argv[1])
if err != nil {
log.PanicError(err)
}
r.DbId = DbId
continue
}
e := entry.NewEntry()
e.Argv = argv
e.DbId = r.DbId
e.Offset = aofReader.Offset()
r.ch <- e
}
}
func (r *psyncReader) sendReplconfAck() {
for range time.Tick(time.Millisecond * 100) {
// send ack receivedOffset
r.client.Send("replconf", "ack", strconv.FormatInt(r.receivedOffset, 10))
}
}