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 key
to 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.