If a large number of requests are sent to concurrently access and update the shared resources stored in Redis, an accurate and efficient concurrency control mechanism is required. The mechanism must be able to help you prevent logical failures and data errors. One of the mechanism examples is optimistic locking. Performance-enhanced instances of ApsaraDB for Redis Enhanced Edition (Tair) provides the TairString data structure. Therefore, compared with native Redis databases, ApsaraDB for Redis Enhanced Edition (Tair) allows you to implement optimistic locking that delivers higher performance at lower costs.

Concurrency and last-writer-wins

The following figure shows a typical scenario where concurrent requests cause race hazards.

  1. At the initial stage, the value of key_1 is hello. The values of this key are strings.
  2. At the t1 time point, application 1 reads the value of key_1: hello.
  3. At the t2 time point, application 2 reads the value of key_1: hello.
  4. At the t3 time point, application 1 changes the value of key_1 to world.
  5. 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. At the t4 time point, application 1 considers the value of key_1 as world, but the actual value is universe. Therefore, the subsequent operations may become faulty. This process explains what is last-writer-wins. To resolve the issues that are caused by last-writer-wins, you must ensure the atomicity of the operations of accessing and updating string data. In other words, you must convert the string data of the shared resources into atomic variables. To do this, you can implement high-performance optimistic locking by using the TairString data structure. This data structure is offered by the performance-enhanced instances of ApsaraDB for Redis Enhanced Edition (Tair).

Implement optimistic locking based on 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. TairString data consists of keys, values, and version numbers. Therefore, TairString is more suitable for optimistic locking. For more information about TairString commands, see TairString commands.

Note The TairString data structure is different from the native Redis string data structure. Two sets of commands are provided for the two data structures. You can use only one set of the commands in a system.

TairString has the following features:

  • A version number is provided for each key. The version number indicates the current version of a 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 for a specified key, you can retrieve the values of two fields: value and version.
  • Before you update a TairString value, the version is verified. If the verification fails, the following error message is returned: ERR update version is stale.
  • After the TairString value is updated, the version number is automatically incremented by 1.
  • TairString integrates all the features of native Redis strings except bit operations.

Due to these features, the lock mechanism is native to TairString data. Therefore, TairString provides an easy method for you to implement optimistic locking. An example is described as follows:

while(true){
    {value, version} = EXGET(key);      // Retrieve the value and the version of the key.
    value2 = update(...) ;               // Save a new value to the value2 field.
    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.
}
Note
  • If you delete TairString data and create new TairString data that has the same key as the deleted TairString data, the key version of the new TairString data is 1. The new TairString data does not inherit the key version of the deleted TairString data.
  • You can specify the ABS option to skip version verification and forcibly overwrite the current version to update TairString data. 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 the version of the shared resource before the update is successful. As a result, two I/O operations are performed to access Redis in each while loop. However, by using the EXCAS command of TairString, you only need to send one access request in each while loop. 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 is successful, the TairString value is updated. If the verification fails, the following three elements are returned:

  • update version is stale
  • value
  • version

If the update fails, the command returns the current version of the TairString data. You do not have to run another query to retrieve the current version, and only one access request is required for each while loop. An example is described as follows:

while(true){
    {ret, value, version} = excas(key, new_value, old_version)    // Use the CAS command to replace an 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 two variables are updated: value and old_version.
       update(value);
       old_version = version;
 }