大家好,我是一只普通的煎鱼,周四晚上很有幸邀请到 goproxy.cn 的作者 @盛傲飞(@aofei) 到 Go 夜读给我们进行第 61 期 《Go Modules、Go Module Proxy 和 goproxy.cn》的技术分享。 本次 @盛傲飞 的夜读分享,是对 Go Modules 的一次很好的解读,比较贴近工程实践,我必然希望把这块的知识更多的分享给大家,因此有了今天本篇文章,同时大家也可以多关注 Go 夜读,每周会通过 zoom 在线直播的方式分享 Go 相关的技术话题,希望对大家有所帮助。 前言 Go 1.11 推出的模块(Modules)为 Go 语言开发者打开了一扇新的大门,理想化的依赖管理解决方案使得 Go 语言朝着计算机编程史上的第一个依赖乌托邦(Deptopia)迈进。随着模块一起推出的还有模块代理协议(Module proxy protocol),通过这个协议我们可以实现 Go 模块代理(Go module proxy),也就是依赖镜像。
前段时间,某同学说某服务的容器因为超出内存限制,不断地重启,问我们是不是有内存泄露,赶紧排查,然后解决掉,省的出问题。我们大为震惊,赶紧查看监控+报警系统和性能分析,发现应用指标压根就不高,不像有泄露的样子。 那么问题是出在哪里了呢,我们进入某个容器里查看了 top 的系统指标,结果如下: PID VSZ RSS ... COMMAND 67459 2007m 136m ... ./eddycjy-server 从结果上来看,也没什么大开销的东西,主要就一个 Go 进程,一看,某同学就说 VSZ 那么高,而某云上的容器内存指标居然恰好和 VSZ 的值相接近,因此某同学就怀疑是不是 VSZ 所导致的,觉得存在一定的关联关系。 而从最终的结论上来讲,上述的表述是不全对的,那么在今天,本篇文章将主要围绕 Go 进程的 VSZ 来进行剖析,看看到底它为什么那么 “高”,而在正式开始分析前,第一节为前置的补充知识,大家可按顺序阅读。 基础知识 什么是 VSZ VSZ 是该进程所能使用的虚拟内存总大小,它包括进程可以访问的所有内存,其中包括了被换出的内存(Swap)、已分配但未使用的内存以及来自共享库的内存。
最近 Go1.13 终于发布了,其中一个值得关注的特性就是 defer 在大部分的场景下性能提升了30%,但是官方并没有具体写是怎么提升的,这让大家非常的疑惑。而我因为之前写过《深入理解 Go defer》 和 《Go defer 会有性能损耗,尽量不要用?》 这类文章,因此我挺感兴趣它是做了什么改变才能得到这样子的结果,所以今天和大家一起探索其中奥妙。 一、测试 Go1.12 $ go test -bench=. -benchmem -run=none goos: darwin goarch: amd64 pkg: github.com/EDDYCJY/awesomeDefer BenchmarkDoDefer-4 20000000 91.4 ns/op 48 B/op 1 allocs/op BenchmarkDoNotDefer-4 30000000 41.
什么是 GC 在计算机科学中,垃圾回收(GC)是一种自动管理内存的机制,垃圾回收器会去尝试回收程序不再使用的对象及其占用的内存。而最早 John McCarthy 在 1959 年左右发明了垃圾回收,以简化 Lisp 中的手动内存管理的机制(来自 wikipedia)。 为什么要 GC 手动管理内存挺麻烦,管错或者管漏内存也很糟糕,将会直接导致程序不稳定(持续泄露)甚至直接崩溃。 GC 带来的问题 硬要说会带来什么问题的话,也就数大家最关注的 Stop The World(STW),STW 代指在执行某个垃圾回收算法的某个阶段时,需要将整个应用程序暂停去处理 GC 相关的工作事项。例如: 行为 会不会 STW 为什么 标记开始 会 在开始标记时,准备根对象的扫描,会打开写屏障(Write Barrier) 和 辅助GC(mutator assist),而回收器和应用程序是并发运行的,因此会暂停当前正在运行的所有 Goroutine。 并发标记中 不会 标记阶段,主要目的是标记堆内存中仍在使用的值。 标记结束 会 在完成标记任务后,将重新扫描部分根对象,这时候会禁用写屏障(Write Barrier)和辅助GC(mutator assist),而标记阶段和应用程序是并发运行的,所以在标记阶段可能会有新的对象产生,因此在重新扫描时需要进行 STW。 如何调整 GC 频率 可以通过 GOGC 变量设置初始垃圾收集器的目标百分比值,对比的规则为当新分配的数值与上一次收集后剩余的实时数值的比例达到设置的目标百分比时,就会触发 GC,默认值为 GOGC=100。如果将其设置为 GOGC=off 可以完全禁用垃圾回收器,要不试试?
让 Go 更强大的原因之一莫过于它的 GODEBUG 工具,GODEBUG 的设置可以让 Go 程序在运行时输出调试信息,可以根据你的要求很直观的看到你想要的调度器或垃圾回收等详细信息,并且还不需要加装其它的插件,非常方便,今天我们将先讲解 GODEBUG 的调度器相关内容,希望对你有所帮助。 不过在开始前,没接触过的小伙伴得先补补如下前置知识,便于更好的了解调试器输出的信息内容。 前置知识 Go scheduler 的主要功能是针对在处理器上运行的 OS 线程分发可运行的 Goroutine,而我们一提到调度器,就离不开三个经常被提到的缩写,分别是: G:Goroutine,实际上我们每次调用 go func 就是生成了一个 G。 P:处理器,一般为处理器的核数,可以通过 GOMAXPROCS 进行修改。 M:OS 线程 这三者交互实际来源于 Go 的 M: N 调度模型,也就是 M 必须与 P 进行绑定,然后不断地在 M 上循环寻找可运行的 G 来执行相应的任务,如果想具体了解可以详细阅读 《Go Runtime Scheduler》,我们抽其中的工作流程图进行简单分析,如下:
在 Go 中有许许多多的分析工具,在之前我有写过一篇 《Golang 大杀器之性能剖析 PProf》 来介绍 PProf,如果有小伙伴感兴趣可以去我博客看看。 但单单使用 PProf 有时候不一定足够完整,因为在真实的程序中还包含许多的隐藏动作,例如 Goroutine 在执行时会做哪些操作?执行/阻塞了多长时间?在什么时候阻止?在哪里被阻止的?谁又锁/解锁了它们?GC 是怎么影响到 Goroutine 的执行的?这些东西用 PProf 是很难分析出来的,但如果你又想知道上述的答案的话,你可以用本文的主角 go tool trace 来打开新世界的大门。目录如下: 初步了解 import ( "os" "runtime/trace" ) func main() { trace.
gRPC 在 Go 语言中大放异彩,越来越多的小伙伴在使用,最近也在公司安利了一波,希望这一篇文章能带你一览 gRPC 的巧妙之处,本文篇幅比较长,请做好阅读准备。本文目录如下: 简述 gRPC 是一个高性能、开源和通用的 RPC 框架,面向移动和 HTTP/2 设计。目前提供 C、Java 和 Go 语言版本,分别是:grpc, grpc-java, grpc-go. 其中 C 版本支持 C, C++, Node.js, Python, Ruby, Objective-C, PHP 和 C# 支持。
如果你以前有涉猎过 gRPC+gRPC Gateway 这两个组件,你肯定会遇到这个问题,就是 “为什么非得开 TLS,才能够实现同端口双流量,能不能不开?” 又或是 “我不想用证书就实现这些功能,行不行?”。我被无数的人问过无数次这些问题,也说服过很多人,但说服归说服,不代表放弃。前年不行,不代表今年不行,在今天我希望分享来龙去脉和具体的实现方式给你。 过去 为什么 h2 不行 因为 net/http2 仅支持 “h2” 标识,而 “h2” 标识 HTTP/2 必须使用传输层安全性(TLS)的协议,此标识符用于 TLS 应用层协议协商字段以及识别 HTTP/2 over TLS。 简单来讲,也就 net/http2 必须使用 TLS 来交互。通俗来讲就要用证书,那么理所当然,也就无法支持非 TLS 的情况了。
上个月在 @polaris @轩脉刃 的全栈技术群里看到一个小伙伴问 “说 defer 在栈退出时执行,会有性能损耗,尽量不要用,这个怎么解?”。 恰好前段时间写了一篇 《深入理解 Go defer》 去详细剖析 defer 关键字。那么这一次简单结合前文对这个问题进行探讨一波,希望对你有所帮助,但在此之前希望你花几分钟,自己思考一下答案,再继续往下看。 测试 func DoDefer(key, value string) { defer func(key, value string) { _ = key + value }(key, value) } func DoNotDefer(key, value string) { _ = key + value } 基准测试:
在上一章节 《深入理解 Go panic and recover》中,我们发现了 defer 与其关联性极大,还是觉得非常有必要深入一下。希望通过本章节大家可以对 defer 关键字有一个深刻的理解,那么我们开始吧。你先等等,请排好队,我们这儿采取后进先出 LIFO 的出站方式… 特性 我们简单的过一下 defer 关键字的基础使用,让大家先有一个基础的认知 一、延迟调用 func main() { defer log.Println("EDDYCJY.") log.Println("end.") } 输出结果: $ go run main.go 2019/05/19 21:15:02 end.