Go Best Practices 之 B 站源码分析

2019-04-22 23:35:03 +08:00
 KaSunMt

过度加锁

Source: library/net/http/blademaster/{server,client}.go

Code Sample:

type Engine struct {
  RouterGroup

  lock sync.RWMutex
  conf *ServerConfig

  address string

  mux       *http.ServeMux                    // http mux router
  server    atomic.Value                      // store *http.Server
  metastore map[string]map[string]interface{} // metastore is the path as key and the metadata of this path as value, it export via /metadata

  pcLock        sync.RWMutex
  methodConfigs map[string]*MethodConfig

  injections []injection
}

两个锁+一个 atomic,我很难猜测写这段代码的人当时在想什么。。。

Go 的并发模型是共享内存的,刚接触的新手都喜欢在每个可能需要加锁的地方加上锁。以上述代码为例,这是一个只需要初始化一次,也就是只需要写操作一次的配置文件。这种配置文件要么在cmd/main.go调用一次( B 站就是这么做的),要么在init里调用。init是单线程的,自带依赖树检查,可以放心大胆地做任何操作(单个 package 多个 init 的写法最好避免)。

json.Encoder & sync.Pool

Code Sample:

func writeJSON(w http.ResponseWriter, obj interface{}) (err error) {
  var jsonBytes []byte
  writeContentType(w, jsonContentType)
  
  // err = json.NewEncoder(w).Encode(obj)
  if jsonBytes, err = json.Marshal(obj); err != nil {
    err = errors.WithStack(err)
    return
  }
  if _, err = w.Write(jsonBytes); err != nil {
    err = errors.WithStack(err)
  }
  return
}

优化 JSON 序列化性能的常见操作,避免 GC 和数据拷贝操作。

context.WithTimeout

Code Sample:

func (engine *Engine) handleContext(c *Context) {
  // ...
  // get derived timeout from http request header,
  // compare with the engine configured,
  // and use the minimum one
  engine.lock.RLock()
  tm := time.Duration(engine.conf.Timeout)
  engine.lock.RUnlock()
  // the method config is preferred
  if pc := engine.methodConfig(c.Request.URL.Path); pc != nil {
    tm = time.Duration(pc.Timeout)
  }
  if ctm := timeout(req); ctm > 0 && tm > ctm {
    tm = ctm
  }
  md := metadata.MD{
    metadata.Color:      color(req),
    metadata.RemoteIP:   remoteIP(req),
    metadata.RemotePort: remotePort(req),
    metadata.Caller:     caller(req),
    metadata.Mirror:     mirror(req),
  }
  ctx := metadata.NewContext(context.Background(), md)
  if tm > 0 {
    c.Context, cancel = context.WithTimeout(ctx, tm)
  } else {
    c.Context, cancel = context.WithCancel(ctx)
  }
  defer cancel()
  c.Next()
}

你需要调用<-ctx.Done()来判断 ctx 是不是已经结束了: https://golang.org/pkg/context/#example_WithTimeout

这是 Go,不是 C,不要省那么点空间

Code Sample:

const (
  // PlatAndroid is int8 for android.
  PlatAndroid = int8(0)
  // PlatIPhone is int8 for iphone.
  PlatIPhone = int8(1)
  // PlatIPad is int8 for ipad.
  PlatIPad = int8(2)
  // PlatWPhone is int8 for wphone.
  PlatWPhone = int8(3)
  // PlatAndroidG is int8 for Android Global.
  PlatAndroidG = int8(4)
  // PlatIPhoneI is int8 for Iphone Global.
  PlatIPhoneI = int8(5)
  // PlatIPadI is int8 for IPAD Global.
  PlatIPadI = int8(6)
  // PlatAndroidTV is int8 for AndroidTV Global.
  PlatAndroidTV = int8(7)
  // PlatAndroidI is int8 for Android Global.
  PlatAndroidI = int8(8)
  // PlatAndroidB is int8 for Android Blue.
  PlatAndroidB = int8(9)
  // PlatIPhoneB is int8 for Ios Blue
  PlatIPhoneB = int8(10)
  // PlatBilistudio is int8 for bilistudio
  PlatBilistudio = int8(11)
  // PlatAndroidTVYST is int8 for AndroidTV_YST Global.
  PlatAndroidTVYST = int8(12)
)

目前只看了library/net/blademaster的代码,欢迎大家继续补充。

1712 次点击
所在节点    问与答
0 条回复

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/557712

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX