neo4j实现分布式锁
应用场景:分布式环境下,希望能够生成一个唯一自增的序列号,自增序列号的存储使用neo4j
问题一:使用neo4j存储,节点的属性使用一个定值,节点的属性值是自增的,首先是节点的创建,分布式环境下,节点的创建无法保证,因为比如两个线程同时创建节点,第一个线程判断neo4j中没有该节点,准备创建;这时,还没有创建成功,第二个线程进入判断该节点不存在,创建节点,这样就会创建两个节点,这样是不对的。
解决办法:可以使用一个脚本,将该节点事先创建好
问题二:要保证更改节点的属性值的操作是一个原子性操作,不能先查出该节点,再将该节点的属性值自增,这样存在的问题同问题一
解决方法:写一个Cypher查询语句同时实现查询和自增:match (n:lock) set n.num = case when exists(n.num) then n.num + 1 else 1 end return n.num
实现代码:
// 创建节点,这个方法是要有的,避免节点未创建报错,但是要注意初始化节点不能被两个线程同时访问
public Boolean createAutoIncrementLock(String enterpriseCode, String serverCode) {
openDriver();
try (Session session = driver.session()) {
try (Transaction transaction = session.beginTransaction()) {
String searchSql = "match (l: " + Neo4jDBNodeType.IotAutoIncrementLock + " {" + BaseFiled.ENTERPRISECODE + ":'" + enterpriseCode + "'," + BaseFiled.SERVERCODE + ":'" + serverCode + "'}) return count(l)";
StatementResult statementResultSearch = transaction.run(searchSql);
List<Record> recordListSearch = statementResultSearch.list();
if (CollectionUtils.isEmpty(recordListSearch) || recordListSearch.get(0).values().get(0).asInt() < 1) {
String createSql = "create (l: " + Neo4jDBNodeType.IotAutoIncrementLock + " {" + BaseFiled.ENTERPRISECODE + ":'" + enterpriseCode + "'," + BaseFiled.SERVERCODE + ":'" + serverCode + "'}) return count(l)";
StatementResult statementResultCreate = transaction.run(createSql);
List<Record> recordListCreate = statementResultCreate.list();
if (CollectionUtils.isEmpty(recordListCreate) || recordListCreate.get(0).values().get(0).asInt() < 1) {
transaction.failure();
return false;
}
}
transaction.success();
return true;
}
}
}
// 获取自增锁的属性值
public String getAutoIncrementLock(String field, String enterpriseCode, String serverCode) {
openDriver();
try (Session session = driver.session()) {
try (Transaction transaction = session.beginTransaction()) {
if (!createAutoIncrementLock(enterpriseCode, serverCode)) {
return null;
}
String sql = "match (l: " + Neo4jDBNodeType.IotAutoIncrementLock + "{" + BaseFiled.ENTERPRISECODE + ":'" + enterpriseCode + "'," + BaseFiled.SERVERCODE + ":'" + serverCode + "'}" + ") set l."+field+" = case when exists(l."+field+") then l."+field+" + 1 else 1 end return l."+field;
logger.info("执行的sql:" + sql);
logger.info("开始执行时间:" + System.currentTimeMillis());
StatementResult statementResult = transaction.run(sql);
List<Record> recordListSet = statementResult.list();
if (CollectionUtils.isEmpty(recordListSet) || recordListSet.get(0).values().get(0).asInt() < 1) {
transaction.failure();
return null;
}
transaction.success();
logger.info("结束时间:" + System.currentTimeMillis() + ",结果:" + recordListSet.get(0).values().get(0).asInt());
return String.valueOf(recordListSet.get(0).values().get(0).asInt());
}
}
}