关于 Ruby / Rails 的线程模型

inu 的项目中有一个导入功能,将用户从浏览器、del.icio.us 导出的收藏条目导入到 inu 收藏夹中。这个功能推出以来,用户的反响并不好,其主要原因在于:速度慢,考验用户的耐心。

速度慢的问题,根本原因在于 model 层需要做的工作非常多,也是目前不完善的架构以及比较特殊的需求导致的,可以说不能从根本上解决。每次导入一条记录,都需要更新好几个表,本身 Ruby 在目前虚拟机下效率并不高,所以导入的速度并不理想。

那么,退而求其次,可以设置一个直观的进度条来告诉用户目前导入的进度,可以在很大程度上缓解用户的焦虑、不信任的心理。

解决方案是确定了,可是问题由此而来:Ruby 是单线程的。

严格来说,应该是目前的虚拟机还不支持真正多线程的 Ruby 应用。虽然 Ruby 中确实有 Thread 类 ,但是查阅相关文档可以知道,这个类是一个伪 Thread,不同于真正的多线程。如果通过 join 将子线程挂到当前的主线程中(某个 mongrel 进程),那么该线程在结束之前会等待所有的子线程结束。通常情况下这种机制可以模拟出和其他语言相类似的线程模型。但是在 Rails 中,有新的问题产生:无论是 FastCGI 还是 Mongrel ,都是一个请求独占一个进程,每次请求都会创建新的 Controller 实例,请求结束后该实例销毁。也就是说,在 Controller 内无论是不使用线程、或是把子线程挂到主线程上,在所有操作结束之前,当前进程一直处于 block 状态。

很可怕!

通常用 Thread 是在不影响主线程的情况下处理耗时很多的操作。但是这种机制明显不能应付这种需求。一台服务器上 mongrel 数量有限,都被阻塞了,相当于服务器在这段时间内不能接收其他的请求。

那么是否让 Thread 单独执行就可以了呢?答案也是否定的。在单独的线程中,Model 类将不再是 Model。其实意思是:线程中的 ActiveRecord 类不再具有标表间关联(目前我测试出来就这么多,可能还会失去更多东西),只是一个普通的实体类。对于这样的导入,无疑就是毁灭性的。

至此,可以确定,这个应用不可避免的要阻塞一个 mongrel 了。那么如果要反应当前导入的进度,就是要发起一个新的请求,查询导入状态。如果这个请求发送到 Rails ,意味着用户的导入要占用 2 个 mongrel ,服务器不可能有这么多的资源。

最终我采取了一种折中的方案,导入时创建一个可访问的静态资源,查询进度的功能由这个静态资源来完成,而 Web 应用对于静态资源的访问则由 Apache 来完成。

至此,问题算是解决了,虽然算不上完美。期望 Ruby 的虚拟机能够尽快支持 Multi-Thread 吧!

学习笔记

今天在处理一个样式上的问题,需要在 IE 和 Firefox 浏览器下应用不同的样式,CSS Hack,使用了 css2 的选择器。

在搜索资料的时候,发现了这个站 http://www.carvetime.net/article,有相当丰富的技术档案可供参考,忍不住就收藏起来了。

每次学习都会有一个小结,这次也不例外,总结如下:

滥用 div 之前

自以为看过相当多的关于 web 设计标准、w3c 的文档,总以为足够了。这次发现还是有一些错误的做法,比如滥用 div。

div 的语义是“分割、区分、部分"(division),用于将页面中相关的内容划分所属块。但是有个前提:在使用其他标签不能区分这部分内容的时候,才使用 div 标签。

比如在 ul 标签外面套一个 div ,就是无意义的做法。

css2 选择器

有时候为了兼容 ie 与其他符合 css2 规范的浏览器,需要表现出不同的样式。一般情况下可以配合 JavaScript 脚本来判断浏览器类型。但是,大部分时候这种判断都是不涉及浏览器的具体类型的(例如:对于 Firefox 1.5、IE、Opera 7 要有不同的样式),这些情况下使用 CSS2 选择器来实现是最优雅的做法,不用增加额外的脚本块,也不会增加太多浏览器的负担。

rel="nofollow"

a 标签具有一个 rel 的属性,表示该链接的目标地址指向的资源与该页面的关系,属性的值有 external 等等,相当多。为何只提到 nofollow 这个属性值?通过搜索关键字 "nofollow seo" 等可以得到结论:如果一个链接的 rel 属性设置为 nofollow ,搜索引擎在对本页面进行评价的时候,不会将该链接指向的目标站点的权重考虑在内。根据这一点,可以避免因为引用了一个未知的、恶劣的站点,导致本站在搜索引擎中的评价降低、甚至受到惩罚。这一点在 seo 的过程当中我个人觉得可以注意一下。

inu.cc 开发日志

最近几次更新,发现了项目中一点小问题,算是对 rails 框架理解不够深刻导致的吧。blog 下来,方便以后提醒自己。

check_box 方法会生成一个 type="checkbox" 的选择框。通常情况下,不选中这个框将不会提交相关的数据,controller 中取得该值为 nil,其实从逻辑上说,不选择表示 false ,而不是 nil 。因此,rails 选择的做法是添加一个同名的 type="hidden" 的隐藏表单,值为 0 。一般来说这种做法能够保证 controller 中取得的值只有 true 和 false 两种情况,但是,有一种情况例外:method="get" 。应该算是 rails 没有对这个方法实现完整,只针对 post 的情况额外处理了 checkbox 的这种特例(两个同名的 field 将会提交一个数组)。解决的办法只有在 controller 取值的时候用 ||= 操作符另外赋值,或者重写 request.params 方法以识别这种情况。

第二个问题是数据库表字段的默认值。对于“默认值”的概念经常会产生一种混淆:没有值就应该用默认值。其实不是这样的。如果一个字段允许 NULL ,默认值为 0 ,如果在 SQL 语句中显式声明插入一个 null 值,数据库是不会使用默认值的。只有在没有显示为该字段指明值的时候才会使用默认值,提供空值并不属于这种情况。

学习笔记

最近遇到一些 JavaScript 的问题,在学习过程中发现了一些技巧,记录一下

Element.appendChild 方法

DOM对象的 appendChild 方法一般用于向一个容器添加一个对象作为他的 child。通常情况是如此,但是如果这个对象原来就是这个容器的子节点,结果会怎么样?该对象从原位置 remove,移动到容器最后一个子节点之后。
应用:滚动新闻,原本烦琐的滚动判断现在只要一行代码……

event 时间对象

对于 IE 浏览器,所有事件的触发都会更新 window.event 对象,但是对于其他浏览器则不是,那么如何在方法里取得 event 对象?
绑定一个方法到一个事件以后,传递的参数除了签名中参数列表那些之外,第一个隐含参数就是 event,可以通过 arguments[0] 来得到。不过只适用与在运行期绑定的事件( document.body.onload = functionName();)。
应用:取得 event 对象

event 事件源

对于 IE 浏览器,可以使用 event.srcElement ;对于 Firefox 则是 event.target。其他浏览器未测试

caller

得到当前函数的调用者的引用

inu.cc 开发日志

这个礼拜的开发基本上都以 JavaScript 为主,实现一个类似于 bluedot.us 的收藏对话框功能。因为要在未知的页面中插入 JavaScript ,所以还是碰到了很多问题。包括 JavaScript 的字符编码、各个页面不同的 DOCTYPE 声明导致的盒模型解析问题、从 inu.cc 载入脚本、跨域提交、callbacks 等等,都比想象的复杂的多。

特别是跨域提交的问题,至今仍然没有搞清除 bluedot.us 是怎么实现的,不得不佩服他们的技术实力。

当然,在这个过程当中也学会了一些技巧,以后写 js 的时候肯定会派上用场的