Others
关于Go,还有哪些重要的技术值得了解的,下面详细分享。
GC
GC一般是C/C++程序员对于Go最常见,也是最先想到的一个质疑,GC这玩意儿能行吗?我们以前C/C++程序都是自己实现内存池的,我们内存分配算法非常牛逼的。
Go的GC优化之路,可以详细读Getting to Go: The Journey of Go's Garbage Collector
。
2014年Go1.4,GC还是很弱的,是决定Go生死的大短板。
上图是Twitter的线上服务监控。Go1.4的STW(Stop the World) Pause time是300毫秒,而Go1.5优化到了30毫秒。
而Go1.6的GC暂停时间降低到了3毫秒左右。
Go1.8则降低到了0.5毫秒左右,也就是500微秒。从Go1.4到Go1.8,优化了600倍性能。
如何看GC的STW时间呢?可以引入net/http/pprof
这个库,然后通过curl来获取数据,实例代码如下:
package main
import (
"net/http"
_ "net/http/pprof"
)
func main() {
http.ListenAndServe("localhost:6060", nil)
}
启动程序后,执行命令就可以拿到结果(由于上面的例子中没有GC,下面的数据取的是另外程序的部分数据):
$ curl 'http://localhost:6060/debug/pprof/allocs?debug=1' 2>/dev/null |grep PauseNs
# PauseNs = [205683 79032 202102 82216 104853 142320 90058 113638 152504
145965 72047 49690 158458 60499 99610 112754 122262 52252 49234 68420 159857
97940 226085 103644 135428 245291 141997 92470 79974 132817 74634 65653 73582
47399 51653 86107 48619 62583 68906 131868 111903 85482 44531 74585 50162
31445 107397 10903081771 92603 58585 96620 40416 29763 102248 32804 49394
83715 77099 108983 66133 47832 35379 143949 69235 27820 35677 99430 104303
132657 63542 39434 126418 63845 167969 116438 68904 77899 136506 119708 47501]
可以用python计算最大值是322微秒,最小是26微秒,平均值是81微秒。
Declaration Syntax
关于Go的声明语法Go Declaration Syntax
,和C语言有对比,在The "Clockwise/Spiral Rule"
这个文章中也详细描述了C的顺时针语法规则。其中有个例子:
int (*signal(int, void (*fp)(int)))(int);
这个是个什么呢?翻译成Go语言就能看得很清楚:
func signal(a int, b func(int)) func(int)int
signal是个函数,有两个参数,返回了一个函数指针。signal的第一个参数是int,第二个参数是一个函数指针。
当然实际上C语言如果借助typedef也是能获得比较好的可读性的:
typedef void (*PFP)(int);
typedef int (*PRET)(int);
PRET signal(int a, PFP b);
只是从语言的语法设计上来说,还是Go的可读性确实会好一些。这些点点滴滴的小傲娇,是否可以支撑我们够浪程序员浪起来的资本呢?至少Rob Pike不是拍脑袋和大腿想出来的规则嘛,这种认真和严谨是值得佩服和学习的。
Documents
新的语言文档支持都很好,不用买本书看,Go也是一样,Go官网历年比较重要的文章包括:
- 语法特性及思考:
Go Declaration Syntax
,The Laws of Reflection
,Constants
,Generics Discussion
,Another Go at Language Design
,Composition not inheritance
,Interfaces and other types
- 并发相关特性:
Share Memory By Communicating
,Go Concurrency Patterns: Timing out, moving on
,Concurrency is not parallelism
,Advanced Go Concurrency Patterns
,Go Concurrency Patterns: Pipelines and cancellation
,Go Concurrency Patterns: Context
,Mutex or Channel
- 错误处理相关:
Defer, Panic, and Recover
,Error handling and Go
,Errors are values
,Stack traces and the errors package
,Error Handling In Go
,The Error Model
- 性能和优化:
Profiling Go Programs
,Introducing the Go Race Detector
,The cover story
,Introducing HTTP Tracing
,Data Race Detector
- 标准库说明:
Go maps in action
,Go Slices: usage and internals
,Arrays, slices (and strings): The mechanics of append
,Strings, bytes, runes and characters in Go
- 和C的结合:
C? Go? Cgo!
- 项目相关:
Organizing Go code
,Package names
,Effective Go
,versioning
,Russ Cox: vgo
- 关于GC:
Go GC: Prioritizing low latency and simplicity
,Getting to Go: The Journey of Go Garbage Collector
,Proposal: Eliminate STW stack re-scanning
其中,文章中有引用其他很好的文章,我也列出来哈:
-
Go Declaration Syntax
,引用了一篇神作,介绍C的螺旋语法,写C的多,读过这个的不多,The "Clockwise/Spiral Rule"
-
Strings, bytes, runes and characters in Go
,引用了很好的一篇文章,号称每个人都要懂的,关于字符集和Unicode的文章,Every Software Developer Must Know (No Excuses!)
- 为何错误码模型,比异常模型更有优势,参考Cleaner, more elegant, and wrong 以及Cleaner, more elegant, and harder to recognize。
- Go中的面向对象设计原则SOLID。
- Go的版本语义Semantic Versioning,如何在大型项目中规范版本,避免导致依赖地狱(Dependency Hell)问题。
SRS
SRS是使用ST,单进程单线程,性能是EDSM模型的nginx-rtmp的3到5倍,参考SRS: Performance,当然不是ST本身性能是EDSM的三倍,而是说ST并不会比EDSM性能低,主要还是要根据业务上的特征做优化。
关于ST和EDSM,参考本文前面关于Concurrency对于协程的描述,ST它是C的一个协程库,EDSM是异步事件驱动模型。
SRS是单进程单线程,可以扩展为多进程,可以在SRS中改代码Fork子进程,或者使用一个TCP代理,比如TCP代理go-oryx: rtmplb。
在2016年和2017年我用Go重写过SRS,验证过Go使用2CPU可以跑到C10K,参考go-oryx,v0.1.13 Supports 10k(2CPUs) for RTMP players
。由于仅仅是语言的差异而重写一个项目,没有找到更好的方式或理由,觉得很不值得,所以还是放弃了Go语言版本,只维护C++版本的SRS。Go目前一般在API服务器用得比较多,能否在流媒体服务器中应用?答案是肯定的,我已经实现过了。
后来在2017年,终于找到相对比较合理的方式来用Go写流媒体,就是只提供库而不是二进制的服务器,参考go-oryx-lib。
目前Go可以作为SRS前面的代理,实现多核的优势,参考go-oryx。
Links
由于简书限制了文章字数,只好分成不同章节:
- Overview 为何Go有时候也叫Golang?为何要选择Go作为服务器开发的语言?是冲动?还是骚动?Go的重要里程碑和事件,当年吹的那些牛逼,都实现了哪些?
- Could Not Recover 君可知,有什么panic是无法recover的?包括超过系统线程限制,以及map的竞争写。当然一般都能recover,比如Slice越界、nil指针、除零、写关闭的chan等。
- Errors 为什么Go2的草稿3个有2个是关于错误处理的?好的错误处理应该怎么做?错误和异常机制的差别是什么?错误处理和日志如何配合?
- Logger 为什么标准库的Logger是完全不够用的?怎么做日志切割和轮转?怎么在混成一坨的服务器日志中找到某个连接的日志?甚至连接中的流的日志?怎么做到简洁又够用?
- Interfaces 什么是面向对象的SOLID原则?为何Go更符合SOLID?为何接口组合比继承多态更具有正交性?Go类型系统如何做到looser, organic, decoupled, independent, and therefore scalable?一般软件中如果出现数学,要么真的牛逼要么装逼。正交性这个数学概念在Go中频繁出现,是神仙还是妖怪?为何接口设计要考虑正交性?
- Modules 如何避免依赖地狱(Dependency Hell)?小小的版本号为何会带来大灾难?Go为什么推出了GOPATH、Vendor还要搞module和vgo?新建了16个仓库做测试,碰到了9个坑,搞清楚了gopath和vendor如何迁移,以及vgo with vendor如何使用(毕竟生产环境不能每次都去外网下载)。
- Concurrency & Control 服务器中的并发处理难在哪里?为什么说Go并发处理优势占领了云计算开发语言市场?什么是C10K、C10M问题?如何管理goroutine的取消、超时和关联取消?为何Go1.7专门将context放到了标准库?context如何使用,以及问题在哪里?
- Engineering Go在工程化上的优势是什么?为什么说Go是一门面向工程的语言?覆盖率要到多少比较合适?什么叫代码可测性?为什么良好的库必须先写Example?
- Go2 Transition Go2会像Python3不兼容Python2那样作吗?C和C++的语言演进可以有什么不同的收获?Go2怎么思考语言升级的问题?
- SRS & Others Go在流媒体服务器中的使用。Go的GC靠谱吗?Twitter说相当的靠谱,有图有真相。为何Go的声明语法是那样?C的又是怎样?是拍的大腿,还是拍的脑袋?