Sike java series of synchronized distributed lock mysql

problem

(1) What is the distributed lock?

(2) Why do we need a distributed lock?

How (3) mysql achieve Distributed Lock?

(4) mysql distributed lock advantages and disadvantages?

Brief introduction

With the increasing amount of concurrency, sooner or later stand-alone service or want to multi-node micro-services evolution, this time synchronized or ReentrantLock used under the original stand-alone mode will no longer apply, we urgently need a guarantee thread safety in distributed environments solutions, today we are together to learn about how to implement distributed mysql distributed lock thread safe.

Basics

mysql provides two functions - get_lock('key', timeout)and release_lock('key')- distributed lock is achieved, according keyto the lock, which is a string that can set a timeout time (unit: second), when calling release_lock('key')or 客户端断线when the lock is released.

Their use is as follows:

mysql> select get_lock('user_1', 10);
    -> 1
mysql> select release_lock('user_1');
    -> 1

get_lock('user_1', 10)If within 10 seconds to obtain a lock 1 is returned, otherwise 0;

release_lock('user_1')If the lock is currently held by the client returns 1 if the lock is held by the other client terminal returns 0 if the lock is not held by any client return null;

Multi-client case

For purposes of example [This article from the "Tong brother read the source" original, please support the original, thank you! ], Where all of the timeout is set to 0, that is, to return immediately.

time Client A Client B
1 get_lock('user_1', 0) -> 1 -
2 - get_lock('user_1', 0) -> 0
3 - release_lock('user_1', 0) -> 0
4 release_lock('user_1', 0) -> 1 -
5 release_lock('user_2', 0) -> null -
6 - get_lock('user_1', 0) -> 1
7 - release_lock('user_1', 0) -> 1

Java implementation

In order to achieve easily and quickly, using the herein springboot2.1 + mybatis implemented, and spring configuration will be omitted, just to name a few main categories.

Locker defined interfaces

Only one interface method, is locked into a reference key, the command execution of the parameter 2.

public interface Locker {
    void lock(String key, Runnable command);
}

Distributed Lock achieve mysql

Mysql to achieve in two caveats:

(1) lock, release the lock must be in the same session (with a client), so there can not be called use Mapper interface, since the interface Mapper may cause not in the same session.

(2) by reentrancy ThreadLocal guaranteed;

@Slf4j
@Component
public class MysqlLocker implements Locker {

    private static final ThreadLocal<SqlSessionWrapper> localSession = new ThreadLocal<>();

    @Autowired
    private SqlSessionFactory sqlSessionFactory;

    @Override
    public void lock(String key, Runnable command) {
        // 加锁、释放锁必须使用同一个session
        SqlSessionWrapper sqlSessionWrapper = localSession.get();
        if (sqlSessionWrapper == null) {
            // 第一次获取锁
            localSession.set(new SqlSessionWrapper(sqlSessionFactory.openSession()));
        }
        try {
            // 【本篇文章由“彤哥读源码”原创,请支持原创,谢谢!】
            // -1表示没获取到锁一直等待
            if (getLock(key, -1)) {
                command.run();
            }
        } catch (Exception e) {
            log.error("lock error", e);
        } finally {
            releaseLock(key);
        }
    }

    private boolean getLock(String key, long timeout) {
        Map<String, Object> param = new HashMap<>();
        param.put("key", key);
        param.put("timeout", timeout);
        SqlSessionWrapper sqlSessionWrapper = localSession.get();
        Integer result = sqlSessionWrapper.sqlSession.selectOne("LockerMapper.getLock", param);
        if (result != null && result.intValue() == 1) {
            // 获取到了锁,state加1
            sqlSessionWrapper.state++;
            return true;
        }
        return false;
    }

    private boolean releaseLock(String key) {
        SqlSessionWrapper sqlSessionWrapper = localSession.get();
        Integer result = sqlSessionWrapper.sqlSession.selectOne("LockerMapper.releaseLock", key);
        if (result != null && result.intValue() == 1) {
            // 释放锁成功,state减1
            sqlSessionWrapper.state--;
            // 当state减为0的时候说明当前线程获取的锁全部释放了,则关闭session并从ThreadLocal中移除
            if (sqlSessionWrapper.state == 0) {
                sqlSessionWrapper.sqlSession.close();
                localSession.remove();
            }
            return true;
        }
        return false;
    }

    private static class SqlSessionWrapper {
        int state;
        SqlSession sqlSession;

        public SqlSessionWrapper(SqlSession sqlSession) {
            this.state = 0;
            this.sqlSession = sqlSession;
        }
    }
}

LockerMapper.xml

Defined get_lock (), release_lock () statement.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="LockerMapper">
    <select id="getLock" resultType="integer">
        select get_lock(#{key}, #{timeout});
    </select>

    <select id="releaseLock" resultType="integer">
        select release_lock(#{key})
    </select>
</mapper>

Test category

Here starts 1000 threads, each sleeping two seconds and print a word.

@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public class MysqlLockerTest {

    @Autowired
    private Locker locker;

    @Test
    public void testMysqlLocker() throws IOException {
        for (int i = 0; i < 1000; i++) {
            // 多节点测试
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            new Thread(()->{
                locker.lock("lock", ()-> {
                    // 可重入性测试
                    locker.lock("lock", ()-> {
                        System.out.println(String.format("time: %d, threadName: %s", System.currentTimeMillis(), Thread.currentThread().getName()));
                        try {
                            Thread.sleep(2000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    });
                });
            }, "Thread-"+i).start();
        }

        System.in.read();
    }
}

operation result

View running and found that every 2 seconds to print a thread of information, indicating that the lock is valid, as the following verification distributed environment is also very simple, since multiple instances can MysqlLockerTest.

time: 1568715905952, threadName: Thread-3
time: 1568715907955, threadName: Thread-4
time: 1568715909966, threadName: Thread-8
time: 1568715911967, threadName: Thread-0
time: 1568715913969, threadName: Thread-1
time: 1568715915972, threadName: Thread-9
time: 1568715917975, threadName: Thread-6
time: 1568715919997, threadName: Thread-5
time: 1568715921999, threadName: Thread-7
time: 1568715924001, threadName: Thread-2

to sum up

(1) a distributed environment requires the use of distributed lock, single lock will not guarantee thread-safe;

(2) mysql is based on a distributed lock get_lock('key', timeout)and release_lock('key')two functions that are implemented;

(3) mysql distributed reentrant lock is locked;

Egg

Use mysql distributed lock what needs to pay attention to it?

A: You must ensure that multiple service nodes using the same mysql database [This article from the "Tong brother read the source" original, please support the original, thank you! ].

mysql distributed lock What are the benefits?

A: 1) convenient, because every basic service will connect to the database, but not every service will use redis or zookeeper;

2) If the client is disconnected automatically releases the lock, the lock will not cause has been occupied;

3) mysql distributed lock is reentrant lock, low cost for the transformation of the old code;

mysql distributed lock What are the shortcomings?

A: 1) lock directly hit the database, increasing the pressure on the database;

2) locking thread will occupy a session, that is, a number of connections, if a large amount of concurrent sql statement could lead to execution of acquisition is less than the normal connection;

3) After the service split if each service uses its own database, it is not appropriate;

4) with respect to the distributed lock redis or zookeeper, relatively lower efficiency;


I welcome the attention of the public number "Tong brother read source" view source code more series, and brother Tong source of ocean swim together.

qrcode

Guess you like

Origin www.cnblogs.com/tong-yuan/p/11616782.html