Java中分布式锁的应用
本文只做引导,具体实现可能需要另行搜索
SQL | Redis | Zookeeper | Curator | Redisson
对比
数据库层级
- 建立两个数据库,一个是业务库,一个是锁库
- 正常些Service代码
- 在
锁
库创建表, 字段是锁的名字 - 利用怕他锁 select * from 表 where 锁的字段= 锁的名字 for update
- 正常写代码
Redis
public class RedisLock implements AutoCloseable {
private RedisTemplate redisTemplate;
private String key;
private String value;
//单位:秒
private int expireTime;
public RedisLock(RedisTemplate redisTemplate,String key,int expireTime){
this.redisTemplate = redisTemplate;
this.key = key;
this.expireTime=expireTime;
this.value = UUID.randomUUID().toString();
}
/**
* 获取分布式锁
* @return
*/
public boolean getLock(){
RedisCallback<Boolean> redisCallback = connection -> {
//设置NX
RedisStringCommands.SetOption setOption = RedisStringCommands.SetOption.ifAbsent();
//设置过期时间
Expiration expiration = Expiration.seconds(expireTime);
//序列化key
byte[] redisKey = redisTemplate.getKeySerializer().serialize(key);
//序列化value
byte[] redisValue = redisTemplate.getValueSerializer().serialize(value);
//执行setnx操作
Boolean result = connection.set(redisKey, redisValue, expiration, setOption);
return result;
};
//获取分布式锁
Boolean lock = (Boolean)redisTemplate.execute(redisCallback);
return lock;
}
public boolean unLock() {
String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then\n" +
" return redis.call(\"del\",KEYS[1])\n" +
"else\n" +
" return 0\n" +
"end";
RedisScript<Boolean> redisScript = RedisScript.of(script,Boolean.class);
List<String> keys = Arrays.asList(key);
Boolean result = (Boolean)redisTemplate.execute(redisScript, keys, value);
log.info("释放锁的结果:"+result);
return result;
}
@Override
public void close() throws Exception {
unLock();
}
}
----------------
## 使用
try (RedisLock redisLock = new RedisLock(redisTemplate,"redisKey",30)){
if (redisLock.getLock()) {
log.info("我进入了锁!!");
Thread.sleep(15000);
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
ZooKeeper
public class ZkLock implements Watcher,AutoCloseable {
private ZooKeeper zooKeeper;
private String businessName;
private String znode;
public ZkLock(String connectString,String businessName) throws IOException {
this.zooKeeper = new ZooKeeper(connectString,30000,this);
this.businessName = businessName;
}
public boolean getLock() throws KeeperException, InterruptedException {
Stat existsNode = zooKeeper.exists("/" + businessName, false);
if (existsNode == null){
zooKeeper.create("/" + businessName,businessName.getBytes(),
ZooDefs.Ids.OPEN_ACL_UNSAFE,
CreateMode.PERSISTENT);
}
znode = zooKeeper.create("/" + businessName + "/" + businessName + "_", businessName.getBytes(),
ZooDefs.Ids.OPEN_ACL_UNSAFE,
CreateMode.EPHEMERAL_SEQUENTIAL);
znode = znode.substring(znode.lastIndexOf("/")+1);
List<String> childrenNodes = zooKeeper.getChildren("/" + businessName, false);
Collections.sort(childrenNodes);
String firstNode = childrenNodes.get(0);
if (!firstNode.equals(znode)){
String lastNode = firstNode;
for (String node:childrenNodes){
if (!znode.equals(node)){
lastNode = node;
}else {
zooKeeper.exists("/"+businessName+"/"+lastNode,true);
break;
}
}
synchronized (this){
wait();
}
}
return true;
}
@Override
public void process(WatchedEvent watchedEvent) {
if (watchedEvent.getType() == Event.EventType.NodeDeleted){
synchronized (this){
notify();
}
}
}
@Override
public void close() throws Exception {
zooKeeper.delete("/"+businessName+"/"+znode,-1);
zooKeeper.close();
}
}
----------------
## 使用
try (ZkLock zkLock = new ZkLock("localhost:2181","order")){
if (zkLock.getLock()) {
log.info("我进入了锁!!");
Thread.sleep(15000);
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
Curator
Redisson
优缺点对比
实现 | 优点 | 缺点 |
---|---|---|
SQL | 实现简单,容易理解 | 对数据库压力大 |
Redis | 容易理解 | 不支持阻塞 |
Zookeeper | 支持阻塞 | 需要了解ZK,程序复杂 |
Curator | 提供现成锁的方法 | 依赖Zookeeper,强一致 |
Redisson | 提供线程锁的方法 |