If a large number of requests are sent to concurrently access and update the shared resources stored in Tair, an accurate and efficient concurrency control mechanism is required. The mechanism must be able to help you prevent logic exceptions and data errors. One of the mechanisms is optimistic locking. Compared with open source Redis, the TairString data module of Tair allows you to implement optimistic locking to deliver higher performance at lower costs.
Concurrency and last-writer-wins
The following figure shows a typical scenario where concurrent requests cause race conditions.
At the initial stage, the value of key_1 is
hello
. The key is of the string type.At the t1 time point, application 1 reads the key_1 value
hello
.At the t2 time point, application 2 reads the key_1 value
hello
.At the t3 time point, application 1 changes the value of key_1 to
world
.At the t4 time point, application 2 changes the value of key_1 to
universe
.
The value of key_1 is determined by the last write operation. At the t4 time point, application 1 considers the value of key_1 as world, but the actual value is universe. This leads to potential issues in subsequent operations. This process is known as last-writer-wins. To resolve the issues that are caused by last-writer-wins, you must ensure that the operation of accessing and updating string data is atomic. In other words, you must convert the string data, which serves as a shared resource, into atomic variables. To do this, you can implement high-performance optimistic locking by using the exString data structure.
Implement optimistic locking by using TairString
TairString, also known as an extended string (exString), is a string data structure that carries a version number. Native Redis strings consist of only keys and values. TairStrings consist of keys, values, and version numbers. For this reason, TairString is ideal for optimistic locking. For more information about TairString commands, see exString.
TairStrings differ from native Redis strings. The commands that are supported by TairStrings and native Redis strings are not interchangeable.
TairString has the following features:
A version number is provided for each key. The version number indicates the current version of the key. If you run the EXSET command to create a key, the default version number of the key is 1.
If you run the EXGET command to query a key, you can retrieve the values of two fields: value and version.
When you update a TairString value, the version is verified. If the verification fails, the following error message is returned:
ERR update version is stale
. If the verification succeeds, the TairString value is updated, and the version number is automatically incremented by 1.TairString integrates all the features of Redis String except bit operations.
Due to these features, the locking mechanism is native to TairString data. TairString makes it easy to implement optimistic locking. Example:
while(true){
{value, version} = EXGET(key); // Retrieve the value and version number of the key.
value2 = update(...); // Save the new value as value 2.
ret = EXSET(key, value2, version); // Update the key and assign the return value to the ret variable.
if(ret == OK)
break; // If the return value is OK, the update is successful and the while loop exits.
else if (ret.contanis("version is stale"))
continue; // If the return value contains the "version is stale" error message, the update fails and the while loop is repeated.
}
If you delete a TairString and create a TairString that has the same key as the deleted TairString, the key version of the new TairString is 1. The new TairString does not inherit the key version of the deleted TairString.
You can specify the ABS option to skip version verification and forcefully overwrite the current version to update a TairString. For more information, see EXSET.
Reduce resource consumption for optimistic locking
In the preceding sample code, if another client updates the shared resource after you run the EXGET command, you receive an update failure message and the while loop is repeated. The EXGET command is repeatedly run to retrieve the value and version number of the shared resource until the update is successful. As a result, two I/O operations are performed to access Tair in each while loop. However, you need to only send one access request in each while loop by using the EXCAS command of TairString. This results in a significant decrease in the consumption of system resources and improves service performance in high concurrency scenarios.
When you run the EXCAS command, you can specify a version number in the command to verify the version. If the verification succeeds, the TairString value is updated. If the verification fails, the following elements are returned:
"ERR update version is stale"
Value
Version
If the update fails, the command returns the current version number of the TairString. You do not need to run another query to retrieve the current version number, and only one access request is required for each while loop. Sample code:
while(true){
{ret, value, version} = excas(key, new_value, old_version) // Use the CAS command to replace the original value with a new value.
if(ret == OK)
break; // If the return value is OK, the update is successful and the while loop exits.
else (if ret.contanis("update version is stale")) // If the return value contains the "update version is stale" error message, the update fails. The values of the value and old_version variables are updated.
update(value);
old_version = version;
}