mirror of
https://github.com/HDT3213/godis.git
synced 2025-09-27 13:12:19 +08:00
WIP
This commit is contained in:
@@ -5,7 +5,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
_ "github.com/hdt3213/godis/cluster/commands" // register nodes
|
_ "github.com/hdt3213/godis/cluster/commands" // register commands
|
||||||
"github.com/hdt3213/godis/cluster/core"
|
"github.com/hdt3213/godis/cluster/core"
|
||||||
"github.com/hdt3213/godis/cluster/raft"
|
"github.com/hdt3213/godis/cluster/raft"
|
||||||
"github.com/hdt3213/godis/config"
|
"github.com/hdt3213/godis/config"
|
||||||
|
74
cluster/commands/mset.go
Normal file
74
cluster/commands/mset.go
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hdt3213/godis/cluster/core"
|
||||||
|
"github.com/hdt3213/godis/interface/redis"
|
||||||
|
"github.com/hdt3213/godis/lib/utils"
|
||||||
|
"github.com/hdt3213/godis/redis/protocol"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
core.RegisterCmd("mset_", execMSet_)
|
||||||
|
}
|
||||||
|
|
||||||
|
type CmdLine = [][]byte
|
||||||
|
|
||||||
|
// execMSet_ executes msets in local node
|
||||||
|
func execMSet_(cluster *core.Cluster, c redis.Connection, cmdLine CmdLine) redis.Reply {
|
||||||
|
if len(cmdLine) < 3 {
|
||||||
|
return protocol.MakeArgNumErrReply("mset")
|
||||||
|
}
|
||||||
|
cmdLine[0] = []byte("mset")
|
||||||
|
return cluster.LocalExec(c, cmdLine)
|
||||||
|
}
|
||||||
|
|
||||||
|
func requestRollback(cluster *core.Cluster, c redis.Connection, txId string, routeMap map[string][]string) {
|
||||||
|
rollbackCmd := utils.ToCmdLine("rollback", txId)
|
||||||
|
for node := range routeMap {
|
||||||
|
cluster.Relay(node, c, rollbackCmd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// execMSetSlow execute mset through tcc
|
||||||
|
func execMSetSlow(cluster *core.Cluster, c redis.Connection, cmdLine CmdLine, routeMap map[string][]string) redis.Reply {
|
||||||
|
txId := utils.RandString(6)
|
||||||
|
|
||||||
|
keyValues := make(map[string][]byte)
|
||||||
|
for i := 1; i < len(cmdLine); i += 2 {
|
||||||
|
key := string(cmdLine[i])
|
||||||
|
value := cmdLine[i+1]
|
||||||
|
keyValues[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
// make prepare requests
|
||||||
|
nodePrepareCmdMap := make(map[string]CmdLine)
|
||||||
|
for node, keys := range routeMap {
|
||||||
|
prepareCmd := utils.ToCmdLine("prepare", txId, "mset")
|
||||||
|
for _, key := range keys {
|
||||||
|
value := keyValues[key]
|
||||||
|
prepareCmd = append(prepareCmd, []byte(key), value)
|
||||||
|
}
|
||||||
|
nodePrepareCmdMap[node] = prepareCmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// send prepare request
|
||||||
|
for node, prepareCmd := range nodePrepareCmdMap {
|
||||||
|
reply := cluster.Relay(node, c, prepareCmd)
|
||||||
|
if protocol.IsErrorReply(reply) {
|
||||||
|
requestRollback(cluster, c, txId, routeMap)
|
||||||
|
return protocol.MakeErrReply("prepare failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// send commit request
|
||||||
|
commiteCmd := utils.ToCmdLine("commit", txId)
|
||||||
|
for node := range nodePrepareCmdMap {
|
||||||
|
reply := cluster.Relay(node, c, commiteCmd)
|
||||||
|
if protocol.IsErrorReply(reply) {
|
||||||
|
requestRollback(cluster, c, txId, routeMap)
|
||||||
|
return protocol.MakeErrReply("commit failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return protocol.MakeOkReply()
|
||||||
|
}
|
@@ -21,6 +21,7 @@ type Cluster struct {
|
|||||||
|
|
||||||
slotsManager *slotsManager
|
slotsManager *slotsManager
|
||||||
rebalanceManger *rebalanceManager
|
rebalanceManger *rebalanceManager
|
||||||
|
transactions *TransactionManager
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
@@ -127,6 +128,7 @@ func NewCluster(cfg *Config) (*Cluster, error) {
|
|||||||
config: cfg,
|
config: cfg,
|
||||||
rebalanceManger: newRebalanceManager(),
|
rebalanceManger: newRebalanceManager(),
|
||||||
slotsManager: newSlotsManager(),
|
slotsManager: newSlotsManager(),
|
||||||
|
transactions: newTransactionManager(),
|
||||||
}
|
}
|
||||||
cluster.injectInsertCallback()
|
cluster.injectInsertCallback()
|
||||||
cluster.injectDeleteCallback()
|
cluster.injectDeleteCallback()
|
||||||
|
@@ -152,7 +152,6 @@ func execFinishExport(cluster *Cluster, c redis.Connection, cmdLine CmdLine) red
|
|||||||
}
|
}
|
||||||
logger.Infof("finishing migration task %s, got task info", taskId)
|
logger.Infof("finishing migration task %s, got task info", taskId)
|
||||||
|
|
||||||
|
|
||||||
// transport dirty keys within lock, lock will be released while migration done
|
// transport dirty keys within lock, lock will be released while migration done
|
||||||
var lockedSlots []uint32
|
var lockedSlots []uint32
|
||||||
defer func() {
|
defer func() {
|
||||||
@@ -171,7 +170,6 @@ func execFinishExport(cluster *Cluster, c redis.Connection, cmdLine CmdLine) red
|
|||||||
}
|
}
|
||||||
logger.Infof("finishing migration task %s, dirty keys transported", taskId)
|
logger.Infof("finishing migration task %s, dirty keys transported", taskId)
|
||||||
|
|
||||||
|
|
||||||
// propose migrate finish
|
// propose migrate finish
|
||||||
leaderConn, err := cluster.BorrowLeaderClient()
|
leaderConn, err := cluster.BorrowLeaderClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
128
cluster/core/tcc.go
Normal file
128
cluster/core/tcc.go
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/hdt3213/godis/database"
|
||||||
|
"github.com/hdt3213/godis/interface/redis"
|
||||||
|
"github.com/hdt3213/godis/redis/protocol"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TransactionManager struct {
|
||||||
|
txs map[string]*TCC
|
||||||
|
mu sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
type TCC struct {
|
||||||
|
realCmdLine CmdLine
|
||||||
|
undoLogs []CmdLine
|
||||||
|
writeKeys []string
|
||||||
|
readKeys []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTransactionManager() *TransactionManager {
|
||||||
|
return &TransactionManager{
|
||||||
|
txs: make(map[string]*TCC),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterCmd("prepare", execPrepare)
|
||||||
|
RegisterCmd("commit", execCommit)
|
||||||
|
RegisterCmd("rollback", execRollback)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// execPrepare executes prepare command
|
||||||
|
// commandline: prepare txid realCmd realArgs...
|
||||||
|
// execPrepare will check transaction preconditions, lock related keys and prepare undo logs
|
||||||
|
func execPrepare(cluster *Cluster, c redis.Connection, cmdLine CmdLine) redis.Reply {
|
||||||
|
if len(cmdLine) < 3 {
|
||||||
|
return protocol.MakeArgNumErrReply("prepare")
|
||||||
|
}
|
||||||
|
txId := string(cmdLine[1])
|
||||||
|
realCmdLine := cmdLine[2:]
|
||||||
|
|
||||||
|
// create transaction
|
||||||
|
cluster.transactions.mu.Lock()
|
||||||
|
tx := cluster.transactions.txs[txId]
|
||||||
|
if tx != nil {
|
||||||
|
cluster.transactions.mu.Unlock()
|
||||||
|
return protocol.MakeErrReply("transction existed")
|
||||||
|
}
|
||||||
|
tx = &TCC{}
|
||||||
|
cluster.transactions.txs[txId] = tx
|
||||||
|
cluster.transactions.mu.Unlock()
|
||||||
|
|
||||||
|
// todo: pre-execute check
|
||||||
|
|
||||||
|
// prepare lock and undo locks
|
||||||
|
tx.writeKeys, tx.readKeys = database.GetRelatedKeys(realCmdLine)
|
||||||
|
cluster.db.RWLocks(0, tx.writeKeys, tx.readKeys)
|
||||||
|
tx.undoLogs = cluster.db.GetUndoLogs(0, realCmdLine)
|
||||||
|
tx.realCmdLine = realCmdLine
|
||||||
|
|
||||||
|
return protocol.MakeOkReply()
|
||||||
|
}
|
||||||
|
|
||||||
|
func execCommit(cluster *Cluster, c redis.Connection, cmdLine CmdLine) redis.Reply {
|
||||||
|
if len(cmdLine) != 2 {
|
||||||
|
return protocol.MakeArgNumErrReply("commit")
|
||||||
|
}
|
||||||
|
txId := string(cmdLine[1])
|
||||||
|
|
||||||
|
cluster.transactions.mu.Lock()
|
||||||
|
tx := cluster.transactions.txs[txId]
|
||||||
|
if tx == nil {
|
||||||
|
cluster.transactions.mu.Unlock()
|
||||||
|
return protocol.MakeErrReply("transction not found")
|
||||||
|
}
|
||||||
|
cluster.transactions.mu.Unlock()
|
||||||
|
|
||||||
|
resp := cluster.db.Exec(c, tx.realCmdLine)
|
||||||
|
|
||||||
|
// unlock regardless of result
|
||||||
|
cluster.db.RWUnLocks(0, tx.writeKeys, tx.readKeys)
|
||||||
|
|
||||||
|
if protocol.IsErrorReply(resp) {
|
||||||
|
// do not delete transaction, waiting rollback
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
// todo delete transaction after deadline
|
||||||
|
// cluster.transactions.mu.Lock()
|
||||||
|
// delete(cluster.transactions.txs, txId)
|
||||||
|
// cluster.transactions.mu.Unlock()
|
||||||
|
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
|
||||||
|
func execRollback(cluster *Cluster, c redis.Connection, cmdLine CmdLine) redis.Reply {
|
||||||
|
if len(cmdLine) != 2 {
|
||||||
|
return protocol.MakeArgNumErrReply("rollback")
|
||||||
|
}
|
||||||
|
txId := string(cmdLine[1])
|
||||||
|
|
||||||
|
// get transaction
|
||||||
|
cluster.transactions.mu.Lock()
|
||||||
|
tx := cluster.transactions.txs[txId]
|
||||||
|
if tx == nil {
|
||||||
|
cluster.transactions.mu.Unlock()
|
||||||
|
return protocol.MakeErrReply("transction not found")
|
||||||
|
}
|
||||||
|
cluster.transactions.mu.Unlock()
|
||||||
|
|
||||||
|
// rollback
|
||||||
|
cluster.db.RWLocks(0, tx.writeKeys, tx.readKeys)
|
||||||
|
for i := len(tx.undoLogs) - 1; i >= 0; i-- {
|
||||||
|
cmdline := tx.undoLogs[i]
|
||||||
|
cluster.db.Exec(c, cmdline)
|
||||||
|
}
|
||||||
|
cluster.db.RWUnLocks(0, tx.writeKeys, tx.readKeys)
|
||||||
|
|
||||||
|
// delete transaction
|
||||||
|
cluster.transactions.mu.Lock()
|
||||||
|
delete(cluster.transactions.txs, txId)
|
||||||
|
cluster.transactions.mu.Unlock()
|
||||||
|
|
||||||
|
return protocol.MakeOkReply()
|
||||||
|
}
|
@@ -34,6 +34,11 @@ func (cluster *Cluster) Relay(peerId string, c redis.Connection, cmdLine [][]byt
|
|||||||
return cli.Send(cmdLine)
|
return cli.Send(cmdLine)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LocalExec executes command at local node
|
||||||
|
func (cluster *Cluster) LocalExec(c redis.Connection, cmdLine [][]byte) redis.Reply {
|
||||||
|
return cluster.db.Exec(c, cmdLine)
|
||||||
|
}
|
||||||
|
|
||||||
// GetPartitionKey extract hashtag
|
// GetPartitionKey extract hashtag
|
||||||
func GetPartitionKey(key string) string {
|
func GetPartitionKey(key string) string {
|
||||||
beg := strings.Index(key, "{")
|
beg := strings.Index(key, "{")
|
||||||
@@ -65,4 +70,4 @@ func execRaftCommittedIndex(cluster *Cluster, c redis.Connection, cmdLine CmdLin
|
|||||||
return protocol.MakeErrReply(err.Error())
|
return protocol.MakeErrReply(err.Error())
|
||||||
}
|
}
|
||||||
return protocol.MakeIntReply(int64(index))
|
return protocol.MakeIntReply(int64(index))
|
||||||
}
|
}
|
||||||
|
@@ -164,6 +164,7 @@ func (db *DB) GetUndoLogs(cmdLine [][]byte) []CmdLine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetRelatedKeys analysis related keys
|
// GetRelatedKeys analysis related keys
|
||||||
|
// returns related write keys and read keys
|
||||||
func GetRelatedKeys(cmdLine [][]byte) ([]string, []string) {
|
func GetRelatedKeys(cmdLine [][]byte) ([]string, []string) {
|
||||||
cmdName := strings.ToLower(string(cmdLine[0]))
|
cmdName := strings.ToLower(string(cmdLine[0]))
|
||||||
cmd, ok := cmdTable[cmdName]
|
cmd, ok := cmdTable[cmdName]
|
||||||
|
Reference in New Issue
Block a user