五种类型的企业家

作者:Brian

翻译:ShiningRay

原文:Defining The 5 Classes of Entrepreneurs

最近我一直在思考是什么成就伟大的企业家以及有些人努力成为其中之一背后的动机。常有人向我咨询关于创办公司和集资的建议,我还在不同的活动上讲演我那微不 足道的企业家经历。我发现我很难回答那么多问题,而且如果没有某种“情景语境”,关于哪些行得通哪些行不通,我也难以给出无可辩驳的论述。关于情景语境,我的意思是比如你想尝试创办下一个Google或者如果你想创办一个新的鞋厂。我略微思考之后,总结出实际上有5“类”企业家形式,每个有着不同的动机。希望借此文能抛砖引玉。

五类企业家

I. 投机型(Opportunist) – Class I Entrepreneur
II. 追求生活方式型(Lifestyle Entrepreneur) – Class II Entrepreneur
III. 解决问题型(Problem Solver) – Class III Entrepreneur
IV. 梦想型(Visionary) – Class IV Entrepreneur
V. 改变游戏型(Game Changer) – Class V Entrepreneur

在我的观念中每个“类型”代表了目标、动机和成果的不同层次,当然人可以从一个层次变成另一个层次。

I. 投机型企业家 (Class I): 投机者认为特定的局势能代表赚钱的机会,然后就上了。这些机会可以是在雨天卖雨伞,在盛夏烈日中午卖冰水,也就是说,扑向一个热门以及适时的潮流中,并提 供相关的某种服务。很多企业家都是这样起家的,因为最佳的“投机”机会往往不需要很多启动资本,但需要对它的一种紧迫的直觉。

我在大学的时候也有一段投机者的经历。那还是90年代初期,那时候的人还在用usenet新闻组,Web还处于lynx/mosaic的阶段。当时我很饭体 育卡(比如棒球卡),我发现了一个机会,我从本地的针对团队和卡片零售商那里接手体育卡的库存,然后通过Internet销售那些在当地没什么需求的卡 (如,49人的卡在波士顿常常卖得不好)。这个小小的兴趣带来的业务,让我能够支付相当多的大学费用。不过,创办一个网站让我和朋友能在线交换货物则是另外一个故事了。 :)

技能要求:3 / 耐久力要求:2 / 眼光:4

II. 追求生活方式型 (Class II): 这种类型的企业家想建立他/她自己的事业,他/她自己做老板,和他/她的朋友、家人一起工作,也就是追求一种迷人但并不是全部投入的事业机遇同时还能维持生活上的平衡。很多此类事业都是白手起家的,因为一旦业务上有外部投资,那么就会有来自外部的更快扩展或者是寻求某种退出的压力。这种企业家可能会从事各种行业,从运营一个成功(且有趣的)的饭店到操作一个软件业务。然而,定义这种事业的关键特征在于它是为了达到工作/生活的平衡或者是个人的满足而开始的,而并不是为了事业的成长。

我曾经为这种追求生活方式型企业家工作过,那是一段很开眼界的经历,从那里我了解了人们不 同的动机和目标。我个人完全尊重这些寻求生活方式和事业平衡的人。其实一个人真的需要多少钱呢?同时,也不是每个人都想要35岁退休,那么何尝不去做些刺 激而有趣的工作,同时还能到处去玩玩。
要求: 自给自足的事业; 人生目标.

III. 解决问题型 (Class III): 解决问题型的案例始于某人一直纠结于某个无法解决的问题,直到他们找到一种解决方法。然后当他们下决心以此做点事情的时候,他们就变成了第三类企业家了。很多公司在初创的时候都有解决某一个问题的目标。这个事实很重要。对我而言,当我朋友或者朋友的朋友问我业务上点子,他们已经想了各种各样的点子和解决方案——我很担心。只要选一个问题并围绕他开始。只要你能解决这个问题(同时有足够的人也有这种问题),那么你很有可能就可以建立某种事业。

Visible Measures就是这样启动的。当我们某种程度上解决了我们所要着手的问题(了解用户行为),我们将范围缩小了到了在线视频领域作为一条路,并且很有希望能在此领域有所发展和专精。想作为解决问题的人切入,需要专注;要创建并拓展与解决的问题相关的业务,需要用到企业家方方面面的特质。
特征定义: 渴望改进

IV. 梦想型 (Class IV): 梦想型的人认为他们可以看到未来,一种更加高效或者是更加开放、更加协作的未来。自从学术界的好奇第一次点燃了Internet的星星之火,到其成燎原之势,再到成为业界重要的基础结构,其间涌现了一批领先于潮流的梦想型企业,包括Napster(P2P以及音乐自由)、6Degrees(社会关系)、Altavista (并行搜索)、Friendster(社会化网络)以及Second Life(虚拟世界)。所有这些企业都有个共同的特征:企业内植根的强大梦想,有着改变游戏规则的影响力以及潜力,还有努力跨越鸿沟以形成长期的成长力的拼搏精神。从某些方面说,这些梦想型的人对企业家社群的贡献最大,因为我们当中很多人紧紧跟随着这些拓荒者们,是他们在我们前面革新和创造,为我们开辟了新的道路。
遗产: 世界(或者至少人们对其中可能存在东西的认知)已被改变。

V. 游戏改变者 (Class V): 游戏改变者就是我们耳熟能详的那些人,他们还往往被学生或者是业务创新的爱好者崇拜。对于我来说,我总是惊叹于那些业界领袖的成就和推动力,像Steve Jobs, Bill Gates, Michael Dell, Larry Ellison还有很多其他人。抛开对于某个人或某个公司个实际操作的争论,第五类企业家不仅仅将产品、技术甚至是整个行业带上了创造曲线上的一个新高度,更让我崇敬得是,他们中大多数日复一日年复一年地坚持如此。Larry & Sergey现在进入了这个梯队,当然,Mark Z.已在路上了。

对我而言,从第四类进入第五类的一个关键的因素是对于“持续创新”的能力。如果仅仅是做一回耀眼的流星,几年之后陨落,在我的观点中是不可接受的。如果Facebook在今后几年内达到了5亿用户并且坐上社会化平台的第一把交椅,那么Mark Z就毫无疑问能冠以“游戏改变者”之名。

我思索着Steve Jobs曾说过的死亡是我们都一样的宿命——“追随内心和直觉的勇气。它们已经知道你真正所要成为的人。其他都是次要的。”
长寿: 至少十年持续创新。影响: 伟大.

这就是我遇到的五种类型的企业家。我很想知道大家的想法。

因为Steve Jobs曾说过: Stay Hungry.  Stay Foolish.

(或者努力做到“渴求但谦卑”)

建立一个OTP应用

概述

Erlang是一门干净简洁的语言,也容易学习。这只是Erlang,我们现在要讨论OTP。当进入到OTP之后,学习曲线就一下子上去了。各种各样的问题就来了,比如:我怎么启动一个应用,监督员(supervisor)的功能是什么,还有我要怎样使用gen_server?这还只是开始,后面还有更让人迷惑的……“.app文件是啥,怎么发布,还有,有人知道.script和.boot文件是干嘛的吗?”

本教程将带你一起创建一个叫做location_server的OTP应用。这个应用可以储存客户端的位置,允许别人从连接的节点查询这些位置,同时还允许客户端订阅位置变更时间所发出的通知。在整个创建location_server的过程中,我将解答以上那些关于OTP应用的问题,以及一些别的东西。

第一部分:建立服务器

首先要下载本教程的引用构建系统,可以在www.erlware.org上的downloads otp_base-<vsn>中找到。你在自己本地的Unix/Linux机器上下好这个代码之后,将构建系统解压出来。

我们首先要做的是对这个构建系统进行初始构建,只要在otp目录下输入make即可。注意:如果你的Erlang没有安装/usr/local/lib/erlang下的话,你可以创建一个指向你所安装的目录的符号链接,或者也可以将环境变量ERL_RUN_TOP设置为安装目录(确保使用的是绝对路径)。

然后第二件要做的事情是搭好应用程序的骨架。完成之后我就会剖析这个骨架并解释它的各个部分的含义。使用appgen工具来创建location_server的应用程序骨架。

我们已经在lib/location_server中创建好了一个应用、并在release/location_server_rel中创建好了一个发布。现在转入lib/location_server目录看看都有些什么。

其中include目录包含了一个叫作location_service.hrl的文件。我们会把所有共享的宏(macro)和记录定义放在这里。src目录包含了所有的Erlang文件(.erl)。vsn.mk文件包含了make变量LOCATION_SERVER_VSN=1.0,表示我们的应用的初始版本(可以随意更改)。这个版本在处理发布的时候会起到作用,我们会在后面讲到。

src目录包含了一个叫做location_server.erl的文件。注意这个Erlang源文件和我们的应用的名字一样。根据约定,这个文件将包含我们的应用的API。这个文件也包含了-behaviour(application)指令。这表示这个文件可以用作该OTP应用结构的入口点。

start/2函数是一个回调函数,OTP应用系统将用它启动由OTP发布系统所指定的应用。这个函数要求两个参数(你可以阅读关于监督机制的文章来了解更多关于该参数的信息)同时它会返回{ok, Pid}或者{ok, Pid, State},前者更为常见,代表应用成功启动。注意start/2函数调用了ls_sup:start_link/1函数。ls_sup.erl包含了我们的顶层监督员。注意名称ls_sup中的前缀“ls”其实是我们的应用程序名称“location_server”的两个单词的缩写。这种前缀也是一种约定,它用于实现Erlang中的包结构来避免一个发布中的名称冲突。你也可以随意指定一个前缀,这种应用程序名称缩写的方式仅仅是我的建议。

顶层监督员将启动应用程序所必须的所有的工人(worker)以及底层的监督员。它指定了一种策略,详细描述了当进程崩溃的时候如何被重启。这在Erlang中是一个关键的概念,我建议你阅读一下www.erlang.org上的关于监督机制的设计原理。

这个文件导出了两个函数start_link/1init/1。这是遵照监督员行为的两个函数——注意出现在文件顶部的-behaviour(supervisor)指令。指定一个函数start_link是在OTP中的一种约定,表示该函数会产生(spawn)一个进程并且这个函数的调用者,也就是父进程,会被联接到(参见link/1)到子进程。这种情况下OTP应用结构会被联接到监督员进程上。

init/1函数则是真正的监督员核心所在。记住不要将任何程序逻辑放到监督员中,它必须尽可能简单并且不会出错,你应用程序的命运就掌握在这里。让我们将它进行分解:

RestartStrategy = one_for_one表示对于每个挂掉的进程只需要重启该进程而不影响其他的进程。还有其他两种重启策略,one_for_llrest_for_all,你可以在监督员文档中了解到它们的信息。MaxRestarts表示在MaxTimeBetweenRestarts秒内随多允许的重启次数。

这个监督员只有一个ChildSpec表示它仅会启动一个进程。按顺序看看数据结构,ls_server是要被启动的进程的名字,{ls_server, start_link, []}是启动这个子进程要调用的模块函数和参数,permanent表示这个进程除了重启决不允许挂掉(还有其他的选项,参考erlang.org上关于监督机制的文档),1000是在野蛮杀死进程之前等待进程结束的时间,worker表示该子进程并非一个监督员,[ls_server]是该进程所依赖的模块的列表。

监督员回调模块函数init/1返回所有这些信息,同时监督员行为会负责按指定顺序启动所有子进程。

现在进入ls_server.erl文件。这个文件展示了gen_server行为。这表示它将遵循这种行为的规定导出以下回调函数:

另外我们还要创建一下外部函数:

我们开始吧。为所有的外部函数编写文档非常重要。OTP基本构建系统已经通过“make docs”来支持“edoc”,这个指令可以自动遍历并编译应用的所有文档并存入应用目录中的doc目录中(location_server/doc)。回到我们的函数:

这个函数启动服务器,并在本地注册为?SERVER(在源代码里找找-define(SERVER,?MODULE).看)并且指示gen_server行为当前模块导出了必要的回调函数。

stop很简单,不过注意gen_server:cast/2的使用。这个函数基本和使用“!”操作符类似。他用于给一个通用服务器发送一个异步消息。这里我们给标识为?SERVER的本地注册进程发送原子stop

store_location/3是一个非常简单的函数,但是也有不少地方需要引起我们注意。注意我们使用了守护表达式(guard expression),is_atom/1以及is_list/1。要确保传递进来的变量的类型是正确的。这是个不信任边界外事物的例子:在这里检查过之后就不用再检查了。你有没有注意到,这个函数的名字和我们发送的消息中的“标签”是一样的?没有么?我来解释一下:

上面这行中的store_location就是我们的“标签”,它可以将这个消息和其他消息区分开来。{Id, Location}是我们所发送的在标签store_location之下的有效载荷。在gen_server中所发送的消息应该遵循“{标签, 值}”的约定。其实在上面这行还有一个更重要的东西需要注意。注意这个函数的第一个参数是{?SERVER, Node}而不仅仅是我们在前面一个cast中看到的?SERVER。出现这个情况是因为我们想要能够与在网络中任何节点上进程联系而不仅仅是我们自己的进程。要给远程注册的消息发送消息,就可以使用这种语法{注册名,节点}其中节点可以是在网络上的任何节点包括本地节点。

这里要注意的东西是我们没有再使用守护条件——我们不关心——这个函数不会改变服务器的状态,如果它挂了对我们也没什么影响。最重要需要注意的地方是gen_server:call/2的使用。这个函数是向一个gen_server发起一个同步(阻塞的)调用。

现在我们完成了所有的外部程序,让我们充实服务器吧。对于新接触Erlang的人来说,有一个事情很容易混淆,就是:这些外部函数是在调用进程的进程空间中被调用的,虽然他们生成的消息是是被同一个模块中的语句接收的,但是这些语句却是存在于一个完全不同的进程中的。有可能的话,要使用模块来封装服务器协议,这点很重要。

在进程能被外界访问前,由init函数设置进程状态。这里状态储存于一个字典dict中。这个字典可以用于储存形如{Id, {Location, ListOfSubscribers}}这样的键值对。init函数还记录了ls_server进程启动的信息。日志文件的位置和管理将在本教程的第二部分(讲述如何为应用创建一个发布)进行阐述。

这样服务器和location_server应用的所有功能就完成了,但是我们还有最后一步要做。显然,直接让应用程序的客户直接调用ls_server:store_location这种非常笨拙和丑陋。我们所需要的就是针对客户良好定义的外部API。放这些API的地方就是和我们的应用程序名称一样的那样模块——我们的应用行为模块,location_server.erl

毫无疑问必须得导出这些函数。

现在我们完成了location_server的编码,剩下要做的就是到应用程序的目录中,输入“make”和“make docs”,并等待代码和文档编译成功。

注意应用程序根目录下出现了两个新目录“doc”和“ebin”。location_server/doc包含了前面写的代码中的文档。所有的外部函数都会出现在这些文档中。让我们稍微深入研究一下ebin目录。

ebin包含了每个.erl源文件对应的编译好的.beam字节码文件。ebin目录也有一个.app文件和一个.appup文件。.appup文件用于处理升级发布,这超出了本教程的范围。.app文件对于创建和安装OTP发布是必不可少的,所以我们再更深入地研究一下它。

以上代码中的注释已经基本完全解释清楚了。这个.app文件是动态创建由make进程从location_server/src/location_server.app.src文件中自动创建的。这表示你应该基本无须手动更改这个文件。

恭喜你已经创建了首个漂亮的OTP应用。现在让我们进入本教程的第二部分并创建一个发布,一边能运行和部署我们的OTP应用。

第二部分:打造一个发布

在otp目录中:

该目录包含了一个vsn.mk文件,用于储存本发布的版本字符串。还有一个yaws.conf.src文件用于生成yaws.conf文件,如果你选择在发布中包含yaws服务器。目前我们将注意力集中在location_server_rel.config.srclocation_server.rel.src这两个文件上。.config.src文件由make翻译为.config文件。这个文件包含了发布中的必须的各种不同应用程序(诸如截断翻转日志)的所有配置。.rel.src文件则会被make转成.rel文件。.rel文件又会用于创建.script.boot文件,这些文件将告诉Erlang系统当启动的时候要载入哪些代码。下面是我们的.rel.src文件。

这个文件指定了我们的发布的名字,并且跟了一个版本串,当进行make的时候会将vsn.mk文件中的版本串填入这个地方。接下来指定了我们的发布中要包含erts,以及kernel, stdlib, sasl, fslib, gas最后我们自己的应用location_server。其中fslib和gas这两个应用为我们的发布提供了配置和日志翻转截断,关于他们的详细信息可以到erlware.org找到完整的文档。另外你还可以进入otp/lib/fslib或者otp/lib/gas目录中并运行“make docs”就可以在本地生成所有的文档了。我们运行了make之后,这个.rel.src文件就会被转换成实际的.rel文件,它包含了.rel.src文件中出现的所有应用名字结合了对应的版本号。关于.rel.src结构的更多信息,可以看一下fslib文件fs_boot_smithe的文档。

是时候该编译并运行我们的发布了。

生成了不少文件还有两个目录。我们看到了一个.rel文件,一个.script文件和一个.boot文件。每个文件的内容都看看,.boot文件就不用看了因为它只是.script文件的二进制版本。注意刚刚创建的两个目录:locallocation_server_rellocation_server_rel是一个用于安装和应用程序生产发布的一个场景区域,更多信息请参考erlware.org上的OTP Base中的文档。接下来将焦点放在这里的local目录。这个目录让我们可以交互式地运行我们的发布。如果我们要将其作为一个守护进程运行,只需要简单地调用local/location_server_rel.sh,并加上-detached标志就行了。现在转到local目录中并直接运行我们的应用程序。

err_log文件包含了所有通过saslerror_loggerinfo_msgerror_msg打印的日志。其他日志,如在sasl_log中你可以看到所有的sasl错误报告。如果发布的程序崩溃了,首先要查看sasl_log。默认情况下构建系统已经加入了对fs_elwrap_h(通过G.A.S应用,已作为这个发布的一部分)的应用以便截断和翻转日志文件。这可以防止日志文件无限制地增长。如果要配置或者移出这个功能,可以配置“local”目录下的location_server.config文件。

mod_specs告诉GAS应用对于某个发布,要启动哪些服务以及如何启动。后面的实际的配置元组,elwrap将根据它来工作。首先是err_log的位置,后面跟着wrap specs,指定了sasl日志和错误日志的单个文件进行翻转之前最大可以达到多少,以及指定的日志记录器保存多少个文件。配置的最后一位指定错误记录器是否要打印到屏幕。

第三部分:测试我们所构建的东西

若要测试我们的项目,首先要启动三个单独的Erlang命名节点。打开三个终端窗口并启动三个节点,其中第一个是位置服务器location server,由local/location_server.sh启动,第二个由erl -name a -pz <location_server/ebin的路径>启动,第三个erl -name b -pz <location_server/ebin的路径>。其中-pz参数告诉解释起将指定的路径添加到代码加载器的搜索路径中。这表示我们可以在节点a和b上使用location_server的接口了。(这种代码加载没有文件。)这样我们启动了三个节点a@machinename.comb@machinename.com以及<username>_location_server@machinename.com。要连接节点,从其他两个节点上执行nat_adm:ping('a@machinename.com')。完了后,在任意一个节点上运行nodes(),应该得到在我们的云中的其他节点的列表。在节点上运行以下命令。确保以下例子中的节点正确,因为这些命令不都是运行于同一个节点上的。

上面所发生的内容是:我们通过location_server_rel.sh启动脚本将位置服务器作为节点’martinjlogan_location_server_rel@core.martinjlogan.com’启动了。然后我们启动了两个节点a@core.martinjlogan.com和b@core.martinjlogan.com。然后执行了以下步骤:

  1. 在“a”上的第1个提示,我们存储了键“martin”和值“at work”
  2. 在“b”上的第1个提示,我们发出了要更改键“martin”的警告
  3. 在“b”上的第2个提示,我们等待接受信息并打印
  4. 在“a”上的第2个提示,我们又存储了个位置,将“martin”的位置改成了“at home”并让“b”退出了接受语句并打印了该消息:Msg: {location_changed, {martin, "at home"}}.订阅消息也成功了!
  5. 最后在“a”上的第3个地址,我们验证了fetch_location正常工作。

你可以从这里下载本教程的全部代码,祝你用Erlang愉快编程。

Rails SQL Session Store优化版

问题根源

原始的ActiveRecord会话仓库很慢。对于低流量的网站而言没有什么问题,但是对于大一点的而言就慢了。首先,它的慢是因为ActiveRecord本身比较慢。虽然这是一个强大的ORM框架,但对于像会话管理这种简单的任务而言就是杀鸡用牛刀了。

还有其他的解决方案如cookie会话仓库(会话长度有限,不能在会话中存放敏感数据),memcached(无法持久化+难以实现高可用性方案)。

这就是为何要创建SqlSession仓库的原因。它直接操作mysql的数据库API,要比原始的AR会话仓库快很多。不过有时候它还是比较慢,因为:

  • 每次访问都会创建、更新会话 – 任何机器人或者偶然的访客都会在数据库中创建一条会话记录,最后导致会话表里面会有成千上万的无用记录,但其中99%的访问其实是不需要任何会话更新的。
  • 它使用32位字符串作为会话记录的键 – 所有RDBMS在处理字符串索引都要比整数慢很多,所以使用整数更好,然而我们的会话ID非常长,同时这些会话仓库都直接使用他们作为表索引。
  • 使用了auto_increment 主键,对于MySQL 5.1.21之前的版本都会导致InnoDB使用表级锁。在不必要的插入上使用表级锁会给大型网站造成奇怪的问题。

解决方案

FastSessions Rails插件便是作为对于以上几个问题的解决方案而诞生的。

首先,我们从会话表中去掉了id字段,所以我们无须用到auto-increment锁。接下来为了让查找更快,我们使用了以下这些技术:不使用(session_id)作为查找索引,使用(CRC32(session_id), session_id)——双列键,可以帮助MySQL更快地找到会话记录因为键的基数更高(这样,mysql可以更快地找到记录而不用检查很多索引行)。我们测试过这种方法,在大会话表中显示了10-15%的性能提升

最后,也是最强的优化是,对于空会话不创建数据库记录,同时如果数据没有在请求处理过程中没有更新过,则会话数据不会存回数据库。这个更改基本上可以减少50-90%的插入数量(根据应用程序的情况)。

那么,你肯定在想,用了这个插件之后会话究竟能快多少?这很难说。结果要看你是怎么操作会话的:如果你很少更改会话数据(如登录时储存用户id),那么可能会有90%的性能提升,但如果每次访问,你都要给用户会话写入些信息(比如最后一次访问时间last_visit_time),那么根据服务器的负载和会话表的大小,可能有5-15%的性能提升。

你只需要安装一个简单的插件,便可以自动实现上面这些更改。

我们解决了AUTO_INC锁的问题并去掉auto-increment键之后,又引入了一个新的问题,我在这里想说一下。这个问题是这样的。InnoDB会根据主键将所有数据分组。这意味着当我们使用自增长主键并向表插入记录时,会话记录会被组到一起,顺序地存储在磁盘上。然而如果使用比较随机的值(如一个随机会话id的crc32值)作为主键,那么每一个会话记录会被插入到属于它自己的不同的地方,那么会产生一些随机I/O,这对于I/O有限制的服务器不是很理想。所以,我们决定让用户自己选择在部署的时候使用何种主键,当你想在MySQL 5.1.22+上使用这个模块的话,可以设置

CGI::Session::ActiveRecordStore::FastSessions.use_auto_increment = true

这样在InnoDB中会连贯地插入数据。另一个情况当你的MySQL服务器的I/O有限制,不想因为随机主键而增加随机I/O,也可以这样设置。

如果你不想丢失使用AR会话插件创建的旧会话数据,你可以设置

CGI::Session::ActiveRecordStore::FastSessions.fallback_to_old_table = true

这样当某些session_id在新的会话表中找不到的时候,就会回去访问旧的会话表。旧会话表的名字可以使用CGI::Session::ActiveRecordStore::FastSessions.old_table_name 变量进行设置.

这个选项会使会话变慢所以我建议只要在升级到新会话表的时候才使用。在这种情况下,新的会话数据就会存入新表中,当到了会话超时期限的时候,就可以删除旧表了(我们用了两个月的期限,过了两个月之后,我们就可以删除旧表,并将此选项关闭。)。

安装

安装FastSessions插件十分简单,只需以下几个步骤:

  1. 将该插件代码从我们的SVN库中安装到vendor/plugins目录中(可以使用./script/plugin install安装,或者piston import命令进行安装——看你喜欢)例如:

    $ piston import http://rails-fast-sessions.googlecode.com/svn/trunk/ vendor/plugins/fast_sessions
  2. 在config/environment.rb文件中启用ActiveRecord会话仓库:

    Rails::Initializer.run do |config|
    ......
    config.action_controller.session_store = :active_record_store
    ......
    end
  3. 为新的会话表创建数据库迁移:

    $ ./script/generate fast_session_migration AddFastSessions
  4. 如果需要,可以打开新创建的迁移脚本并更改表名table_name和插件use_auto_increment参数。
  5. 运行数据库迁移:

    $ rake db:migrate
  6. 启动应用程序并尝试进行一定会保存数据到会话的操作。然后检查fast_sessions会话表(如果你没有改名字的话)有没有这条记录。

下载

该插件的最新版本可以在它的项目网站或在 SVN仓库中找到。该插件由Alexey KovyrinPercona的MySQL性能专家)制作。开发由Scribd.com赞助。

Facebook对memcached的提升

如果你翻阅过一些关于大型网站扩展(Scaling)的资料,那么你可能听说过一个叫memcached的东西。memcached是一个高性能、分布式的内存对象缓存系统。我们Facebook可能是世界上最大的memcached用户了。我们利用memcached来减轻数据库的负担。memcached确实很快,但是我们还要让他更快、更高效。我们使用了超过800台服务器,提供超过28TB的内存来服务于用户。在过去的一年里,随着Facebook的用户量直线上升,我们遇到了一系列的扩展问题。日益增长的需求使得我们必须对操作系统和memcached进行一些修改,以获得足够的性能来为我们的用户提供最好的体验。

因为我们有好几千台机器,每个都运行了几百个Apache进程甚至更多,最终导致到memcached进程的TCP链接有几十万个。这些链接本身并不是什么大问题,但是memcached为每个TCP链接分配内存的方法却很成问题。memcached为每个链接使用单独的缓存进行数据的读写。当达到几十万链接的时候,这些累计起来达好几个G——这些内存其实可以更好地用于存储用户数据。为了收复这些内存,我们实现了一个针对TCP和UDP套接字的每线程共享的链接缓存池。这个改变使每个服务器可以收回几个G的内存。

虽然TCP上我们改进了内存的使用效率,但我们还是转向了UDP,目的是让get(获取)操作能降低网络流量、让multi-get(同时并行地获取几百个键值)能实现应用程序级别的流量控制。我们发现Linux上到了一定负载之后,UDP的性能下降地很厉害。这是由于,当从多个线程通过单个套接字传递数据时,在UDP套接字锁上产生的大量锁竞争导致的。要通过分离锁来修复内核恐怕不太容易。所以,我们使用了分离的UDP套接字来传递回复(每个线程用一个答复套接字)。这样改动之后,我们就可以部署UDP同时后端性能不打折。

另一个Linux中的问题是到了一定负载后,某个核心可能因进行网络软终端处理会饱和而限制了网络IO。在Linux中,网络中断只会总是传递给某个核心,因此所有的接受软终端的网络处理都发生在该内核上。另外,我们还发现某些网卡有过高的中断频率。我们通过引入网络接口的“投机”轮询解决了这两个问题。在该模型中,我们组合了中断驱动和轮询驱动的网络IO。一旦进入网络驱动(通常是传输一个数据包时)以及在进程调度器的空闲循环的时候,对网络接口进行轮询。另外,我们也用到了中断(来控制延迟),不过网络中断用到的数量大大减少(一般通过大幅度提升中断联结阈值interrupt coalescing thresholds)。由于我们在每个核心上进行网络传输,同时由于在调度器的空闲循环中对网络IO进行轮询,我们将网络处理均匀地分散到每个核心上。

最后,当开始部署8核机器的时候,我们在测试中发现了新的瓶颈。首先,memcached的stat工具集依赖于一个全局锁。这在4核上已经很令人讨厌了,在8核上,这个锁可以占用20-30%的CPU使用率。我们通过将stats工具集移入每个线程,并且需要的时候将结果聚合起来。其次,我们发现随着传递UDP数据包的线程数量的增加,性能却在降低。最后在保护每个网络设备的传送队列的锁上发现了严重的争用。数据包是由设备驱动进行入队传输和出队。该队列由Linux的“netdevice”层来管理,它位于IP和设备驱动之间。每次只能有一个数据包加入或移出队列,这造成了严重的争用。我们当中的一位工程师修改了出队算法,实现了传输的批量出队,去掉了队列锁,然后批量传送数据包。这个更正将请求锁的开销平摊到了多个数据包,显著地减少了锁争用,这样我们就能在8核系统上将memcached伸展至8线程。

做了这些修改之后,我们可以将memcached提升到每秒处理20万个UDP请求,平均延迟降低为173微秒。可以达到的总吞吐量为30万UDP请求/s,不过在这个请求速度上的延迟太高,因此在我们的系统中用处不大。对于普通版本的Linux和memcached上的50,000 UDP请求/s而言,这是个了不起的提升。

我们希望尽快将我们的修改集成到官方的memcached仓库中去,我们决定在这之前,先将我们对memcached的修改发布到github上。

Rails内置缓存引擎与线程安全

升级到Rails 2.1之后,使用Rails内置的缓存之后,发现使用mem_cache_store总是报错,于是干脆研究了Rails内置缓存引擎的代码。阅读的结果发现,Rails的内置引擎只应该应用于进程模式,尤其是mem_cache_store,因为mem_cache_store是每进程建立一个到memcached的链接,即便是读取,也需要写入套接字,所以为了同步,必须使用锁。因此即使在读取缓存的时候,也会出现争用的情况。mem_cache_store也必须像Rails2.2中的ActiveRecord一样实现一个到memcached的线程池,或者使用异步链接,否则是发挥不出memcached的效率的。

我研究了另外几个cache_store,

  • memory_store则根本连锁机制都没有,但对应有个多线程的synchronized_memory_store,使用了Monitor
  • drb_store没有用过,可能Drb本身有一些同步机制,但估计也可能会出现与mem_cache_store一样的问题;
  • file_store应该是在多线程中应该比较理想的,因为使用了文件系统自身的同步机制,使用了File.atomic_write,无论是多线程还是多进程都能共享同一个file_store

所以,Rails的线程安全还有很长一段路要走。

PS:前面的我说的我遇到的mem_cache_store的错误是Rails.cache.fetch误加了:raw => true 参数。

横向扩展(Facebook)

原文:Scaling Out

作者:Jason Sobel (notes)

翻译:ShiningRay

我于2007年四月加入了Facebook,在结束了几周的课程之后,我的经理Robert Johnson来找我。我们谈了很久,不过内容可以归结为:

Bobby: “那么,Jason,我们要在2008年之前在弗吉尼亚开一个新的数据中心。你能去帮点忙吗?”
Me: “呃…. 可以?”
Bobby: “很好!”

我在Facebook的第一个项目上投入的要比我预期的多一点点,但是我认为这是为何我们拥有如此一个非常强大的工程组织的原因;我们还有很多难题有待解决,这里每个人都迫不及待要立刻去解决他们。我开始了解为何我们需要建造一个新的数据中心以及我们需要解决什么问题才能让他正常工作。

有何必要?

在东海岸建造一个新的数据中心的主要原因就是“延迟”。在一个高速连接上发送一个包横穿大陆需要大概70微秒的时间,而对于普通的互联网用户而言,可能会需要的时间就长得多。通过将服务器放在弗吉尼亚,我们可以大大减少给东海岸和欧洲的用户传送网页的时间。

第二个关注点是空间、能源和灾难恢复。在我们位于加利福尼亚的主数据中心中已经没有多少物理空间了,而弗吉尼亚的点可以给我们充分的空间添加东西。我们还有一个类似问题就是要给予充足的电能驱动所有的服务器。最后,如果把我们限制在某个单独的地方,意味着如果出现灾难事件(断电、地震、怪兽),可能会导致Facebook长时间无法访问。

开始构建!

在我们能处理应用级的问题之前,我们的小组在弗吉尼亚投入了大量的心血构建服务器和物理空间。他们还完成了数据中心之间的网络和低延迟光线通道连接。这些工作是非常巨大的工程,然而我们顶尖的团队使之看上去像是小菜一碟。

网络和硬件都到位后,我们搭建了标准的3层架构:Web服务器,memcache服务器和MySQL数据库。在弗吉尼亚的MySQL数据库作为西海岸的数据库的从数据库(Slave)运行,所以我们花了几周的时间复制所有的数据,然后建立同步复制流(replication stream)。

现在硬件、网络和基础的设备都已经建立好,那现在就要面对两个主要的应用级的挑战:缓存一致性(Cache Consistency)和流量路径选择(traffic routing)。

缓存一致性

先说一下我们的缓存模型:当一个用户修改了数据对象后,我们的底层设施会向数据库写入新的值,并且从memcache中删除旧的值(如果存在)。下一次用户请求该用户对象的时候,我们从数据库中取出新的结果并写入memcache。后续的请求就会直接从从memcache中取出数据直到缓存过期或者被另外一次更新删除。

这种设置在只有一套数据库的时候运行得很好,因为我们只有当数据库完成了新值的写操作之后才删除memcache中的值。这种方式保证了我们能够从数据库中获得新值并且放入memcache中。然而,当在东海岸有一个从数据库后,情况就有些棘手了。

当我们在西海岸的主数据库中更新了一些数据之后,在东海岸的从数据库能正确反映这些新数值之前,中间有一个同步复制的延迟。通常这个延迟小于一秒钟,但是在高峰时期,它可能会延长到20秒。

现在我们假设在更新了加利福尼亚的主数据库的同时,我们从弗吉尼亚的memcache层中删除了旧值。然后有一个对弗吉尼亚的从数据库的读操作可能由于复制延迟还是看到的旧数值。然后弗吉尼亚的memcache可能会更新为旧的(不正确)的数值,然后它可能被“困住”直到被删除。如你所见,最差的情况是弗吉尼亚的memcache层可能总是同一个版本而非正确的数据。

考虑下面的例子:

  1. 我将我的名字从“Jason”改成了“Monkey”
  2. 我们把“Monkey”写入了加利福尼亚的主数据库并且从加利福尼亚和弗吉尼亚的memcache中删除了原来的名字
  3. 有个人在弗吉尼亚访问我的信息
  4. 在memcache中没有找到我的姓名信息,所哟我们从弗吉尼亚的从数据库中读取,由于复制的延迟获得了“Jason”
  5. 我们将姓名“Jason”存入弗吉尼亚的memcache
  6. 同步复制上来了,我们将名字信息在从数据库中更新为“Monkey”
  7. 另一个人在弗吉尼亚访问我的信息
  8. 我们在memcache中找到了名字并返回“Jason”。

在我再更新我的名字或者数据过期需要再访问数据库之前,我的名字在弗吉尼亚会一直显示为“Jason”,在加利福尼亚显示为“Monkey”。混乱吧?确实。欢迎来到分布式系统的世界,在这里一致性确实是一个难题。

幸好,解决方案要比问题容易解释。我们对MySQL做了一个小小的改动,让MySQL能在同步复制流中附加一个额外的信息。我们利用这个功能将要变更的所有数据对象追加到给定查询上,然后当从数据库“看到”这些对象后,要负责在进行了数据库更新后将这些值从缓存中删除。

我们是怎么做到的呢?MySQL是用了一个词法解析器和yacc语法来定义查询的结构然后对其进行解析。为了解释方便,我对其进行了简化,这个语法最顶层差不多如下:

很直观吧?一个query(查询)是一个能分解成某种我们熟知的MySQL表达式的statement(语句)。我们将这个语法修改为允许在任意查询后追加memcache键,如下:

查询现在可以有一个额外的组件;在语句statement之后有mc_dirty可以为空或者为一个关键词MEMCACHE_DIRTY后面跟着一个mc_key_list。一个mc_key_list只是一个逗号隔开的字符串列表,该规则会告诉解析器将所有字符串一个接一个存入某个叫做mc_key_list向量中,这个向量将被存入每查询解析器对象中。

看个例子,某个老式的查询看上去像:
REPLACE INTO profile (first_name) VALUES ('Monkey') WHERE user_id='jsobel'
在新语法下会变成:a
REPLACE INTO profile (first_name) VALUES ('Monkey') WHERE user_id='jsobel' MEMCACHE_DIRTY 'jsobel:first_name'

新的查询会告诉MySQL,除了要将我的名字更改为Monkey外,它还需要将一个对应的memcache键设脏。这很容易实现。由于每对象解析器对象现在储存了所有的memcache键,我们在mysql_execute_command最后添加了一小段代码——如果查询成功了,就设脏这些键。看看,我们成功地按照我们的目的——缓存一致性——劫持了MySQL同步复制流。

新的工作流变成了(更改的内容为粗体):

  1. 我将我的名字从“Jason”改为“Monkey”。
  2. 我将“Monkey”写入加利福尼亚的主数据库并从加利福尼亚的memcache中删除我的名字,但不包括弗吉尼亚的memcache
  3. 某个人在弗吉尼亚访问了我信息。
  4. 在memcache中找到了我的名字,并返回“Jason”。
  5. 同步复制到了之后,将从数据库中我的名字更新为“Monkey”。还需要从弗吉尼亚的memcache中删除我的名字因为缓存对象出现在同步复制流中了。
  6. 另一个人在弗吉尼亚访问了我的信息
  7. 没有在memcache中找到我的名字,所以从从数据库读出名字,得到了“Monkey”。

页面路径选择

我们还需要解决的另一个主要的问题是只有在加利福尼亚州的主数据库才可能接受写操作。这个情况就是说我们需要避免在弗吉尼亚服务那些需要进行数据库写操作的页面,因为他们都需要穿越整个大陆访问我们在加利福尼亚的主数据库。幸好,我们最频繁访问的页面(首页、档案、照片页面)在正常情况下都不会进行写操作。这样这个问题就归结于,当一个用户请求某个页面时,我们怎么判断它是否可以被“安全”地送到弗吉尼亚,或者它必须被引导到加利福尼亚?

这个问题最后有一个比较直观的答案。某个用户请求Facebook时,命中了第一批服务器其中的一个,这个服务器称之为负载均衡器;该机器的主要职责是选择一个Web服务器来处理该请求,不过它也进行一些其他目的的服务:防御拒绝服务攻击,多路复用用户连接等。这个负载均衡器拥有可以在第7层模式运行的能力,这样他可以检查用户请求的URI并根据这个信息进行路由选择决定。这个特性意味着,我们可以很容易地告诉负载均衡器哪些是“安全”页面,然后可以根据页面的名字和用户的位置决定是否要将请求发送到弗吉尼亚或者是加利福尼亚。

不过,这里还有一点问题。假设你访问editprofile.php来更改家乡信息。该页面没有被标记为安全所以他被引导到了加利福尼亚,并且进行了更改。然后你访问你的档案页面,同时由于这个页面是安全页面,所以被引导到了弗吉尼亚。然而因为前面提到的同步复制延迟,你可能不能立刻看到你刚刚做过的改动!这种体验会令用户感到非常混乱,同时会导致双重提交。我们通过在浏览器中设置一个包含(有过写入数据库操作的)当前时间cookie来绕开这个问题。负载均衡器会查看该cookie,如果它注意到20秒内你写入了些东西,将无条件地传送到加利福尼亚。过了20秒之后,我们确保数据已经同步到弗吉尼亚,这时便允许你回来访问安全页面。

回顾

从我们第一个用户在弗吉尼亚数据中心访问页面后的九个月中我们一直在运行同样架构获得了很好的效果。当然,一路上还有挫折;在头一两个月中,缓存一致性的框架非常地不稳定,逼我们在诊断和修复错误的时候每隔一段时间就要把流量从弗吉尼亚转移出去。当然,过了一段时间,我们消灭了这个问题,现在这个数据中心在Facebook的流量中占了很大的比重。

这个架构中主要的伸缩方面的挑战很明显:所有的写操作必须在同一个地方发生。更进一步我们对开发新的可以让我们在任何位置进行写操作的技术非常感兴趣。我们也在思考如何将新的数据中心做成一个灾难恢复点,以防怪兽要进攻加利福尼亚!想来帮帮我们吗?www.facebook.com/jobs!

亚原子粒子有自由意志吗?

如果我们有自由意志,那么亚原子粒子也有,有数学家说可以证明。

“如果原子永不为创始那些攫取命运的纠葛和永恒的因果循环的新运动而转向,那么什么才是这地球上的生物所拥有的自由意志的源泉?”
——卢克莱修,罗马哲学家兼诗人,公元前99-55。

人类自由意志可能哲学主题中最模棱两可的,超出数学证明范畴。然而两位德高望重的普林斯通数学家,表示他们已经证明了如果只要人类有那么一点点最小量的自由意志,那么原子本身的行为就一定无法预测。

该发现不会给多数物理学家造成困扰,因为量子力学的传统解释已经包含了不确定性了。量子原理表示,最好的方式是预测一个粒子的某种方式的行为的可能性。

但是爱因斯坦开始的物理学家则对这个想法非常不满。爱因斯坦最著名的抱怨——“上帝不掷筛子”。确实,自从量子理学诞生那刻起,一些物理学家给出了它的等式的替代解法,旨在消除其不确定性原理。最著名的替代法由物理学家David Bohm给出,他在1950s争论说,亚原子粒子的行为完全由无法被观测的“隐变量”决定。

Conway和Kochen说该研究是没戏的,同时他们表示不确定性是继承自这个世界本身,而不是在量子理论中。并且他们二人对支持Bohm的物理学家以及其他类似想法的人说:放弃确定性,要么就放弃自由意志。即使是一点点自由意志。

他们的论点源自Kochen在40年前用Ernst Specker创建的证明。亚原子粒子有一个特性叫做“自旋”,它可以绕任意轴进行。已有实验证明了一类自旋为1粒子有一中独特的属性:选择三个相互垂直的坐标,然后探测该自旋为1的粒子确定它绕哪个轴为的自旋0。确切地结果是,其中一个轴会自旋为0,另外两个会有非0自旋。Conway和Kochen称之为1-0-1规则。

自旋是目前物理学家尚不能在探测之前事先预测的特性之一。然而,任何人都可能会想象粒子绕任意轴的自旋是在任何人去探测它之前就已经确定好的。这也是我们在生活中的常见假设。我们不会想象,比如,一个栏杆是因为我们看到了它,所以它变成了白色——我们认为它应该一直都是白色。

但是Kochen和Specker证明了这个假设——栏杆一直是白色——在奇异的亚原子粒子的世界中是说不通的。他们是用了一个纯数学的论据表明了粒子无法用一种与1-0-1规则一致的方式来选择绕每一个可想到的轴旋转。实际上,一个只有33个轴的集合已经足够令粒子变成一个悖论。它可以选择绕前32个符合该规则的轴自旋,然而对于最后一个,无论0或者非0都不可能。选择零自旋将会创建一套有两个零点的正交的三个轴,选择非零自旋则会创建另一套,有正交的三个轴,以及三个非零点,两种方式都会打破1-0-1规则。

这也就意味着,粒子在被测量之前不可能在任何一个方向都有一个确定的自旋,Kochen和Specker总结到。如果该情况成立,那么物理学家应该能偶然观察到它打破了1-0-1规则,而这个从未发生过。相反,它必须在瞬间“决定”怎么自旋。

Conway将该情况与“二十个问题”游戏进行了比较。如果你公平地玩该游戏,你在一开始就确定了某个对象,然后诚实地回答每个问题,然后祈祷对手不会过早推出你的想法。然而一个机灵的玩家会去骗人,在玩了一半就去改变自己选的对象。在这种情况下,它的答案就不能事先确定了。Kochen和Specker说,粒子就像一个会骗人的玩家。他们发现没有哪个单一的对象能一次完全满足所有“问题”(或者说所有33个轴)。

但是还有另外一种可能的解释。如果粒子的自旋是完全确定的——但是依赖于和这个宇宙的状态相关的别的东西。这就好像“二十个问题”中的一个玩家它不管对手问什么问题,只要对方问的问题是以“是不是”开头,那么这个玩家就在脑子里确定选一个驴子,并回答是,否则,这个玩家就选马,并回答否。在这种情况下,它的回答也是可预先确定的即使他在脑子里一个对象也没选。

Conway和Kochen说他们闲在证明了粒子的响应是无法被预先确定的,即使后面这个认为可行的解释中。“我们可以证明没有任何算法和方法,能让粒子可以事先给出唯一以及确切的答案,”Conway说。“我还是很惊奇我们居然能证明这一点。”

他们为他们的证明编造了一个想法上的实验。可以纠结两个自旋1的粒子这样他们的自旋就在每一个可能的轴上都一样,并且保持如此,即使他们彼此相隔很远。这样纠结两个粒子,然后将其中一个发给位于火星的叫做Alice的物理学家,另一个发给位于地球叫做Bob的物理学家。根据相对论,这两个粒子之间是不能传递信息的。Alice和Bob分别在某个他们自由选择的轴上探测粒子。如果 Alice和Bob碰巧选择了相同的轴,那么他们会得到相同的答案。

现在,想象该粒子就像“20个问题”的玩家,它选的对象有时候是一头了驴,有时候是一匹马,根据不变的规则决定什么时候回答哪种动物。无论规则是什么,它都作用于纠结的两个粒子中的每一个,并且可以造成他们有同样的自旋。这就好像“20 个问题”的玩家被克隆了,并且二者都被强制针对相同的动物给出回答。

但是Conway和Kochen证明了这种情节对于互不通信的粒子是不可能的。他们调用了老的Kochen-Specker悖论来证明如果自旋1粒子的行为是可预先确定的那么它就不允许“改变它的动物”,它就不能给出与1-0-1相一致的答案。所以如果Alice和Bob幸运地选择了同样的轴,那么他们应该能强迫粒子要么结果不同要么违反1-0-1理论——这与实验结果相矛盾。

Kochen和Conway说解决悖论的最佳方法就是接受粒子的自旋只有到它被测量的时候才存在这个论断。不过还有一种方法跳出他们的套:假设当时Alice和Bob对于选择哪个轴进行测量并非自由的选择。那么大自然会神秘地阻止他们去选择会触犯规则的那些轴。Kochen和Conway不能完全消除这种可能性,不过Kochen说,“任何走在大街上的人都会说:‘别搞笑了’很自然的感觉是,当然了,我们会根据我们自己的自由意志来做。不能完全消除这种可能性,但是就目前我们所知而言,无疑我们可能选择在实验中按哪个按钮。”

理想情况下,一个数学证明应该解决一切的不确定性,但是Kochen和Conway并没有让很多他们提到的物理学家相信。“我不相信”,Rutgers大学的Sheldon Goldstein说,他是一名Bohm支持者。他认为这些言论没有包含什么新东西,而且他对自由意志只是实际存在(而非原理上)的观点感到满意。四年前,两位数学家发布了他们的成果后,他和他的助手花了很长时间与两位科学家探讨了这些问题。两位科学家的新版理论,在7月21日,发表在了 Arxiv.org,旨在根据批评的观点来强化理论结果。然而,大家还未达成共识。“当人们不能互相沟通的时候,还是非常郁闷的”Goldstein 说,“我们知道在原则上是对的,但是你认为这不可能。”

不过,荷兰的Utrecht大学的Gerard’t Hooft——他获得了1999年的诺贝尔物理学奖——说他俩的解决是合法的,不过他选择了确定性原理而非自由意志。“作为一个坚定的确定论者,我会说那没错,一个实验者选择去测量什么是从开天辟地那时就已经确定了,包括那些东西——他决定称之为光子——的属性,”’t Hooft说。“如果你坚信确定性原理,你必须自始至终坚信它。不能逃避。Conway和Kochen这里用一种优美的方式证明了对伪确定性原理的半心半意的信仰是无以为继的。”

关于Erlang和SMP的一些说明

原文:http://groups.google.com/group/erlang-questions/browse_thread/thread/7827f5e32681ca8e

by.Kenneth Erlang/OTP team, Ericsson
译:ShiningRay

以下是一些Erlang SMP实现的细节和与性能与伸缩性相关一些简单介绍。

几周之内还有有一个关于多核如何运作以及未来如何发展的更详细的介绍。我打算将一些内容放在我的报告中,将于9月27日的ICFP2008,Erlang Workshop在Victoria BC展示给大家。

没有SMP支持的Erlang VM只有1个运行在主处理线程中的调度器。该调度器从运行队列(run-queue)中取出可以运行的Erlang进程以及IO任务,而且因为只有一个线程访问他们所以无须锁定任何数据。

而带有SMP支持的Erlang VM可以有一个或多个调度器,每个运行在一个线程中。调度器从同一个公共运行队列中取出可运行的Erlang进程和IO任务。在SMP VM中所有的共享数据结构都会由锁进行保护,运行队列就是这样一个由锁保护的数据结构。

从OTP R12B开始,如果操作系统报告有多于1个的CPU(或者核心)VM的SMP版本会自动启动,并且根据CPU或者核心的数量启动同样数量的调度器。

你可以从“erl”命令打印出来的第一行看到它选择了哪些参数。例如:

默认值可以用“-smp [enable|disable|auto]”来替换,auto是默认的。如果smp被启用了(-smp enable),要设置调度器的数量可以使用“+S Number”其中Number是调度器的数量(1到1024)

注意1:运行多于CPU或核心总数的调度器不会有任何提升。

注意2:在某些操作系统中一个进程可使用的CPU或者核心的数量可以被限制。例如,在Linux中,命令“taskset”就可以实现这个功能。Erlang VM目前还只能探测CPU或者核心的总数,不会考虑“taskset”所设置的掩码。正因如此,例如可能会出现(已经出现过了)即使Erlang VM运行了4个调度器,也只使用了2个核心。OS会进行限制因为它要考虑“taskset”所设置的掩码。

每个Erlang VM的调度器都运行于一个OS线程上,是OS来决定线程是否执行在不同的核心上。一般来说OS会很好地处理这个问题并且会保证线程在执行期间运行于同一个核心上。

Erlang进程会被不同的调度器运行,因为他们是从一个公共运行队列中被取出,由首先可用的调度器运行。

性能和伸缩性

只有一个调度器的SMP VM要比非SMP的VM稍微慢那么一点点。SMP VM内部需要用到各种锁,不过只要不存在锁的争用,那么由锁引起的开销不会非常大(就是锁争用上面需要花时间)。这也解释了为何在某些情况下,运行多个只有一个调度器的SMP VM要比包含多个调度器的单一SMP VM更加高效。当然运行多个VM要求应用可以按照多个并行任务的方式运行并且之间没有或者几乎不通讯。

一个程序是否能在多核上的SMP VM中良好地进行提升很大程度上取决于程序的性质,某些程序可以保持线性提升至8核甚至16核,同时其他某些程序基本不能提升,连2核都不行。实际应用中很多程序都能在主流市场的核心数上得到提升,见下文。

若并行的持续“通话”由每个核心一个或多个Erlang进程来表示,实际的支持大量通话的电信产品已经先现出在双核和四核处理器上不俗的伸缩性。注意,这些产品是在SMP VM和多核处理器出现很久以前按照普通的Erlang风格来写的,他们也能无须任何修改甚至不需重新编译代码就能从Erlang SMP VM中获益。

SMP性能得到持续改进

SMP实现正被不断改进以便能得到更好的性能和伸缩性。在每个服务发布版R12B-1,2,3,4,5…,R13B等等中,你都能发现新的优化。

一些已知的瓶颈

单一的常见运行队列随着CPU或核心的数量的增加会成为一个显著的瓶颈。

这从4核开始往上就会显现出来,不过4核仍然可以为多数应用程序提供不错的性能。我们正在从事一个每个调度器一个运行队列的解决方法作为目前最重要的改进。

Ets表格会引入锁。在R12B-4之前在每次对一个ets-table的访问中会用到两个锁,但是在R12B-4中meta-table的锁被优化过,可以显著减少争用(前面已经提到争用是有很大代价的)。如果很多Erlang进程访问同一个表格,就会有很多锁争用造成性能降低尤其当这些进程主要工作是访问ets-table。锁存在于表级而非记录级。注意!这也会影响到Mnesia因为Mnesia用到了很多ets-table。

我们关于SMP的策略

当我们开始实现SMP VM的最初,我们就确定了策略:“首先让它可以运行,然后测量,然后优化”。自从2006年五月我们发布了第一个稳定的SMP VM(R11B)以来,我们一直遵循着这个策略。

还有更多已知的东西可以改进,我们会按照性能的收益大小先后各个击破。

我们将主要的精力放在多核(大于4)上更好的连续伸缩性上。

卓越典范

即使SMP系统有还有一些已知的瓶颈不过已经有不错的整体性能和伸缩性,同时我相信在让程序员利用多核机器事半功倍方面,我们是一个卓越的典范。

Slapp: 简易聊天墙的Merb教程

有问题或评价,请联系: socialface@gmail.com

程序截图: http://www.socialface.com/slapp/screenshot.jpg

简介

欢迎来到Slapp的教程。本文的主要目标是通过构建一个简易的聊天墙应用来介绍一下Merb微框架的主要组件。

本文其次的目标是成为最好的Merb开放教程并能不断更新。同时,我们希望本教程可以逐渐变得丰富来展现Merb框架的所有方面和开发方式。

许可

This tutorial is Copyright 2008 Social Corp. and is licensed under a Creative
Commons Attribution-Noncommercial 3.0 United States License, available at:

相关的源代码以MIT形式的开发源代码许可:

直接浏览代码:

也可以直接下载:

参与人员名单

本教程最初由来自#merb的Slurry撰写。Slurry在撰写本文时主要参照了Merb的官方文档[^a]以及#merb IRC频道的内容。

在学习Merb的RSpec的过程中,还咨询了John Hornbeck的Blerb[^b],参考了Tim Connor的“Isolate controller and view testing in merb”一文[^c]——虽然帮助很大,真正掌握Merb/Rspec还是得益于来自#merb的benburkert。

中文版由ShiningRay翻译

前期决定

设计上来说,Merb是一个ORM无关的框架。当然,这仅仅是表示有不同的模型层可以选择。对于Merb v0.9.2来说,包括:DataMapper、Sequel和ActiveRecord(来自Rails)。

尽管DataMapper和Sequel都是很好的选择,但现在关注Merb的主流人群基本上都是来自Rails背景;因此,我们在本教程中会继续使用ActiveRecord。

1.) 创建模型

在像Merb这样快速成长的社区中,一个问题可能只需更新或者重装Merb相关的包(gem)就能简单解决,所以让我们先花点时间这样准备一下:

完成了前两行命令后,Merb、ActiveRecord以及Rspec应该已经安装好或已经更新了,同时后两行命令则应该创建了一个初始的空Merb应用。

从这里开始,我们就要告诉Merb:我们要怎样用ActiveRecord,要使用哪个测试框架,以及需要载入哪些确切的merb_helpers包(如表单相关的东西)

编辑:slapp/config/init.rb 并取消第2742的注释,同时将:dependency "merb_helpers" 添加在
Merb::BootLoader.after_app_loads do 这一行之前:

现在我们已经告诉Merb要使用ActiveRecord: use_orm :activerecord, 让我们来生成第一个模型:

靠,居然没效。不过等等,你应该还有一个全新的slapp/config/database.yml.sample 文件等待配置,对吧?[^1]

好,现在你要做的就是将这个文件名字改成database.yml并在其中插入合适的数据库链接信息,像这样:

让我们重新运行上面的merb-gen命令,不过这次我们还要再加上一些Post的默认属性:

如果一切正常,那么第一个模型类就有了。让我们使用rake对数据库进行迁移,来加上表:

现在,Slapp应该有一个有效可用的Post模型了。为了以防万一,我们将介绍RSpec[^2].

2.) RSpec

RSpec——你可能已经知道了——是Test::Unit的另一种替代测试方案。与RSpec交互主要通过rake任务和spec命令:

或者,更加详细的:

(重要:这里使用的rake任务来自Merb前沿代码,应该很快会发布为标准包。在那之前,可以使用以下同等任务:
rake specs 以及 rake spec TASK=controllers/pages)

两个任务会运行slapp/spec/*中的任何代码,简洁起见,我们这里会使用rake。

让我们再次运行测试:

如你所见,Merb已经为我们创建了一个默认的测试,但是他还不能通过:

打开: slapp/spec/models/post_spec.rb 看看这个测试在哪里:

将该spec改成实用的代码,比如:

该测试看上去好像不多,但是它确实可以验证Merb、RSpec、ActiveRecord和我们的数据库都已经安装成功并工作正常。

同时,如果我们在此运行新的spec,数据库又出现一个问题。确切地说,我们忘记了创建slapp_test 数据库并将slapp_development的结构复制过去。

重新运行spec:

这时成功在向我们问候:1 example, 0 failures,这一行表示所有的spec都通过了。更重要的是,我们遇到并克服了实际使用RSpec的第一个问题。

3.) 控制器

虽然现在从技术上说我们没有控制器也能启动Merb,但是基本上作不了什么。事实上就是什么都作不了,我们还是先来创建一个控制器:

如你所见,Merb的控制器的命名方式是模型名(或者资源等)的名字复数化,且不使用Controller后缀。

看一下slapp/app/controllers/posts.rb,你应该看到我们新的Posts控制器里有一个默认的#index动作。另外,Merb还应该创建了一个新的spec: slapp/spec/controllers/posts_spec.rb ,里面的内容应该类似于:

让我们编辑:slapp/spec/controllers/posts_spec.rb,更改内容为:

指定控制器是比较直观的,在上面的例子中,我们描述了Posts控制器(从dispatch_to中返回的)有一个#index动作并且它能被外部世界成功调用(即,它返回一个HTTP 20x 代码)[^3].

我们想再次运行spec,但是由于我们没有添加任何模型或视图测试,所以让我们执行更加明确的“仅控制器”的rake任务:

成功的测试结果:

漂亮。现在我们已经有了一个可以工作的控制器和一个有效的模型——我们只缺一些好的视图了。

4.) The View from Above

前面当我们创建Posts控制器时,Merb同时也为#index动作创建了一个草图。位于:slapp/app/views/posts/index.html.erb,你也许会看看里面有什么,然而,让我们先暂时忽略这个视图,并回到控制器。

slapp/app/controllers/posts.rb 中,我们执行一个简单的 ActiveRecord #find
并在#index动作中将结果存储为一个实例变量:

然后我们确认该动作仍然是可以调用的:

就和Rails(或者其他Web框架)中一样,在控制器中创建的实例变量可以在对应的视图中调用。

在本案中,我们可以回到:slapp/app/views/posts/index.html.erb 并将临时文本替换成显示@posts变量内容的代码。

为了能保持模块化,我们将使用Merb #partial[^4] 功能来达到这个目的。

slapp/app/views/posts/index.html.erb 的内容替换成:

不看HTML,调用#partial应该还是比较容易理解的——
我们想多次渲染slapp/app/views/shared/_post.html.erb 视图来呈现@posts的内容。

现在创建: shared/ 目录以及: _post.html.erb 文件:

并编辑 slapp/app/views/shared/_post.html.erb 的内容为:

上面调用: partial("/shared/post", :with => @posts) 会反复传递一个单个post对象给视图_post.html.erb并渲染。

5.) 启动Merb

现在我们有了模型、控制器、以及一个视图,让我们启动Merb:

你应该看到了一个普通的欢迎页,再转到:

这时你应该看到slapp/app/views/posts/index.html.erb的内容了。当然,由于我们还未创建任何帖子,所以应该看不到任何东西。

让我们使用交互Merb会话(其实就是一个在Merb应用的内容中启动的IRB)修正上面的问题:

使用典型的 ActiveRecord #create 方法,我们现在创建了一个Post。重新载入
Posts#index 页面:

我们应该成功地看到了新的帖子。

6.) 特殊的视图

现在我们已经实现了视图,也许他们不会经常更改,让我们先描述他们。

首先,创建目录和spec文件:

然后将一下代码放入slapp/spec/views/posts/index_spec.rb

当使用RSpec描述对象时,常常需要在运行测试的前后维护测试特定的内容。毫不奇怪,RSpec向我们提供了#before#after块来实现这个目的。

回到代码中:我们在#before块中首先做的是从我们的 Posts 创建@controller实例。下面我们使用fake_request助手[^5]来模拟HTTP请求。

回想一下我们的视图:

我们知道我们还需要一组帖子来进行测试,碰巧,这就是#before块的第二和第三行所做的事情:

@controller.instancevariableset(:@posts, @posts)

这里,我们仅仅将插入了一组记录并保存为@controller@posts实例变量。记住,调用Posts控制器的#index动作和我们之前所做的没有什么区别,除了我们使用了一个ActiveRecord的#find,而不是手工使用#new创建帖子:

前面的Posts控制器Our Posts controller from earlier:

最后,#before的第四和最后一行渲染了视图并将响应的主体(本案中是HTML)放入了@body 实例变量。

(本质上来说,我们是使用fake_request来“查看”Posts#index动作。)

现在,我们的控制器已经设置好了,同时视图也渲染了,我们就开始列出我们对视图中应该有什么的预期。[^6]

首先,我们描述HTML里应该最外面有一个div来放每一个单独的帖子的div:

下面,我们断定容器div确实包含着帖子的div:

然后,我们进入每个帖子的div来验证内容准确地匹配对应的Post:

最后,我们使用#after块来删除在#before块中创建的帖子:

尽管我们还没真正完成,我们先来验证一下整个应用:

7.) 表单创建

有了浏览帖子的能力之后,现在就可以实现同样重要的创建帖子的功能了。

打开slapp/app/views/posts/index.html.erb并在帖子列表下面添加该表单[^7]:

几乎不言自明,我们是要构建一个简单的表单,有一个文本输入框和一个提交按钮。

你应该已经注意到我们已经将表单设置为递交到Posts控制器的#create动作。我们需要实现这个动作,不过在我们继续之前,我们先快速描述一下这个表单。

编辑: slapp/spec/views/posts/index_spec.rb并在#after 块上面添加一下内容:

和前面一样,我们使用#match_selector 来断言表单、正文输入框以及提交按钮的存在。唯一不同的是我们使用了一个基于HTML属性的选择器[^8]form[@action=/posts/create]

Run the specs:

我们可以再次启动Merb来亲眼检验一下新的表单了。不过,因为我们已经使用了RSpec,这步不是非常必须的,我们可以立刻继续往下。

说道RSpec,这次,当我们在要去完成#create动作的时候,我们应该在写代码之前先写出该步骤的spec。

将以下内容复制到 slapp/spec/controllers/posts_spec.rb

这里,在#before块中,我们准备了一个只有一个:body键的@params表以便开始描述#create动作 。下面,我们列出了第一个预期:create动作在它成功创建一个帖子之后应该重定向到#index动作。

(这就是如果一个浏览器通过表单提交了某些信息,在我们的应用中能看到的情况。)

我们用一个lambda表达式检验一个帖子是不是被创建了,其中RSpec会调用两次:先执行一次,然后等相关的{...}块执行完之后再执行另一次。

这两次中,RSpec都会调用Post.count,如果两次调用返回不同的值,那么我们就能确信Post被创建了,那么这个块(该动作)就是成功的。

因为我们还没有编码#create,所以spec显然会失败:

切换到: slapp/app/controllers/posts.rb并再次复制以下内容:

现在我们定义了#create,现在回到spec文件,应该就可以通过了:

通过了这些spec,我们就有了一个可以正常工作的聊天墙了。

听起来是个好消息,我们也几乎就要完成了。我们还需要做得就是检验创建一个新的帖子需要一定的文本,否则则会出现一个异常。

8.) 收尾

现在,任何都可以点击 “Post Message!” 然后创建一个新的帖子。因为我们并不想让一堆空白的帖子占据聊天墙,所以我们应该在创建新帖子之前校验至少有一些文本被提交了。

因为本文是一个教程,我们不打算将所有东西都仔细进行合适的处理,所以这里我们直接使用ActiveRecord的validates_length_of过滤器。 ;-)

打开: slapp/spec/models/post_spec.rb 并观察我们现有的spec:

因为我们要校验正文文本的存在,这个spe已经不再有效,我们需要如下的内容来替代:

和平时一样,我们首先运行失败的spec来建立我们对于特定行为的预期:

然后实现上面提出的修正,在本案中,则是在slapp/app/models/post.rb中加入一样:

再次运行spec来检验我们的修正是否有效:

这时我们的模型完成了。让我们继续给Posts控制器添加一个spec——
我们需要描写当提交一个帖子没有包含正文的时候,我们没有真正去处理这个错误而已直接返回由我们的ORM抛出的异常。

编辑slapp/spec/controllers/posts_spec.rb并加入下面这个spec:

如你所见,我们仅仅是不带@params表来调用#create。这创建一个没有正文的空Post,这样就会导致ActiveRecord的校验失败,并抛出我们预期的RecordInvalid异常。

有了这个,我们的第一个聊天墙的版本就完成了。就和前面一样,你可以通过启动Merb并浏览Posts#index动作来试试程序:

最后的思考

从这里开始,你可能还有很多东西想添加,例如:分页、动态发布/更新、SPAM过滤器、文本格式化等等。

这些对于任何优秀的聊天程序都是很基本的,你都可以利用Merb来实现。

同时,别忘了浏览官方的项目首页看看有没有最新的更新、看看别人的版本甚至创建属于你自己的:

有任何问题/评价,请致电socialface@gmail.com或者在#merb找 Slurry

脚注

[^1]: The observant may have also just found their first “Merb” bug.. the
generator claimed to have made a “database.sample.yml” file, although the file
is really named “database.yml.sample”. :-)

[^2]: RSpec links in order of approximate handyness to the beginner:

[^3]: Merb RSpec controller matchers:

[^4]: Merb partials:

[^5]: Merb fake_request helper:

[^6]: Merb RSpec view matchers:

[^7]: Merb Form Helpers:

[^8]: Hpricot CSS Selectors: