問題
File Storage NAS では、複数のクライアントが同じ名前空間内の異なるファイルにデータを書き込むことができます。ただし、ネットワーク ファイル システム (NFS) はアトミック追加をサポートしていません。複数のプロセスまたはクライアントが同じファイルに同時にデータを書き込むと、上書き、クロスオーバー、コンテンツの順序の乱れなどの例外が発生する可能性があります。これは、各プロセスがコンテキスト情報を個別に保持しているためです。ファイルはログファイルの場合があります。コンテキスト情報には、ファイル記述子と書き込み場所が含まれます。
ソリューション
(推奨) 異なるプロセスまたはクライアントが同じファイルシステム内の異なるファイルにデータを書き込むように構成します。データを分析または処理するときに、これらのファイルをマージできます。このソリューションは、ファイルロックを使用せずに、同時書き込み操作によって発生する問題を解決できます。このソリューションは、システム パフォーマンスに影響を与えません。
flock 関数と seek 関数を一緒に使用します。これは、書き込み操作の原子性と整合性を保証します。ただし、このソリューションには長い時間がかかり、システム パフォーマンスに大きな影響を与える可能性があります。次の手順でこの方法について説明します。
flock 関数と seek 関数を一緒に使用する
NFS はアトミック追加をサポートしていません。複数のクライアントがログなどの同じファイルにデータを追加する場合、データエントリが互いに上書きされる可能性があります。Linux では、flock 関数と seek 関数を一緒に使用して、NFS ファイルシステムでアトミック追加をシミュレートできます。これにより、複数のプロセスが同じファイルに同時にデータを追加する場合のデータ整合性が確保されます。
flock 関数と seek 関数を一緒に使用するには、次の手順を実行します。
fd=open(filename, O_WRONLY | O_APPEND | O_DIRECT) 文を呼び出して、追加メソッドを使用してファイルを開きます。この文は、書き込みメソッドを O_DIRECT に設定し、ファイル記述子を取得するために使用されます。O_DIRECT は書き込み専用メソッドを指定します。この場合、ページキャッシュは使用されません。
flock(fd, LOCK_EX|LOCK_NB) 関数を呼び出して、ファイルロックを取得します。関数がファイルロックの取得に失敗した場合、エラーメッセージが返されます。失敗の考えられる原因は、ファイルロックが使用中であることです。再試行するか、障害のトラブルシューティングを行うことができます。
ファイルロックを取得した後、lseek(fd, 0, SEEK_END) 関数を呼び出して、ファイル記述子の現在のファイルオフセットをファイルの末尾に設定します。
ファイルの末尾にデータを書き込みます。ファイルロックは、データエントリが互いに上書きされないようにするために使用されます。
データがファイルに書き込まれた後、flock(fd, LOCK_UN) 関数を呼び出して、ファイルロックを解放します。
次のコードは、C で記述されたサンプル プログラムを示しています。
#define _GNU_SOURCE
#include<stdlib.h>
#include<stdio.h>
#include<fcntl.h>
#include<string.h>
#include<unistd.h>
#include<sys/file.h>
#include<time.h>
const char *OUTPUT_FILE = "/mnt/blog";
int WRITE_COUNT = 50000;
// ファイルをロックする
int do_lock(int fd)
{
int ret = -1;
while (1)
{
ret = flock(fd, LOCK_EX | LOCK_NB);
if (ret == 0)
{
break;
}
usleep((rand() % 10) * 1000);
}
return ret;
}
// ファイルのロックを解除する
int do_unlock(int fd)
{
return flock(fd, LOCK_UN);
}
int main()
{
int fd = open(OUTPUT_FILE, O_WRONLY | O_APPEND | O_DIRECT);
if (fd < 0)
{
printf("Error Open\n");
exit(-1);
}
for (int i = 0; i < WRITE_COUNT; ++i)
{
char *buf = "one line\n";
/* Lock file */ // ファイルをロックする
int ret = do_lock(fd);
if (ret != 0)
{
printf("Lock Error\n");
exit(-1);
}
/* Seek to the end */ // ファイルの末尾までシークする
ret = lseek(fd, 0, SEEK_END);
if (ret < 0)
{
printf("Seek Error\n");
exit(-1);
}
/* Write to file */ // ファイルに書き込む
int n = write(fd, buf, strlen(buf));
if (n <= 0)
{
printf("Write Error\n");
exit(-1);
}
/* Unlock file */ // ファイルのロックを解除する
ret = do_unlock(fd);
if (ret != 0)
{
printf("UnLock Error\n");
exit(-1);
}
}
return 0;
}
flock() 関数の呼び出し方法の詳細については、「Linux ファイルロックメカニズム - Flock、Lockf、および Fcntl」をご参照ください。
Linux カーネル バージョン 2.6.12 以降のみが flock() 関数をサポートしています。以前のカーネルバージョンの Linux を使用している場合は、fcntl() 関数を呼び出してください。