版權聲明:本文由魏佳原創文章,轉載請註明出處:
文章原文連接:https://www.qcloud.com/community/article/173java
來源:騰雲閣 https://www.qcloud.com/communitymysql
使用go語言作後臺服務已經有3年了,經過項目去檢驗一個又一個的想法,而後不斷總結,優化,最終造成了本身的一整套體系,小到一個打印對象的方法,大到一個web後臺項目最佳實踐指導,這一點一滴都是在不斷的實踐中進化開來。如下內容將是一次總體的彙報,各位看官若有興致,請移步GitHub 關注最新的代碼變動。c++
大型互聯網社交業務git
路由自動生成,按要求提供controller/action的實現代碼,wsp執行後會分析項目代碼,自動生成路由表並記錄在文件demo/WSP.go裏,controller/action定義代碼必須符合函數定義:func(http.ResponseWriter, *http.Request)
,而且是帶receiver的methoddemo_set.gogithub
package controller import ( "net/http" "github.com/simplejia/wsp/demo/service" ) // @prefilter("Login", {"Method":{"type":"get"}}) // @postfilter("Boss") func (demo *Demo) Set(w http.ResponseWriter, r *http.Request) { key := r.FormValue("key") value := r.FormValue("value") demoService := service.NewDemo() demoService.Set(key, value) json.NewEncoder(w).Encode(map[string]interface{}{ "code": 0, }) }
WSP.gogolang
// generated by wsp, DO NOT EDIT. package main import "net/http" import "time" import "github.com/simplejia/wsp/demo/controller" import "github.com/simplejia/wsp/demo/filter" func init() { http.HandleFunc("/Demo/Get", func(w http.ResponseWriter, r *http.Request) { t := time.Now() _ = t var e interface{} c := new(controller.Demo) defer func() { e = recover() if ok := filter.Boss(w, r, map[string]interface{}{"__T__": t, "__C__": c, "__E__": e}); !ok { return } }() c.Get(w, r) }) http.HandleFunc("/Demo/Set", func(w http.ResponseWriter, r *http.Request) { t := time.Now() _ = t var e interface{} c := new(controller.Demo) defer func() { e = recover() if ok := filter.Boss(w, r, map[string]interface{}{"__T__": t, "__C__": c, "__E__": e}); !ok { return } }() if ok := filter.Login(w, r, map[string]interface{}{"__T__": t, "__C__": c, "__E__": e}); !ok { return } if ok := filter.Method(w, r, map[string]interface{}{"type": "get", "__T__": t, "__C__": c, "__E__": e}); !ok { return } c.Set(w, r) }) }
func (http.ResponseWriter
, *http.Request
, map[string]interface{}) bool
,過濾器函數以下: method.gopackage filter import ( "net/http" "strings" ) func Method(w http.ResponseWriter, r *http.Request, p map[string]interface{}) bool { method, ok := p["type"].(string) if ok && strings.ToLower(r.Method) != strings.ToLower(method) { http.Error(w, "405 Method Not Allowed", http.StatusMethodNotAllowed) return false } return true }
filter輸入參數map[string]interface{},會自動設置"T",time.Time類型,值爲執行起始時間,可用於耗時統計,"C",{Controller}類型,值爲{Controller}實例,可經過接口方式存取相關數據(這種方式存取數據較context方式更簡單實用),"E",值爲recover()返回值,用於檢測錯誤並處理(後置過濾器必須recover())web
package main import ( "log" "github.com/simplejia/clog" "github.com/simplejia/lc" "net/http" _ "github.com/simplejia/wsp/demo/clog" _ "github.com/simplejia/wsp/demo/conf" _ "github.com/simplejia/wsp/demo/mysql" _ "github.com/simplejia/wsp/demo/redis" ) func init() { lc.Init(1e5) http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { http.NotFound(w, r) }) } func main() { clog.Info("main()") log.Panic(http.ListenAndServe(":8080", nil)) }
提供一個簡單易擴展的項目stubredis
├── WSP.go ├── clog │ └── clog.go ├── conf │ ├── conf.go │ └── conf.json ├── controller │ ├── base.go │ ├── demo.go │ ├── demo_get.go │ └── demo_set.go ├── demo ├── filter │ ├── boss.go │ ├── login.go │ └── method.go ├── main.go ├── model │ ├── demo.go │ ├── demo_get.go │ └── demo_set.go ├── mysql │ ├── demo_db.json │ └── mysql.go ├── redis │ ├── demo.json │ └── redis.go ├── service │ ├── demo.go │ ├── demo_get.go │ └── demo_set.go └── test ├── clog -> ../clog ├── conf -> ../conf ├── demo_get_test.go ├── demo_set_test.go ├── init_test.go ├── mysql -> ../mysql └── redis -> ../redis
接口實現上,建議一個接口對應一個文件,如controller/demo_get.go, service/demo_get.go, model/demo_get.gosql
lc_test.goshell
package lc import ( "testing" "time" ) func init() { Init(65536) // 使用lc以前必需要初始化 } func TestGetValid(t *testing.T) { key := "k" value := "v" Set(key, value, time.Second) time.Sleep(time.Millisecond * 10) // 給異步處理留點時間 v, ok := Get(key) if !ok || v != value { t.Fatal("") } }
寫redis+mysql代碼時(還可能加上lc),示意代碼以下:
func orig(key string) (value string) { value = redis.Get(key) if value != "" { return } value = mysql.Get(key) redis.Set(key, value) return } // 若是再加上lc的話 func orig(key string) (value string) { value = lc.Get(key) if value != "" { return } value = redis.Get(key) if value != "" { lc.Set(key, value) return } value = mysql.Get(key) redis.Set(key, value) lc.Set(key, value) return }
有了lm,再寫上面的代碼時,一切變的那麼簡單 lm_test.go
func tGlue(key, value string) (err error) { err = Glue( key, &value, func(p, r interface{}) error { _r := r.(*string) *_r = "test value" return nil }, func(p interface{}) string { return fmt.Sprintf("tGlue:%v", p) }, &LcStru{ Expire: time.Millisecond * 500, Safety: false, }, &McStru{ Expire: time.Minute, Pool: pool, }, ) if err != nil { return } return }
自動添加緩存代碼,支持lc, redis,減輕你的心智負擔,讓你的代碼更加簡單可靠,少了大段的冗餘代碼,複雜的事全交給lm自動幫你作了
支持Glue[Lc|Mc]及相應批量操做Glues[Lc|Mc],詳見lm_test.go示例代碼
lm.LcStru.Safety,當置爲true時,對lc在併發狀態下返回的nil值不接受,由於lc.Get在併發狀態下,同一個key返回的value有多是nil,而且ok狀態爲true,Safety置爲true後,對以上狀況不接受,會繼續調用下一層邏輯
rows, err := db.Query("SELECT ...") defer rows.Close() for rows.Next() { var id int var name string err = rows.Scan(&id, &name) } err = rows.Err() ...
但實際項目場景裏,咱們更想這樣:
rows, err := db.Query("SELECT ...") defer rows.Close() var d []*stru err = Rows2Strus(rows, &d)
這就是一種簡單的對象映射,經過轉爲對象的方式,咱們的代碼更方便處理了
一共提供四種場景的使用方法:
Rows2Strus, sql.Rows轉爲struct slice
sql.Rows轉爲struct,等同db.QueryRow
Rows2Cnts, sql.Rows轉爲int slice
Rows2Cnt, sql.Rows轉爲int,用於select count(1)操做
支持tag: orm,以下:
type Demo struct { Id int DemoName string `orm:"demo_name"` // 映射成demo_name字段 }
支持匿名成員,以下:
type C struct { Id int } type P struct { C // 映射成id字段 Name string }
支持snakecase配置,經過設置orm.IsSnakeCase = true,以下:
type Demo struct { Id int DemoName string // 映射成demo_name字段 }
用於進程監控,管理
配置文件:conf.json (json格式,支持註釋) conf.json
{ "env": "dev", // 配置運行環境 "envs": { "dev": { "port": 29118, // 配置監聽端口 "rootpath": "/home/simplejia/tools/go/ws/src/github.com/simplejia", "environ": "ulimit -n 65536", // 配置環境變量 "svrs": { // demo "demo": "wsp/demo/demo" // key: 名字 value: 將與rootpath拼接在一塊兒運行 }, "log": { "mode": 3, // 0: none, 1: localfile, 2: collector (數字表明bit位) "level": 15 // 0: none, 1: debug, 2: warn 4: error 8: info (數字表明bit位) } }, "test": { "port": 29118, // 配置監聽端口 "rootpath": "/home/simplejia/tools/go/ws/src/github.com/simplejia", "environ": "ulimit -n 65536", // 配置環境變量 "svrs": { // demo "demo": "wsp/demo/demo" }, "log": { "mode": 3, // 0: none, 1: localfile, 2: collector (數字表明bit位) "level": 15 // 0: none, 1: debug, 2: warn 4: error 8: info (數字表明bit位) } }, "prod": { "port": 29118, // 配置監聽端口 "rootpath": "/home/simplejia/tools/go/ws/src/github.com/simplejia", "environ": "ulimit -n 65536", // 配置環境變量 "svrs": { // demo "demo": "wsp/demo/demo" }, "log": { "mode": 2, // 0: none, 1: localfile, 2: collector (數字表明bit位) "level": 14 // 0: none, 1: debug, 2: warn 4: error 8: info (數字表明bit位) } } } }
$ cmonitor -status all *****STATUS OK SERVICE LIST***** demo PID:13539 *****STATUS FAIL SERVICE LIST***** $ cmonitor -restart demo SUCCESS
agent機器
佈署本機agent服務:agent/agent,配置文件:agent/conf/conf.json
master機器
佈署master服務:master/master,配置文件:master/conf/conf.json
agent和master服務建議用cmonitor啓動管理
package procs import ( "encoding/json" "os" ) func init() { // 請替換成你本身的報警處理函數 AlarmFunc = func(sender string, receivers []string, text string) { params := map[string]interface{}{ "Sender": sender, "Receivers": receivers, "Text": text, } json.NewEncoder(os.Stdout).Encode(params) } }
package procs func XxxHandler(cate, subcate string, content []byte, params map[string]interface{}) { } func init() { RegisterHandler("xxxhandler", XxxHandler) }
api_test.go
demo (demo項目裏有clog的使用例子)
有時想快速驗證go某個函數的使用,臨時寫個程序過低效,有了gop,立馬開一個shell環境,邊寫邊運行,自動爲你保存上下文,還可隨時導入導出snippet,另外還有代碼自動補全等等特性
import "github.com/simplejia/utils" var println = utils.IprintD
$ gop Welcome to the Go Partner! [[version: 1.7, created by simplejia] Enter '?' for a list of commands. [r]$ ? Commands: ?|help help menu -[dpc][#],[#]-[#],... pop last/specific (declaration|package|code) ![!] inspect source [with linenum] <tmpl source tmpl >tmpl write tmpl [#](...) add def or code run run source compile compile source w write source mode on r write source mode off reset reset list tmpl list [r]$ for i:=1; i<3; i++ { ..... print(i) ..... time.Sleep(time.Millisecond) .....} 1 2 [r]$ import _ "github.com/simplejia/wsp/demo/mysql" [r]$ import _ "github.com/simplejia/wsp/demo/redis" [r]$ import _ "github.com/simplejia/wsp/demo/conf" [r]$ import "github.com/simplejia/lc" [r]$ import "github.com/simplejia/wsp/demo/service" [r]$ lc.Init(1024) [r]$ demoService := service.NewDemo() [r]$ demoService.Set("123", "456") [r]$ time.Sleep(time.Millisecond) [r]$ echo demoService.Get("123") 456 [r]$ >gop [r]$ <gop [r]$ ! package main p0: import _ "github.com/simplejia/wsp/demo/mysql" p1: import _ "github.com/simplejia/wsp/demo/redis" p2: import _ "github.com/simplejia/wsp/demo/conf" p3: import "github.com/simplejia/lc" p4: import "github.com/simplejia/wsp/demo/service" p5: import "fmt" // imported and not used p6: import "strconv" // imported and not used p7: import "strings" // imported and not used p8: import "time" // imported and not used p9: import "encoding/json" // imported and not used p10: import "bytes" // imported and not used func main() { c0: lc.Init(1024) c1: demoService := service.NewDemo() c2: _ = demoService c3: demoService.Set("123", "456") c4: time.Sleep(time.Millisecond) } [r]$