mirror of
https://github.com/HDT3213/godis.git
synced 2025-10-19 07:05:24 +08:00
Support failover in cluster (experimental)
This commit is contained in:
@@ -105,8 +105,8 @@ func (server *Server) AddAof(dbIndex int, cmdLine CmdLine) {
|
||||
}
|
||||
}
|
||||
|
||||
func (server *Server) bindPersister(aofHandler *aof.Persister) {
|
||||
server.persister = aofHandler
|
||||
func (server *Server) bindPersister(persister *aof.Persister) {
|
||||
server.persister = persister
|
||||
// bind SaveCmdLine
|
||||
for _, db := range server.dbSet {
|
||||
singleDB := db.Load().(*DB)
|
||||
|
@@ -311,7 +311,7 @@ func (server *Server) execPSync(c redis.Connection, args [][]byte) redis.Reply {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
if err != nil && err != cannotPartialSync {
|
||||
if err != cannotPartialSync {
|
||||
server.removeSlave(slave)
|
||||
logger.Errorf("masterTryPartialSyncWithSlave error: %v", err)
|
||||
return
|
||||
@@ -422,7 +422,7 @@ func (listener *replAofListener) Callback(cmdLines []CmdLine) {
|
||||
}
|
||||
}
|
||||
|
||||
func (server *Server) initMaster() {
|
||||
func (server *Server) initMasterStatus() {
|
||||
server.masterStatus = &masterStatus{
|
||||
mu: sync.RWMutex{},
|
||||
replId: utils.RandHexString(40),
|
||||
|
@@ -11,13 +11,14 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hdt3213/godis/aof"
|
||||
"github.com/hdt3213/godis/config"
|
||||
rdb "github.com/hdt3213/rdb/parser"
|
||||
"github.com/hdt3213/godis/lib/utils"
|
||||
"github.com/hdt3213/godis/redis/connection"
|
||||
"github.com/hdt3213/godis/redis/parser"
|
||||
"github.com/hdt3213/godis/redis/protocol"
|
||||
"github.com/hdt3213/godis/redis/protocol/asserts"
|
||||
rdb "github.com/hdt3213/rdb/parser"
|
||||
)
|
||||
|
||||
func mockServer() *Server {
|
||||
@@ -31,7 +32,7 @@ func mockServer() *Server {
|
||||
server.dbSet[i] = holder
|
||||
}
|
||||
server.slaveStatus = initReplSlaveStatus()
|
||||
server.initMaster()
|
||||
server.initMasterStatus()
|
||||
return server
|
||||
}
|
||||
|
||||
@@ -212,6 +213,7 @@ func TestReplicationMasterRewriteRDB(t *testing.T) {
|
||||
Databases: 16,
|
||||
AppendOnly: true,
|
||||
AppendFilename: aofFilename,
|
||||
AppendFsync: aof.FsyncAlways,
|
||||
}
|
||||
master := mockServer()
|
||||
aofHandler, err := NewPersister(master, config.Properties.AppendFilename, true, config.Properties.AppendFsync)
|
||||
|
@@ -272,6 +272,11 @@ func (server *Server) psyncHandshake() (bool, error) {
|
||||
if err != nil {
|
||||
return false, errors.New("send failed " + err.Error())
|
||||
}
|
||||
return server.parsePsyncHandshake()
|
||||
}
|
||||
|
||||
func (server *Server) parsePsyncHandshake() (bool, error) {
|
||||
var err error
|
||||
psyncPayload := <-server.slaveStatus.masterChan
|
||||
if psyncPayload.Err != nil {
|
||||
return false, errors.New("read response failed: " + psyncPayload.Err.Error())
|
||||
|
@@ -2,18 +2,21 @@ package database
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/hdt3213/godis/aof"
|
||||
"github.com/hdt3213/godis/config"
|
||||
"github.com/hdt3213/godis/lib/utils"
|
||||
"github.com/hdt3213/godis/redis/client"
|
||||
"github.com/hdt3213/godis/redis/connection"
|
||||
"github.com/hdt3213/godis/redis/protocol"
|
||||
"github.com/hdt3213/godis/redis/protocol/asserts"
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hdt3213/godis/aof"
|
||||
"github.com/hdt3213/godis/config"
|
||||
"github.com/hdt3213/godis/lib/utils"
|
||||
"github.com/hdt3213/godis/redis/client"
|
||||
"github.com/hdt3213/godis/redis/connection"
|
||||
"github.com/hdt3213/godis/redis/parser"
|
||||
"github.com/hdt3213/godis/redis/protocol"
|
||||
"github.com/hdt3213/godis/redis/protocol/asserts"
|
||||
)
|
||||
|
||||
func TestReplicationSlaveSide(t *testing.T) {
|
||||
@@ -151,12 +154,15 @@ func TestReplicationSlaveSide(t *testing.T) {
|
||||
}
|
||||
|
||||
// check slave aof file
|
||||
aofLoader := MakeAuxiliaryServer()
|
||||
aofHandler2, err := NewPersister(aofLoader, config.Properties.AppendFilename, true, aof.FsyncNo)
|
||||
aofLoader.bindPersister(aofHandler2)
|
||||
ret = aofLoader.Exec(conn, utils.ToCmdLine("get", "zz"))
|
||||
aofCheckServer := MakeAuxiliaryServer()
|
||||
aofHandler2, err := NewPersister(aofCheckServer, config.Properties.AppendFilename, true, aof.FsyncNo)
|
||||
if err != nil {
|
||||
t.Error("create persister failed")
|
||||
}
|
||||
aofCheckServer.bindPersister(aofHandler2)
|
||||
ret = aofCheckServer.Exec(conn, utils.ToCmdLine("get", "zz"))
|
||||
asserts.AssertNullBulk(t, ret)
|
||||
ret = aofLoader.Exec(conn, utils.ToCmdLine("get", "1"))
|
||||
ret = aofCheckServer.Exec(conn, utils.ToCmdLine("get", "1"))
|
||||
asserts.AssertBulkReply(t, ret, "4")
|
||||
|
||||
err = server.slaveStatus.close()
|
||||
@@ -164,3 +170,82 @@ func TestReplicationSlaveSide(t *testing.T) {
|
||||
t.Error("cannot close")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReplicationFailover(t *testing.T) {
|
||||
tmpDir, err := os.MkdirTemp("", "godis")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
aofFilename := path.Join(tmpDir, "a.aof")
|
||||
defer func() {
|
||||
_ = os.Remove(aofFilename)
|
||||
}()
|
||||
config.Properties = &config.ServerProperties{
|
||||
Databases: 16,
|
||||
AppendOnly: true,
|
||||
AppendFilename: aofFilename,
|
||||
}
|
||||
conn := connection.NewFakeConn()
|
||||
server := mockServer()
|
||||
aofHandler, err := NewPersister(server, aofFilename, true, aof.FsyncAlways)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
server.bindPersister(aofHandler)
|
||||
|
||||
masterCli, err := client.MakeClient("127.0.0.1:6379")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
masterCli.Start()
|
||||
|
||||
// sync with master
|
||||
ret := masterCli.Send(utils.ToCmdLine("set", "1", "1"))
|
||||
asserts.AssertStatusReply(t, ret, "OK")
|
||||
ret = server.Exec(conn, utils.ToCmdLine("SLAVEOF", "127.0.0.1", "6379"))
|
||||
asserts.AssertStatusReply(t, ret, "OK")
|
||||
success := false
|
||||
for i := 0; i < 30; i++ {
|
||||
// wait for sync
|
||||
time.Sleep(time.Second)
|
||||
ret = server.Exec(conn, utils.ToCmdLine("GET", "1"))
|
||||
bulkRet, ok := ret.(*protocol.BulkReply)
|
||||
if ok {
|
||||
if bytes.Equal(bulkRet.Arg, []byte("1")) {
|
||||
success = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if !success {
|
||||
t.Error("sync failed")
|
||||
return
|
||||
}
|
||||
|
||||
t.Log("slave of no one")
|
||||
ret = server.Exec(conn, utils.ToCmdLine("SLAVEOF", "no", "one"))
|
||||
asserts.AssertStatusReply(t, ret, "OK")
|
||||
server.Exec(conn, utils.ToCmdLine("set", "2", "2"))
|
||||
|
||||
replConn := connection.NewFakeConn()
|
||||
server.Exec(replConn, utils.ToCmdLine("psync", "?", "-1"))
|
||||
masterChan := parser.ParseStream(replConn)
|
||||
serverB := mockServer()
|
||||
serverB.slaveStatus.masterChan = masterChan
|
||||
serverB.slaveStatus.configVersion = 0
|
||||
serverB.parsePsyncHandshake()
|
||||
serverB.loadMasterRDB(0)
|
||||
server.masterCron()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||
defer cancel()
|
||||
go serverB.receiveAOF(ctx, 0)
|
||||
|
||||
time.Sleep(3 * time.Second)
|
||||
ret = serverB.Exec(conn, utils.ToCmdLine("get", "1"))
|
||||
asserts.AssertBulkReply(t, ret, "1")
|
||||
ret = serverB.Exec(conn, utils.ToCmdLine("get", "2"))
|
||||
asserts.AssertBulkReply(t, ret, "2")
|
||||
}
|
@@ -85,7 +85,7 @@ func NewStandaloneServer() *Server {
|
||||
}
|
||||
}
|
||||
server.slaveStatus = initReplSlaveStatus()
|
||||
server.initMaster()
|
||||
server.initMasterStatus()
|
||||
server.startReplCron()
|
||||
server.role = masterRole // The initialization process does not require atomicity
|
||||
return server
|
||||
|
Reference in New Issue
Block a user