“全栈”开发者是近年来出现的一个热词。究竟什么是“全栈”开发者,怎样才能成为“全栈”开发者?我将在本文中谈谈对这几个问题的理解。
- 什么是“全栈”开发者
"全栈"(Full Stack)一词包含了两个字 - “全”和“栈”。我们先来看看什么是"栈",它一般有以下几个方面的含义,并且常常被混用:
- 一种后进先出的线性数据结构
- 函数调用栈(Callstack)
- 函数调用栈反映出的系统的层次结构和依赖关系
"全栈"一词中的"栈"是含义3. “全栈”开发者就是能够全面理解和操控自己开发的系统的栈的开发者。
那么,到底栈上都有什么?理解到什么样的程度才能算得上全面?我们举两个例子来看一看。
先看一个简单的栈,以一段经典的Hello World C代码实现 :)
#include <stdio.h>
int main() {
printf("Hello World!\n");
return 0;
}
在一个普通开发者的眼中,这个程序的栈是很简单的,大概是下面这个样子:
图1 |
---|
printf() |
main() |
C代码从main函数开始运行,main函数调用printf函数打印出"Hello World!",over.
对C语言运行时和操作系统有更多了解的开发者能看到一个更全的栈,至少是下面这个样子 (本文采用Linux的系统术语,各主流操作系统的进程模型和系统调用接口设计差别不大,读者可自行类比):
图2 |
---|
a.out ---------- |
write() |
printf() |
main() |
crt0() |
shell ---------- |
fork() |
当上面的C代码编译链接生成binary a.out后,在终端中运行a.out时, shell会fork出一个子进程并在其中通过exec()系统调用将a.out的代码覆盖自身。C链接器在生成a.out时会链接C运行时初始化代码crt0.o并以crt0()作为代码镜像入口,该代码会初始化动态分配内存的堆,处理环境变量和命令行参数,并把它们作为参数传给main函数,main()函数返回后接着处理通过atexit()注册的回调,最后调用exit()使自己的进程退出。printf()库函数通过write()系统调用打印出"Hello World!",而它写入的文件句柄在不同的场景下可能完全不同,例如,当我们把stdout重定向到一个本地文件时,它是一个本地文件句柄;当运行在ssh连接的终端时,它是一个TCP socket.
尽管图2的栈比图1多了几层,它仍然是一个高度简化的模型。对内核和软硬件接口有更深了解的开发者至少还能看到:
- 进程的虚拟内存模型: .text, .data, .bss, 堆和栈的映射地址以及读写属性的不同,其中.text是只读的,无法修改。虚拟内存需要CPU的MMU硬件配合才能实现。
- fork()可能因为进程数超限或内存不足失败
- exec()加载另一image对cpu cache的影响
- write() (如果是ssh连接的终端) 由于send buffer占满而阻塞
- 不同ISA的CPU可能有不同的word size和不同的寄存器使用惯例
- ...
综上,即便只是一个最初级的编程教学示例代码,要深入全面理解它的栈,也需要对数据结构,操作系统,编译原理,网络,系统结构这些方面有基本的掌握。
下面我们再来看一个更实际的例子,小博无线官网( www.rippletek.com )的栈(图3)。由于小博无线官网基于阿里云计算平台,下图的云计算工具名称均采用阿里云官网的命名,但这些基础工具在全球各大云计算平台均有相同功能的实现。
图3 |
---|
动态页面 ------- |
公网4层转发负载均衡 (1) |
nginx容器组 (2) |
内网7层转发负载均衡 (3) |
rails容器组 (4) |
云数据库RDS版 (5) |
静态资源 ------- |
对象存储 (6) |
全站采用动静分离以及前后端分离的建站方案。栈的各个层级说明如下:
- 域名www.rippletek.com指向一个公网4层转发负载均衡器
- http请求通过公网负载均衡器进入nginx容器组
- nginx反向代理到内网7层转发负载均衡器
- 反向代理请求通过内网负载均衡器进入rails容器组,rails渲染完成带资源版本号的页面
- 页面通过ajax请求动态数据, rails应用从RDS中读取数据并返回
- 静态资源都存放于对象存储
对于这样规模的系统的栈,深入讨论到上一个Hello World的例子不现实的,也是不必要的,仅掌握基本的系统层次结构即可,在必要时再深入探索细节。下图为rails容器的实现层级。
图4 |
---|
rails |
ruby |
passenger |
apache |
docker |
Ubuntu |
云主机 |
- 怎样才能成为“全栈”开发者
计算机系统的整体概念,对于软件开发者来说,至关重要。
只有将数据结构,操作系统,编译原理和系统结构融汇贯通,才能全面深入的理解系统的各个层级,进而随心所欲的操纵计算机。这样的程度,不是任何21天的速成培训可以达成的,21个月亦不可能。至少需要10年坚持不懈的学习,思考,探索和实践。
从随心所欲控制计算机这个角度来看,所谓"全栈"开发者,和《人月神话》中的首席程序员,以及《黑客与画家》中的黑客级开发者,似乎并没有什么区别。但为何最近几年才出现了“全栈”这个新词呢?这是由于全开源栈(上面的图4就是一个很好的例子)在最近几年兴起并被大量的互联网创业公司广泛采用。而云计算的兴起,又大幅降低了互联网创业的资金和技术门槛。全开源栈出现后,整个系统的所有知识都可以通过阅读源码和文档获得,这才使得“全栈”开发者的出现成为可能,而黑客级的开发者可以在短时间内成为“全栈”开发者。
对热爱技术的人们来说,我们目前所处的这个时代是一个前所未有的好时代。google + github + stackoverflow 可以让我们不用去bookkeeping各种trciks, 而是专注于思维框架和内心机器的训练。
然而,必须记住的是,在成为“全栈”开发者的道路上,绝无任何捷径,这是一条只有静下心来死磕才能走通的路。结合我自己的经历,有下面几点建议:
- 一定要有完整的作品。哪怕只是一个很小的东西,自己一个人独立完成框架设计,功能实现,测试并发布,也会对能力提升有很大帮助。当然,在绝大部分技术公司,开发人员不可能有这样的机会。这说明了在业余时间进行编程训练的必要性。
- 尽可能多学习几种不同类型的编程语言。要理解计算机系统的运行方式,对C语言的纯熟掌握是必不可少的。而C语言归根到底只是汇编语言的速记形式,所以还需要掌握一种主流CPU,如x86或ARM的常用指令。ISA的设计不会有太大的差别,掌握一种后,其余都可类推。至少学习一种能快速开发验证自己想法的语言,推荐python或ruby, 性价比都很高。至少学习一种面向对象以及面向函数的语言。
- "食不厌精,脍不厌细"。遇到问题多追细节。例如上面图1的Hello World callstack, 多问几个为什么,就能把计算机系统几乎一切方面的知识都包含进去。
- 掌握本质,方能举一反三。以数据结构中的散列表为例,它可以是lua中的一个table, php里的一个array, javascript的一个object, ruby的一个class, golang里的一个map。而一个大型分布式散列表,就是阿里云对象存储。