why do we need generics? Go 社区之父早期提到过 less is more 的哲学,可惜社区里有不少人被带偏了。 每次 Go 增加语法上的新特性、新功能,就会有原教旨主义者跳出来扔出一句 less is more(或者也可能是大道至简),扬长而去,留下风中凌乱的你。 即使到了官方已经确定要增加泛型功能的 2020 年,依然有人煞有介事地写文章说为什么 go doesn't need generics,作为理智的 Gopher,最好不要对别人的结论尽信,至少要看看其它语言社区怎么看待这件事情。 Java 社区是怎么理解泛型的必要性的呢? 简而言之,
开源说线上分享 当前互联网公司的后端架构都是微服务化的,服务彼此使用 RPC 通信,与业务无关的功能部分会从业务代码中抽离为框架。 框架提供了基础的 RPC 功能,同时需要对稳定性负责,一个微服务框架包含但不限于以下功能: 路由 限流 熔断 负载均衡 服务注册与发现 tracing 链路加密 这些功能看起来也是通用且稳定,所以我们对一个框架的印象往往是成型了之后便很少再升级了。但是云原生时代将我们的假设击得粉碎。底层基础设施也开始迅速发生变化,例如: 物理机集群 -> k8s 集群,破坏了我们的服务实例 ip 不会变的假设 基础设施给服务的资源分配方式发生变化,超卖使以往未经审慎设计的代码在恶劣环境下更易崩溃 服务数量的爆炸式增长使每个内部的微服务都要面对
一个 bad file descriptor 的问题 先来看一个 demo: 1 package main 2 3 import ( 4 "fmt" 5 "net" 6 "os" 7 "runtime" 8 ) 9 10 var rawFileList []*os.File 11 12
open coded defer 是怎么实现的 1.14 defer 正常处理流程 在 Go 1.14 中,增加了一种新的 defer 实现:open coded defer。当函数内 defer 不超过 8 个时,则会使用这种实现。 形如下面这样的代码: defer f1(a) if cond { defer f2(b) } body... 会被翻译为: deferBits
用 subsetting 限制连接池中的连接数量 内网使用服务发现后,服务与其它服务的实例之间使用一条 TCP 长连接进行通信。这种情况下常见的做法是按照 registry 下发的 host:port 列表来直接建连。 简单来说就是下图这样: 每一个服务实例都需要和它依赖的服务的每一个实例都把连接给建上。如果各个服务的规模不大,这样没什么问题。 互联网公司的核心服务规模都比较大,几千/万台机器(或几千/万个实例)的单一服务并不少见,这时候 client 要和所有 server 实例建连,会导致 client 端的 conn pool 里有大量连接,当然,server
packetdrill 简介 本文内容是 2013 年 Google 对 packetdrill 的论文翻译。 网络协议测试很麻烦,线上的网络问题往往都是偶发的,难以捕捉。 packetdrill 是一个跨平台的脚本工具,可以用来测试整个 TCP/UDP/IP 网络栈实现的正确性和性能,从系统调用一直到硬件网络接口,从 IPv4 到 IPv6。 该工具对 Google 工程师研发 Linux TCP 中的 Early Retransmit,Fast Open,Loss
无人值守的自动 dump(二) 之前在这篇 无人值守(一) 简单介绍了我们针对线上抖动问题定位的工具的设计思路,思路很简单,技术含量很低,是个人都可以想得到,但是它确实帮我们查到了很多很难定位的问题。 在本篇里,我们重点讲一讲这个工具在生产环境帮我们发现了哪些问题。 OOM 类问题 RPC decode 未做防御性编程,导致 OOM 应用侧的编解码可能是非官方实现(如 node 之类的无官方 SDK 的项目),在一些私有协议 decode 工程中会读诸如 list len 之类的字段,如果外部编码实现有问题,发生了字节错位,就可能会读出一个很大的值。 非标准
10 个让微服务完全失败的 tips 这是三年前伦敦一个技术大会上的一场非常独特的分享,没想到一场技术大会上能有这么幽默的另类架构师,作者以反讽的形式举出了 10 个微服务环境下对系统搞破坏的 tips。我看了很多遍,其中的案例其实日常研发中大部分也都遇到过了,之前也总结过各式各样的吐槽,比如之前写的《中台的末路》《微服务的灾难》《MQ 正在变成臭水沟》等等。希望日后自己也能做类似风格的演讲。 下面是演讲的内容: 开场白,先是免责声明,让人对号入座就不好了: 然后是听众收益: 老板想要搞事情,让公司系统更现代化,你不想这么干,得找点理由反驳他;或者你想破坏你们现在的系统;学习从实践中总结的血淋淋教训;具体案例做过了匿名化处理,让罪犯可以稍微心安理得一些。 老板从网上学到了新词汇,比如工业4.0(哪个营销号说德国人不讲工业4.
极端情况下收缩 Go 的线程数 在 Go 的 runtime 里有一些创建了就没法回收的东西。 之前在 这篇 里讲过 allgs 没法回收的问题。 除了 allgs 之外,当前 Go 创建的线程也是没法退出的,比如这个来自 xiaorui.cc 的例子,我简单做了个修改,能从网页看到线程: package main /* #include <stdio.h> #include <stdlib.h&
在 Go 语言中 Patch 非导出函数 TLDR; 使用 https://github.com/cch123/supermonkey 可以 patch 任意导出/非导出函数。 Update: 目前在 Linux 直接运行 go test 生成的二进制文件没有符号表,所以如果在 test 中使用需要先用 go test -c 生成带符号表的二进制文件,然后再运行,略麻烦。 目前在 Go 语言里写测试还是比较麻烦的。 除了传统的 test double,
无人值守的自动 dump(一) Go 项目做的比较大(主要说代码多,参与人多)之后,可能会遇到类似下面这样的问题: 程序老是半夜崩,崩了以后就重启了,我也醒不来,现场早就丢了,不知道怎么定位 这压测开压之后,随机出问题,可能两小时,也可能五小时以后才出问题,这我蹲点蹲出痔疮都不一定能等到崩溃的那个时间点啊 有些级联失败,最后留下现场并不能帮助我们准确地判断问题的根因,我们需要出问题时第一时间的现场 Go 内置的 pprof 虽然是问题定位的神器,但是没有办法让你恰好在出问题的那个时间点,把相应的现场保存下来进行分析。特别是一些随机出现的内存泄露、CPU 抖动,等你发现有泄露的时候,可能程序已经 OOM 被 kill
Go context 填坑。。 https://github.com/cch123/golang-notes/blob/master/context.md ascii 图太多,blog 上没法看。就这样
一些连接池相关的总结 http 标准库 服务端 请求处理 package main import ( "io" "log" "net/http" ) func sayhello(wr http.ResponseWriter, r *http.Request) { wr.Header()["Content-Type"] = []string{"application/
reviewdog 几年前写过一篇 如何使你的代码达到 awesome-go 的入选标准。几年过去了,标准和工具都有所变化。 linter 已经换了两代,刚开始是散乱的 golint,go vet,staticcheck,后来出现了 gometalinter,把散乱的工具集集成到一起。 gometalinter 时代,我把这个工具集成到了当时组内所有项目 Makefile 里,合并代码前要求提交者自行执行 make lint 来检查不符合规范的代码并修正,可惜 Makefile 里的命令没有强制约束,某些没有追求的程序员根本不把君子之约当回事,所以难以执行下去。没办法,只要一个人不在乎,就会影响到后来加入的工程师,
fasthttp 快在哪里 坊间传言 fasthttp 在某些场景下比 nginx 还要快,说明 fasthttp 中应该是做足了优化。我们来做一些相关的验证工作。 先是简单的 hello server 压测。下面的结果是在 mac 上得到的,linux 下可能会有差异。 fasthttp: ~ ❯❯❯ wrk -c36 -t12 -d 5s http://127.0.0.1:8080 Running 5s test
架构的腐化是必然的 架构的腐化是必然的,不以人的意志为转移。 我们先从一个故事开始,从前有一个公司,这个公司有一个部门,这个部门里有两个组。 两个组做的项目比较类似,都是策略类项目。 其中一个组做需求基本靠堆人,业务和 PM 的所有需求,能找到人,并且让这个人在各种场景,各种模块,各种分支里加 if else 就可以搞定,代码膨胀飞快。很快没人能说得清项目内的细节,但是公司业务涉及的策略又很多,需求做不过来,所以疯狂堆人,小组规模迅速膨胀到几十个人。大家都很忙碌,充实,每天都在加班,就是代码稍微有点看不懂,但这不重要。重要的是大家都很充实且周报饱满。小组 leader
为什么提升 Go 项目的测试覆盖率有点难 注意,这里讨论的内容可能有争议。如果不同意,欢迎讨论。 awesome-go 要求项目测试覆盖率达到 80% 以上才符合入选标准。有一些公司也会要求项目有相对合理的测试覆盖率(如 70% 以上才符合代码准入条件等等)。 但有时,我们的逻辑代码却挺难做到这么高的覆盖率,主要还是因为目前 Go 的错误处理逻辑: func Register(req RegisterReq) error{ if len(req.Username) == 0 { return errors.New("length of
go mod 的智障版本选择 之前 go mod 用的比较少,而且一直听社区有各种抱怨,所以也兴趣寥寥。新公司的项目直接使用了 go mod,本来觉得无非是个简单的工具,不需要学习,结果在一个简单的依赖上却浪费了很多时间。 先来看看我碰到的例子: package main import "fmt" import registry "github.com/apache/dubbo-go/registry" import zk "github.com/
工程师应该怎么学习 只要一日自诩工程师,就没有办法放弃学习。本文不算是技术文,只是介绍一些个人的学习方法和经验。如果很多点你已经做到并且做好,一笑了之便可。 阅读书籍 对于工程师来说,从书籍得来的知识是必不可少的。现在很多年轻的程序员会从网络博客来学习技术,但博客内容大多缺乏体系(主要说总结性质的博客内容),不系统。很多博主为了掩饰自己的未知,遇到不知道的关键点就一笔带过,进而导致缺失。即使原作者非常努力,内容上没有缺失,你能从中获取的也只是别人总结好的知识,没有自己的主动思考,这中间便缺少过程式的沉淀,一味地满足于背诵别人总结好的知识,最后也只不过沦为他人的复读机而已。 对于工程师来说,书籍依然是最重要的知识获取媒介。即使只是通过目录概览,也能获取某个领域的大致蓝图。 目前大部分优秀的技术书籍依然以英文为主,能够读懂英文技术书籍是工程师的硬实力。英语阅读能力怎么训练呢?如果不是为了应试,可以尝试逼迫自己去翻译一些英文文档/文章来进行专门训练。
slice 类型内存泄露的逻辑 Go 101 总结了几个可能导致内存泄露的场景: https://gfw.go101.org/article/memory-leaking.html goroutine 阻塞在 channel,time.Ticker 不使用但未 stop,以及 for 循环里用 defer 导致泄露,这三个场景其实已经比较常见了,这里就不说了。 我们来看看子切片截取为什么会导致内存泄露。 因为 Go 是一门带 GC 的语言,虽然官方宣传 GC stw
生于非阻塞,死于日志 metrics 上报在高并发时会带来性能问题,为了解决问题,有时反而又会带来别的问题。 举个例子,一般的 metrics 上报代码可能是下面这样: // when request in metrics.Req(caller, count, latency) 内部实现一般也就是个 UDP 调用,可能碰到的是 fd 锁的问题,在 这篇 里已经写过了。 为了优化这个锁带来的阻塞问题,有些系统会把 metrics 上报改为非阻塞逻辑: func Req(caller string,
map 并发崩溃一例 某系统中有类似下面这样的代码: package main import ( "sync" "time" ) type resp struct { k string v string } func main() { res := fetchData() log.Print(res) } func rpcwork() resp { // do some rpc work return resp{
一个空 struct 的“坑” https://www.zhihu.com/question/364687707 在知乎上已经说了原因,不过只是讲官方的 spec,我们再来看看实现上具体为什么会是这样的。 package main import "fmt" func main() { a := new(struct{}) b := new(struct{}) println(a, b, a == b) c := new(struct{}) d
MQ 正在变成臭水沟 MQ 对于业务系统建模非常重要,是解决分离关注点、依赖反转、CQRS、最终一致等业务问题的重要法宝。 然而企业对于 MQ 中的数据管理却并不重视。从互联网企业发展的历程来看这个问题,最初 MQ 不是很可靠,大家不会把让特别重要的业务依赖 MQ,所以接入到 MQ 的业务事件并不多。总共也就两三个 topic,开发相应的系统对这些内容进行管理看起来没什么必要,甚至可能连详尽的业务信息都要从生产者的代码注释中去寻找。公司规模不大,这些都是可以接受的。 经历过 1Mb 小水管的朋友大概还记得当初火爆的 Flashget 的 Slogan: 下载的最大问题是什么——速度,其次是什么—
ACL 和俄罗斯套娃 逻辑复杂的系统中,我们经常能看到“规则引擎”和“决策树”之类的东西。 在数据准备好的前提下,规则引擎和决策树都可以理解成在大宽表上进行决策的过程,两者进行逻辑运算需要较为统一的数据格式,即需要对不同来源的数据内容进行统一抽象。 在之前的文章中,我写过 一劳永逸接入所有下游数据系统,讲的便是复杂系统中对准备数据过程的一种抽象。这种抽象在方法论大湿们发明的概念中早有总结:Anti Corruption Layer。你可以把它叫做 adapter,也可以把它称为 facade,或者把它贯名 anti corruption layer,你的听众听不懂哪一个名词,你就尽量用那种叫法,可以显得自己博学多才出世高人。 数据准备过程是典型的对外部系统进行适配的过程,在一个中大型规模的公司,没有进行过数据治理的前提下,必然会产生大量的像下图架构的技术产品: