为什么Ruby程序员应该了解和掌握Docker

Python013

为什么Ruby程序员应该了解和掌握Docker,第1张

Docker技术在Ruby社区是有影响力的,我所知道的一些创业团队很早就在运用它来解决环境管理、持续集成以及部署的问题了。但是,也有一些同学尚未注意到这个技术,或者了解过后认为它不是很重要,所以我想讨论一下Docker对Ruby系技术的帮助。

有的人可能对Docker技术不太了解,不妨参考论坛里的这篇文章(https://ruby-china.org/topics/22004 )以及肖德时写的系列文章(http://www.infoq.com/cn/articles/docker-core-technology-preview )。 Docker 与 Vagrant

我一直很喜欢Vagrant这个工具,两三年前就用它来进行自己项目的环境维护,那时候主要是做测试,由于Vagrant将操作系统环境进行了标准化,我很容易就能让自己的应用系统以及相关的测试结果保持稳定。

Vagrant还有一个好处,Ruby社区比较偏爱Mac,但是线上的系统基本都是Linux,所以开发环境所做的测试是有疑问的,特别是遇到一些有so依赖的gem,这时一个和线上完全一样的环境就特别重要。

其实上面的表述不太准确,Vagrant也有各种provider,我所说的场景,基本上都是virtualbox的provider,所以这些地方正确的说法是 vagrant/virtualbox。

和Docker相比,vagrant/virtualbox组合的成本还是很高的,无论是setup一个环境还是reset一个环境,都需要一段时间的等待,Vagrant只是把virtualbox的操作DSL了而已,底层的做法没有变化。而Docker由于本质上就是一个进程,因此天生就是轻量级的。对于运行时间在分钟级别的自动化测试工作,Docker显然有很大的优势。

当然,也有人会认为Docker不能模拟完整的操作系统,不过这恐怕是一个优点而不是缺点。我在以前的文章中已经说过了,这里概述一下主要观点——

Docker简化了操作系统这个基础设施,让应用精简为其最核心的形态——携带有限资源的进程,在此基础上更有利于架构上的最佳实践。

而对Ruby工程师而言,这个“最佳实践”中肯定少不了的一条就是——微服务。

微服务

Ruby工程师中有很多就是Rails工程师,而Rails实际上更倾向于单体架构,因此后来社区的工程师们才需要在实际工作中总结1 to 30这样的实践。

其实微服务本身不是个教条,即使没有人教,我们也常常自发的去进行服务化改造,但是这个工作并不容易,主要是会受到一些问题的掣肘,比如运维复杂度和系统测试成本会大幅度上升等等。

处理这些困难,首先当然是看是否必要,一些简单场景我们也可以用单体架构直接搞定,但是我们很容易会注意到,这两年大家越来越多的提到了微服务或者服务化,这背后其实是有趋势的——各种业务形态都在朝着互联网级的用户规模推进,同时大家都在努力从每一个用户的各种维度上挖掘价值(这导致了大数据的需求),这些场景变得越来越常见,单体架构是难以支持的。

既然微服务或者服务化不可避免,那么就要有相应的对策,虽然Ruby社区也有很多人在不同问题点上针对微服务进行改进(比如完善异步化框架,以及对服务协议的探索等),但是在基础设施层面,Docker是最重要的武器,没有之一!

对Ruby工程师来说,Docker能做两件事:约束边界和建立通用基础服务。

约束服务边界

Ruby项目Docker化,并不是简单换个虚拟机那么简单,我们会面对拆分的压力,相信很多人尝试用Dockerfile来描述自己的项目的时候都会觉得束手束脚,但这些地方其实是促使我们想清楚——这个应用到底要做什么?它和外界是什么关系?对于外界的变化它如何响应?失败后怎样恢复?

这类的问题对系统架构非常重要。比如应用到底要做什么,这是让工程师去思考系统的目标,无论是提供web服务,管理调度后台任务,还是提供实时分析,它们都应该有一个尽可能单一的目标,在这个基础之上,我们建立的服务才有可能是易测试、易扩展和易维护的。

其它问题也类似,这些地方以前如果没有留意,很可能不是没问题,而是没意识到,使用Docker有助于我们意识到这些问题。

另外补充一点,由于Ruby项目不能完全脱离动态库依赖(java大都可以),本身的打包机制又没有自包含结构(gem+bundle不包括动态库,相比之下,Golang是静态联编的),在分布式环境中的交付和软件包分发其实是有着先天不足的,Docker的Image恰好补上了这一块,简直是睡觉时候有人送枕头了。 建立通用基础服务

当我们将应用系统分裂为各种服务并明确其边界以后,就出现了“分久必合”的问题,这很自然,服务化改造并不是各行其是,应用之间还是要协作,而对应用的运维——服务发现、水平扩展、容错等等——都需要基础设施的支持。

以前,对于这种运维基础设施,各公司甚至同一个公司的各个团队的做法都千差万别,但是借助Docker以及周边的生态圈,我们可以很容易的得到通用的服务发现框架,享受自动的部署和弹性扩展。

更好的消息是,这些基础服务是通用的——不但不关心是rails还是sinatra,甚至根本不关心是不是Ruby。

这也很好理解,Docker是对进程这个操作系统工作单元进行了简化约束,而进程的概念本来就是与语言和框架无关的。

这使得Ruby工程师以及Ruby项目可以更为自由的选择合适的技术去扩展公司的产品线。

延伸技术框架

Ruby 刚出来的时候,有很多来自 Java 社区的工程师加入其中(我也算是其中之一吧),很多人最大的感受是——视野被打开了。曾经象口号一样的“all in java”变成了落后的标志,大家意识到,一把钥匙开一把锁,用最合适的技术针对性的解决问题才是聪明的做法,单纯排斥某种技术或者语言框架并不明智。

这个道理在Ruby/RoR应用开发中也不例外,但是不少人在使用了几年Ruby以后都会遇到一个问题——“Ruby确实很适合开发Web,但是现在有些问题需要使用XX技术,而我们的系统严重依赖Ruby环境,这该怎么办呢?”

我认为问题就出在“系统严重依赖Ruby环境”上,研发的基础设施,比如配管、自动化测试、打包、部署,不应该仅满足一种技术或是语言,它一开始就要考虑到通用性,否则我们就只能“手里拿着锤子,看谁都像钉子”。

Docker本身和语言无关,它唯一的约束大概就是要运行在Linux上,这个对互联网服务端系统来说也算是标准了,问题不大。所以,我们应该以Docker为核心打造研发的基础设施,这将是未来的一笔重要投资。

当然,为未来画饼是危险的,不过还好,Docker领域的创业很活跃,有很多团队和公司已经做了相当多的基础工作,对于Ruby工程师和Ruby创业团队,去用现成的基础设施其实更方便。

我一直很喜欢Vagrant这个工具,两三年前就用它来进行自己项目的环境维护,那时候主要是做测试,由于Vagrant将操作系统环境进行了标准化,我很容易就能让自己的应用系统以及相关的测试结果保持稳定。

Vagrant还有一个好处,Ruby社区比较偏爱Mac,但是线上的系统基本都是Linux,所以开发环境所做的测试是有疑问的,特别是遇到一些有so依赖的gem,这时一个和线上完全一样的环境就特别重要。

其实上面的表述不太准确,Vagrant也有各种provider,我所说的场景,基本上都是virtualbox的provider,所以这些地方正确的说法是 vagrant/virtualbox。

和Docker相比,vagrant/virtualbox组合的成本还是很高的,无论是setup一个环境还是reset一个环境,都需要一段时间的等待,Vagrant只是把virtualbox的操作DSL了而已,底层的做法没有变化。而Docker由于本质上就是一个进程,因此天生就是轻量级的。对于运行时间在分钟级别的自动化测试工作,Docker显然有很大的优势。

当然,也有人会认为Docker不能模拟完整的操作系统,不过这恐怕是一个优点而不是缺点。我在以前的文章中已经说过了,这里概述一下主要观点——

Docker简化了操作系统这个基础设施,让应用精简为其最核心的形态——携带有限资源的进程,在此基础上更有利于架构上的最佳实践。

而对Ruby工程师而言,这个“最佳实践”中肯定少不了的一条就是——微服务。

有没有觉得,发展到现在,软件开发行业是越来越成熟了,无论是过程管理、架构方法、设计方法,还是语言、平台、框架、工具等,都发展到了一个前所未有的高度,相关思想和理念也日臻完善,我们真正进入了一个最好的时代。

单就编程语言来说,近些年包括Scala(2003)、Groovy(2003)、Go(2009)、Kotlin(2011)、Swift(2014)等新兴编程语言如雨后春笋版涌现出来,也给我们带来了很多让人眼前一亮的编程特性,甚至Java这等老牌编程语言也是不断推陈出新,编程再也不像过去那般枯燥。

本篇就带大家一起感受一下现代编程语言那些激动人心的特性。

这个特性其实有点早了,但是也是很早就让人感动的语言特性了,熟悉Javascript的同学应该对它很了解。Javascript语言具有动态性,我们可以随时为类的某个实例添加方法,也可以利用动态原型,为类的所有实例添加方法,有没有感觉扩展类的实现变得非常方便了呢?

扩展和原型很像,允许我们在不修改或继承类的情况下,将新的函数方法添加到原类中。这个特性较早见于C#这门语言,目前在Kotlin、Swift中均可以看到。这里顺便说一下C#,当时C#出来的时候,不得不说很多特性是非常棒的,包括扩展方法、泛型、分部类等等,比Java好不要太多。像Kotlin,不仅可以扩展类的方法,还可以扩展类的属性。

前两个都是关于扩展代码的,这里再来一个。我们知道Java 1.8以来,接口interface里的方法可以有自己的默认实现了,大大方便了实现类,减少了重复代码。相对于Java的这个实现是显示的,Go语言的接口实现可以是隐式的,添加隐式实现后,所有继承的结构(Go没有类,都是结构struct)都可以调用这个方法,和前面的两个特性有异曲同工之妙,下面我们对比看一下。

C语言就有宏的概念,通过 #define 定义,然后在代码中进行替换。宏作为Rust语言的高级特性,可以操作语法单元,是一种通过编写代码来生成代码的方式,被称作“元编程”(meta programming)。相对于函数,宏可以接受任意多个参数,可以减少重复代码,定义DSL。宏语法比较复杂,难以编写和调试,以至于在Rust文档中说,宏将是其最后的特性。

当你回想写代码枯燥的时候,应该会想到为字段编写getter、setter吧?较早的时候,C#就意识到了这个问题,贴心地推出了自动属性这个语法糖。而Java开发者则是通过Eclipse、IDEA这样的开发工具来自动生成getter、setter代码。当然,现在也可以依赖Lombook包,使用lombok的注解@Getter @Setter来编译时生成相关代码。

据说空指针异常是软件业最贵的异常,价值10亿美元。你有没有为处理调用链中的null值而烦恼过?又或者被伤害过?Kotlin会在编译期提示对可能为null变量的不安全使用,也提供了Elvis 操作符 ?: 来方便地处理null值。而有了可选链,就舒服多了。可选链语法应该较早出现在JavaScript语言中,新兴语言Swift也提供了这一省心的特性。Swift英明地决定变量是不允许直接存储NIL值,当然也提供了optionals的装箱功能允许将NIL或其它值包装起来,方便有时使用。

输入乃万恶之源,函数首要的事情就是检查不规范和不安全的输入,这也是卫语句的来历。Swift语言为此提供了专门的卫语句语法,有了它的贴身防护,整个代码都干爽多了,剧烈运动都不怕,不信往下瞧:

如果要评选最酷的语言特性,那么Lambda表达式必须获得提名。Lambda表达式很早就出现在Lisp语言中,python也有,在后来的C#语言大放异彩,又一次狠狠地羞辱了不长进的Java,而Java也终于在1.8版本后加入了这一特性,甚至C++ 11也光荣地上车了。

我们知道编程语言有静态和动态之分,静态语言如Java 、 C# 、 C 和 C++,动态语言如Perl,Python,JavaScript,Ruby 和 PHP等,多数为脚本语言。而融合了静态和动态特性的语音,就被称为渐进式语言,如TypeScript、Common LISP、Dylan、Cecil、Visual Basic.NET、Bigloo Scheme、Strongtalk等。静态类型检查可以尽早地发现 BUG,动态类型检查可以方便地处理依赖于运行时信息的值的类型。 渐进式语言允许类型注释来控制程序的一部分使用静态类型检查,而另一部分为动态检查,更具灵活性。 Python从3.5开始引入了对静态类型检查的支持。

在面向对象的编程语言中,状态是计算的基础。由于可变状态的存在,在编写高并发,多线程代码时,无法知道并行进行的诸多状态读写中是否有顺序上的错误,而且这种错误又是难以察觉的,而不变性则规避了这个问题。 不变性是函数式编程的基础,不变性意味着函数没有副作用,无论多少次执行,相同的输入就意味着相同的输出,所有线程都可以无所顾忌的执行同一个函数的代码,代码更像数学函数,更易理解和测试。

String就是构建在Java语言内核中的不可变类的一个典型例子。Java 的 CopyOnWrite系列容器类也是利用了不变性增强了并发安全性。Java可以通过final修饰符实现类和变量的不可变。而Scala、Swift、Groovy等语言也有各自的语法实现不可变的变量和类。

多重分派是一些编程语言的特性,其中的函数或者方法,可以在运行时间(动态的)使用一个或多个实际参数的组合特征,路由动态分派至实现函数或方法。多重分派主要区别于我们常见的重载方法,重载方法是在编译期就绑定了,而多重分派是在运行期分派的。Lisp、Julia、C#、Groovy等语言内建多分派特性,JavaScript、Python和C等语言通过扩展支持多分派。 多重分派可以避免我们写很多分支条件,而是更直观地用对象类型表达,使代码变得可读性更好并且较少发生错误。

前面几个特性是不是略显沉闷,那么来看一下这个激动一下。解构这一语法特性用于从数组索引或对象属性创建变量,简直帅到飞起。

爱写单元测试的同学有福了,这个绝壁是重磅炸弹,在生产代码里夹着测试代码,你有想过这么写测试吗?谁想的?简直脑洞打开啊!该特性在Pyret语言中,Pyret旨在作为编程教育的杰出选择,同时 探索 脚本和函数式编程的融合。

如果内联测试没有让你震惊,D语言内联编译期的这个特性绝对会让你惊掉下巴,基于该特性,开发人员可以直接在D语言中嵌入汇编代码,彻底放飞自我了,俺滴亲娘啊!受不了!受不了!顺便说一下,D语言比较小众,是C++的一个改进型,它包括了按合约设计、垃圾回收、关联数组、数组切片和惰性求值等特性。

好吧,我们看点其它的来压压惊吧。尽管Kotlin语言也说自己实现了模式匹配,但是实际上只是一点点帅,真正帅的是 Elixir语言的模式匹配,Elixir作为一种在Erlang OTP上运行的动态类型语言,将模式匹配提升到了一个全新的水平。

在编程语法上,Python真是个神一样的存在,for循环都能写出花来。

Java 8 中提供了Stream API特性, Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式,来提供一种对 Java 集合运算和表达的高阶抽象。事实上这个特性C#早就有了(Java又躺枪一次)。不得不说,利用这个特性写出来的代码,看上去还真的是很流利的。