ledisdb/server/cmd_replication.go

270 lines
5.1 KiB
Go
Raw Normal View History

package server
2014-06-08 12:43:59 +04:00
import (
2014-10-22 05:11:14 +04:00
"encoding/binary"
2014-06-08 12:43:59 +04:00
"fmt"
2014-09-24 08:34:21 +04:00
"github.com/siddontang/go/hack"
2014-10-21 13:35:03 +04:00
"github.com/siddontang/go/num"
2014-06-08 12:43:59 +04:00
"github.com/siddontang/ledisdb/ledis"
2014-10-21 13:35:03 +04:00
"net"
2014-06-08 12:43:59 +04:00
"strconv"
"strings"
2014-10-11 13:44:31 +04:00
"time"
2014-06-08 12:43:59 +04:00
)
2014-08-25 10:18:23 +04:00
func slaveofCommand(c *client) error {
args := c.args
2014-06-08 12:43:59 +04:00
2014-10-10 05:49:16 +04:00
if len(args) != 2 && len(args) != 3 {
return ErrCmdParams
}
2014-06-08 12:43:59 +04:00
masterAddr := ""
restart := false
2014-10-10 05:49:16 +04:00
readonly := false
2014-06-08 12:43:59 +04:00
2014-09-24 08:34:21 +04:00
if strings.ToLower(hack.String(args[0])) == "no" &&
strings.ToLower(hack.String(args[1])) == "one" {
2014-06-08 12:43:59 +04:00
//stop replication, use master = ""
2014-10-10 05:49:16 +04:00
if len(args) == 3 && strings.ToLower(hack.String(args[2])) == "readonly" {
readonly = true
}
2014-06-08 12:43:59 +04:00
} else {
2014-09-24 08:34:21 +04:00
if _, err := strconv.ParseInt(hack.String(args[1]), 10, 16); err != nil {
2014-06-08 12:43:59 +04:00
return err
}
masterAddr = fmt.Sprintf("%s:%s", args[0], args[1])
if len(args) == 3 && strings.ToLower(hack.String(args[2])) == "restart" {
restart = true
}
}
2014-10-10 05:49:16 +04:00
if err := c.app.slaveof(masterAddr, restart, readonly); err != nil {
return err
}
2014-08-25 10:18:23 +04:00
c.resp.writeStatus(OK)
return nil
}
2014-06-09 13:23:32 +04:00
2014-08-25 10:18:23 +04:00
func fullsyncCommand(c *client) error {
2014-10-11 13:44:31 +04:00
args := c.args
needNew := false
if len(args) == 1 && strings.ToLower(hack.String(args[0])) == "new" {
needNew = true
2014-06-09 13:23:32 +04:00
}
2014-10-11 13:44:31 +04:00
var s *snapshot
var err error
var t time.Time
dumper := c.app.ldb
2014-06-09 13:23:32 +04:00
2014-10-11 13:44:31 +04:00
if needNew {
s, t, err = c.app.snap.Create(dumper)
} else {
if s, t, err = c.app.snap.OpenLatest(); err != nil {
return err
} else if s == nil {
s, t, err = c.app.snap.Create(dumper)
} else {
gap := time.Duration(c.app.cfg.Replication.ExpiredLogDays*24*3600) * time.Second / 2
minT := time.Now().Add(-gap)
//snapshot is too old
if t.Before(minT) {
s.Close()
s, t, err = c.app.snap.Create(dumper)
}
}
}
2014-06-09 13:23:32 +04:00
2014-10-11 13:44:31 +04:00
if err != nil {
return err
}
2014-06-09 13:23:32 +04:00
2014-10-11 13:44:31 +04:00
n := s.Size()
2014-06-09 13:23:32 +04:00
2014-10-11 13:44:31 +04:00
c.resp.writeBulkFrom(n, s)
2014-06-09 13:23:32 +04:00
2014-10-11 13:44:31 +04:00
s.Close()
2014-06-09 13:23:32 +04:00
return nil
}
2014-10-22 05:11:14 +04:00
var dummyBuf = make([]byte, 8)
2014-08-25 10:18:23 +04:00
func syncCommand(c *client) error {
args := c.args
2014-09-22 13:50:51 +04:00
if len(args) != 1 {
2014-06-09 13:23:32 +04:00
return ErrCmdParams
}
var logId uint64
2014-06-09 13:23:32 +04:00
var err error
if logId, err = ledis.StrUint64(args[0], nil); err != nil {
2014-06-09 13:23:32 +04:00
return ErrCmdParams
}
2014-11-01 18:28:28 +03:00
lastLogID := logId - 1
2014-06-09 13:23:32 +04:00
2014-10-21 18:51:17 +04:00
stat, err := c.app.ldb.ReplicationStat()
if err != nil {
return err
}
2014-11-01 18:28:28 +03:00
if lastLogID > stat.LastID {
2014-10-21 18:51:17 +04:00
return fmt.Errorf("invalid sync logid %d > %d + 1", logId, stat.LastID)
2014-11-01 18:28:28 +03:00
}
c.lastLogID.Set(lastLogID)
if lastLogID == stat.LastID {
2014-10-21 18:51:17 +04:00
c.app.slaveAck(c)
}
2014-06-09 13:23:32 +04:00
c.syncBuf.Reset()
2014-06-10 06:41:50 +04:00
2014-10-22 05:11:14 +04:00
c.syncBuf.Write(dummyBuf)
if _, _, err := c.app.ldb.ReadLogsToTimeout(logId, &c.syncBuf, 30, c.app.quit); err != nil {
2014-06-09 13:23:32 +04:00
return err
} else {
2014-08-25 10:18:23 +04:00
buf := c.syncBuf.Bytes()
2014-06-09 13:23:32 +04:00
2014-10-22 05:11:14 +04:00
stat, err = c.app.ldb.ReplicationStat()
if err != nil {
return err
}
binary.BigEndian.PutUint64(buf, stat.LastID)
2014-08-25 10:18:23 +04:00
c.resp.writeBulk(buf)
2014-06-09 13:23:32 +04:00
}
2014-10-21 13:35:03 +04:00
return nil
}
//inner command, only for replication
//REPLCONF <option> <value> <option> <value> ...
func replconfCommand(c *client) error {
args := c.args
if len(args)%2 != 0 {
return ErrCmdParams
}
//now only support "listening-port"
for i := 0; i < len(args); i += 2 {
switch strings.ToLower(hack.String(args[i])) {
case "listening-port":
var host string
var err error
if _, err = num.ParseUint16(hack.String(args[i+1])); err != nil {
return err
}
if host, _, err = net.SplitHostPort(c.remoteAddr); err != nil {
return err
} else {
c.slaveListeningAddr = net.JoinHostPort(host, hack.String(args[i+1]))
}
2014-10-21 13:35:03 +04:00
c.app.addSlave(c)
default:
return ErrSyntax
}
}
c.resp.writeStatus(OK)
2014-06-09 13:23:32 +04:00
return nil
}
2015-02-03 09:15:30 +03:00
func roleCommand(c *client) error {
if len(c.args) != 0 {
return ErrCmdParams
}
c.app.m.Lock()
slaveof := c.app.cfg.SlaveOf
2015-02-03 09:15:30 +03:00
c.app.m.Unlock()
isMaster := len(slaveof) == 0
2015-02-03 09:15:30 +03:00
ay := make([]interface{}, 0, 5)
var lastId int64 = 0
stat, _ := c.app.ldb.ReplicationStat()
if stat != nil {
lastId = int64(stat.LastID)
}
if isMaster {
ay = append(ay, []byte("master"))
ay = append(ay, lastId)
items := make([]interface{}, 0, 3)
c.app.slock.Lock()
for addr, slave := range c.app.slaves {
host, port, _ := splitHostPort(addr)
items = append(items, []interface{}{[]byte(host),
strconv.AppendUint(nil, uint64(port), 10),
strconv.AppendUint(nil, slave.lastLogID.Get(), 10)})
}
c.app.slock.Unlock()
ay = append(ay, items)
} else {
host, port, _ := splitHostPort(slaveof)
2015-02-03 09:15:30 +03:00
ay = append(ay, []byte("slave"))
ay = append(ay, []byte(host))
ay = append(ay, int64(port))
ay = append(ay, []byte(replStatetring(c.app.m.state.Get())))
ay = append(ay, lastId)
}
c.resp.writeArray(ay)
return nil
}
func replStatetring(r int32) string {
switch r {
case replConnectState:
return "connect"
case replConnectingState:
return "connecting"
case replSyncState:
return "sync"
case replConnectedState:
return "connected"
default:
return "unknown"
}
}
func splitHostPort(str string) (string, int16, error) {
host, port, err := net.SplitHostPort(str)
if err != nil {
return "", 0, err
}
p, err := strconv.ParseInt(port, 10, 16)
if err != nil {
return "", 0, err
}
return host, int16(p), nil
}
2014-06-09 13:23:32 +04:00
func init() {
register("slaveof", slaveofCommand)
register("fullsync", fullsyncCommand)
register("sync", syncCommand)
2014-10-21 13:35:03 +04:00
register("replconf", replconfCommand)
2015-02-03 09:15:30 +03:00
register("role", roleCommand)
2014-06-09 13:23:32 +04:00
}