[Golang] 利用build tags達到不同的build config

##前言

image

為了要寫一個Web Service但是有可能會發生以下的需求:

  • 不同Server,有不同的設定參數
  • 不同的OS有不同的command set
  • 想要快速的開啟或是關閉debug log

在有以上的需求的時候,一開始都是使用OS偵測或是configuration file來區隔.但是到後其實是希望能透過不同的build config能夠產生不同的binary.

決定研究了一下: go build 有以下兩種方式可以達到部分的效果.

##Go build -ldflags

這可以在go build的時候,先設定一些變數的名稱.通常我自己比較習慣透過OS環境變數來設定,然後程式裡面再去讀取.

在你的主程式裡面,可以先定義一個變數flagString:

package main

import (
	"fmt"
)

var flagString string

func main() {
	fmt.Println("This build with ldflag:", flagString)
}

透過外在go build來設定這個變數 go build -ldflags '-X main.flagString "test"' 這樣你的結果會是

>> This build with ldflag: test

這個方式可以直接設定參數,讓initialize value透過外部設定來跑.

##Go build -tags

透過go build -tags 可以達到加入不同的檔案在compiling time.由於這樣,你能夠放在這個檔案裡面的東西就有很多.可以是:

  • 不同系列的define (達到ifdef的效果)
  • 不同的function implement (達到同一個function 在不同設定下有不同實現)

以下增加一個簡單的範例,來達到不同的build config可以載入不同的define value.

file: debug_config.go

//+build debug

package main

var TestString string = "test debug"
var TestString2 string = " and it will run every module with debug tag."

func GetConfigString() string {
	return "it is debug....."
}

請注意: //+build debug 前後需要一個空行(除非你在第一行)

另外,我們也有一般的設定檔 release_config.go

//+build !debug

package main

var TestString string = "test release"
var TestString2 string = " and it will run every module as no debug tag."

func GetConfigString() string {
	return "it is release....."
}

最後在主要的main裡面,可以直接去參考這兩個define value file: main.go

package main

import (
	"fmt"
)

func main() {
	fmt.Println("This build is ", TestString, TestString2, GetConfigString())
}

在這裡如果你是跑 go build -tags debug 那麼執行結果會是:

>> This build is  test debug  and it will run every module with debug tag. it is debug.....

如果跑的是 go build會預設去讀取!debug,那麼結果會是:

>> This build is  test release  and it will run every module as no debug tag. it is release.....

可以看到他不只可以加入define參數,也可以把不同function 有著不同的implement.

##使用時機討論與心得

使用上差異:

  • ldflags 可以加入一些參數,就跟gcc的ldflags 一樣-
  • tags 很像是 gcc -D 不過由於在檔案裡面要定義 //+build XXXX,感覺有點繁瑣. 不過由於可以以檔案來區隔,你可以加入多個定義值跟function

ldflags 使用時機:

個人認為可能可以拿來改變初始值得設定,或是去改變一些程式內的設定. **比如說: **

  • buffer value: 透過build 來改變buffer size,來做不同的測試與應用.
  • log flag: 決定要不要印log

tags 使用時機:

tags的使用時機相當的多,列出幾個我看到的:

  • debug log/encrypt : 定義debug func 然後再release/debug有不同的implement (印或是不印log),也可以在某些狀況下開啟或是關閉encrypt來測試.
  • 跨平台部分: 不同OS平台需要不同的設定與implement

其他更多的部分,可以看到在逐漸加上去.

##參考鏈結

[Golang]學習disque(一)

image (pic: source from here)

以上的圖是利用Redis 達到 Message Queue 的方式,也是Disque要達到的事情.

##前言

這一兩個月,比較常聽到大家討論的就是Disque的使用方式與疑問.本來我對於Message Queue的系統(尤其是backend那一塊)比較不熟.於是還是花了一點時間把Disque裝起來,並且把sample code跑了一下.希望對於基本架構有一些了解.

##關於Disque

DisqueRedis原作者Salvatore Sanfilippo根據大家在Redis上面針對Message Queue處理的部份來加強,並且下去拿Redis的source code加以修改,改造出這套專門處理Message Queue的系統.

主要特色如下: (參考這裡)

  • 消息發送可以選擇至少一次或者最多一次。
  • 消息需要消費者確認。
  • 如果沒有確認,會一直重發,直至到期。確認信息會廣播給擁有消息副本的所有結點,然後消息會被垃圾收集或者刪除。
  • 隊列是持久的。
  • Disque默認只運行在內存裡,持久性是通過同步備份實現的。
  • 隊列為了保證最大吞吐量,不是全局一致的,但會儘力提供排序。
  • 在壓力大的時候,消息不會丟棄,但會拒絶新的消息。
  • 消費者和生產者可以通過命令查看隊列中的消息。
  • 隊列盡力提供FIFO。
  • 一組master作為中介,客戶端可以與任一結點通信。
  • 中介有命名的隊列,無需消費者和生產者干預。
  • 消息發送是事務性的,保證集群中會有所需數量的副本。
  • 消息接收不是事務性的。
  • 消費者默認是接收時是阻塞的,但也可以選擇查看新消息。
  • 生產者在隊列滿時發新消息可以得到錯誤信息,也可以讓集群非同步地複製消息。
  • 支持延遲作業,粒度是秒,最久可以長達數年。但需要消耗內存。
  • 消費者和生產者可以連接不同的結點。

###優缺點與比較:

優點其實蠻容易被瞭解的:

  • 容易安裝使用,而且小.本身就有資料庫(類似 redis)
  • 當不需要太複雜的傳輸格式與介面的時候,disque效能應該不差 (base on Redis v.s. RabbitMQ)

幾個缺點,可能需要注意:

  • disque 目前還是alpha
  • disque 目前只有單線程

關於與Kafka的比較,Salvatore Sanfilippo在他的推特有以下有趣的回應:

    Salvatore Sanfilippo: Disque VS Kafka is the new Redis VS PosgreSQL which was the new Apple VS Orange :-)

##安裝與使用

###安裝與使用Disque

  • git clone https://github.com/antirez/disque
  • make
  • make test (make sure port and compiling result success.)
  • cd src
  • run disque-server

###簡單的操作

當你跑disque之後,就會自動連接local disque server.

    //Add a job by producer
    ADDJOB job1 "This is sample job1" 0   #create job1 with comment
    ->DI3b2954204f8f86168198266221515fb011a1eea005a0SQ #server response task id.
    
    //Get a job by consumer
    GETJOB from job1
    ->1) 1) "job1"
    ->2) "DI3b2954204f8f86168198266221515fb011a1eea005a0SQ"
    ->3) "This is sample job1"

透過Golang 來測試job-queue

主要是使用go-disque的source code 來當作job-queue 的機制.

以下有三段程式碼,分別是兩個worker跟一個發送者,發送者(disque-enque)會發送兩個訊息排程給disque,當worker上線或是開始抓取訊息後,就會執行該訊息定義的事項.

主要程式碼並沒有做太大修改:

Worker(Consumer 消費者): Downloader : 會下載指定網頁的資料

    package main
    
    import (
    	"github.com/EverythingMe/go-disque/tasque"
    
    	"crypto/md5"
    	"fmt"
    	"io"
    	"net/http"
    	"os"
    	// "time"
    )
    
    // Step 1: Define a handler that has a unique name
    var Downloader = tasque.FuncHandler(func(t *tasque.Task) error {
    
    	u := t.Params["url"].(string)
    	res, err := http.Get(u)
    	if err != nil {
    		return err
    	}
    	defer res.Body.Close()
    
    	fp, err := os.Create(fmt.Sprintf("/tmp/%x", md5.Sum([]byte(u))))
    	if err != nil {
    		return err
    	}
    	defer fp.Close()
    
    	if _, err := io.Copy(fp, res.Body); err != nil {
    		return err
    	}
    	fmt.Printf("Downloaded %s successfully\n", u)
    
    	return nil
    
    }, "download")
    
    // Step 2: Registering the handler and starting a Worker
    
    func main() {
    
    	// Worker with 10 concurrent goroutines. In real life scenarios set this to much higher values...
    	worker := tasque.NewWorker(10, "127.0.0.1:7711")
    
    	// register our downloader
    	worker.Handle(Downloader)
    
    	// Run the worker
    	worker.Run()
    
    }

Worker(Consumer 消費者): foo : 顯示指定字串

    package main
    
    import (
    	"fmt"
    	"github.com/EverythingMe/go-disque/tasque"
    )
    
    // Step 1: Define a handler that has a unique name
    var fooWorker = tasque.FuncHandler(func(t *tasque.Task) error {
    	u := t.Params["text"].(string)
    	fmt.Printf("foo worker runs, param is %s\n", u)
    	return nil
    
    }, "foo")
    
    // Step 2: Registering the handler and starting a Worker
    
    func main() {
    
    	// Worker with 10 concurrent goroutines. In real life scenarios set this to much higher values...
    	worker := tasque.NewWorker(10, "127.0.0.1:7711")
    
    	// register our downloader
    	worker.Handle(fooWorker)
    
    	// Run the worker
    	worker.Run()
    
    }

最後就是把工作派發出去的Enque (角色上被稱為是製造者 Producer)

package main

import (
	"fmt"
	"github.com/EverythingMe/go-disque/tasque"
	"time"
)

func main() {

	client := tasque.NewClient(5*time.Second, "127.0.0.1:7711")
	task := tasque.NewTask("download").Set("url", "http://google.com")
	err := client.Do(task)
	if err != nil {
		panic(err)
	}
	fmt.Println("First work queue run..")

	client = tasque.NewClient(5*time.Second, "127.0.0.1:7711")
	task = tasque.NewTask("foo").Set("text", "I am the kind of world.")
	err = client.Do(task)
	if err != nil {
		panic(err)
	}
	fmt.Println("Second work queue run..")
}

##心得

Disque善用了Redis的特性,並且幫大家把一些基本功能都勾勒出來.簡單講就是看到太多人把Messagq Queue弄在上面,原作者才會這樣改.

不過由於我本身用到的地方比較少,我相信有機會要使用的時後應該會更容易上手才對.

##相關文章

[Heroku][Golang] 一些關於筆記關於 Heroku 與 Golang

給自己做個筆記….

關於 Heroku

以下指令都需要安裝Heroku Toolbelt後登入Heroku. heroku login

  • 查詢Server 目前的log: heroku logs -t 其他更多指令在這裡
  • Heroku App/Worker crash如何重啟: heroku restart web.1 更多指令

  • 如何在Heroku上面快速的rollback: 到control panel 下 [activity] 選擇你要的change旁邊有[roll back to here].
  • Performance Status: 可以透過add-on Librato來監測你目前在Heroku的webapp.

關於Golang

  • Golang 的scope是依照package,所以同目錄下面的檔案,只要package一樣都會被歸在一起.
  • init() 跑的順序是依照 alphabet axxx.go bxxx.go …… zxxx.go
  • 關於go import 更多使用要看這(好像是FAQ)
    • import _ “xxx”: 代表的是使用 Init 但是不把整個package 套進來(不會有compiler error if no use)
    • import . “fmt”: 打所有function 不用加上namespace
    • import m “fmt”: fmt.Println 變成 m.Println

參考資料

[Ubuntu][Golang]關於Ubuntu Server連線能力設定

image

前言:

架設Golang Server選擇可以有兩個方向,大部分的人可能選擇自己架設主機來跑Golang Server如果要能夠達到10k甚至是100k連線數的話.這邊把一些經驗記錄下來.

架設環境

這邊的伺服器主要是架設在以下的設備與軟體:

  • Ubuntu 14.04 X64
  • MySQL + Apache + Ejabberd

了解主機原先設定的上限與調整

先就不討論硬體架構的連線能力狀況下,還是有一些軟體設定需要調整.

MySQL 連線上限調整

首先會遇到的問題就會是MySQL query 卡住的問題,每一次的Query Request 都會卡在MySQL那一端.主要是以下的設定需要調高.max_connections原先設定只有100.設定方式如下:(細節參考這裡)

    mysql> set GLOBAL max_connections=65536;

可能還是會遇到有一些問題,建議把另外一個max_connect_errors一併也調高

    mysql> set GLOBAL max_connect_errors=65536;
改完MySQL 接下來可能出現的會是 Ubuntu 設定

改完了MySQL的設定之後,整個設定就可以變得相當的順暢.但是偶爾會出現一些問題,就得接下去追查原因.

問題: 一次能進入的TCP connection 太少

首先可能去做調整就是去/etc/sysctl.conf來增加TCP連線數,以下是參考設定.

    net.ipv4.tcp_syncookies = 1
    net.ipv4.tcp_tw_reuse = 1
    net.ipv4.tcp_tw_recycle = 1
    net.ipv4.tcp_fin_timeout = 30
    net.ipv4.tcp_keepalive_time = 1200
    net.ipv4.ip_local_port_range = 1024 65000
    net.ipv4.tcp_max_syn_backlog = 8192
問題: “too many open files”

解決了大量連線數無法接收的問題,卻發生了"too many open files"的error.

根據這個Stackoverlow,由於每一個TCP的Connection在Linux系統本身都被當作是一個file來存取,所以面對到大量connection的時候,可能遇到的問題就是依照前面的方式把TCP connection 打開到65000之後,但是由於每一個TCP connection 會對應到一個檔案的開啟,接下來就是檔案開啟的上限被打爆.於是就會出現….

    Too many open files

這個問題可能是關於到Ubuntu原本的設定.

    ulimit -n //console mode 可以顯示目前可以同時開啟起檔案的最大量. 預設: 1000

修改方式主要是打開 /etc/security/limits.conf

    *         hard    nofile      500000
    *         soft    nofile      500000
    root      hard    nofile      500000
    root      soft    nofile      500000

這樣應該可以初步的把server request產生到同時間至少有3000以上.

參考文章

[Golang][Martini]在Martini-render上面的layout/template套用

image

前言:

學習架設Golang Web Services 的時候,也需要有後台網頁管理介面.這時候就需要有樣板來套.才能讓網站架設起來比較快.

筆記:

以下的部分會提到如何使用,我個人使用習慣. 不會將所有的程式碼都貼出來,但是會顯示部份的整理過的資料.

關於Martini的layout:

網頁上的資料部分,可以拆解成兩大部分:

  • 不可以抽換,使用layout替換 (ex: layout.tmpl)
    • 舉例: <HTML><HEAD> 甚至是CSS JS都可以.
  • 需要用程式來表示的部分,用render來顯示 (ex: hello_world.tmpl, layout_hello.tmpl)
    • 舉例: table content或是資料庫裡面的資料.

Layout file layout.tmpl

    <!doctype html>  
    <html>  
    <head>  
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Layout sample</title>
    </head>
    
    <body style="margin: 20px;">  
    <h1>This is a layout sample</h1>  
      <!-- 可以被替換的部分放這裡 -->
    </body>  
    </html>  

其中 `` 將你要顯示的tmpl部分全部換到這邊.

接下來要處理的部分拆解到hello_world.tmpl.

    <CENTER> Hello  </CENTER>

接下來另外一個需要處理的到layout_hello.tmpl.

    <CENTER> This is another layout Hello  </CENTER>

接下來就要提到Golang處理layout的部分.

如何使用Layout:

提到Golang處理的部分,先看看在一開始martini啟動的部分

    .....
    func main() {
        m := martini.Classic()
        
        // 將所有預設的layout都先設定到"layout.tmpl"
        m.Use(render.Renderer(render.Options {
            Layout: "layout",
        }))
        .....
        m.Run()
    }

這邊要記住,預設的目錄都會在templates下面.

要顯示的時候可以使用以下的方式.

    // 使用hello_world.tmpl
      m.Get("/", func(r render.Render, db *mgo.Database) {
        display_name := "John"
        r.HTML(200, "hello_world", display_name)
      })  

如果不同的entry point 要使用不同的layout呢?

有一些時候,網站開始變大之後.layout 的版型也開始變多了.這時候可能需要動態的去變你要套用的layout.原本的martini-render 就提供可以在render.HTML裡面加上 HTMLOptions.其實也就是加上你要指定的layout.

    // 使用hello_world.tmpl,並且套上layout_hello.tmpl
      m.Get("/", func(r render.Render, db *mgo.Database) {
        display_name := "John"
        r.HTML(200, "hello_world", display_name, render.HTMLOptions{Layout: "layout_hello"})
      })  

相關鏈結:

[MongoDB]More study about mongodb and mgo 3 - logical operator and like

Preface

When we trying to use MongoDB, the requirement comes more and more complex and diversity. Here is some note during my implement.

Multiple condition in MongoDB Query

It is very easy to find data in MongoDB, but how about multiple condition such as “AND” and “OR” ?

AND OR in MongoDB

It is very easy to find “AND” support in MongoDB, but how to apply in mgo (MongoDB driver in Go)?

    // Find user name is John and Contry is US.
    var alldb []User
    UserCollection.Find(bson.M{"$and": []bson.M{bson.M{"name": "John"}, bson.M{"Contry": "US"}}}).All(&alldb)

Please note: the $and need combine an array of bson.M.

    // Find CONDITION_A and CONDITION_B
    bson.M{"$and": []bson.M{ CONDITION_A, CONDITION_B }}

So, it is similar with “OR” ($or), detail doc is here.

    // Find CONDITION_A or CONDITION_B
    bson.M{"$or": []bson.M{ CONDITION_A, CONDITION_B }}

Make it more clear in code.

    // Find user name is John or Tom.
    var alldb []User
    UserCollection.Find(bson.M{"$or": []bson.M{bson.M{"name": "John"}, bson.M{"name": "Tom"}}}).All(&alldb)

Using “like” or simple regular expression in MongoDB Query

When we trying to query data, sometime we need use “like”. In MongoDB it is easy to find data as follow:

db.users.find({name: /a/})  //like '%a%'

But the special charactor "/" will skip during Go programming, so we need change to use regular expression.

Collection.Find(bson.M{"name": bson.RegEx{"a", ""}}).All(&result) //like `%a%`

Query array size over “n” (great than “n”)

The major adanvantage of MongoDB is to store JSON directly. What if we want to find a array size which great than 2 ?

The first idea is to find if the array size great than 2 in the collection. So the query string should be follow:

Collection.Find(bson.M{"array": bson.M{"$size" : bson.M{"$gt" : 2}}}).All(&result) //like `%a%`

But it don’t work? How, so I find here is a discussion here.

The major idea is to get array element and check if exist. The easy way to get array index 1 (which mean second element of array aka. array[1])

Collection.Find(bson.M{"array.1": bson.M{"$exists" : true} }).All(&result) //like `%a%`

So, if you want to find array size great than 5, just change to array.4.

Reference