Linux下锁的应用


概述 ¶

前不久工作需要实现一个文件转递的应用系统,功能如下:

  • 数据采集方通过ftp上传文件(大小在200KB)
  • 转递程序监听ftp的上传文件夹,实时转递文件到大数据平台
  • 应用部署在Centos7 64bit

第一版实现 ¶

采用vsftpd作为ftp服务器, 配置文件夹并共享给转递程序.转递程序使用1个线程轮寻文件夹, 60个转递线程推送
推送文件到大数据平台.

在实际运行中,出现文件解析失败数量随着数据量增大而增多.经过多方排查, 找奥问题处在并发上: 当文件增多,
ftp服务器写入到硬盘的速度变慢,当转递程序扫描文件夹的时候, 文件虽然已经生成,但文件内容却没有完全写入
完毕, 从而造成读取到文件但解析失败的情况.

探究 ¶

那有没有系统锁可以锁住文件, 当文件在被ftp写入的时候,转递程序不去处理这个文件?
Linux系统下, 有两种锁的机制, 分别是Advisory record Lock和Mandatory lock.

一种需要资源竞争者约定俗成,使用同一个api去加锁才能达到资源隔离的目的锁机制.

一种内核保证的所有进程间的强制锁,但目前Linux下很多BUG,而且在4.5以后变成可选的特性.不建议使用.

那我们有没有办法用这些机制去保证呢?

如果想使用Advisory record lock去保证锁的话, 必须修改vsftpd的代码,按照标准api实现加锁.
硬着头皮去看下源码, 幸好vsftpd作者考虑到了这点, 在写入文件的时候已经加如了锁的处理.
具体戳这里

由于转递程序是Java写的, 那怎么使用fcntl呢?难道使用jni吗?会不会太复杂? 首先, 我们了解下Java里面文件锁如何添加:

File outFile = new File("/tmp/filelock.tmp");
FileOutputStream outputStream = new FileOutputStream(outFile);
FileChannel channel = outputStream.getChannel();
FileLock lock = channel.lock();
System.out.println("lock success");
lock.release();
channel.close();
outFile.delete();

能不能linux下的锁共用呢?也只能先去看下源码了.
在openjdk8的源码里,先找到FileOutputStream的getChannel方法的代码(L376-L381) 然后查看FileChannelImpl的lock()方法的代码(L1005),发现最终使用了FileDispatcher作为 锁管理.查看FileDispatcher代码(L43),使用了native的方法.最后我们找到文件 FileDispatcherImpl.c代码(L174-L210).到此确定了java底层的实现也是使用 Advisory record lock.

虽然源码如此,我们还是需要写个c程序确认下

#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>

int main() {

  int fd = open("/tmp/filelock.tmp", O_WRONLY);
  if (0 > fd) {
    printf("open file error, %s\n", strerror(errno));
    return errno;
  }
  struct flock the_lock;
  memset(&the_lock, 0, sizeof(struct flock));
  the_lock.l_type = F_WRLCK;
  the_lock.l_whence = SEEK_SET;
  the_lock.l_start = 0;
  the_lock.l_len = 0;

  if (-1 != fcntl(fd, F_SETLKW, &the_lock)) {
    printf("lock success \n");
    sleep(30);
    printf("sleep done\n");

    memset(&the_lock, 0, sizeof(struct flock));
    the_lock.l_type = F_UNLCK;
    the_lock.l_whence = SEEK_SET;
    the_lock.l_start = 0;
    the_lock.l_len = 0;
    if (0 == fcntl(fd, F_SETLKW, &the_lock)) {
      printf("unlock success \n");
    } else {
      printf("unlock error \n");

    }

  } else {
      printf("lock error %s\n", strerror(errno));

  }
  close(fd);
}

经过测试确实可以和java的锁达到资源隔离的目的.至此,改造完成.

升华 ¶

静下来思考,锁其实非常消耗资源,能避免则避免.那这个程序能否用异步的方式去实现呢? 这里提供一种思路:利用ftp服务器的hook机制,转递端改造成服务器的模式,每次上传完成由ftp服务器主动推送 通知转递程序进行文件转递流程.这样既可以减少轮寻的次数,又能缩短文件转递的延迟,何乐而不为呢?