If you are asked about distributed locks, how should you answer?

cc277ee092477f76debbf2eef89f45ec.gif

Author | tech-bus.71

Source | Programmer Bus

Speaking of locks, in our usual work, we mainly use the synchronized keyword or some related class libraries to achieve synchronization, but this is all based on stand-alone applications. When our application is deployed with multiple instances, this time Distributed locks need to be used. The commonly used distributed locks are mainly distributed locks based on redis, distributed locks based on zookeeper, and distributed locks based on the base database. The first two are mainly based on the characteristics of middleware. Let's take a look at the implementation of database-based distributed locks, which are more suitable in some scenarios with low concurrency.

First, you need to create a data table in the database, and the relevant fields are as follows:

CREATE TABLE IF NOT EXISTS `lock_tbl`(
   `lock_id` INT NOT NULL, -- 主键且主要字段不可少
   `des_one` VARCHAR(20), -- 可有可无
   `des_two` VARCHAR(20), -- 可有可无
   PRIMARY KEY ( `lock_id` )
)ENGINE=InnoDB DEFAULT CHARSET=utf8;

Then we use a single instance application to write an interface to buy a product in the table. The general idea is: read the inventory, reduce the inventory by one, write back to the database, and return successfully. The core code is as follows:

public class StockServiceImpl implements StockService{


    @Autowired
    StockMapper stockMapper;


    @Override
    public Stock selectByPrimaryKey(Integer goodsId) {
        return stockMapper.selectByPrimaryKey(goodsId);
    }
    // 加锁也只能保证单个实例线程安全性
    public synchronized void byGoods() throws InterruptedException {
        // 这里写死,数据库里就一条记录且ID为1,拿到数据
        Stock stock = selectByPrimaryKey(1);
        // 获取到商品的库存
        Long goodsStock = stock.getGoodsStock();
        // 减库存
        goodsStock -= 1;
        stock.setGoodsStock(goodsStock);
        // 为了将问题放大这里睡上几秒 拉长查库存和更新库存的之间的时间间隔
        Thread.sleep(3000);
        // 更新
        updateByPrimaryKeySelective(stock);
        // 输出
        System.out.println("更新后库存为:" + goodsStock);
    }


    @Override
    public int updateByPrimaryKeySelective(Stock record) {
        return stockMapper.updateByPrimaryKeySelective(record);
    }
}

After adding a synchronized in a single instance, it is completely normal to reduce the inventory. Then we start two instances and use postman to test the interface. The following situation occurs:

eac64d81b4f6a4efdb448fa616a34c92.png
Example 1 print log
3dec93d55e6b4e195791a114ff623c59.png
Example 2 print log

It can be seen from the screenshot that the above program has been oversold. Next, we will transform it and use the lock at the database level. We know that inserting two pieces of data with the same primary key into a table can only succeed in one, because the primary key is binding, so use this feature , when we insert into the database successfully, it means that the lock is acquired, so as to run our business code. When our business code is finished running, we delete the record in the database, which means that the lock is released, so that other threads That is, there is a chance to obtain the lock and then run the business code, so that even if two instances are running, only one thread can run the business code at the same time, and there will be no oversold situation. The codes for locking and unlocking are given below:

// 上锁。由于上锁失败的话会直接返回失败,并不会再次获取
// 是非阻塞的,这里利用循环实现阻塞。
    @Override
    public boolean tryLock() {
        // 这里的Lock就是简单的一个POJO对象映射到数据库中一张表的字段
        Lock lock = new Lock();
        lock.setLockId(1);
        // 通过while循环来实现阻塞
        while (true) {
            try {
                // 首先查询一下主键为1的数据是否存在,如果存在则说明锁已经被占用了
                if (lockMapper.selectByPrimaryKey(1) == null) {
                    // 不存在则尝试加锁即向数据库中插入数据
                    int i = lockMapper.insert(lock);
                    if (i == 1) {
                    return true;
                    }
                }
                Thread.sleep(1000);
            } catch (InterruptedException e) {
            }
        }
    }
    // 解锁代码
    @Override
    public void unLock() {
        deleteByPrimaryKey(1);
    }

The code of the purchased product at the service layer is locked

// 买商品
    public void byWithLock() throws InterruptedException {
        // 上锁
       lockService.tryLock();
       // 业务代码
       byGoods();
       // 释放锁并跳出循环
       lockService.unLock();
    }

Code for the controller layer

@RestController
public class LoadBalance {
    @Autowired
    StockServiceImpl stockService;
    @RequestMapping("/balance")
    public String balance() {
        try {
            stockService.byWithLock();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "success";
    }
}

Start the program again, use postman to do a simple pressure test, and find that the inventory reduction has been carried out normally. The result is shown below

2e4b9d753e8a5f2f17f57e44440b6c9d.png
Instance 1 log
70e5059a260c94486266c9b748af4c17.png
Instance 2 log

‍‍

existing problems

  • If one instance crashes after getting the lock, and the lock is not released in time, other instances will never be able to acquire the lock.

  • Non-reentrant, after an instance gets the lock, it will fail when trying to acquire the lock again

How to solve

  • For the problem that the lock cannot be released due to instance downtime, you can insert a current timestamp into the database when inserting data, then start a scheduled task, scan the table regularly, and set a lock timeout (the The timeout time must be greater than the normal interface call time), and the timeout record will be deleted.

  • For non-reentrant, you can add instance and thread-related information when inserting data into the table, judge when acquiring the lock, and acquire the lock directly if it matches.

pessimistic lock

A simple understanding of pessimistic locking is that in any case, it is pessimistic that when a critical resource is requested, it will conflict with other threads, so every time a pessimistic lock is added, which is strongly invasive and exclusive. The lock added in the above example is a pessimistic lock, that is, the lock is taken first and then accessed. The pessimistic lock that comes with MySql is For Update. Using For Update can display the increase of row locks, but the pessimistic lock will increase the database overhead and increase the deadlock. risk of locking.

optimistic locking

A simple understanding of optimistic locking is that every time a thread requests a critical resource, it thinks that no other thread will compete with it, and only competes when data is submitted. When detecting data conflicts, it does not rely on the locking mechanism of the database itself and does not affect the request. performance. In the above example, we can add a Version version number to the database table. For the data to be modified, first find out the version number of the modified Version from the database, and then modify it together with the version number when modifying.

SELECT VERSION FROM TABLE_A  -- 假设这里查出来version的值是OldVersion
UPDATE TABLE_A SET COUNT = COUNT -1, VERSION = VERSION + 1 WHERE VERSION = OldVersion

Summarize

When concurrency is not particularly high, you can consider using database-based distributed locks, and try to use optimistic locking to improve application throughput.

2aedf86348bae0ed66d87a46416168b3.gif

a60e1303ec7fa6be798f98cf99bf98bf.png

Recommended in the past

Why is everyone resisting the use of timed tasks to implement the function of "closing overtime orders"?

Gartner Announces Five Technology Trends for the Automotive Industry in 2022

Stop using Redis List to implement message queues, Stream is designed for queues

How OpenStack can be upgraded across versions

37ecf3916b6ff4c1cb9122550b15f57a.gif

point to share

cf83e09c915e7119245f7ace6ccda35c.gif

Favorites

1809ffb83eda92140f13998ce626b34e.gif

Like

42d68d3eff1cb19c28d99a2fa3329abf.gif

click to watch

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324144357&siteId=291194637