[譯]從LinkedIn,Apache Kafka到Unix哲學

發表於2019-05-11
原文連結:
http://www.confluent.io/blog/apache-kafka-samza-and-the-Unix-philosophy-of-distributed-data
作者:Martin Kleppmann
譯者:傑微刊-macsokolot(@gmail.com) 
 
當我在為我的書做研究時,我意識到現代軟體工程仍然需要從20世紀70年代學習很多東西。在這樣一個快速發展的領域,我們往往有一種傾向,認為舊觀念一無是處——因此,最終我們不得不一次又一次地為同樣的教訓買單,這真艱難。儘管現在電腦已經越來越快,資料量也越來越大,需求也越來越複雜,許多老觀點至今仍有很大的用武之地。

在這篇文章中,我想強調一個陳舊的觀念,但它現在更應該被關注:Unix哲學(philosophy)。我將展示這種哲學與主流資料庫設計方式截然不同的原因;並探索如果現代分散式資料系統從Unix中學到了一些皮毛,那它在今天將發展成什麼樣子。


[img=LinkedIn,ApacheKafka,Unix]https://jf-bucket-public.oss-cn-qingdao.aliyuncs.com/jfperiodical/attached/image/20150911/134226037.jpg[/img]


特別是,我覺得Unix管道與ApacheKafka有很多相似之處,正是由於這些相似性使得那些大規模應用擁有良好的架構特性。但在我們深入瞭解它之前,讓我稍稍跟你提一下關於Unix哲學的基礎。或許,你之前就已經見識過Unix工具的強大之處——但我還是用一個大家相互都能討論的具體例子來開始吧。
假設你有一個web伺服器,每次有請求,它就向日志檔案裡寫一個條目。假設使用nginx的預設訪問日誌格式,那麼這行日誌可能看起來像這樣:

216.58.210.78 - - [27/Feb/2015:17:55:11 +0000] "GET /css/typography.css HTTP/1.1"
200 3377 "http://martin.kleppmann.com/" "Mozilla/5.0 (Macintosh; Intel Mac OS X
10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.115 Safari/537.36"

(這裡實際上只有一行,分成多行只是方便閱讀。)此行的日誌表明,伺服器在2015年2月27日17:55:11從客戶端地址216.58.210.78收到了一個檔案請求/css/typography.css。它還記錄了其他各種細節,包括瀏覽器的使用者代理字串。

許多工具能夠利用這些日誌檔案,並生成您的網站流量報告,但為了練練手,我們建立一個自己的工具,使用一些基本的Unix工具,在我們的網站上確定5個最熱門的網址。首先,我們需要提取出被請求的URL路徑,這裡我們可以使用awk.

awk並不知道nginx的日誌格式——它只是將日誌檔案當作文字檔案處理。預設情況下,awk一次只能處理一行輸入,一行靠空格分隔,使之能夠作為變數的空格分隔部件$1, $2, etc。在nginx的日誌示例中,請求的URL路徑是第7個空格分隔部件:


[img=LinkedIn,ApacheKafka,Unix]https://jf-bucket-public.oss-cn-qingdao.aliyuncs.com/jfperiodical/attached/image/20150911/-306847819.jpg[/img]


現在我們已經提取出了路徑,接下來就可以確定伺服器上5個最熱門的網站,如下所示:

這一系列的命令執行後輸出的結果是這樣的:

4189 /favicon.ico
3631 /2013/05/24/improving-security-of-ssh-private-keys.html
2124 /2012/12/05/schema-evolution-in-avro-protocol-buffers-thrift.html
1369 /
915 /css/typography.css


[img=LinkedIn,ApacheKafka,Unix]https://jf-bucket-public.oss-cn-qingdao.aliyuncs.com/jfperiodical/attached/image/20150911/-766589424.jpg[/img]


如果你並不熟悉Unix工具的話,上述命令看起來有點難懂,但它真的很強大。這幾條簡單的命令能夠在幾秒鐘內處理千兆位元組的日誌檔案,而且你可以根據需要,非常容易地更改分析內容。比如說,你現在想統計訪問次數最多的客戶端IP地址,而不是最熱門的那幾個網頁,只需更改awk的引數'{print $1}'

按需求使用這些組合命令awk, sed, grep, sort, uniq , xargs的話,海量資料分析能夠在幾分鐘內完成,其效能表現讓人出乎意料。這不是巧合,是Unix設計哲學的結果。


[img=LinkedIn,ApacheKafka,Unix]https://jf-bucket-public.oss-cn-qingdao.aliyuncs.com/jfperiodical/attached/image/20150911/-1246790721.jpg[/img]


Unix哲學就是一套設計準則, 在20世紀60年代末與70年代初,這些準則是在設計和實現Unix系統時才逐漸出現的。關於Unix哲學有非常多的闡述,但有兩點脫穎而出,由Doug McIlroy, Elliot Pinson 和Berk Tague在1978年描述如下:

1. 每個程式只做好一件事。如果有新的任務需求,那就編寫一個新的程式而不是在一箇舊的程式上加一個新的“功能”,使其越來越複雜。

2. 期望每個程式的輸出都能是其他程式的輸入,即使是未知的程式。

這些準則是能把各種程式連線成管道的基礎,而有了管道就能完成複雜的處理任務。這裡的核心思想就是一個程式不知道或者說不需要關心它的輸入是從哪裡來的,輸出要往哪裡去:可能是一個檔案,或者作業系統的其他程式,又或者是完全由某個開發者開發的程式。


[img=LinkedIn,ApacheKafka,Unix]https://jf-bucket-public.oss-cn-qingdao.aliyuncs.com/jfperiodical/attached/image/20150911/1962725447.jpg[/img]


作業系統附帶的工具都是通用的,但是它們被設計成能夠組合起來執行特定任務的較大的程式。

Unix的設計者遵循這種程式設計方法所帶來的好處有點像幾十年後出現的Agile 和DevOps的成果:指令碼與自動化,快速原型編碼(rapid prototyping),增量迭代,友好的測試(being friendly to experimentation),以及將大型專案分解成可控的模組。再加上CA的改變。(Plus ?a change.)


[img=LinkedIn,ApacheKafka,Unix]https://jf-bucket-public.oss-cn-qingdao.aliyuncs.com/jfperiodical/attached/image/20150911/1526264852.jpg[/img]


當你在shell裡為2個命令加上管道的標示符,那麼shell就會同時啟動這2個命令程式,然後將第一個程式處理的輸出結果作為第二個程式的輸入。這種連線機構由作業系統提供管道系統呼叫服務。
請注意,這種線性處理不是由程式本身來完成的,而是靠shell——這就使得每個程式之間是“鬆耦合”,這使得程式不用擔心它們的輸入從哪裡來,輸出要往哪裡去。


[img=LinkedIn,ApacheKafka,Unix]https://jf-bucket-public.oss-cn-qingdao.aliyuncs.com/jfperiodical/attached/image/20150911/956585814.jpg[/img]


1964年,管道(Pipe)由Doug McIlroy發明,他首次在Bell實驗室內部備忘錄裡將其描述為:“我們需要一些連線各種程式的方法就像花園裡的軟管——當它成為另一種必要的訊息資料時,需要擰入其他的訊息段。” Dennis Richie後來將他的觀點寫進了備忘錄


[img=LinkedIn,ApacheKafka,Unix]https://jf-bucket-public.oss-cn-qingdao.aliyuncs.com/jfperiodical/attached/image/20150911/1861235921.jpg[/img]


他們也很早就意識到程式間的通訊機制(管道)與讀寫檔案機制非常相似。我們現在稱之為輸入重定向(用一個檔案內容作為一個程式的輸入)和輸出重定向(將一個程式的結果輸出到一個檔案)。

Unix程式之所以能夠有這麼高的組合靈活性,是因為這些程式都遵循相同的介面:大多數程式都有一個資料輸入流(stdin)和兩個輸出流(stdout常規資料輸出流和stderr錯誤與診斷資訊輸出流)。


[img=LinkedIn,ApacheKafka,Unix]https://jf-bucket-public.oss-cn-qingdao.aliyuncs.com/jfperiodical/attached/image/20150911/176194616.jpg[/img]


程式通常除了讀stdin流和寫stdout流之外,它們還可以做其它的事,比如讀取和寫入檔案,在網路上通訊,或者繪製一個使用者介面。然而,該stdin/stdout通訊被認為是資料從一個Unix工具流向另一個的最主要的途徑。

其實,最令人高興的事莫過於任何人可以使用任意語言輕鬆地實現stdin/stdout介面。你可以開發自己的工具,只要其遵循這個介面,那麼你的工具能和其他標準工具一樣高效,並能作為作業系統的一部分。


[img=LinkedIn,ApacheKafka,Unix]https://jf-bucket-public.oss-cn-qingdao.aliyuncs.com/jfperiodical/attached/image/20150911/1347099347.jpg[/img]


舉個例子,當你想分析一個web伺服器的日誌檔案,或許你想知道來自每個國家的訪問量有多少。但是這個日誌並沒有告訴你國家資訊,只是告訴了你IP地址,那麼你可以通過IP地理資料庫將IP地址轉換成國家。預設情況下,你的作業系統並沒有附帶這個資料庫,但是你可以編寫一個將IP地址放進stdin流,將輸出國家放進stdout流的工具。

一旦你把這個工具寫好了,你就可以將它使用在我們之前討論過的資料處理管道里,它將會工作地很好。如果你已經使用了Unix一段時間,那麼這樣做似乎很容易,但是我想強調這樣做非常了不起:你自己的程式碼程式與作業系統附帶的那些工具地位是一樣的。

圖形使用者介面的程式和Web應用似乎不那麼容易能夠被擴充或者像這樣串起來。你不能用管道將Gmail傳送給一個獨立的搜尋引擎應用,然後將結果輸出到wiki上。但是現在是個例外,跟往常不一樣的是,現在也有程式能夠像Unix工具一樣能夠協同工作。


[img=LinkedIn,ApacheKafka,Unix]https://jf-bucket-public.oss-cn-qingdao.aliyuncs.com/jfperiodical/attached/image/20150911/-1842205947.jpg[/img]


換個話題。在Unix系統開發的同時,關係型資料模型就被提出來了,不久就演變成了SQL,被運用到很多主流的資料庫中。許多資料庫實際上仍在Unix系統上執行。這是否意味著它們也遵循Unix哲學?


[img=LinkedIn,ApacheKafka,Unix]https://jf-bucket-public.oss-cn-qingdao.aliyuncs.com/jfperiodical/attached/image/20150911/1953468426.jpg[/img]


在大多資料庫系統中資料流與Unix工具中非常不同。不同於使用stdin流和stdout流作為通訊渠道,資料庫系統中使用DB server以及多個client。客戶端(Client)傳送查詢(queries)來讀取或寫入伺服器上的資料,server端處理查詢(queries)併傳送響應給客戶端(Client)。這種關係從根本上是不對稱的:客戶和伺服器都是不同的角色。


[img=LinkedIn,ApacheKafka,Unix]https://jf-bucket-public.oss-cn-qingdao.aliyuncs.com/jfperiodical/attached/image/20150911/1015817052.jpg[/img]


Unix系統裡可組合性和擴充性是指什麼?客戶端(Clients)能做任何他們喜歡的事(因為他們是程式程式碼),但是DB Server大多是在做儲存和檢索資料的工作,執行你寫的任意程式碼並不是它們的首要任務。

也就是說,許多資料庫提供了一些方法,你能用自己的程式碼去擴充套件資料庫伺服器功能。例如,在許多關係型資料庫中,讓你自己寫儲存過程,基本的程式語言如PL / SQL(和一些讓你在通用程式語言上能執行程式碼比如JavaScript)。然而,你可以在儲存過程中所做的事情是有限的。

其他擴充方式,像某些資料庫支援使用者自定義資料型別(這是Postgres的早期設計目標),或者支援可插拔的資料引擎。基本上,這些都是外掛的介面:
你可以在資料庫伺服器中執行你的程式碼,只要你的模組遵循一個特定用途的資料庫伺服器的外掛介面。

這種擴充套件方式並不是與我們看到的Unix工具那樣的可組合性一樣。這種外掛介面完全由資料庫伺服器控制,並從屬於它。你寫的擴充套件程式碼就像是資料庫伺服器家中一個訪客,而不是一個平等的合作伙伴。


[img=LinkedIn,ApacheKafka,Unix]https://jf-bucket-public.oss-cn-qingdao.aliyuncs.com/jfperiodical/attached/image/20150911/1923472712.jpg[/img]


這種設計的結果是,你不能用管道將一個資料庫與另一個連線起來,即使他們有相同的資料模型。你也不能將自己的程式碼插入到資料庫的內部處理管道(除非該伺服器已明確提供了一個擴充套件點,如觸發器)。

我覺得資料庫設計是很以自我為中心的。資料庫似乎認為它是你的宇宙的中心:這可能是你要儲存和查詢資料,資料真正來源,和所有查詢最終抵達的唯一地方。你得到管道資料最近的方式是通過批量載入和批量傾倒(bulk-dumping)(備份)操作,但這些操作不能真正使用到資料庫的任何特性,如查詢規劃和索引。

如果資料庫遵循Unix的設計思想,那麼它將是基於一小部分核心原語,你可以很容易地進行結合,擴充和隨意更換。而實際上,資料庫猶如極其複雜,龐大的野獸。Unix也承認作業系統不會讓你真的為所欲為,但是它鼓勵你去擴充它,你或許只需一個程式就能實現資料庫系統想要實現所有的功能。


[img=LinkedIn,ApacheKafka,Unix]https://jf-bucket-public.oss-cn-qingdao.aliyuncs.com/jfperiodical/attached/image/20150911/133146643.jpg[/img]


在只有一個資料庫的簡單應用中,這種設計可能還不錯。

然而,在許多複雜的應用中,他們用各種不同的方式處理他們的資料:對於OLTP需要快速隨機存取,資料分析需要大序列掃描,全文搜尋需要倒排索引,用於連線的資料圖索引,推薦引擎需要機器學習系統,訊息通知需要的推送機制,快速讀取需要各種不同的快取表示資料,等等。

一個通用資料庫可以嘗試將所有這些功能集中在一個產品上(“一個適合所有”),但十有八九,這個資料庫不會為了某個特定的功能而只執行一個工具程式。在實踐中,你可以經常通過聯合各種不同的資料儲存和檢索系統得到最好的結果:例如,你可以把相同的資料並將其儲存在關聯式資料庫中,方便其隨機訪問,在Elasticsearch進行全文搜尋,在Hadoop中做柱狀格式分析,並以非規範化格式在memcached中快取。

完整內容點此檢視
回覆

相關文章