In a stand-alone Redis cluster, there are two ways to implement complex atomic operations for multiple keys. One is Watch+Multi, which is monitor plus transaction, and the other is by executing lua scripts.

The complex atomistic operation described here, such as the deduction of 5 inventory of an item, requires the determination of whether the remaining inventory of the current item is sufficient for the deduction. However, we cannot avoid the situation that the inventory has not been modified by others during the period when we judge enough and then execute the deduction operation.

Watch+Multi

Watch can monitor multiple keys and monitored keys. As long as one of the monitored keys is modified, all operations will not be performed, not only on the monitored keys. Watch cannot be used alone, but in conjunction with transactions.

Now let’s see what happens if the key is changed after the Watch, but before the transaction.

This makes sense. Even if the order of the commands in the transaction is different, because the commands are executed in a single thread, exec will already know which keys have been changed and it will simply not execute the transaction.

The watched_keys dictionary is stored in each Redis database. The key of the dictionary is a key that is monitored by the Watch command, such as S1 and S2 in the above example. The value of the dictionary is a list of all the clients that monitor the key, i.e. a connection.

After all write commands are executed, the watched_keys dictionary is checked to see if any client is monitoring the newly written key. If yes, the REDIS_DIRTY_CAS identifier of the client monitoring the key is enabled, indicating that the transaction security of the client has been breached. When a client (a connection) commits (exec) a transaction, the identity is first checked to see if it is turned on, and if so, the committed transaction is rejected by the current client.

Suppose it is executed under a Cluster, what is the result? I deployed a cluster on my server, which was deployed a long time ago. Let’s see what happens when you execute Watch on multiple keys in a Cluster.

As you can see, there is an error on the Watch. The request does not allow the key to be in a different slot. Even if the slots are different, they must be in the same slot. In the figure below, keys S4 and S5 are on the same node.

Watch is really strict, different slot is not acceptable. Ever wonder why listening for multiple keys on different nodes is not allowed? Under single node, Redis executes in single thread, which can guarantee atomicity. However, under different nodes, it is the problem of multi-process and multi-thread, so Watch cannot be used naturally.

The Lua script

Redis executing lua scripts is atomic and is treated the same as executing Redis commands. Since Redis executes commands in a single thread and lua scripts are queued for execution, you need to be careful not to execute too much code in lua scripts. It is best not to write for loops, which can block and cause the Redis node to suspended animation.

In the primary/secondary Redis cluster and the read/write separation Redis cluster, the writing of lua scripts does not need to worry too much, just consider the script time. However, in a cluster with slots, if we want to implement atomicity operation through lua script, we must ensure that all keys operated by the script are under the same Redis node, that is, slots calculated by all keys fall under the same Redis node (master in small cluster), so as to ensure that the command is atomicity.

In fact, if the key to be operated on is not on the same node, the command execution will fail. Below is an error thrown by a Lua script trying to access a non-local node in the cluster.

Even if multiple Lua scripts are executed in a single transaction, if one lua script operates on a key on a different node, the execution will fail.

It’s not just lua scripts, because transactions do not support commands between multi and exec, where the key of the operation falls on different nodes.

Different slots won’t work either.

Goods in and out of the storage problem

In terms of inventory, there are a lot of videos on using distributed locks, and you might think about using distributed locks, but if you’ve used distributed locks, you know what performance looks like.

If the goods inventory only uses the Redis cache, there is no need to modify the inventory in the database. For the inventory of goods to achieve warehousing and warehousing, we can use lua script to achieve atomic modification of inventory. The sequence of operations in the Lua script to determine if the inventory is sufficient to reduce the inventory, and to update the inventory if the inventory is sufficient to reduce the inventory, is atomic.

Lua scripts aren’t hard to learn, and after looking at conditional selection, branching, and judging statements, I can write my own scripts. Because we don’t need to do anything complicated with Lua. Here is an example lua script for atomically modifying inventory:

local kc=tonumber(redis.call('GET',KEYS[1])); 
if kc==nil 
then 
    return - 1; 
end 
local newKc=kc-ARGV[1]; 
if newKc<0 
then 
    return 0; 
else 
    redis.call('SET',KEYS[1],newKc); 
    return 1; 
end
Copy the code