赞
踩
“这是我参与8月更文挑战的第2天,活动详情查看:8月更文挑战”
文件锁是文件系统的最基本特性之一,应用程序借助文件锁可以控制其他应用对文件的并发访问。NFS作为类UNIX系统的标准网络文件系统,在发展过程中逐步的原生支持了文件锁 (从NFSv4开始)。
通过命令man nfs
可以查看官方说明手册内容:
其中比较重要的是第三句:NLM仅支持建议文件锁。要锁定NFS文件,需使用fcntl(2)和F_GETLK和F_SETLK命令。NFS客户端将通过flock(2)获得的文件锁转换为咨询锁。
也就是说应用程序可以通过fcntl()
或flock()
系统调用管理NFS文件锁。
下面阿里云文件存储 NAS使用NFSv4挂载时获取文件锁的调用过程图: 图片来源:# NFS文件锁一致性设计原理解析
从上图调用栈容易看出来,NFS文件锁实现逻辑基本复用了VFS层设计和数据结构,在通过RPC从Server成功获取文件锁后调用lockslockinode_wait()函数将获得文件锁交给到VFS层管理,关于VFS层文件锁设计的相关资料比较多,感兴趣的读者可以自己搜索了解
观察上图的文件锁调用过程,看出最主要的交互在nfs4_proc_lock()
方法上,通过查看Linux内核源码 https://git.kernel.org/pub/scm/linux/kernel/git/cel/linux.git/tree/fs/nfs/nfs4proc.c
可以看到相关的实现与调用
除了上面通过linux 内核源码了解nfsv4文件锁的定义存在,还可以查看其 Network File System (NFS) Version 4 Protocol规范,通过阅读第9章节,也可以知道nfsv4对文件锁的支持说明
NFSV4文件锁通过在通信协议增加stateid的方式来实现服务器端文件锁功能,在 NFSv3 中,没有 stateid 的概念,因此无法判断发送 READ 或WRITE 操作的客户端的应用程序进程是否也获取了文件上的适当字节范围锁定。因此,没有办法实现强制锁定。使用 stateid 构造,此障碍已被移除。
Java 提供了文件锁FileLock
类,利用这个类可以控制不同程序(JVM)对同一文件的并发访问,实现进程间文件同步操作。
FileLock是java 1.4 版本后出现的一个类,它可以通过对一个可写文件(w)加锁,保证同时只有一个进程可以拿到文件的锁,这个进程从而可以对文件做访问;而其它拿不到锁的进程要么选择被挂起等待,要么选择去做一些其它的事情, 这样的机制保证了众进程可以顺序访问该文件。也可以看出,能够利用文件锁的这种性质,在一些场景下,虽然我们不需要操作某个文件, 但也可以通过 FileLock 来进行并发控制,保证进程的顺序执行,避免数据错误。
我们需要通过调用 FileChannel
类上的 lock()
或 tryLock()
来获得 FileLock 文件锁,
FileChannel.java
这里选用trylock()
,其实选lock()
也一样,因为是抽象方法,所以需要到实现类Impl中查看具体逻辑。
FileChannelImpl.java
注意try语句的部分,一般try都是做重要事情的,可以看到正在进行锁lock的操作。
FileDispatcher.java
lock()
在这里也是一个抽象方法,继续去找它的实现类Impl看看。
FileDispatcherImpl.java
终于找到了lock()
的实现,发现最终调用的是lock0()
,而一直往下深入发现是一个native method
。
FileDispatcherImpl.c
看到native method
,肯定是用c写的实现逻辑,所以找到FileDispatcherImpl.c
如下内容,发现其实现中有调用fcntl()
,nfsv4文件锁的使用也正好需要该函数的调用。
前面已经学习过NFS的安装与配置,而且也了解了nfsv4文件锁和java文件锁的,接下来将进行实战演示。
首先新建一个Java类,该类对文件进行读写锁操作,内容如下: ```java package com.jpsite.utils;
import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.file.FileAppender; import cn.hutool.core.io.file.FileReader; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.net.InetAddress; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; import java.util.Objects;
public class FileUtilTest { public static void test1() throws Exception { InetAddress ia = InetAddress.getLocalHost(); String host = ia.getHostName();//获取计算机主机名 String IP= ia.getHostAddress();//获取计算机IP final File file = FileUtil.touch(new File("/data/share/b.txt")); FileAppender appender = new FileAppender(file, 1, true); appender.append("123"+IP); appender.append("abc"+IP); appender.append("xyz"+IP); appender.flush(); FileReader fileReader = new FileReader(file); String result = fileReader.readString(); System.out.println(result);
- final FileLock fileLock = getFileLock(file);
- if (Objects.isNull(fileLock)) {
- System.out.println("not get lock");
- } else {
- final String lockType = fileLock.isShared() ? "share lock" : "exclusive lock";
- System.out.println(String.format("getted lock %s", lockType));
- int i = 0;
- while (true) {
- Thread.sleep(1000);
- i++;
- System.out.println(lockType + "no release");
- if (i == 30) {
- fileLock.release();
- if (!fileLock.isValid()) {
- System.out.println("lock is valid ,return while");
- break;
- }
- }
- }
- }
- }
-
- public static FileLock getFileLock(File file) throws IOException {
- final RandomAccessFile rw = new RandomAccessFile(file, "rw");
- final FileChannel channel = rw.getChannel();
- return channel.tryLock();
- }
-
- public static void main(String[] args) throws InterruptedException {
- new Thread(() -> {
- try {
- test1();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }).start();
- }
} ```
把这个类分别上传到NFS Client的 Linux机器,比如centos 1, centos2,cd
到java项目的src路径,分别执行 ```
javac -classpath /root/.m2/repository/cn/hutool/hutool-all/4.6.17/hutool-all-4.6.17.jar ./com/jpsite/utils/FileUtilTest.java
java -classpath .:/root/.m2/repository/cn/hutool/hutool-all/4.6.17/hutool-all-4.6.17.jar com.jpsite.utils.FileUtilTest ``` 可以看到 centos1 中java代码正常在执行
centos2 执行过程中由于获取不到文件锁被退出
刚刚的测试是两台云主机centos机器,现在改为一台centos和一台本地电脑,首先本地电脑执行命令挂载 sudo mount -t nfs -o vers=4.0,proto=tcp,timeo=60 129.xxx.x.139:/data/share /Users/hahas/Documents/data/share
测试出现了两种结果: * ✅结果一:先执行centos中的java代码,再执行m1 macbook的java代码,结果和两台云主机centos机器测试结果一致。 * ❌结果二:先执行m1 macbook的java代码,再执行centos中的java代码,发现都能获取到文件锁。
出现结果二问题原因,m1 macbook的nfs client版本过低,为能提供有效支持;或者是因为采用了arm架构内核,导致相关功能失效。
网络抓包的手段和工具很多,本文在centos系统中使用的是内置的tcpdump
,而macbook则使用wireshark
通过wireshark可以方便的看到网络协议的相关数据
js // 查看发给129.xxx.x.139 nfs server的网络数据 (ip.dst eq 129.xxx.x.139) and nfs
通过抓包发现lock和unlock操作,对比其中的网络数据包,发现其内容和NFSV4文件锁介绍那章相符,所以NFS文件锁的使用,是需要网络协议中stateid等数据字段的支持。
ps:如果有厉害的小伙伴或者黑客,可以不用通过系统底层方法调用,直接采用数据包拦截篡改的方式,使nfs server的文件上锁
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/703630
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。