正文

  1. 这里给大家总结一些 Go player 开发小技巧. 欢迎批评和交流, 望大家喜欢.

1. 配置管理

  1. 推荐一种简单粗暴的配置管理方式 [配置 映射 内部结构].
  2. 例如有个配置文件 config.online.yaml
  1. # 常量
  2. pi: 3.14159265358
  3. # 即表示网址属性值
  4. uri: https://www.google.com
  5. # 即表示 server.host 属性的值
  6. server:
  7. host: http://www.youtube.com
  8. # 数组, 即表示 server 为 [a, b, c]
  9. host:
  10. - 172.217.161.132
  11. - 216.58.220.206
  12. - 8.8.8.8
  1. 我们可以在代码直接写映射规则.
  1. var C = struct {
  2. PI float64 `yaml:"pi"`
  3. URL `yaml:"uri"`
  4. Server struct {
  5. Host `yaml:"host"`
  6. } `yaml:"server"`
  7. Host []string `yaml:"host"`
  8. }{}
  1. 程序启动时候, 通过 func init() {} 初始化. 使用时只需要使用 config.C.PI,
  2. 是不是很方便. 再补充一个更好的配置文件协议 toml.

toml

  1. 如果换用 toml 配置(config.online.toml)的内容更好理解
  1. pi = 3.14159265358
  2. uri = https://www.google.com
  3. [server]
  4. host = http://www.youtube.com
  5. host = [
  6. "172.217.161.132",
  7. "216.58.220.206",
  8. "8.8.8.8"
  9. ]
  1. 真的, 看见 toml 的第一眼就喜欢上了. 好舒服 ~ 让人觉得好舒服, 就应该这样的雕琢.

2. fmt.Sprintf

  1. 有时候我们看见这样的代码片段
  1. if len(v) > 0 {
  2. errMessage = fmt.Sprintf(t, v...)
  3. } else {
  4. errMessage = t
  5. }
  1. 其实对于 fmt.Sprintf 是画蛇添足, 可以直接
  1. errMessage = fmt.Sprintf(t, v...)

3. 乒乓结构

  1. (说的很轻巧, 推荐有所思考) 普通的读写操作代码有
  1. var lastMd5sLock = sync.RWMutex{}
  2. var lastMd5s map[string]map[string]string
  3. func ClearCache() {
  4. lastMd5sLock.Lock()
  5. defer lastMd5sLock.Unlock()
  6. lastMd5s = make(map[string]map[string]string)
  7. }
  1. 这里分享个干掉 RWMutex 的无锁技巧. 运用新旧两份配置, 使用空间换时间技巧.
  1. var nowIndex uint32
  2. var dataConf [2]map[string]map[string]string
  3. // ClearCache conf map clear
  4. func ClearCache() {
  5. lastConf := make(map[string]map[string]string)
  6. lastIndex := 1 - atomic.LoadUint32(&nowIndex)
  7. dataConf[lastIndex] = lastConf
  8. atomic.StoreUint32(&nowIndex, lastIndex)
  9. }
  1. 我们来讲解代码, 原先的 ClearCache 那段代码加了写锁. 写锁能够做到两件事情
  2. 1' 临界情况有人在单条读取, 清除会让其等待
  3. 2' 临界情况有人在单条写入, 清除会让其等待
  4. 假如我们不对 ClearCache 加写锁, 采用原子交换技巧.
  5. 由于此刻内存中存在 dataConf[1] new dataConf[0] old 两个配置对象.
  6. 临界情况指读取和写入都在进行, 但此刻触发清除操作
  7. 1' 临界情况有人在单条读取, 写方将 nowIndex 指向了 1, 但读取的仍然是 dataConf[0] old
  8. 2' 临界情况有人在单条写入, 写入的还是 dataConf[0] old
  9. 上面行为和加锁后产出结果一样. 因而清除函数, 可以用原子技巧替代锁.
  10. 通过这个原理, 我们做配置更新或者同步时候可以采用下面步骤获取最优性能
  11. 1' 解析配置, 生成一个新的配置对象 map 填充到 dataConf[lastIndex]
  12. 2' 新的配置对象读取索引原子赋值给当前的读取索引 lastIndex = lastIndex
  13. 为什么说这么多呢. 因为锁是一个我们需要慎重对待的点.
  14. 而对于那些不加锁, 也没有原子操作的乒乓结构, 可以自行利用 go -race 分析.
  15. 其读写一致性无法保证(读写撕裂, 脏读), 而且无法保证编译器不做优化. 有时候那种写法线上居然
  16. 不出问题, 但是一旦出了问题就是莫名其妙, 很难追查. 这里就不表那种错误的乒乓写法, 来污染同
  17. 行代码.

4. 配置库解析

  1. 说起配置库, 我看有的同学通过这样代码做配置文件内容提取和分割.
  1. content, err := ioutil.ReadFile(file)
  2. if err != nil {
  3. // ...
  4. }
  5. for _, line := range strings.Split(string(content), "\n") {
  6. // ...
  7. }
  1. 上面代码存在两个潜在问题
  2. 1' 大文件内存会炸
  3. 2' 不同平台换行符不统一 mac \r linux \n windows \r\n
  4. 一个稳健漂亮代码模板推荐用下面
  1. fin, err := os.Open(path)
  2. if err != nil {
  3. // Error ...
  4. }
  5. defer fin.Close()
  6. // create a Reader
  7. var buf bytes.Buffer
  8. reader := bufio.NewReader(fin)
  9. for {
  10. line, isPrefix, err := reader.ReadLine()
  11. if len(line) > 0 {
  12. buf.Write(line)
  13. if !isPrefix {
  14. // 完整的行并且不带 \r\n, 运行独立的业务代码 ~
  15. lins := string(buf.Bytes())
  16. buf.Reset()
  17. }
  18. }
  19. if err != nil {
  20. break
  21. }
  22. }
  1. 强烈推荐!! 各位保存这个套路模板.

5. Go MD5

  1. 这种高频出现代码片段, 强烈建议统一封装. 保证出口统一. 这里带大家封装两个.
  1. // MD5String md5 hash
  2. func MD5String(str string) string {
  3. data := md5.Sum([]byte(str))
  4. return fmt.Sprintf("%x", data)
  5. }
  1. // MD5File 文件 MD5
  2. func MD5File(path string) (string, error) {
  3. fin, err := os.Open(path)
  4. if err != nil {
  5. return "", err
  6. }
  7. defer fin.Close()
  8. m := md5.New()
  9. // 文件读取解析, 并设置缓冲缓冲大小
  10. const blockSize = 4096
  11. buf := make([]byte, blockSize)
  12. for {
  13. n, err := fin.Read(buf)
  14. if err != nil {
  15. return "", err
  16. }
  17. // buf[:0] == []
  18. m.Write(buf[:n])
  19. if n < blockSize {
  20. break
  21. }
  22. }
  23. return fmt.Sprintf("%x", m.Sum(nil)), nil
  24. }
  1. 不要问为什么那么麻烦, 因为那叫专业. 小点游戏包片段 4G, 你来个 md5 试试

6. github.com/spf13/cast

  1. 不要用这个库, 性能全是呵呵呵.
  2. Go 中类型转换代码其实很健全(实在没办法可以自行写反射), 举例如下
  1. // ParseBool returns the boolean value represented by the string.
  2. // It accepts 1, t, T, TRUE, true, True, 0, f, F, FALSE, false, False.
  3. // Any other value returns an error.
  4. func ParseBool(str string) (bool, error)
  5. // ParseFloat converts the string s to a floating-point number
  6. // with the precision specified by bitSize: 32 for float32, or 64 for float64.
  7. // When bitSize=32, the result still has type float64, but it will be
  8. // convertible to float32 without changing its value.
  9. func ParseFloat(s string, bitSize int) (float64, error)
  10. // ParseInt interprets a string s in the given base (0, 2 to 36) and
  11. // bit size (0 to 64) and returns the corresponding value i.
  12. func ParseInt(s string, base int, bitSize int) (i int64, err error)
  1. 可以看看 github.com/spf13/cast 源码设计水平线 ~
  1. // ToBoolE casts an empty interface to a bool.
  2. func ToBoolE(i interface{}) (bool, error) {
  3. i = indirect(i)
  4. switch b := i.(type) {
  5. case bool:
  6. return b, nil
  7. case nil:
  8. return false, nil
  9. case int:
  10. if i.(int) != 0 {
  11. return true, nil
  12. }
  13. return false, nil
  14. case string:
  15. return strconv.ParseBool(i.(string))
  16. default:
  17. return false, fmt.Errorf("Unable to Cast %#v to bool", i)
  18. }
  19. }
  1. 首先看到的是 b := i.(type) 断言, 触发一次反射.
  2. 随后可能到 case int 分支 i.(int) or case string 分支 i.(string) 触发二次反射.
  3. 非常浪费. 因为 b 就是反射后的值了. 猜测作者当时喝了点酒.
  4. 其实作者写的函数还有个商榷地方在于调用 indirect 函数找到指针指向的原始类型.
  1. // From html/template/content.go
  2. // Copyright 2011 The Go Authors. All rights reserved.
  3. // indirect returns the value, after dereferencing as many times
  4. // as necessary to reach the base type (or nil).
  5. func indirect(a interface{}) interface{} {
  6. if a == nil {
  7. return nil
  8. }
  9. if t := reflect.TypeOf(a); t.Kind() != reflect.Ptr {
  10. // Avoid creating a reflect.Value if it's not a pointer.
  11. return a
  12. }
  13. v := reflect.ValueOf(a)
  14. for v.Kind() == reflect.Ptr && !v.IsNil() {
  15. v = v.Elem()
  16. }
  17. return v.Interface()
  18. }
  1. 这个函数引自 Go 标准库 html/template/content.go 中.
  2. 用于将非 nil 指针转成指向类型. 提高代码兼容性.
  3. 这是隐藏的反射. 个人觉得用在这里很浪费 ~
  4. Go 开发中反射是低效的保证. 反射性能损耗在
  5. 1' 运行时安全检查
  6. 2' 调用底层的类型转换函数
  7. 不到非用不可, 请不要用反射. 和锁一样都需要慎重
  8. 外部库太多容易造成版本管理复杂, 而且生产力和效率也不一定提升. 例如上面的包 ~
  9. ... ...
  10. 其实我们的协议层, 是太爱客户端了. int, number, string 全都兼容.
  11. 把原本 json 协议要做的事情, 抛给了运行时问题. 这方面, 强烈推荐 json 协议语义明确.
  12. 方便我们后端做参数健壮性过滤. 避免部分 CC 攻击.

7. MySQL 相关讨论

  1. 在数据业务设计时. 顺带同大家交流下 MySQL 设计过程中小技巧(模板)
  1. create table [table_nane] (
  2. id bigint unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '物理主键',
  3. update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  4. create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  5. [delete_time timestamp DEFAULT NULL COMMENT '删除时间']
  6. [template]
  7. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
  1. 问题 1: 物理主键 id 为什么是 unsigned ?
  2. 回答 :
  3. 1' 性能更好, unsigned 不涉及 反码和补码 转码消耗
  4. 2' 表示物理主键更广 [-2^63, 2^63-1] -> [0, 2^64-1]
  5. 3' mysql 优化会更好. select * from * where id < 250;
  6. 原先是 select * from * where -2^63 <= id and id < 250;
  7. 现在是 select * from * where 0 <= id and id < 250;
  8. 问题 2: 为什么用 timestamp 表示时间?
  9. 回答 :
  10. 1' timestamp int 一样都是 4字节. 用它表示时间戳更友好.
  11. 2' 业务不再关心时间的创建和更新相关业务代码. 省心, 省代码
  12. 问题 3: 为什么是 utf8mb4 而不是 utf8?
  13. 回答 :
  14. mysql 的 utf8 不是标准的 utf8. unicode 编码定义是使用 1-6 字节表示一个字符.
  15. 但 mysql utf8 只使用了 1-3 字节表示一个字符, 那么遇到 4字节编码以上的字符(表情符号)
  16. 会发生意外. 所以 mysql 在 5.5 之后版本推出了 utf8mb4 编码, 完全兼容以前的 utf8.

后记

渴望光荣 - https://music.163.com/#/song?id=31421394

golang 杂思的更多相关文章

  1. QA笑话----杂思

    QA工程师走进酒吧,要了一杯啤酒,要了0杯啤酒,要了999999999杯啤酒,要了一只蜥蜴,要了-1杯啤酒,要了一个sfdeljknesv,酒保从容应对,QA工程师 很满意.接下来,一名顾客来到了同一 ...

  2. 关于H5的一些杂思细想(一)

    作为一名前端程序媛,虽然整天和代码打交道,但是还是有一颗小清新的内心,虽然有时候加起班来不是人,但是空闲的时候还是会整理一下思绪,顺便整理一下自己,两个多月的加班,一直没有更新,今天就把自己最近做的一 ...

  3. hook杂思-面向函数编程

    hook:方法拦截 以函数单元为编程对象: 在编译时或运行时进行函数单元的替代.修改.功能添加操作: 所有的操作都不是在原始编码时完成的: 函数单元作为参量.操作对象.编码对象存在于机制中: 机制: ...

  4. 【GoLang】转载:我为什么放弃Go语言,哈哈

    我为什么放弃Go语言 作者:庄晓立(Liigo) 日期:2014年3月 原创链接:http://blog.csdn.NET/liigo/article/details/23699459 转载请注明出处 ...

  5. 谏牲口TT十思疏

    予闻:求木之长着,必固其根本:欲流之远者,必浚其泉源:思吾之长者,必积其学识.源不深而望流之远,根不固而求木之长,识不积而思指日之安,斯虽下愚,知其不可,而况于TT乎?TT当举家之重,虑只此一生,将孝 ...

  6. Golang哲学思想

    Golang是一门新语言,经过几年发展,慢慢地也已经被许多大公司认可.最大的特点是速度快,并发性好,与网络的功能结合好,是一门服务端语言,号称“网络时代的新语言”:另外还是一个编译型的Python.不 ...

  7. 搭建golang的beego注意事项

    大家都知道,在学golang的时候,大家都会去关注谢大的beego快速开发架构. 首先,小弟是win7 32bit系统,在这里,我要把我学习golang的过程和小心得记录起来. 相信想学的人一定会早早 ...

  8. golang(5):编写WebSocket服务,client和html5调用

    本文的原文连接是: http://blog.csdn.net/freewebsys/article/details/46882777 转载请必须注明出处! 1.关于websocket HTML5定义了 ...

  9. 以太坊系列之十六: 使用golang与智能合约进行交互

    以太坊系列之十六: 使用golang与智能合约进行交互 以太坊系列之十六: 使用golang与智能合约进行交互 此例子的目录结构 token contract 智能合约的golang wrapper ...

随机推荐

  1. Condition使用

    面试题:写一个固定容量同步容器,拥有put和get方法,以及getCount方法,    能够支持2个生产者线程以及10个消费者线程的阻塞调用 有两种方法 1.使用wait和notify/notify ...

  2. Android Viewpager+Fragment实现滑动标签页

    ViewPager 结合Fragment实现一个Activity里包含多个可滑动的标签页,每个标签页可以有独立的布局及响应. 主页布局 <?xml version="1.0" ...

  3. 【Java】【转】在命令行中编译和运行java

    原文:http://blog.csdn.net/u010900574/article/details/50792353 同时加载编译多个jar包和java文件 在个人平常使用或者当我们把代码部署到Li ...

  4. 创建完美SDK的10个技巧

    [编者按]本文作者为 Gal Lavinsky,文中将列出10个零基础小技巧,帮你创建完美的Java SDK.文章系国内 ITOM 管理平台 OneAPM 编译呈现.以下为正文. 本文起源于笔者朋友的 ...

  5. SQLSERVER2012里的扩展事件初尝试(上)

    SQLSERVER2012里的扩展事件初尝试(上) SQLSERVER2012里的扩展事件初尝试(下) 周未看了这两篇文章: 扩展事件在Denali CTP3里的新UI(一) 扩展事件在Denali ...

  6. [C/C++]如何解读返回函数指针的函数声明

    今天在看<深入理解C++11>的时候,看到一段有意思的代码: int (*(*pf())())() { return nullptr; } 我立刻就懵了——从来没有见过这样的函数声明.那么 ...

  7. [控件] AngleGradientView

    AngleGradientView 效果 说明 1. 用源码产生带环形渐变色的view 2. 可以配合maskView一起使用 (上图中的右下角图片的效果) 源码 https://github.com ...

  8. Spring 如何在 WEB 应用中使用

    1. Spring 如何在 WEB 应用中使用 ? 1). 需要额外加入的 jar 包: spring-web-4.0.0.RELEASE.jar spring-webmvc-4.0.0.RELEAS ...

  9. 用HashSet存储自定义对象

      案例 package cn.itcast_02; import java.util.HashSet; /* * 需求:存储自定义对象,并保证元素的唯一性 * 要求:如果两个对象的成员变量值都相同, ...

  10. centos7.4之zabbix4.0的fping监控

    参考博文: https://www.cnblogs.com/lei0213/p/8859326.html 注释:他是额外安装fping的:因为我yum安装的zabbix,fping就已经自带了:安装步 ...