mirror of
https://github.com/HDT3213/godis.git
synced 2025-10-05 08:46:56 +08:00
raft cluster
wip: raft does not care about migrating wip: optimize code wip: raft election wip wip: fix raft leader missing log entries wip fix a dead lock batch set slot route wip: raft persist wip refactor cluster suite remove relay rename relay2 refactor: allow customizing client factory test raft refactor re-balance avoid errors caused by inconsistent status on follower nodes during raft commits test raft election
This commit is contained in:
146
cluster/topo_gcluster.go
Normal file
146
cluster/topo_gcluster.go
Normal file
@@ -0,0 +1,146 @@
|
||||
package cluster
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/hdt3213/godis/aof"
|
||||
"github.com/hdt3213/godis/interface/redis"
|
||||
"github.com/hdt3213/godis/lib/logger"
|
||||
"github.com/hdt3213/godis/redis/protocol"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func init() {
|
||||
registerCmd("gcluster", execGCluster)
|
||||
}
|
||||
|
||||
func execGCluster(cluster *Cluster, c redis.Connection, args [][]byte) redis.Reply {
|
||||
if len(args) < 2 {
|
||||
return protocol.MakeArgNumErrReply("gcluster")
|
||||
}
|
||||
subCmd := strings.ToLower(string(args[1]))
|
||||
switch subCmd {
|
||||
case "set-slot":
|
||||
// Command line: gcluster set-slot <slotID> <targetNodeID>
|
||||
// Other node request current node to migrate a slot to it.
|
||||
// Current node will set the slot as migrating state.
|
||||
// After this function return, all requests of target slot will be routed to target node
|
||||
return execGClusterSetSlot(cluster, c, args[2:])
|
||||
case "migrate":
|
||||
// Command line: gcluster migrate <slotId>
|
||||
// Current node will dump the given slot to the node sending this request
|
||||
// The given slot must in migrating state
|
||||
return execGClusterMigrate(cluster, c, args[2:])
|
||||
case "migrate-done":
|
||||
// command line: gcluster migrate-done <slotId>
|
||||
// The new node hosting given slot tells current node that migration has finished, remains data can be deleted
|
||||
return execGClusterMigrateDone(cluster, c, args[2:])
|
||||
case "request-donate":
|
||||
// command line: gcluster donate <nodeID>
|
||||
// picks some slots and gives them to the calling node for load balance
|
||||
return execGClusterDonateSlot(cluster, c, args[2:])
|
||||
}
|
||||
return protocol.MakeErrReply(" ERR unknown gcluster sub command '" + subCmd + "'")
|
||||
}
|
||||
|
||||
// execGClusterSetSlot set a current node hosted slot as migrating
|
||||
// args is [slotID, newNodeId]
|
||||
func execGClusterSetSlot(cluster *Cluster, c redis.Connection, args [][]byte) redis.Reply {
|
||||
if len(args) != 2 {
|
||||
return protocol.MakeArgNumErrReply("gcluster")
|
||||
}
|
||||
slotId0, err := strconv.Atoi(string(args[0]))
|
||||
if err != nil || slotId0 >= slotCount {
|
||||
return protocol.MakeErrReply("ERR value is not a valid slot id")
|
||||
}
|
||||
slotId := uint32(slotId0)
|
||||
targetNodeID := string(args[1])
|
||||
targetNode := cluster.topology.GetNode(targetNodeID)
|
||||
if targetNode == nil {
|
||||
return protocol.MakeErrReply("ERR node not found")
|
||||
}
|
||||
cluster.setSlotMovingOut(slotId, targetNodeID)
|
||||
logger.Info(fmt.Sprintf("set slot %d to node %s", slotId, targetNodeID))
|
||||
return protocol.MakeOkReply()
|
||||
}
|
||||
|
||||
// execGClusterDonateSlot picks some slots and gives them to the calling node for load balance
|
||||
// args is [callingNodeId]
|
||||
func execGClusterDonateSlot(cluster *Cluster, c redis.Connection, args [][]byte) redis.Reply {
|
||||
targetNodeID := string(args[0])
|
||||
nodes := cluster.topology.GetNodes() // including the new node
|
||||
avgSlot := slotCount / len(nodes)
|
||||
cluster.slotMu.Lock()
|
||||
defer cluster.slotMu.Unlock()
|
||||
limit := len(cluster.slots) - avgSlot
|
||||
if limit <= 0 {
|
||||
return protocol.MakeEmptyMultiBulkReply()
|
||||
}
|
||||
result := make([][]byte, 0, limit)
|
||||
// use the randomness of the for-each-in-map to randomly select slots
|
||||
for slotID, slot := range cluster.slots {
|
||||
if slot.state == slotStateHost {
|
||||
slot.state = slotStateMovingOut
|
||||
slot.newNodeID = targetNodeID
|
||||
slotIDBin := []byte(strconv.FormatUint(uint64(slotID), 10))
|
||||
result = append(result, slotIDBin)
|
||||
if len(result) == limit {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return protocol.MakeMultiBulkReply(result)
|
||||
}
|
||||
|
||||
// execGClusterMigrate Command line: gcluster migrate slotId
|
||||
// Current node will dump data in the given slot to the node sending this request
|
||||
// The given slot must in migrating state
|
||||
func execGClusterMigrate(cluster *Cluster, c redis.Connection, args [][]byte) redis.Reply {
|
||||
slotId0, err := strconv.Atoi(string(args[0]))
|
||||
if err != nil || slotId0 >= slotCount {
|
||||
return protocol.MakeErrReply("ERR value is not a valid slot id")
|
||||
}
|
||||
slotId := uint32(slotId0)
|
||||
slot := cluster.getHostSlot(slotId)
|
||||
if slot == nil || slot.state != slotStateMovingOut {
|
||||
return protocol.MakeErrReply("ERR only dump migrating slot")
|
||||
}
|
||||
// migrating slot is immutable
|
||||
logger.Info("start dump slot", slotId)
|
||||
slot.keys.ForEach(func(key string) bool {
|
||||
entity, ok := cluster.db.GetEntity(0, key)
|
||||
if ok {
|
||||
ret := aof.EntityToCmd(key, entity)
|
||||
// todo: handle error and close connection
|
||||
_, _ = c.Write(ret.ToBytes())
|
||||
expire := cluster.db.GetExpiration(0, key)
|
||||
if expire != nil {
|
||||
ret = aof.MakeExpireCmd(key, *expire)
|
||||
_, _ = c.Write(ret.ToBytes())
|
||||
}
|
||||
|
||||
}
|
||||
return true
|
||||
})
|
||||
logger.Info("finish dump slot ", slotId)
|
||||
// send a ok reply to tell requesting node dump finished
|
||||
return protocol.MakeOkReply()
|
||||
}
|
||||
|
||||
// execGClusterMigrateDone command line: gcluster migrate-done <slotId>
|
||||
func execGClusterMigrateDone(cluster *Cluster, c redis.Connection, args [][]byte) redis.Reply {
|
||||
slotId0, err := strconv.Atoi(string(args[0]))
|
||||
if err != nil || slotId0 >= slotCount {
|
||||
return protocol.MakeErrReply("ERR value is not a valid slot id")
|
||||
}
|
||||
slotId := uint32(slotId0)
|
||||
slot := cluster.getHostSlot(slotId)
|
||||
if slot == nil || slot.state != slotStateMovingOut {
|
||||
return protocol.MakeErrReply("ERR slot is not moving out")
|
||||
}
|
||||
cluster.cleanDroppedSlot(slotId)
|
||||
cluster.slotMu.Lock()
|
||||
delete(cluster.slots, slotId)
|
||||
cluster.slotMu.Unlock()
|
||||
return protocol.MakeOkReply()
|
||||
}
|
Reference in New Issue
Block a user