我已经「叛逃」去写Go了
/ / / 阅读数:10582前言
这篇文章从前司离职后就想写了,一直没有决心下笔,毕竟曾经我整个「青春」都「押」在 Python 这个语言,这俨然已经是信仰了,很难和 Python 告别。之前我的个人博客也刻意的不写 Go 语言的内容,是希望别人想到这个博客就只有「Python」这一个标签。
大概 17 年我学习 Python 感到了瓶颈,写完书后就开始学习 Go,但由于 Python 一直是我的主力编程语言,对 Go 的学习一直断断续续,从 20 年以后基本都在写 Go,Python 代码虽然也会写但是相对很少。
这篇文章介绍了我为什么从 Python 叛逃到 Go,也从一个资深 Python 开发 + Go 熟手的角度分析一下为什么一些主要使用 Python 的公司没有升级 Python 3,或者改 Go。
Python 的优势
Python 作为 TIOBE 榜单中最受欢迎的编程语言,它的存在和流行肯定是有必然的原因。根据我这些年的经验,我理解的 Python 的优势如下:
- 开发效率。我接触过的语言中没有一个语言的开发效率能和 Python 相提并论。
- 专注于解决问题。Python 的语法和语言特性很丰富和友好,从错误提示、调试、工具链、第三方库、学习成本等角度看,这个语言可以让开发者专注于快速完成功能需求。
- 有大量经过市场验证过的成熟技术方案。社区发展多年,任何一个方向都有积累,可以拿来直接用,相关的问题和解决方案都可以方便的搜索到。
对于初创公司,Python 这个简单、实用的语言我认为是最好的选择,可以快速实现产品功能,在创业早期能够快速迭代,试错并做出改变,而且它对开发者要求也比较低。在激烈的竞争能够活下来,远比选择一个更快的语言重要。
我「叛逃」到 Go 的理由
换编程语言 (框架) 是为了更好的解决工程问题,我在职业生涯中这种体验很多:
- 我刚工作时是做 Linux 运维的,一开始很多工作都是用 Shell 脚本去完成,其中很多脚本运行都很慢,印象最深的是一个做数据分析发邮件的脚本挺慢的,后来新来了一个写 Perl 的同事,他的 Perl 版本比我那个 Shell 版本相比代码行数少了一半多,效率提升了很多倍 (具体多少倍不记得了),反正后来我就开始写 Perl,也知道了正则表达式。
- 接触 Python 后,发现它对于 Perl 来说语法简单、学习曲线平,社区繁盛。后来我们组全部运维和运维开发相关的内容全部都使用 Python。
- 在之后转做运维开发时,很多后台是用 PHP 写的,再之后我们后端自己前后端一起用 Python 写,没人再用 PHP 了。
- 我一开始所在的公司可以说是最早用 Puppet 做配置管理的那一波公司里的,当时我们还都为此学习了 Ruby,再之后开始用 Ansible/SaltStack/Fabric,因为它们是 Python 写的,很顺手,我还贡献过一些代码。而现在它们都被 Kubernetes 降维打击了。
- jQuery 是我初学前端的框架,那个时候我熟悉各种 DOM 操作和事件的用法,后来 Angular (及同类) 革了 jQuery 的命,再后来 React (及 Vue) 革了 Angular 的命,这些框架体验也太好了,不过现在我已经不太会写直接操作 DOM。
一个新的语言 (框架) 的流行必然是因为它提出了对现存的某个 (些) 工程问题更好的解决方案。回过头开看,我每次的改变都是顺应发展做出的必然选择,而现在即便写了十多年 Python,我也不得不拥抱变化。
对使用 Python,尤其是 Python 2 的开发者和企业,我的建议是你应该使用 Go。我从前司离职后,主力开发语言就改成了 Go,其实不是因为它革了 Python 的命 (当然也革不了),只是 Go 有我实在不能拒绝的几个优点 (和 Python 相比)。
1. 效率高
慢就是原罪,Python 著名槽点之一,之前我写文章介绍 刚发布的 Python 3.11 比 Python 3.10 速度了 10-60% ,有人分享到网上,其中有人评论说:「恭喜,从慢 200 倍提升到慢 150 倍了」,当然这个话非常无知,一会我会反驳。但是在大型企业应用中这个效率确实不太好看。
有同学说「选择从 Python 转到其他语言还是工程师写 Python 不行啊」,本质上是的,但是市场上找不到那么多合适的靠谱开发者企业能有什么办法。有的同学会问「写 Go 好的人也不多啊!」。但是别忘了,Go 本来就更快,另外由于是静态语言,它会设置门槛让开发者更少的犯错。
2. 占用资源少
相对的 Python 对资源消耗过大。在下面「效率对比」没有你想的那么差距悬殊
小节的结论部分有具体的值作为参考。
3. 部署简单
Go 是一种编译语言,所以 Go 的源代码会被翻译成机器语言,编译成独立的二进制文件可以很方便的部署到需要的操作系统,而对于 Python 就很麻烦,一方面需要安装各个依赖,而为了解决依赖的依赖需要安装很多系统包,尤其难受的是各种特定场景下需要特别设置编译参数,另外一方面云部署时的镜像大到不想去想,我接触到一个最复杂的项目,requirements.txt
文件 257 行,镜像 7.5G,第一次看那个依赖文件我都惊了,我当时还尝试优化构建镜像的流程,但是之前的开发者这方面显然已经非常用心,确实优化不了。
而 Go 的构建镜像 / 部署的方式非常贴合微服务和云原生。现在一般大型点的应用都会在 Kubernetes 上部署应用。用 Go 代码构建的镜像只需把可执行的文件放进去即可运行,如果做好优化镜像文件很小,分发和部署更快。
4. 并发
众所周知 Python 语言在权衡后设计了 GIL,虽然 Python 3 推出了 asyncio,但是这些解决方案非常劝退,尤其是需要通过共享内存来通信时,很容易用的效率不够好甚至用错,别说新开发者就拿我这老司机我也不敢说自己精通。而反观 Go,可以很轻松地在 Goroutine+Channel 的加持下编写并发程序而不会有任何显式的锁。
5. 适合团队协作开发
有人说 Python 这种动态语言不适合构建大型项目,是有一定的道理的,单从多人协作开发这个角度,动态语言写起来坑比较多,太灵活,容易出错,能力差的开发者把路走窄了后期维护确实很痛苦。即便我再有经验,修改别人代码时我也需要特别细心,因为没有类型注解所以无法确定复杂对象的类型就非常容易改出问题。尤其是我经历过前司各种不能使用 Python 3,没有构建好完善的 Type Hints 的项目,这个感受很深。现在 Python 开发者能力普遍不高,我认为 Type Hints 是必选的。
而 Go 由于类型系统完善,相对的可以显著的提升团队协作效率,不会出现低级的类型错误把全站搞挂这种情况。
6. gofmt
Python 代码在不同的开发者和团队中都会有不同的理解,作为一个 Python 工程师我每天都在纠结怎么才是「Pythonic」代码、需要用各种静态检查工具、代码规范工具等等,看别人写的代码不够好,生气还心累。
反观 Go 直接格式化成统一的风格,避免了不必要的讨论。不夸张的说,自从我开始写 Go,每天真的会开心很多。
好啦,就这 6 条。
优化不如换语言来的实际
注:这里提的优化包含:运行效率、资源占用 (CPU / 内存)。
换个角度。我有一个问题:「如果坚持用 Python,为了更大限度的降本增益,你需要怎么样继续优化」?
我以前经常会陷入在各种细节里面扣优化,还挺有经验的。但任何一个细节优化到最后会越来越难,效果也越来越不明显。这就需要考虑提升效率的性价比,如果你使用了很复杂的方法才能提高 3% 的性能,那么是否有必要呢?也许从个人或者公司的角度,都是不合算的。
我有一个观点 (不记得哪里总结出来的了,反正一直在脑海里):
如果服务器数量的增长率已经超过了用户增长率,那么就该重构了
这种情况下,就要思考是不是技术 (包含编程语言) 落后了,架构有问题,或者其它问题。
现在给你一个更工程化的 Go 语言,使用它能够节省大量的机器资源,降低硬件成本,API 响应更快直接让用户体验更好。这笔账也太好算了,从长远的看,能省下的钱完全可以搞一个团队专门做重构,超值好不好?
编程语言的「效率对比」没有你想的那么差距悬殊
「效率高」这件事大家不要盲目的看网上各种帖子的性能对比,无论是语言还是框架,那种数据意义不大,关键还是要看业务,看实际的使用场景。我目前还没有把一个类似前司那种大型 Python 应用移植到 Go 的经验,所以没办法举个真实的例子 (其实即便有,那也只是在哪个特定的条件下对比的结果)。在我个人项目体验中,用 Go 比 Python 快 3 倍肯定是有的。这里我猜有很多同学很好奇:「很一般嘛,为什么不是网上或者某篇文章里说的 10 倍、30 倍这种提升呢?」这主要是因为在实际的应用中不仅仅是各种逻辑操作,还有很多非代码执行的环节是很难优化的,例如 I/O 操作。
举个「访问数据库」的例子,假设 Go 代码执行效率会高 10 倍,在一个场景下相同的逻辑 Go 花了 1ms,而 Python 花了 10ms,但是数据库操作可能要花费 60ms (忽略语言库效率的不同,单纯指操作数据库耗时),那么最终 Go 花了 61ms,Python 花了 70ms,其实就是差了 9ms。
这不到 1.2 倍 (9ms) 的效率提升其实远小于我们一开始预想的 10 倍,这也是所有效率对比文章都刻意避开的话题:抛开业务只谈代码执行的提升。
所以各个公司在换语言这件事上并没有那么大的动力,因为:
编程程语言的效率的差距会因为开发效率、维护成本、工具链、业务特点等等因素抹平
再拿一个真实的例子,延伸阅读链接 3 是知乎问答 (知乎最大流量的应用) 的 Go 重构实践经验,其中提到了重构的结果:
Go 语言重构项目完成之后,相比之前的 Python 项目,资源占用减少了 70% - 80% ,复杂接口的性能有 50% 左右的提升。
单从结论上来说,资源使用方面提升明显,但是性能上你可能觉得用 Go 好像并没有想象的那么好的提升,但这其实这才是真实的移植结果。
Python 的发展遇到了瓶颈
是的,Python 和我一样也遇到了瓶颈,它受到的关注从 17 年左右开始疲软,热度因云原生 / 微服务时代出现的 Go 语言而出现了滑坡。从我的角度分析下为什么 Python 不能一直「流行下去」:
- Python 3 的平庸。Python 3 在我看来亮点不多,能想到的大的特性只有 asyncio 和类型注解。我的感受是社区在抛弃旧包袱后,重新再来的思路不太对,Ta 们想要好好的打磨它,加了更多的 Python 语法糖 (当然其中一些新特性也确实挺好的),但是没有把解决构建大型应用中需要解决的问题 (如提升效率和降低资源占用) 放在首位,再比如最近 2 个新版本都在让错误提示更好,怎么说呢?也没问题,但这对于真正的开发者更来说对价值不大。
- Python3 不兼容 Python2。Python 2 很早就停止增加新的特性,但是迁移到 Python 成本的是很高的,Python 3.11 之前效率一直没有放在第一位,没有动力迁移,丧失了很多早期有积累的使用 Python 的技术团队的支持和推动。Python 发展下降的时间在 Python3.6 发布之后,虽然这个版本已经可以在生产环境中使用,但是 3.0 到 3.6 这一路还是挺让人失望的,很多企业和开发者看不到未来开始寻找其他语言 (主要是 Go 和 Rust)。
- 掌握 Python 进阶知识密码的人越来越少。我当时学 Python 时,国内外有很多优秀的 Python 语言的技术博客和文章可以看,而现在?这些博客大部分都不更新了,Ta 们可能做了管理、年龄大了太懒、换了其他语言,还可能已经不从事这个行业等。如果说 5 年前有 5% 的 Python 开发可以叫做「精通」,现在可能不到 1%,后继无人了,这就像武功秘籍,没人学就会失传。
- 架不住 Go 发展的快。你不努力,会被别人蚕食。
Python 工程师写 Go 的体验
接着写写是这几年我写 Go 的感受:
- 开发效率。Go 在开发效率和运行效率之间取得了比较好的平衡,「相对于 Python 没有明显降低」,但是还是低了,我的感受简单的需求效率低 20%,复杂的需求效率能低 30-40%,别和我说写 Go 没那么低,那是你 Python 不够熟。
- 学习曲线。Python 的特点是入门更容易,但是进阶很难,Go 的特点是入门没那么容易了,但是熟悉后进阶的空间更少。
- 类型系统。由于我很早就接受了 Type Hints,所以这部分心理的过渡还是很平缓的。
为什么前司没有重构
这个问题各种渠道的朋友问过我很多次了。今天我以一个普通工程师的角度,把我的思考写出来,当然在前司时并没有做过这个角度的讨论 (当然也可能讨论过我不在场),所以我的观点不一定对,请各位谨慎阅读。
我说的重构分为 2 个方向。注意,我这篇说的只代表离职前那个时间节点对各种相关事情的理解,之后的变化不了解也没有关注了,不过这个总结应该并不会过时。
为什么没有迁移升级 Python 3?
我认为 Python 3.6 之后都可以用于生产环境,当时 Python 最新版本是 Python 3.8。在很早之前,我就在厂内的 Github issue 里面多次留言,期望能够更早的升级到 Python 3,其实我自己也知道,这个升级很渺茫。
为什么呢?或者换个问题:「大型的项目为什么很少听过会有人愿意重构呢?」
基于我的工作内容,我的答案是:
- 收益太低。Python 3 在官方数据或者我在前司的一些个人项目中的体验,效率提升很有限,在当时,应该某些地方效率还不如 Python 2.7。我觉得迁移到 Python 3 最新版, 最终能让应用有个 20% 的提升就很超出我的预期,而其中的提升很多还是顺便做的对逻辑重构带来的 。
- 成本太高。公司成立时间越长,线上的遗留代码就越多,你能看到大量很古老的库、非常特别的用法、各种兼容性质的逻辑等等,很难维护,没人愿意动。很多库和逻辑的依赖会造成如果升级,会有大量大量的代码需要兼容,这是一个巨大的工程,这可不是一个部门几个人几个月能搞完的。
- 没有 OKR 目标。企业招聘产品开发本质上是要做业务的迭代的,所有人都有自己的事情,如果没有一个被高层同意、战略性质的目标和关键结果,用爱升级可别逗了。
当然,在个人情怀里,我非常希望能用起来 Python 3,这样大家可以使用 Python 最新的各种语法和特性,也能完整的用上类型注解,其实长久地看还是值得的。
为什么不换 GO?
2 个理解吧。
1. 现在就挺好
在 Python2017 上,Instagram 的工程师的主题演讲 (延伸阅读链接 1) 中的内容提到了一个让 Python 开发者热血沸腾的观点:
At Instagram, our bottleneck is development velocity, not pure code execution.
演讲者说 Instagram 的最大瓶颈在于开发效率,而不是代码的执行效率。非常霸气,Instagram 作为一个当时月活用户超过 7 亿的超大型应用,可以说回应了对于 Python 执行效率的质疑,当时也给我吃了一颗定心丸。
这个演讲当时看了好几遍,对当时的我来说收获很多,但是同时也给我了一个错觉:我可以义无反顾的专注于 Python 本身,而不考虑语言本身的限制。
演讲中提到了提升运行效率的几个思路:
- 更好的开发工具。在前司这方面可以说做得很好,基本出现问题可以通过各种手段快速的定位到问题,无论是性能瓶颈或者 bug。
- 使用 C/C++ 来重写部分性能敏感的组件。这也是 Python 世界里面提高效率的主流方案,也是前司的主要方法。
- 使用 Cython。前司这方面应用很少,我个人比较喜欢。
除此之外,应该还有个思路,合理的架构设计。
在大厂工作过的朋友,你可能会发现在大型项目上,编程语言其实没有那么重要。在优秀的基础设施环境下,从一个开发者角度看,Python 语言真的远不是最大的问题。例如在前司,会得益于以下几点:
- 基础设施。去前司做产品开发体验是非常好的,只需要关注实现产品需求即可,基础设施提供了足够好的体验,各种问题有平台组的同事帮助解决。
- 微服务。千万不要轻易相信现在还有千万行代码级别的项目,绝绝大部分都改用了微服务架构,按照业务等等角度把它们拆分成不同的微服务,这样不会出现一个代码量「超大」的服务的情况,这样的优点微服务可以独立部署、资源隔离,出了问题不会影响到其他服务,尽最大的努力从架构角度让项目更可控。所以开发者只需要关注这个服务调用的性能,而对于非常具体的需求,通常服务调用的效果还不错就会造成一种错觉。
- 不差钱。不考虑机器成本,在微服务架构下只要给这个服务横向的多加些资源就可以了。
- 对性能不「卷」。一般都不会对工程师有性能方面的苛刻的 OKR 要求,更多的是开发者自己对自己的要求。所以当开发者能力和水平普遍较差是就会给性能问题埋下爆发的种子。
需要说明:
- 微服务架构下编程语言带来的运行效率问题本质上没有得到解决,甚至更差 (因为有远程调用、序列化 / 反序列化、资源调度等等额外的开销),只是让语言的问题对业务发展的影响降到最低。
- 微服务架构还会隐藏一个问题,就是可能一个 API 调用链条上每步的调用耗时都可以接受,但是一个 API 往往会请求不同服务最终产生结果,那么 API 耗时是所有服务调用的总和,这个耗时就容易长到不能接受。开发者容易在微服务架构下只关注服务调用本身,没人站在全局的角度去关注最终的效率,所以很难发现性能问题和瓶颈。
所以如果你发现一个大公司在执着于某个语言,从外面看它就是不想拥抱变化,也不用觉得它一直在挣扎和痛苦,也许还可以,就算是有些阵痛但远没有需要达到必须刮骨疗伤的地步。
小节最后,还需要提醒一下。大家如果用过 Instagram,可以知道它的产品复杂度远低于大部分国内应用 (包含知乎),Instagram 有明显的热点数据和页面,在产品开发和架构实践上,要保证这个产品服务的质量其实难度并不高,所以也不是它们用的 Python 足够好,只是还没不到语言的瓶颈那一步。
2. 缺乏一个足够有视野和情商的高层
早些年我对知乎的技术挺无感的,当时知乎挺有野心的拓展了很多业务,作为前司雇员的身份我一直很敌视知乎,所以在公众场合我一般是骂知乎的 (可以翻我的知乎想法,离职后只剩下夸了)。当年得知知乎把社区核心业务使用 Go 重构,我从心底里佩服。
现在再回头看,当初知乎 Python 转 Go,从选择的语言、重构速度、结果上我认为都非常不错。可以想到其中肯定有一个或者多个足够有视野和情商的高层在推动才最终落地。
为了说明这部分内容,先讲一个真实的故事。时间回到 19 年 8 月份,我突然有了想法,想做一个我当时认为「宏伟」的事情。所以有一段时间搞了一个 subject-go (github) 组织,准备把我一个主要维护和开发的项目重构成 Go 的。当时选择它的原因如下:
- 只是一个服务,不直接对用户访问。
- 流量还可以,不大不小。
- 可以说是我从零开始做,我对其业务、代码极为熟悉,其中业务逻辑和代码质量优秀 (自夸一下),非常方便在重构后用作对比
- 代码量少。这个 Python 应用代码量不多,应该在 3-5 千行。
但是由于它和前司各种基础设施以及主站复用部分代码高度关联,我需要把所有相关的地方都要改成 Go 的版本,例如数据库、缓存、消息队列等等。所以需要改的代码量还是挺巨大的,不过由于在前司呆的时间比较久,各个部分都很了解,再加上自认为对 Go 还算熟悉,我觉得完成它没有问题。
我当时的设想是先把它做的差不多的时候再和大家说,到时候开发经验、难度、重构效果等等内容就可以有比较准确的结论了。不过当时遇到它会调用其他服务,而其他服务还是 Python 写的,且微服务方案是自研的无法直接用,所以一开始我的预案是给其他服务额外添加一个供外部调用的 Thrift 服务作为过渡,我的设计是 Go 服务和其他服务都通过 Thrift 通信。
然后在 Github issue 里面 (应该是它: subject-go/README/issues/1,具体的内部人员可以拼出地址看到) 发起了讨论,介绍了我想做的这个服务的具体方案、目前的进展、使用 Go 的体验,也结合各种厂内原因提出为什么应该使用 Go 等等内容,之后在技术大群邀请大家来讨论。不过很遗憾我没把这个页面截图保留。这个很有意思,其中很多人同意我的观点,并有同学想要参与进来;很多人不同意我的观点,有人提出了具体的反对意见。过程不提,很遗憾,它最终无疾而终,我在离职前也关掉了这个 issue。
今天我说出来它的目的只是在反思,当时我有几件事情是错的:
- 应该说服老板在动手。工程方面的问题一定可以找到解决方案,但是工作中最大的挑战是当我有了这个想法,且坚持它是对的,没有第一时间说服老板,和 Ta 达成共识后,让 Ta 帮你反复沟通,开会,最终把事情做成,各方利益也都得到满足。
- 只想着技术推动而没有考虑业务。开发者会有一堆待办事项,迁移 Python 3 或者换成 Go 这种事情优先级会被排到最后,当时我应该从业务角度出发,实现「合作共赢」。这正好要提到在国内我目前最佩服的毛剑老师的一个采访视频 技术人如何在多重角色中游刃有余 ,里有一个非常好的说明,合作共赢部分从 3:18 到 4:40。当时看完这段感觉整个人都升华了,快来试试。
- 情商。直且不愿意控制情绪,努力提高中,哈哈。
- 没有持续的坚持。我是一个很坚持的人,但也是一个容易放弃的人,很矛盾。第一次看《肖申克的救赎》,看到「安迪为了增加监狱里的图书,给州议会写信,起初一周一封,后面一周两封,最终肖申克监狱图书馆扩充成了新英格兰地区最好的监狱图书馆」的情节非常震撼。如果是现在的我,当时这件事即便得不到支持我会做完 (哪怕只是本地可用),也会尝试。
- 权力。有了权力,才能做自己想做的。不过这个确实不是我的性格。
回到这小节的主题,每个做成了重构 (不限于 Go) 的企业,都有一个或者多个足够有视野和情商的高层,Ta 们会说服自己的老板并能把这个事情推动直到完成,厉害👍🏻
后记
好了,该说的应该都说完了。
这个博客未来会主要更新 Go 相关的文章,但我会继续写 Python,也会持续关注它。
最后的最后,感谢 Python。
写得很棒