github.com/hdt3213/godis@v1.2.9/cluster/multi.go (about) 1 package cluster 2 3 import ( 4 "github.com/hdt3213/godis/database" 5 "github.com/hdt3213/godis/interface/redis" 6 "github.com/hdt3213/godis/lib/utils" 7 "github.com/hdt3213/godis/redis/protocol" 8 "strconv" 9 ) 10 11 const relayMulti = "_multi" 12 const innerWatch = "_watch" 13 14 var relayMultiBytes = []byte(relayMulti) 15 16 // cmdLine == []string{"exec"} 17 func execMulti(cluster *Cluster, conn redis.Connection, cmdLine CmdLine) redis.Reply { 18 if !conn.InMultiState() { 19 return protocol.MakeErrReply("ERR EXEC without MULTI") 20 } 21 defer conn.SetMultiState(false) 22 cmdLines := conn.GetQueuedCmdLine() 23 24 // analysis related keys 25 keys := make([]string, 0) // may contains duplicate 26 for _, cl := range cmdLines { 27 wKeys, rKeys := database.GetRelatedKeys(cl) 28 keys = append(keys, wKeys...) 29 keys = append(keys, rKeys...) 30 } 31 watching := conn.GetWatching() 32 watchingKeys := make([]string, 0, len(watching)) 33 for key := range watching { 34 watchingKeys = append(watchingKeys, key) 35 } 36 keys = append(keys, watchingKeys...) 37 if len(keys) == 0 { 38 // empty transaction or only `PING`s 39 return cluster.db.ExecMulti(conn, watching, cmdLines) 40 } 41 groupMap := cluster.groupBy(keys) 42 if len(groupMap) > 1 { 43 return protocol.MakeErrReply("ERR MULTI commands transaction must within one slot in cluster mode") 44 } 45 var peer string 46 // assert len(groupMap) == 1 47 for p := range groupMap { 48 peer = p 49 } 50 51 // out parser not support protocol.MultiRawReply, so we have to encode it 52 if peer == cluster.self { 53 return cluster.db.ExecMulti(conn, watching, cmdLines) 54 } 55 return execMultiOnOtherNode(cluster, conn, peer, watching, cmdLines) 56 } 57 58 func execMultiOnOtherNode(cluster *Cluster, conn redis.Connection, peer string, watching map[string]uint32, cmdLines []CmdLine) redis.Reply { 59 defer func() { 60 conn.ClearQueuedCmds() 61 conn.SetMultiState(false) 62 }() 63 relayCmdLine := [][]byte{ // relay it to executing node 64 relayMultiBytes, 65 } 66 // watching commands 67 var watchingCmdLine = utils.ToCmdLine(innerWatch) 68 for key, ver := range watching { 69 verStr := strconv.FormatUint(uint64(ver), 10) 70 watchingCmdLine = append(watchingCmdLine, []byte(key), []byte(verStr)) 71 } 72 relayCmdLine = append(relayCmdLine, encodeCmdLine([]CmdLine{watchingCmdLine})...) 73 relayCmdLine = append(relayCmdLine, encodeCmdLine(cmdLines)...) 74 var rawRelayResult redis.Reply 75 if peer == cluster.self { 76 // this branch just for testing 77 rawRelayResult = execRelayedMulti(cluster, conn, relayCmdLine) 78 } else { 79 rawRelayResult = cluster.relay(peer, conn, relayCmdLine) 80 } 81 if protocol.IsErrorReply(rawRelayResult) { 82 return rawRelayResult 83 } 84 _, ok := rawRelayResult.(*protocol.EmptyMultiBulkReply) 85 if ok { 86 return rawRelayResult 87 } 88 relayResult, ok := rawRelayResult.(*protocol.MultiBulkReply) 89 if !ok { 90 return protocol.MakeErrReply("execute failed") 91 } 92 rep, err := parseEncodedMultiRawReply(relayResult.Args) 93 if err != nil { 94 return protocol.MakeErrReply(err.Error()) 95 } 96 return rep 97 } 98 99 // execRelayedMulti execute relayed multi commands transaction 100 // cmdLine format: _multi watch-cmdLine base64ed-cmdLine 101 // result format: base64ed-protocol list 102 func execRelayedMulti(cluster *Cluster, conn redis.Connection, cmdLine CmdLine) redis.Reply { 103 if len(cmdLine) < 2 { 104 return protocol.MakeArgNumErrReply("_exec") 105 } 106 decoded, err := parseEncodedMultiRawReply(cmdLine[1:]) 107 if err != nil { 108 return protocol.MakeErrReply(err.Error()) 109 } 110 var txCmdLines []CmdLine 111 for _, rep := range decoded.Replies { 112 mbr, ok := rep.(*protocol.MultiBulkReply) 113 if !ok { 114 return protocol.MakeErrReply("exec failed") 115 } 116 txCmdLines = append(txCmdLines, mbr.Args) 117 } 118 watching := make(map[string]uint32) 119 watchCmdLine := txCmdLines[0] // format: _watch key1 ver1 key2 ver2... 120 for i := 2; i < len(watchCmdLine); i += 2 { 121 key := string(watchCmdLine[i-1]) 122 verStr := string(watchCmdLine[i]) 123 ver, err := strconv.ParseUint(verStr, 10, 64) 124 if err != nil { 125 return protocol.MakeErrReply("watching command line failed") 126 } 127 watching[key] = uint32(ver) 128 } 129 rawResult := cluster.db.ExecMulti(conn, watching, txCmdLines[1:]) 130 _, ok := rawResult.(*protocol.EmptyMultiBulkReply) 131 if ok { 132 return rawResult 133 } 134 resultMBR, ok := rawResult.(*protocol.MultiRawReply) 135 if !ok { 136 return protocol.MakeErrReply("exec failed") 137 } 138 return encodeMultiRawReply(resultMBR) 139 } 140 141 func execWatch(cluster *Cluster, conn redis.Connection, args [][]byte) redis.Reply { 142 if len(args) < 2 { 143 return protocol.MakeArgNumErrReply("watch") 144 } 145 args = args[1:] 146 watching := conn.GetWatching() 147 for _, bkey := range args { 148 key := string(bkey) 149 peer := cluster.peerPicker.PickNode(key) 150 result := cluster.relay(peer, conn, utils.ToCmdLine("GetVer", key)) 151 if protocol.IsErrorReply(result) { 152 return result 153 } 154 intResult, ok := result.(*protocol.IntReply) 155 if !ok { 156 return protocol.MakeErrReply("get version failed") 157 } 158 watching[key] = uint32(intResult.Code) 159 } 160 return protocol.MakeOkReply() 161 }