Tuesday, January 24, 2017

從 open-falcon 的 transfer 模組來理解 RabbitMQ

我初次看 RabbitMQ 的文件時,覺得這個東西似乎滿利害的。可是,心裡卻有很大的困擾,到底要怎麼用啊? 何時會需要呢?被這樣子的問題困擾了很久。直到最近重新去想 open-falcon transfer 的 source code 之後,才覺得心裡的迷團解開了~。

open-falcon 的 Transfer 的架構圖,大概是類似左圖。Agent 將 metric 送進 Transfer 之後,會將 metric 分別放到兩個 queue ,一個是準備之後要送到 Judge 的資料。另一個是準備之後要送到 Graph 的資料。 當 Transfer 要跟外部的模組連線時,它用了 connection pools 的設計,跟 Judge 還有 Graph 都建立了多條的連線。因為當 Agent 多的時候, Agent 上報的 metric 數量會非常多。要讓 queue 可以不會被塞爆,其中一種方式就是透過 connections pool 建立多條連線來加快 queue 清空的速度。


上述的 Transfer 從某些角度來說,可以說是應用了兩種 RabbitMQ 的使用情境:

(1) connections pool 可以視為是 Work queues 的應用情境。因為每一條 connection 其實也可以想象成是獨立的 consumer ,或是稱之為 worker 。

(2) Transfer 內部針對要送到不同的外部模組而分別建立兩個獨立的 queues 的作法,可以視為是 Publish/Subscribe 的應用情境。在這個情境裡, 負責連線到 Judge 和 Graph 的程式碼就可以視為是獨立的兩個 consumer 。

Sunday, January 15, 2017

choose the programming language

記憶中,大學的時候,趨勢科技舉辦的百萬程式大賽。系上有兩隊去參加。其中一隊是號稱為 F4 的隊伍,成員都是系上公認實作能力一等一的學長。另一隊則是比較像是雜牌軍。然而,參賽結果卻相當令人意外,F4 第一關就中箭落馬,雜牌軍則是過關斬將到最後一關,而且有得到名次。後來,聽雜牌軍隊講他們成功的要件: 用 C# 而不是 C++ 來實作。這樣子的作法會有下列的好處: (1) 實作起來比較快。 (2) 當遇到沒有寫好的部分,只是跳出 exception ,而不是程式直接 crash 。

上述的故事,在我日後的職場生涯偶爾想起。從這個故事中得到的啟發是:
「選擇正確的 technology stack 有時候對於軟體公司的影響,不輸給選擇好的員工。」

選擇 technology stack ,這個題目還是有點困難,先從「選擇 programming language 」這個題目開始吧。如果是小公司、小團隊、該如何選擇合適的 programming language 呢?從我的觀點的話,我會認為,應該優先考慮四件事:
  1. productivity
  2. learning curve
  3. core library & third party library
  4. performance
第一件事是「程式語言的生產力」,這件事很多人都已經知道了。然而,有寫過的人總是有基於自身經驗的認知,沒有寫過數種語言的人則主要依賴書本上的數據。也因此,這邊我用 lines of code 來比較,因為我傾向相信某個論文提出的觀點,「相同的時間內,程式設計師可以寫的總行數是類似的。」也因此程式語言所決定的生產力與它的 lines ratio 極為相關。下圖取自 code complete 一書,圖中的 lines ratio 是指實現同樣的功能為前提之下, lines ratio =  C語言需要行數除某一程式語言需要的行數。而 Paul Graham 在他的推荐 Lisp 文章 (Revenge of the Nerds) 中,表示 Lisp 的 lines ratio 一般而言是 7~ 10 ,如果面對複雜的問題、且使用進階的技巧,甚至可以到達 20 。
LanguageStatements ratio[35]Lines ratio[36]
C11
C++2.51
Fortran20.8
Java2.51.5
Perl66
Smalltalk66.25
Python66.5

第二件事是「學習曲線」。學習曲線陡峭的程式語言,也就意味著該語言的使用者會變少。對公司的立場來說,也就意謂著不容易找合適的工程師。舉個例子,就我自己學習 clojure 語言 (Lisp 的方言) 的經驗來說,確實不那麼容易學,因為很多觀念都不存在於過去的經驗。從 TIOBE 來看的話, Lisp 語言曾經一度是排行前 20 的程式語言,應該是因為學習曲線等因素,始終是小眾的語言。

第三件事是「函式庫支援」。這個要素其實跟「生產力」也類似。畢竟,如果主要選用的程式語言,沒有合適的函式庫支援時,往往軟體就是要透過使用多種程式語言來完成,又增加了複雜度和維護成本。

最後一件事才是「效能」。對於效能一事,我本來一直有一種誤解,覺得「程式語言之間」會有效能的差異。然而,一回我查了網路的文章,才發覺這個來自經驗的想法不太正確。程式語言和它的效能是脫勾的。效能是跟語言的實作 (implementation) 才有關系。所以,要探討 Java 語言的執行效能,重點要去探討 JVM 的實作。而一些流行的 scripting language 為何要特別設計出可以編譯到 JVM 的版本?重點是在於,做出可以在 JVM 執行的版本,比起直接將該語言做成 native machine code 省事太多,同時,JVM 又已經「夠快」了,有著幾乎不輸給 native machine code 的效能。這邊特別提一下,很多人也跟我一樣,對 JVM 有著誤解,以為 JVM 很慢,跟一般的 scripting language 所用的 engine 或是 VM 差不多的效能。這個也是來自經驗的誤解。 JVM 只是啟動比較慢,而這一點是有技巧可以克服的。

Sunday, January 8, 2017

[open-falcon] proc.num/name=crond

又被運維人員問了,我答不出來的問題:「為什麼明明用 ps 指令去查,只有一隻 crond 的 process ,但是看監控項對時間畫出的圖形,卻明顯的是 2、3~10 不等的數字。

運維人員把他下的指令給我看:
ps -ef | grep crond
我也看不出任何問題。


沒有辦法,只好去追 open-falcon 的源碼。在源碼中: agent 計算名稱為 $name 的進程個數的方式,是到 /proc/%d/status 找, status 檔裡頭 name 為 $name 的檔案個數。

於是我下了一個指令:
[root@foo ~]# find /proc -name status -exec grep -l crond {} \;
/proc/7811/task/7811/status
/proc/7811/status
/proc/8853/task/8853/status
/proc/8853/status

再下一個指令來看好了
[root@foo ~]# pstree | grep crond
     |-crond-+-crond---bash---sleep
     |       `-crond---sh---fwrsync-+-fwrsync---sleep

Saturday, January 7, 2017

函數式編程 (functional programming) -- 語言的雜交

不知道大家有沒有跟我一樣類似的經驗?想要了解 functional programming 的時候,會看到一些句子:「Lisp 語言受到 lambda calculus 的影響」「lambda calculus 是 Turing complete 。」「 Closures are poor man's objects and vice versa. 」然後,心裡冒出了許多困惑。

比方說:
(*) 那 lambda calculus 跟 functional programming 有什麼關系?為什麼需要去討論 turing complete ?
(*) 那既然 functional programming 也可以產生跟 object oriented programming 一樣的 object ,那為什麼要取名為 closures。

關於這一類的疑惑,在我學習 Lisp 語言的時候,終於漸漸地一一澄清了。而這一類困惑的源頭,我認為,來自於「語言的雜交」。近來的語言發展趨勢,許多主流語言都轉向了加入函數式編程的特色。然而,由於考慮向後相容性,加入函數式編程的特色的同時,並沒有移去本來屬於 OOP 的特色,於是就導致了用既有編程思想去理解函數式編程的困惑。

首先,C++ 語言、 Java 語言,這一類語言,本來是基於 Von Neumann 架構,由組合語言加以抽象化,而發展出來的語言設計,骨子裡,它的運算就是 state transfer 。如果把一些高級的特性一一拿去,讓它們退化,退化的過程也許是 Java 先退化成 C++ ,最後退化成 brainfuck 。這時候,我們來仔細檢驗一下 brainfuck ,真的就只能做 state transfer ,已經沒有任何函數呼叫的語法,而它依然是一種 Turing complete 的程式語言,換言之, Java 做得到的事,它也辦得到。

然而,如果某一種語言,比方說 Java 8 已經同時擁有完整的函數式語言的特性,它其實可以有另一種退化的方向, Java 先一一移除物件導向的特性,最後連 loop, goto, pointer 等特性也一一移除。最後也可以變成某種程式語言,只有 lambda calculus 的語言特性,於是,依照教科書上說的,它依然是一種 Turing complete 的程式語言。但是,沒有 goto, 沒有 loop ,這樣子的語言要怎麼做出計算呢?當函數式編程語言用極簡的形式來呈現時,我們更容易看出來,它計算的本質不是 state transfer ,而是 stateless transformations of immutable things, where things can be both data and functions, or even partially applied functions.

在理解了 lambda calculus 和 Turing machine 的計算本質性的差異之後,才能理解,同樣是函數,在 Von Neumann programming paradigm 之下的函數,它主要是用來做為程式碼模組化的功能,減少行數。真正的計算,發生在變數之間的狀態轉換。而在 functional programming paradigm 之下的函數,它必須是「無狀態的變換」 (stateless transformation) ,它本身就是真正的計算發生之所在。

更進一步,既然在 functional programming paradigm 之下,計算應該要用「無狀態的變換」來達成。程式語言自然要設計數種語法,其可以用來生成「無狀態的變換」。比方說: closure等語法適合生成容易客製化的 first order functions ,這些 first order function 又可以做為「變換」來使用 。既然要做「無狀態的變換」,程式語言自然要設計,可以達成高效能又可以無狀態的資料結構,例如: immutable data structure 。