后端语言选择

基本要求

1、服务端要求运行时灵活更新,能长时间不停机,有可靠的分布式解决方案。

2、招聘容易能快速招聘到合格的开发人员


我们在比较流行的语种中进行了选择:

不用考虑先排除 C和C++以及Rust ,之前有项目用过!性能确实高但静态语言容易出灾难性Bug;

再看Nodejs网易也推出过pomelo,调研后我们认为内存管理有一定缺陷GC较弱,系统容易崩盘,语种编程范式多在大型分布式系统的构建中,我们希望代码更严格。

Scala属于学院派语种,不稳定版本兼容性差,还不如直接用Java。

Python最主要的优势在于代码容易撰写,可读性很高,但面对大型业务耦合紧密的工程时就不行了。


我们主要在下面三个语言之间做选择

语言

性能

编码简单

热更新

低耦合度

高容灾

高并行

灵活性

性能追踪

敏捷处理

框架

总分

Go

10

10

2

8

5

9

8

5

4

4

65

Erlang

5

5

10

10

10

10

9

7

8

8

82

Java

10

6

5

5

7

8

6

9

6

10

72

这三类语言的应用框架,我们都尝试过并运用在很高流水的项目中;

我们最得兴应手的是Java,不是因为语言简单,主要是因为团队迭代不会影响项目,好招聘。

尽管Java得分较高,但代码上显得很臃肿,写起来让人不怎么开心。

Golang和Erlang是我优先考虑的,Erlang高容灾,Golang高性能。

我之前在Erlang和Golang之间有过徘徊主要是Erlang又爱又恨,原因在于

1、Erlang有最灵活的开发编程模式,控制台,热更新

2、Erlang有最天然的容错能力,进程状态独立

但 ---------------------》

3、性能问题,性能比较低,综合密集性业务处理有一定开发难度

4、开发成本,招人比较难,广州、成都两地Erlang程序较多,其他地区如果地方没有人才吸引力,切记慎用。

下面谈谈实现

1、并发

Go 对高并发的支持通过 goroutine 实现。goroutine 可以理解为轻量级的 线程(thread)。同一个 Go 应用创建的 goroutine 共享地址空间。

Erlang 的高并发通过轻量级 进程(process)实现,每一个进程都有独立的状态记录。

另外,使用 goroutine 要注意,goroutine 运行完毕后,占用的内存放回内存池备用,不会释放。

对于每一个任务都需要有独立状态的场景,Erlang 的 process 更有优势。

2、抢占式调度

Erlang 的任务调度器有一个 reduction budget 的概念。进程的任何操作都会造成预算消耗,包括函数调用、调用 BIF、进程堆垃圾回收、ETS 读写、发消息(目标邮箱堆积的消息越多,消耗越大)。Erlang 的 正则表达式库 也被做了修改以支持 reductions。所以如果进程在长时间执行正则表达式匹配,也一样会消耗 reductions,也会被抢占。

Go 之前的调度器只在 syscall 发生时调度,优化后可以在任何函数调用时调度。但是要注意,如果在 goroutine 里写一个死循环,Go 的调度器不能有效抢占,同一个调度器的 其他 goroutine 会被挂起。

实际上,这就是为什么我说Erlang是真正能够实现抢占式多任务并且能真正做好软实时的少数语言之一的原因。Erlang更看重的是低延迟而不是单纯的吞吐量,这在程序设计语言运行时中是不多见的。

再准确地说,抢占(preemption)[2]指的是调度器能够强制剥夺任务的执行。所有基于协作(cooperation)的多任务都是做不到抢占的,例如Python的twisted库、Node.js和LWT(Ocaml)等。但是更有意思的是,Go(golang.org)和Haskell(GHC)也都不是完全抢占式的。Go只有在通信的时候会发生上下文切换,因此一个密集的循环就会霸占整个处理器核心。GHC会在内存分配的时候发生切换(不得不承认内存分配是Haskell程序中一个非常频繁的操作)。这些系统的问题在于,将处理器核心霸占一段时间的后果就是影响系统的响应延迟——想象一下这两种语言执行数组操作的时候的情景。

这就引出了软实时(soft-realtime)[3]的概念,软实时指的是如果无法满足时间截止线需求的时候会导致系统服务水准降级(而不是整个失败)。假设在运行队列中有500有100个进程。第一个进程正在做一个耗时50毫秒的数组操作。在Go或Haskell/GHC[注3]中,这意味着任务2-100都需要至少50ms。而在Erlang中则不同,任务1有2000个reduction的预算,相当于大约1ms的时间。然后用完reduction预算后,任务1会被放回运行队列,这样任务2-任务100就有机会运行。这自然意味着所有的任务都有公平的时间份额。

3、垃圾回收

像 Java 一样,Go 的垃圾回收是全局的,这意味着一旦垃圾回收被触发,所有的 goroutine 都会被暂停,造成一段时间的业务延迟。

Erlang 的垃圾回收是 进程 级别的,每一个进程都有自己独立的垃圾回收器,一个进程的垃圾回收被触发,不会造成其他进程被挂起。相对来说带来的业务延迟小。

4、错误处理

Erlang 的每一个进程都有 进程 ID (PID),同时也可以给进程注册名字,也就是说每一个进程都有独立的身份,可以有效的监控每一个进程的状态。进程异常退出时,可以捕捉到退出事件,并重启进程(参见 otp 的 supervisor/worker)。

Go 的 goroutine 没有身份识别,goroutine 的状态没办法监控。

5、动态反射

Erlang 动态语言的特点,使它天然支持 REPL,另外 Erlang 支持 remote shell,我们可以在 Erlang 运行时,连接到 remote shell 与任何一个进程交互。这些特性对一个需要长期运行的复杂系统的维护带来了极大的便利。开发阶段也能有一些便利。

Go 是静态语言,不支持 REPL。

6、静态编译

Erlang 是动态语言,有所有动态语言的所有缺点:

  • 运行速度慢
  • 不能做早期错误检查,需要依赖全覆盖单元测试
  • 代码规模大了,给编写带来困扰

Erlang 现在也引入了 spec,对函数的参数返回值在编译时做类型检查,但是跟静态语言比起来效果差的很远。

不过正是因为是动态语言,Erlang 实现了运行时代码替换,这个特性对一个需要长时间运行的工业级产品,是一个非常重要的功能。

Go 是静态语言,运行速度快,编译时做严格的类型检查,可以避免很多隐患。

6、框架

Erlang 的 OTP 框架支持服务器端开发常见的几种模式(applications, supervisors, wokers),方便代码的组织。

Go 暂时没看到类似的框架。

结论 , 根据公司的实际情况人员配置,如果不够高,团队迭代,新鲜血液融入困难,就别选Erlang了。

发布了41 篇原创文章 · 获赞 10 · 访问量 3万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章