e.g. SELECT * FROM distributed_lock WHERE business_code='demo' FOR UPDATE
Pros and Cons
Pros:
Easy to build
Cons:
Big pressure on database if there are high number of concurrent requests. Recommend to separate the business logic DB and lock DB
Example
// DistributeLockMapper.xml
<select id="selectDistributeLock" resultType="com.example.distributelock.model.DistributeLock">
select * from distribute_lock
where business_code = #{businessCode,jdbcType=VARCHAR}
for update
</select>
// DemoController.java
@RestController
@Slf4j
public class DemoController
{
@Resource
private DistributeLockMapper distributeLockMapper;
@RequestMapping("singleLock")
@Transactional(rollbackFor = Exception.class)
public String singleLock() throws Exception
{
DistributeLock distributeLock = distributeLockMapper.selectDistributeLock("demo");
if (distributeLock==null) throw new Exception("cannot get distributed lock");
try
{
Thread.sleep(20000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
return "Finished execution!";
}
}
Zookeeper
How will the node be deleted:
Client deletes the node proactively
How will the previous node get changed?
Watch mechanism get -w /gupao.
Too many notifications:
Each node only needs to monitor the previous node
@Slf4j
public class ZkLock implements AutoCloseable, Watcher
{
private ZooKeeper zooKeeper;
private String znode;
public ZkLock() throws IOException
{
this.zooKeeper = new ZooKeeper("localhost:2181",
10000,this);
}
public boolean getLock(String businessCode)
{
try
{
// Create business root node, e.g. /root
Stat stat = zooKeeper.exists("/" + businessCode, false);
if (stat==null)
{
zooKeeper.create("/" + businessCode,businessCode.getBytes(),
ZooDefs.Ids.OPEN_ACL_UNSAFE,
CreateMode.PERSISTENT);
}
// Create temporary sequential node /order/order_00000001
znode = zooKeeper.create("/" + businessCode + "/" + businessCode + "_", businessCode.getBytes(),
ZooDefs.Ids.OPEN_ACL_UNSAFE,
CreateMode.EPHEMERAL_SEQUENTIAL);
// Get all nodes under business node
List<String> childrenNodes = zooKeeper.getChildren("/" + businessCode, false);
// Sort children nodes under root
Collections.sort(childrenNodes);
// Obtain the node which has the least sequential number
String firstNode = childrenNodes.get(0);
// If the node created is the first one, then get the lock
if (znode.endsWith(firstNode))
{
return true;
}
// If not the first child node, then monitor the previous node
String lastNode = firstNode;
for (String node:childrenNodes)
{
if (znode.endsWith(node))
{
// watch parameter is implemented in the process method below
zooKeeper.exists("/"+businessCode+"/"+lastNode, watch: true);
break;
}
else
{
lastNode = node;
}
}
// Wait for the previous node to release
// This is
synchronized (this)
{
wait();
}
return true;
}
catch (Exception e)
{
e.printStackTrace();
}
return false;
}
@Override
public void close() throws Exception
{
zooKeeper.delete(znode, -1); // path, version: version is to avoid deleting wrong node. Passing -1 here because it is not used before at all
zooKeeper.close();
log.info("I have unlocked!");
}
@Override
public void process(WatchedEvent event)
{
// Only get notification when the previous node get deleted.
if (event.getType() == Event.EventType.NodeDeleted)
{
synchronized (this)
{
notify();
}
}
}
}
@Slf4j
public class ZookeeperController
{
@Autowired
private CuratorFramework client;
@RequestMapping("zkLock")
public String zookeeperLock()
{
log.info("entered method!");
try (ZkLock zkLock = new ZkLock())
{
if (zkLock.getLock("order"))
{
log.info("get the lock");
Thread.sleep(10000);
}
}
catch (IOException e)
{
e.printStackTrace();
}
catch (Exception e)
{
e.printStackTrace();
}
log.info("finish method execution!");
return "finish method execution!";
}
}
Curator
Motivation: Curator encapsulates the one-time watch logic so easier to use.
There are three methods which could set watcher: GetData(); getChildren(); exists().
Whenever there is a change to the watched data, the result will be returned to client.