登录
首页 >  数据库 >  MySQL

Go 语言开发设计指北

来源:SegmentFault

时间:2023-01-19 13:14:17 199浏览 收藏

IT行业相对于一般传统行业,发展更新速度更快,一旦停止了学习,很快就会被行业所淘汰。所以我们需要踏踏实实的不断学习,精进自己的技术,尤其是初学者。今天golang学习网给大家整理了《Go 语言开发设计指北》,聊聊MySQL、Redis、go,我们一起来看看吧!

友情提示:此篇文章大约需要阅读 20分钟33秒,不足之处请多指教,感谢你的阅读。 订阅本站
此文章首发于 Debug客栈 |https://www.debuginn.cn

Go 语言是一种强类型、编译型的语言,在开发过程中,代码规范是尤为重要的,一个小小的失误可能会带来严重的事故,拥有一个良好的 Go 语言开发习惯是尤为重要的,遵守开发规范便于维护、便于阅读理解和增加系统的健壮性。

以下是我们项目组开发规范加上自己开发遇到的问题及补充,希望对你有所帮助:
注:我们将以下约束分为三个等级,分别是:【强制】【推荐】【参考】

Go 编码相关

【强制】代码风格规范遵循 go 官方标准:CodeReviewComments,请使用官方golint lint 进行风格静态分析;

【强制】代码格式规范依照

func crond() {
    defer func() {
      if err := recover(); err != nil {
        // dump stack & log
      }
    }()
    // do something
}
func main() {
    // init system
    go crond()
    go crond2()
    // handlers
 } 

【强制】异步开启

func globalCrond() {
   for _ := ticker.C {
      projectCrond()
      itemCrond()
      userCrond()
   }
}
func projectCrond() {
   defer func() {
      if err := recover(); err != nil {
         // 打日志,并预警
      }
   }
   // do 
}

【强制】当有并发读写

resp, err := package1.GetUserInfo(xxxxx)
// 在err == nil 情况下,resp不能为nil或者空值

【强制】当操作有多个层级的结构体时,基于防御性编程的原则,需要对每个层级做空指针或者空数据判别,特别是在处理复杂的页面结构时,如:

type Section struct {
     Item   *SectionItem
     Height int64
     Width  int64
 }
 type SectionItem struct {
     Tag    string
     Icon   string
     ImageURL string
     ImageList []string
     Action *SectionAction
 }
 type SectionAction struct {
     Type  string
     Path  string
     Extra string
 }

func getSectionActionPath(section *Section) (path string, img string, err error) {
   if section.Item == nil || section.Item.Action == nil { // 要做好足够防御,避免因为空指针导致的panic
      err = fmt.Errorf("section item is invalid")
      return
   }

   path = section.Item.Action.Path

   img = section.Item.ImageURL
   // 对取数组的内容,也一定加上防御性判断
   if len(section.Item.ImageList) > 0 { 
      img = section.Item.ImageList[0]
   }
   return
}

【推荐】生命期在函数内的资源对象,如果函数逻辑较为复杂,建议使用

func MakeProject() {
   conn := pool.Get()
   defer pool.Put(conn)

   // 业务逻辑
   ...
   return
}

对于生命期在函数内的对象,定义在函数内,将使用栈空间,减少

func MakeProject() (project *Project){

   project := &Project{} // 使用堆空间
   var tempProject Project  // 使用栈空间

   return
}

【强制】不能在循环里加

// 反例:
for {
   row, err := db.Query("SELECT ...")
   if err != nil {
      ...
   }
   defer row.Close() // 这个操作会导致循环里积攒许多临时资源无法释放
   ...
}

// 正确的处理,可以在循环结束时直接close资源,如果处理逻辑较复杂,可以打包成函数:
for {
   func () {
      row, err := db.Query("SELECT ...")
      if err != nil {
         ...
      }
      defer row.Close()
      ...
   }()
}

【推荐】对于可预见容量的

headList := make([]home.Sections, 0, len(srcHomeSection)/2) tailList := make([]home.Sections, 0, len(srcHomeSection)/2)
dstHomeSection = make([]*home.Sections, 0, len(srcHomeSection))
….
if appendToHead {
   headList = append(headList, info)
} else {
   tailList = append(tailList, info)
}
….
dstHomeSection = append(dstHomeSection, headList…)
dstHomeSection = append(dstHomeSection, tailList…)

【推荐】逻辑操作中涉及到频繁拼接字符串的代码,请使用

// 正例:
var buf bytes.Buffer
for _, name := range userList {
   buf.WriteString(name)
   buf.WriteString(",")
}
return buf.String()

// 反例:
var result string
for _, name := range userList {
   result += name + ","
}
return result

【强制】对于固定的正则表达式,可以在全局变量初始化时完成预编译,可以有效加快匹配速度,不需要在每次函数请求中预编译:

var wordReg = regexp.MustCompile("[w]+")
func matchWord(word string) bool {
   return wordReg.MatchString(word)
}

【推荐】JSON 解析时,遇到不确定是什么结构的字段,建议使用

// 业务名.服务名.模块.功能.方法
service.gateway.module.action.func

【强制】打点使用场景是监控系统的实时状态,不适合存储任何业务数据;

【强制】在打点个数太多时,展示时速度会变慢。建议单个服务打点的key不超过10000个,

log.Debug("get home page failed %s, id %d", err, id)

【强制】如果是解析

value, err := c.RedisCache.GetGzip(key)
….
c.RedisCache.SetExGzip(content, 60)

【推荐】Redis 的分布式锁,可以使用:

lock: redis.Do("SET", lockKey, randint, "EX", expire, "NX")
unlock: redis.GetAndDel(lockKey, randint) // redis暂不支持,可以用lua脚本

【推荐】尽量避免在逻辑循环代码中调用 Redis,会产生流量放大效应,请求量较大时需采用其他方法优化(比如静态配置文件);

【推荐】

key := "demoid:3344"
value := localcacche.Get(key)
if value == "" {
   value = rediscache.Get(key)
   if value != "" {
      // 随机缓存 1~5s,各个机器间错开峰值,只要比 redis缓存短即可
      localcache.SetEx(key, value, rand.Int63n(5)+1)
   }
}
if value == "" {
   ....
   // 从其他系统或者数据库获取数据
   appoint.GetValue()

   // 同时设置到redis及localcache中
   rediscache.SetEx(key, content, 60)
   localcache.SetEx(key, content, rand.Int63n(5)+1)
}

【参考】对于请求量高,实时性也高的内容,如果纯粹使用缓存,当缓存失效瞬间,会导致大量请求穿透到后端服务,导致后端服务有雪崩危险:

如何兼顾扛峰值,保护后端系统,同时也能保持实时性呢?在这种场景下,可以采用随机更新法更新数据,方法如下:

  1. 正常请求从缓存中读取,缓存失效则从后端服务获取;
  2. 在请求中根据随机概率1%(或者根据实际业务场景设置比率)会跳过读取缓存操作,直接从后端服务获取数据,并更新缓存。

这种做法能保证最低时效性,并且当访问量越大,更新概率越高,使得内容实时性也越高。

如果结合上一条

-- 字段区分度 item_id > project_id
alter table xxx add index idx_item_project (item_id, project_id)

【强制】所有数据库表必须有主键 id;

【强制】主键索引名为 pk字段名; 唯一索引名为 uk字段名; 普通索引名则为 idx_字段名;

【强制】防止因字段类型不同造成的隐式转换,导致索引失效,造成全表扫描问题;

【强制】业务上有唯一特性的字段,即使是多字段的组合,也必须建成唯一索引;

【强制】一般事务标准操作流程:

func TestTXExample(t *testing.T) {
   // 打开事务
   tx, err := db.Beginx()
   if err != nil {
      log.Fatal("%v", err)
      return
   }

   // defer异常
   needRollback := true
   defer func() {
      if r := recover(); r != nil {  // 处理recover,避免因为panic,资源无法释放
         log.Fatal("%v", r)
         needRollback = true
      }
      if needRollback {
         xlog.Cause("test.example.transaction.rollback").Fatal()
         tx.Rollback()
      }
   }()

   // 事务的逻辑
   err = InsertLog(tx, GenTestData(1)[0])
   if err != nil {
      log.Fatal("%v", err)
      return
   }

   // 提交事务
   err = tx.Commit()
   if err != nil {
      log.Fatal("%v", err)
      return
   }
   needRollback = false

   return
}

【强制】执行事务操作时,请确保

SELECT ... FOR UPDATE
条件命中索引,使用行锁,避免一个事务锁全表的情况;

【强制】禁止超过三个表的

join
,需要
join
的字段,数据类型必须一致,多表关联查询时,保证被关联的字段有索引;

【强制】数据库

max_open
连接数不可设置过高,会导致代理连接数打满导致不可用状况;

今天带大家了解了MySQL、Redis、go的相关知识,希望对你有所帮助;关于数据库的技术知识我们会一点点深入介绍,欢迎大家关注golang学习网公众号,一起学习编程~

声明:本文转载于:SegmentFault 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>
评论列表