日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

深入浅出 NXLog (一)

發(fā)布時間:2023/12/31 编程问答 48 豆豆
生活随笔 收集整理的這篇文章主要介紹了 深入浅出 NXLog (一) 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

轉(zhuǎn)自 http://www.jianshu.com/p/ac9f5ae4beb9

1. NXLog 簡介

nxlog 是用 C 語言寫的一個開源日志收集處理軟件,它是一個模塊化、多線程、高性能的日志管理解決方案,支持多平臺。今天我主要分析一下 nxlog 的啟動流程,基于的 code 版本是 nxlog-ce-2.8.1248

2. NXLog 啟動流程圖

上圖是 nxlog 啟動的一個大致流程圖,大家可以先看一眼,對整個流程有個大致認(rèn)識,具體的解析下面奉上。

3. NXLog 啟動詳解

下面根據(jù)啟動流程的各個步驟分別進行分析。

3.1 NXLog Init

nxlog->ctx = nx_ctx_new();nx_ctx_register_builtins(nxlog->ctx);CHECKERR(apr_thread_mutex_create(&(nxlog->mutex), APR_THREAD_MUTEX_UNNESTED, nxlog->pool));CHECKERR(apr_thread_cond_create(&(nxlog->event_cond), nxlog->pool));CHECKERR(apr_thread_cond_create(&(nxlog->worker_cond), nxlog->pool));

nxlog_init 主要干了三件事情。
1. 通過 nx_ctx_new 接口創(chuàng)建一個configuration context。ctx 很重要,主要用來存儲 nxlog 的配置,module,jobgroups 等信息。
2. 通過 nx_ctx_register_builtins 接口綁定 IN, OUT 以及 CORE 模塊的 callback。下面以 IN 模塊為例來進行說明。

static void nx_ctx_register_builtin_inputfuncs() {nx_module_input_func_register(NULL, "linebased",&nx_module_input_func_linereader, NULL, NULL);nx_module_input_func_register(NULL, "dgram",&nx_module_input_func_dgramreader, NULL, NULL);nx_module_input_func_register(NULL, "binary",&nx_module_input_func_binaryreader, NULL, NULL); }

Input module 提供三種 callback。根據(jù)數(shù)據(jù)源的不同分為 linebased,dgram,binary 三種方式。linebased 代表log一行一行切分的,適用于讀取日志文件。dgram 代表log是一個包一個包來切分的,適用于接收 UDP syslog 消息。binary 代表數(shù)據(jù)源為二進制流。nxlog 的配置文件里每個 module 下面有個 InputType 選項,我們可以把它分別配置成 LineBased,Dgram,Binary 來實現(xiàn)相應(yīng)的功能。
3. 通過 apr_thread_mutex_createapr_thread_cond_create 創(chuàng)建 互斥鎖(nxlog->mutex) 和條件變量(nxlog->event_cond 和 nxlog->worker_cond)。這里我們看到 nxlog 有使用 APR(Apache Portable Runtime)的接口,在 nxlog 里很多涉及到平臺相關(guān)的代碼都是調(diào)用 APR 的接口來實現(xiàn)的,以此來實現(xiàn)跨平臺。
互斥鎖,是一種信號量,常用來防止兩個進程或線程在同一時刻訪問共享資源。條件變量(Condtion Variable)是在多線程程序中用來實現(xiàn)“等待” -> “喚醒”邏輯的常用方法。
NXLog 使用的是生產(chǎn)者消費者模式,該模式需要有一個緩沖區(qū)處于生產(chǎn)者和消費者之間,生產(chǎn)者把數(shù)據(jù)放入緩沖區(qū),而消費者從緩沖區(qū)取出數(shù)據(jù)。這個緩沖區(qū)在 NXLog 里叫做 jobqueue,jobqueue屬于共享資源,我們需要使用 nxlog->mutex 對多個線程進行同步,防止多個線程同時訪問 jobqueue 而導(dǎo)致數(shù)據(jù)出錯。
既然是生產(chǎn)者消費者模式,那就有可能出現(xiàn)生產(chǎn)者快于消費者或者消費者快于生產(chǎn)者的情況。當(dāng)生產(chǎn)者快于消費者時,我們首先想到的是 jobqueque 會不會滿,事實上 jobqueue 是用一個雙向鏈表來實現(xiàn)的,它只是負責(zé)把各種 event 鏈在一起,本身并不維護一塊內(nèi)存空間,真正消耗內(nèi)存的是 event 本身,所以理論上只要 event 能創(chuàng)建就沒有問題。但是如果程序經(jīng)常出現(xiàn)這種情況,那就代表處理能力不足,影響運行效率。為了不出現(xiàn)這種問題,消費者這一塊 nxlog 是用線程池來實現(xiàn)的(在后面 Create threads 我們再細說),這也就是它號稱多線程,高性能的原因。
下面再來看看當(dāng)消費者快于生產(chǎn)者的情況,出現(xiàn)這種情況后,我們就發(fā)現(xiàn) jobqueue 是空的,這時我們不希望消費者忙等而消耗資源,而是希望他們睡眠,當(dāng)有新的 event 產(chǎn)生時再喚醒消費者。這就需要上面我們說到的條件變量,消費者調(diào)用 apr_thread_cond_wait 睡眠,當(dāng)生產(chǎn)者有消息時調(diào)用 apr_thread_cond_signal 來喚醒消費者。

3.2 NXLog parse configuration

NXLog 的配置分為兩部分,一部分通過命令行參數(shù)的方式帶進來,不過這部分配置很少,大多數(shù)配置還是通過配置文件的方式下發(fā)。
1. Command line
命令行就沒有什么好說的了, 主要是用來指定運行方式以及配置文件路徑等。

static const apr_getopt_option_t options[] = {{ "help", 'h', 0, "print help" }, { "foreground", 'f', 0, "run in foreground" },{ "stop", 's', 0, "stop a running instance" },{ "reload", 'r', 0, "reload configuration of a running instance" },{ "conf", 'c', 1, "configuration file" }, { "verify", 'v', 0, "verify configuration file syntax" },{ NULL, 0, 1, NULL }, };
  • Config File
    配置文件會被解析成一個config tree,nxlog 通過遞歸調(diào)用 nx_cfg_parse 接口對配置文件進行逐行(如果在行尾有反斜杠 ‘\’, 會把多行并作一行)分析,最后返回一個 cfgtree,這個 tree 中的每個節(jié)點都保存了一行配置中的指令和參數(shù),同時它還會保存父節(jié)點,子節(jié)點以及兄弟節(jié)點的地址。
    這樣就把配置文件保存在數(shù)據(jù)結(jié)構(gòu) cfgtree 中,最后在每個 module 啟動之前會調(diào)用這個 module 的 config 接口,這個接口遍歷該模塊緩存在 cfgtree 中的配置,然后使能這些配置,這樣 config file 的使命就完成了。關(guān)于 config file 的寫法以及每個 module 的配置選項有很多,在此我就不贅述,不清楚的朋友可以參考 doc/reference-manual 目錄,在 config-examples 目錄下有一些配置模板,在 en 目錄下有參考手冊 nxlog-reference-manual.pdf,這里面關(guān)于 config file 講的很詳細。
  • 3.3 Read config cache

    Config cache 里保存了 nxlog 上次采集的位置,比如文件的話它會記錄文件的名字以及最后一次的 offset,這樣當(dāng) nxlog 重啟后它就知道上次采集到了那里,然后沿著上一次采集的位置繼續(xù)采集,這樣就避免了重復(fù)采集。針對日志采集,我們比較忌諱的有兩點,一是丟數(shù)據(jù),二就是重復(fù)采集,這兩種情況都會導(dǎo)致采集上來的日志和原來的日志不一致。
    Nxlog 在啟動的時候調(diào)用 nx_config_cache_read 接口,把 configcache.dat 文件里保存的信息讀出并保存在 config_cache 數(shù)據(jù)結(jié)構(gòu)中,采集文件的模塊啟動時候會在 config_cache 里查找有沒有某個文件的數(shù)據(jù),如果不存在就從文件的開頭或者末尾開始讀取,如果存在就調(diào)用 apr_file_seek 移動文件指針到上次讀取的位置,當(dāng) nxlog 要退出的時候?qū)⒏?config_cache 到文件最新的讀取位置,然后調(diào)用 nx_config_cache_write 將 config_cache 回寫到 configcache.dat 文件中。

    3.4 Add & config modules

    Nxlog 會首先遍歷 cfgtree, 針對所有的 module(共有4種, input, output, processor, extension) 分別調(diào)用 nx_module_add 接口。該接口會調(diào)用 nx_module_new 創(chuàng)建一個 module,然后再調(diào)用 nx_module_load_dso 綁定該 module 的方法,等一些必要的初始化都完成后會將該 module 放到 ctx->modules 鏈表中統(tǒng)一管理。

    if ( strcasecmp(curr->directive, "input") == 0 ){if ( curr->first_child == NULL ){nx_conf_error(curr, "empty 'Input' block");}nx_module_add(ctx, curr->first_child, curr->args, NX_MODULE_TYPE_INPUT);}else if ( strcasecmp(curr->directive, "processor") == 0 ){if ( curr->first_child == NULL ){nx_conf_error(curr, "empty 'Processor' block");}nx_module_add(ctx, curr->first_child, curr->args, NX_MODULE_TYPE_PROCESSOR);}else if ( strcasecmp(curr->directive, "output") == 0 ){if ( curr->first_child == NULL ){nx_conf_error(curr, "empty 'Output' block");}nx_module_add(ctx, curr->first_child, curr->args, NX_MODULE_TYPE_OUTPUT);}else if ( strcasecmp(curr->directive, "extension") == 0 ){if ( curr->first_child == NULL ){nx_conf_error(curr, "empty 'Extension' block");}nx_module_add(ctx, curr->first_child, curr->args, NX_MODULE_TYPE_EXTENSION);}

    接著 NXLog 會遍歷 ctx->modules 鏈表,分別調(diào)用每個 module 的 config 方法對該 module 進行配置,然后會解析該 module 的 “Exec” 以及 “Schedule” 配置。Nxlog 確實比較強大,不僅能采集各種類型的日志,而且還能通過 “Exec” 執(zhí)行一些任務(wù),比如日志過濾等,還能通過 “Schedule” 做一些任務(wù)調(diào)度,比如定期進行日志歸檔等。

    ASSERT(module->status == NX_MODULE_STATUS_UNINITIALIZED);if ( module->decl->config != NULL ){module->decl->config(module);}else{nx_module_empty_config_check(module);}module->exec = nx_module_parse_exec_block(module, module->pool, module->directives);nx_module_parse_schedule_blocks(module);

    3.5 Init modules

    NXLog 會遍歷 ctx->modules 鏈表,得到每個 module,然后調(diào)用 nx_module_init 接口對該 module 進行初始化。nx_module_init 也沒干什么事情,只是調(diào)用該 module 的 init 方法。

    for ( module = NX_DLIST_FIRST(ctx->modules);module != NULL;module = NX_DLIST_NEXT(module, link) ){try{nx_module_init(module);if ( module->type == NX_MODULE_TYPE_INPUT ){num_input++;}}

    3.6 Init routes and jobs

    Route 顧名思義就是路由,它定義數(shù)據(jù)從哪個 INPUT 模塊采集,經(jīng)過哪個 PROCESSOR(不是必須的) 模塊處理,送到哪個 OUTPUT 模塊。Route 包含兩個配置,一個是 “Path”,它定義了數(shù)據(jù)的流動方向。第二個是 “Priority”,它定義了該 route 的優(yōu)先級,路由會將自己的優(yōu)先級賦值給 path 中的各個 module。

    while ( curr != NULL ){if ( strcasecmp(curr->directive, "route") == 0 ){if ( nx_add_route(ctx, curr, curr->args) == TRUE ){num_routes++;}}curr = curr->next;}

    Nxlog 維護一個 jobgroups 鏈表,該鏈表按照優(yōu)先級順序存放著許多 jobgroup,相同優(yōu)先級的 module 會把他們的 job 掛到同一個 jobgroup->jobs 上面。worker_thread 會優(yōu)先從 priority 高的 jobgroup 里取 job,最終演變下來就是 priority 高的 module 的 event 會優(yōu)先得到處理。

    ASSERT(ctx != NULL);ctx->jobgroups = apr_palloc(ctx->pool, sizeof(nx_jobgroups_t));NX_DLIST_INIT(ctx->jobgroups, nx_jobgroup_t, link);for ( module = NX_DLIST_FIRST(ctx->modules);module != NULL;module = NX_DLIST_NEXT(module, link) ){jobgroup = nx_ctx_get_jobgroup(ctx, module->priority);job = apr_pcalloc(ctx->pool, sizeof(nx_job_t));NX_DLIST_INSERT_TAIL(&(jobgroup->jobs), job, link);module->job = job;}

    3.7 Create threads

    NXLog 會創(chuàng)建一個 event_thread 和多個 worker_thread。究竟會創(chuàng)建幾個 worker_thread 由 num_worker_thread 變量來決定,下面來說說這個 num_worker_thread。
    如果配置文件里配置了 "Threads"num_worker_thread 將會被設(shè)置成該值。如果沒有配置,nxlog 會根據(jù)配置文件里配置的 module 的個數(shù)以及所有 module 的 pollset 個數(shù)計算得到 num_worker_thread,具體算法可以參見 nxlog_create_threads 函數(shù),在此我就不贅述。當(dāng)配置文件里配了很多 module 的時候,這個數(shù)值有可能會大于 CPU core 的數(shù)量,這時可能會有人認(rèn)為這是沒用的,因為這些 threads 不可能同時得到執(zhí)行,而且會增加系統(tǒng)的工作量(內(nèi)存,調(diào)度的開銷)。在我看來這要分情況,如果這些 worker_thread 從事的是 CPU 密集型的工作,我覺得這種觀點是正確的。但是如果這些 worker_thread 從事的是 IO 密集型的工作,這種觀點就值得推敲了,IO 密集型的工作一個顯著的特點是 Thread 在 IO 不 Ready 的情況下會睡眠,這樣即使你起的 thread 數(shù)量超過了 CPU core 的數(shù)量,但由于很多 thread 都處于睡眠狀態(tài),真正執(zhí)行的 thread 并不多。針對 IO 密集型的工作,增加 thread 數(shù)量是能顯著提升性能的,但也不是越多越好,當(dāng) thread 數(shù)量太多的時候反而會走向另一個極端。
    event_thread 主要是處理延時的 event,它會從 ctx->events 中依次取出 event, 當(dāng)該 event 的 time 已經(jīng)超時了 event_thread 會把該 event 交給 worker_thread 來處理。如果沒有超時會得到離當(dāng)前時間最近的一個 event 要超時的時間,然后調(diào)用 apr_thread_cond_timedwait sleep 這么長的時間,等醒來后再去處理這個 event,當(dāng)然在 sleep 的這段時間內(nèi)如有新的 event 產(chǎn)生會調(diào)用 apr_thread_cond_signal(nxlog->event_cond) 喚醒 event_thread。由于 event_thread 干的事情不多,因此只需要一個。
    worker_thread 先通過 nx_ctx_next_job 接口獲取一個 event,如果沒有 event 可處理,worker_thread 會調(diào)用 apr_thread_cond_wait(nxlog->worker_cond, nxlog->mutex) 睡眠,等有新的 event 產(chǎn)生時會調(diào)用 apr_thread_cond_signal(nxlog->worker_cond) 把它喚醒。如果有 event 會調(diào)用 nx_event_process 來處理。nx_ctx_next_job 獲取 event 有一定的講究,他會優(yōu)先獲取 priority 高的 module 的 event,它是通過優(yōu)先遍歷 priority 高的 jobgroup 來實現(xiàn)的,前面創(chuàng)建 jobgroups 的時候我們就說過,nxlog 會把不同 priority 的 module 的 job 放到不同 priority 的 jobgroup 中。

    nxlog->worker_threads = apr_palloc(nxlog->pool, sizeof(apr_thread_t *) * nxlog->num_worker_thread);nxlog->worker_threads_running = apr_pcalloc(nxlog->pool, sizeof(uint32_t) * nxlog->num_worker_thread);log_debug("spawning %d worker threads", nxlog->num_worker_thread);for ( i = 0; i < nxlog->num_worker_thread; i++){nx_thread_create(&(nxlog->worker_threads[i]), NULL, nxlog_worker_thread, NULL, nxlog->pool);}nx_thread_create(&(nxlog->event_thread), NULL, nxlog_event_thread, NULL, nxlog->pool);

    3.8 Start modules

    NXLog 會遍歷 ctx->modules 鏈表,得到每個 module,然后調(diào)用 nx_module_start 啟動該 module。nx_module_start 會發(fā)送 NX_EVENT_MODULE_START event 給 worker_thread,worker_thread 收到該 event 后會調(diào)用 nx_module_start_self,繞了一圈后真正干活的才出現(xiàn),nx_module_start_self 會首先獲取該 module 的狀態(tài),如果是 NX_MODULE_STATUS_STOPPED 會調(diào)用該 module 的 start 接口,然后把它的狀態(tài)置成 NX_MODULE_STATUS_RUNNING。

    ASSERT(nx_module_get_status(module) == NX_MODULE_STATUS_STOPPED);if ( module->decl->start != NULL ){module->decl->start(module);}nx_module_add_scheduled_events(module);nx_module_set_status(module, NX_MODULE_STATUS_RUNNING);

    3.9 Main loop

    nxlog_mainloop 沒干什么事情,一直調(diào)用 apr_sleep(NX_POLL_TIMEOUT),把 CPU 讓給 event_thread 和 worker_threads。
    當(dāng) nxlog 收到 SIGTERM, SIGINT, SIGQUIT 這三個信號中的任何一個時,terminate_request 會被置成 TRUE,nxlog_mainloop 結(jié)束,Nxlog 調(diào)用 nxlog_exit 執(zhí)行 stop/shutdown module, 結(jié)束 event_thread, worker_threads, 回寫 config cache,remove pidfile,回收資源等動作,最后 Nxlog 進程退出。

    總結(jié)

    以上是生活随笔為你收集整理的深入浅出 NXLog (一)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。