GoLang

1. 性能

重性能的 api 别用反射

2. framework

3. list

4. CSP 并发模型, communicating sequential processes

不同于传统的多线程通过共享内存来通信,CSP讲究的是以通信的方式来共享内存。

  • 传统
    • 线程间通信都是通过共享内存。
    • 非常典型的方式是锁,因此,衍生出一种方便操作的“线程安全的数据结构”。
    • go 也有
      • 临界区(critical section), 每次只允许一个goroutine进入某个代码块
        • 互斥锁
        • 条件变量 Cond
          • Wait
          • Signal
          • Broadcast
      • 原子操作(atomicity)
        • Add
        • CAS, compare and swap 交换并比较
        • store value & load value
        • swap

4.1. Don't communicate by sharing memory; share memory by communicating

4.2. channel

5. pprof

6. Mem leak & GC

  • 减少对象分配
    • 尽量做到对象的重用
  • goroutine channel leak
    • 绝对不能由消费者关channel
      • 因为向关闭的channel写数据会panic。
      • 正确的姿势是生产者写完所有数据后,关闭channel,消费者负责消费完channel里面的全部数据
          func produce(ch chan<- T) {
              defer close(ch) // 生产者写完数据关闭channel
              ch <- T{}
          }
          func consume(ch <-chan T) {
              for _ = range ch { // 消费者用for-range读完里面所有数据
              }
          }
          ch := make(chan T)
          go produce(ch)
          consume(ch)
        
        • 为什么consume要读完channel里面所有数据?
          • 因为 go produce()可能有多个,这样可以确定所有 produce 的 goroutine 都退出了,不会泄漏。
    • 利用关闭channel来广播取消动作
      • 对于每个长连接请求各开了一个读取和写入协程,全部采用endless for loop不停地处理收发数据。当连接被远端关闭后,如果不对这两个协程做处理,他们依然会一直运行,并且占用的channel也不会被释放…这里就必须十 分注意,在不使用协程后一定要把他依赖的channel close并通过再协程中判断channel是否关闭以保证其退出。
  • 不要光盯着top上面的数字
    • 因为Go向系统申请的内存不使用后,也不会立刻归还给系统。
      • 只是告诉系统这些内存可以回收;操作系统并不是立即回收,等 到系统内存紧张时才会开始回收
  • gc stop the world, gc 过多会抢占程序的正常执行时间
  • go的 垃圾回收有个触发阈值,这个阈值会随着每次内存使用变大而逐渐增大(如初始阈值是10MB则下一次就是20MB,再下一次就成为了40MB…),如果长时 间没有触发gc go会主动触发一次(2min)。
  • 关注(从系统申请的内存会在Go的内存池管理,整块的内存页,长时间不被访问并满足一定条件后,才归还给操作系统。又因为有GC,堆内存也不能代表内存占用,清理过之后剩下的,才是实际使用的内存。)
    • 程序占用的系统内存
    • Go的堆内存
    • 实际使用到的内存
  • 使用
    • runtime.ReadMemStats: Go 内存使用信息
    • pprof
  • 考虑到程序中为了更好地做抽象,使用了反射操作,而 reflect.Value 会将对象拷贝并分配到堆上,程序中的对象都是消息体,有的消息体会超大,因此会分配较多的堆内存。对程序做了一版优化,去掉这个反射逻辑,改为 switch case
  • fmt.Sprint, 这个函数会把对象分配到堆上

7. pool

8. 百万长连接

8.1. 前言

最近在看 golang 如何建立百万 tcp 连接 https://github.com/smallnest/1m-go-tcp-server , 觉得会对 blockcenter 有帮助 这里是我的一些总结

A Million WebSockets and Go 2017 Sergey Kamardin 就已经介绍过 golang 如何实现百万 WebSocket 连接. 介绍了epoll的使用 (https://github.com/mailru/easygo)

2019 2月 Eran Yanay 也进行了一个 百万 websocket 链接的分享, 对epoll的处理做了简化,而且提供了docker测试的脚本,很方便的在单机上进行百万连接的测试。

这篇文章 则是探讨了 更通用的 tcp 连接,而非 websocket

8.2. 动机

go 常见处理连接的方式是一个连接一个goroutine. goroutine 虽然开销便宜,但如果上到一百万的连接, 一百万个goroutine 锁使用的栈大小(gostack) 就要花费十几G内存,如果在每个goroutine中在分配byte buffer用以从连接中读写数据, 内存开销就要几十G。

典型场景: 消息推送、IOT、页游等场景,追求的是大量连接,并发量相对不大的场景

当然,当上到百万连接时也可以 多服务器负载均衡 但如果能垂直扩展,则能有效降低成本.

8.3. 对比

Eran Yanay使用epoll的方式代替goroutine-per-connection, 显然这个单goroutine处理的模式不适合耗时较长的业务处理, 对于并发量很大,延迟要求比较低的场景,可能存在问题。

  1. 多 epoller vs 单 epoller 多 epoller 吞吐率大幅增加,而延迟略微增加。

  2. prefork vs 多 epoller prefork的服务器客户端吞吐率大幅增加,而延迟比多poller翻倍。

  3. workerpool Reactor的方式,将I/O goroutine和业务goroutine分离, I/O goroutine采用单goroutine的方式,监听的消息交给一个goroutine池 (workerpool)去处理,这样可以并行的处理业务消息,而不会阻塞I/O goroutine。

吞吐率 (tps) 延迟 (latency) goroutine-per-conn 202830 4.9s 单epoller 42402 0.8s 多epoller 197814 0.9s prefork 444415 1.5s workerpool 190022 0.3s

从测试结果来看, 在百万并发的情况下, workerpool既能达到很高的吞吐率, 延迟也很低 prefork可以大幅提高吞吐率,但是延迟要稍微长一些。

8.4. 正常连接数量情况下 多epoller服务器 vs goroutine-per-conn

I/O密集型: 吞吐率会和连接数相关,但不是线性,随着连接数的增加,,连接数的增加带来的吞吐率的增加将变得很小。连接数比较小的情况下,正统的goroutine-per-connection可以取得很好的延迟,并且为了提高吞吐率,可以适当增加连接数。

计算密集型: 两种方式的性能差别不大。

8.5. 其他

今日头条Go建千亿级微服务的实践中提到

尽量避免反射,在高性能服务中杜绝反射的使用

9. Checklist

9.1. project structure

9.2. styleguide

9.3. goreportcard

9.4. pitfalls

9.5. go-critic

casting check

uint 转 int 要检查会不会 变成负数

尤其是 存在 用户输入的 p2p交换 和 合约

avoid copying arrays in loops

https://github.com/ethereum/go-ethereum/pull/17265/

https://go-critic.github.io/overview.html#rangeexprcopy

rangeValCopy

Avoid copying big objects during each iteration.

Use index access or take address and make use pointer instead.

https://go-critic.github.io/overview.html#rangevalcopy

builtinshadow

变量名不要和关键字重复, 比如 len, error

https://go-critic.github.io/overview.html#builtinshadow

9.6. Mics

submodule

  • Delete the relevant section from the .gitmodules file.
  • Stage the .gitmodules changes git add .gitmodules
  • Delete the relevant section from .git/config.
  • Run git rm --cached path_to_submodule (no trailing slash).
  • Run rm -rf .git/modules/path_to_submodule (no trailing slash).
  • Commit git commit -m "Removed submodule "
  • Delete the now untracked submodule files rm -rf path_to_submodule

append

https://stackoverflow.com/questions/27622083/performance-slices-of-structs-vs-slices-of-pointers-to-structs

AppendingStructs is faster than AppendingPointers

Copyright © ChrisLinn 2017-2018 all right reserved,powered by Gitbook该文件修订时间: 2020-02-15 15:47:54

results matching ""

    No results matching ""