Implementing a distributed lock using MySQL

Implementing a distributed lock using MySQL

introduce

In a distributed system, distributed lock is the most basic tool class. For example, if two microservices with payment functions are deployed, a user may initiate two payment operations for an order, and these two requests may be sent to two services. Therefore, a distributed lock must be used to prevent duplicate submissions. The service that obtains the lock performs the payment operation normally, and the service that does not obtain the lock prompts for duplicate operations.

Our company has encapsulated a large number of basic tool classes. When we want to use distributed locks, we only need to do three things:

1. Create a globallocktable table in the database
2. Introduce the corresponding jar package
3. Write @Autowired GlobalLockComponent globalLockComponent in the code to use this component

After reading this article, you can also use springboot-starter to achieve the same function. But we did not implement it this way. We will write another article to analyze how we implemented it.

This article first analyzes the implementation of MySQL distribution

Create a table

CREATE TABLE `globallocktable` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `lockKey` varchar(60) NOT NULL COMMENT 'Lock name',
 `createTime` datetime NOT NULL COMMENT 'Creation time',
 PRIMARY KEY (`id`),
 UNIQUE KEY `lockKey` (`lockKey`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='Global lock';

Components for others to use

@Component
public class GlobalLockComponent {
 @Resource
 GlobalLockTableDAO globalLockDAO;
 /**
  * Try to get the lock, true if successful, false if failed
  */
 public boolean tryLock(String key) {
  return GlobalLockUtil.tryLock(this.globalLockDAO, key);
 }
 /**
  * If another program has occupied the lock and it exceeds timeoutMs (milliseconds), the lock will be cleared by force. * That is, delete the record according to the key first, and then add the record */
 public boolean tryLockWithClear(String key, Long timeoutMs) {
  return GlobalLockUtil.tryLockWithClear(this.globalLockDAO, key, timeoutMs);
 }
 /**
  * Release the lock and delete the record according to the key */
 public void releasLock(String key) {
  GlobalLockUtil.releasLock(this.globalLockDAO, key);
 }
}

The lock object is defined as follows

public class GlobalLockTable {

 private Integer id;
 private String lockKey;
 private Date createTime;
 // Omit get and set methods}

GlobalLockTableDAO is defined as follows public interface GlobalLockTableDAO {
 int deleteByPrimaryKey(Integer id);
 int deleteByLockKey(String lockKey);
 GlobalLockTable selectByLockKey(String key);
 int insertSelectiveWithTest(GlobalLockTable record);
}

Specific locking and unlocking logic

public class GlobalLockUtil {
 private static Logger logger = LoggerFactory.getLogger(GlobalLockUtil.class);
 private static GlobalLockTable tryLockInternal(GlobalLockTableDAO lockDAO, String key) {
  GlobalLockTable insert = new GlobalLockTable();
  insert.setCreateTime(new Date());
  insert.setLockKey(key);
  // Note 1
  int count = lockDAO.insertSelectiveWithTest(insert);
  if (count == 0) {
   GlobalLockTable ready = lockDAO.selectByLockKey(key);
   logger.warn("can not lock the key: {}, {}, {}", insert.getLockKey(), ready.getCreateTime(),
     ready.getId());
   return ready;
  }
  logger.info("yes got the lock by key: {}", insert.getId(), insert.getLockKey());
  return null;
 }
 /** Timeout to clear the lock and re-lock**/
 public static boolean tryLockWithClear(GlobalLockTableDAO lockDAO, String key, Long timeoutMs) {
  GlobalLockTable lock = tryLockInternal(lockDAO, key);
  if (lock == null) return true;
  if (System.currentTimeMillis() - lock.getCreateTime().getTime() <= timeoutMs) {
   logger.warn("sorry, can't get the key. : {}, {}, {}", key, lock.getId(), lock.getCreateTime());
   return false;
  }
  logger.warn("the key already timed out before : {}, {}, will clear", key, timeoutMs);
  // Note 2
  int count = lockDAO.deleteByPrimaryKey(lock.getId());
  if (count == 0) {
   logger.warn("sorry, the key already preemptived by others: {}, {}", lock.getId(), lock.getLockKey());
   return false;
  }
  lock = tryLockInternal(lockDAO, key);
  return lock != null ? false : true;
 }
 /** Lock **/
 public static boolean tryLock(GlobalLockTableDAO lockDAO, String key) {
  return tryLockInternal(lockDAO, key) == null ? true : false;
 }
 /** Unlock **/
 public static void releasLock(GlobalLockTableDAO lockDAO, String key) {
  lockDAO.deleteByLockKey(key);
 }
}

There are two particularly interesting things about this tool class. Let’s first look at point 2 (marked in the code above)

1. In order to avoid the lock not being released for a long time, if it is implemented with Redis, you can set the lock timeout, and the lock will be automatically released after the timeout (I will write about how to implement distributed locks with Redis later). If it is implemented with MySQL, you can delete it first and then add it. You can see that when deleting, the id is used to delete, not the name. Why? Think about it first

Because if you delete it by name, it is possible that someone else deleted the lock and then added a lock by name before the timeout period, but you deleted it by name. If you delete by id, when the returned id=0, it means someone else has locked it again and you need to get it again.

2. The other methods of the GlobalLockTable object dao layer are self-explanatory. Let's take a look at this method. That is, note 1 in the code
You can see that each time you try to lock, you don't select first, but directly insertSelectiveWithTest, which saves a query time and improves efficiency.

The function of insertSelectiveWithTest is to not perform the insert operation when lockKey exists and return 0. When the lockKey does not exist, perform an insert operation and return 1

<insert id="insertSelectiveWithTest" useGeneratedKeys="true" keyProperty="id" parameterType="com.javashitang.middleware.lock.mysql.pojo.GlobalLockTable">
 insert into `globallocktable` (`id`,
 `lockKey`, `createTime` )
  select #{id,jdbcType=INTEGER}, #{lockKey,jdbcType=VARCHAR}, #{createTime,jdbcType=TIMESTAMP}
  from dual where not exists
  (select 1 from globallocktable where lockKey = #{lockKey,jdbcType=VARCHAR})
</insert>

use

When we want to use it, we just need to write the business logic, which is very convenient.

if (!globalLockComponent.tryLock(name)) {
 // Return if the lock is not acquired;
}
try {
 // Write business logic here} catch (Exception e) {
finally
 globalLockComponent.releasLock(name)

Summarize

The above is the editor's introduction to using MySQL to implement a distributed lock. I hope it will be helpful to everyone!

You may also be interested in:
  • Detailed explanation of the idea of ​​distributed lock in MySQL with the help of DB

<<:  JavaScript knowledge: Constructors are also functions

>>:  How to quickly copy large files under Linux

Recommend

50 lines of code to implement Webpack component usage statistics

background Recently, a leader wanted us to build ...

Detailed explanation of HTML tables

Function: data display, table application scenari...

Share 20 JavaScript one-line codes

Table of contents 1. Get the value of browser coo...

Example of using #include file in html

There are two files a.htm and b.htm. In the same d...

Use CSS content attr to achieve mouse hover prompt (tooltip) effect

Why do we achieve this effect? ​​In fact, this ef...

Notes on Using Textarea

Why mention textarea specifically? Because the tex...

MySQL learning tutorial clustered index

Clustering is actually relative to the InnoDB dat...

How to change the root user's password in MySQL

Method 1: Use the SET PASSWORD command mysql> ...

How to export mysql table structure to excel

The requirements are as follows Export the table ...

CSS and JS to achieve romantic meteor shower animation

1. Rendering 2. Source code HTML < body > &...

How to install common components (mysql, redis) in Docker

Docker installs mysql docker search mysql Search ...

HTML&CSS&JS compatibility tree (IE, Firefox, Chrome)

What is a tree in web design? Simply put, clicking...

Simple implementation of html hiding scroll bar

1. HTML tags with attributes XML/HTML CodeCopy co...

Detailed explanation of the difference between $router and $route in Vue

We usually use routing in vue projects, and vue-r...