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  }