之前被Parse搞的半死, 一直很好奇它的API到Mongodb的request之間到底是怎樣的對應

要弄清楚這個其實也不難, 把Mongodb的profiler全打開去看log就好了(db.setProfilingLevel(2)), 但這也是有缺點, profiler會寫到system.profile這個collection去, 而它是固定大小, 不能無限制的放, 再加上它還要多寫入這段, 多多少少影響效能

我需要的是一個從外部來觀察的工具, 不會影響到DB本身, 並且也可以將網路本身所花費的時間也包含進去, 所以想到的是在中間插一個proxy server

在現成的工具找到一個叫MonoDB Proxy的工具, 這是用nodejs寫的, 勉強可以, 也證明了這個方法是可行的, 但這工具雖然有做到代理這部份, 但在log部分, 由於它並未解析bson, 所以詳細的內容並不好看, 所以就自己來寫一個

功能需求

  1. 支援mongodb wire protocol, 而不是只是單純的轉送資料
  2. 印出request跟response內JSON的內容
  3. 要能夠知道每個request所需要的時間(含網路)

成品

最後寫出的的成品在這: https://github.com/julianshen/mongoproxy

整個還蠻簡單的:

  1. wire.go 實作wire protocol
  2. proxy.go 實作從client收資料並轉寫到server端
  3. cmd/mp/main.go command line主程式的部分

使用方法

這個工具是用Go寫的, 所以使用之前需要先安裝go

安裝

go get julianshen/mongoproxy/mp

這個步驟做完後, 就可以把mp這個指令裝好了, 確定 $GOPATH/bin是在你路徑內, mp這個檔也是在那邊

使用

mp --port=6001 --remote=mydb:27017 --response

其中:

  1. port是你這個proxy server的服務點
  2. remote是遠端的mongodb (host:port)
  3. Reponse是需不需要log回傳的部分

實作Wire protocol

本來覺得Wire protocol會蠻複雜的, 結果, 其實是蠻簡單的

所有的wire protocol request都會有一個標準的表頭:

struct MsgHeader {
    int32   messageLength; // total message size, including this
    int32   requestID;     // identifier for this message
    int32   responseTo;    // requestID from the original request
                           //   (used in responses from db)
    int32   opCode;        // request type - see table below
}

對應golang, 我定義成這樣:

type MsgHeader struct {
	MessageLength int32 // total message size, including this
	RequestID     int32 // identifier for this message
	ResponseTo    int32 // requestID from the original request
	//   (used in responses from db)
	Opcode // request type - see table below
}

因為一開始就可以讀到整個訊息長度的, 所以就蠻好解析的, wire protocol的實作我是有參考了dvara, 本來是有想拿它的code來改, 但看了一下發現它也沒完整實作wire protocol, 秉著自己也來了解一下這部份的想法, 就重頭自己刻了

dvara不同的地方是, 我用go的binary package來讀header而非自己刻一個, binary.Read的確是一個蠻好用的工具, 用底下的code就可以讀出header這個資料結構:

h := MsgHeader{}
err := binary.Read(r, binary.LittleEndian, &h)

另外, 除header外, 各request的所帶的欄位各自不同, 這部份的作法就是定好各個所需的資料結構, 用reflection的方式來讀取各相關資料:

v := reflect.ValueOf(req)
v = v.Elem()

// 根據資料結構內定義的每個欄位用相關的方法讀取
for i := 0; i < v.NumField(); i++ {
    f := v.Field(i)
    t := f.Type()

    if bytesRead == int(h.MessageLength) {
        break
    } else if bytesRead > int(h.MessageLength) {
        return nil, ErrorWrongLen
    }

    switch {
    case t == reflect.TypeOf((bson.D)(nil)):
        d, n, e := readDoc(bufferReader)

解析bson

這部份就沒再重新造輪子了, 直接用golang著名的mongodb driver mgo裡的bson lib: https://godoc.org/gopkg.in/mgo.v2/bson , 這個bson lib已經寫的很不錯了, 直接拿來用即可

在這個package內, 泛用的bson資料結構有兩種: bson.Mbson.D

這兩個是不同用途的,仔細看一下M跟D的定義:

type M map[string]interface{}

type D []DocElem

如果你是要把解析出的資料用map來操作, M是蠻方便的, 一開始我也是依著之前我寫相關的東西的習慣用M, 不過這邊卻是不可以用M的, 這也是我碰到bug的地方

由於M解析出的是Map, 所以每個field的順序它並沒記住, 但偏偏在wire protocol裡, 尤其是 $cmd, 順序是重要的, 所以Unmarshal出的M再Marshal回去, 順序可能不是原本的順序了, 而這在這個proxy應用上, client寫什麼東西過來就要寫什麼到server才不至於出錯

測試和Debug

我是用Wireshark來驗證我的實作有沒問題, Wireshark預設會把到27017 port的資料解析成wire protocol的內容供閱讀, 當然也可以自己手動請它解析

其他應用

這樣的proxy應該可以不只應用在debug, 像是Parse open source的dvara, 它也是利用了proxy來做connection pooling, 應該也可以用在request routing和caching的應用上

目前Json已經可以說是Internet上相當流行的格式了, 雖然說他缺點還算蠻多的, 但很多主流的程式語言都有成熟的parser可供使用,  在Golang則可利用內建的encoding/json這個package來達成

用“encoding/json”來解析Json其實很簡單, 基本上就是建立一個Decoder然後把json內容”解”到你對應的資料結構去, 可以參考這範例: http://golang.org/pkg/encoding/json/#example_Decoder

但在某些狀況下並不是很好的解法, 舉個例子來說, 我這兩天打算拿從Parse那邊匯出的使用者資料來做一些處理, Parse輸出給我的JSON檔大約長得像這樣: 

{
       results: [
             {
                  “objectId”:”zzreueaWq4″,
                  “username”:”julian”,
             }
       ]
}

按照這定義寫出的程式碼就類似這樣:

但這有一個很大的問題, Parse那邊輸出給我的Json檔就有400MB之多(二十多萬筆), 可想而知的, 這程式直接先因為out of memory炸掉了, Decode需要把所有內容載入到記憶體才做解析, 而且以這範例, 解析完的結果全部存到我們的資料結構內, 自然有這問題, 常常這種應用我們都不是很需要把所有內容在記憶體放一份, 而是來一個處理一個

這時候就需要用Streaming parser的作法來解決這問題了, Golang的Json package也有支援這一模式, 但這也是在最新的1.5之後才有支援這模式, 使用這方法, 程式可以改成如下:

在這邊由於我只需要results這陣列的內容而已, 因此我先用Token找到第一個”[”(以這範例來說, 第一個”[”代表的就是results的陣列了), 之後就是用迴圈一個個去解些個別的User物件, 由於這20多萬筆資料, 我也只是要印出來而已, 我也不用特地用一個大的資料結構來存, 自然記憶體的問題就比較沒那麼吃緊

最近在嘗試寫一些動態gif的應用, 所以就研究了一下怎用go去產生動態gif, 後來找到以下這篇:

Go言語で画像の減色を行う

其實這篇已經寫的蠻完整的了, 不過由它是日文(有很多日文的技術文章其實還不錯), 所以我嘗試用我自己的方法用中文再闡釋一遍

要用輸出動態gif其實go就已經支援了, 使用EncodeAll即可:

https://golang.org/pkg/image/gif/#EncodeAll

不過, 仔細看一下GIF這個struct裡的那個Image陣列, 接受的是Paletted, 也就是附有調色盤的圖(色彩較少), 而現在大部分的圖片大多是全彩的, 因此, 需要先把全彩的圖轉成Paletted, 而這部份的作法是利用image.draw裡的Draw把全彩的圖畫到一個新的Paletted去:

palImg := image.NewPaletted(img.Bounds(), palette.WebSafe)
draw.Draw(palImg, img.Bounds(), img, image.ZP, draw.Over)

由於新創一個Paletted圖, 需要給它一個調色盤, 最簡單的方法是給它預設的調色盤, 在image.color.palette裡有兩個(plan9, web safe), 這邊採用了比較通用的Web safe, 最後輸出的結果如下:

呃, 好醜…..不過這是一定的, web safe一是組比較通用標準的色盤, 但大多數的顏色跟原本的圖的色彩差異可能相當的大, draw.Draw是在調色盤找出相近色, 但有可能找出來這顏色差異就很大了

那怎麼辦?我們就必須對全彩影像找出最佳的調色盤, 從成千上萬色中找出適合的256種顏色, 這在影像處理技巧中有個叫Color quantization, 它就是用來處理這樣的狀況的

在image.draw裡也找到了一個Quantizer:

https://golang.org/src/image/draw/draw.go?s=688:910#L17

不過看來這個Quantizer只有空殼(interface)並未有實作(也就是這樣我才去找到那篇日文的參考文章), 雖說, 實作一個median cut演算法不難, 但對懶人來說, 能有現成的當然最好, 從那篇參考文章中看到了, 有人幾經寫好的(更不錯的是已經實作了Quantizer) -

https://github.com/soniakeys/quant/

使用它後, 先前得程式碼可以簡單的改成 -

q := median.Quantizer(256) //256個顏色
palImg := q.Image(img)

得到的結果會優很多:

這樣好很多了吧?不過還不是最佳的, 由於顏色數變少了, 有些漸層的地方(比如說範例裡企鵝的脖子)會變成一條條帶狀的, 所以我們還需要做dithering, 在Go的image.draw裡其實已經有實作了Floyd Steinberg演算法, 因此可以用這個來做dithering, 程式就改成:

palette := q.Quantize(make(color.Palette, 0, 256), img)
palImg := image.NewPaletted(img.Bounds(), palette)
draw.FloydSteinberg.Draw(palImg, img.Bounds(), img, image.ZP)

出來的結果是這樣:

細部的差異差不多像這樣:

第二張是有dithering的(怎感覺比較醜)

總之, Happy GIFing~~~

會寫這東西, 起因是上週跟同事聊到, 在social network上, 尤其是我們自己的ㄍservice, 似乎很多人不習慣或是會沒設大頭照, 如果有一個可以自動產生不同的大頭照的服務, 應該會不錯, 所以就用週末寫了這個小程式, 先給兒子試玩了一下, 他對改個東西就能產生變化還頗有興趣的, 以後再來教他寫, 哈 

Source code在此: github.com/julianshen/goticon

有了動機後, 突然想到, Github也是會給沒設大頭照的人設定一張方格圖像當預設大頭照, 所以第一個方向就是拷貝出一個這樣的功能:

這樣的東西並不難寫, 它只是一個5x5的方格, 我把名字用SHA512算出一個固定長度的binary string (5x5有25個方格, 我打算每格是1byte, 所以至少要有25 bytes), 每一byte如果大於某一數就填滿對應那格, 本來我的設定是128, 不過後來想說這樣如果有些極端的案例可能造成方格太多或太少, 因此這個數我取前25 bytes的中位數, 至於顏色, 我採用了一個叫go-colorful的lib去產生隨機的暖色, 以下就是我做出的結果:

不過這離有趣還有一些些距離, 所以又找到一個應用: https://github.com/matveyco/8biticon 

這是一個做8bits人偶的應用程式(open source, MIT License), 不過它是一個web app, 然後讓使用者自己拼湊, 這當然跟我懶人的目的不同, 我是希望只要有名字就自動產生, 所以就利用了它的圖, 自己來疊囉, 這比上面那個稍稍有趣點但更簡單, 只是依照對應的資料選出不一樣的圖疊起來而已:

我把這整個應用放到heroku去了(我原始碼也已經用godep整理好了, 有興趣的人也可以自行deploy到heroku去修改測試)”

有興趣的可以用以下的URL產生自己的identicon:

  1. Github style: https://goticon.herokuapp.com/i/g/julianshen (把julianshen取代成你自己名字就可以了)
  2. 8 bits style: https://goticon.herokuapp.com/i/8/m/julianshen (一樣換掉名字, 如果你是女生, 把”m”(male)換成”f”(female)就好, 像: https://goticon.herokuapp.com/i/8/f/julianshen

自己想做的東西可能需要一個UUID的產生器, 找了一下既有的packages跟網路上的, 好像沒一個自己滿意的, 所以就自己從java.util.UUID po過來

Source在此: http://bit.ly/X3w0V7

目前只implement RFC 4122 Version 4

Sample:



via Blogger http://bit.ly/X3w0Vb