Elixir: 編程語言的未來
這篇文章談一談最近火爆的 Elixir,同時(shí)說一下對編程語言選擇的看法。同時(shí)作為 Erlang 發(fā)燒友,Elixir 不可不提。即使有了那么多編程語言 Elixir 也值得接觸。
Elixir 并不是一個(gè)最近出現(xiàn)的語言。但是近期 Elixir 的生態(tài)逐漸完善,越來越多的專家開始關(guān)注這門語言,并且 給予 Elixir 好評。
現(xiàn)在開始接觸 Elixir
一個(gè)小的 Elixir 例子
并行處理 JSON 字符串輸入,并且解析成可用的變量,計(jì)算每秒處理的速度并輸出。
https://github.com/doubaokun/exsample
用 entop 監(jiān)控 Elixir 應(yīng)用狀態(tài)
對編程語言選擇的一點(diǎn)看法
作為個(gè)語言發(fā)燒友,之前接觸過 Java、Erlang、Scala、PHP、JavaScript、C#、C、Python、Ruby 等一大堆各種風(fēng)格的編程語言。有人說,學(xué)那么多編程語言是想做”翻譯”嗎?其實(shí)事情并不那么簡單。
不同的語言背后是風(fēng)格截然不同的類庫群、技術(shù)堆棧、生態(tài)和工具鏈。不同的語言針對了不同類型的問題。某些語言解決某些問題的成本會比其他語言低非常多?;貧w本質(zhì),學(xué)習(xí)編程語言還是為了低成本高效的解決實(shí)際的業(yè)務(wù)問題。
個(gè)人喜歡的編程語言風(fēng)格
可以近實(shí)時(shí)更新變更
***不需要長時(shí)間編譯才能執(zhí)行、應(yīng)用啟動快。
Java、 C 編譯很慢,不適合頻繁修改的項(xiàng)目。但是 PHP 、Node.js 修改即可見,可以極大提高開發(fā)效率。***還能 hot-reload 就像很多前端工具一樣,只要源碼有一點(diǎn)變更,不需要刷新頁面自動反應(yīng)在瀏覽器中。Play framework 類似的自動加載功能也可以。
更進(jìn)一步,能夠在生產(chǎn)環(huán)境熱加載就更好了,更新代碼不影響用戶。針對這一點(diǎn),很多人樂了, PHP 默認(rèn)就是這樣的啊,部署后刷新 APC 緩存就可以實(shí)現(xiàn)。
這正是無狀態(tài)、短鏈接的 HTTP 應(yīng)用的優(yōu)勢,雖然隨之而來的是性能相對降低更多 TCP 的開銷,但是把問題變得簡單很多。但是很多其他語言做到這點(diǎn)就很難了,比如大部分 Java 應(yīng)用。
Elixir、Erlang 可以做到真正的任何情況下開著跑車換輪子。
關(guān)于熱加載,見另一篇文章:編程開發(fā)常用的熱加載工具。
支持并發(fā)執(zhí)行
人們更習(xí)慣順序執(zhí)行的思路,并且大部分業(yè)務(wù)邏輯都是順序執(zhí)行的。但是為了降低延遲、提高性能,***能在語言層面支持并發(fā)執(zhí)行。比如,一個(gè)操作開始還未返回結(jié)果,就可以開始另一個(gè)操作。
這樣調(diào)用遠(yuǎn)程 API 或者遠(yuǎn)程 RPC,耗時(shí)為最慢那一個(gè)操作的耗時(shí)。從這一點(diǎn)看,大部分流行語言都可以做到并發(fā)調(diào)用,但 PHP 難以做到。
輕量級執(zhí)行進(jìn)程或者線程
由于某些限制,某些業(yè)務(wù)邏輯不可避免的會因?yàn)榇罅坑?jì)算、網(wǎng)絡(luò)磁盤 IO 等占用一個(gè)執(zhí)行進(jìn)程或者線程。所以希望這個(gè)執(zhí)行體能夠盡量輕量級,很少的內(nèi)存占用,很快的啟動時(shí)間,很少的切換消耗,***能在 IO 執(zhí)行的時(shí)候自動讓出計(jì)算資源。
并發(fā)和并行
我們更多關(guān)注并發(fā),但是比較少關(guān)注并行。因?yàn)橥ㄟ^增加機(jī)器數(shù)量能抗住大量用戶的請求比節(jié)省機(jī)器更加簡單和迫切。
這也是很多互聯(lián)網(wǎng)公司動輒幾百臺上千臺服務(wù)器的現(xiàn)狀。用戶和請求量的多少由于業(yè)務(wù)邏輯的不同很難比較,只能比較機(jī)器數(shù)量了。
并發(fā)之進(jìn)程模型
PHP 既是典型的這種模式。曾經(jīng)見過某異步 PHP 框架 CS 高居不下,甚至比業(yè)務(wù)邏輯的 CPU 使用更高。
并發(fā)之線程模型
這種模型相對于進(jìn)程模型好了很多,因?yàn)榫€程比進(jìn)程輕量很多,創(chuàng)建、切換也快很多。
問題:線程和內(nèi)核線程的關(guān)系為多對多,內(nèi)核線程有限。能夠調(diào)度的用戶線程有限,無法充分利用多核性能。創(chuàng)建新線程消耗非常大。IO 阻塞無法釋放計(jì)算資源。
每個(gè) CPU 核心只能同時(shí)運(yùn)行一個(gè)線程,多個(gè)線程之間需要切換調(diào)度(CS)。如果是 CPU 密集類型的計(jì)算,沒有或者很少 IO 操作,***啟動 CPU 核心數(shù)量的線程。
但是如果有 IO 操作,比如 磁盤或者網(wǎng)絡(luò),多余 CPU 核心數(shù)的線程有效,因?yàn)?IO 操作的時(shí)候可以切換到其他線程執(zhí)行 CPU 操作。
并發(fā)之 Fork-join 輕量級進(jìn)程模型:
Fork-join 創(chuàng)建自己的進(jìn)程池來執(zhí)行小粒度的任務(wù)。
相對于 Erlang 那種真正的搶占式調(diào)度的 VM 實(shí)現(xiàn)或者操作系統(tǒng)的搶占式調(diào)度,F(xiàn)ork-join 模型非常簡單,也意味著相比之下效率相對低。
Fork-join 針對計(jì)算密集操作設(shè)計(jì),意味著無法告訴 F/J 框架你因?yàn)?IO 等待而釋放一會兒計(jì)算資源。所以,一般需要將異步 IO 操作放到另外的線程池,F(xiàn)J 只處理純計(jì)算。
基于 Scala 的 Akka 既是這種模型。所以,假如處理不當(dāng), Akka 的 Actor 很容易阻塞執(zhí)行線程,如果執(zhí)行線程池的線程被耗光,整個(gè)應(yīng)用將會僵死在那里。而 Erlang 則沒有這個(gè)問題。
并發(fā)之 Erlang 輕量級進(jìn)程模型:
VM 調(diào)度線程,將計(jì)算劃分為非常小的執(zhí)行單元??梢灾С址浅6嗟倪M(jìn)程。IO 阻塞可以自動釋放資源。真正的搶占式調(diào)度。
類型系統(tǒng)
靜態(tài)類型可以避免很多失誤。動態(tài)類型經(jīng)常會出現(xiàn)不可預(yù)期的結(jié)果,這有悖于 UNIX 風(fēng)格的最少意外原則。
動態(tài)類型可以讓開發(fā)更加快速。強(qiáng)靜態(tài)類型系統(tǒng)會執(zhí)行很快,比如 Java,但是也可以在有必要的時(shí)候使用反射,比如很多 RPC 框架的實(shí)現(xiàn) (當(dāng)然也有更進(jìn)一步的字節(jié)碼修改技術(shù))。
每個(gè)語言的類型系統(tǒng)都有自己的特點(diǎn)。
豐富的內(nèi)置結(jié)構(gòu)或者容器類
***能夠區(qū)分 Interface、Struct 和 Implementation。能夠以比較統(tǒng)一的模式輕松的定義自己需要的結(jié)構(gòu)體。
GC 系統(tǒng)
除非 Erlang 無可媲美的輕量級線程級別的 GC 。否則你要么需要記住和理解復(fù)雜的 GC 調(diào)優(yōu)參數(shù)、要么像 PHP 那樣過一段時(shí)間將進(jìn)程殺掉重來。
元編程和 DSL 擴(kuò)展性
在語法級別的抽象和封裝更能提高開發(fā)效率。Elixr 中如何實(shí)現(xiàn) DSL。
執(zhí)行速度和性能
這點(diǎn)和并發(fā)并行模式、以及多核利用率密切相關(guān)。
UNIX 風(fēng)格
簡單說就是模塊化;每個(gè)模塊完成相對單一的功能、復(fù)制任務(wù)由多個(gè)模塊組合完成。項(xiàng)目設(shè)計(jì)就像搭積木。不同模塊之前的輸入輸出可以拼接。
另外就是極簡風(fēng)格。
依賴和庫管理系統(tǒng)
這點(diǎn) Node.js npm 是***的依賴管理系統(tǒng)了,這樣導(dǎo)致了 Node.js 社群庫數(shù)量的爆發(fā)。因?yàn)閯?chuàng)建和發(fā)布一個(gè)庫實(shí)在是太容易了,找到需要的庫也非常簡單。
極大提高了開發(fā)效率。
打包和發(fā)布系統(tǒng)
***能打包成單一文件,容易分發(fā)和部署。比如 Java 應(yīng)用打包成 Fat Jar 包到處執(zhí)行,或者 Golang 那樣編譯成單一文件。
日志系統(tǒng)
真實(shí)的項(xiàng)目、日志非常重要。之前的文章已經(jīng)提到日志的重要性。所以好的內(nèi)置日志系統(tǒng)或者比較統(tǒng)一高效的日志模式非常重要。
***支持屏幕打印、寫文件等等功能。這可能不能算一個(gè)編程語言的特性了,要看這個(gè)語言是不是有很好的日志類庫。
Java 的 SLF 就是一個(gè)比較好的日志系統(tǒng)類庫。
工具鏈
項(xiàng)目構(gòu)建、編譯、測試工具比較完善。
比如 Java、Scala 項(xiàng)目的 maven、sbt 。Erlang 項(xiàng)目可以用 rebar ,但是 Elixir 的 mix 友好的很多倍。
另外一個(gè)好的 REPL 命令行工具非常重要,因?yàn)檫@可以方便的侵入應(yīng)用進(jìn)行調(diào)試,或者測試一條代碼片段。
比如 PHP 的 php -a, sbt, Clojure 的 lein, Erlang 的 erl, Elixir 的 iex 等等。
腳本執(zhí)行
這是腳本語言的一大優(yōu)勢。小任務(wù)可以立刻創(chuàng)建一個(gè)腳本執(zhí)行,而不需要修改、編譯部署現(xiàn)有運(yùn)行的應(yīng)用。
這點(diǎn)對于小任務(wù)非常重要。Erlang 和 Elixir 都支持這樣運(yùn)行,escript 或者 Elixir 腳本。比如,連接到集群,讀取狀態(tài)或者進(jìn)行一次性的數(shù)據(jù)操作,然后斷開。
測試系統(tǒng)
***有一種比較標(biāo)準(zhǔn)的單元測試模型。比如 Java、Node.js、Scala、Elixir 等等。
說了這么多,回到 Elixir。首先 Elixir 執(zhí)行和 Erlang 沒有任何差別。Erlang 的優(yōu)點(diǎn) Elixir 完全具備。比如:真正的搶占式調(diào)度;充分利用多核心并行執(zhí)行;Actor 模型;監(jiān)控樹;透明的分布式;
極其高的穩(wěn)定性;代碼的熱更新部署;函數(shù)式編程;模式匹配;等等。并且很多 Erlang 下工具也是可以直接使用。比如 entop 。
另外 Elixir 比 Erlang 多出的好處在于更加友好的語法、工具鏈、社群。很多之前寫 Ruby 的開始寫 Elixir,因?yàn)樗麄兊恼Z法最接近。
Elixir 的元編程 (meta programming) 和 DSL
1. quote 將代碼變成 AST,很像 LISP 語法。
quote do: 1 + 2
2. 執(zhí)行 quote 的表達(dá)式
Code.eval_quoted(quote do: 1 + 2)
3. unquote 用來引用 quote 范圍之外的變量
number = 13
Macro.to_string(quote do: 11 + unquote(number))
Elixir 成熟的工具鏈
mix:項(xiàng)目創(chuàng)建、構(gòu)建工具
hex:可以和 npm 媲美的依賴和庫管理系統(tǒng) https://hex.pm/
iex: 類似 Erlang 的 erl 既是 EPRL 又是應(yīng)用啟動命令
exunit: 單元測試工具
Tip: (Erlang\Elixir\Akka 都需要注意不要讓某一個(gè) Actor 的 Queue 積壓過多消息成為系統(tǒng)瓶頸。監(jiān)控 Queue 長度非常必要。)
Erlang、Elixir 一些有用的工具和庫
entop
gproc
:observer.start()
rebar
更多有用的 Elixir / Erlang 類庫,比如 Web 類庫 Phoenix Webframework,常見的 MySQL、Redis、MongoDB 類庫:
https://github.com/h4cc/awesome-elixir
很多有用的鏈接
https://en.wikipedia.org/wiki/Strong_and_weak_typing
https://en.wikipedia.org/wiki/Unix_philosophy
https://en.wikipedia.org/wiki/Fork%E2%80%93join_model
https://en.wikipedia.org/wiki/Preemption_(computing)
http://yosefk.com/blog/parallelism-and-concurrency-need-different-tools.html
https://www.subbu.org/blog/2012/03/async-io-and-fork-join
http://www.slideshare.net/zacharycox/dont-block
http://stackoverflow.com/questions/4436422/how-does-java-makes-use-of-multiple-cores
http://cabol.github.io/programming-languages-and-multicore-crisis/
http://www.neo.com/2013/08/27/two-days-with-elixir
http://learningelixir.joekain.com/profiling-elixir/
https://github.com/d0rc/exrabbit/blob/master/lib/exrabbit/dsl.ex
http://blog.jonharrington.org/elixir-and-docker/
http://www.smashingmagazine.com/2013/04/introduction-to-programming-type-systems/