为什么用 Java —— 关于并发编程

1,154 阅读9分钟
原文链接: www.sdk.cn

摘要:前几天发完《聊聊 Ruby on Rails》那篇文章后,有朋友问到:后台准备考虑从 Ruby 迁移,问有没有什么推荐的语言,尤其是主要需求是大规模高并发,便于维护升级。从我个人来看,Square 和 Airbnb 的后端迁移都是移到了 Java。对于 Java 的优势,很多文章讨论过,我也没必要重复,简单概括下:

前几天发完《聊聊 Ruby on Rails》那篇文章后,有朋友问到:后台准备考虑从 Ruby 迁移,问有没有什么推荐的语言,尤其是主要需求是大规模高并发,便于维护升级。

从我个人来看,Square 和 Airbnb 的后端迁移都是移到了 Java。对于 Java 的优势,很多文章讨论过,我也没必要重复,简单概括下:

  1. JVM 经过这么多年的千锤百炼,无论从性能还是稳定性来说,已经成为一个可靠的系统。尤其是 JVM 的垃圾回收算法等,比一些其它语言已经好出太多。JVM 的性能想达到最优化,需要经过一些 tuning,通常比较复杂。听陶涛师兄说 Azul 公司开发的一个叫 Zing 的 JVM,做了一个很牛的 GC 算法,用来做 JVM tuning,听说很好用。

  2. 各种成熟的库函数

  3. 各种成熟的 design pattern

  4. Java 存在大量的优秀工程师。据 StackOverflow 最近的一个统计,全栈工程师会使用 Java 的约占 30.7%,而后端工程师中会使用 Java 的则占约 41.6%。语言界最成熟的 community。

  5. Java 谨慎而持续的版本更新。

然而毕竟 Square 还是 Airbnb,其后端对于高并发的需求量并不是很大,所以说到这一方面,我确实没有什么发言权。恰好昨天和陶涛师兄在食堂吃饭,就聊起了这个话题。聊完了觉得获益匪浅。

陶涛,是我科大少年班的同系师兄,2006 年到 2010 年在微软锻炼了四年之后,2010 年在起在 Twitter 就职了两年参与了 Twitter 早期的架构。2012年又加入 Pinterest 工作约三年参与了 Pinterest 早期的架构。2014年来到 Airbnb 成为 Airbnb 搜索引擎的核心技术人员。和陶师兄每次吃饭聊天都觉得可以从他那学习到很多他多年在 Infrasturcture 上积累下来的宝贵经验。本来这个话题其实想让他自己写的,可是貌似很多牛人对写文章还是有点惰性的,所以今天很多的内容来自于陶涛师兄,不过还是由我操笔了。

提高并发的两种模式

提高高并发其实有两种大思路。一种是基于异步编程的思想,一种是基于同步多进程/线程的思想。基于异步的方案简单来说就是 Non-blocking on waiting。很典型的如 JaveScript/Node.js。而同步模式则更为广泛,会写的人也很多。对于到底哪种方案更有优势,一是视具体情况而定,二是业界各说各话,并没有一个结论。

值得一提的是,异步编程带来的并发对程序员素质的要求其实要更高。因为一旦你的整个系统中的一个部分没有正确的按照异步来实现。这一部分就很容易成为性能的瓶颈,让整体的并发性能大打折扣。当然,这并不是说所有的程序员都能写出很好的基于同步并发的程序。

基于异步编程的既有方案

处理高并发的访问量,很早其实就出现了一些基于异步思想的 NIO(Non-blocking IO)。一个例子就是 Twitter 在 2011 年开源的 Finagle 系统。(blog.twitter.com/2011/finagl…)Finagle 是一个用于 JVM 的可扩展的 RPC 系统,可以用来构建高并发的服务器系统。Finagle 是用 Scala 写的,但是为 Scala 和 Java 提供了一些 uniform 的客户端和服务器 API,可以很方便的支持新的 protocal。除了 Twitter 自己,Pinterest,Box 等约五十家公司都使用了这一方案。

语言对并行模型的支持

Go 语言通过 Goroutines(CSP模型),Scala 通过 Actors model,Java 通过 akka 中的 Actors 和 本身的 Future,都可以实现对并行的支持。那么这些模型各有什么优势呢?

首先就要看一下 Actor 模型和 CSP 模型的区别:

  1. CSP 使用 channel 通信,没有固定的对应关系。而 Actors 使用 address system 和 inboxes 系统。每个进程只有一个地址。

  2. 严格说来,CSP 中只有 buffered channel 是 asynchronous 的,也就是消息的发送和收取不用等待,而 sender 和 receiver 都是 synchronous 的,都有可能被 block,而 Actor 模型中只有 receiver 会被 block,sender 也是 async 的。因此,CSP 中消息的收取是 in order 的,Actor 模型中却不一定。

  3. Actors 更适用于分布式系统,而 CSP 的 blocking 属性注定它不能很好的应用到多台机器的分布式系统中。

最早对 Actor 模型的实现比较成熟的语言其实是 Erlang。使用 Erlang 的常见的公司和产品包括:

  • Amazon.com 的 SimpleDB

  • Ericsson 的 GPRS, 3G 和 LTE mobile networks。

  • Facebook 的 chat service

  • Linden Lab 的一些 games.

  • Motorola 的 call processing products in the public-safety industry

  • T-Mobile 的 SMS 和 authentication systems

  • WhatsApp 的 messaging servers

  • Yahoo! 的 social bookmarking service, Delicious。

而 Akka 是 JVM 上的一个工具包,支持多种并行编程模型,尤其是 Actor 模型,很多思想来自于 Erlang。目前有针对 Java 和 Scala 的 language bindings。Akka 是用 Scala 写的,Scala 2.10 之后,Akka 的 Actor 实现已经成为 Scala 标准库的一部分。这也是为什么我们常看到说 Scala 是支持 Actor 模型异步编程的。

另一个基于 Actor 模型的工具包叫 vert.x,用于在 JVM 上开发 reactive 的 application。用的人也比较多。提供对 Java, Groovy, Javascript, Ruby 等语言的支持。

而同样最近比较火的 Celluloid,则是针对 Ruby 语言的一个基于 Actor 的并发对象的框架,需要避免任何阻塞操作。如果 Actor 里存在长时间的 IO 阻塞导致线程耗尽,会使所有的 Actor 都卡住。

对于这几种语言方案的比较,网上有很多资料,众说纷纭。但是总的看来,每种方案都是针对每一个特定应用场景而设计的,并不能一概而言哪一种模型一定更优。进一步了解 Actors 编程模型,推荐一个 jdon 的博客:www.jdon.com/actors.html

其中一篇关于《不要将 Actors 用于并发编程》的文章也很有意思:www.jdon.com/46968

所以结论是什么呢?

  1. 如果为了 Scala 的并发特性而选 Scala 不选 Java 完全没有必要,因为 Akka 虽然是 Scala 的标准库的一部分,但是也是可以被加载到 Java 上的。

  2. Java 的 Future 特性也可以对并发提供一定的支持。

  3. 如果 Go 的基于 CSP 的 Goroutines 真的是你觉得必不可少的,可以考虑 Go,但是需要注意的是,Go 的人比较难招,能写好 Go 的并发程序的人更难招。根据 StackOverflow 的统计,熟悉 Go 的程序员可能不到总数的 2%。

关于语言选择

初创公司的 CTO 最重要的就是技术的选择,而技术的选择首当其冲就是语言的选择。先看看你公司的目的是啥。你的产品是 docker 这类的呢?还是 facebook 这类的呢?对高并发的要求是不是真的很高呢?绝大数产品都是应用型创业。这类型的公司,往往主流的老语言就足够用了。好多东西看上去很美,用起来全是坑。

程序员总是对创新和新技术充满渴望的,即使是 Java 老手,可能在学了三五个月 Scala 之后对于有机会加入一个写 Scala 的组(跳槽,换岗,开新service 等)肯定都会是跃跃欲试的。当年某公司的 Ads 部门,语言选型的时候,head of engineering 没有架住 engineer 们的撺掇,选了 Go 作为开发语言,而那时候的 Go,还有很多 library 和 client 等都还没有的。所以该公司就一边开发产品,一边实现 Go 的一些基本库。这对产品最终的发布,在延时上可能至少有一年半载的影响。在当今这个竞争激烈,先下手为强的圈子里,这个代价只能自己闷声吞下去了。

类似的,早在 2010-11 年附近,Twitter 就开始用 Scala。每每遇到 Twitter 的 engineer,那自然对于使用新技术是颇有几分优越感的。然而一样的,不仅需要一边做产品,一边写各种 patch,甚至还要帮助 report 和 fix 一些语言本身的 bug。

当然不论是 Scala 还是 Go,都比几年前那个时候成熟的多。可是比起 Java 丰富的工程师资源,几十年的 community 资源,任何其他语言都没发比。选其他语言可能都会遇到意想不到的麻烦。何况上面说的,该有的并发支持 Java 也都存在。

其实 Square 当年也有一些 service 用的 Go,Airbnb 我们支付组有一个基于 Spark 的 service 也用了 Scala。当公司过了早期阶段,核心的框架都成型,产品也比较稳定后,对新的技术在一定范围内进行适当的尝试还是应该持鼓励态度的。

说到这不的不提一下 C++。只要谈到性能,几乎不可能被超越的语言就是 C++ 了。据说 Google 内部很多的高并发程序还是用 C++ 来写的。然而他们的积累过于强大,很多 internal 的库和工具已经做到千锤百炼,这又是普通小公司没法短期内达到的了。有位八一级计算机系的唐师兄,听说是做 TCP level 的monitoring,自然对性能要求也是极高的。对 C++ 也是推崇备至。所以如果公司有足够优秀的 C++ 工程师资源,又对性能有着极高的要求,C++ 可能还是应该考虑的。

题图:by 张骏峰,科大少年班师兄,小蚁首席架构师