2016年12月1日 星期四

golang是用同樣的記憶體來存放 for range 取得的變數

在切片裡面走訪的時候,利用range所取出的變數,在記憶體中是同一個位置。不能利用此位置來區別切片裡的內容。例如:


package main

import (
 "fmt"
)

func main() {
 cars := []string{"toyota","honda","bmw"}

 for idx,car := range cars{

        fmt.Printf("car at:%p\n", &car)

        fmt.Printf("cars[%d] at:%p\n", idx,&cars[idx])
 }
}



https://play.golang.org/p/JuH5WdOw7q


這段程式碼跑的結果是這樣:

car at:0x1040a120
cars[0] at:0x10432200
car at:0x1040a120
cars[1] at:0x10432208
car at:0x1040a120
cars[2] at:0x10432210


每次從切片取出來的內容都是放到同一個 car。
因此不能利用car這個變數的位置來作為後續使用,而是要直接用idx索引 cars。



2016年6月20日 星期一

Use context to cancel a function




我曾經使用到一個函數,是要帶入 context.Background。然後這個函數就等在裡面了,直到有回應才回傳結果出來。最近要將這個函數強制的跳出,一時間還不知道該怎麼做。苦惱之際看到了這個函數帶入了 context.Background(),於是就從context這個庫去爬文。於是先找到了


http://studygolang.com/articles/5131


裡面重點的說明了原文裡面 context 的原理與使用方法。原文在這兒:

https://godoc.org/golang.org/x/net/context

還有Google使用context的博客

http://blog.golang.org/context


前情提要講完了,現在直些貼程式碼該怎麼強制跳出一個帶入了context.Background的函數呢?


原本是:

err = aBlockedFunction(context.Background())

想要能夠跳出,就利用WithCancel來生一個cancel函數出來:

ctx,cancel := context.WithCancel( context.Background() )然後放進去
err = aBlockedFunction(ctx)
這樣就能在想要的時機點,呼叫cancel就能夠將旗下的子孫都清除掉,然後從函式返回了。
這也提醒了我,應該來用context來管理資源,這樣子就能夠很容易寫出讓別人cancel的函數了


2016年4月11日 星期一

讀取 http Body 而不會造成 proxy error: http: ContentLength=xx with Body length 0

如果想要看http request內容,看完以後再決定要不要代理到後端服務。要開一個 Proxy 的方法如下:

director := func(req *http.Request) {
    req = r
    req.URL.Scheme = "http"    req.URL.Host = "localhost:2379"}
proxy := &httputil.ReverseProxy{Director: director}
proxy.ServeHTTP(w, r)



在Proxy之前,如果要讀取request body,需要使用ReadAll來把Body讀出來,像這樣子

body,err:=ioutil.ReadAll(reader)

ReadAll返回了資料以後,Body的索引也都會改變。這時去呼叫Proxy的程序則會產生這樣的錯誤: http: proxy error: http: ContentLength=153 with Body length 0
下面的方式是可以讀取Body,而且又不影響到Proxy運作:

b := bytes.NewBuffer(make([]byte, 0))
reader := io.TeeReader(r.Body, b)

body,err:=ioutil.ReadAll(reader)
if err != nil {
    Error(err)
    return}
r.Body = ioutil.NopCloser(b)


為了讓Proxy能夠繼續運作,TeeReader從r.Body複製資料到b裡面並返回一個Reader。過程中不會有其他資料的複製。最後再把複製的資料b放回去r.Body。




2015年12月31日 星期四

golang Anonymous Functions and Closures 心得

golang Anonymous Functions and Closures 心得
===
這個方式可以讓你在同一個A func 的scope裡面,直接在某個函數裡面放入一個B func callback函數。
如果要在C語言裡面要放這種callback函數,必須在A func裡面某行先填入一個function pointer,然後等到A func返回以後才能有機會呼叫callback
但常常B func callback裡面想要直接調用A func裡面的變數。C語言裡面一定要動態配置才能將這變數儲存起來,返回以後傳入到B func callback,否則就需要動用到傳入到A的結構內容,如果沒有結構傳入A,就還要在呼叫A的地方配置一個結構,利用call by reference的方式傳回B func callback需要的資料。這樣不僅牽涉的很廣,也一再挑戰人類的懶惰。

go裡面不再需要了弄function pointer了,callback直接寫在已定義好為函數的變數上面。這樣子A func所屬的結構,可以直接在callback裡面去存取。不須動態配置、不須修改結構。對於追求agile的工程師來說,可以常常重構常常減少重複程式碼,真是大快人心。


  • 範例

原本這動作是要走訪所有的FileList[key],然後對每一個key做一種動作。此範例是有找到key就刪除。
func xxx(){
...
for key,value := range bj.Root.FileList {
_,ok := fileListMap[key]
if ok {
delete(bj.Root.FileList,key)
}
}
...
}

但是如果之後需要對 fileListMap[key] 其中的 value做判斷才決定要刪除的話,那就非得要整段程式碼複製貼上了。
別以為這短短的程式碼複製貼上很簡單容易,這只是一個範例所以很短。如果是有一堆複雜判斷的時候,複製貼上就代表這個程式碼逐漸腐敗。
過去C語言有上述的麻煩,因此就是copy past解決是最快。但是違反了agile精神,也就是不寫重複的程式碼。
如果是go就可以將重複的程式碼寫成函數walkInKey,並直接在上述的程式碼位置加上Closure。如下:
func (bj *BackupJson) walkInKey( w WalkFunc ){
for key,value := range bj.Root.FileList {
if w( key,value )==false {
delete(bj.Root.FileList,key)
}
}
}
func xxx(){
...
ks.walkInKey( func ( key string ,wasUpload bool)bool {
_,ok := fileListMap[key]
if ok {
return true
}
return false
})
...
}


這樣一來,如果有其他種判斷需要將key-value刪除。直接修改Closure內容return true or false就可以了。為golang又記上一筆agile friendly經驗


golang 多型運用心得

golang 多型運用心得
===
一開始想找一個kay-value的儲存方式,先找到了一個gkvlite的開源項目。經過測試以後也覺得應該適用,於是使用了之後發現到它轉成json的方式好像不如預期。
後來想一想,那倒不如就用map轉json的方式存就好了,這樣也沒有開源bug的風險,自己有bug也好除錯。但是已經實做gkvlite下去了,也幫它寫好了struct以及一些包裝函數。
這時候又思考,要換成json的話需要打掉重練嗎?還是應該弄一個介面出來變成多形的方式,讓兩種方式並存。即使現在覺得gkvlite可能永遠不用,但哪怕有一天還會用到。
心中懶惰的想法這時又油然而生,砍掉重練比較輕鬆啦,不要搞甚麼多型累死了。另一方面較為專業的想法會想要累積這兩種儲存方式,以便未來再重複使用。
我想這個情況就是利用多形是最好時候,因此我把心中的懶惰蟲給抓出來,面對它吧!以下是多形重構過程:

type BackupDB struct{
f *os.File
c *gkvlite.Collection
s *gkvlite.Store
notify chan BackupNote
}
func openBackupDB( dbfile string ) *BackupDB {
...
}
func ( db *BackupDB )closeStore() {
...
}
func ( db *BackupDB )flushStore() {
...
}

其實原本沒有這麼整齊每一個函數都有用到receiver,而是為了要多形而先補齊這樣抽像化的結構。
花一點時間整理好之後就可以開始來建立另一個以json儲存的方式了


type BackupJson struct{
f *os.File
}
func openBackupJson( dbfile string ) *BackupJson {
bj := &BackupJson{}
f,err := os.Create( dbfile )
if err != nil{
Error(err)
return nil
}
bj.f = f
return bj
}
func ( bj *BackupJson )closeStore() {
}
func ( bj *BackupJson )flushStore() {
}
func ( bj *BackupJson )deleteKey( key *string ) bool{
wasDeleted := true
return wasDeleted
}
func ( bj *BackupJson )setKey( key *string ) bool{
wasSet := true
return wasSet
}


很快地寫完函數規格但還不能動的版本,兩種方法都有一樣的函數宣告,只有receiver不同,這樣子正洽好就是golang的多形規則。這時候就可以宣告一個interface


type KeyStore interface{
setKey( key *string , value *string ) bool
deleteKey( key *string ) bool
closeStore()
flushStore()
}

最後一步就是在使用KeyStore的地方兩個方法擇一執行,使用指派的方式去轉成interface操作


var ks KeyStore
if ( DB_SELECT == JSON ){
ks = KeyStore( openBackupJson( FILELIST_PATH ) )
}else{
ks = KeyStore( openBackupDB( FILELIST_PATH ) )
}
  ks.setKey( &file , &value )
ks.closeStore()


這樣,只要指派不同的結構,就可以將兩個方法給結合起來,之後只要面對KeyStore介面就可以了。
過去C語言裡面會用#ifdef來做這個區分,很偷懶很難看但是很實用。但是用#ifdef也就宣告了兩邊的程式碼互相無關,也不會再為了對方修改了。
其實是因為go裡面如果用不到的func會是個錯誤,從這邊為源頭,想要讓自己的code更好才會想要再進一步用多型,所以go的語言內涵就包含了除去人們懶惰的習慣的功能...









-

2014年11月23日 星期日

Docker心得,以及Container怎麼自動加入本地域名

基於敏捷開發,我希望軟體開發的時候能夠隨時的自動測試,這樣才能夠隨心所欲的重構以及保證有基本的品質。在開發雲端應用的時候,常常會有很多台的伺服器需要相互運作。過去虛擬化的做法,常常是利用類似VMWare的虛擬機來建立整個機器。因為是虛擬化整台機器,每次安裝的速度之慢,根本不能拿來做自動化測試。尤其是在設定伺服器的過程中,常常就是Operation的領域,各出奇招,只要伺服器能照著自己的"魔術"正常運作就好了,至於要其他人看懂或接手,那就是另一個考驗了。

Docker,就是拿來解決這種VM開啟速度慢、耗費資源高的問題,但是又能讓應用程式互相跑得好像在不同機器上一樣的獨立。不只如此,Dockerfile的文件可以讓整個檔案結構、相依套件都好好的用同一種格式寫好,這樣一來就不是Operation個人的魔術了。我自己把Docker看做是一個綠色軟體的產生器,因為每一個應用包在Container裡面,都已具備所有的相依套件。如果想要公開自己開發的服務,給個Dockerfile再給個簡單的docker run,完成。裝好Container以後,保證能跑!所以現在很多套件都已經支援了Docker,也都附上了Dockerfile。

而基於敏捷開發,想要快速的增加開發雲端服務的迭代次數,就是非Docker莫屬了。利用Docker從官方的發布版本為基礎(ubuntu, centos, dabian)開始建立整套雲端服務,速度非常之快,已經達到能夠每次commit都自動測試的次數。但是我遭遇了一個問題:啟動了一個Container以後要怎麼知道它的IP呢?這個問題在過去的網路服務的架構早就已經遭遇過了,只是從不常關機的Virtual Private Server(VPS)換成變化更快的Container罷了。這麼頻繁的開啟伺服器,就必須要有Service Discover的機制。否則整天去管理開開關關的Container就夠煩的了。

待續....skydns + skydock

2014年7月24日 星期四

別讓make的頻率過快,gc不是隨時都在作用

我以為使用俱有gc的語言,就可以為所欲為的配置記憶體(反正有gc在後面擦屁股)。但是從C寫系統程式上來的經驗告訴我:真的有這麼好嗎?雖然抱持著懷疑,但還是先下手寫點code吧。

於是我開始寫go,不疑有他、放心的make([]byte,hugeSize),直到最近的案子中,需要模擬5萬條長連線到一臺測試機器。只送出了幾千個連線請求後,就被OOM Killer 關心了。code大概是這樣:

for {
        bigArray := make([]byte, 1024*1024*10)
readFromTcp(bigArray)
}

debug 之後讓我發現不要在一個for迴圈裏面make,尤其是我的測試需要跑5萬個迴圈,雖然每次回圈的make只用來讀取一次,但是gc並不會“瞬間”就把剛剛沒用到的記憶體清掉。因此,要善用每一次的make,盡可能地重複使用它。

bigArray := make([]byte, 1024*1024*10)
for {
readFromTcp(bigArray)
}

另外,參考 io.Copy() 裡面的實作方式,是在回圈之外先make一個小塊的記憶體,再用回圈的方式去將資料全部複製。

buf := make([]byte, 32*1024)
for {
nr, er := src.Read(buf)
if nr > 0 {
nw, ew := dst.Write(buf[0:nr])
if nw > 0 {

但,如果 io.Copy 頻繁的呼叫(1024次/s),記憶體以近32M/s的速度消耗,又該怎麼辦?可能就不能使用io.Copy,而是自己管理記憶體了。

有人提出以buffer pool搭配channel來明確(explicit)的還回記憶體,來減少make的頻率。在記憶體未使用完前,讓gc有機會工作。http://blog.cloudflare.com/recycling-memory-buffers-in-go