【仅供内部供应商使用,不提供对外解答和培训】
【仅供内部供应商使用,不提供对外解答和培训】
使用共享存储的场景下,集群多个节点访问同一个文件时,为了保证避免数据错误,需要加锁对进程访问文件进行控制。
对于File,java提供了FileLock用于直接锁定FileChannel的region,来实现文件锁的效果。
FileChannel channel = new FileOutputStream(LOG_FILE_NAME, true).getChannel();
//第三个参数是文件锁是否共享,若系统不支持,将会采用排他锁
FileLock fileLock = channel.lock(0L, Long.MAX_VALUE, true);
fileLock = channel.lock();
//read write file
fileLock.release();
在我们的集群场景中,由于后端资源文件存储并非某个特定的存储介质,因此使用FileLock只能只能适用于使用系统FileSystem,而使用FileSystem的情况下的存储使用的是同步而非共享,因此不适合。
另外一种比较常用的方式是利用zookeeper的临时节点特性:客户端连接断开自动删除该客户端创建的临时节点,在zk的dataDir下的文件同名节点创建临时有序子节点,序号最前的获得读写该文件的锁。
具体的创建临时节点:
private String createLockNode(ZkClient client, String path) throws LockException {
// 创建临时循序节点
try {
return client.createEphemeralSequential(path, null);
} catch (ZkNoNodeException e) {
//
}
throw new LockException("Create ephemeral sequential node failed!");
}
监视前一节点的删除,立即抢占锁
//被监视的节点
String watchPath = getWatchPath(getSortedChildren(), currentNode);
final CountDownLatch latch = new CountDownLatch(1);
//前一节点监听处理
final IZkDataListener previousNodeListener = new IZkDataListener() {
public void handleDataDeleted(String dataPath) throws Exception {
latch.countDown();//前一个删掉,等待锁
}
public void handleDataChange(String dataPath, Object data) throws Exception {
//do nothing
}
};
zkClient.subscribeDataChanges(watchPath, previousNodeListener);//监听前一个节点
优点是使用共享存储时,不需要考虑存储的具体实现,而是在平台的文件访问层面设置访问标志控制,速度快,无服务器和存储通信的网络延迟。这个方式不仅仅适用于共享存储方案,也适用于各自使用本机文件系统。
缺点是集群需要部署zookeeper,考虑到集群可能使用其它zk的功能也需要不是zk,可行性比较高。
仿照zookeeper的思路,在共享存储中需要加锁的文件的同名目录下创建一个锁文件,另一个进程需要访问这个文件时,根据锁情况(读写)需要等待或者获得锁。
private String createLockFile() {
int seq = 0;
//不存在先创建锁文件夹,rr为抽象存储
if (!rr.exist(lockFilesPath)) {
rr.createDirectory(lockFilesPath);
}
List<String> lockFiles = listLockFiles(lockFilesPath);
try {
if (lockFiles.size() > 0) {
String last = lockFiles.get(lockFiles.size() - 1);
seq = getSeq(last) + 1;
}
String lockFileName = lockFilesPath + lockPrefix + seq;
if (rr.create(lockFileName)) {
return lockFileName;
}
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
1)例如当前节点机器对WEB-INF/reportlets/WorkBook13.cpt 文件加锁,则创建一个WEB-INF/reportlets/WorkBook13.cpt/的路径;
2)多个节点在该路径下创建锁文件如write-lock-01,read-lock-02, read-lock-03,最后的数字代表创建顺序,对于读锁,没有写锁时,共享锁,对于写锁,排在最前时获得锁;
3)等待锁,一直轮训当前的锁文件排序是否为最小,最小获得锁,超时删除锁文件,加锁失败;
这样做有两个问题:
1)当jvm进程在获得锁之后的过程中意外关闭,会导致锁文件没有及时删除,其它进程永远获取不到锁;
2)网络通信的延迟或者故障,导致创建或者删除锁文件感知有延迟带来的性能问题。
这个方案需要考虑在进程意外关闭后锁文件的管理,实时性也是老大难,保持怀疑。
和zookeeper类似,在redis设置一个访问标志来控制文件的访问,与利用zk的临时节点特性不一样之处在于,基于redis的SETNX(SET if Not Exist)命令来实现:
1) 最基本的做法,使用该命令实现的排他锁,setnx key若key不存在,返回1,表示获得锁;一旦key存在,其它设置该key的命令均返回0,即获取锁失败;
2)setnx可以设置超时时间,一旦这个kv超时就会被删除,让出锁,避免死锁;
3)全部为排他锁比较低效,读锁共享。