Go 語言很好很強大,但我有幾個問題想吐槽

Go 是一門非常不錯的編程語言。然而,我在公司的Slack 編程頻道中對Go 的抱怨卻越來越多(猜到我是做啥了的吧?),因此我認為有必要把這些吐槽寫下來並放在這裡,這樣當人們問我抱怨什麼時,我給他們一個鏈接就行了。

image

先聲明一下,在過去的一年裡,我大量地使用Go語言開發命令行應用程序、scclc和API。其中既有供客戶端調用的大規模API,也有即將在https://searchcode.com/使用的語法高亮顯示器

我這些批評全部是針對Go 語言的。但是,我對使用過的每種語言都有不滿。我非常贊同下面的話:

“世界上只有兩種語言:人們抱怨的語言和沒人使用的語言。” —— Bjarne Stroustrup

1 不支持函數式編程

我並不是一個函數式編程狂熱者。說到Lisp 語言,我首先想到的是語言障礙。

這可能是Go 語言最大的痛點了。與大部分人不同,我不希望Go 支持泛型,因為它會為多數Go 項目帶來不必要的複雜性。我希望Go 語言支持適用於內置切片和Map 的函數式方法。切片和Map 具有通用性,並且可以容納任何類型,從這個意義上講,它們已經非常神奇。在Go 語言中只有利用接口才能實現類似效果,但這樣一來將喪失安全性和速度。

例如,請考慮下面的問題。

給定兩個字符串切片,找出二者都包含的字符串,並將其放入新的切片以備後用。

複製代碼

existsBoth := []string{}
for _, first := range firstSlice {
for _, second := range secondSlice {
if first == second {
existsBoth = append(existsBoth, proxy)
break
}
}
}

上面是一個用Go 語言實現的簡單方案。當然還有其它方法,比如借助Map 來減少運行時間。這裡我們假設內存足夠用或者切片都不太大,同時假設優化運行時間帶來的複雜性遠超收益,因此不值得優化。作為對比,使用Java 流和函數式編程把相同的邏輯重寫如下:

複製代碼

var existsBoth = firstList.stream()
.filter(x -> secondList.contains(x))
.collect(Collectors.toList());

上面的代碼隱藏了算法的複雜性,但是,你更容易理解它實際做的事情。

與Go 代碼相比,Java 代碼的意圖一目了然。真正靈活之處在於,添加更多的過濾條件易如反掌。如果使用Go 語言添加下面例子中的過濾條件,我們需要在嵌套的for 循環中再添加兩個if 條件。

複製代碼

var existsBoth = firstList.stream()
.filter(x -> secondList.contains(x))
.filter(x -> x.startsWith(needle))
.filter(x -> x.length() >= 5)
.collect(Collectors.toList());

有些借助go generate 命令的項目可以幫你實現上面的一些功能。但是,如果缺少良好的IDE 支持,抽取循環中的語句作為單獨的方法是一件低效又麻煩的事情。

2 通道/ 並行切片處理

Go 通道通常都很好用。但它並不能提供無限的並發能力。它確實存在一些會導致永久阻塞的問題,但這些問題用競爭檢測器能很容易地解決。對於數量不確定或不知何時結束的流式數據,以及非CPU 密集型的數據處理方法,Go 通道都是很好的選擇。

Go 通道不太適合併行處理大小已知的切片。

多線程編程、理論和實踐

image

幾乎在其它任何語言中,當列表或切片很大時,為了充分利用所有CPU 內核,通常都會使用並行流、並行Linq、Rayon、多處理或其它語法來遍歷列表。遍歷後的返回值是一個包含已處理元素的列表。如果元素足夠多,或者處理元素的函數足夠複雜,多核系統會更高效。

但是在Go 語言中,實現高效處理所需要做的事情卻並不顯而易見。

一種可能的解決方案是為切片中的每個元素都創建一個Go 例程。由於Go 例程的開銷很低,因此從某種程度上來說這是一個有效的策略。

複製代碼

toProcess := []int{1,2,3,4,5,6,7,8,9}
var wg sync.WaitGroup
for i, _ := range toProcess {
wg.Add(1)
go func(j int) {
toProcess[j] = someSlowCalculation(toProcess[j])
wg.Done()
}(i)
}
wg.Wait()
fmt.Println(toProcess)

上面的代碼會保持切片中元素的順序,但我們假設不必保持元素順序。

這段代碼的第一個問題是增加了一個WaitGroup,並且必須要記得調用它的Add 和Done 方法。這增加了開發人員的工作量。如果弄錯了,這個程序不會產生正確的輸出,結果是要么輸出不確定,要么程序永不結束。此外,如果列表很長,你會為每個列表創建一個Go 例程。正如我之前所說,這不是問題,因為Go 能輕鬆搞定。問題在於,每個Go 例程都會爭搶CPU 時間片。因此,這不是執行該任務的最有效方式。

你可能希望為每個CPU內核創建一個Go例程,並讓這些例程選取列表並處理。創建Go例程的開銷很小,但是在一個非常緊湊的循環中創建它們會使開銷陡增。當我開發scc時就遇到了這種情況,因此我採用了每個CPU內核對應一個Go例程的策略。在Go語言中,要這樣做的話,你首先要創建一個通道,然後遍歷切片中的元素,使函數從該通道讀取數據,之後從另一個通道讀取。我們來看一下。

複製代碼

toProcess := []int{1,2,3,4,5,6,7,8,9}
var input = make(chan int, len(toProcess))
for i, _ := range toProcess {
input <- i
}
close(input)
var wg sync.WaitGroup
for i := 0; i < runtime.NumCPU(); i++ {
wg.Add(1)
go func(input chan int, output []int) {
for j := range input {
toProcess[j] = someSlowCalculation(toProcess[j])
}
wg.Done()
}(input, toProcess)
}
wg.Wait()
fmt.Println(toProcess)

上面的代碼創建了一個通道,然後遍歷切片,將索引值放入通道。接下來我們為每個CPU 內核創建一個Go 例程,操作系統會報告並處理相應的輸入,然後等待,直到所有操作完成。這裡有很多代碼需要理解。

然而,這種實現有待商榷。如果切片非常大,通道的緩衝區長度和切片大小相同,你可能不希望創建一個有這麼大緩衝區的通道。因此,你應該創建另一個Go 例程來遍歷切片,並將切片中的值放入通道,完成後關閉通道。但這樣一來代碼會變得冗長,因此我把它去掉了。我希望可以大概地闡明基本思路。

使用Java 語言大致這樣實現:

複製代碼

var firstList = List.of(1,2,3,4,5,6,7,8,9);
firstList = firstList.parallelStream()
.map(this::someSlowCalculation)
.collect(Collectors.toList());

通道和流並不等價。使用隊列去仿寫Go 代碼的邏輯更好一些,因為它們更具有可比性,但我們的目的不是進行1 對1 的比較。我們的目標是充分利用所有的CPU 內核處理切片或列表。

如果someSlowCalucation 方法調用了網絡或其它非CPU 密集型任務,這當然不是問題。在這種情況下,通道和Go 例程都會表現得很好。

這個問題與問題#1 有關。如果Go 語言支持適用於切片/Map 對象的函數式方法,那麼就能實現這個功能。但是,如果Go 語言支持泛型,有人就可以把上面的功能封裝成像Rust 的Rayon 一樣的庫,讓每個人都從中受益,這就很令人討厭了(我不希望Go 支持泛型)。

順便說一下,我認為這個缺陷妨礙了Go 語言在數據科學領域的成功,這也是為什麼Python 仍然是數據科學領域的王者。Go 語言在數值操作方面缺乏表現力和能力,原因就是以上討論的這些。

3 垃圾回收器

Go 的垃圾回收器做得非常不錯。我開發的應用程序通常都會因為新版本的改進而變得更快。但是,它以低延遲為最高優先級。對於API 和UI 應用來說,這個選擇完全可以接受。對於包含網絡調用的應用,因為網絡調用往往會是瓶頸,所以它也沒問題。

我發現的問題是Go對UI應用來講一點也不好(我不知道它有任何良好的支持)。如果你想要盡可能高的吞吐量,那這個選擇會讓你很受傷。這是我開發scc時遇到的一個主要問題。scc是一個CPU密集型的命令行工具。為了解決這個問題,我不得不在代碼裡添加邏輯關閉GC,直到達到某個閾值。但是我又不能簡單的禁用它,因為有些任務會很快耗盡內存。

缺乏對GC 的控制時常令人沮喪。你得學會適應它,但是,有時候如果能做到這樣該有多好:“嘿,這些代碼確實需要盡可能快地運行,所以如果你能在高吞吐模式運行一會,那就太好了。”

image

我認為這種情況在Go 1.12 版本中有所改善,因為GC 得到了進一步的改進。但僅僅是關閉和打開GC 還不夠,我期望更多的控制。如果有時間我會再進行研究。

4 錯誤處理

我並不是唯一一個抱怨這個問題的人,但我不吐不快。

複製代碼

value, err := someFunc()
if err != nil {
// Do something here
}
err = someOtherFunc(value)
if err != nil {
// Do something here
}

上面的代碼很乏味。Go 甚至不會像有些人建議的那樣強制你處理錯誤。你可以使用“_”顯式忽略它(這是否算作對它進行了處理呢?),你還可以完全忽略它。比如上面的代碼可以重寫為:

複製代碼

value, _ := someFunc()
someOtherFunc(value)

很顯然,我顯式忽略了someFunc 方法的返回。someOtherFunc(value)方法也可能返回錯誤值,但我完全忽略了它。這裡的錯誤都沒有得到處理。

說實話,我不知道如何解決這個問題。我喜歡Rust中的“?”運算符,它可以幫助避免這種情況。V-Lang https://vlang.io/看起來也可能有一些有趣的解決方案。

另一個辦法是使用可選類型(Optional types)並去掉nil,但這不會發生在Go 語言裡,即使是Go 2.0 版本,因為它會破壞向後兼容性。

結語

Go 仍然是一種非常不錯的語言。如果你讓我寫一個API,或者完成某個需要大量磁盤/ 網絡調用的任務,它依然是我的首選。現在我會用Go 而非Python 去完成很多一次性任務,數據合併任務是例外,因為函數式編程的缺失使執行效率難以達到要求。

與Java 不同,Go 語言盡量遵循“最小驚喜“原則。比如可以這樣比較字兩個符串是否相等:stringA == stringB。但如果你這樣比較兩個切片,那麼會產生編譯錯誤。這些都是很好的特性。

的確,二進製文件還可以變的更小(一些編譯標誌和upx可以解決這個問題),我希望它在某些方面變得更快,GOPATH雖然不是很好,但也沒有人們想得那麼糟糕,默認的單元測試框架缺少很多功能,模擬(mocking)有點讓人痛苦…

它仍然是我使用過的效率較高的語言之一。我會繼續使用它,雖然我希望https://vlang.io/能最終發布,並解決我的很多抱怨。V語言或Go 2.0,Nim或Rust。現在有很多很酷的新語言可以使用,我們開發人員真的要被寵壞了。

查看英文原文:

https://boyter.org/posts/my-personal-complaints-about-golang/

本博客Nginx 配置之安全篇

之前有細心的朋友問我,為什麼你的博客副標題是「專注WEB 端開發」,是不是少了「前端」的「前」。我想說的是,儘管我從畢業到現在七年左右的時間一直都在專業前端團隊從事前端相關工作,但這並不意味著我的知識體係就必須局限於前端這個範疇內。現在比較流行「全棧工程師」的概念,我覺得全棧意味著一個項目中,各個崗位所需要的技能你都具備,但並不一定意味著你什麼都需要做。你需要做什麼,更多是由能力、人員配比以及成本等各個因素所決定。儘管我現在的工作職責是在WEB 前端領域,但是我的關注點在整個WEB 端。

我接觸過的有些前端朋友,從一開始就把自己局限在一個很小的範圍之中,這在大公司到也無所謂,大公司分工明確,基礎設施齊全,你只要做好自己擅長的那部分就可以了。但是當他們進入創業公司之後,會發現一下子來了好多之前完全沒有接觸過的東西,十分被動。

去年我用Lua + OpenResty替換了線上千萬級的PHP + Nginx服務,至今穩定運行,算是前端之外的一點嘗試。我一直認為學習任何知識很重要的一點是實踐,所以我一直都在折騰我的VPS,進行各種WEB安全、優化相關的嘗試。我打算從安全和性能兩方面介紹一下本博客所用Nginx的相關配置,今天先寫安全相關的。

隱藏不必要的信息

大家可以看一下我的博客請求響應頭,有這麼一行server: nginx,說明我用的是Nginx服務器,但並沒有具體的版本號。由於某些Nginx漏洞只存在於特定的版本,隱藏版本號可以提高安全性。這只需要在配置裡加上這個就可以了:

server_tokens   off;

如果想要更徹底隱藏所用Web Server,可以修改Nginx源碼,把Server Name改掉再編譯,具體步驟可以自己搜索。需要提醒的是:如果你的網站支持SPDY,只改動網上那些文章寫到的地方還不夠,跟SPDY有關的代碼也要改。更簡單的做法是改用Tengine這個Nginx的增強版,並指定server_tag為off或者任何想要的值就可以了。另外,既然想要徹底隱藏Nginx,404、500等各種出錯頁也需要自定義。

同樣,一些WEB語言或框架默認輸出的x-powered-by也會洩露網站信息,他們一般都提供了修改或移除的方法,可以自行查看手冊。如果部署上用到了Nginx的反向代理,也可以通過proxy_hide_header指令隱藏它:

proxy_hide_header        X-Powered-By;

禁用非必要的方法

由於我的博客只處理了GET、POST 兩種請求方法,而HTTP/1 協議還規定了TRACE 這樣的方法用於網絡診斷,這也可能會暴露一些信息。所以我針對GET、POST 以及HEAD 之外的請求,直接返回了444 狀態碼(444 是Nginx 定義的響應狀態碼,會立即斷開連接,沒有響應正文)。具體配置是這樣的:

NGINXif ($request_method !~ ^(GET|HEAD|POST)$ ) {
    return    444;
}

合理配置響應頭

我的博客是由自己用ThinkJS 寫的Node 程序提供服務,Nginx 通過proxy_pass 把請求反向代理給Node 綁定的IP 和端口。在最終輸出時,我給響應增加了以下頭部:

NGINXadd_header  Strict-Transport-Security  "max-age=31536000";
add_header  X-Frame-Options  deny;
add_header  X-Content-Type-Options  nosniff;
add_header  Content-Security-Policy  "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://a.disquscdn.com; img-src 'self' data: https://www.google-analytics.com; style-src 'self' 'unsafe-inline'; frame-src https://disqus.com";

Strict-Transport-Security(簡稱為HSTS)可以告訴瀏覽器,在指定的max-age內,始終通過HTTPS訪問我的博客。即使用戶自己輸入HTTP的地址,或者點擊了HTTP鏈接,瀏覽器也會在本地替換為HTTPS再發送請求。另外由於我的證書不支持多域名,我沒有加上includeSubDomains。關於HSTS更多信息,可以查看我之前的介紹

X-Frame-Options用來指定此網頁是否允許被iframe嵌套,deny就是不允許任何嵌套發生。關於這個響應頭的更多介紹可以看這裡

X-Content-Type-Options用來指定瀏覽器對未指定或錯誤指定Content-Type資源真正類型的猜測行為,nosniff表示不允許任何猜測。這部分內容更多介紹見這裡

Content-Security-Policy(簡稱為CSP)用來指定頁面可以加載哪些資源,主要目的是減少XSS的發生。我允許了來自本站、disquscdn的外鏈JS,還允許內聯JS,以及在JS中使用eval;允許來自本站和google統計的圖片,以及內聯圖片(Data URI形式);允許本站外鏈CSS以及內聯CSS;允許iframe加載來自disqus的頁面。對於其他未指定的資源,都會走默認規則self,也就是只允許加載本站的。關於CSP的詳細介紹請看這裡

之前的博客中,我還介紹過X-XSS-Protection這個響應頭,也可以用來防範XSS。不過由於有了CSP,所以我沒配置它。

需要注意的是,以上這些響應頭現代瀏覽器才支持,所以並不是說加上他們,網站就可以不管XSS,萬事大吉了。但是鑑於低廉的成本,還是都配上。

HTTPS 安全配置

啟用HTTPS 並正確配置了證書,意味著數據傳輸過程中無法被第三者解密或修改。有了HTTPS,也得合理配置好Web Server,才能發揮最大價值。我的博客關於HTTPS 這一塊有以下配置:

NGINXssl_certificate      /home/jerry/ssl/server.crt;
ssl_certificate_key  /home/jerry/ssl/server.key;
ssl_dhparam          /home/jerry/ssl/dhparams.pem;

ssl_ciphers          ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:AES128-GCM-SHA256:AES256-GCM-SHA384:DES-CBC3-SHA;

ssl_prefer_server_ciphers  on;

ssl_protocols        TLSv1 TLSv1.1 TLSv1.2;

最終效果是我的博客在ssllabs的測試中達到了A+,如下圖:

ssllabs test

如何配置ssl_ciphers可以參考這個網站。需要注意的是,這個網站默認提供的加密方式安全性較高,一些低版本客戶端並不支持,例如IE9-、Android2.2-和Java6-。如果需要支持這些老舊的客戶端,需要點一下網站上的「Yes, give me a ciphersuite that works with legacy / old software」鏈接。

另外,我在ssl_ciphers最開始加上了CHACHA20,這是因為我的Nginx支持了CHACHA20_POLY1305加密算法,這是由Google開發的新一代加密方式,它有兩方面優勢:更好的安全性和更好的性能(尤其是在移動和可穿戴設備上)。下面有一張移動平台上它與AES-GCM的加密速度對比圖(via):

chacha20 poly1305

啟用CHACHA20_POLY1305最簡單的方法是在編譯Nginx時,使用LibreSSL代替OpenSSL。下面是用Chrome訪問我的博客時,點擊地址欄小鎖顯示的信息,可以看到加密方式使用的就是CHACHA20_POLY1305:

imququ.com

關於CHACHA20_POLY1305安全性和性能的詳細介紹可以查看本文

補充:使用CHACHA20_POLY1305的最佳實踐是「僅針對不支持AES-NI的終端使用CHACHA20算法,否則使用AES-GCM」。關於這個話題的詳細解釋和配置方法,請參考我的這篇文章:使用BoringSSL優化HTTPS加密算法選擇

關於ssl_dhparam的配置,可以參考這篇文章:Guide to Deploying Diffie-Hellman for TLS

SSLv3已被證實不安全,所以在ssl_protocols指令中,我並沒有包含它。

ssl_prefer_server_ciphers配置為on,可以確保在TLSv1握手時,使用服務端的配置項,以增強安全性。

好了,本文先就這樣,後面再寫跟性能有關的配置。

搭建 Docker 环境

安装 Docker

Docker 软件包已经包括在默认的 CentOS-Extras 软件源里。因此想要安装 docker,只需要运行下面的 yum 命令:

yum install docker-io -y

直接yum安装,安装成功后查看版本

docker -v

启动docker

service docker start

设置开机启动

chkconfig docker on

配置 Docker

因为国内访问 Docker Hub 较慢, 可以使用腾讯云提供的国内镜像源, 加速访问 Docker Hub

依次执行以下命令

echo "OPTIONS='--registry-mirror=https://mirror.ccs.tencentyun.com'" >> /etc/sysconfig/docker
systemctl daemon-reload
service docker restart

Docker 的简单操作

任务时间:10min ~ 20min

下载镜像

下载一个官方的 CentOS 镜像到本地

docker pull centos

下载好的镜像就会出现在镜像列表里

docker images

运行容器

这时我们可以在刚才下载的 CentOS 镜像生成的容器内操作了。

生成一个 centos 镜像为模板的容器并使用 bash shell

docker run -it centos /bin/bash

这个时候可以看到命令行的前端已经变成了 [root@(一串 hash Id)] 的形式, 这说明我们已经成功进入了 CentOS 容器。

在容器内执行任意命令, 不会影响到宿主机, 如下

mkdir -p /data/simple_docker

可以看到 /data 目录下已经创建成功了 simple_docker 文件夹

ls /data

退出容器

exit

查看宿主机的 /data 目录, 并没有 simple_docker 文件夹, 说明容器内的操作不会影响到宿主机

ls /data

保存容器

查看所有的容器信息, 能获取容器的id

docker ps -a

然后执行如下命令[?],保存镜像:

docker commit -m="备注" 你的CONTAINER_ID 你的IMAGE

请自行将 -m 后面的信息改成自己的容器的信息

大功告成!

恭喜你结束了 Docker 的教程并学会了 Docker 的一些基本操作,

 

使用docker attach命令
我们使用
docker attach db3 或者 docker attach d48b21a7e439

 

db3是后台容器的NAMES,d48b21a7e439是容器的进程ID  CONTAINER ID
然后就进去了这个容器的ssh界面。
但是它有一个缺点,只要这个连接终止,或者使用了exit命令,容器就会退出后台运行
 
 
使用docker exec命令
这个命令使用exit命令后,不会退出后台,一般使用这个命令,使用方法如下
docker exec -it db3 /bin/sh 或者 docker exec -it d48b21a7e439 /bin/sh

Jenkins 环境搭建

Jenkins 简介

Jenkins 是一个开源软件项目,是基于Java开发的一种持续集成工具,用于监控持续重复的工作,旨在提供一个开放易用的软件平台,使软件的持续集成变成可能。

Java 安装

首先我们需要准备 Java 环境,使用下面命令来安装 Java:

yum -y install java-1.8.0-openjdk-devel

Jenkins 安装

为了使用 Jenkins 仓库,我们要执行以下命令:

sudo wget -O /etc/yum.repos.d/jenkins.repo https://pkg.jenkins.io/redhat-stable/jenkins.repo
sudo rpm --import https://pkg.jenkins.io/redhat-stable/jenkins.io.key

如果您以前从 Jenkins 导入过 key,那么 rpm --import 将失败,因为您已经有一个 key。请忽略,继续下面步骤。

安装

接着我们可以使用 yum 安装 Jenkins:

yum -y install jenkins

启动 Jenkins

任务时间:5min ~ 10min

启动

启动 Jenkins 并设置为开机启动:

systemctl start jenkins.service
chkconfig jenkins on

测试访问

Jenkins 默认运行在 8080端口。

稍等片刻,打开 http://<您的 CVM IP 地址>:8080 测试访问。

进入 Jenkins

任务时间:5min ~ 10min

管理员密码

登入 Jenkins 需要输入管理员密码,按照提示,我们使用以下命令查看初始密码:

cat /var/lib/jenkins/secrets/initialAdminPassword

复制密码,填入,进入 Jenkins。

定制 Jenkins

我们选择默认的 install suggested plugins 来安装插件。

创建用户

请填入相应信息创建用户,然后即可登入 Jenkins 的世界。

实验完成

恭喜,您已完成 搭建 Jenkins 环境搭建 实验。

搭建 Hadoop 伪分布式环境

软硬件环境

  • CentOS 7.2 64位
  • OpenJDK-1.7
  • Hadoop-2.7

关于本教程的说明

云实验室云主机自动使用root账户登录系统,因此本教程中所有的操作都是以root用户来执行的。若要在自己的云主机上进行本教程的实验,为了系统安全,建议新建一个账户登录后再进行后续操作。

安装 SSH 客户端

任务时间:1min ~ 5min

安装SSH

安装SSH:

sudo yum install openssh-clients openssh-server

安装完成后,可以使用下面命令进行测试:

ssh localhost

输入root账户的密码,如果可以正常登录,则说明SSH安装没有问题。测试正常后使用exit命令退出ssh。

安装 JAVA 环境

任务时间:5min ~ 10min

安装 JDK

使用yum来安装1.7版本OpenJDK:

sudo yum install java-1.7.0-openjdk java-1.7.0-openjdk-devel

安装完成后,输入javajavac命令,如果能输出对应的命令帮助,则表明jdk已正确安装。

配置 JAVA 环境变量

执行命令:

编辑 ~/.bashrc,在结尾追加:

export JAVA_HOME=/usr/lib/jvm/java-1.7.0-openjdk

保存文件后执行下面命令使JAVA_HOME环境变量生效:

source ~/.bashrc

为了检测系统中JAVA环境是否已经正确配置并生效,可以分别执行下面命令:

java -version
$JAVA_HOME/bin/java -version

若两条命令输出的结果一致,且都为我们前面安装的openjdk-1.7.0的版本,则表明JDK环境已经正确安装并配置。

安装 Hadoop

任务时间:10min ~ 15min

下载 Hadoop

本教程使用hadoop-2.7版本,使用wget工具在线下载(注:本教程是从清华大学的镜像源下载,如果下载失败或报错,可以自己在网上找到国内其他一个镜像源下载2.7版本的hadoop即可):

wget https://mirrors.tuna.tsinghua.edu.cn/apache/hadoop/common/hadoop-2.7.4/hadoop-2.7.4.tar.gz

安装 Hadoop

将Hadoop安装到/usr/local目录下:

tar -zxf hadoop-2.7.4.tar.gz -C /usr/local

对安装的目录进行重命名,便于后续操作方便:

cd /usr/local
mv ./hadoop-2.7.4/ ./hadoop

检查Hadoop是否已经正确安装:

/usr/local/hadoop/bin/hadoop version

如果成功输出hadoop的版本信息,表明hadoop已经成功安装。

Hadoop 伪分布式环境配置

任务时间:15min ~ 30min

Hadoop伪分布式模式使用多个守护线程模拟分布的伪分布运行模式。

设置 Hadoop 的环境变量

编辑 ~/.bashrc,在结尾追加如下内容:

export HADOOP_HOME=/usr/local/hadoop
export HADOOP_INSTALL=$HADOOP_HOME
export HADOOP_MAPRED_HOME=$HADOOP_HOME
export HADOOP_COMMON_HOME=$HADOOP_HOME
export HADOOP_HDFS_HOME=$HADOOP_HOME
export YARN_HOME=$HADOOP_HOME
export HADOOP_COMMON_LIB_NATIVE_DIR=$HADOOP_HOME/lib/native
export PATH=$PATH:$HADOOP_HOME/sbin:$HADOOP_HOME/bin

使Hadoop环境变量配置生效:

source ~/.bashrc

修改 Hadoop 的配置文件

Hadoop的配置文件位于安装目录的/etc/hadoop目录下,在本教程中即位于/url/local/hadoop/etc/hadoop目录下,需要修改的配置文件为如下两个:

/usr/local/hadoop/etc/hadoop/core-site.xml
/usr/local/hadoop/etc/hadoop/hdfs-site.xml

编辑 core-site.xml,修改<configuration></configuration>节点的内容为如下所示:

示例代码:/usr/local/hadoop/etc/hadoop/core-site.xml
<configuration>
    <property>
        <name>hadoop.tmp.dir</name>
        <value>file:/usr/local/hadoop/tmp</value>
        <description>location to store temporary files</description>
    </property>
    <property>
        <name>fs.defaultFS</name>
        <value>hdfs://localhost:9000</value>
    </property>
</configuration>

同理,编辑 hdfs-site.xml,修改<configuration></configuration>节点的内容为如下所示:

示例代码:/usr/local/hadoop/etc/hadoop/hdfs-site.xml
<configuration>
    <property>
        <name>dfs.replication</name>
        <value>1</value>
    </property>
    <property>
        <name>dfs.namenode.name.dir</name>
        <value>file:/usr/local/hadoop/tmp/dfs/name</value>
    </property>
    <property>
        <name>dfs.datanode.data.dir</name>
        <value>file:/usr/local/hadoop/tmp/dfs/data</value>
    </property>
</configuration>

格式化 NameNode

格式化NameNode:

/usr/local/hadoop/bin/hdfs namenode -format

在输出信息中看到如下信息,则表示格式化成功:

Storage directory /usr/local/hadoop/tmp/dfs/name has been successfully formatted.
Exiting with status 0

启动 NameNode 和 DataNode 守护进程

启动NameNode和DataNode进程:

/usr/local/hadoop/sbin/start-dfs.sh

执行过程中会提示输入用户密码,输入root用户密码即可。另外,启动时ssh会显示警告提示是否继续连接,输入yes即可。

检查 NameNode 和 DataNode 是否正常启动:

jps

如果NameNode和DataNode已经正常启动,会显示NameNode、DataNode和SecondaryNameNode的进程信息:

[hadoop@VM_80_152_centos ~]$ jps
3689 SecondaryNameNode
3520 DataNode
3800 Jps
3393 NameNode

运行 Hadoop 伪分布式实例

任务时间:10min ~ 20min

Hadoop自带了丰富的例子,包括 wordcount、grep、sort 等。下面我们将以grep例子为教程,输入一批文件,从中筛选出符合正则表达式dfs[a-z.]+的单词并统计出现的次数。

查看 Hadoop 自带的例子

Hadoop 附带了丰富的例子, 执行下面命令可以查看:

cd /usr/local/hadoop
./bin/hadoop jar ./share/hadoop/mapreduce/hadoop-mapreduce-examples-2.7.4.jar

在 HDFS 中创建用户目录

在 HDFS 中创建用户目录 hadoop:

/usr/local/hadoop/bin/hdfs dfs -mkdir -p /user/hadoop

准备实验数据

本教程中,我们将以 Hadoop 所有的 xml 配置文件作为输入数据来完成实验。执行下面命令在 HDFS 中新建一个 input 文件夹并将 hadoop 配置文件上传到该文件夹下:

cd /usr/local/hadoop
./bin/hdfs dfs -mkdir /user/hadoop/input
./bin/hdfs dfs -put ./etc/hadoop/*.xml /user/hadoop/input

使用下面命令可以查看刚刚上传到 HDFS 的文件:

/usr/local/hadoop/bin/hdfs dfs -ls /user/hadoop/input

运行实验

运行实验:

cd /usr/local/hadoop
./bin/hadoop jar ./share/hadoop/mapreduce/hadoop-mapreduce-examples-2.7.4.jar grep /user/hadoop/input /user/hadoop/output 'dfs[a-z.]+'

上述命令以 HDFS 文件系统中的 input 为输入数据来运行 Hadoop 自带的 grep 程序,提取其中符合正则表达式 dfs[a-z.]+ 的数据并进行次数统计,将结果输出到 HDFS 文件系统的 output 文件夹下。

查看运行结果

上述例子完成后的结果保存在 HDFS 中,通过下面命令查看结果:

/usr/local/hadoop/bin/hdfs dfs -cat /user/hadoop/output/*

如果运行成功,可以看到如下结果:

1       dfsadmin
1       dfs.replication
1       dfs.namenode.name.dir
1       dfs.datanode.data.dir

删除 HDFS 上的输出结果

删除 HDFS 中的结果目录:

/usr/local/hadoop/bin/hdfs dfs -rm -r /user/hadoop/output

运行 Hadoop 程序时,为了防止覆盖结果,程序指定的输出目录不能存在,否则会提示错误,因此在下次运行前需要先删除输出目录。

关闭 Hadoop 进程

关闭 Hadoop 进程:

/usr/local/hadoop/sbin/stop-dfs.sh

再起启动只需要执行下面命令:

/usr/local/hadoop/sbin/start-dfs.sh

部署完成

任务时间:时间未知

大功告成

恭喜您已经完成了搭建 Hadoop 伪分布式环境的学习

Facebook针对hbase的优化方案分析

使用hbase的目的是为了海量数据的随机读写,但是在实际使用中却发现针对随机读的优化和gc是一个很大的问题,而且hbase的数据是存储在Hdfs,而Hdfs是面向流失数据访问进行设计的,就难免带来效率的下降。下面介绍一下Facebook Message系统在HBase online storage场景下的一个案例(《Apache Hadoop Goes Realtime at Facebook》, SIGMOD 2011),最近他们在存储领域顶级会议FAST2014上发表了一篇论文《Analysis of HDFS Under HBase: A Facebook Messages Case Study》分析了他们在使用HBase中遇到的一些问题和解决方案。该论文首先讲了Facebook的分析方法包括tracing/analysis/simulation,FM系统的架构和文件与数据构成等,接下来开始分析FM系统在性能方面的一些问题,并提出了解决方案。

7e05f2a60f826648e732fb0de6eeb88b9a41ed00

FM系统的主要读写I/O负载

Figure 2描述了每一层的I/O构成,解释了在FM系统对外请求中读占主导,但是由于logging/compaction/replication/caching导致写被严重放大。

  • HBase的设计是分层结构的,依次是DB逻辑层、FS逻辑层、底层系统逻辑层。DB逻辑层提供的对外使用的接口主要操作是put()和get()请求,这两个操作的数据都要写到HDFS上,其中读写比99/1(Figure 2中第一条)。
  • 由于DB逻辑层内部为了保证数据的持久性会做logging,为了读取的高效率会做compaction,而且这两个操作都是写占主导的,所以把这两个操作(overheads)加上之后读写比为79/21(Figure 2中第二条)。
  • 相当于调用put()操作向HBase写入的数据都是写入了两份:一份写入内存Memstore然后flush到HFile/HDFS,另一份通过logging直接写HLog/HDFS。Memstore中积累一定量的数据才会写HFile,这使得压缩比会比较高,而写HLog要求实时append record导致压缩比(HBASE-8155)相对较低,导致写被放大4倍以上。    Compaction操作就是读取小的HFile到内存merge-sorting成大的HFile然后输出,加速HBase读操作。Compaction操作导致写被放大17倍以上,说明每部分数据平均被重复读写了17次,所以对于内容不变的大附件是不适合存储在HBase中的。由于读操作在FM业务中占主要比例,所以加速读操作对业务非常有帮助,所以compaction策略会比较激进。
    HBase的数据reliable是靠HDFS层保证的,即HDFS的三备份策略。那么也就是上述对HDFS的写操作都会被转化成三倍的local file I/O和两倍的网络I/O。这样使得在本地磁盘I/O中衡量读写比变成了55/45。
  • 然而由于对本地磁盘的读操作请求的数据会被本地OS的cache缓存,那么真正的读操作是由于cache miss引起的读操作的I/O量,这样使得读写比变成了36/64,写被进一步放大。    另外Figure 3从I/O数据传输中真正业务需求的数据大小来看各个层次、各个操作引起的I/O变化。除了上面说的,还发现了整个系统最终存储在磁盘上有大量的cold data(占2/3),所以需要支持hot/cold数据分开存储。
7e05f2a60f826648e732fb0de6eeb88b9a41ed00

总的来说,HBase stack的logging/compaction/replication/caching会放大写I/O,导致业务逻辑上读为主导的HBase系统在地层实际磁盘I/O中写占据了主导。
FM系统的主要文件类型和大小  

40f2cdd5037aa74c7f96f02cada56e3ca247f708

FM系统的几种文件类型如Table 2所示,这个是纯业务的逻辑描述。在HBase的每个RegionServer上的每个column family对应一个或者多个HFile文件。FM系统中有8个column family,由于每个column family存储的数据的类型和大小不一样,使得每个column family的读写比是不一样的。而且很少数据是读写都会请求的,所以cache all writes可能作用不大(Figure 4)。
ea019d03c555e91a66960da7f61b9426d4a96444

对于每个column family的文件,90%是小于15M的。但是少量的特别大的文件会拉高column family的平均文件大小。例如MessageMeta这个column family的平均文件大小是293M。从这些文件的生命周期来看,大部分FM的数据存储在large,long-lived files,然而大部分文件却是small, short-lived。这对HDFS的NameNode提出了很大的挑战,因为HDFS设计的初衷是为了存储少量、大文件准备的,所有的文件的元数据是存储在NameNode的内存中的,还有有NameNode federation。
FM系统的主要I/O访问类型

下面从temporal locality, spatial locality, sequentiality的角度来看。
73.7%的数据只被读取了一次,但是1.1%的数据被读取了至少64次。也就是说只有少部分的数据被重复读取了。但是从触发I/O的角度,只有19%的读操作读取的是只被读取一次的数据,而大部分I/O是读取那些热数据。
在HDFS这一层,FM读取数据没有表现出sequentiality,也就是说明high-bandwidth, high-latency的机械磁盘不是服务读请求的理想存储介质。而且对数据的读取也没有表现出spatial locality,也就是说I/O预读取也没啥作用。
解决方案1. Flash/SSD作为cache使用

095d39b45168460de2f41581e4f271d96eb56e94

下面就考虑怎么架构能够加速这个系统了。目前Facebook的HBase系统每个Node挂15块100MB/s带宽、10ms寻址时间的磁盘。Figure 9表明:a)增加磁盘块数有点用;b)增加磁盘带宽没啥大用;c)降低寻址时间非常有用。
由于少部分同样的数据会被经常读取,所以一个大的cache能够把80%左右的读取操作拦截而不用触发磁盘I/O,而且只有这少部分的hot data需要被cache。那么拿什么样的存储介质做cache呢?Figure 11说明如果拿足够大的Flash做二级缓存,cache命中率会明显提高,同时cache命中率跟内存大小关系并不大。
注:关于拿Flash/SSD做cache,可以参考HBase BucketBlockCache(HBASE-7404)
17b348cd8343d187910d175838ec68caaae9dc22
我们知道大家比较关心Flash/SSD寿命的问题,在内存和Flash中shuffling数据能够使得最热的数据被交换到内存中,从而提升读性能,但是会降低Flash的寿命,但是随着技术的发展这个问题带来的影响可能越来越小。
说完加速读的cache,接着讨论了Flash作为写buffer是否会带来性能上的提升。由于HDFS写操作只要数据被DataNode成功接收到内存中就保证了持久性(因为三台DataNode同时存储,所以认为从DataNode的内存flush到磁盘的操作不会三个DataNode都失败),所以拿Flash做写buffer不会提高性能。虽然加写buffer会使后台的compaction操作降低他与前台服务的I/O争用,但是会增加很大复杂度,所以还是不用了。最后他们给出了结论就是拿Flash做写buffer没用。
然后他们还计算了,在这个存储栈中加入Flash做二级缓存不但能提升性能达3倍之多,而且只需要增加5%的成本,比加内存性价比高很多。
2.分层架构的缺点和改进方案
c51484c944de4521dece47cfd616da656a8fe54a

如Figure 16所示,一般分布式数据库系统分为三个层次:db layer/replication layer/local layer。这种分层架构的最大优点是简洁清晰,每层各司其职。例如db layer只需要处理DB相关的逻辑,底层的存储认为是available和reliable的。
HBase是图中a)的架构,数据的冗余replication由HDFS来负责。但是这个带来一个问题就是例如compaction操作会读取多个三备份的小文件到内存merge-sorting成一个三备份的大文件,这个操作只能在其中的一个RS/DN上完成,那么从其他RS/DN上的数据读写都会带来网络传输I/O。
图中b)的架构就是把replication层放到了DB层的上面,Facebook举的例子是Salus,不过我对这个东西不太熟悉。我认为Cassandra就是这个架构的。这个架构的缺点就是DB层需要处理底层文件系统的问题,还要保证和其他节点的DB层协调一致,太复杂了。
图中c)的架构是在a的基础上的一种改进,Spark使用的就是这个架构。HBase的compaction操作就可以简化成join和sort这样两个RDD变换。

f43f47a9afb7a6fc49c810e9144e9b88af618793
Figure 17展示了local compaction的原理,原来的网络I/O的一半转化成了本地磁盘读I/O,而且可以利用读cache加速。我们都知道在数据密集型计算系统中网络交换机的I/O瓶颈非常大,例如MapReduce Job中Data Shuffle操作就是最耗时的操作,需要强大的网络I/O带宽。加州大学圣迭戈分校(UCSD)微软亚洲研究院(MSRA)都曾经设计专门的数据中心网络拓扑来优化网络I/O负载,相关研究成果在计算机网络顶级会议SIGCOMM上发表了多篇论文,但是由于其对网络路由器的改动伤筋动骨,最后都没有成功推广开来。
d1dc231e39134be01ad3dfafe7d28161efcabbc9
Figure 19展示了combined logging的原理。现在HBase的多个RS会向同一个DataNode发送写log请求,而目前DataNode端会把来自这三个RS的log分别写到不同的文件/块中,会导致该DataNode磁盘seek操作较多(不再是磁盘顺序I/O,而是随机I/O)。Combined logging就是把来自不同RS的log写到同一个文件中,这样就把DataNode的随机I/O转化成了顺序I/O。

Face Recognition

Recognize and manipulate faces from Python or from the command line with the world’s simplest face recognition library.

Built using dlib‘s state-of-the-art face recognition built with deep learning. The model has an accuracy of 99.38% on theLabeled Faces in the Wild benchmark.

This also provides a simple face_recognition command line tool that lets you do face recognition on a folder of images from the command line!

 

Find faces in pictures

Find all the faces that appear in a picture:

 

 

Project site : https://github.com/ageitgey/face_recognition

L2TP/IPSec一键安装脚本

本脚本适用环境:
系统支持:CentOS6+,Debian7+,Ubuntu12+
内存要求:≥128M
更新日期:2017 年 05 月 28 日

关于本脚本:
名词解释如下
L2TP(Layer 2 Tunneling Protocol)
IPSec(Internet Protocol Security)
IKEv2 (Internet Key Exchange v2)
能实现 IPsec 的目前总体上有 openswan,libreswan,strongswan 这3种。
libreswan 是基于 openswan 的 fork,所以现在各个发行版基本已经看不到 openswan 的身影了。
当然也有使用 strongswan 的。

之所以要更新 L2TP 一键安装脚本,是因为随着各个 Linux 发行版不断推陈出新,原有的脚本已经不适应现在的需求。
本脚本通过编译安装最新版 libreswan 来实现 IPSec(CentOS7 下则是全部 yum 安装),yum 或 apt-get 来安装 xl2tpd,再根据各个发行版的使用方法不同,部署防火墙规则。

写在前面:

基于 OpenVZ 虚拟化技术的 VPS 需要开启TUN/TAP才能正常使用,购买 VPS 时请先咨询服务商是否支持开启 TUN/TAP。

OpenVZ 虚拟的 VPS 需要系统内核支持 IPSec 才行。也就是说,母服务器的内核如果不支持的话那就没办法,只能换 VPS。
因此,一般不建议在 OpenVZ 的 VPS 上安装本脚本。脚本如果检测到该 VPS 为 OpenVZ 架构,会出现警告提醒。

如何检测是否支持TUN模块?
执行命令:
cat /dev/net/tun
如果返回信息为:cat: /dev/net/tun: File descriptor in bad state 说明正常

如何检测是否支持ppp模块?
执行命令:
cat /dev/ppp
如果返回信息为:cat: /dev/ppp: No such device or address 说明正常
当然,脚本在安装时也会执行检查,如果不适用于安装,脚本会予以提示。

使用方法:

root 用户登录后,运行以下命令:

wget --no-check-certificate https://raw.githubusercontent.com/teddysun/across/master/l2tp.sh
chmod +x l2tp.sh
./l2tp.sh

执行后,会有如下交互界面

L2TP

Please input IP-Range:
(Default Range: 192.168.18):
输入本地IP段范围(本地电脑连接到VPS后给分配的一个本地IP地址),直接回车意味着输入默认值192.168.18

Please input PSK:
(Default PSK: teddysun.com):
PSK意为预共享密钥,即指定一个密钥将来在连接时需要用到,直接回车意味着输入默认值teddysun.com

Please input Username:
(Default Username: teddysun):
Username意为用户名,即第一个默认用户。直接回车意味着输入默认值teddysun

Please input teddysun’s password:
(Default Password: Q4SKhu2EXQ):
输入用户的密码,默认会随机生成一个10位包含大小写字母和数字的密码,当然你也可以指定密码。

ServerIP:your_server_main_IP
显示你的 VPS 的主 IP(如果是多 IP 的 VPS 也只显示一个)

Server Local IP:192.168.18.1
显示你的 VPS 的本地 IP(默认即可)

Client Remote IP Range:192.168.18.2-192.168.18.254
显示 IP 段范围

PSK:teddysun.com
显示 PSK

Press any key to start…or Press Ctrl+c to cancel
按下任意按键继续,如果想取消安装,请按Ctrl+c键

安装完成后,脚本会执行 ipsec verify 命令并提示如下:

If there are no [FAILED] above, then you can connect to your
L2TP VPN Server with the default Username/Password is below:

ServerIP:your_server_IP
PSK:your PSK
Username:your usename
Password:your password

If you want to modify user settings, please use command(s):
l2tp -a (Add a user)
l2tp -d (Delete a user)
l2tp -l (List all users)
l2tp -m (Modify a user password)
Welcome to visit https://teddysun.com/448.html
Enjoy it!

如果你要想对用户进行操作,可以使用如下命令:
l2tp -a 新增用户
l2tp -d 删除用户
l2tp -m 修改现有的用户的密码
l2tp -l 列出所有用户名和密码
l2tp -h 列出帮助信息

其他事项:

1、脚本在安装完成后,已自动启动进程,并加入了开机自启动。
2、脚本会改写 iptables 或 firewalld 的规则。
3、脚本安装时,会即时将安装日志写到 /root/l2tp.log 文件里,如果你安装失败,可以通过此文件来寻找错误信息。

使用命令:

ipsec status (查看 IPSec 运行状态)
ipsec verify (查看 IPSec 检查结果)
/etc/init.d/ipsec start|stop|restart|status (CentOS6 下使用)
/etc/init.d/xl2tpd start|stop|restart (CentOS6 下使用)
systemctl start|stop|restart|status ipsec (CentOS7 下使用)
systemctl start|stop|restart xl2tpd (CentOS7 下使用)
service ipsec start|stop|restart|status (Debian/Ubuntu 下使用)
service xl2tpd start|stop|restart (Debian/Ubuntu 下使用)

更新日志

2017 年 05 月 28 日:
升级 libreswan 到版本 3.20。
修正 libreswan 的若干配置问题。
修正 xl2tpd 的端口监听配置问题。
修正在 CentOS 6 对 libevent2 的依赖问题,改为 yum 安装 libevent2-devel。
测试表明,在内网环境的 VPS 里(如AWS, IDCF,GCE,腾讯云,阿里云等)也可以正常使用了。

2017 年 02 月 25 日:
升级 libreswan 到版本 3.19。

2016 年 09 月 12 日:
修正了在 CentOS 6 下 libevent2 依赖的问题;
新增了一个 -m 选项,用以修改现有用户的密码。

2016 年 08 月 13 日:
修正 Debian 8 下的 sd-daemon.h: No such file or directory 问题,是由于缺少依赖包 libsystemd-daemon-dev 导致的。

2016 年 08 月 05 日:
升级 libreswan 到版本 3.18。

2016 年 06 月 10 日:
脚本在安装完成后,新增了几个命令,便于操作用户
l2tp -a 新增用户
l2tp -d 删除用户
l2tp -l 列出所有用户
l2tp -h 列出帮助信息

2016 年 04 月 25 日:
4、在 Vultr 的 Debian 7的系统模板下安装时,软件包 libcurl4-nss-dev 会出现依赖错误。如下所示:

The following packages have unmet dependencies:
libcurl4-nss-dev : Depends: libldap2-dev but it is not going to be installed
Depends: librtmp-dev but it is not going to be installed

而 libldap2-dev 和 librtmp-dev 又依赖了其他几种软件包。总之最后的依赖关系如下:

libldap2-dev : Depends: libldap-2.4-2 (= 2.4.31-2+deb7u1) but 2.4.31+really2.4.40+dfsg-1+deb8u1~bpo70+1 is to be installed
librtmp-dev : Depends: libgnutls-dev but it is not going to be installed
libgnutls-dev : Depends: libp11-kit-dev (>= 0.4) but it is not going to be installed
libp11-kit-dev : Depends: libp11-kit0 (= 0.12-3) but 0.20.7-1~bpo70+1 is to be installed

那么解决办法就是把最底层的依赖包 libp11-kit0 先卸载掉,然后再安装 libcurl4-nss-dev 即可。

apt-get -y remove libp11-kit0
apt-get -y --no-install-recommends install libcurl4-nss-dev wget

然后再运行脚本安装即可。

2016 年 04 月 22 日:
修复了在 Ubuntu 16.04 下因为默认缺少 python 命令而导致 ipsec verify 等命令不能用的问题。

2016 年 04 月 19 日:
修复了在 Debian 7 下因为 libnss3 和 libnspr4 的版本过低而导致编译 libreswan 失败的问题。

2016 年 04 月 18 日:
目前在 Debian 7 上测试的结果,因为 libnss3 和 libnspr4 的版本过低而导致编译 libreswan 失败。临时解决办法是 dpkg 安装 libnss3_3.17.2 和 libnspr4_4.10.7 的 deb 包后重试。

参考链接:
https://libreswan.org/wiki/3.14_on_Debian_Wheezy
https://github.com/libreswan/libreswan

转载请注明:秋水逸冰 » L2TP/IPSec一键安装脚本

Why Go is my favorite programming language

I strive to respect everybody’s personal preferences, so I usually steer clear of debates about which is the best programming language, text editor or operating system.

However, recently I was asked a couple of times why I like and use a lot of Go, so here is a coherent article to fill in the blanks of my ad-hoc in-person ramblings :-).

My background

I have used C and Perl for a number of decently sized projects. I have written programs in Python, Ruby, C++, CHICKEN Scheme, Emacs Lisp, Rust and Java (for Android only). I understand a bit of Lua, PHP, Erlang and Haskell. In a previous life, I developed a number of programs using Delphi.

I had a brief look at Go in 2009, when it was first released. I seriously started using the language when Go 1.0 was released in 2012, featuring the Go 1 compatibility guarantee. I still have code running in production which I authored in 2012, largely untouched.

1. Clarity

Formatting

Go code, by convention, is formatted using the gofmt tool. Programmatically formatting code is not a new idea, but contrary to its predecessors, gofmt supports precisely one canonical style.

Having all code formatted the same way makes reading code easier; the code feels familiar. This helps not only when reading the standard library or Go compiler, but also when working with many code bases — think Open Source, or big companies.

Further, auto-formatting is a huge time-saver during code reviews, as it eliminates an entire dimension in which code could be reviewed before: now, you can just let your continuous integration system verify that gofmt produces no diffs.

Interestingly enough, having my editor apply gofmt when saving a file has changed the way I write code. I used to attempt to match what the formatter would enforce, then have it correct my mistakes. Nowadays, I express my thought as quickly as possible and trust gofmt to make it pretty (example of what I would type, click Format).

High-quality code

I use the standard library (docssource) quite a bit, see below.

All standard library code which I have read so far was of extremely high quality.

One example is the image/jpeg package: I didn’t know how JPEG worked at the time, but it was easy to pick up by switching between the Wikipedia JPEG article and theimage/jpeg code. If the package had a few more comments, I would qualify it as a teaching implementation.

Opinions

I have come to agree with many opinions the Go community holds, such as:

Few keywords and abstraction layers

The Go specification lists only 25 keywords, which I can easily keep in my head.

The same is true for builtin functions and types.

In my experience, the small number of abstraction layers and concepts makes the language easy to pick up and quickly feel comfortable in.

While we’re talking about it: I was surprised about how readable the Go specification is. It really seems to target programmers (rather than standards committees?).

2. Speed

Quick feedback / low latency

I love quick feedback: I appreciate websites which load quickly, I prefer fluent User Interfaces which don’t lag, and I will choose a quick tool over a more powerful tool any day. The findings of large web properties confirm that this behavior is shared by many.

The authors of the Go compiler respect my desire for low latency: compilation speed matters to them, and new optimizations are carefully weighed against whether they will slow down compilation.

A friend of mine had not used Go before. After installing the RobustIRC bridge using go get, they concluded that Go must be an interpreted language and I had to correct them: no, the Go compiler just is that fast.

Most Go tools are no exception, e.g. gofmt or goimports are blazingly fast.

Maximum resource usage

For batch applications (as opposed to interactive applications), utilizing the available resources to their fullest is usually more important than low latency.

It is delightfully easy to profile and change a Go program to utilize all available IOPS, network bandwidth or compute. As an example, I wrote about filling a 1 Gbps link, and optimized debiman to utilize all available resources, reducing its runtime by hours.

3. Rich standard library

The Go standard library provides means to effectively use common communications protocols and data storage formats/mechanisms, such as TCP/IP, HTTP, JPEG, SQL, …

Go’s standard library is the best one I have ever seen. I perceive it as well-organized, clean, small, yet comprehensive: I often find it possible to write reasonably sized programs with just the standard library, plus one or two external packages.

Domain-specific data types and algorithms are (in general) not included and live outside the standard library, e.g. golang.org/x/net/html. The golang.org/x namespace also serves as a staging area for new code before it enters the standard library: the Go 1 compatibility guarantee precludes any breaking changes, even if they are clearly worthwhile. A prominent example is golang.org/x/crypto/ssh, which had to break existing code to establish a more secure default.

4. Tooling

To download, compile, install and update Go packages, I use the go get tool.

All Go code bases I have worked with use the built-in testing facilities. This results not only in easy and fast testing, but also in coverage reports being readily available.

Whenever a program uses more resources than expected, I fire up pprof. See this golang.org blog post about pprof for an introduction, or my blog post about optimizing Debian Code Search. After importing the net/http/pprof package, you can profile your server while it’s running, without recompilation or restarting.

Cross-compilation is as easy as setting the GOARCH environment variable, e.g. GOARCH=arm64 for targeting the Raspberry Pi 3. Notably, tools just work cross-platform, too! For example, I can profile gokrazy from my amd64 computer: go tool pprof ~/go/bin/linux_arm64/dhcp http://gokrazy:3112/debug/pprof/heap.

godoc displays documentation as plain text or serves it via HTTP. godoc.org is a public instance, but I run a local one to use while offline or for not yet published packages.

Note that these are standard tools coming with the language. Coming from C, each of the above would be a significant feat to accomplish. In Go, we take them for granted.

Getting started

Hopefully I was able to convey why I’m happy working with Go.

If you’re interested in getting started with Go, check out the beginner’s resources we point people to when they join the Gophers slack channel. See https://golang.org/help/.

Caveats

Of course, no programming tool is entirely free of problems. Given that this article explains why Go is my favorite programming language, it focuses on the positives. I will mention a few issues in passing, though:

  • If you use Go packages which don’t offer a stable API, you might want to use a specific, known-working version. Your best bet is the dep tool, which is not part of the language at the time of writing.
  • Idiomatic Go code does not necessarily translate to the highest performance machine code, and the runtime comes at a (small) cost. In the rare cases where I found performance lacking, I successfully resorted to cgo or assembler. If your domain is hard-realtime applications or otherwise extremely performance-critical code, your mileage may vary, though.
  • I wrote that the Go standard library is the best I have ever seen, but that doesn’t mean it doesn’t have any problems. One example is complicated handling of comments when modifying Go code programmatically via one of the standard library’s oldest packages, go/ast.