讓Flume的TaildirSource支持Windows操作系統

最近準備重寫以前的監控日志文件變動的項目,以前監控日志文件變動使用的技術基礎是jdk1.7新出的WatchService,但是使用了接近一年以后發現了幾個問題:

1.無法指定專門文件監聽,只能對整個目錄的所有文件進行監聽,然后自己過濾
2.當修改了目錄或者文件的任何metadata后,就不會再傳入任何變動事件給監聽者了
3.無法很好的控制監控頻率

對于以上的三個問題,使用WatchService技術無法很好的解決他們,所以到處找別的解決方案。
因為我使用的是flume, 版本是1.6,當我看到flume1.7使用的TaildirSource的時候,忍不住眼前一亮,這就是我需要的解決方案。但是使用之前我要判斷下是否有坑,于是去讀了下他的源代碼。

TaildirSource的代碼很簡單,主要就是五個類:
ReliableTaildirEventReader類
TaildirMatcher類
TaildirSource類
TaildirSourceConfigurationConstants類
TailFile類

這五個類之間的關系描述如下:
TailSource不用說,Flume的啟動點
在其process方法中
TailSource調用ReliableTaildirEventReader的updateTailFiles方法去獲取所有需要關注的文件,并讀取解析
而為了只關注需要關注的文件,在ReliableTaildirEventReader的updateTailFiles方法中使用了TaildirMatcher去過濾出需要的文件
而TaildirMatcher和文件系統的目錄是一一對應的,一個目錄可以抽象為一個TaildirMatcher,而被關注的文件被抽象為TailFile對象
TaildirSourceConfigurationConstants類不用多說,一些配置常量而已

理解了類和代碼,我們就可以明白整套TaildirSource動態監聽文件變化的技術基礎就是獲取文件的inode,建立inode和文件之間的一一對應關系,利用RandomAccessFile去讀取文件,并將inode和讀取的位置以及文件位置保存成json文件進行持久化,以便后續的繼續跟蹤。
但是,注意,inode是linux文件的概念,而獲取inode是在ReliableTaildirEventReader的getInode方法里,在這個方法里,我們將受到一萬點暴擊:(long) Files.getAttribute(file.toPath(), "unix:ino");
這段代碼無恥的排除了windows操作系統的存在。

看明白了TaildirSource的代碼,發現TaildirSource是不支持Windows的。怎么辦呢?首先想想解決思路:
我們知道整體TaildirSource的思想是獲取一個文件的標識(linux里inode可以作為文件的標識使用,當系統讀取文件時,其實就是根據文件路徑轉換成對應的inode值來做的操作)并記錄對應的文件路徑,那么我們首先要確認在Windows中是否是有類似于inode這種東西的存在。這個回答解答了這個問題,windows中是有file id這種類似于inode的存在的。

那么繼續,這個file id是否是有什么限制或者有什么特性呢?
參考這個回答 我們知道file id是跟文件系統有關的, 在FAT系統中,如果修改的名字長于舊名字,file id可能會發生改變,但是在NTFS系統中,在刪除之前file id都是穩定的。

ok,方案可以確定了,如果是windows系統 并且文件系統是ntfs,那么我們就使用file id去獲取文件,剩下的邏輯幾乎和linux是一模一樣。
如果是fat系統(在我們的工作環境中不可能出現),我們暫時不做支持。
(另外提一句,在windows 2012中新增加了一個Refs 這個由于我們基本不用,所以沒有做考慮,參考這個)
方案落地如下:
使用java的jna
Kernel32的代碼:

package com.creditease.ns.jna;/* Copyright (c) 2007, 2013 Timothy Wall, Markus Karg, All Rights Reserved
 *
 * The contents of this file is dual-licensed under 2 
 * alternative Open Source/Free licenses: LGPL 2.1 or later and 
 * Apache License 2.0. (starting with JNA version 4.0.0).
 * 
 * You can freely decide which license you want to apply to 
 * the project.
 * 
 * You may obtain a copy of the LGPL License at:
 * 
 * http://www.gnu.org/licenses/licenses.html
 * 
 * A copy is also included in the downloadable source code package
 * containing JNA, in file "LGPL2.1".
 * 
 * You may obtain a copy of the Apache License at:
 * 
 * http://www.apache.org/licenses/
 * 
 * A copy is also included in the downloadable source code package
 * containing JNA, in file "AL2.0".
 */

import com.sun.jna.Native;
import com.sun.jna.Structure;
import com.sun.jna.platform.win32.WinBase;
import com.sun.jna.platform.win32.WinNT;
import com.sun.jna.platform.win32.Wincon;
import com.sun.jna.win32.StdCallLibrary;
import com.sun.jna.win32.W32APIOptions;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * Interface definitions for <code>kernel32.dll</code>. Includes additional
 * alternate mappings from {@link WinNT} which make use of NIO buffers,
 * {@link Wincon} for console API.
 */
public interface Kernel32 extends StdCallLibrary, WinNT, Wincon {

    /**
     * The instance.
     */
    Kernel32 INSTANCE = (Kernel32) Native.loadLibrary("kernel32", Kernel32.class, W32APIOptions.DEFAULT_OPTIONS);

    /**
     * The GetLastError function retrieves the calling thread's last-error code
     * value. The last-error code is maintained on a per-thread basis. Multiple
     * threads do not overwrite each other's last-error code.
     *
     * @return The return value is the calling thread's last-error code value.
     */
    int GetLastError();

    /**
     * The CreateFile function creates or opens a file, file stream, directory,
     * physical disk, volume, console buffer, tape drive, communications
     * resource, mailslot, or named pipe. The function returns a handle that can
     * be used to access an object.
     *
     * @param lpFileName            A pointer to a null-terminated string that specifies the name
     *                              of an object to create or open.
     * @param dwDesiredAccess       The access to the object, which can be read, write, or both.
     * @param dwShareMode           The sharing mode of an object, which can be read, write, both,
     *                              or none.
     * @param lpSecurityAttributes  A pointer to a SECURITY_ATTRIBUTES structure that determines
     *                              whether or not the returned handle can be inherited by child
     *                              processes. If lpSecurityAttributes is NULL, the handle cannot
     *                              be inherited.
     * @param dwCreationDisposition An action to take on files that exist and do not exist.
     * @param dwFlagsAndAttributes  The file attributes and flags.
     * @param hTemplateFile         Handle to a template file with the GENERIC_READ access right.
     *                              The template file supplies file attributes and extended
     *                              attributes for the file that is being created. This parameter
     *                              can be NULL.
     * @return If the function succeeds, the return value is an open handle to a
     * specified file. If a specified file exists before the function
     * call and dwCreationDisposition is CREATE_ALWAYS or OPEN_ALWAYS, a
     * call to GetLastError returns ERROR_ALREADY_EXISTS, even when the
     * function succeeds. If a file does not exist before the call,
     * GetLastError returns 0 (zero). If the function fails, the return
     * value is INVALID_HANDLE_VALUE. To get extended error information,
     * call GetLastError.
     */
    HANDLE CreateFile(String lpFileName, int dwDesiredAccess, int dwShareMode,
        WinBase.SECURITY_ATTRIBUTES lpSecurityAttributes,
        int dwCreationDisposition, int dwFlagsAndAttributes,
        HANDLE hTemplateFile);


    /**
     * Closes an open object handle.
     *
     * @param hObject Handle to an open object. This parameter can be a pseudo
     *                handle or INVALID_HANDLE_VALUE.
     * @return If the function succeeds, the return value is nonzero. If the
     * function fails, the return value is zero. To get extended error
     * information, call {@code GetLastError}.
     * @see <A >CloseHandle</A>
     */
    boolean CloseHandle(HANDLE hObject);


    class BY_HANDLE_FILE_INFORMATION extends Structure {
        public DWORD dwFileAttributes;
        public FILETIME ftCreationTime;
        public FILETIME ftLastAccessTime;
        public FILETIME ftLastWriteTime;
        public DWORD dwVolumeSerialNumber;
        public DWORD nFileSizeHigh;
        public DWORD nFileSizeLow;
        public DWORD nNumberOfLinks;
        public DWORD nFileIndexHigh;
        public DWORD nFileIndexLow;

        public static class ByReference extends BY_HANDLE_FILE_INFORMATION
            implements Structure.ByReference {

        }


        public static class ByValue extends BY_HANDLE_FILE_INFORMATION
            implements Structure.ByValue {

        }

        @Override
        protected List getFieldOrder() {
            List fields = new ArrayList();
            fields.addAll(Arrays.asList(new String[]{"dwFileAttributes",
                "ftCreationTime", "ftLastAccessTime", "ftLastWriteTime",
                "dwVolumeSerialNumber", "nFileSizeHigh", "nFileSizeLow",
                "nNumberOfLinks", "nFileIndexHigh", "nFileIndexLow"}));
            return fields;

        }
    }

    boolean GetFileInformationByHandle(HANDLE hFile,
        BY_HANDLE_FILE_INFORMATION lpFileInformation);
}
package com.creditease.ns.jna;

import com.sun.jna.platform.win32.WinBase;
import com.sun.jna.platform.win32.WinNT;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.nio.file.Path;

public class WinFileUtils {
    private static Logger logger = LoggerFactory.getLogger(WinFileUtils.class);

    private final static int GENERIC_READ = 0x80000000;
    private final static int FILE_SHARE_READ = 0x00000001;
    private static final WinBase.SECURITY_ATTRIBUTES SECURITY_ATTRIBUTES = null;
    private static final int OPEN_EXISTING = 3;
    private static final int FILE_FLAG_BACKUP_SEMANTICS = 0x02000000;
    public static final String IO_ERROR = "_ERROR_";

    public static String getUniqueFileId(Path file) {
        Kernel32.BY_HANDLE_FILE_INFORMATION nfo = new Kernel32.BY_HANDLE_FILE_INFORMATION();
        WinNT.HANDLE handle = Kernel32.INSTANCE.CreateFile(file.toString(), GENERIC_READ, FILE_SHARE_READ,
            SECURITY_ATTRIBUTES, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, null);
        int errorRet = Kernel32.INSTANCE.GetLastError();
        String identifier;
        if (errorRet != 0) {
            logger.error("創建/打開文件時發生錯誤 錯誤級別:{} 文件路徑:{}", errorRet, file);
            identifier = IO_ERROR;
        } else {
            Kernel32.INSTANCE.GetFileInformationByHandle(handle, nfo);
            errorRet = Kernel32.INSTANCE.GetLastError();
            if (errorRet != 0) {
                logger.error("獲取文件信息時發生錯誤 錯誤級別:{} 文件路徑:{}", errorRet, file);
                identifier = IO_ERROR;
            } else {
                identifier = nfo.nFileIndexHigh + nfo.nFileIndexLow.toString() + Integer.toHexString(nfo
                    .dwVolumeSerialNumber.intValue());
            }
        }
        Kernel32.INSTANCE.CloseHandle(handle);
        return identifier;
    }
}

以上代碼綜合參考了這里這里
再次強調下,以上獲取FileId的代碼不適用于Refs系統,如果想支持的更完善,請使用JNA 4.4.0版本 這個版本里的kernel32有一個方法叫做GetFileInformationByHandleEx 它是支持最新的Refs系統的。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,362評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,013評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,346評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,421評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,146評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,534評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,585評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,767評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,318評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,074評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,258評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,828評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,486評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,916評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,156評論 1 290
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,993評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,234評論 2 375

推薦閱讀更多精彩內容

  • Linux系統一般有4個主要部分: 內核、shell、文件系統和應用程序。內核、shell和文件系統一起形成了基本...
    偷風箏的人_閱讀 3,265評論 1 17
  • Linux系統一般有4個主要部分:內核、shell、文件系統和應用程序。 內核、shell和文件系統一起形成了基本...
    請愛護小動物閱讀 2,600評論 0 22
  • 一個基本的計算機系統由“硬件”和“軟件”組成,一臺Linux設備,主要的組成如下圖所示: 一般情況下,我們所說的L...
    時待吾閱讀 1,660評論 0 16
  • 1. 硬鏈接和軟連接區別 硬連接-------指通過索引節點來進行連接。在Linux的文件系統中,保存在磁盤分區...
    杰倫哎呦哎呦閱讀 2,300評論 0 2
  • 煙雨灰蒙,檐青瓦黛。我撐著紙傘沿著青石板街徐徐走著,雙眼望著前路,空洞迷惘。 時不時有路過的公子,皆玉帶錦...
    知子當如閱讀 263評論 0 0