技术漫谈

关于开发语言的选择

2015-09-28  本文已影响0人  墨弈

背景

我们所有产品在初期的时候都使用的Java语言作为后端开发语言,整个架构在演进了几次之后形成了基于微服务的一个复杂架构状态,包括了Restful Service作为Web层抽象,基于Netty的分布式抓取框架,基于Quartz的定时服务,基于Storm的分布式实时计算框架,还有一些附加的日志处理、消息转发、Websocket消息推送服务等等,多个服务之间通过Http协议、或者TCP/UDP协议,或者基于AMQP的消息队列来进行通信。总体来看整体的解决方案严重依赖Java语言或者说JVM语言,在开发的过程中这些解决方案无疑都是强大的,不管是Netty还是Storm都是当时效率比较高且成熟的基础组件,可以说从IO和计算层面我们尽可能的都选用了JVM生态系统里边当时最好的,比如说考虑到Netty本身的多路复用和多线程模型的优秀,我们尽可能的在处理IO上采用类似的结构,或者直接使用Netty作为入口,包括JAX-RS的容器都是直接采用的Netty作为IO和协议解析的框架,而没有采用其他的虽然现在也支持NIO的Tomcat或者Jetty,分布式抓取之前的通信也是采用Netty,Storm之间的通信也依赖于Netty(官方声明测试结果比之前的ZeroMQ的解决方案要好不少),也是在我们技术栈内部实现了某种层面的统一,所有的IO或者通信都采用的Netty,不过也在这几年的开发中发现了一些问题,导致我们一直在用Java的过程中也想找到一种合适的语言来替代如日中天的Java。

Java语言的问题

1. 生产力问题

开发调试慢,整体解决方案复杂。这只是相对而言,对于熟练的开发这也许不是问题,作为企业级解决方案而言,Java语言确实比较繁重,即使依赖了很多自动编译、动态加载、打包并部署的持续集成工具,调试速度依然很慢,与现在的快节奏的开发要求有明显矛盾,举例来说,如果我们要开发一个小的项目,就是包含一个Web api服务,这时候我们需要新建一个工程,写自己的pom文件,组织好工程的依赖和配制文件,然后呢就是基于某种框架,可以是servlet-api也可以是restful风格,或者是spring等等,然后选择一种嵌入式的容器以便不想要部署到哪里就可以自己启动服务,然后代码可能很简单,写一个接口然后打包,编译,一般情况下生成一个可以自己运行的Jar包以及他所依赖的jar包目录,将这个目录上传,启动并测试,假如说需要修改的话就需要改动然后重启整个服务,等着新的代码提交并触发编译部署重启才能生效,为了避免中断服务也需要做一些HA的策略等等,即使做一个很小的事情也需要想对比较重量级的一套东西来作为支撑,互联网时代的开发节奏要求更加敏捷,Java的状态有点令人着急,相对来说动态语言在这方面确实得天独厚,用来开发产品或者服务的原型速度更快,之后如果有性能问题(一般也确实会有)可以考虑用静态类型语言重构。

2. 表达能力问题

作为一个上世纪的语言,一个面向对象但又不是纯的面向对象语言,本身的抽象能力是不错的,在很多大型的服务系统里边我们能够使用一些模式来进行很好的抽象,使整个架构优美,可读性和扩展性都能够良好的体现出来。但总体来说Java语言的编写过程更倾向于过程式的开发,在上一层面上封装了面向对象的特征和行为,语言的设计是上个世纪九十年代的风格,不是说语言本身不好是其抽象能力不够,即使到了Java8也只是对Lambda表达式进行了支持,因此引入了Functional Interface也即只有一个方法的接口,和接口里边的带具体实现的方法(为了兼容以前的代码不得不作出的让步)。Java语言发展到现在其语言特性庞大,如果要完全了解需要几百页的文档,在其发展过程中又只做加法没又减法,语言慢慢风格混杂,变成了现在这种四不像的状态,函数式的特性硬生生的嫁接在原来的面向对象特性之上。

3. 资源消耗问题

Java语言号称一次编译,处处运行,就在于它基于一个需要首先先安装到他所谓的处处的JDK,通过JVM解析编译完成后的字节码来运行,跟操作系统的接口也是在JVM托管的。这样的好处是JVM可以在实时运行的时候对字节码进行进一步的优化,也就是大名鼎鼎的JIT,问题是所有的机器上都要安装可以兼容你的应用程序的JDK,同时JVM启动消耗的资源不少,起码数百M,且启动速度缓慢,同样的直接编译成目标操作系统二进制可执行程序的服务,启动起来消耗的资源小很多且速度快了很多。因此,我个人更喜欢编译语言而不是某种VM上运行的语言,在当前差异化的芯片结构中,像C、GO、RUST这种能直接运行于操作系统之上不基于某些庞大繁重的VM之上还是很有必要的,比如物联网的控制芯片,通常内存也只有几百K,适用性更强一些,而且现在LLVM架构的编译器能够带来性能的大幅优化,所以编译依然是一个很好的选择,除非JIT能够逆天的达到解释执行的极限,因此假如我们看到某些语言有Java语言的开发能力和内存安全特性,依然是可以考虑的。

最近几年所关注的语言

以上说了一些我们虽然完全在使用Java语言开发但是也不得不承受的一些问题,随着时代的发展有一些新的语言进入大家的视线,首先要说的是,我们都不是研究语言的科学家,不是严格的从语言的语法设计和类型系统的别致程度来评价一门语言,更多的是从我们作为使用者实际应用中来考虑哪个更有利于我们整个团队的进化和生产力的提高,如有疏漏和不严谨的地方,望指出。
  曾经在考虑之中的语言包括了Haskell, Go, Scala, Rust四种,主要考虑的因素包括了语言的能力、开发效率、商业化程度、学习曲线、招聘市场人才储备状态。
  后边三中都是比较新的语言了,为什么包含了Haskell呢,他虽然很老但是一直是作为学院派函数式语言的代表,其纯函数式的特性和简洁漂亮的语法(糖)让人看了非常舒服,在接触了面向过程和面向对象的开发后,如果要学习一种新的写代码的思路,面向函数式的语言是目前最好的选择了,而Haskell有是函数式语言的先驱和集大成者,很多函数式语言的语法都是从Haskell借鉴来的。下边我分开说一下我自己的体验,通过学习和写一些实际的项目所体会到的一些优点和缺点。

1. Haskell

作为纯函数式语言,Haskell将必然会产生Side-Effect的代码比如IO操作放到了一起,也即monad风格的部分,而其他的函数可以保证完全的函数式特征,对于同样的输入无论运行多少次结果都是一样的,跟数学中函数的定义一样严格,函数式是一种CPU友好的语言,在当前多核计算机发展状况下,函数式可以让程序非常安全的在多个核心上并发而不用担心大量的数据交互和side-effect, 从而在语言编译过程中能够针对并发进行大幅的优化。语言本身的很多写法也跟数学中的定义很接近,比如定义一个集合
ghci> [x*2 | x <- [1..10]] [2,4,6,8,10,12,14,16,18,20]
看起来很像数学定义,语言可谓优雅漂亮,看着很舒服。作为学院派语言,语言自身设计的要求不可谓不严格,完美的阐述了函数式是什么意思,但是语言的复杂度较高,学习曲线很陡峭,很难保证团队成员的接收程度,也很难招到相关的技术人才。从效率上来讲,Haskell可以优化的跟C语言的级别类似,但如果对某些特性不熟悉稍微改动一些就会造成性能的大幅下降,对新手不算友好。同时在函数式不那么擅长的领域Haskell的商业化程度很低,我们不可能都用Haskell来写一些语法解释或者正则解析等,涉及IO的分布式存储和计算都相对很初级,尤其是对于我们比较感兴趣的数据挖掘机器学习领域没有成熟的解决方案,对于Web项目支持的尚可,有优秀的Yesod框架作为代表。总的来说,我们最终将Haskell定义为值的学习但不会在大型的生产环境中使用的语言。

2. Scala

Scala语言的出现目的很明确,感觉就是为了替代Java而存在,在Java语言越来越力不从心的今天,能够有一门语言既继承了它广大的生态系统,又能够在表达能力和开发效率大大改进的情况,可以说是很有希望的。

3. Go

Go语言目前呈现了很火爆的趋势,由于其简单,整个语言的specification也不过十几页,最多半天就能够完全了解并上手写一些小工具。GO语言最初是希望替代C和C++成为新的系统语言,自带GC垃圾回收,不过最终更多的是替代了python来开发一些服务或者工具,并没有成为系统级别的语言。
  Go语言有很多的优点,编译速度快,有协程和Channel做并发支持和通信,有很多官方的网络协议的库,非常适合于写一些网络服务,启动一个http的接口服务只需要几行代码。目前github上也有大量的第三方项目使用go语言来开发应用或者扩展go的功能,在使用的时候直接import即可。Go的多返回机制也还不错,节省了大量的无意义数据结构和不可读的Map的使用,总的来说Go在其擅长的领域生产力很高,写起来比较流畅,静态类型也足够的安全。目前Docker生态系统里边的各种工具都是Go来写的。最新发布的1.5版本使得交叉编译更加容易,静态链接库的方式使生成的可执行文件在相同CPU架构的操作系统都能运行,减少了额外查找依赖的问题,对我们现在基本同构的Linux服务器而言,也打到了一次编译处处运行的目的。同时Go语言在运行时消耗的资源也比Java要小,启动速度更快,确实是轻量级服务的优选。
  在公司内部我们也使用Go语言做了一些项目,包括了日志分析、日志收集、小规模的产品项目实现,开发的过程中也找到了一些优秀的框架,包括revel, goin等,由于使用简单上手容易,需求的实现速度很快,同时部署也比较容易,就一个二进制文件和对应的配制文件即可。既保持了生产力,也没有牺牲运行效率,处理速度很快,goroutine很好用。但是同时我们也发现了一些问题,在往大规模的系统架构上去演进的时候,go语言的抽象能力有限,除了interface我们没有太多的手段去让整个工程的层次更清晰,只能依靠interface之间的继承、包含关系,让实现有一定程度的分离,可是对于结构复杂的程序抽想出来的效果不够好,也许是我们对Go语言的一些开发模式还不够熟悉,经常出现一个复杂的功能都在一个文件里边且代码上千行。对于小规模的开发,Go语言速度尚可,也相对比较成熟了,Go语言语法简单的好处还在于实现一个东西往往只有一种方法,不同习惯的人看不同的代码也容易看懂,简单直接。
  如果说Go语言最让人烦的地方,目前我感觉就是对于error的处理了,基本上所有的多返回的api都会习惯性的返回一个error,在一些开发调用的方法内部可能是一层一层的往上返回,在应用程序级别的时候就需要对这些error做一些判断处理了,经常发现在一个处理过程中出现了好多个if err != nil 的判断,如果全部都panic出去可能会导致进程不稳定,导致当前运行的goroutine挂掉,进而使整个进程崩溃。

4. Rust

Rust由于1.0版本release的时间太晚,目前没有在实际的项目中使用过,通过目前的了解,Rust应该是可以替代C语言在系统级开发的地位,同时由于一些新的语言特性使得Rust在进行应用开发的时候也依然高效,目前看起来整个语言的适用范围非常的广,只是现在成熟程度太低,没有大规模的应用开发实例。另外直观的感觉,Rust的代码看起来很丑,远不如Haskell的优美,可能对于面向生产环境的语言跟学院派风格差异确实比较大,但也太丑了一点。

最终的选择和原因

通过上边的列举,从篇幅上也能看出我们最终的选择是Scala, 真正在生产中经过开发测试的就是Go和Scala,初期我们优先考虑了Go,毕竟编译成原生的可执行程序理论上能够达到比JVM语言更快的运行速度并且节省资源消耗,如果一门语言有C的效率和Java的生产力,那最好不过了,不过后期使用中也发现了虽然开发足够糙快猛,但是纯粹的面向过程开发,代码平铺直叙的有点冗长,不容易抽象,不是说不能做,而是做起来不那么方便,没有范型,更是缺乏现在的一些高级语言的特点。在以后的技术演进中,整个技术栈可能是异构的,那Go会占其中一部分,但主体可能不是。
  汇总下这两个语言在目前我们公司的技术栈下进行切换的优缺点:

  1. Go的优点
  1. Go的缺点
  1. Scala的优点
  1. Scala的缺点
上一篇 下一篇

猜你喜欢

热点阅读