mirror of
https://github.com/HDT3213/godis.git
synced 2025-10-05 00:42:43 +08:00

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
205 lines
5.3 KiB
Go
205 lines
5.3 KiB
Go
package cluster
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"github.com/hdt3213/godis/redis/protocol"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// marshalSlotIds serializes slot ids
|
|
// For example, 1, 2, 3, 5, 7, 8 -> 1-3, 5, 7-8
|
|
func marshalSlotIds(slots []*Slot) []string {
|
|
sort.Slice(slots, func(i, j int) bool {
|
|
return slots[i].ID < slots[j].ID
|
|
})
|
|
// find continuous scopes
|
|
var scopes [][]uint32
|
|
buf := make([]uint32, 2)
|
|
var scope []uint32
|
|
for i, slot := range slots {
|
|
if len(scope) == 0 { // outside scope
|
|
if i+1 < len(slots) &&
|
|
slots[i+1].ID == slot.ID+1 { // if continuous, then start one
|
|
scope = buf
|
|
scope[0] = slot.ID
|
|
} else { // discrete number
|
|
scopes = append(scopes, []uint32{slot.ID})
|
|
}
|
|
} else { // within a scope
|
|
if i == len(slots)-1 || slots[i+1].ID != slot.ID+1 { // reach end or not continuous, stop current scope
|
|
scope[1] = slot.ID
|
|
scopes = append(scopes, []uint32{scope[0], scope[1]})
|
|
scope = nil
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// marshal scopes
|
|
result := make([]string, 0, len(scopes))
|
|
for _, scope := range scopes {
|
|
if len(scope) == 1 {
|
|
s := strconv.Itoa(int(scope[0]))
|
|
result = append(result, s)
|
|
} else { // assert len(scope) == 2
|
|
beg := strconv.Itoa(int(scope[0]))
|
|
end := strconv.Itoa(int(scope[1]))
|
|
result = append(result, beg+"-"+end)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
// unmarshalSlotIds deserializes lines generated by marshalSlotIds
|
|
func unmarshalSlotIds(args []string) ([]uint32, error) {
|
|
var result []uint32
|
|
for i, line := range args {
|
|
if pivot := strings.IndexByte(line, '-'); pivot > 0 {
|
|
// line is a scope
|
|
beg, err := strconv.Atoi(line[:pivot])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("illegal at slot line %d", i+1)
|
|
}
|
|
end, err := strconv.Atoi(line[pivot+1:])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("illegal at slot line %d", i+1)
|
|
}
|
|
for j := beg; j <= end; j++ {
|
|
result = append(result, uint32(j))
|
|
}
|
|
} else {
|
|
// line is a number
|
|
v, err := strconv.Atoi(line)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("illegal at slot line %d", i)
|
|
}
|
|
result = append(result, uint32(v))
|
|
}
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
type nodePayload struct {
|
|
ID string `json:"id"`
|
|
Addr string `json:"addr"`
|
|
SlotDesc []string `json:"slotDesc"`
|
|
Flags uint32 `json:"flags"`
|
|
}
|
|
|
|
func marshalNodes(nodes map[string]*Node) [][]byte {
|
|
var args [][]byte
|
|
for _, node := range nodes {
|
|
slotLines := marshalSlotIds(node.Slots)
|
|
payload := &nodePayload{
|
|
ID: node.ID,
|
|
Addr: node.Addr,
|
|
SlotDesc: slotLines,
|
|
Flags: node.Flags,
|
|
}
|
|
bin, _ := json.Marshal(payload)
|
|
args = append(args, bin)
|
|
}
|
|
return args
|
|
}
|
|
|
|
func unmarshalNodes(args [][]byte) (map[string]*Node, error) {
|
|
nodeMap := make(map[string]*Node)
|
|
for i, bin := range args {
|
|
payload := &nodePayload{}
|
|
err := json.Unmarshal(bin, payload)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unmarshal node failed at line %d: %v", i+1, err)
|
|
}
|
|
slotIds, err := unmarshalSlotIds(payload.SlotDesc)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
node := &Node{
|
|
ID: payload.ID,
|
|
Addr: payload.Addr,
|
|
Flags: payload.Flags,
|
|
}
|
|
for _, slotId := range slotIds {
|
|
node.Slots = append(node.Slots, &Slot{
|
|
ID: slotId,
|
|
NodeID: node.ID,
|
|
Flags: 0,
|
|
})
|
|
}
|
|
nodeMap[node.ID] = node
|
|
}
|
|
return nodeMap, nil
|
|
}
|
|
|
|
// genSnapshot
|
|
// invoker provide lock
|
|
func (raft *Raft) makeSnapshot() [][]byte {
|
|
topology := marshalNodes(raft.nodes)
|
|
snapshot := [][]byte{
|
|
[]byte(raft.selfNodeID),
|
|
[]byte(strconv.Itoa(int(raft.state))),
|
|
[]byte(raft.leaderId),
|
|
[]byte(strconv.Itoa(raft.term)),
|
|
[]byte(strconv.Itoa(raft.committedIndex)),
|
|
}
|
|
snapshot = append(snapshot, topology...)
|
|
return snapshot
|
|
}
|
|
|
|
// makeSnapshotForFollower used by leader node to generate snapshot for follower
|
|
// invoker provide with lock
|
|
func (raft *Raft) makeSnapshotForFollower(followerId string) [][]byte {
|
|
snapshot := raft.makeSnapshot()
|
|
snapshot[0] = []byte(followerId)
|
|
snapshot[1] = []byte(strconv.Itoa(int(follower)))
|
|
return snapshot
|
|
}
|
|
|
|
// invoker provide with lock
|
|
func (raft *Raft) loadSnapshot(snapshot [][]byte) protocol.ErrorReply {
|
|
// make sure raft.slots and node.Slots is the same object
|
|
selfNodeId := string(snapshot[0])
|
|
state0, err := strconv.Atoi(string(snapshot[1]))
|
|
if err != nil {
|
|
return protocol.MakeErrReply("illegal state: " + string(snapshot[1]))
|
|
}
|
|
state := raftState(state0)
|
|
if _, ok := stateNames[state]; !ok {
|
|
return protocol.MakeErrReply("unknown state: " + strconv.Itoa(int(state)))
|
|
}
|
|
leaderId := string(snapshot[2])
|
|
term, err := strconv.Atoi(string(snapshot[3]))
|
|
if err != nil {
|
|
return protocol.MakeErrReply("illegal term: " + string(snapshot[3]))
|
|
}
|
|
commitIndex, err := strconv.Atoi(string(snapshot[4]))
|
|
if err != nil {
|
|
return protocol.MakeErrReply("illegal commit index: " + string(snapshot[3]))
|
|
}
|
|
nodes, err := unmarshalNodes(snapshot[5:])
|
|
if err != nil {
|
|
return protocol.MakeErrReply(err.Error())
|
|
}
|
|
raft.selfNodeID = selfNodeId
|
|
raft.state = state
|
|
raft.leaderId = leaderId
|
|
raft.term = term
|
|
raft.committedIndex = commitIndex
|
|
raft.proposedIndex = commitIndex
|
|
raft.initLog(term, commitIndex, nil)
|
|
raft.slots = make([]*Slot, slotCount)
|
|
for _, node := range nodes {
|
|
for _, slot := range node.Slots {
|
|
raft.slots[int(slot.ID)] = slot
|
|
}
|
|
if node.getState() == leader {
|
|
raft.leaderId = node.ID
|
|
}
|
|
}
|
|
raft.nodes = nodes
|
|
return nil
|
|
}
|