91精品国产综合久久四虎久久_国产成人午夜高潮毛片_99er视频精品免费观看_2020亚洲熟女在线观看_日本女优人体写真_国内黄色毛片_年轻的老师中文版在线_丰满女邻居做爰_久久久久久精品成人免费图片

超越批處理的世界:流計算101
現(xiàn)代數(shù)據(jù)處理概念的高層次概覽
編者注:本文是關(guān)于數(shù)據(jù)處理演進(jìn)的兩部分系列博文的第一篇。這個系列主要關(guān)注流計算系統(tǒng)、無窮數(shù)據(jù)集和大數(shù)據(jù)的未來。

今日,流式數(shù)據(jù)處理是大數(shù)據(jù)里的很重要一環(huán)。原因有不少,其中包括:

  • 商業(yè)(競爭)極度渴望更快的數(shù)據(jù),而轉(zhuǎn)換成流計算則是一個好的方法來降低延遲。
  • 海量的、無窮數(shù)據(jù)集在現(xiàn)在的商業(yè)環(huán)境里變的越來越常見,而用專門設(shè)計來處理這樣數(shù)據(jù)的系統(tǒng)來應(yīng)對這些數(shù)據(jù)則更為容易。
  • 在數(shù)據(jù)到達(dá)時就對他們進(jìn)行處理能夠更加平均地把負(fù)載進(jìn)行均衡,取得更好的一致性和更可預(yù)測的計算資源消耗。

盡管業(yè)務(wù)驅(qū)動帶來了對流計算興趣的猛增,但絕大部分現(xiàn)有的流計算系統(tǒng)相比于批處理還不夠成熟,而后者已經(jīng)產(chǎn)生了很多令人激動的、多產(chǎn)的應(yīng)用。

作為從事海量大規(guī)模流計算系統(tǒng)的從業(yè)者(在谷歌工作超過五年,開發(fā)了MillWheelCloud Dataflow),我很高興能看到對于流計算的時代熱潮??紤]到批處理系統(tǒng)和流計算系統(tǒng)在語義上的不同,我也很愿意來幫助大家來理解流計算的方方面面,如它能做什么?怎么使用它最好?O’Reilly的編輯邀請我就我在2015 Strata+Hadoop World倫敦大會上的演講《對批處理說再見》寫一些文字的東西。這就是你所看到的這篇博文。因?yàn)樾枰采w的題目很多,我把他們分成了兩篇來寫:

  1. 流計算101:本文是第一篇,主要介紹一些基礎(chǔ)背景知識,澄清了一些技術(shù)術(shù)語。隨后會進(jìn)入技術(shù)細(xì)節(jié),關(guān)注時間域的內(nèi)容,并對常見的數(shù)據(jù)處理方法(包括批處理和流計算)做一個高層次的總覽。
  2. 數(shù)據(jù)流的模型:第二篇文章將會主要介紹Cloud Dataflow所使用的統(tǒng)一的批處理加流計算的模型,以及此模式應(yīng)用于多種數(shù)據(jù)集場景下的一個具體案例。之后,我會就現(xiàn)有的批處理和流計算系統(tǒng)做一個簡單的語義比較作為整篇的結(jié)論。

好的,下面會有很長的內(nèi)容,讓我們變成技術(shù)狂吧。

背景

開始我會介紹一些對我們理解后文的內(nèi)容很重要的背景知識。我會分三個主題來講:

  • 技術(shù)術(shù)語:為了能精確地講解復(fù)雜的題目,必須對相關(guān)術(shù)語做精確的定義。對于一些已經(jīng)被濫用的術(shù)語,我也會很明確地說明我用它們時的意思。
  • 能力:我會對一些反復(fù)感受到的流計算系統(tǒng)的缺點(diǎn)做一些評論。我也會提出我所認(rèn)為的數(shù)據(jù)處理系統(tǒng)的建造者應(yīng)采用的基本思路,基于這樣思路構(gòu)建的系統(tǒng)或可以應(yīng)對現(xiàn)代數(shù)據(jù)消費(fèi)者不斷增長的需求。
  • 時間域:我會介紹與數(shù)據(jù)處理相關(guān)的兩個主要時間域概念,解釋他們是如何相關(guān)的,并給出這兩個域所帶來的一些難題。

技術(shù)術(shù)語:什么是流計算

在繼續(xù)前行前,讓我們先解決一個重要問題:“什么是流計算?”。盡管文章到這里為止我也是在隨意的用著這個名詞。流計算這個詞有很多不同的意思,這就導(dǎo)致了關(guān)于到底什么是流計算或者到底流計算系統(tǒng)能做什么的誤解。正因如此,我愿意在這里先精確地定義它。

這個問題的難點(diǎn)在于很多術(shù)語本應(yīng)該被描述成他們是什么(例如無窮數(shù)據(jù)處理和近似結(jié)果處理),但卻被描述為他們過去是怎么被實(shí)現(xiàn)的(例如通過流計算執(zhí)行引擎)。缺乏精確的定義模糊了流計算真正的意思,在某些場合下它還被貼上了它的能力僅限于“流”的那些特征(如近似結(jié)果、推測結(jié)果處理)的標(biāo)簽。鑒于良好設(shè)計的流計算系統(tǒng)能與現(xiàn)有的批處理引擎一樣產(chǎn)生準(zhǔn)確、一致和可再現(xiàn)的結(jié)果,我更愿意把流計算非常明確地定義為:一種被設(shè)計來處理無窮數(shù)據(jù)集的數(shù)據(jù)處理系統(tǒng)引擎。僅此而已??紤]到完整性,需要強(qiáng)調(diào)的是這個定義不僅包含了真正的流計算實(shí)現(xiàn),也包括微批處理(micro-batch)的實(shí)現(xiàn)。

下面是與流計算相關(guān)的其他幾個經(jīng)常出現(xiàn)的術(shù)語,我也給出了更精確和清晰的解釋。希望業(yè)界能夠采納和使用。

  1. 無窮數(shù)據(jù)(Unbounded data:一種持續(xù)生成,本質(zhì)上是無窮盡的數(shù)據(jù)集。它經(jīng)常會被稱為“流數(shù)據(jù)”。然而,用流和批次來定義數(shù)據(jù)集的時候就有問題了,因?yàn)槿缜八?,這就意味著用處理數(shù)據(jù)的引擎的類型來定義數(shù)據(jù)的類型?,F(xiàn)實(shí)中,這兩類數(shù)據(jù)的本質(zhì)區(qū)別在于是否有限,因此用能體現(xiàn)出這個區(qū)別的詞匯來定性數(shù)據(jù)就更好一些。因此我更傾向于用無窮數(shù)據(jù)來指代無限流數(shù)據(jù)集,用有窮數(shù)據(jù)來指代有限的批次數(shù)據(jù)。
  2. 無窮數(shù)據(jù)處理(Unbounded data processing:一種發(fā)展中的數(shù)據(jù)處理模式,應(yīng)用于前面所說的無窮數(shù)據(jù)類型。盡管我本人也喜歡使用流式計算來代表這種類型的數(shù)據(jù)處理方式,但是在本文這個環(huán)境里,這個說法是誤導(dǎo)的。用批處理引擎循環(huán)運(yùn)行來處理無窮數(shù)據(jù)這個方法在批處理系統(tǒng)剛開始構(gòu)思的時候就出現(xiàn)了。相反的,設(shè)計完善的流計算系統(tǒng)則比批處理系統(tǒng)更能承擔(dān)處理有窮數(shù)據(jù)的工作。因此,為了清晰明了,本文里我就只用無窮數(shù)據(jù)處理。
  3. 低延遲,近似和/或推測性結(jié)果(Low-latency,approximate,and/or speculative results:這些結(jié)果和流處理引擎經(jīng)常關(guān)聯(lián)在一起。批處理系統(tǒng)傳統(tǒng)上不是設(shè)計來處理低延遲或推測性結(jié)果這個事實(shí)僅僅是一個歷史產(chǎn)物,并無它意。當(dāng)然,如果想,批處理引擎也完全能產(chǎn)生近似結(jié)果。因此就如其他的術(shù)語,最好是用這些術(shù)語是什么來描述這些結(jié)果,而不是用歷史上它們是用什么東西(通過流計算引擎)產(chǎn)生的來描述。

此后,文里任何地方我使用術(shù)語“流計算”,我就是指為無窮數(shù)據(jù)集所設(shè)計的處理引擎,僅此而已。當(dāng)我使用上述任何術(shù)語時,我就會明確說無窮數(shù)據(jù)、無窮數(shù)據(jù)處理,或低延遲,近似和/或推測性結(jié)果。這些也是我在Cloud Dataflow里使用的術(shù)語,我也建議業(yè)界去使用。

流計算的最夸張的限制

下面讓我們看看流計算系統(tǒng)能和不能做什么,重點(diǎn)是能做什么。在這個博文里我非常想讓讀者了解的一件事便是一個設(shè)計合理的流計算系統(tǒng)能做什么。長久以來,流計算系統(tǒng)被認(rèn)為是專為提供低延遲、不精確/推測性結(jié)果的某些特定市場而設(shè)計,并配合一個更強(qiáng)大的批處理系統(tǒng)來提供最終準(zhǔn)確的結(jié)果,如Lambda架構(gòu)(Lambda Architecture)。

對于不熟悉Lambda架構(gòu)的讀者,它的基本思想就是與批處理系統(tǒng)一起運(yùn)行流計算系統(tǒng),同時進(jìn)行幾乎一樣的計算。流計算系統(tǒng)提供低延遲、不準(zhǔn)確的結(jié)果(或是因?yàn)槭褂昧私扑惴?,或是因?yàn)榱饔嬎阆到y(tǒng)本身沒能提供足夠準(zhǔn)確的結(jié)果),而一段時間之后當(dāng)批處理計算完成,再給出正確的結(jié)果。這個架構(gòu)最初是由推特的內(nèi)森?馬茲(Natan Marz,Storm的發(fā)明人)提出的,結(jié)果在當(dāng)時非常成功。因?yàn)樵诋?dāng)時這是一個非常好的主意:流計算引擎在正確性方面還令人失望,而批處理引擎則是固有的緩慢和笨重,所以Lambda就給出了一套現(xiàn)成的解決方案。不幸的是,維護(hù)Lambda系統(tǒng)是一個麻煩:需要搭建、部署、維護(hù)兩套獨(dú)立的數(shù)據(jù)流管道系統(tǒng),并將兩個系統(tǒng)產(chǎn)生的結(jié)果在最后進(jìn)行某種程度的合并。

作為曾多年從事強(qiáng)一致流計算引擎的從業(yè)者,我認(rèn)為Lambda架構(gòu)的基本原理是有問題的。不出意外,我是杰伊?克雷普(Jay Krep)的博文《質(zhì)問Lambda架構(gòu)》的超級粉絲。很高興的,下面是反對雙模式運(yùn)行必要性的很好的陳述之一??死灼胀ㄟ^使用可重放的系統(tǒng)(如Kafka)作為流計算交匯點(diǎn)來解決重復(fù)性的問題,并更進(jìn)一步的提出“Kafka架構(gòu)”。此架構(gòu)的基本思路就是使用單套合理設(shè)計的引擎作為數(shù)據(jù)流管道來處理Lambda關(guān)注的任務(wù)。雖然我并不認(rèn)同這個概念需要一個名字,但是我完全支持這個觀點(diǎn)里的基本原理。

實(shí)話實(shí)說,我愿意更進(jìn)一步。我認(rèn)為設(shè)計良好的流計算系統(tǒng)的能力是批處理系統(tǒng)的功能的超集(包含關(guān)系)。或許排除增量的效益,未來將不再需要如今日的批處理系統(tǒng)1。Flink基于這個想法開發(fā)了一套完全流計算模式的系統(tǒng)(同時也支持批處理模式)的做法是值得稱贊的。我喜歡他們的工作!

上述思路的必然結(jié)果就是,結(jié)合了魯棒的框架、并不斷成熟的流計算系統(tǒng)可以充分應(yīng)對無窮數(shù)據(jù),也終將會把Lambda架構(gòu)送進(jìn)博物館。我認(rèn)為這個時刻已經(jīng)到來。因?yàn)槿绻胗昧饔嬎阍谂幚砩瞄L的領(lǐng)域打敗它,你只需要能實(shí)現(xiàn)兩件事:

1.正確性:這保證流計算能和批處理平起平坐。

本質(zhì)上,準(zhǔn)確性取決于存儲的一致性。流計算系統(tǒng)需要一些類似于checkpoint的方法來保證長時間的持久化狀態(tài)。克雷普斯(Kreps)在他的博文《為什么本地狀態(tài)化是流計算系統(tǒng)的一個基礎(chǔ)》討論了這個問題。同時流計算系統(tǒng)還必須針對系統(tǒng)宕機(jī)后還能保證數(shù)據(jù)一致性進(jìn)行精心的設(shè)計。幾年前,當(dāng)Spark剛剛出現(xiàn)在大數(shù)據(jù)領(lǐng)域的時候,它幾乎就是照亮了流計算黑暗面的燈塔(譯者注:因?yàn)镾park支持強(qiáng)一致)。在這之后,情況越來越好。但是還是有不少流計算系統(tǒng)被設(shè)計和開發(fā)成盡量不去支持強(qiáng)一致性。我實(shí)在是不能明白為什么“最多處理一次(at-most-once processing)”這樣的方式仍然存在。

再次強(qiáng)調(diào)一遍重點(diǎn):強(qiáng)一致性必須是“只處理一次(exactly-once processing)”,這樣才能保證正確性。只有這樣的系統(tǒng)才能追平并最終超越批處理系統(tǒng)。除非你對計算的結(jié)果是否正確并不介意,否則我還是請你放棄任何不能保證強(qiáng)一致性的流計算系統(tǒng)?,F(xiàn)有的批處理系統(tǒng)都保證強(qiáng)一致性,不會讓你在使用前去檢查計算結(jié)果是否正確。所以也不要浪費(fèi)你的時間在那些達(dá)不到這樣標(biāo)準(zhǔn)的流計算系統(tǒng)上。

如果你很想了解如何才能在一個流計算系統(tǒng)里提供強(qiáng)一致性,我建議你去讀一讀MillWheelSpark Streaming這兩個鏈接里的文章。兩篇文章都有相當(dāng)?shù)钠鶃斫榻B一致性。同時這個題目也有大量的文獻(xiàn)可供參考,所以這里就不再詳細(xì)討論了。

2. 時間推理的工具:這一點(diǎn)讓流計算超越批處理。

在處理無窮的、無序的、事件—時間分布不均衡的數(shù)據(jù)時,好的時間推理工具對于流計算系統(tǒng)是極其重要的?,F(xiàn)在越來越多的數(shù)據(jù)已經(jīng)呈現(xiàn)出上面的這些特征,而現(xiàn)有的批處理系統(tǒng)(也包括幾乎所有的流計算系統(tǒng))都缺少必要的工具來應(yīng)對這些特性帶來的難題。我會在這篇文章的余下部分和下一篇博文的大部分內(nèi)容里來關(guān)注于這個題目。

首先,我會介紹時間域里的一些重要概念。隨后我會深入介紹上面所說的無窮性、無序性和事件—時間分布不均衡這幾個特性。在本文剩下的部分里面,我會介紹常見的處理無窮和有窮數(shù)據(jù)的方法,包括批處理和流計算兩種系統(tǒng)。

事件時間和處理時間

為了能更好的說明無窮數(shù)據(jù)處理,就需要很非常清楚的理解時間域的內(nèi)容。任何一個數(shù)據(jù)處理系統(tǒng)里,都包含兩種典型的時間:

  • 事件時間(Event time):是指事件發(fā)生的時間。
  • 處理時間(Processing time):系統(tǒng)觀察到事件發(fā)生的時間。

不是所有的應(yīng)用場景都關(guān)心事件時間(如果你的場景不用,你的日子就好過多了),但大部分都關(guān)注。隨便舉幾個例子,比如一段時間里的用戶行為刻畫、計費(fèi)應(yīng)用和很多的異常檢測應(yīng)用。

理想化的情況下,事件時間和處理時間應(yīng)該總是相同的,即事件在它發(fā)生的同時就被處理了。但現(xiàn)實(shí)是殘酷的,處理時間和事件時間之間的偏移不僅是非零的,還經(jīng)常是由多種因素(如輸入源、處理引擎和硬件)的特性所共同組合成的一個可變方程??梢杂绊戇@個偏移的因素包括:

  • 共享的資源使用情況:比如網(wǎng)絡(luò)擁塞、網(wǎng)絡(luò)分區(qū)或共享環(huán)境里的CPU使用情況。
  • 軟件因素:如步分布系統(tǒng)邏輯、資源爭奪等。
  • 數(shù)據(jù)自身的特征:包括鍵分布、吞吐量變化、失序?qū)е碌淖兓ū热绯俗w機(jī)的旅客在飛機(jī)落地后把手機(jī)從飛行模型調(diào)整到正常模式,然后某些事件才發(fā)生)。

因此,如果把在實(shí)際系統(tǒng)里的事件時間和處理時間的關(guān)系畫出來,你很可能會得到類似圖1這樣的一些圖。

 

post01_fig01_timedomains-ea86183a3a9d99179a6e6f5c521ffdbf

圖1:時間域?qū)?yīng)的例子。圖里X軸代表系統(tǒng)里的事件時間,即事件發(fā)生的時間在某一點(diǎn)之前的所有事件,Y軸代表事件被處理的時間,即處理某事件數(shù)據(jù)時系統(tǒng)的時間。泰勒?阿克道制作

圖中,黑色的虛線的斜率是1,代表了理想的情況,即事件時間和處理時間是一樣的。紅色的線代表現(xiàn)實(shí)的情況。在這個例子里,系統(tǒng)在處理時間開始階段有一些延遲,隨后趨于理想狀況的同步,最后又產(chǎn)生了一些延遲。在理想情況和實(shí)際情況之間的水平距離則代表了處理時間和事件時間之間的偏移。本質(zhì)上,偏移就是由處理管道產(chǎn)生的延遲。

可見事件時間和處理時間之間的偏移并不是靜態(tài)的,這就意味著如果你關(guān)注的是事件時間(比如事件確切發(fā)生的時間點(diǎn)),在你處理數(shù)據(jù)數(shù)據(jù)時不能只看數(shù)據(jù)被觀察的時間(處理時間)。不幸的是,現(xiàn)在很多的流計算系統(tǒng)卻是按照處理時間設(shè)計來處理無窮數(shù)據(jù)的。為了應(yīng)對無窮數(shù)據(jù)集的無限的特性,這些系統(tǒng)一般都會提供一些把輸入數(shù)據(jù)按時間分片的機(jī)制。下面會仔細(xì)的討論分片機(jī)制,但其本質(zhì)都是按時間把數(shù)據(jù)切割成有限的塊。

如果你真正關(guān)心的是正確性并希望分析的是事件時間,你就不能用處理時間來定義數(shù)據(jù)的時間邊界(比如,用處理時間來分片),雖然現(xiàn)有的很多流計算系統(tǒng)是這么做的。鑒于事件時間和系統(tǒng)時間之間沒有一個一致的關(guān)聯(lián),某些數(shù)據(jù)可能會被錯誤的分到按處理時間分片的數(shù)據(jù)片里,盡管它們的事件時間并不屬于這個片。這可能是由于分布式系統(tǒng)內(nèi)在的延遲,或是由于很多數(shù)據(jù)源的在線/離線特性所造成的。但后果就是準(zhǔn)確性就無法得到保證。下面(包括下一篇博文)我會用一些案例來更詳細(xì)地討論這個問題。

糟糕的是,即便是用事件時間來分片,情況也不那么美好。對于無窮數(shù)據(jù),失序和偏移的變化給分片帶來了另外一個問題:完整性,即既然無法預(yù)測事件時間和處理時間之間的偏移,你怎么能確定你獲得了分片時間內(nèi)的所有數(shù)據(jù)?對很多的真實(shí)數(shù)據(jù),這個問題的答案是無法確定?,F(xiàn)有的大部分?jǐn)?shù)據(jù)處理系統(tǒng)都依賴某種完整性的想法,對于無窮數(shù)據(jù)而言這可能會帶來嚴(yán)重的困難。

我建議與其試圖去把無窮的數(shù)據(jù)梳理成有限的信息片,我們應(yīng)該設(shè)計這樣的工具(系統(tǒng)),他們可以讓我生活在這些復(fù)雜數(shù)據(jù)造成的不確定性中。當(dāng)新的數(shù)據(jù)到來時,我們可以抽取或者更新舊數(shù)據(jù)。任何系統(tǒng)都應(yīng)該能應(yīng)對這些不確定性,去方便的優(yōu)化完整性的概念,而不只是一個口頭上的必須。

在介紹我們是如何在Cloud Dataflow里面使用Dataflow模型去構(gòu)建這樣一個系統(tǒng)前,讓我們再講一些有用的背景知識:常見的數(shù)據(jù)處理模式。

數(shù)據(jù)處理模式

到目前為止,我們已經(jīng)獲得了足夠的背景知識來開始研究處理無窮數(shù)據(jù)和有窮數(shù)據(jù)的常見的核心模型。下面我會在批處理和流計算兩種引擎的環(huán)境下分別對兩種處理模式進(jìn)行介紹。這里我把微批處理和流計算歸為一種,因?yàn)樵谶@個層面上,他們沒有什么特別大的區(qū)別。

有窮數(shù)據(jù)處理

處理有窮數(shù)據(jù)是很簡單直接的,相信大家都比較的熟悉了。如下圖(圖2)所示,我們會先對左邊非結(jié)構(gòu)化的據(jù)進(jìn)行操作。使用某種分析引擎(通常是批處理類型的,但一個設(shè)計良好的流計算引擎也能做的一樣好),比如MapReduce,對這些數(shù)據(jù)做運(yùn)算。最后得到圖右邊所示的有規(guī)則的結(jié)構(gòu)化數(shù)據(jù),并獲得其內(nèi)在的價值。

 

post01_fig02_classicbatch-65147a08a7902873d46be1e5805c4bd4

圖2:用經(jīng)典的批處理引擎來處理有窮數(shù)據(jù)。左邊有限的非結(jié)構(gòu)化數(shù)據(jù)經(jīng)過一個數(shù)據(jù)處理引擎的處理,轉(zhuǎn)變成了右側(cè)的相應(yīng)的結(jié)構(gòu)化數(shù)據(jù)。泰勒?阿克道制作

盡管上述過程可能有無數(shù)多種變形的版本,但他們總體的模式是很簡單的。更有趣的是如何處理無窮數(shù)據(jù)集,下面就讓我們來看一看各種處理無窮數(shù)據(jù)的典型方法。我們從使用傳統(tǒng)的批處理引擎開始,最后以使用專門為無窮數(shù)據(jù)集而設(shè)計的系統(tǒng)(例如大部分流計算或微批處理系統(tǒng))來結(jié)束。

無窮數(shù)據(jù)批處理

批處理引擎盡管不是設(shè)計來處理無窮數(shù)據(jù)的,但從它們誕生開始就已經(jīng)被用來處理無窮數(shù)據(jù)集了??梢韵胂竦氖牵@個方法一般都涉及到把無窮數(shù)據(jù)分片成一系列有窮數(shù)據(jù)集,再用批處理引擎來處理。

固定的時間窗口

批處理引擎最常見的處理無窮數(shù)據(jù)集的方法就是重復(fù)性地把輸入數(shù)據(jù)按固定時間窗口分片,然后再把每個片當(dāng)作一個獨(dú)立有窮數(shù)據(jù)源進(jìn)行處理。特別是像日志這樣的數(shù)據(jù)源,事件被記錄進(jìn)有層級的文件系統(tǒng),而日志文件的名字就對應(yīng)于它們相應(yīng)的時間窗口。第一感就會用這個(固定窗口)方法。因?yàn)楸举|(zhì)上,在數(shù)據(jù)創(chuàng)建之前就已經(jīng)進(jìn)行了基于事件時間的排列來把數(shù)據(jù)寫入適當(dāng)?shù)臅r間窗口了。

然而在實(shí)際場景中,很多系統(tǒng)依然需要處理完整性的問題。例如,要是由于網(wǎng)絡(luò)原因某些事件寫入日志被延遲了,怎么辦?要是你的所有日志都要被傳送到一個通用的存儲區(qū)后才能被處理,怎么辦?要是事件是從移動設(shè)備上發(fā)送來的,怎么辦?這些場景都會需要對原先的處理方法進(jìn)行一定的修改(例如,延遲處理知道確保所有的時間片內(nèi)的事件都收集齊;或如果有數(shù)據(jù)晚到了,就對整個時間窗口內(nèi)的數(shù)據(jù)再處理一次)。

 

post01_fig03_batchfixedwindows-15968ed45b739377364dfbba39f3ff17

圖3:通過臨時的固定窗口,用經(jīng)典的批處理引擎來處理無窮數(shù)據(jù)。無窮數(shù)據(jù)集先通過固定的時間窗口被采集整理成有窮數(shù)據(jù),然后再通過重復(fù)運(yùn)行批處理引擎來處理。泰勒?阿克道制作

會話單元

更復(fù)雜的時間窗口策略可以是會話單元。這個方法把無窮數(shù)據(jù)進(jìn)行了更細(xì)的劃分,以方便批處理引擎來處理無窮數(shù)據(jù)。會話一般是被定義為活動(如某個特定用戶)的時間周期,以一段時間的不活躍來判定結(jié)束。使用批處理引擎來計算會話單元時,也經(jīng)常會碰到同一個會話被分到了兩個單元里,就如圖4里的紅色塊所示。這種情況是可以通過增加批次的大小來減少,但相應(yīng)的延遲也就會增加。另外一個選擇是增加額外的邏輯來把分到前一次運(yùn)行里的會話補(bǔ)進(jìn)本次運(yùn)算,但這會帶來額外的復(fù)雜度。

 

post01_fig04_batchsessions-d32b02f38b923052339bc6ddeb3d7c69

圖4:通過臨時的固定窗口并結(jié)合會話單元,用經(jīng)典的批處理引擎來處理無窮數(shù)據(jù)。無窮數(shù)據(jù)集先通過固定的時間窗口被采集整理成有窮數(shù)據(jù),并再進(jìn)一步劃分成不同的會話單元,然后再通過重復(fù)運(yùn)行批處理引擎來處理。泰勒?阿克道制作

使用傳統(tǒng)的批處理引擎來計算會話單元還不是最理想的方法。一個更好的方法則是用流的方式來構(gòu)建會話。下面我們就來討論。

無窮數(shù)據(jù)流計算

與基于批次的無窮數(shù)據(jù)處理方法的臨時特性相反的是,流計算系統(tǒng)天生就是為無窮數(shù)據(jù)開發(fā)的。如前文所說的,對于很多現(xiàn)實(shí)世界里的分布式數(shù)據(jù)源,你不僅要應(yīng)對數(shù)據(jù)的無窮性,還要處理下面兩個特性:

  • 對應(yīng)于事件時間的高度無序性。這意味著你需要某種程度上對事件做時間排序,如果你想按事件時間來做分析。
  • 時間漂移的變化性。這意味著在一個固定的Y時間的增量里你不能假定你可以看到大部分發(fā)生在對應(yīng)的X時間增量范圍內(nèi)的事件。

有多種可以處理有這樣特性的數(shù)據(jù)集的方法。我大致把它們分成四類:

  • 時間不可知(Time-agnostic)
  • 近似算法(Approximation algorithms)
  • 按處理時間做時間窗口分片
  • 按事件時間做時間窗口分片

下面就分別看一看這幾種方法。

時間不可知

時間不可知處理方法的使用場景是當(dāng)時間本質(zhì)上無關(guān),所有的邏輯僅關(guān)心數(shù)據(jù)本身而非時間。因?yàn)檫@種場景下只關(guān)心數(shù)據(jù)的到達(dá),所以并不需要流計算引擎來做特殊的支持,只要保證數(shù)據(jù)的傳遞就可以了。因此,本質(zhì)上現(xiàn)有的所有流計算系統(tǒng)都支持時間不可知場景(當(dāng)然對于準(zhǔn)確性有要求的用戶,還需要排除那些對強(qiáng)一致性的保證不支持的系統(tǒng))。批處理系統(tǒng)也能很好的支持時間不可知的無窮數(shù)據(jù)的應(yīng)用場景,只要簡單地把數(shù)據(jù)分成特定的有窮數(shù)據(jù)塊序列,再依次處理即可。下面會介紹一些實(shí)際的例子,但考慮到這種場景的處理比較的容易理解,我不會用過多的篇幅。

過濾(Filtering

非?;A(chǔ)的一個場景就是過濾。比如你要處理網(wǎng)站流量日志,想過濾掉不是來自某個特定域名的所有流量。那么就只要在數(shù)據(jù)到達(dá)的時候,檢查一下是不是來自那個特定的域,如果不是就丟棄掉。這種場景只依賴于數(shù)據(jù)元素本身,因此跟數(shù)據(jù)源是否是無窮的、失序的或是延遲有變化的就沒有關(guān)系了。

post01_fig05_filtering-678f1d7bcc5d4b4ce5dc056b7b5ab1a5

圖5:過濾無窮數(shù)據(jù)。不同類型的數(shù)據(jù)從圖左向右流進(jìn),被過濾后形成了只包含一種類型數(shù)據(jù)的統(tǒng)一數(shù)據(jù)集。泰勒?阿克道制作。

內(nèi)連接(Inner-Join

另外一種時間不可知的應(yīng)用場景就是內(nèi)連接(也叫哈希連接, Hash-Join)。當(dāng)連接兩個無窮數(shù)據(jù)源的時候,如果你只想得到在兩個數(shù)據(jù)源里都有的元素,那么這里的邏輯就跟時間無關(guān)了。在得到一個新的值后,只要簡單地把它持久的緩存起來,再一直等到另外一個源里也送來這個值,然后輸出即可。當(dāng)然有可能這里會需要一些垃圾回收機(jī)制來把那些從來沒出現(xiàn)的連接的元素給清理掉,這時候就跟時間有關(guān)了。但是對于那些不會出現(xiàn)不完全連接的場景,這個就沒什么了。

post01_fig06_innerjoin-4e92dd8c27b65f3702c9ef30b96fe20b

圖6:對兩個無窮數(shù)據(jù)源做內(nèi)連接。當(dāng)來自兩個數(shù)據(jù)源中都出現(xiàn)了相同的觀察值后,就進(jìn)行連接操作。泰勒?阿克道制作。

如果問題轉(zhuǎn)變成了外連接(Outer-Join),這就會出現(xiàn)之前討論的完整性的問題,即當(dāng)你看到連接的一邊,你怎么能知道另外一邊是否會出現(xiàn)?事實(shí)是,你不知道!這種情況下,你就必須采用某種超時機(jī)制,而這就又涉及到了時間。這里的時間本質(zhì)上就又是一種時間窗口分片,后面會仔細(xì)分析。

近似算法(Approximation algorithms

 

post01_fig07_approximations-c5ab7dca60514156a63a62a4d11efb24

圖7:對無窮數(shù)據(jù)源進(jìn)行近似計算。數(shù)據(jù)進(jìn)入后通過了一個復(fù)雜的近似算法的運(yùn)算,得到差不多你想要的結(jié)果。泰勒?阿克道制作。

第二類方法是近似算法,比如近似Top-N、流K-means聚類等。他們都以無窮數(shù)據(jù)為輸入,并計算出差不多你想要的結(jié)果。這些近似算法的好處是它們一般開銷小,而且就是設(shè)計來處理無窮數(shù)據(jù)的。缺點(diǎn)是這類方法數(shù)量有限,且實(shí)現(xiàn)都比較復(fù)雜,更新也難。近似的特性又使得它們不能廣泛應(yīng)用。

值得注意的是,這些算法一般都有一些時間域的特性(例如,某種衰退機(jī)制)。同時也因?yàn)檫@些方法一般都是在數(shù)據(jù)到達(dá)后就處理,所以它們基本用的都是處理時間。對于有些可以提供證明的錯誤范圍的算法,這一點(diǎn)很重要。因?yàn)槿绻惴軌蚶脭?shù)據(jù)到達(dá)的順序來預(yù)測錯誤范圍,那么即便是事件—時間漂移有變化,對于無窮數(shù)據(jù),這些錯誤都可以忽略不計了。請記住這一點(diǎn)。

近似算法本身是很有趣的話題,但它們本質(zhì)上也是時間不可知方法的一種(如果不考慮它們自身帶有的一些時間域特性)。而且這些算法也很直白容易理解和使用,這里就不再詳細(xì)地介紹了。

時間窗口分片

另外兩個無窮數(shù)據(jù)處理的方法都是時間窗口分片法的變形。在繼續(xù)前,我會花一些篇幅來講清楚時間窗口分片的具體含義是什么。分片就只是對應(yīng)于一個輸入數(shù)據(jù)源(無窮或有窮),按時間區(qū)間把數(shù)據(jù)分成有限個片,再來處理。圖8里面給出了三種分片的方式。

 

post01_fig08_windowing-01184b86c06843c382f6fc36316d81c0

圖8:不同的時間窗口分片機(jī)制。每個例子都包括三個輸入鍵對應(yīng)的數(shù)據(jù),并按不同的分片方式進(jìn)行了劃分,如窗口對齊的(對所有的鍵都適用)和窗口不對齊的(只對應(yīng)于某些鍵的)。泰勒?阿克道制作。
  • 固定窗口(Fixed windows):固定時間窗口按固定長度的時間來分片。如圖8所示,固定時間窗口典型地會對所有的數(shù)據(jù)集進(jìn)行劃分,也叫對齊的窗口。在某些情形下,可能會希望對不同的數(shù)據(jù)子集應(yīng)用不同的相位偏移,從而能讓分片的完整度更加的平均。這時就不再是對齊的窗口,而是非對齊的。
  • 滑動窗口(Sliding windows):滑動窗口是固定窗口的一個更一般化的形式。一般會定義兩個量,即窗口大小(時間長短)和滑動時間。如果滑動時間比窗口要小,則窗口會重疊;如果相等,這就是固定窗口;如果滑動時間比窗口大,就產(chǎn)生了一種特殊的數(shù)據(jù)采樣,也就是按時間只看數(shù)據(jù)集里的一部分子集的數(shù)據(jù)。類似于固定窗口,滑動窗口一般也是對齊的。出于性能考慮也會在某些情況下是非對齊的。需要注意的是,圖8里為了能表明滑動的性質(zhì)而沒有把每個窗口對應(yīng)到所有的鍵。實(shí)際情況里是都要對應(yīng)到的。
  • 會話單元(Sessions):是動態(tài)窗口的一種。一個會話是在不活躍時間段之間的一連串事件。這個不活躍時間一般是設(shè)定的比超時的時間要長。會話單元一般用來做用戶行為分析,即觀察在一個會話單元里用戶的一系列事件。會話單元的長度一般都沒法提前確定,完全取決于實(shí)際數(shù)據(jù)的情況。會話單元也是非對齊窗口的一個經(jīng)典案例,因?yàn)閷?shí)際情況下,不同子集數(shù)據(jù)的會話單元長度幾乎不可能一致地對齊。

上面討論的處理時間和事件時間是我們最關(guān)心的兩個概念2。在兩種情況下,時間窗口分片都可以使用。所以下面我們會詳細(xì)的來看看他們的區(qū)別。由于按處理時間做窗口分片是最常見的,我們就想講它吧。

按處理時間做時間窗口分片

post01_fig09_processingtimewindowing-ed2691e4602a28f0cbd395f90e2747cb

圖9:按處理時間對數(shù)據(jù)做固定窗口的分片。數(shù)據(jù)按照它們到達(dá)處理管道的時間(處理時間)順序地被分成固定窗口的片。泰勒?阿克道制作。

這種方式下,系統(tǒng)本質(zhì)上是把進(jìn)來的數(shù)據(jù)進(jìn)行緩存,達(dá)到一定的處理時間窗口再對緩存的數(shù)據(jù)進(jìn)行處理。例如,在一個5分鐘的固定窗口里,系統(tǒng)會按自己的系統(tǒng)時間緩存5分鐘內(nèi)的數(shù)據(jù),然后把這5分鐘內(nèi)的數(shù)據(jù)視為一片,交由流程的下一步做處理。

用處理時間做窗口分片有一下幾個好的特性:

  • 簡單。實(shí)現(xiàn)起來非常簡單明了,不用擔(dān)心數(shù)據(jù)失序和重排序。只要把數(shù)據(jù)緩存后按時交給下游就好了。
  • 判斷完整性很容易。因?yàn)橄到y(tǒng)能很清楚地知道某窗口里的數(shù)據(jù)是否已經(jīng)全部到到,所以數(shù)據(jù)的完整性很容易保證。這就意味著系統(tǒng)不用操心去處理那些“晚到的”數(shù)據(jù)了。
  • 如果你關(guān)心的是事件被觀察到后的信息,那么按處理時間做時間窗口分片就是你所需要的方法。很多監(jiān)控應(yīng)用場景都可以歸到這一類。比如你想獲得某大型網(wǎng)站的每秒訪問量,再通過監(jiān)控這個數(shù)量來判斷網(wǎng)站是否有服務(wù)中斷,這時候用處理時間做時間窗口分片就是絕佳的選擇。

盡管有這些好處,這個方法也有一個非常大的缺陷,即如果要處理的數(shù)據(jù)包含事件時間,而時間窗口需要反映的是數(shù)據(jù)的事件時間,那么就需要數(shù)據(jù)嚴(yán)格地按照事件時間來到達(dá)。不幸的是,在現(xiàn)實(shí)中這種按事件時間排好序到達(dá)的數(shù)據(jù)幾乎是沒有的。

舉一個簡單的例子,手機(jī)里的App收集上傳用戶的使用數(shù)據(jù)用于后期分析。當(dāng)手機(jī)離網(wǎng)一段時間后(比如無網(wǎng)絡(luò)連接、飛行模式等),這期間記錄的數(shù)據(jù)就需要等到手機(jī)接入網(wǎng)絡(luò)后才能上傳。這意味著處理時間和事件時間就會出現(xiàn)從分鐘到幾周不等的偏移。這時候用處理時間來做時間窗口分片就沒法對這樣的數(shù)據(jù)做出有效的處理并產(chǎn)生有用的信息。

另外一個例子是有些分布式的數(shù)據(jù)源在系統(tǒng)正常情況下可以提供按事件時間排序好(甚至非常好)的數(shù)據(jù)。但是當(dāng)系統(tǒng)的健康狀況得不到保證的時候,就很難保證有序性了。比如某全球業(yè)務(wù)需要處理采集自多個大洲的數(shù)據(jù)。而洲際間的網(wǎng)絡(luò)帶寬一般會受限(不幸的是,這很常見),這時就會出現(xiàn)突然間一部分?jǐn)?shù)據(jù)會比通常情況下晚到。再用按處理時間做分片,就不再能有效地反映數(shù)據(jù)實(shí)際發(fā)生時的情景了。這時窗口內(nèi)的數(shù)據(jù)就已經(jīng)是新舊混合的數(shù)據(jù)了。

這兩個例子里,我們真正想用的都是事件的發(fā)生時間,因?yàn)檫@樣才能保證數(shù)據(jù)到達(dá)的有序性。這就需要按事件時間進(jìn)行時間窗口分片。

按事件時間做時間窗口分片

當(dāng)你需要的是把事件按照發(fā)生時的時間分進(jìn)有限的塊內(nèi),你所需要的就是按事件時間做時間窗口分片。這是時間窗口分片的黃金標(biāo)準(zhǔn)。很不幸,目前絕大多數(shù)系統(tǒng)都不支持這樣的方法。盡管那些支持強(qiáng)一致的系統(tǒng)(比如Hadoop和Spark)經(jīng)過一些修改都可以支持這種方法。

下圖就給出了一個用一小時的固定窗口對無窮數(shù)據(jù)做按事件時間分片的演示。

post01_fig10_eventtimefixedwindows-1e30b3712f6189766a6ca7df44b4a2f9

圖10:按照事件時間用固定窗口分片。數(shù)據(jù)按照他們發(fā)生的時間收集。白色箭頭指出把那些事件時間屬于同一個分片的數(shù)據(jù)放到同一個窗口中去。泰勒?阿克道制作。

圖里的白色箭頭線對應(yīng)于兩個特別的數(shù)據(jù)。這兩個數(shù)據(jù)先后達(dá)到處理管道的時間和他們的事件時間并不一致。如果是按照處理時間來分片處理,但實(shí)際我們關(guān)心的是事件發(fā)生時的信息,那么計算出的結(jié)果就會不正確了。如此,用事件時間分片來保證事件時間計算的正確性就很完美了。

這個方法來處理無窮數(shù)據(jù)的另外一個好處就是你可以使用動態(tài)大小窗口,比如會話單元,而不用出現(xiàn)前面用批處理引擎來處理會話時會出現(xiàn)的會話被分到兩個窗口里(見圖4)。

post01_fig11_eventtimesessionwindows-f95374f3390bea838413c601ba39131f

圖11:按照事件時間做會話單元的窗口分片。數(shù)據(jù)按照他們發(fā)生的時間以及活動性被分到了不同的會話單元里。白色箭頭指出把那些事件時間屬于同一個分片的數(shù)據(jù)放到同一個窗口中去并按事件時間排序。泰勒?阿克道制作。

當(dāng)然,天下沒有免費(fèi)的午餐,按事件時間做時間窗口分片也不例外。由于窗口必須要比窗口的長度存在更長的時間(處理時間),所以它有兩個很大的缺點(diǎn)。

  • 緩存:由于窗口的存在時間要長,所以就需要緩存更多的數(shù)據(jù)。比較好的是,現(xiàn)在持久化已經(jīng)是整個數(shù)據(jù)處理系統(tǒng)資源里最便宜的部分(其他的是CPU、帶寬和內(nèi)存)。所以在一個設(shè)計良好的數(shù)據(jù)處理系統(tǒng)里,用強(qiáng)一致的持久化機(jī)制加上好的內(nèi)存緩存機(jī)制后,這個問題可能并沒想像的那么嚴(yán)重。另外不少的聚合運(yùn)算(比如求和、算平均)都不需要把所有的數(shù)據(jù)都緩存起來,只要把很小的中間結(jié)果緩存下來,并逐步累積就可以了。
  • 完整性:考慮到我們通常沒有好的方法來確定已經(jīng)收集到了一個窗口片里的所有數(shù)據(jù),我們怎么知道什么時候可以把窗口里的數(shù)據(jù)交給下游去處理?事實(shí)是,我們確實(shí)不知道。對很多輸入類型,系統(tǒng)可以給出一個相對合理準(zhǔn)確的完整性估計,比如在MillWheel系統(tǒng)里使用的水?。ㄔ诘诙┪睦飼懈嗟慕榻B)。但是對于絕對的準(zhǔn)確要求極度高的場景(比如計費(fèi)),唯一的選擇就是提供一個方法讓引擎來決定什么時候交出數(shù)據(jù),同時能讓系統(tǒng)不斷地修正結(jié)果。應(yīng)對窗口內(nèi)數(shù)據(jù)的完整性是一個非常有趣的題目,但最好是能在一個具體的例子里來討論說明,以后我會再介紹。

結(jié)論

哇噢!很多內(nèi)容是不是?如果你堅持讀到這里,你應(yīng)該得到表揚(yáng)。到這里我基本講了我想說的一半內(nèi)容。所以讓我們適當(dāng)?shù)氐耐R幌拢诶^續(xù)第二篇文章前,先回顧一下都講了什么。令人高興的是,第一篇的內(nèi)容比較的沉悶,而第二篇的內(nèi)容會相對有趣很多。

回顧

在上文中,我們已經(jīng):

  • 澄清了一些術(shù)語,特別是把流計算的內(nèi)涵局限到了執(zhí)行引擎。同時把流計算這一大概念下的一些術(shù)語都用了描述性的詞匯來做概念上的區(qū)別,如無窮數(shù)據(jù)、近似結(jié)果/推測結(jié)果等。
  • 評估了設(shè)計良好的批處理和流計算系統(tǒng)的相對能力,并提出流計算系統(tǒng)的能力是批處理系統(tǒng)的超集。而類似于Lambda架構(gòu)這樣的主張(認(rèn)為流計算比批處理要差一些)終會被成熟的流計算系統(tǒng)所取代。
  • 提出了對于流計算系統(tǒng)而言兩個重要的概念,實(shí)現(xiàn)這兩個概念就可以幫助流計算系統(tǒng)追趕并最終超越批處理系統(tǒng)。他們分別是正確性和時間推理工具。
  • 介紹了事件時間和處理時間的重要區(qū)別,指出了在分析數(shù)據(jù)何時發(fā)生的情況下,這些區(qū)別所帶來的困難。并提出了處理方法需要從主張完整性轉(zhuǎn)變到適應(yīng)數(shù)據(jù)隨時間變化的特性。
  • 分析了目前批處理系統(tǒng)和流計算系統(tǒng)對于有窮和無窮數(shù)據(jù)的主要的數(shù)據(jù)處理方法。并大致把處理無窮數(shù)據(jù)的方法分為四類:時間不可知型、近似算法、按處理時間做時間窗口分片和按事件時間做時間窗口分片。

下一篇

本篇為第二篇博文所要的具體的案例提供了必須的上下文。在第二篇博文里,我會介紹如下的主要內(nèi)容。

  • 從概念上來介紹我們是如何在Dataflow模型里把數(shù)據(jù)處理的過程按四個維度進(jìn)行劃分:什么(what)、哪里(where)、什么時候(when)以及如何做(how)。
  • 用一個簡單具體的數(shù)據(jù)集來詳細(xì)的介紹多個場景下的處理方法。重點(diǎn)強(qiáng)調(diào)使用Dataflow模型和各種API能處理的案例。通過這些例子我們可以更好的理解上面所說的事件時間和處理時間。同時也會介紹一個新的概念—水印(watermark)。
  • 針對博文里所介紹的重點(diǎn)特征來比較現(xiàn)有的數(shù)據(jù)處理系統(tǒng),從而能幫助大家更好的做選擇。同時也鼓勵大家對系統(tǒng)里有缺陷的地方進(jìn)行改進(jìn),從而能最終實(shí)現(xiàn)我的目標(biāo),即在大數(shù)據(jù)產(chǎn)業(yè)中完善現(xiàn)有的數(shù)據(jù)處理系統(tǒng),特別是流計算系統(tǒng)。

好,就到這里,下一篇見!

1我這里提出效益增量并不是流計算系統(tǒng)內(nèi)在的缺陷,而只是很多的流計算系統(tǒng)在設(shè)計時的決策的產(chǎn)物。批處理系統(tǒng)相對于流計算系統(tǒng)的效益增量更多的是因?yàn)楦玫拇虬鼨C(jī)制和更有效的排序傳輸機(jī)制?,F(xiàn)在的批處理系統(tǒng)開發(fā)了很多非常好的優(yōu)化機(jī)制,從而能用非常便宜的硬件資源來處理海量的吞吐。沒理由認(rèn)為我們不能把批處理系統(tǒng)里這些好的機(jī)制運(yùn)用到為無窮數(shù)據(jù)設(shè)計的系統(tǒng)里。這樣用戶就可以在高延遲、高效率的批處理系統(tǒng)與低延遲、低效率的流計算系統(tǒng)間做出靈活的選擇了。我們在Cloud Dataflow里面就是這么做的,即使用統(tǒng)一的模型同時提供批處理和流計算的兩個運(yùn)行環(huán)境。我們使用兩個運(yùn)行環(huán)境主要是因?yàn)槲覀兦『糜袃商转?dú)立的系統(tǒng)分別為他們特殊的應(yīng)用做了很好的優(yōu)化。從工程的角度長遠(yuǎn)來看,我喜歡把這兩個系統(tǒng)里的精華融合成單一的系統(tǒng),同時還能保留不同的效率層次以便讓用戶來靈活選擇。不過現(xiàn)在我們還沒有做。實(shí)話實(shí)說由于使用了統(tǒng)一的Dataflow模型,可能也沒有必要這么做。所以這種情況可能不會發(fā)生了。

2 如果你查看足夠多的學(xué)術(shù)論文或基于SQL的流計算系統(tǒng),你可能還會發(fā)現(xiàn)第三種時間窗口分片的機(jī)制:基于記錄的窗口分片,例如窗口的大小取決于記錄里元素的多少。然而,基于記錄的窗口分片本質(zhì)上也是一種基于處理時間的時間窗口分片。就是元素按照他們到達(dá)系統(tǒng)的時間來遞增的賦予時間戳。有鑒于此,我就不會在這里討論這種方式了(在第二篇文章里會有一個關(guān)于這種方式的例子)。

泰勒?阿克道(Tyler Akidau)

泰勒?阿克道是谷歌的一名高級軟件工程師。他是谷歌內(nèi)部流計算數(shù)據(jù)處理系統(tǒng)(如MillWheel)的技術(shù)帶頭人,在過去的五年里開發(fā)了多個大規(guī)模流計算數(shù)據(jù)處理系統(tǒng)。他熱忱地認(rèn)為流計算應(yīng)該是大規(guī)模海量計算的更通用的模型。他最喜歡的交通工具是貨運(yùn)自行車,可以把他的兩個小女兒放到拖兜里面。 泰勒?阿克道是谷歌的一名高級軟件工程師。他是谷歌內(nèi)部流計算數(shù)據(jù)處理系統(tǒng)(如MillWheel)的技術(shù)帶頭人,在過去的五年里開發(fā)了多個大規(guī)模流計算數(shù)據(jù)處理系統(tǒng)。他熱忱地認(rèn)為流計算應(yīng)該是大規(guī)模海量計算的更通用的模型。他最喜歡的交通工具是貨運(yùn)自行車,可以把他的兩個小女兒放到拖斗里面。

Three women wading in a stream gathering leeches (source: Wellcome Library, London).