なぜ Python から Go に切り替えたのですか?


著者微博: http://weibo.com/p/1005052755890521

元のアドレス: http://toutiao.io/r/rnmoe


操り人形マスターである Liu Yu の招待で、私は Xishanju の運用および保守チームに行き、なぜ私たちのプロジェクトを Python から Go に切り替えたいのかについて簡単に話しました。


率直に言って、Python ユーザーのグループの前で、なぜ Python をやめて別の言語に切り替えたのかを話すのは、実際には非常にストレスの多いことです. 言語論争は、vim と emacs 論争と同じです. 永遠の解決不可能なトピックです.注意を払うことは、ファンからの強い反発につながる可能性があります。そこで、プロジェクトの実際の状況から始めて、最終的に go を選んだ理由について話します。


なぜパイソンを捨てるのか

まず最初に、なぜ私たちが python を選んだかについて話さなければなりません。Enterprise Kuaipan チームに参加する前は、以前の金山 Kuaipan を含むプロジェクト全体が Python を使用して開発されていました。この方法を選んだ理由について、当時のアーキテクトであるオニオンは、主に Python が習得しやすく、開発が速いためだと語っています。サーバーサイド開発の経験がないチームのほとんどの学生にとって、python は本当に良い選択です。


Python のシンプルさと効率性を深く理解しています。当時、プライベート クラウド プロジェクトには少数のプログラマーしかいませんでしたが、多くの大企業にサービスを提供し、カスタマイズされた開発を行う必要がありました. Python のおかげで、すぐに生計を立てることができました. その後、同社の高速ディスクが停止された後、ライト オフィス プロジェクトを開始し、当然のことながら python を使用して元のバージョンをビルドしました。

Python は非常に強力ですが、主に次の側面から、Python を使用する際にいくつかの問題が発生しました。

  • 動的言語
    python は動的に強く型付けされた言語です。ただし、int + string などの実行時エラーは引き続き発生する可能性があります。これは、変数の場合、コードを記述するときに、変数の型を簡単に忘れてしまうことがあるためです。
    Python では、同名の関数の出現を許可することができ、後者の関数は前の関数を上書きします. これにより、システムで非常に深刻なエラーが発生したことがあります.
    前述のように、静的言語は、実行時に問題が発生するまで待つのではなく、コンパイル時に問題を検出するのに役立ちます。非常に完全なテスト ケースがありますが、常にケースの省略があります。なので、実行時エラーが発生するたびに、コンパイル時に見つけられたらいいなと心の中で思っています。

  • パフォーマンス
    実際、これは常に多くの人が python に不満を持っている場所でしたが、python には適したものがあります。また、python を使用していくつかの高性能モジュールを開発するのは少し難しいです。
    Python の GIL では、真のマルチスレッド化は不可能です。ただし、一部の計算に複数プロセスの相互作用が必要な場合は、プロセス間の通信オーバーヘッドも考慮する必要があります。
    ステートレスな分散処理で複数のプロセスを使用することは非常に便利です.たとえば、http リクエストを処理するために、nginx の背後に 200 台以上の django サーバーをマウントして http を処理しましたが、プロセスが多すぎると、当然全体のマシン負荷が高くなります。
    しかし、http リクエストを処理するために複数の django プロセスを使用したとしても、Python はまだいくつかの非常に大きなリクエストを処理できません。そのため、openresty を使用して、lua を使用して高頻度の http リクエストを実装します。しかし、これは 2 つの開発言語の使用につながり、ロジックによっては 2 つの異なるコードを記述する必要があります。

  • 同步网络模型
    django的网络是同步阻塞的,也就是说,如果我们需要访问外部的一个服务,在等待结果返回这段时间,django不能处理任何其他的逻辑(当然,多线程的除外)。如果访问外部服务需要很长时间,那就意味着我们的整个服务几乎在很长一段时间完全不可用。
    为了解决这个问题,我们只能不断的多开django进程,同时需要保证所有服务都能快速的处理响应,但想想这其实是一件很不靠谱的事情。

  • 异步网络模型
    tornado的网络模型是异步的,这意味着它不会出现django那样因为外部服务不可用导致这个服务无法响应的问题。话说,比起django,我可是非常喜欢tornado的,小巧简单,以前还写过几篇深入剖析tornado的文章了。
    虽然tornado是异步的,但是python的mysql库都不支持异步,这也就意味着如果我们在tornado里面访问数据库,我们仍然可能面临因为数据库问题造成的整个服务不可用。
    其实异步模型最大的问题在于代码逻辑的割裂,因为是事件触发的,所以我们都是通过callback进行相关处理,于是代码里面就经常出现干一件事情,传一个callback,然后callback里面又传callback的情况,这样的结果就是整个代码逻辑非常混乱。python没有原生的协程支持,虽然可以通过gevent,greenlet这种的上patch方式来支持协程,但毕竟更改了python源码。另外,python的yield也可以进行简单的协程模拟,但毕竟不能跨堆栈,局限性很大,不知道3.x的版本有没有改进。

  • 开发运维部署
    当我第一次使用python开发项目,我是没成功安装上项目需要的包的,光安装成功mysql库就弄了很久。后来,是一位同事将他整个python目录打包给我用,我才能正常的将项目跑起来。话说,现在有了docker,是多么让人幸福的一件事情。
    而部署python服务的时候,我们需要在服务器上面安装一堆的包,光是这一点就让人很麻烦,虽然可以通过puppet,salt这些自动化工具解决部署问题,但相比而言,静态编译语言只用扔一个二进制文件,可就方便太多了。

  • 代码失控
    python非常灵活简单,写c几十行代码才能搞定的功能,python一行代码没准就能解决。但是太简单,反而导致很多同学无法对代码进行深层次的思考,对整个架构进行细致的考量。来了一个需求,啪啪啪,键盘敲完开速实现,结果就是代码越来越混乱,最终导致了整个项目代码失控。
    虽然这也有我们自身的原因,譬如没好的代码review机制,没有好的项目规范,但个人感觉,如果一个程序员没经过良好的编码训练,用python很容易就写出烂的代码,因为太自由了。
    当然,我这里并不是说用python无法进行大型项目的开发,豆瓣,dropbox都是很好的例子,只是在我们项目中,我们的python代码失控了。

上面提到的都是我们在实际项目中使用python遇到的问题,虽然最终都解决了,但是让我愈发的觉得,随着项目复杂度的增大,流量性能压力的增大,python并不是一个很好的选择。


为什么选择go

说完了python,现在来说说为什么我们选择go。其实除了python,我们也有其他的选择,java,php,lua(openresty),但最终我们选择了go。


虽然java和php都是最好的编程语言(大家都这么争的),但我更倾向一门更简单的语言。而openresty,虽然性能强悍,但lua仍然是动态语言,也会碰到前面说的动态语言一些问题。最后,前金山许式伟用的go,前快盘架构师葱头也用的go,所以我们很自然地选择了go。


go并不是完美,一堆值得我们吐槽的地方。

  • error,好吧,如果有语言洁癖的同学可能真的受不了go的语法,尤其是约定的最后一个返回值是error。项目里面经常会充斥这样的代码:

      if _, err := w.Write(data1); err != nil {
          returun err
      }
      if _, err := w.Write(data2); err != nil {
          returun err
      }

    难怪有个梗是对于一个需求,java的程序员在写配置的时候,go程序员已经写了大部分代码,但是当java的程序员写完的时候,go程序员还在写err != nil

    这方面,errors-are-values倒是推荐了一个不错的解决方案。

  • 包管理,go的包管理太弱了,只有一个go get,也就是如果不小心更新了一个外部库,很有可能就导致现有的代码编译不过了。虽然已经有很多开源方案,譬如godep以及现在才出来的gb等,但毕竟不是官方的。貌似google也是通过vendor机制来管理第三方库的。希望go 1.5或者之后的版本能好好处理下这个问题。

  • GC,java的GC发展20年了,go才这么点时间,gc铁定不完善。所以我们仍然不能随心所欲的写代码,不然在大请求量下面gc可能会卡顿整个服务。所以有时候,该用对象池,内存池的一定要用,虽然代码丑了点,但好歹性能上去了。

  • 泛型,虽然go有inteface,但泛型的缺失会让我们在实现一个功能的时候写大量的重复代码,譬如int32和int64类型的sort,我们得为分别写两套代码,好冗余。go 1.4之后有了go generate的支持,但这种的仍然需要自己根据go的AST库来手动写相关的parser,难度也挺大的。虽然也有很多开源的generate实现,但毕竟不是官方的。

当然还有很多值得吐槽的地方,就不一一列举了,但是go仍旧有它的优势。

  • 静态语言,强类型。静态编译能帮我们检查出来大量的错误,go的强类型甚至变态到不支持隐式的类型转换。虽然写代码感觉很别扭,但减少了犯错的可能。

  • gofmt,应该这是我知道的第一个官方提供统一格式化代码工具的语言了。有了gofmt,大家的代码长一个样了,也就没有花括号到底放到结尾还是新开一行这种蛋疼的代码风格讨论了。因为大家的代码风格一样,所以看go的代码很容易。

  • 天生的并行支持,因为goroutine以及channel,用go写分布式应用,写并发程序异常的容易。没有了蛋疼的callback导致的代码逻辑割裂,代码逻辑都是顺序的。

  • 性能,go的性能可能赶不上c,c++以及openresty,但真的也挺强悍的。在我们的项目中,现在单机就部署了一个go的进程,就完全能够胜任以前200个python进程干的事情,而且CPU和MEM占用更低。

  • 运维部署,直接编译成二进制,扔到服务器上面就成,比python需要安装一堆的环境那是简单的太多了。当然,如果有cgo,我们也需要将对应的动态库给扔过去。

  • 开发效率,虽然go是静态语言,但我个人感觉开发效率真的挺高,直觉上面跟python不相上下。对于我个人来说,最好的例子就是我用go快速开发了非常多的开源组件,譬如ledisdb,go-mysql等,而这些最开始的版本都是在很短的时间里面完成的。对于我们项目来说,我们也是用go在一个月就重构完成了第一个版本,并发布。


实际项目中一些Go Tips

到现在为止,我们几乎所有的服务端项目都已经转向go,当然在使用的时候也遇到了一些问题,列出来算是经验分享吧。

  • godep,我们使用godep进行第三方库管理,但是godep我碰到的最大的坑就是build tag问题,如果一个文件有build tag,godep很有可能就会忽略这个文件。

  • IO deadline,如果能自己在应用层处理的都自己处理,go的deadline内部是timer来控制,但timer内部采用一个array来实现的heap,全局共用一个锁,如果大并发量,并且timer数量过多,timeout变动太频繁,很容易就引起性能问题。

  • GC,这个前面也说了,多用内存池,对象池,另外,我还发现,如果对象的生命周期跟goroutine一致,对性能的提升也不错,也在go的group问过相关问题,大家猜测可能是因为一些对象其实是在goroutine的8k栈上面分配的,所以一起回收没有额外GC了。

  • Go gob,如果要做RPC服务,gob并不是一个很好的选择,首先就跟python的pickle不通用,然后为了做不同系统的数据传入,任何包都必须带上类型的详细信息,size太大。go里面现在还没一套官方的RPC方案,gRPC貌似有上位的可能。


总结

虽然我现在选择了go,但是并不表示我以后不会尝试其他的语言。语言没有好坏,能帮我解决问题的就是好语言。但至少在很长的一段时间,我都会用go来进行开发。Let' go!!!


关于我们:

码术微信号:codemanship

简介: “码术”致力于为探索程序世界,提升代码质量。加入“码术”,一起畅游01世界。目前,“码术”正在关注golang语言。


おすすめ

転載: blog.csdn.net/codemanship/article/details/46490775