原文連接:
http://www.confluent.io/blog/apache-kafka-samza-and-the-Unix-philosophy-of-distributed-data
做者:Martin Kleppmann
譯者:傑微刊-macsokolot(@gmail.com) javascript
當我在爲個人書作研究時,我意識到現代軟件工程仍然須要從20世紀70年代學習不少東西。在這樣一個快速發展的領域,咱們每每有一種傾向,認爲舊觀念一無可取——所以,最終咱們不得不一次又一次地爲一樣的教訓買單,這真艱難。儘管如今電腦已經愈來愈快,數據量也愈來愈大,需求也愈來愈複雜,許多老觀點至今仍有很大的用武之地。 css
在這篇文章中,我想強調一個陳舊的觀念,但它如今更應該被關注:Unix哲學(philosophy)。我將展現這種哲學與主流數據庫設計方式大相徑庭的緣由;並探索若是現代分佈式數據系統從Unix中學到了一些皮毛,那它在今天將發展成什麼樣子。 html
特別是,我以爲Unix管道與ApacheKafka有不少類似之處,正是因爲這些類似性使得那些大規模應用擁有良好的架構特性。但在咱們深刻了解它以前,讓我稍稍跟你提一下關於Unix哲學的基礎。或許,你以前就已經見識過Unix工具的強大之處——但我仍是用一個你們相互都能討論的具體例子來開始吧。
假設你有一個web服務器,每次有請求,它就向日志文件裏寫一個條目。假設使用nginx的默認訪問日誌格式,那麼這行日誌可能看起來像這樣: java
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" linux
(這裏實際上只有一行,分紅多行只是方便閱讀。)此行的日誌代表,服務器在2015年2月27日17:55:11從客戶端地址216.58.210.78收到了一個文件請求/css/typography.css。它還記錄了其餘各類細節,包括瀏覽器的用戶代理字符串。 nginx
許多工具可以利用這些日誌文件,並生成您的網站流量報告,但爲了練練手,咱們創建一個本身的工具,使用一些基本的Unix工具,在咱們的網站上肯定5個最熱門的網址。首先,咱們須要提取出被請求的URL路徑,這裏咱們可使用awk. web
awk並不知道nginx的日誌格式——它只是將日誌文件看成文本文件處理。默認狀況下,awk一次只能處理一行輸入,一行靠空格分隔,使之可以做爲變量的空格分隔部件$1, $2, etc。在nginx的日誌示例中,請求的URL路徑是第7個空格分隔部件: sql
如今咱們已經提取出了路徑,接下來就能夠肯定服務器上5個最熱門的網站,以下所示: shell
這一系列的命令執行後輸出的結果是這樣的: 數據庫
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
若是你並不熟悉Unix工具的話,上述命令看起來有點難懂,但它真的很強大。這幾條簡單的命令可以在幾秒鐘內處理千兆字節的日誌文件,並且你能夠根據須要,很是容易地更改分析內容。好比說,你如今想統計訪問次數最多的客戶端IP地址,而不是最熱門的那幾個網頁,只需更改awk的參數'{print $1}'
按需求使用這些組合命令awk, sed, grep, sort, uniq , xargs的話,海量數據分析可以在幾分鐘內完成,其性能表現讓人出乎意料。這不是巧合,是Unix設計哲學的結果。
Unix哲學就是一套設計準則, 在20世紀60年代末與70年代初,這些準則是在設計和實現Unix系統時才逐漸出現的。關於Unix哲學有很是多的闡述,但有兩點脫穎而出,由Doug McIlroy, Elliot Pinson 和Berk Tague在1978年描述以下:
1. 每一個程序只作好一件事。若是有新的任務需求,那就編寫一個新的程序而不是在一箇舊的程序上加一個新的「功能」,使其愈來愈複雜。
2. 指望每一個程序的輸出都能是其餘程序的輸入,即便是未知的程序。
這些準則是能把各類程序鏈接成管道的基礎,而有了管道就能完成複雜的處理任務。這裏的核心思想就是一個程序不知道或者說不須要關心它的輸入是從哪裏來的,輸出要往哪裏去:多是一個文件,或者操做系統的其餘程序,又或者是徹底由某個開發者開發的程序。
操做系統附帶的工具都是通用的,可是它們被設計成可以組合起來執行特定任務的較大的程序。
Unix的設計者遵循這種程序設計方法所帶來的好處有點像幾十年後出現的Agile 和DevOps的成果:腳本與自動化,快速原型編碼(rapid prototyping),增量迭代,友好的測試(being friendly to experimentation),以及將大型項目分解成可控的模塊。再加上CA的改變。(Plus ?a change.)
當你在shell裏爲2個命令加上管道的標示符,那麼shell就會同時啓動這2個命令程序,而後將第一個程序處理的輸出結果做爲第二個程序的輸入。這種鏈接機構由操做系統提供管道系統調用服務。
請注意,這種線性處理不是由程序自己來完成的,而是靠shell——這就使得每一個程序之間是「鬆耦合」,這使得程序不用擔憂它們的輸入從哪裏來,輸出要往哪裏去。
1964年,管道(Pipe)由Doug McIlroy發明,他首次在Bell實驗室內部備忘錄裏將其描述爲:「咱們須要一些鏈接各類程序的方法就像花園裏的軟管——當它成爲另外一種必要的消息數據時,須要擰入其餘的消息段。」 Dennis Richie後來將他的觀點寫進了備忘錄。
他們也很早就意識到進程間的通訊機制(管道)與讀寫文件機制很是類似。咱們如今稱之爲輸入重定向(用一個文件內容做爲一個程序的輸入)和輸出重定向(將一個程序的結果輸出到一個文件)。
Unix程序之因此可以有這麼高的組合靈活性,是由於這些程序都遵循相同的接口:大多數程序都有一個數據輸入流(stdin)和兩個輸出流(stdout常規數據輸出流和stderr錯誤與診斷信息輸出流)。
程序一般除了讀stdin流和寫stdout流以外,它們還能夠作其它的事,好比讀取和寫入文件,在網絡上通訊,或者繪製一個用戶界面。然而,該stdin/stdout通訊被認爲是數據從一個Unix工具流向另外一個的最主要的途徑。
其實,最使人高興的事莫過於任何人可使用任意語言輕鬆地實現stdin/stdout接口。你能夠開發本身的工具,只要其遵循這個接口,那麼你的工具能和其餘標準工具同樣高效,並能做爲操做系統的一部分。
舉個例子,當你想分析一個web服務器的日誌文件,或許你想知道來自每一個國家的訪問量有多少。可是這個日誌並無告訴你國家信息,只是告訴了你IP地址,那麼你能夠經過IP地理數據庫將IP地址轉換成國家。默認狀況下,你的操做系統並無附帶這個數據庫,可是你能夠編寫一個將IP地址放進stdin流,將輸出國家放進stdout流的工具。
一旦你把這個工具寫好了,你就能夠將它使用在咱們以前討論過的數據處理管道里,它將會工做地很好。若是你已經使用了Unix一段時間,那麼這樣作彷佛很容易,可是我想強調這樣作很是了不得:你本身的代碼程序與操做系統附帶的那些工具地位是同樣的。
圖形用戶界面的程序和Web應用彷佛不那麼容易可以被拓展或者像這樣串起來。你不能用管道將Gmail傳送給一個獨立的搜索引擎應用,而後將結果輸出到wiki上。可是如今是個例外,跟往常不同的是,如今也有程序可以像Unix工具同樣可以協同工做。
換個話題。在Unix系統開發的同時,關係型數據模型就被提出來了,不久就演變成了SQL,被運用到不少主流的數據庫中。許多數據庫實際上仍在Unix系統上運行。這是否意味着它們也遵循Unix哲學?
在大多數據庫系統中數據流與Unix工具中很是不一樣。不一樣於使用stdin流和stdout流做爲通訊渠道,數據庫系統中使用DB server以及多個client。客戶端(Client)發送查詢(queries)來讀取或寫入服務器上的數據,server端處理查詢(queries)併發送響應給客戶端(Client)。這種關係從根本上是不對稱的:客戶和服務器都是不一樣的角色。
Unix系統裏可組合性和拓展性是指什麼?客戶端(Clients)能作任何他們喜歡的事(由於他們是程序代碼),可是DB Server大可能是在作存儲和檢索數據的工做,運行你寫的任意代碼並非它們的首要任務。
也就是說,許多數據庫提供了一些方法,你能用本身的代碼去擴展數據庫服務器功能。例如,在許多關係型數據庫中,讓你本身寫存儲過程,基本的程序語言如PL / SQL(和一些讓你在通用編程語言上能運行代碼好比JavaScript)。然而,你能夠在存儲過程當中所作的事情是有限的。
其餘拓展方式,像某些數據庫支持用戶自定義數據類型(這是Postgres的早期設計目標),或者支持可插拔的數據引擎。基本上,這些都是插件的接口:
你能夠在數據庫服務器中運行你的代碼,只要你的模塊遵循一個特定用途的數據庫服務器的插件接口。
這種擴展方式並非與咱們看到的Unix工具那樣的可組合性同樣。這種插件接口徹底由數據庫服務器控制,並從屬於它。你寫的擴展代碼就像是數據庫服務器家中一個訪客,而不是一個平等的合做夥伴。
這種設計的結果是,你不能用管道將一個數據庫與另外一個鏈接起來,即便他們有相同的數據模型。你也不能將本身的代碼插入到數據庫的內部處理管道(除非該服務器已明確提供了一個擴展點,如觸發器)。
我以爲數據庫設計是很以自我爲中心的。數據庫彷佛認爲它是你的宇宙的中心:這多是你要存儲和查詢數據,數據真正來源,和全部查詢最終抵達的惟一地方。你獲得管道數據最近的方式是經過批量加載和批量傾倒(bulk-dumping)(備份)操做,但這些操做不能真正使用到數據庫的任何特性,如查詢規劃和索引。
若是數據庫遵循Unix的設計思想,那麼它將是基於一小部分核心原語,你能夠很容易地進行結合,拓展和隨意更換。而實際上,數據庫猶如極其複雜,龐大的野獸。Unix也認可操做系統不會讓你真的隨心所欲,可是它鼓勵你去拓展它,你或許只需一個程序就能實現數據庫系統想要實現全部的功能。
在只有一個數據庫的簡單應用中,這種設計可能還不錯。
然而,在許多複雜的應用中,他們用各類不一樣的方式處理他們的數據:對於OLTP須要快速隨機存取,數據分析須要大序列掃描,全文搜索須要倒排索引,用於鏈接的數據圖索引,推薦引擎須要機器學習系統,消息通知須要的推送機制,快速讀取須要各類不一樣的緩存表示數據,等等。
一個通用數據庫能夠嘗試將全部這些功能集中在一個產品上(「一個適合全部」),但十有八九,這個數據庫不會爲了某個特定的功能而只執行一個工具程序。在實踐中,你能夠常常經過聯合各類不一樣的數據存儲和檢索系統獲得最好的結果:例如,你能夠把相同的數據並將其存儲在關係數據庫中,方便其隨機訪問,在Elasticsearch進行全文搜索,在Hadoop中作柱狀格式分析,並以非規範化格式在memcached中緩存。
當你須要整合不一樣的數據庫,缺少Unix風格的組合性對於整合來講是一個嚴重的限制。(我已經完成了從Postgres中用管道將數據輸出到其餘應用程序,但這還有很長的路要走,直到咱們能夠簡單地用管道將任一數據庫中的數據導出到其餘數據庫。)
咱們說Unix工具可組合性是由於它們都實現相同的接口——stdin,stdout和stderr——它們都是文件描述符,即:能夠像文件同樣讀寫的字節流。這個接口很簡單以至於任何人均可以很容易地實現它,但它也足夠強大,你可使用它作任何東西。
由於全部的Unix工具實現相同的接口,咱們把它稱爲一個統一的接口。這就是爲何你能夠絕不猶豫地用管道將gunzip數據輸出WC中去,即便開發這兩個工具的做者可能歷來沒有交流過。這就像樂高積木,它們都用相同的模式實現節位和槽位,讓你堆樂高積木的時候可以爲所欲爲,不用管它們的形狀,大小和顏色。
Unix文件描述符的統一接口並不只僅適用於輸入和輸出的過程,它是一個很是普遍的應用模式。若是你在文件系統上打開一個文件,你將獲得一個文件描述符。管道和Unix套接字提供一個文件標識符,這個標示符可以在同一機器上爲其它程序提供一個通訊通道。在Linux中,/dev下的虛擬文件是設備驅動程序的接口,因此你在這裏面能夠跟USB端口甚至GPU打交道。/proc下的虛擬文件是內核的API,可是它是以文件形式存在,你可使用相同的工具,以普通文件的方式訪問它。
即便是經過TCP鏈接到另一臺機器上的程序也是一個文件描述符,雖然BSD套接字API(最經常使用來創建TCP鏈接)不像Unix。Plan 9顯示,即便是網絡能夠被徹底集成到相同的統一接口中去。
完整內容進入此連接查看:http://www.jointforce.com/jfperiodical/article/1036?f=jf_tg_bky