這裏給你們總結一些 Go player 開發小技巧. 歡迎批評和交流, 望你們喜歡.
推薦一種簡單粗暴的配置管理方式 [配置 映射 內部結構]. 例若有個配置文件 config.online.yaml
# 常量 pi: 3.14159265358 # 即表示網址屬性值 uri: https://www.google.com # 即表示 server.host 屬性的值 server: host: http://www.youtube.com # 數組, 即表示 server 爲 [a, b, c] host: - 172.217.161.132 - 216.58.220.206 - 8.8.8.8
咱們能夠在代碼直接寫映射規則.
var C = struct { PI float64 `yaml:"pi"` URL `yaml:"uri"` Server struct { Host `yaml:"host"` } `yaml:"server"` Host []string `yaml:"host"` }{}
程序啓動時候, 經過 func init() {} 初始化. 使用時只須要使用 config.C.PI, 是否是很方便. 再補充一個更好的配置文件協議 toml.
tomlhtml
若是換用 toml 配置(config.online.toml)的內容更好理解
pi = 3.14159265358 uri = https://www.google.com [server] host = http://www.youtube.com host = [ "172.217.161.132", "216.58.220.206", "8.8.8.8" ]
真的, 看見 toml 的第一眼就喜歡上了. 好舒服 ~ 讓人以爲好舒服, 就應該這樣的雕琢.
有時候咱們看見這樣的代碼片斷
if len(v) > 0 { errMessage = fmt.Sprintf(t, v...) } else { errMessage = t }
其實對於 fmt.Sprintf 是多此一舉, 能夠直接
errMessage = fmt.Sprintf(t, v...)
(說的很輕巧, 推薦有所思考) 普通的讀寫操做代碼有
var lastMd5sLock = sync.RWMutex{} var lastMd5s map[string]map[string]string func ClearCache() { lastMd5sLock.Lock() defer lastMd5sLock.Unlock() lastMd5s = make(map[string]map[string]string) }
這裏分享個幹掉 RWMutex 的無鎖技巧. 運用新舊兩份配置, 使用空間換時間技巧.
var nowIndex uint32 var dataConf [2]map[string]map[string]string // ClearCache conf map clear func ClearCache() { lastConf := make(map[string]map[string]string) lastIndex := 1 - atomic.LoadUint32(&nowIndex) dataConf[lastIndex] = lastConf atomic.StoreUint32(&nowIndex, lastIndex) }
咱們來說解代碼, 原先的 ClearCache 那段代碼加了寫鎖. 寫鎖可以作到兩件事情 1' 臨界狀況有人在單條讀取, 清除會讓其等待 2' 臨界狀況有人在單條寫入, 清除會讓其等待 假如咱們不對 ClearCache 加寫鎖, 採用原子交換技巧. 因爲此刻內存中存在 dataConf[1] new 和 dataConf[0] old 兩個配置對象. 臨界狀況指讀取和寫入都在進行, 但此刻觸發清除操做 1' 臨界狀況有人在單條讀取, 寫方將 nowIndex 指向了 1, 但讀取的仍然是 dataConf[0] old 2' 臨界狀況有人在單條寫入, 寫入的仍是 dataConf[0] old 上面行爲和加鎖後產出結果同樣. 於是清除函數, 能夠用原子技巧替代鎖. 經過這個原理, 咱們作配置更新或者同步時候能夠採用下面步驟獲取最優性能 1' 解析配置, 生成一個新的配置對象 map 填充到 dataConf[lastIndex] 2' 新的配置對象讀取索引原子賦值給當前的讀取索引 lastIndex = lastIndex 爲何說這麼多呢. 由於鎖是一個咱們須要慎重對待的點. 而對於那些不加鎖, 也沒有原子操做的乒乓結構, 能夠自行利用 go -race 分析. 其讀寫一致性沒法保證(讀寫撕裂, 髒讀), 並且沒法保證編譯器不作優化. 有時候那種寫法線上竟然 不出問題, 可是一旦出了問題就是莫名其妙, 很難追查. 這裏就不表那種錯誤的乒乓寫法, 來污染同 行代碼.
提及配置庫, 我看有的同窗經過這樣代碼作配置文件內容提取和分割.
content, err := ioutil.ReadFile(file) if err != nil { // ... } for _, line := range strings.Split(string(content), "\n") { // ... }
上面代碼存在兩個潛在問題 1' 大文件內存會炸 2' 不一樣平臺換行符不統一 mac \r linux \n windows \r\n 一個穩健漂亮代碼模板推薦用下面
fin, err := os.Open(path) if err != nil { // Error ... } defer fin.Close() // create a Reader var buf bytes.Buffer reader := bufio.NewReader(fin) for { line, isPrefix, err := reader.ReadLine() if len(line) > 0 { buf.Write(line) if !isPrefix { // 完整的行而且不帶 \r\n, 運行獨立的業務代碼 ~ lins := string(buf.Bytes()) buf.Reset() } } if err != nil { break } }
強烈推薦!! 各位保存這個套路模板.
這種高頻出現代碼片斷, 強烈建議統一封裝. 保證出口統一. 這裏帶你們封裝兩個.
// MD5String md5 hash func MD5String(str string) string { data := md5.Sum([]byte(str)) return fmt.Sprintf("%x", data) }
// MD5File 文件 MD5 func MD5File(path string) (string, error) { fin, err := os.Open(path) if err != nil { return "", err } defer fin.Close() m := md5.New() // 文件讀取解析, 並設置緩衝緩衝大小 const blockSize = 4096 buf := make([]byte, blockSize) for { n, err := fin.Read(buf) if err != nil { return "", err } // buf[:0] == [] m.Write(buf[:n]) if n < blockSize { break } } return fmt.Sprintf("%x", m.Sum(nil)), nil }
不要問爲何那麼麻煩, 由於那叫專業. 小點遊戲包片斷 4G, 你來個 md5 試試
不要用這個庫, 性能全是呵呵呵. Go 中類型轉換代碼其實很健全(實在沒辦法能夠自行寫反射), 舉例以下
// ParseBool returns the boolean value represented by the string. // It accepts 1, t, T, TRUE, true, True, 0, f, F, FALSE, false, False. // Any other value returns an error. func ParseBool(str string) (bool, error) // ParseFloat converts the string s to a floating-point number // with the precision specified by bitSize: 32 for float32, or 64 for float64. // When bitSize=32, the result still has type float64, but it will be // convertible to float32 without changing its value. func ParseFloat(s string, bitSize int) (float64, error) // ParseInt interprets a string s in the given base (0, 2 to 36) and // bit size (0 to 64) and returns the corresponding value i. func ParseInt(s string, base int, bitSize int) (i int64, err error)
能夠看看 github.com/spf13/cast 源碼設計水平線 ~
// ToBoolE casts an empty interface to a bool. func ToBoolE(i interface{}) (bool, error) { i = indirect(i) switch b := i.(type) { case bool: return b, nil case nil: return false, nil case int: if i.(int) != 0 { return true, nil } return false, nil case string: return strconv.ParseBool(i.(string)) default: return false, fmt.Errorf("Unable to Cast %#v to bool", i) } }
首先看到的是 b := i.(type) 斷言, 觸發一次反射. 隨後可能到 case int 分支 i.(int) or case string 分支 i.(string) 觸發二次反射. 很是浪費. 由於 b 就是反射後的值了. 猜想做者當時喝了點酒. 其實做者寫的函數還有個商榷地方在於調用 indirect 函數找到指針指向的原始類型.
// From html/template/content.go // Copyright 2011 The Go Authors. All rights reserved. // indirect returns the value, after dereferencing as many times // as necessary to reach the base type (or nil). func indirect(a interface{}) interface{} { if a == nil { return nil } if t := reflect.TypeOf(a); t.Kind() != reflect.Ptr { // Avoid creating a reflect.Value if it's not a pointer. return a } v := reflect.ValueOf(a) for v.Kind() == reflect.Ptr && !v.IsNil() { v = v.Elem() } return v.Interface() }
這個函數引自 Go 標準庫 html/template/content.go 中. 用於將非 nil 指針轉成指向類型. 提升代碼兼容性. 這是隱藏的反射. 我的以爲用在這裏很浪費 ~ Go 開發中反射是低效的保證. 反射性能損耗在 1' 運行時安全檢查 2' 調用底層的類型轉換函數 不到非用不可, 請不要用反射. 和鎖同樣都須要慎重 外部庫太多容易形成版本管理複雜, 並且生產力和效率也不必定提高. 例如上面的包 ~ ... ... 其實咱們的協議層, 是太愛客戶端了. int, number, string 全都兼容. 把本來 json 協議要作的事情, 拋給了運行時問題. 這方面, 強烈推薦 json 協議語義明確. 方便咱們後端作參數健壯性過濾. 避免部分 CC 攻擊.
在數據業務設計時. 順帶同你們交流下 MySQL 設計過程當中小技巧(模板)
create table [table_nane] ( id bigint unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '物理主鍵', update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新時間', create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '建立時間', [delete_time timestamp DEFAULT NULL COMMENT '刪除時間'] [template] ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
問題 1: 物理主鍵 id 爲何是 unsigned ? 回答 : 1' 性能更好, unsigned 不涉及 反碼和補碼 轉碼消耗 2' 表示物理主鍵更廣 [-2^63, 2^63-1] -> [0, 2^64-1] 3' mysql 優化會更好. select * from * where id < 250; 原先是 select * from * where -2^63 <= id and id < 250; 如今是 select * from * where 0 <= id and id < 250; 問題 2: 爲何用 timestamp 表示時間? 回答 : 1' timestamp 和 int 同樣都是 4字節. 用它表示時間戳更友好. 2' 業務再也不關心時間的建立和更新相關業務代碼. 省心, 省代碼 問題 3: 爲何是 utf8mb4 而不是 utf8? 回答 : mysql 的 utf8 不是標準的 utf8. unicode 編碼定義是使用 1-6 字節表示一個字符. 但 mysql utf8 只使用了 1-3 字節表示一個字符, 那麼遇到 4字節編碼以上的字符(表情符號) 會發生意外. 因此 mysql 在 5.5 以後版本推出了 utf8mb4 編碼, 徹底兼容之前的 utf8.
渴望光榮 - https://music.163.com/#/song?id=31421394mysql