Files
redis-go/cluster/raft_snapshot.go
finley bf7f628810 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
2023-06-10 22:48:24 +08:00

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
}