發表文章

目前顯示的是 八月, 2015的文章

找出 node.js 中 block main loop 的程式

環境 Mac 用 node.js 0.10.36 Ubuntu (VM) 用 node.js 0.10.38問題描述node.js 的 single thread event loop 架構容易處理 I/O bound, 也避開 multi-thread 的各式問題, 但弱點是 main loop 被卡住了, 整個程式就不會動了。幸好找出錯誤原因的方法滿多的, 若確定是 CPU bound 而不是自己呼叫到 sync API, 也有很多成熟的解法。 在開發環境偵錯node.js 有很多種 profiling 的方法, 試過幾種作法, 我偏好這兩個作法:node-inspectorlook 兩者都會產生一個 web server 讓你可以連到指定的 port 用網頁作的 UI 看資料。也都不用傳資料到別人機器上。配合 stress test 可以找出潛在的 CPU bound。look 的介面比較簡單, 啟動速度較快。 在上線環境除錯但若 CPU bound 只有在上線後才會遇到, 就不容易找了。試了一些方法, 覺得用內建的 command line debugger 最管用。 node.js 已內建 debugger, 不需特殊設定就可以用 debugger attach 上線執行的程式。執行方法如下:$ kill -USR1 PID # PID 為 node.js pid $ node debug -p PID 然後執行 pause 暫停程式,接著執行 bt (backtrace) 才會有效。這樣就有線索找出 main loop 卡住的時候在作什麼了。 下面是一個完整的小例子:$ cat b.js function fib(n) { if (n <= 1) { return 1; } return fib(n - 1) + fib(n - 2); } setTimeout(function() { console.log(fib(100)); }, 10000); $ node b.js Hit SIGUSR1 - starting debugger agent. debugger listening on port 5858 在另一個 shell 用 debugger:$ ps auxw | grep node fca…

為什麼需要 async programming 以及相關技術的演進

幾種使用 async programming 的情境:GUI: 像是Web、iOS、Android apps。在 event-driven 的架構下用 callback 滿直覺的, 但是為了避免卡住 UI thread 而需要連續多個 non-blocking I/O 才能完成一件事時,寫起來就不直覺了。這時新增 thread 用 blocking I/O 寫起來比較直覺,但是不容易處理 thread-safety。 server: 像是 Web server、API server、reverse proxy。要能同時承受上千筆連線,必須用 non-blocking I/O。遇到 CPU 吃重的工作時,可以轉交給其它專門的 server (如向資料庫讀資料或用 Lucene 處理 text search)。 其它像是一些會用到多筆 http request 取資料的小程式,例如用 Gmail API 取出所有信件的附件,或是 stress test 的工具,或是 Web crawler,這些也會用 non-blocking I/O 加速程式 (或用 multi-thread/multi-process + blocking I/O 也行,最近才寫了個小工具 prunner.py)。 共通需求 用 non-blocking I/O 避免卡住服務或縮短完成時間。 用 single thread 降低實作複雜度。 為了滿足上述需求,主流的解法是用 single-thread event loop。但遇到 CPU bound 時還是得開新 thread。 注意這個共通需求對 Web page 和 server 是必要需求,但對其它情境只是「優先希望的作法」,Android、iOS 或 crawler 還是可以選擇新增 thread/process 使用 blocking I/O,然後自行承擔 multi-thread/multi-process 衍生的問題。non-blocking I/O 的問題single-thread event loop 只有幫忙監聽所有 non-blocking I/O 的動靜,用 callback 的方式通知結果。用 callback 處理 GUI 反應很自然,但是需要依序發數個 http request 的時候,在 callback 裡跳…

prunner.py: 連猴子都會用的加速框架

問題寫 python script 時有時會遇到 I/O bound (例如需發出大量 HTTP request) 或 CPU bound, 這時可用 multiprocessing 加速。但是自己寫 multi-process 有點麻煩:要留意 API 使用細節。 容易發生 race condition 或 dead lock。 程式較複雜時會切成多個階段作業, 有時會閒置部份 processes 而浪費了加速的機會。 辛苦寫好的平行架構不易重覆使用 (我至少重寫三次以上...泣)。解法prunner.py 是我寫好的框架, 中心思想是透過 task queue 提供容易重覆使用的架構, 還有協助減少閒置的 process。 以計算 sum( (i+1) * 2 ) 為例, 下面的程式會用很蠢的方式透過 10 個 processes 平行計算出結果: import prunner def begin(): prunner.get_dict()['sum'] = 0 prunner.post_task(init, range(2000)) def init(numbers): for i in numbers: prunner.post_task(add_one, i) def add_one(n): prunner.post_task(double, n + 1) def double(n): prunner.post_task(sum_up, n) prunner.post_task(sum_up, n) def sum_up(n): with prunner.global_lock(): prunner.get_dict()['sum'] += n def end(): print prunner.get_dict()['sum'] prunner.init(10, False, begin, end) prunner.start() 每個 function call 會在一個 process 上執行, 藉此增加使用到的 CPU。也可以盡情地用 blocking I/O, 反正有很多 process…

用 grequests 作 stress test

作了一個 C10K 的 server 後, 還要作 stress test 才能驗證 server 真的挺得住 C10K 的連線。試了幾套工具, 最後覺得 grequests 最好用 (用 gevent monkey patch 的 requests), 用法就這麼簡單: import grequests def exception_handler(request, exception): print "Request failed: %s" % exception url = 'SERVER_URL' rs = [grequests.post(url, data={...}) for i in range(10000)] rss = grequests.map(rs, size=1000, exception_handler=exception_handler) 這樣就寫好一個會同時產生 1k 個連線, 總共產生 10k 個連線的 client。因為是直接寫程式, 可以自訂各式各樣的內容, 測得出真正的效果, 不會被簡單的 cache 消掉絕大多數的 requeests。若要改用 get 就用 grequests.get(url) 產生 request, 詳細用法見 requests。 幾個測試情境和作法: Connection bound: 提升 size 數, 或多跑幾個 client CPU bound: 同上, 故意使用耗 CPU 的 API Memory bound: 依 API 的性質, 產生 1MB 或更大的資料, 由 data=... 傳過去 grequests.map() 不設 size 的話, 會盡可能產生最多連線, client 有可能自己先爆掉。依自己的網路環境和 server 狀況, 可試看看不同的值。 執行前記得先用 ulimit -n 提升 open file number, 不過要用 root 才能提升, 作法如下:$ sudo bash $ ulimit -n 50000 $ su YOUR_ID 這樣可以用一般帳號測試, 但保有 max open file number = 50k。 以下是我試過但不管用的方法, 憑印象寫下放棄使用的原因 (不見得是工具不好…

http 1.1 pipeline 失敗的原因

網路連線的基本指標是 latency 和 throughput。用 pipeline 同時提升 latency 和 throughput 還滿直覺的, 但在 http 1.1 上卻意外地以失敗收場, 所以花了點時間查一下為什麼。 What are the disadvantage(s) of using HTTP pipelining? 提到幾個問題: http 1.x 是 stateless 的 protocol, client 沒有能力知道 request 之間的 order (沒有流水號)。 因此, server 必須依 request order 回傳 response, 但是有些 server 實作錯了。 即使 server 作對了, 因為要照 order 回傳 response, server 可能需要暫存大量回應, 有被 DoS 的風險。 因為 response 要依順序回傳, client 有 head-of-line blocking 的風險, 比方說剛好先要一個大圖檔再要一個 JS 檔, 結果 JS 檔因此比較慢收到, 讓網頁比較慢取得較重要的檔案。 client 端的實作比想像中複雜, client 要先偵測 server 是否支援 pipeline 接著才能開始用 pipeline, 因此有些延遲。再者, 因為 client 不會真的只開一個連線, client 還需考慮如何分配 request 到不同連線作 pipeline。 因為 http 1.x 過往的限制, 網站早就依 domain 拆開回資料, client 也會同時發多個連線加速。結果是即使 pipeline 都作對了, 可能也沒賺到什麼。 此外, server 和 client 中間的 proxy 也可能不支援 (如擔心 DoS) 或是作錯, 又降低了可行的機率。 如果 http 裡面有含流水號, 由 http client library 自己匯合好再依順序回傳資料給上層程式 (像 TCP 那樣), 問題會少一些。設計協定時決定由 server 或 client 處理還挺關鍵的, 一但作錯決定, 協定普及後就很難改了。 HTTP/2SPDY 直接支援 multiplexing, 在一開始就作對了 pipeline。剩下的問題只有使用 TCP 造成 head-of…

gold 和 GNU ld 行為差異造成的問題

原因ToolChain/DSOLinking - Debian Wiki 介紹得很清楚, 主要的差異是處理 DT_NEEDED section 的行為不同。 對 program A 和 shared lib B 和 C 來說, 假設相依性如下: A 用到 B 和 C B 用到 C 照理說 link 的設定要如實反應出相依性。若 link 的設定如下: A: link B B: link C linker 是 GNU ld (BFD-based linker) 的時候會成功, 但用 gold 的時候會失敗。原因是 linker 會在目標的 DT_NEEDED section 記錄用到的 shared lib, 然後 GNU ld 預設會將 DT_NEEDED section 的值一併「往上傳」。所以 A link B 的時候不只會在 DT_NEEDED section 加上 B 的記錄, 還會取得 B 的 DT_NEEDED section 內的值, 所以沒寫 link C 也會有 C 的記錄。 這造成一個問題: 日後 B 沒有用到 C 的時候, 會因為升級 B 而造成 A 的 link error, 錯誤原因是沒有 link C。 解法是用 GNU ld link 時也要下 --no-add-needed 避免自動取用 shared lib 內的 DT_NEEDED section 的內容。這樣開發的時候才會注意到漏加 shared lib。 這種情況容易發生嗎? 就我個人的經驗來說, 比我預期的容易。比方說寫 GTK+ 也會用到 glib, 但 GTK+ 自己也有用 glib, 所以 GTK+ 的 DT_NEEDED section 有含 glib, 然後自己就忘了加。若用 GNU ld link, 沒加 glib 也會成功。或是很多 lib 用到 pthread, 自己也用到或加入的 static lib 間接用到, 然後忘了 link pthread。 改用 gold 為預設 linkergold link 的行為不但比較正確,速度比較快也比較省記憶體。可以的話, 改用 gold 作為系統預設 linker 比較省事。Mac OS X 已經用 gold 了, Ubuntu 12.04 要另裝 binutils-gold:$ sudo apt-ge…

PHP + JavaScript 提供 L10N 的文字

我希望的用法如下: PHP 和 JavaScript 共用同一套文字格式。 PHP 和 JavaScript 用同一套 API。 各國語系用字串 key 取出翻譯的內容。優點是可以隨意更改預設語系的內容; 缺點是網頁原始檔有一點難閱讀,無法像 L10N 前那樣直接看到內文。 選用的目標語言缺內容時,改用預設語言。 L10N 的文字存成 JSON, 方便用各種工具編輯。 函式庫相依性愈小愈好。 多數人使用的 gettext 不合我的需求, 查了一下沒找到順手的工具, 最後自己寫了一個。只有一個檔案 l10n.php, 程式不到 100 行。我不太熟 PHP 和 JavaScript, 可能寫得不怎麼道地。 用法是在網頁內 <?php require('l10n.php') ?>, 之後 PHP 和 JavaScript 都可以用 _m(KEY) 取得對到目前語言的字。可用 ?lang=LANG 切換語言,不然會以 Accept-Language 內 weight 最大的為目標語言。預設語言為 en。 API 還有一些可以改進的地方,像是支援替換文字內的變數,不用自己取出來後再替換;或是在 JSON 格式內加些輔助翻譯的描述。不過比較細微就是了,有這打算時再來參考 Chrome extension API 的作法, 還滿直覺的。

近期閱讀有物報告的心得

整理一下最近閱讀的心得,只是雜亂的片段想法。也許有天能串出更深的想法。一個預測未來的簡單方法原文: 有物報告 | 一個預測未來的簡單方法 用正面的方式描述是: 任何沒考慮 smartphone 的產業都會被衝擊。反過來說則是: 基於「不是人人都有 smartphone」的假設而發展出來的產業, 勢必會因 smartphone 普及而被衝擊。我比較喜歡後者的說法,前者聽起來比較空泛,後者有比較強的因果關係 (或著說比較強的推測)。 現行的例子是叫計程車。以前沒有 smartphone 要透過中間的服務人員對上雙方的位置, 幫忙湊合人和車。現在大家都有 smartphone, 定位和上網都很容易。建立平台的成本大幅下降,也不用透過服務人員配對。 去中介化的犧牲者 原文: 有物報告 | 親朋好友經濟、去中介化,與新中間人的誕生有物報告 | 去中介化的犧牲者 — 旅行社、出版社與書店推動聯合漲價的無用 能用數位傳遞的東西, 終將被網路取代。書店的定位必須轉型提供不同的服務,比方說營造店內的氣氛讓人想來逛, 像是誠品。或是經由個人品味挑選書本、寫評論分享自己挑選的書。 全程創業,販售服務原文: 有物報告 | 下一波傳統企業挑戰者 – 全程(end to end)創業有物報告 | 台灣最有野心的科技產品 – Gogoro有物報告 | 歡迎來到訂閱經濟時代有物報告 | 巨型城市襲來,百年福特的轉型策略:無人車與分享經濟有物報告 | [活動記錄] 賣鑽孔機是不完美的妥協,所以有了訂閱經濟販售長期的服務,金流比賣一次性的產品來得穩定。也因為更了解用戶,有機會撥掘和提供新服務取得更多獲利。理論上從上至下都自己來,使用者用起來會更順。像選用 iPhone,和 Apple TV 結合的很流暢 (其它 iCloud 之類的我沒有用, 不知效果如何)。Android phone 和 Miracast 則不容易作到流暢。什麼都自己來, 代價是要作更多事,分散火力。如今透過 smartphone 上搭配的「商城」 (App Store、Google Play), 傳遞產品和收取費用比以前簡單許多。至少降低了一個門檻。 Btw, 這段話讓我特別有感觸,注重在賣服務,而不是工具: 哈佛商學院教授 Theodore Levitt 說:「人們要的不是鑽孔機,而是牆上的洞。」他認為購買鑽孔機是種不完美的…