Support failover in cluster (experimental)

This commit is contained in:
finley
2025-04-19 22:11:58 +08:00
parent 14ec8277ca
commit f4a2c92fc1
20 changed files with 739 additions and 136 deletions

View File

@@ -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)

View File

@@ -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),

View File

@@ -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)

View File

@@ -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())

View File

@@ -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")
}

View File

@@ -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