2016年6月18日 星期六

[Golang] 一個簡單的Mongo db proxy

之前被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所需要的時間(含網路)

成品

最後寫出的的成品在這: http://ift.tt/1UVnl1z

整個還蠻簡單的:

  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, 我定義成這樣: go 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: http://ift.tt/1UVniD7 , 這個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的應用上



from Le murmure de Julian http://ift.tt/1UDwOvO
via IFTTT

2016年6月16日 星期四

又再把Blog搬家了

一直把Blog當作紀錄一些事情用的地方, 想法, 或是學習過的一些東西, 雖然不是很頻繁的在寫, 不過也寫了好幾年了, 從以前到現在, 換過好幾次平台, 從自己架的wordpress, 到posterous, blogger, 一直到現在用的Tumblr, 這中間最喜歡的還是posterous, 不過它已經不存在了, 而Tumblr也用了好一陣子了, 雖然加減湊合著用好像還OK, 不過編輯上也一直不是那麼順手, 尤其是貼source code, 因此也一直想把它換掉

medium 呢? medium我還沒很有動力去搞懂它, 會在上面看一些文章沒錯, 但把Blog移到上面去, 好像也沒啥動力

考慮了好一陣子, 不過最後還是把整個Blog遷移了, 反正現在有一個自己的domain (blog.jln.co), 搬家不用改地址!

遷移之前, 想了幾個我的需求:

需求

  1. 有方便的編輯器可以用, 最好可以支援mark down, 打html有點麻煩, WYSIWYG編輯器通常效率也不高
  2. 貼code好貼, 也方便閱讀
  3. 模版好編輯, 至少我要能知道怎麼改模版
  4. 好預覽
  5. 能同步到各個social network, 至少Facebook, Twitter, 也能同步到我原本的Tumblr和blogger
  6. 要能夠友善支援Open graph和Twitter card, 對每次share的FB的醜醜文字版型實在不喜歡
  7. 好轉移, 好備份, 有版本控制更好!

解決方案

現成的blog service, 好的什麼都要錢(ghost, wordpress), 免費的大概也都被我用過了, 自己架, 又得管server, 後來想想, blog都是靜態的網頁, 也真的不需要一個很複雜的系統, 這時候就想到一個解決方案, 那就是Github

Github有一個Github pages的服務, 可以讓你host你的靜態網頁, 所以只要有一個方法可以把Blog轉成靜態網頁就可以了, 這個還蠻Geek的方式感覺就蠻適合我的

找到了幾個方法轉blog:

  1. Jekyll
  2. Octopress
  3. Hexo

Octopress是已經完結不再維護了, 所以就Jekyll和Hexo兩個在抉擇, 兩者都有蠻多的主題跟模版功能, 也有不少plugins

Hexo是台灣精品, 而且是nodekjs寫的, 語言上我比較熟, 要改比較好改,而Jekyll是Ruby寫的, 我跟Ruby很不熟, 但它跟Github pages結合緊密, 而且已經有tool可以從Tumblr移轉內容過來了, 最後我選擇了Jekyll, 選擇Jekyll的原因是

  1. Github page直接原生支援, 只要把md檔push上去後, 就會自動產生網頁(不過後來我還是先在自己電腦產生完再push, 後面再說原因)
  2. 找到一個適合的也不太難改的theme, 就懶得去翻Hexo的了, 而且找到的那個對Open graph和Twitter card的支援也不錯, 不用改太多
  3. 原生的Tumblr import tool

不過整個也花了不少個晚上修改, 又花了一個晚上才能寫完這篇紀錄 @@

安裝Jekyll

網路上可以找到很多安裝Jekyll的文章, 這邊就不說太多, 我試了兩個不同的作法:

作法 1

這是最基本的作法

  1. 先用 gem install jekyll 安裝jekyll
  2. jekyll new myblog 在myblog目錄產生一個基本的網站
  3. jekyll s 會在 localhost:4000 開啟一個服務, 這時就透過browser可以看看你的blog長怎樣了

這方法的缺點是, 套用主題時, 還得改一堆東西, 有點麻煩

作法 2
  1. 一樣先裝jekyll
  2. 找到一個人家做好的theme (我是用hpstr這個), clone下來改, 因為大部分的都已經把他用的plugin之類的都先寫好在config了, 就省不少事, 建議找theme要注意其對應的Jekyll版本, 像我裝的Jekyll是3, 所以找的是適合3的
  3. gem install bundler 安裝bundler
  4. bundle install 來安裝相關的plugins
  5. jekyll s 看結果

我後來採用的是作法2, 踩在人家的肩膀上比較快 :P

設定github pages

這部份沒什麼難度, 創建一個專案名稱叫做 你的名字.github.com , 把相關檔案放到這邊去就行了

Github pages支援兩種方式, 一種是純粹的靜態網頁, 就是把html跟其他相關檔案丟到這邊就好(master branch), 另一種方式就是Jekyll, Jekyll的部分只要把前一個動作的檔案放進來就好, 它會自己幫你產生對應的靜態網頁

不過第二種方法的缺點是因為Github pages只支援幾種Jekyll plugins, 而且不支援自訂的plugins(放在 _plugins 目錄下的), 就算你在你自己電腦裡面跑完把產生後的 _site 目錄一起放上去也是沒用的

從Tumblr匯入之前文章

這部份沒太複雜, 是透過jekyll-import

安裝jekyll-import一樣可以透過gem install來安裝, 但建議不要這樣做, 是因為他用的Tumblr API是JSONP但程式卻將它當一般JSON在解碼, 會出錯, 這部份在最新版的code有解決(害我還去追了source code), 所以抓source回來自己build比較安全

裝好之後執行:

#!/bin/sh
ruby -rubygems -e 'require "jekyll-import";
    JekyllImport::Importers::Tumblr.run({
      "url"            => "http://blog.jln.co",
      "format"         => "html", # or "md"
      "grab_images"    => true,  # whether to download images as well.
      "add_highlights" => false,  # whether to wrap code blocks (indented 4 spaces) in a Liquid "highlight" tag
      "rewrite_urls"   => false   # whether to write pages that redirect from the old Tumblr paths to the new Jekyll paths
    })'

它就會把文章抓回到 _post的目錄下

Open graph & Twitter card

如果把內容分享到Facebook或是Twitter上只有短短的文字, 不是很好看, 所以希望在這部份能夠至少加上一張圖

hpstr這個theme已經有在 ** _includes/head.html ** 寫好OG跟Twitter card相關的tag了, 圖片(og:image)的部分, 他的規則/順序是:

  1. Front matter 裡面設定的
  2. _config.xml 裡的logo設定

我希望是更自動一點, 而不是自己去設定, 所以找到一個plugin : jekyll-auto-image, 這個plugin聰明一點, 他的順序是:

  1. 內文內出現的第一張圖
  2. _config.xml 裡面設定的圖

但這樣還稍嫌單調一些, 我想要達成的是:

  1. 內文內出現的第一張圖
  2. 根據標籤選的對應的圖(比如說把我go語言相關的設成Gopher的圖片), 這樣不會每個都一樣, 比較有變化一點
  3. 預設的圖

再加上auto image裡面關於image的設定跟我用的theme有所衝突, 這就需要把原本的auto image改一下了

我新增一個rb檔, 放在 ** _plugins **目錄下, 內容如下:

require "jekyll"

module Jekyll

  class AutoImageGenerator < Generator

    def generate(site)
      @site = site

      site.pages.each do |page|
        img = get_image(page)
        page.data['image1'] = img if img
      end
      # Now do the same with posts
      site.posts.docs.each do |post|
        #puts "hola"
        #puts Jekyll::VERSION
        #puts post.class
        #puts post.inspect
        #puts post.data.inspect
        #puts "-----"      
        #puts post.output
        #puts "----"
        img = get_image(post)
        post.data['thumb'] = img if img
      end
    end # generate

    def get_image(page)

      if page.data['thumb']
        return page.data['thumb']
      end
      # convert the contents to html, and extract the first <img src="" apearance
      # I know, it's not efficient, but rather easy to implement :)

      if page.class == Jekyll::Document # for jekyll 3.0 posts & collections
        htmled = Jekyll::Renderer.new(@site, page, @site.site_payload).convert(page.content)
      else
        htmled = page.transform # for jekyll 2.x pages
      end

      img_url = htmled.match(/<img.*\ssrc=[\"\']([\S.]+)[\"\']/i)
      return img_url[1] if img_url != nil

      tags = page.data['tags']
      imagemap = @site.config['imagemap']
      if tags != nil && imagemap != nil
        tags.each do |t|
          if imagemap[t.downcase] != nil
            url = imagemap[t.downcase]

            if !url.match(/^http/)
              url = '/images/' + url
            end
            return url
          end
        end
      end

      return @site.config['logo'] if @site.config['logo'] != nil
      return nil
    end

  end # class
end # module

這版就有達到我想要的目的, 但這不代表問題解了, 反而是問題的開始, 前面有說過github只支援特定的plugins, 所以怎樣這段改過的plugin都不會被它執行到, 唯一能做的解法就是改用靜態網頁的方式

jekyll產生的靜態網頁都放在 _site 目錄下, 所以真的要進master branch只有這裡面的, 其他都不用, 所以要先在本地端用 jekyll b去產生相關的靜態網頁到 _site裡再上傳

那怎測試呢? 一開始為了測試方便我都以本地端的server做測試, 但如果要測試Facebook share的話則需要能夠讓Facebook 連過來抓取網頁來分析, 但這點在區網不是很方便, 所以就帶入了 ngrok 這個工具, 這工具就是提供給你一個外部的網址, 你在本地端跑了ngrok這程式後, 就有一個可以對應你本地端的server, 還蠻好用的

自動發布到社群網站

這當然是用ifttt囉, 因為有用到jekyll-feed這個plugin, 所以會產生一個feed.xml的檔案, 所以就利用ifttt的feed channel來設定觸發

結語

本來明明不想花太多功夫的, 結果不但花了很多功夫, 還花了一頁來紀錄這篇 orz



from Le murmure de Julian http://ift.tt/1OszPie
via IFTTT

2016年6月15日 星期三

Hey blog test

This is to test my own blog

header 1

header 2

console.log("111")

test test

Header1

Header2

Header3

GoH1, GoH2, GoH3



from Le murmure de Julian http://ift.tt/1UW76yv
via IFTTT

Hey blog test

This is to test my own blog

header 1

header 2

console.log("111")

test test

Header1

Header2

Header3

GoH1, GoH2, GoH3



from Le murmure de Julian http://ift.tt/1UW76yv
via IFTTT

test

test test



from Le murmure de Julian http://ift.tt/1S59cKL
via IFTTT

test222

sssss

wereetrereter ### ressr

test # 2 # 1

  1. 22
  2. 22
  3. 222


from Le murmure de Julian http://ift.tt/1YrkWia
via IFTTT

test from jekyll 2

12223

sress



from Le murmure de Julian http://ift.tt/1VYHC8M
via IFTTT

test from jekyll

==Test is test



from Le murmure de Julian http://ift.tt/1PtdADF
via IFTTT