[{"data":1,"prerenderedAt":863},["ShallowReactive",2],{"content-zh-TW-async-pipeline":3},{"doc":4,"debug":793},{"id":5,"title":6,"body":7,"description":786,"extension":787,"meta":788,"navigation":521,"path":789,"seo":790,"stem":791,"__hash__":792},"content\u002Fzh-TW\u002Fasync-pipeline.md","HFT 非同步架構",{"type":8,"value":9,"toc":763},"minimark",[10,14,23,58,65,68,73,78,88,95,133,147,151,158,176,184,188,206,210,232,236,260,262,266,273,277,280,383,386,394,397,401,407,411,421,469,484,488,495,535,542,546,556,558,562,573,608,623,625,629,650,676,680,687,693,699,701,705,759],[11,12,6],"h1",{"id":13},"hft-非同步架構",[15,16,17,18,22],"p",{},"OzaLog 內含兩條",[19,20,21],"strong",{},"獨立","的非同步管線:",[24,25,26,45],"ol",{},[27,28,29,32,33,37,38,37,41,44],"li",{},[19,30,31],{},"主 logger pipeline"," — 應用程式日誌(",[34,35,36],"code",{},"LOG.Info_Log","、",[34,39,40],{},"Error_Log",[34,42,43],{},"CustomName_Log","、…)",[27,46,47,50,51,37,54,57],{},[19,48,49],{},"報價 pipeline","(v3.1+)— 高頻市場 tick\u002Fquote 資料(",[34,52,53],{},"LOG.Quote",[34,55,56],{},"LOG.QuoteTicker",")",[15,59,60,61,64],{},"兩條管線",[19,62,63],{},"不共享 lock、不共享 stream、不共享 dispatcher 執行緒","。只有預設參數調校不同(佇列大小、檔案數上限、批次大小)。",[66,67],"hr",{},[69,70,72],"h2",{"id":71},"_1-主-logger-pipeline","1. 主 logger pipeline",[74,75,77],"h3",{"id":76},"_11-寫入路徑","1.1 寫入路徑",[79,80,85],"pre",{"className":81,"code":83,"language":84},[82],"language-text","[呼叫端執行緒]\n       │  入隊(struct LogItem,零配置)\n       ▼\n[ConcurrentQueue\u003CLogItem>]\n       │  透過 SemaphoreSlim signal\n       ▼\n[單一 dispatcher 執行緒]\n       │  排出批次 → 格式化 → AppendLine\n       ▼\n[FileStreamPool: per-(level, name) 持久化 FileStream]\n       │  LRU 上限 = LogOptions.MaxOpenFileStreams(預設 100)\n       │  換日內聯處理\n       │  size-based 分割 → {name}_part2_Log.{ext}\n       ▼\n{baseDir}\u002F{LogPath}\u002F{yyyyMMdd}\u002F{TypeDirectories.*}\u002F{name}_Log.{ext}\n","text",[34,86,83],{"__ignoreMap":87},"",[74,89,91,92,57],{"id":90},"_12-呼叫端成本每次-loginfo_log","1.2 呼叫端成本(每次 ",[34,93,94],{},"LOG.Info_Log(...)",[96,97,98,105,116,122,128],"ul",{},[27,99,100,101,104],{},"1× volatile read 讀快取時間戳(~5 ns;",[34,102,103],{},"HighPrecisionTimestamp=true"," 時 ~30 ns)",[27,106,107,108,111,112,115],{},"1× ",[34,109,110],{},"LogFormatter.EscapeMessage"," 跳脫 ",[34,113,114],{},"{}","(訊息無大括號則跳過)",[27,117,118,119],{},"1× struct field copy 建構 ",[34,120,121],{},"LogItem",[27,123,107,124,127],{},[34,125,126],{},"ConcurrentQueue.Enqueue","(CAS)",[27,129,107,130],{},[34,131,132],{},"SemaphoreSlim.Release",[15,134,135,146],{},[19,136,137,138,141,142,145],{},"呼叫端路徑無 ",[34,139,140],{},"DateTime.Now","、無 ",[34,143,144],{},"string.Format","、無 heap 配置","。所有格式化都在 dispatcher 執行緒做。",[74,148,150],{"id":149},"_13-背壓drop-oldest","1.3 背壓:drop-oldest",[15,152,153,154,157],{},"當佇列項目數超過 ",[34,155,156],{},"AsyncLogOptions.MaxQueueSize","(預設 10000):",[24,159,160,167,170],{},[27,161,162,163,166],{},"最舊的項目 ",[34,164,165],{},"TryDequeue"," 丟掉",[27,168,169],{},"drop 計數器 atomic 遞增",[27,171,172,175],{},[34,173,174],{},"LogOptions.OnDropped"," callback 觸發(如有設定)",[15,177,178,179,183],{},"這保證佇列永遠不會無限成長 — log 暴增不會造成 OOM,只是犧牲",[180,181,182],"em",{},"最舊","的那幾筆。",[74,185,187],{"id":186},"_14-immediate-flush","1.4 Immediate flush",[15,189,190,193,194,197,198,201,202,205],{},[34,191,192],{},"Error"," 與 ",[34,195,196],{},"Fatal"," 級別(以及任何 ",[34,199,200],{},"immediateFlush: true"," 的呼叫)會在呼叫端執行緒同步寫入 + ",[34,203,204],{},"FileStream.Flush(flushToDisk: true)",",額外做一次。這保證 crash log 在程式死掉前落盤。",[74,207,209],{"id":208},"_15-disk-flush-timer","1.5 Disk flush timer",[15,211,212,215,216,219,220,223,224,227,228,231],{},[34,213,214],{},"Timer"," 每 ",[34,217,218],{},"DiskFlushIntervalMs","(預設 100 ms)呼叫一次 ",[34,221,222],{},"FileStreamPool.FlushAll()",",對所有開啟的 stream 呼叫 ",[34,225,226],{},"StreamWriter.Flush()","。OS 決定 write-back 時機(不強制 ",[34,229,230],{},"fsync",")— 偏好吞吐勝過耐久性。",[74,233,235],{"id":234},"_16-收尾保證","1.6 收尾保證",[96,237,238,244,250],{},[27,239,240,243],{},[34,241,242],{},"AppDomain.CurrentDomain.ProcessExit"," → drain + flush + 關閉所有 stream",[27,245,246,249],{},[34,247,248],{},"AppDomain.CurrentDomain.UnhandledException"," → 同上",[27,251,252,255,256,259],{},[34,253,254],{},"LOG.Configure"," 可訂閱 ",[34,257,258],{},"EnableGlobalExceptionCapture = true",",在 unhandled exception 與 unobserved Task exception 時額外做 Fatal 級別寫入。",[66,261],{},[69,263,265],{"id":264},"_2-報價-pipeline-v31","2. 報價 pipeline (v3.1+)",[15,267,268,269,272],{},"報價 pipeline 與主 logger ",[19,270,271],{},"平行","運作 — 相同架構,獨立狀態。",[74,274,276],{"id":275},"_21-為什麼要分開做","2.1 為什麼要分開做?",[15,278,279],{},"報價 \u002F tick 資料與應用程式日誌特性根本不同:",[281,282,283,297],"table",{},[284,285,286],"thead",{},[287,288,289,292,295],"tr",{},[290,291],"th",{},[290,293,294],{},"主 logger",[290,296,49],{},[298,299,300,312,323,337,350,361,372],"tbody",{},[287,301,302,306,309],{},[303,304,305],"td",{},"吞吐量",[303,307,308],{},"~10–1000 筆\u002F秒",[303,310,311],{},"~10,000–1,000,000 筆\u002F秒",[287,313,314,317,320],{},[303,315,316],{},"資料型態",[303,318,319],{},"自由文字",[303,321,322],{},"結構化(Symbol、Bid、Ask、…)",[287,324,325,331,334],{},[303,326,327,328],{},"預設 ",[34,329,330],{},"MaxQueueSize",[303,332,333],{},"10,000",[303,335,336],{},"50,000",[287,338,339,344,347],{},[303,340,327,341],{},[34,342,343],{},"MaxOpenStreams",[303,345,346],{},"100",[303,348,349],{},"500",[287,351,352,357,359],{},[303,353,327,354],{},[34,355,356],{},"MaxBatchSize",[303,358,346],{},[303,360,349],{},[287,362,363,366,369],{},[303,364,365],{},"嚴重性級別",[303,367,368],{},"有(Trace … Fatal)",[303,370,371],{},"無 — 所有筆數等價",[287,373,374,377,380],{},[303,375,376],{},"Immediate flush",[303,378,379],{},"Error\u002FFatal 觸發",[303,381,382],{},"無 — 純非同步批次",[15,384,385],{},"若放同一個 dispatcher 會出現:",[96,387,388,391],{},[27,389,390],{},"報價暴增(~1M\u002F秒)塞爆佇列 → drop 掉 Error\u002FFatal 應用程式 log",[27,392,393],{},"應用程式 log immediate-flush 時,報價 dispatcher 延遲拉長",[15,395,396],{},"分開做就根本沒這種衝突。",[74,398,400],{"id":399},"_22-寫入路徑","2.2 寫入路徑",[79,402,405],{"className":403,"code":404,"language":84},[82],"[呼叫端執行緒 — WebSocket consumer、REST poller 等]\n       │  驗證(Symbol\u002FBucket 非空、Extras key 撞名、…)\n       │  入隊(struct QuoteRecord,零配置)\n       ▼\n[ConcurrentQueue\u003CQuoteRecord>]                ← QuoteOptions.MaxQueueSize\n       │  透過 SemaphoreSlim signal\n       ▼\n[獨立 dispatcher 執行緒]\n       │  排出批次 → QuoteFormatter.Format → AppendLine\n       ▼\n[QuoteFileStreamPool: per-(bucket, symbol) 持久化 FileStream]\n       │  LRU 上限 = QuoteOptions.MaxOpenStreams(預設 500)\n       │  檔系統非法字元自動 sanitize\n       ▼\n{baseDir}\u002F{LogPath}\u002F{yyyyMMdd}\u002F{QuotePath}\u002F{Bucket}_{Symbol}_Quote.{ext}\n",[34,406,404],{"__ignoreMap":87},[74,408,410],{"id":409},"_23-呼叫端的同步驗證","2.3 呼叫端的同步驗證",[15,412,413,416,417,420],{},[34,414,415],{},"LOG.Quote(...)"," 在入隊",[19,418,419],{},"之前","驗證 record:",[96,422,423,432,440,452],{},[27,424,425,428,429],{},[34,426,427],{},"Symbol"," 為 null 或空 → 在呼叫端拋 ",[34,430,431],{},"ArgumentException",[27,433,434,437,438],{},[34,435,436],{},"Bucket"," 為 null 或空 → ",[34,439,431],{},[27,441,442,443,193,446,449,450],{},"同時設定 ",[34,444,445],{},"Extras",[34,447,448],{},"ExtrasJson"," → ",[34,451,431],{},[27,453,454,456,457,37,460,37,463,466,467],{},[34,455,445],{}," 含保留 key(",[34,458,459],{},"bid",[34,461,462],{},"ask",[34,464,465],{},"last","、…)→ ",[34,468,431],{},[15,470,471,472,475,476,479,480,483],{},"這讓呼叫端可以用 ",[34,473,474],{},"try","\u002F",[34,477,478],{},"catch"," 攔截 programmer error。驗證失敗",[19,481,482],{},"絕不","會被靜默吞進 dispatcher。",[74,485,487],{"id":486},"_24-背壓drop-oldest-加上批次-callback","2.4 背壓:drop-oldest 加上批次 callback",[15,489,490,491,494],{},"drop-oldest 策略與主 logger 相同,但 ",[34,492,493],{},"OnDropped"," callback 簽章不同:",[79,496,500],{"className":497,"code":498,"language":499,"meta":87,"style":87},"language-csharp shiki shiki-themes github-light","\u002F\u002F 主 logger\npublic Action OnDropped { get; set; }              \u002F\u002F 每次 drop 觸發一次\n\n\u002F\u002F 報價 pipeline\npublic Action\u003Clong> OnDropped { get; set; }        \u002F\u002F 批次:參數 = 距上次 callback 新丟棄的數量\n","csharp",[34,501,502,510,516,523,529],{"__ignoreMap":87},[503,504,507],"span",{"class":505,"line":506},"line",1,[503,508,509],{},"\u002F\u002F 主 logger\n",[503,511,513],{"class":505,"line":512},2,[503,514,515],{},"public Action OnDropped { get; set; }              \u002F\u002F 每次 drop 觸發一次\n",[503,517,519],{"class":505,"line":518},3,[503,520,522],{"emptyLinePlaceholder":521},true,"\n",[503,524,526],{"class":505,"line":525},4,[503,527,528],{},"\u002F\u002F 報價 pipeline\n",[503,530,532],{"class":505,"line":531},5,[503,533,534],{},"public Action\u003Clong> OnDropped { get; set; }        \u002F\u002F 批次:參數 = 距上次 callback 新丟棄的數量\n",[15,536,537,538,541],{},"報價的 callback 收到的是",[19,539,540],{},"自上次 callback 以來新丟棄的筆數","(非累計),讓你可以在劇烈暴增時做高效的 metric 上報,不必每筆都呼叫 callback。",[74,543,545],{"id":544},"_25-收尾","2.5 收尾",[15,547,548,549,552,553,555],{},"跟主 logger 一樣 hook ",[34,550,551],{},"ProcessExit"," — 報價 pipeline 獨立 flush 並關閉自己的 stream。兩條 pipeline ",[19,554,271],{}," flush(無順序耦合),總收尾時間取兩者較慢的那條。",[66,557],{},[69,559,561],{"id":560},"_3-為何不用-thread-pool-dispatcher","3. 為何不用 thread-pool dispatcher?",[15,563,564,565,568,569,572],{},"兩條 pipeline 各自用一個獨立 ",[34,566,567],{},"Task.Run(...)"," dispatcher,",[19,570,571],{},"不","走 thread-pool worker。理由:",[96,574,575,581,591],{},[27,576,577,580],{},[19,578,579],{},"可預測的延遲",":獨立執行緒不會被使用者程式碼搶占。",[27,582,583,586,587,590],{},[19,584,585],{},"無鎖 FileStreamPool 存取",":只有一個執行緒寫入 pool,所以 ",[34,588,589],{},"FileStream"," 狀態在正常寫入路徑無需 lock(只在 shutdown \u002F disk-flush timer \u002F immediate-flush 交織時用 lock)。",[27,592,593,596,599,600,603,604,607],{},[19,594,595],{},"快取局部性",[597,598],"dispatcher",{}," 執行緒讓自己的 ",[34,601,602],{},"FileStreamPool"," slot、",[34,605,606],{},"StreamWriter"," buffer、dictionary 保持在 CPU cache 熱區。",[15,609,610,611,614,615,618,619,622],{},"代價:每個 ",[34,612,613],{},"(level, name)","(或 ",[34,616,617],{},"(bucket, symbol)",")的寫入順序有保證,但",[19,620,621],{},"跨 key 的寫入順序可能輕微錯位","(不同 key 可能被批次分組 flush)。這在 HFT tick 重建是可接受的 — 記錄裡的時間戳才是事實來源,不是檔案順序。",[66,624],{},[69,626,628],{"id":627},"_4-timestampcache","4. TimestampCache",[15,630,631,632,634,635,638,639,642,643,645,646,649],{},"背景 ",[34,633,214],{}," 每 1 ms 呼叫 ",[34,636,637],{},"DateTime.Now.Ticks"," 更新 ",[34,640,641],{},"volatile long _currentTicks","。呼叫端做一次 atomic read(~5 ns)而非每次 log 都付 ",[34,644,140],{}," syscall 成本(~80 ns on Windows,因為 ",[34,647,648],{},"GetSystemTimeAsFileTime"," + 時區轉換)。",[15,651,652,655,656,659,660,663,664,667,668,671,672,675],{},[19,653,654],{},"1 ms 精度上限",":若 ",[34,657,658],{},"TimeFormat"," 用了比 ",[34,661,662],{},".fff"," 更精細的精度(例如 ",[34,665,666],{},".ffffff"," 取 µs),最後幾位數字永遠是 ",[34,669,670],{},"0000",",除非你 opt-in ",[34,673,674],{},"HighPrecisionTimestamp = true","。",[74,677,679],{"id":678},"_41-highprecisiontimestamp-模式-v31","4.1 HighPrecisionTimestamp 模式 (v3.1+)",[15,681,682,683,686],{},"啟用後,cache 在每 1 ms 更新時同時記錄 ",[34,684,685],{},"Stopwatch.GetTimestamp()","。讀取時,呼叫端計算:",[79,688,691],{"className":689,"code":690,"language":84},[82],"actualTicks = cachedTicks + (Stopwatch.GetTimestamp() - cachedSwTimestamp) * (TimeSpan.TicksPerSecond \u002F Stopwatch.Frequency)\n",[34,692,690],{"__ignoreMap":87},[15,694,695,696,698],{},"這從 1 ms cache 重建出 sub-millisecond 精度,不必付 ",[34,697,140],{}," 成本。呼叫端 read 從 ~5 ns 增加到 ~30 ns。只在你需要 µs 級時間戳做 latency 分析或 tick-level 時序時才啟用。",[66,700],{},[69,702,704],{"id":703},"_5-內容來源","5. 內容來源",[96,706,707,719,729,739,749],{},[27,708,709,718],{},[710,711,715],"a",{"href":712,"rel":713},"https:\u002F\u002Fgithub.com\u002Fozakboy\u002FOzaLog\u002Fblob\u002Fmain\u002FOzaLog\u002FOzaLog\u002FCore\u002FAsyncLogHandler.cs",[714],"nofollow",[34,716,717],{},"OzaLog\u002FOzaLog\u002FCore\u002FAsyncLogHandler.cs"," — 主 pipeline dispatcher",[27,720,721,728],{},[710,722,725],{"href":723,"rel":724},"https:\u002F\u002Fgithub.com\u002Fozakboy\u002FOzaLog\u002Fblob\u002Fmain\u002FOzaLog\u002FOzaLog\u002FCore\u002FFileStreamPool.cs",[714],[34,726,727],{},"OzaLog\u002FOzaLog\u002FCore\u002FFileStreamPool.cs"," — 主 pipeline FileStream + LRU",[27,730,731,738],{},[710,732,735],{"href":733,"rel":734},"https:\u002F\u002Fgithub.com\u002Fozakboy\u002FOzaLog\u002Fblob\u002Fmain\u002FOzaLog\u002FOzaLog\u002FCore\u002FQuoteLogHandler.cs",[714],[34,736,737],{},"OzaLog\u002FOzaLog\u002FCore\u002FQuoteLogHandler.cs"," — 報價 pipeline dispatcher",[27,740,741,748],{},[710,742,745],{"href":743,"rel":744},"https:\u002F\u002Fgithub.com\u002Fozakboy\u002FOzaLog\u002Fblob\u002Fmain\u002FOzaLog\u002FOzaLog\u002FCore\u002FQuoteFileStreamPool.cs",[714],[34,746,747],{},"OzaLog\u002FOzaLog\u002FCore\u002FQuoteFileStreamPool.cs"," — 報價 pipeline FileStream",[27,750,751,758],{},[710,752,755],{"href":753,"rel":754},"https:\u002F\u002Fgithub.com\u002Fozakboy\u002FOzaLog\u002Fblob\u002Fmain\u002FOzaLog\u002FOzaLog\u002FCore\u002FTimestampCache.cs",[714],[34,756,757],{},"OzaLog\u002FOzaLog\u002FCore\u002FTimestampCache.cs"," — 快取時間戳",[760,761,762],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":87,"searchDepth":512,"depth":518,"links":764},[765,774,781,782,785],{"id":71,"depth":512,"text":72,"children":766},[767,768,770,771,772,773],{"id":76,"depth":518,"text":77},{"id":90,"depth":518,"text":769},"1.2 呼叫端成本(每次 LOG.Info_Log(...))",{"id":149,"depth":518,"text":150},{"id":186,"depth":518,"text":187},{"id":208,"depth":518,"text":209},{"id":234,"depth":518,"text":235},{"id":264,"depth":512,"text":265,"children":775},[776,777,778,779,780],{"id":275,"depth":518,"text":276},{"id":399,"depth":518,"text":400},{"id":409,"depth":518,"text":410},{"id":486,"depth":518,"text":487},{"id":544,"depth":518,"text":545},{"id":560,"depth":512,"text":561},{"id":627,"depth":512,"text":628,"children":783},[784],{"id":678,"depth":518,"text":679},{"id":703,"depth":512,"text":704},"ConcurrentQueue + 持久化 FileStream 池 + 快取時間戳 + drop-oldest 背壓 — 以及 v3.1+ 獨立的報價 pipeline。","md",{},"\u002Fzh-tw\u002Fasync-pipeline",{"title":6,"description":786},"zh-TW\u002Fasync-pipeline","-Nq1HxS-CFbevpbt8JdWBFQ0utPrz8CVPpPNpIKMZPU",{"matched":794,"target":795,"all":796},"fallback (.all() + manual match)","\u002Fzh-TW\u002Fasync-pipeline",[797,802,807,812,817,822,827,832,837,838,843,848,853,858],{"path":798,"id":799,"stem":800,"title":801},"\u002Fen\u002Fapi","content\u002Fen\u002Fapi.md","en\u002Fapi","API Reference",{"path":803,"id":804,"stem":805,"title":806},"\u002Fen\u002Fasync-pipeline","content\u002Fen\u002Fasync-pipeline.md","en\u002Fasync-pipeline","HFT Async Architecture",{"path":808,"id":809,"stem":810,"title":811},"\u002Fen\u002Fbenchmarks","content\u002Fen\u002Fbenchmarks.md","en\u002Fbenchmarks","Benchmarks",{"path":813,"id":814,"stem":815,"title":816},"\u002Fen\u002Fchangelog","content\u002Fen\u002Fchangelog.md","en\u002Fchangelog","Changelog",{"path":818,"id":819,"stem":820,"title":821},"\u002Fen\u002Fconfiguration","content\u002Fen\u002Fconfiguration.md","en\u002Fconfiguration","Configuration",{"path":823,"id":824,"stem":825,"title":826},"\u002Fen\u002Fgetting-started","content\u002Fen\u002Fgetting-started.md","en\u002Fgetting-started","Getting Started",{"path":828,"id":829,"stem":830,"title":831},"\u002Fen\u002Fmigration","content\u002Fen\u002Fmigration.md","en\u002Fmigration","Migration Guide",{"path":833,"id":834,"stem":835,"title":836},"\u002Fzh-tw\u002Fapi","content\u002Fzh-TW\u002Fapi.md","zh-TW\u002Fapi","API 參考",{"path":789,"id":5,"stem":791,"title":6},{"path":839,"id":840,"stem":841,"title":842},"\u002Fzh-tw\u002Fbenchmarks","content\u002Fzh-TW\u002Fbenchmarks.md","zh-TW\u002Fbenchmarks","效能對比",{"path":844,"id":845,"stem":846,"title":847},"\u002Fzh-tw\u002Fchangelog","content\u002Fzh-TW\u002Fchangelog.md","zh-TW\u002Fchangelog","版本歷史",{"path":849,"id":850,"stem":851,"title":852},"\u002Fzh-tw\u002Fconfiguration","content\u002Fzh-TW\u002Fconfiguration.md","zh-TW\u002Fconfiguration","配置選項",{"path":854,"id":855,"stem":856,"title":857},"\u002Fzh-tw\u002Fgetting-started","content\u002Fzh-TW\u002Fgetting-started.md","zh-TW\u002Fgetting-started","快速開始",{"path":859,"id":860,"stem":861,"title":862},"\u002Fzh-tw\u002Fmigration","content\u002Fzh-TW\u002Fmigration.md","zh-TW\u002Fmigration","升級指南",1778734456327]