哪里才是中国最真实的农村?

北京-周妮娜-Nina当我们与6月说再见的时候,P3的“你我同行”系列活动也已完整告一段落。从上半年双月P3工作坊[1]以及三次“下农村”[2]体验活动,我相信每个人心中或许和我一样,一方面向往着“采菊东篱下、悠然见南山”的郝堂村生活意境,但当看到蒲城或是巴中市花溪乡走马村那种村落凋零、文化跌落或是农村社会断裂时,另一方面却更加充满了对农村“贫困”回归的失落,失落的背后是更多地思考真正的乡村未来是什么?我们能为现在的农村建设做点什么?中国拥有6亿农村人口,包括其中的7000万贫困人口和6100万留守儿童,而我们在郝堂、蒲城以及走马村所看到的也不是“贫困”问题的全部,但是在三个案例中,让我产生了三点特别深切的感受:

第一,扶贫进程中的民间组织力量。早在联合国提出将减少贫困作为千年目标以及中国政府主导的农村改革失灵的时候,越来越多的民间组织(NGO)从事扶贫,长期驻扎并活跃在一线贫困农村,不但地域上覆盖了很多难以到达的区域,而且从影响力看来,更加得到了贫困村民的高度认可。民间组织显然成为权利和资本拥有者的政府部门的补充剂,但其角色更像是发起者、协调者和激励者。例如,小母牛在巴中花溪乡走马村的十年陪伴式发展,除了带着强烈的自身使命和价值观深入农村,更是将农民作为主体实现自治。小母牛在激活外部资源时,更是以赋权的方式保证了乡村的活力——集体修山路、改造凉亭和组织妇女表演队等等。

第二,建筑师和学者等专业人士的长期投入。郝堂村的土屋设计使我看到了那些拥有乡土情怀的建筑设计师们对中国乡土问题的深刻理解,以及对自然和乡村文化的尊重。有别于斥巨资设计的“密云古北水镇”式的供城市精英专门消费的“乡村”不同,更加有异于那些带着工业革命成果的“专业”城市设计思维的建筑师所打造的城镇, 郝堂村的改建则依然以农民为主体,使本无特殊景观资源的村落成为最美乡村。这个例子也更加说明了,当我们在行动前应该直面真实、倾听乡村的历史和人文、倾听村民的想法并共同发现创造的空间,将我们的解决方案融入自然和本土文化,而不是站在专业制高点俯视农村建设。

第三,农村也是我们大可作为的土壤。如今的经济发展离不开网络,而在村庄的探访也令我们看到了真实的网络“难民”和数字鸿沟带来的发展不平等——走马村柏林湾和蒲城村民或是因为文化程度低而无法享受网络资源带来的社会红利,又或是由于经济贫困而不能承担昂贵的信息化设备和服务,这些都是横亘在农村信息化发展的屏障。如果我们能为他们设计一款有用的非IT类产品或者工具,或是用我们的声音呼吁公众的关注,甚至最终影响决策者们增加贫困人群的“信息化”补贴等等,这些都是具有强烈现实意义的。

最后,大家也许还会有这样的疑问,我们到底应该把我们的力量投入在哪些农村?到底哪些贫困人群是我们真正要帮助的?思考这两个问题是特别有意义的事情,当我们无法忽视那些发展道路上受限的空心村事实的时候,一时间我也难以给出明确的答案。但是,如果说我们为郝堂村做点什么是锦上添花,那么更加专注蒲城、走马村甚至大凉山这些具有典型当代农村的贫穷、脏乱差和基础设施缺失的地区,则是济困解危、救人于水火。

[1]P3委员会于今年2月、4月和6月分别邀请了外部嘉宾分享了关于农村问题中的三个议题,即如何利用畜牧业帮助农村增收,新农村建设和留守儿童问题。

[2]上半年的三次下农村体验活动包括郝堂农村参访以及蒲城、花溪乡走马村的农村问题调研。

Share

getter和setter的那些事

相信每一个以Java或者C++作为编程入门语言的程序员,一定会记得一条金科玉律:字段(Filed)要声明成private,如果要读取或修改字段,就声明一些公开方法(Public Method),以get和set开头,像这段Java代码一样:

01

这些以get和set开头的方法,被称为getter和setter。时间久了,这种做法似乎成了一种神圣的约定,每个人都记得应该这么写,而忘记了为什么这么写。尤其是,当IDE变得足够智能,getter和setter可以自动生成,想要挑战这个约定的人就更少了——不过多按两下快捷键而已。

但是,当你写了很多程序,写过很多getter和setter,尤其是有些类方法,只有getter和setter时,总会有一天,你会疑惑,我到底为什么要这么干?

Why private field?

要解释为什么需要getter和setter,先要知道为什么字段应该是private的。

在汇编语言时,数据都是公开的。所谓公开,是指几乎任何指令,都可以作用在任意的数据块上。编写某段代码的程序员,通常知道自己在做什么,自己正在操作的数据代表什么含义,也就能选择合适的指令。而这段代码的用户——例如另一个程序员——可能并不知道数据的确切含义,比如把一个本应代表字符的数据块,当成数字进行计算,导致得到的结果和预期不符。

后来,类型的概念出现,某些操作开始只能作用在某些特定的类型上。以C语言为例,“*”这样的操作只能作用在数值类型上;而strcat函数则只能作用在char*类型上。这时,数据,和作用在数据上的函数,是分开的两部分,尽管两者之前保持着千丝万缕的关系。而一个函数,能够作用在哪些数据上,仅仅通过类型来限制,很难满足真实业务需求。比如,一个代表年龄的数值型变量,可能会被错误的传递给处理温度(也是数值型)的函数,得到一个负值作为返回值。

既然数据和函数是相关联的,何不将两者放在一起呢?于是在基本类型之上,更进一步的抽象被提出来,即数据,应该和相关的操作封装在一起。这就是对象(Object)的概念。一个对象,应该由该对象代表的数据,以及可以作用于这些数据的操作组合而成。

理想情况下,数据应该和所有相关的操作封装在一起,也就是说,除了这些操作外,不能有其他操作作用于这些数据。因此,数据需要被保护起来。这就是为什么Java, C++, C#等面向对象语言提供了private, protected, public等accessor来控制对数据和方法的访问权限。

另一方面,当前的编程语言,本质上都是图灵机的一种实现。每一个独立的代码单元,都可以看成一个作用在无限长纸带上的机器,这个机器存储着自己的内部状态,每次操作可以从纸带上的一个格子读取数据,然后计算一个结果输出到纸带上,同时更新自己的状态。这个机器的内部状态转移,对于计算结果的正确性,有着至关重要的作用。因此,要保证机器处于合法的状态,就必须保护内部状态,只在某些可控的操作下更新。

Why getter & setter?

数据需要被保护起来,而getter和setter是将数据暴露出来。看起来这是一对矛盾。

前面提到,每一个独立的代码单元都可以看成是一个图灵机。而要完成一个复杂任务,需要多个代码单元相互合作,组成更强大的图灵机。多个代码单元之间要合作,就不可避免的需要知道互相的状态,甚至一个代码单元需要修改另一个代码单元的状态。

也就是说,为了合作的需要,代码单元需要将数据暴露出来。

那么直接将数据字段设置为public,与通过getter和setter方式来暴露字段,有什么区别?

面向对象编程中有一条非常重要的原则,就是面向接口(Interface)编程。只要在一个稍具规模的团队工作过,就一定经历过与不同人写的代码进行集成的痛苦。不论设计阶段做的多么详尽,在开发过程中,接口都不可避免的会发生变化。一旦接口变化,所有与它相关的代码都要修改。所以,面向对象编程提出,尽量保证接口稳定,而内部逻辑可以改变,以达到最小化变化的目的。

02

如果直接将内部数据字段暴露出来,比如上面这段代码中的name,如果某天有一个新的需求,要求所有名字都用大写字母表示,就只能添加一个新的接口upperName,而使用name的地方,需要修改调用方式。如果采用文章开始时的代码,即添加getter和setter,有新需求出现时,只需修改getName方法,不需要修改调用处的代码,即可实现。

正是考虑到未来可能出现的功能扩展,所以像Java和C++这样的语言,即使还不确定是否应该将字段保护起来,也要写getter和setter,而这也导致了很多多余代码。

Why getter & setter, again?

然而,却并不是所有语言都是这样的。比如和Java最像的C#,虽然也建议将字段设置为private,但是却可以不用getter和setter。

03

上面这种property的写法,让Person的调用代码可以很直接的访问私有变量。

04

另一个提供property特性的语言是Python.

05

因为在C#和Python中,property的访问方式和直接将数据字段暴露出来的访问方式完全一样,所以在写代码时可以考虑先将数据暴露出来,避免过多的getter和setter,减少冗余代码。

One more thing…

Java代码的冗余是出了名的,同样的功能,像Python,甚至C#,可以写出更简洁,可读性更好的代码。不过,想要实现类似property的功能,也不是不可能。lombok提供了很多方便的注解来帮助Java程序员减少冗余代码。比如下面这段代码:

6-7
使用lombok,等价于下面这段代码:

08
看起来还不错。不过,因为这只是通过注解做的一种Hack,加了@Data注解,相当于编译器自动生成getter和setter,所以调用代码还是要用getIdgetName这样的方法名来访问变量。

Share

Ruby Web服务器:这十五年

坦率的说,作为一门年轻的计算机语言,Ruby在最近二十年里的发展并不算慢。但如果与坐拥豪门的明星语言们相比,Ruby就颇显平民范儿,表现始终不温不火,批评胜于褒奖,下行多过上扬。但总有一些至少曾经自称过Rubyist的程序员们,愉快地实践了这门语言,他们没有丝毫的歧视习惯,总是努力尝试各家之长,以语言表达思想,用基准评判高下,一不小心就影响了整个技术发展的进程。本文谨以Ruby Web服务器技术的发展为线索,回顾Ruby截至目前最为人所知的Web领域中,重要性数一数二的服务器技术的发展历程,试图帮助我们了解过去,预见未来。timeline

Ruby Web服务器发展时间轴

一、随波逐流

长久以来,任何Web服务器都具备的两项最重要的功能:一是根据RFC2616解析HTTP/1.1协议,二是接收、处理并响应客户端的HTTP请求。幸运的是Web技术的发展并不算太早,使得Ruby恰好能赶上这趟顺风车,但在前期也基本上受限于整个业界的进展。像Apache HTTP Server、Lighttpd和Nginx这些通用型Web服务器+合适的Web服务器接口即可完成大部分工作,而当时开发者的重心则是放在接口实现上。

cgi.rb

作为Web服务器接口的早期标准,CGI程序在调用过程中,通过环境变量(GET)或$stdin(POST)传递参数,然后将结果返回至$stdout,从而完成Web服务器和应用程序之间的通信。cgi.rb是Ruby官方的CGI协议标准库,发布于2000年的cgi.rb包含HTTP参数获取、Cookie/Session管理、以及生成HTML内容等基本功能。

Web服务器和CGI

Web服务器和CGI

当支持CGI应用的Web服务器接到HTTP请求时,需要先创建一个CGI应用进程,并传入相应的参数,当该请求被返回时再销毁该进程。因此CGI原生是单一进程/请求的,特别是每次请求时产生的进程创建/销毁操作消耗了大量系统资源,根本无法满足较高负载的HTTP请求。此外,CGI进程模型还限制了数据库连接池、内存缓存等资源的复用。

对于标准CGI应用存在的单一进程问题,各大厂商分别提出了兼容CGI协议的解决方案,包括网景的NSAPI、微软的ISAPI和后来的Apache API(ASAPI)。上述服务器API的特点是既支持在服务器进程内运行CGI程序,也支持在独立进程中运行CGI程序,但通常需要在服务器进程中嵌入一个插件以支持该API。

Webrick

作为最古老的Ruby Web服务器而不仅仅是一个接口,诞生于2000年的Webrick从Ruby 1.9.3(2011年10月正式发布)起被正式纳入标准库,成为Ruby的默认Web服务器API。Webrick支持HTTP/HTTPS、代理服务器、虚拟主机服务器,以及HTTP基础认证等RFC2617及以外的其它认证算法。同时,一个Webrick服务器还能由多个Webrick服务器或服务器小程序组合,提供类似虚拟主机或路由等功能:例如处理CGI脚本、ERb页面、Ruby块以及目录服务等。

Webrick曾被用于Rails核心团队的开发和测试中。但是,Webrick内置的HTTP Parser非常古老,文档缺失,性能低下且不易维护,功能单一且默认只支持单进程模式(但支持多线程,不过在Rails中默认关闭了对Webrick的多线程支持),根本无法满足产品环境中的并发和日常维护需求。目前一般只用于Web应用的本地开发和基准测试。

fcgi.rb

fcgi.rb是FastCGI协议的Ruby封装(latest版底层依赖libfcgi)。为了与当时的NSAPI竞争,FastCGI协议最初由Open Market提出和开发、并应用于自家Web服务器,延续了前者采用独立进程处理请求的做法:即维持一个FastCGI服务器。当Web服务器接收到HTTP请求时,请求内容和环境信息被通过Socket(本地)或TCP连接(远程)的方式传递至FastCGI服务器进行处理,再通过相反路径返回响应信息。分离进程的好处是Web服务器进程和FastCGI进程是永远保持的,只有相互之间的连接会被断开,避免了进程管理的开销。

Web服务器和FastCGI:SCGI服务器

Web服务器和FastCGI/SCGI服务器

进一步,FastCGI还支持同时响应多个请求。为了尽量减少资源浪费,若干请求可以复用同一个与Web服务器之间的连接,且支持扩展至多个FastCGI服务器进程。FastCGI降低了Web服务器和应用程序之间的耦合度,进而为解决安全、性能、管理等各方面问题提供新的思路,相比一些嵌入式方案如mod_perl和mod_php更具灵活性。

由于FastCGI协议的开放性,主流Web服务器产品基本都实现了各自的FastCGI插件,从而导致FastCGI方案被广泛使用。fcgi.rb最早开发于1998年,底层包含C和Ruby两种实现方式,早期曾被广泛应用于Rails应用的产品环境。

mod_ruby

mod_ruby是专门针对Apache HTTP Server的Ruby扩展插件,支持在Web服务器中直接运行Ruby CGI代码。由于mod_ruby在多个Apache进程中只能共享同一个Ruby解释器,意味着当同时运行多个Web应用(如Rails)时会发生冲突,存在安全隐患。因此只在一些简单部署环境下被采用,实际上并没有普及。

LiteSpeed API/RubyRunner

LiteSpeed是由LiteSpeed Tech公司最初于2002年发布的商用Web服务器,特点是与被广泛采用的Apache Web服务器的配置文件兼容,但因为采用了事件驱动架构而具有更好的性能。

LiteSpeed API(LSAPI)是LiteSpeed专有的服务器API,LSAPI具备深度优化的IPC协议以提升通信性能。类似其它Web服务器,LiteSpeed支持运行CGI、FastCGI、以及后来的Mongrel。同时在LSAPI的基础上开发了Ruby接口模块,支持运行基于Ruby的Web应用。此外,LiteSpeed还提供RubyRunner插件,允许采用第三方Ruby解释器运行Ruby应用,但综合性能不如直接基于LSAPI Ruby。

由于LiteSpeed是收费产品,其普及率并不高,一般会考虑采用LiteSpeed作为Web服务器的业务场景包括虚拟主机/VPS提供商、以及相关业务的cPanel产品。同时,LiteSpeed也会被用于一些业务需求比较特殊的场合,例如对Web服务器性能要求高,且应用程序及其部署需要兼容Apache服务器。LiteSpeed于2013年发布了开源的轻量Web服务器——OpenLiteSpeed(GPL v3),移除了商业版本中偏具体业务的功能如cPanel等,更倾向于成为通用Web服务器。

scgi.rb

scgi.rb是对SCGI协议的纯Ruby实现。从原理上来看,SCGI和FastCGI类似,二者的性能并无多大差别。但比起后者复杂的协议内容来说,SCGI移除了许多非必要的功能,看起来十分简洁,且实现复杂度更低。

Web服务器和多FastCGI:SCGI服务器

Web服务器和多FastCGI/SCGI服务器

与FastCGI类似,一个SCGI服务器可以动态创建服务器子进程用于处理更多请求(处理完毕将转入睡眠),直至达到配置的子进程上限。当获得Web服务器请求时,SCGI服务器进程会将其转发至子进程,并由子进程运行CGI程序处理该请求。此外,SCGI还能自动销毁退出和崩溃的子进程,具有良好的稳定性。

二、闻名天下

2005年,David Heinemeier Hansson(DHH)发布了基于Ruby的开发框架Ruby on Rails(Rails),聚光灯第一次聚焦在Ruby身上。但是业内普遍对Web服务器的方案感到棘手,本地环境Webrick/产品环境FastCGI+通用Web服务器几乎成了标配,无论是开发、部署或维护都遇到不少困难,一些吃螃蟹的人遂把此视为Rails不如J2EE、PHP方案的证据。

Mongrel

2006年,Zed Shaw发布了划时代的Mongrel。Mongrel把自己定位成一个“应用服务器”,因为其不仅可以运行Ruby Web应用,也提供标准的HTTP接口,从而使Mongrel可以被放置在Web代理、Load Balancer等任意类型的转发器后面,而非像FastCGI、SCGI一样通过调用脚本实现Web服务器和CGI程序的通信。

Mongrel采用Ragel开发HTTP/1.1协议的Ruby parser,而后者是一个高性能有限自动机编译器,支持开发协议/数据parser、词法分析器和用户输入验证,支持编译成多种主流语言(包括Ruby)。采用Regel也使parser具有更好的可移植性。但是,Mongrel本身不支持任何应用程序框架,而需要由框架自身提供这种支持。

Mongrel Web服务器

Mongrel Web服务器

Mongrel支持多线程运行(但对于当时非线程安全的Rails来说,仍然只能采用多进程的方式提高一部分并发能力),曾被Twitter作为其第一代Web服务器,还启发了Ryan Dahl发布于2009年的Node.JS。

但是当Mongrel发布后没过多久,Shaw就与Rails社区的核心成员不和(实际上Shaw对业界的许多技术和公司都表达过不满),随后就终止了Mongrel的开发。进而在其Parser的基础上开发了其后续——语言无关的Web服务器Mongrel2(与前续毫无关系)。

尽管Mongrel迅速衰落,却成功启发了随后更多优秀Ruby应用服务器的诞生,例如后文将介绍的Thin、Unicorn和Puma。

Rack

随着Web服务器接口技术的发展,从开始时作为一个module嵌入Web服务器,到维护独立的应用服务器进程,越来越多的应用服务器产品开始涌现,同时相互之间还产生了差异化以便适应不同的应用场景。但是,由于底层协议和API的差别,基于不同的应用服务器开发Web产品时,意味着要实现各自的通信接口,从而为Web应用开发带来更多工作量。特别是对于类似Django、Rails这些被广泛使用的Web框架来说,兼容主流应用服务器几乎是必须的。

2003年,Python界权威Phillip J. Eby发表了PEP 0333(Python Web Server Gateway Interface v1.0,即WSGI),提出一种Web服务器和应用程序之间的统一接口,该接口封装了包括CGI、FastCGI、mod_python等主流方案的API,使遵循WSGI的Python Web应用能够直接部署在各类Web服务器上。与Python的发展轨迹相似,Ruby界也遇到了类似的挑战,并最终在2007年出现了与WSGI类似的Rack。

与WSGI最初只作为一份建议不同,Rack直接提供了模块化的框架实现,并由于良好的设计架构迅速统一了Ruby Web服务器和应用程序框架接口。

Rack被设计成一种中间件“框架”,接收到的HTTP请求会被rack放入不同的管线(中间件)进行处理,直到从应用程序获取响应。这种设计通过统一接口,把一般Web应用所需的底层依赖,包括Session处理、数据库操作、请求处理、渲染视图、路由/调度、以及表单处理等组件以中间件的形式“放入”rack的中间件管线中,并在HTTP请求/响应发生时依次通过上述管线传递至应用程序,从而实现Web应用程序对底层通信依赖的解绑。

Rack中间件

Rack中间件

Rack接口部分包含两类组件:Handler,用于和Web服务器通信;Adapter,用于和应用程序通信。截至Rack 1.6,Rack内置的handlers包括WEBrick、FCGI、CGI、SCGI、LiteSpeed以及Thin,上述handlers用以兼容已有的常见应用服务器。而2008年后,随着rack逐渐成为事实标准,更新的Ruby Web服务器几乎都包含Rack提供的handler。包括Rails、Sinatra、Merb等等几乎所有主流框架都引入了Rack Adapters的支持。

三、百花齐放

Mongrel和Rack的相继诞生,使Ruby Web服务器、乃至应用程序框架的发展有了一定意义上可以遵循的标准。Mongrel后相继派生出Thin、Unicorn和Puma;而Rack统一了Ruby Web服务器和应用程序框架接口,使应用开发不再需要考虑特定的部署平台。Ruby Web服务器开始依据特定需求深入发展。

Thin/Goliath

发布于2009年的Thin沿用了Mongrel的Parser,基于Rack和EventMachine开发,前者上文已有介绍,EventMachine是一个Ruby编写的、基于Reactor模式的轻量级事件驱动I/O(类似JBoss Netty、Apache MINA、Python Twisted、Node.js、libevent和libev等)和并发库,使Thin能够在面对慢客户端的同时支持高并发请求。

发表自1995年的Reactor模型的基本原理是采用一个单线程事件循环缓存所有系统事件,当事件发生时,以同步方式将该事件发送至处理模块,处理完成后返回结果。基于Reactor模型的EventMachine具备异步(非阻塞)I/O的能力,被广泛用于大部分基于Ruby的事件驱动服务器、异步客户端、网络代理以及监控工具中。

Reactor模型

Reactor模型

2011年,社交网络分析商PostRank开源了其Web服务器Goliath,与Thin相似(都采用了EventMachine)但又有很大不同,采用新的HTTP Parser,同时针对异步事件编程中的高复杂度回调函数问题,借助Ruby1.9+的纤程技术实现了线性编码,使程序具备更好的可维护性。Goliath支持MRI、JRuby和Rubinius等多平台。在附加功能方面,Goliath的目标不仅是作为Web服务器,更是一个快速构建WebServices/APIs的开发框架,但是随着之后PostRank被Google收购,Goliath项目也就不再活跃在开源界了。

Unicorn

2009年,Eric Wong在Mongrel 1.1.5版本的基础上开发了Unicorn。Unicorn是一个基于Unix/类Unix操作系统的、面向快客户端、低延迟和高带宽场景的Rack服务器,基于上述限制,任何情况下几乎都需要在Unicorn和客户端之间设置一个反向代理缓存请求和响应数据,这是Unicorn的设计特点所决定的,但也使得Unicorn的内部实现相对简洁、可靠。

尽管来源于Mongrel,但Unicorn只在进程级运行,且吸收和利用了一些Unix/类Unix系统内核的特性,如Prefork模型。

Unicorn由1个master进程和n个fork(2)子进程组成,子进程分别调用select(2)阻塞自己,直到出错或者超时时,才做一些写日志、处理信号以及维护与master的心跳链接等内置任务。子进程和master间通过一个共享socket实现通信,而由Unix/类Unix系统内核自身处理资源调度。

Unicorn的多进程模型

Unicorn的多进程模型

Unicorn的设计理念是“只专注一件事”:多进程阻塞I/O的方式令其无从接受慢客户端——但前置反向代理能解决这一问题;workers的负载均衡就直接交给操作系统处理。这种理念大大降低了实现复杂度,从而提高了自身可靠性。此外,类似Nginx的重加载机制,Unicorn也支持零宕机重新加载配置文件,使其允许在线部署Web应用而不用产生离线成本。

Phusion Passenger(mod_rails/mod_rack)

2008年初,一位叫赖洪礼的Ruby神童发布了mod_rails。尽管Mongrel在当时已经席卷Rails的Web服务器市场,但是面对部署共享主机或是集群的情况时还是缺少统一有效的解决方案,引起业内一些抱怨,包括DHH(也许Shaw就不认为这是个事儿)。

mod_rails最初被设计成一个Apache的module,与FastCGI的原理类似,但设置起来异常简单——只需要设置一个RailsBaseURI匹配转发至Rails服务器的URI串。mod_rails服务器会在启动时自动加载Web应用程序,然后按需创建子进程,并协调Web服务器和Rails服务器的通信,从而支持单一服务器同时部署多个应用,还允许按需自动重启应用服务器。

mod_rails遵循了Rails的设计原则,包括Convention over Configuration、Don’t Repeat Yourself,使其面向部署非常友好,很快得到了业界青睐,并在正式release时改名Passenger。

在随后的发展中,Passenger逐渐成为独立的Ruby应用服务器、支持多平台的Web服务器。截至2015年6月,Phusion Passenger的版本号已经达到5.0.10(Raptor),核心采用C++编写,同时支持Ruby、Python和Node.js应用。支持Apache、Nginx和独立HTTP模式(推荐采用独立模式),支持Unix/类Unix系统,在统计网站Builtwith上排名Ruby Web服务器使用率第一。

值得一提的是,Phusion Passenger的开源版本支持多进程模式,但是其企业版同样支持多线程运行。本文撰写时,Phusion Passenger是最近一个号称“史上最快”的Ruby Web服务器(本文最后将进一步介绍Raptor)。

Trinidad/TorqueBox

Trinidad发布于2009年,基于JRuby::Rack和Apache Tomcat,使Rails的部署和世界上最流行的Web服务器之一Tomcat结合,支持集成Java代码、支持多线程的Resque和Delayed::Job等Worker,也支持除Tomcat以外的其它Servlet容器。

与Trinidad相比,同样发布于2009年的TorqueBox不仅仅是一个Web服务器,而且被设计成一个可移植的Ruby平台。基于JRuby::Rack和WildFly(JBoss AS),支持多线程阻塞I/O,内置对消息、调度、缓存和后台进程的支持。同时具有集群、负载均衡、高可用等多种附加功能。

Puma

Puma——Mongrel最年轻的后代于2011年发布,作者是Evan Phoenix。

由于Mongrel诞生于前Rack时期,而随着Rack统一了Web服务器接口,任何基于Rack的应用再与Mongrel配合就有许多不便。Puma继承了前者的Parser,并且基于Rack重写了底层通信部分。更重要的是,Puma部分依赖Ruby的其它两个流行实现:Rubinius和JRuby,与TorqueBox类似拥有多线程阻塞I/O的能力(MRI平台不支持真正意义上的多线程,但Puma依然具有良好并发能力),支持高并发。同时Puma还包含了一个事件I/O模块以缓冲HTTP请求,以降低慢客户端的影响。但是,从获得更高吞吐量的角度来说,Puma目前仍然需要采用Rubinius和JRuby这两个平台。

Reel

Reel是最初由Tony Arcieri发布于2012年的采用事件I/O的Web服务器。采用了不同于Eventmachine的Celluloid::IO,后者基于Celluloid——Actor并发模型的Ruby实现库,解决了EM只能在单一线程中运行事件循环程序的问题,从而同时支持多线程+事件I/O,在非阻塞I/O和多线程方案间实现了良好的融合。

与其它现代Ruby Web服务器不同的是,Reel并不是基于Rack创建,但通过Reel::Rack提供支持Rack的Adapter。尽管支持Rails,与Puma也有一定的相似性,但与Unicorn、Puma和Raptor相比,Reel在部署Rails/Rack应用方面缺少易用性。实际上基于Celluloid本身的普及程度和擅长领域,相比其它Web服务器而言,Reel更适合部署WebSocket/Stream应用。

Yahns

2013年,Eric Wong等人受Kqueue(源自FreeBSD,同时被Node.js作为基础事件I/O库)的启发启动了Yahns项目。其目标与Reel类似,同样是在非阻塞I/O设计中引入多线程。与Reel不同的是,Yahns原生支持Rack/HTTP应用。

Yahns被设计成具有良好的伸缩性和轻量化特性,当系统应用访问量较低或为零时,Yahns本身的资源消耗也会保持在较低水平。此外,yahns只支持GNU/Linux(并通过kqueue支持FreeBSD),并声称永远不会支持类似Unicorn或Passenger里的Watchdog技术,不会因为应用崩溃而自动销毁和创建进程/线程,因此对应用程序本身的可靠性有一定要求。

四、迈向未来

回顾过去,Ruby Web服务器在发展中先后解决了缺少部署方案、与Web应用程序不兼容、运维管理困难等问题,基础架构趋于成熟且稳定。而随着更多基准测试结果的出现,业界逐渐开始朝着更高性能和并发量发展,同时针对HTTP协议本身的优化和扩展引入的HTTP/2,以及HTML5的WebSocket/Stream等需求均成为未来Ruby Web服务器发展的方向。

高吞吐量

以最新的Raptor(上文提到的Phusion Passenger 5)为例,其在网络I/O模型的选择上融合了现有其它优秀产品的方案,包括Unicorn的多进程模型、内置基于多线程和事件I/O模型的反向代理缓冲(类似Nginx的功能,但对Raptor自身做了大量裁减和优化)、以及企业版具有的多线程模型(类似Puma和TorqueBox);此外,Raptor采用的Node.js HTTP Parser(基于Nginx的Parser)的性能超过了Mongrel;更进一步,Raptor甚至实现了Zero-copy和一般在大型游戏中使用的区域内存管理技术,使其对CPU和内存访问的优化达到了极致(感兴趣的话可以进一步查阅这里)。

Raptor的优化模型

Raptor的优化模型

另外也需要看到,当引入多线程运行方式,现有Web应用将不得不仔细检查自身及其依赖,是否是线程安全的,同时这也给构建Ruby Web应用带来更多新的挑战。这也是为什么更多人宁愿选择进程级应用服务器的方式——毕竟对大多数应用来说不需要用到太多横向扩展,引入反向代理即可解决慢客户端的问题,而采用Raptor甚至在独立模式能工作的更好(这样就不用花时间去学习Nginx)。

除非你已经开始考虑向支持大规模并发的架构迁移,并希望节省接下来的一大笔花费了。

HTTP/2

2015年5月,HTTP/2随着RFC7540正式发布。如今各主流服务器/浏览器厂商正在逐渐完成从HTTP/2测试模块到正式版本的过渡。而截至目前,主流Ruby Web服务器都还没有公开HTTP/2的开发信息。HTTP-2是在2013年由Ilya Grigorik发布的纯Ruby的HTTP/2协议实现,包括二进制帧的解析与编码、流传输的多路复用和优先级制定、连接和流传输的流量控制、报头压缩与服务器推送、连接和流传输管理等功能。随着HTTP/2的发布和普及,主流Ruby Web服务器将不可避免的引入对HTTP/2的支持。

WebSocket/流(Stream)/服务器推送事件(Server Sent Events,SSE)

2011年,RFC6455正式公布了WebSocket协议。WebSocket用于在一个TCP链接上实现全双工通信,其目的是实现客户端与服务器之间更高频次的交互,以完成实时互动业务。鉴于该特点,仅支持慢客户端的Web服务器就无法有效支撑WebSocket的并发需求,更何况后者对并发量更加严苛的要求了。而对于同样需要长连接的流服务器和服务器推送事件服务(SSE),都避免不了对长连接和高并发量的需求。尽管高性能的Ruby Web服务器都有足够的潜力完成这些任务,但是从原生设计的角度来看,更加年轻的Reel和Yahns无疑具有优势。

最近Planet ruby在Ruby邮件组发布了Awesome Webservers,该Github Repo旨在对主流Ruby Web服务器进行总结和对比,并且保持持续更新,推荐开发者关注。

Share

郝堂新村——新农村新面貌之星星之火

作为今年P3“economic justice”活动内容之一的Personal Journey,郝堂村之旅为我们更加深入理解农村经济状况提供了极大的帮助。这次活动的主旨是为了帮助ThoughtWorkers对这些农村相对弱势的群体给予更多的关注,并且思考能为他们做点什么。在国家快速城市化过程中,通过郝堂村我们也可以对如何构建新农村以及当前面临的挑战有更加全面的认识。

5

放眼当今的世界,发展仍是当下的时代主题。无论是城市的发展,还是农村的发展,一切问题都是经济发展的问题,其很大程度上能够对本地区或者社区的教育水平、医疗环境、生态结构的健康状态产生重大影响。发展经济的一个主要核心问题是如何健康实现筹集资金、投资决策、资金回报的金融闭环。城市,包括国内的一线、二线城市在这方面积累了很多的经验,并且也享受到了其带动经济发展后的成果。但是国内的农村在这方面才刚刚迈起脚步,由于不能套用或者直接融入城市金融系统,农村的金融建设可谓困难重重。城市与农村不是两个孤立的人类社区,农村问题也是城市问题。

翻开我国农村的发展的近代历史,不难发现,农村问题可谓‘根深蒂固’。它大概经历了三个阶段,一是全力支撑工业发展的阶段,农村的主要任务就是为工业发展提供源源不断的食粮,二是联产承包责任制后的农民个体的农耕阶段,一方面农民的积极性提高了,但另一方面有些地区出现了牺牲生态环境扩大耕种的不良现象,三是当今新农村发展阶段,其目标是实现农村经济与生态环境的和谐、可持续发展。农村的问题集中在土地上,土地制度经历了从合作社到家庭联产承包责任制。

那么现当今农村的问题到底是什么问题呢?我想大多数人可以不假思索就可以说出很多具体的实际问题,并且涵盖农村农民生活水平、农村教育、农村医疗、农村生态,以及农民外出打工状貌等等。 郝堂村作为国内试点发展的一个新农村, 它一方面可以让我们更加深切真实地认识到农村面临的问题,又另一方面能启发我们能为改善economic justice做些什么。

talk

郝堂村作为一个视角,可以让我们更加真实地了解国内的村情。在与禹书记的访谈中,相信每个参与的人都收获很多。农民面临的重要的一个问题就是“进不去的城市,回不去的故乡”。一句普普通通的话,听起来让人心疼,但却形象地描绘出了外出务工的农民所面临的问题。城市纵有“广厦千万间”,但是却庇护不了他们,这些外出务工的农民大都上有老需要赡养、下有小需要抚养,他们的外出,不仅仅会造成老人和孩子情感上的一些缺失,还会带来一些其他的社会问题。俗话说养儿防老,如果身边既没有子女,又没有足够的生活费用,老人怎么能够安度晚年?子不教,父之过,如果没有父母的陪伴,这些孩子会不会从小就对这个社会、对家庭产生了误解 ?

年轻的农村劳动力如果大量涌入城市,不但生活质量得不到保障,无形之中也给城市增加了承受压力,从这个角度上讲,农村的问题确实也是城市的问题。然而如果家乡有机会、有资源可以拼搏,又有谁愿意背井离乡、抛老弃小去外面奔波闯荡呢?现在的很多农村是凋敝的,由于缺乏好的制度,无法提供良好的平台,难以吸引从农村走出去的青年才俊回乡建设,这样,久而久之就会陷入恶性循环的怪圈里。农村建设需要接地气,它必须是可实施的。农村问题表面上是脏乱差、年轻人外出,实际上是农村失去了造血机制,没有内生动力。

农民的唯一收入来源主要依靠农业收入,然而一亩地能够带来的净利润却相当微薄,细如牛毛。没有资金、没有人才、没有技术,缺乏基本的基础设施,导致农村生活的垃圾只能靠填埋,生活污水靠蒸发。缺乏资金,也将导致教育基础设施跟不上现代教育的步伐,同时也会影响农村教师队伍的建设。缺乏资金,农村的医疗基础设施无法完善,看病贵依然是问题。农村所有的问题可以归结收敛于两个基本问题,一缺乏资金,二缺乏产生资金的源动力,二者统一于缺乏“造血功能”。那么,既然城市已经拥有比较完善的筹集资金系统,比如银行,为什么农村不能利用呢?目前国内的金融系统或者金融制度只适合城市,有种种因素制约银行向农村借贷,比如信息的不对称(信用度、熟知度)、成本问题(比如交通)以及最重要的一点,由于农村土地与城市土地并非同地同权,农村没有有效的抵押。因而农村想要发展,想要聚拢资金,就必须建立因地制宜的农村金融制度。

farm

农村金融制度必须建立在农村土地制度的基础之上,这样的金融制度就能弥补城市金融制度的缺陷,由于农村社区家家户户都非常熟悉,农村金融制度落户农村,以及农民的土地具有抵押的功能,因而借贷信息对称、成本低、农民也有有效的抵押,资金流向也能受到有效的监督。这样的金融制度是可实施的,农民都有资格进行资金的借贷。郝堂村采用这样的金融制度,他们称之为“内置金融”,机构名称是“老人合作社”,积累的农村集体金融资金已经从当年的几万元升到了如今的几百万元,真可谓“茁壮成长”。随之而来的问题是,为什么叫“老人合作社”,好塘村怎么筹到的第一笔资金,又是怎么实现资金的健康回流?

郝堂村本身之前还有一个问题,就是老人问题,表现在几个方面:一是老人的医疗问题,有些老人不及时就诊,疼惜花钱或者疼惜麻烦儿女,可能会出现极端状况,二是老人手里没钱,生活无法得到应有的保障。归根到底,老人问题也是资金问题。郝堂村筹集的首笔基础资金一部分来源于当地研究农村问题的一些专家或者村主任,另外一部分则来自老人。老人加入老人合作社的唯一条件就是需要缴纳2000元作为集体资金。这些入社的老人们现如今每年都能享受到“发红包”。

那么老人互助社怎么实现资金的回流问题呢?这必然与郝堂村本身的造血能力有关,由于郝堂具有得天独厚的天然优势,以及政府投资4000万用于改善交通基础设施,郝堂村的旅游业得以发展得如火如荼。这给村里的农民带来了巨大的获得收入的平台,他们通过向互助社借贷,在获得资金以及乡建院的帮助下建立了遍地的农家乐、客栈,赢得收入,使得互助社的资金得以回流。这些农家乐与客栈的建设都是在乡建院规划之下进行的,它使得经济与生态得以和谐、健康、可持续发展。

郝堂村内置金融获得巨大成功还有一个因素特别重要,那就是这位禹书记。他是一个有想法的人,是一个绞尽脑汁真正想为农村建设出力的一位干部。他也一直强调“自联产承包责任制后,农民如果是原子的,就不会有力量。政府强大的地区往往是社区生态比较脆弱的地区。如何构建一个开源、包容的社区是现代新农村建设必须要考虑的问题”。

郝堂村内部造血机制建立起来了,大量的外出务工人员大都回乡发展,农村的教育配套基础设施建设、医疗基础设施、生态发展都处于一个非常健康的状态。

hourse

doctor

从上面的调查中我们可以看出郝堂村成功的案例是天时地利人和综合作用下的产物,既有郝堂村相对比较好的地理位置(修路之后距离高铁站仅半小时车程),又有李昌平老师及乡建院工作人员的持续支持,还有各级政府的支持和资金投入。这些条件的达成有很多偶然性和不可复制性,但是郝堂村的案例是不是能给我国广大乡村在新农村建设方面有一些启发呢?

  通过调研,我们发现了以下三点:

  1. 保持与当地生产方式的村落空间历史形态,避免简单粗暴照搬城市的环境景观建设。任何有历史的村庄形态都带有其特殊的功能密码,经过历史的磨合,村落选址、布局、空间走向与山川地形相附会,村落建筑与自然生态相和谐,农民生产生活与山水环境互交融,构成了乡村特有的空间布局,也促成了乡村区别于城市的生产生活方式。郝堂村在建设时尊重这一规律,村庄原有道路、农田、沟渠一律不变。新农村采取不集中居住,在原有住房基础上,不破坏原有生态环境,分别建设,分散居住,这样就确保农民居住环境生态优美、原汁原味。
  2. 盘活农村自有资源,培养农村集体经济。农村集体经济对于优化配置农村资源、转变农村生产方式、提升农村经济地位起着关键作用,同时农村基础设施建设、公共事业投入等很大程度上也需要依靠农村集体经济提供资金支持和保障。郝堂村村委拥有集体茶场、林场等,通过承包、租赁等形式交给农民开发利用,取得了稳定的村集体经济收入。郝堂村的夕阳红养老资金互助合作社,通过熟人社会监管机制有效地盘活了农村资金,进一步壮大了集体经济。除此之外,郝堂村村委还保留了集体建设用地指标,这样村委就拥有了集体建设用地土地开发权,在郝堂村的建设过程中,成立了绿园生态旅游开发公司。村集体有效地将村土地增值的收益以集体经济的形式保留在村庄内部,避免了有些村庄开发过程中土地增值收益被外部开发商攫取的情况。
  3. 重视农民组织建设。新农村建设的主体是农民和农民集体,但是任何小农都不可能成为强势的市场主体,只有将千千万万的小农组织起来,才能变成一个有组织的社会主体和市场的主体。在分散的家庭经营基础上,引导农民自愿联合起来,发展社区性和专业性的合作经济组织,并引导这些合作经济组织把农产品产销、甚至村庄旅游服务联结起来,这将成为农村集体经济发展的新的重要形式。郝堂村在进行旅游开发建设过程中,农户自由自愿的组织了农家乐合作社、采摘园合作社等不同的农民组织,通过各个合作社,一方面形成市场标准,规范各个农民的市场行为,防止恶性竞争和不正当竞争;另一方面形成规模效应和品牌效应,提高外部竞争力和可持续发展的能力。

develop

郝堂村的案例显然不是新农村建设的标准答案,但是它带给我们很多的启发。当我们P3在讨论城乡经济发展不公的时候,当我们想要为中国的乡村,或者是我们家乡的农村做点事情的时候,我们是否可以从农村组织、农村经济、农村空间形态这几个方面去思考和推进?

flower

  作为一家软件技术公司,我们 ThoughtWorks 有没有可能从技术的角度,让这种新农村建设和变革变得更容易?

Share

学习的逻辑 2: 职业半山腰

galaxy

<<学习的逻辑: 知识经济学>>中介绍了基础的逻辑. 本文是其姊妹篇, 进一步从不同角度来阐述.

我该学什么? 这是一个错误的问题

这个问题可以有很多出发点. 今天讨论基于的假设是对工作方向的迷惘, 即不知道自己下一步努力的重点是什么, 但又不想时光虚度, 总觉得该学点什么, 又不知从何学起.

想学习是好的, 但考虑下面这种场景. 你走进领导的办公室说: “我要加薪, 因为我参加了两个培训, 看了三本书”. 你觉得领导会答应吗?

再考虑第二种场景. 你走进领导的办公室说: “我要加薪, 因为我搞定了两个项目, 解决了三个问题”.

显然第二个场景比第一个更可能一点. 因此我们应该关注的, 是我可以有什么样的贡献, 什么样的产出, 而不是我应该学习哪些知识. 你的身价是由你表现出来的知识决定的, 不是你掌握的知识决定的. 就算我们的目标是学习, 让自己更强大, 一条更具可操作性的途径是以终为始, 先设定自己想做成什么事, 再反推需要什么样的知识.

另外一个类似的思考角度是, 你希望别人怎么介绍你.

  • 一种是这位是xxx, 他这一生看了很多书, 学富五车.
  • 一种是这位是xxx, 他是 yyy 产品的设计者 / zzz 书的作者…

你更希望是哪一种?

你对世界的认识和世界对你的认识都只能通过你可观测的行为来进行. 你的老板, 你的同事, 他们不可能知道你都看了啥想了啥学到了啥, 他们只能看到你说了啥做了啥. 换句话说, 你是个黑盒, 外在的行为定义了你. 如果你所知所学不能反映在外在行为上,则你的内部状态不会对世界产生可观测的影响. 也就是说, 如果你学了某些知识, 但行为没有任何变化, 那跟没学是一样的. 从这一点上,你的行动, 你的产出才是你知识和智慧的反映,所谓知行合一.

所以我该学什么, 是一个不恰当的问题. 更合适的问题是: 我该做什么, 我该对外界产生什么样的影响?

这里的讨论是在工作的上下文中, 不涉及哲学和宗教, 人生意义等.

有时忙得要死, 有时无所事事? 其实无关时间

有没有过几件事找过来, 你不知道该接哪个的情景, 最后只好只要时间排的过来就都接了, 结果搞的自己过载? 而有时忙完手头的事, 发现没什么可做的, 只好无所事事上上网?

有人会求诸于时间管理. 但其实原因不在于你的时间管理技巧, 而在于你根本就没有明确的目标, 无论是短期的还是长期的, 尤其是长期的. 没有目标, 时间管理就没有依据.

没有目标, 你就排不出优先级. 当一堆任务涌过来的时候, 你就不知道哪个对你重要, 哪个不重要, 哪个必须做, 哪个可以不做. 当你有多个项目可以去做的时候, 你不知道该挑哪一个, 该拒绝哪一个. 没事的时候, 你也不知道该看啥学啥.

有个项目可以练习你的产品设计能力, 另外一个可以出国练几个月英语, 还有一个当下最火热也是公司未来方向的大数据项目, 而你只能选一个, 你会选哪个? 你业余时间打算看本书, todo list 里面有三本, 但时间只够你看一本, 你如何决定该看哪一本?

比如你觉得要去学iOS开发, 是时代潮流, 可为什么不去学习智能硬件呢? 你觉得要去学数据结构和算法, 往基础知识层面走, 可为什么不去学习操作系统, 数据库原理呢?

不是从目标分解下来的学习任务, 更像是随机的碰运气, 恰好碰对的可能性很小.

一旦确立了真正的目标, 这些问题就不再是问题, 就不会时忙时闲, 也不会选择恐惧, 因为你知道忙时拒绝那些对目标没什么帮助的工作以削峰, 而闲时主动补充对目标有帮助的知识而填谷; 最契合目标的就去做, 无关紧要的就拒绝.

不善交际, 没有话题? 其实无关性格

周围总是有资深的人, 但你跟他们在一起的时候, 想要聊些什么, 又不知从何聊起. 有人会求诸于社交技巧. 但其实根本的原因还在于你没有明确的目标. 没有目标, 也就没有深入思考, 因此也就没有问题. 跟周围同事, 业界大牛聊的时候, 仅仅是泛泛而谈, 止于人云亦云, 网上遍地都能找到的一些讨论. 并且你也不会主动挑起话题, 因为你没问题嘛.

一旦有了目标, 在你向目标前进的时候, 一定会碰到很多具体的, 实际的问题. 此时你会主动去找资源, 找人聊, 也会聊的比较深入, 比较有成效. 你会突然变得积极主动, 谈笑风生起来.

同时你会发现, 几乎每个人身上, 你都可以学到你关心的知识. 所谓当学生准备好时, 老师自然会出现.

什么都会, 但又都不突出, 通才? 其实是学霸综合症

李敖有段时间一三五学法语, 二四六学德语, 别人问他哪一门强, 他说要看是星期几问. 与此类似, 你要问我有什么特长, 擅长什么, 那要看我当时是在哪个项目. 做过很多项目, 什么都知道点, 但依然没什么擅长, 当时在做啥, 就相对擅长啥. 但这并不能称之为通才, 仅仅是善于快速入门而已.

究其原因, 还是没有目标, 只知低头干活, 不知抬头看路, 转来转去都离原点不远. 最慢的步伐是徘徊.

以终为始. 反馈是学习的唯一途径. 输入只是娱乐, 输出才是学习.

Share