Tag Archives: rails

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插件十分简单,只需以下几个步骤:

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

$ piston import http://rails-fast-sessions.googlecode.com/svn/trunk/ vendor/plugins/fast_sessions

在config/environment.rb文件中启用ActiveRecord会话仓库:

Rails::Initializer.run do |config|
……
config.action_controller.session_store = :active_record_store
……
end

为新的会话表创建数据库迁移:

$ ./script/generate fast_session_migration AddFastSessions

如果需要,可以打开新创建的迁移脚本并更改表名table_name和插件use_auto_increment参数。
运行数据库迁移:

$ rake db:migrate

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

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

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 参数。

关于《浅析Rails部署》

我之前写的文章《浅析Ruby on Rails部署方案》受到不少同学的关注,在此首先感谢大家。
但是也有同学对此提出了一些疑问,我经过检查,发现文章确实存在很多漏洞和不足:

Lighttpd作为负载均衡反向代理时,无论是链接FastCGI还是HTTP后端,KeepAlive链接默认都是关闭的
Nginx的FastCGI模式,默认也是关闭持久链接的
缺少了一些重要的前后端搭配的方式
对于Rails应用的内存占用也应该考虑在内

我会针对这一些问题,重新设计测试案例,并重写该文章。再次感谢大家的关注。

浅析Ruby on Rails部署方案

2006初,我接到了公司分配的一个遗留项目,让我负责一个基于C/S的系统的服务器端。其实是系统是基于HTTP协议的,因为负责客户端的同事对于服务器端编程不甚了解,虽然使用PHP对熟悉C++的他来说是驾轻就熟,但是在进一步实现更多的功能和更高的性能上就捉襟见肘了。项目是在非常突然的情况下交给我的,因为该同事在客户端上有更多的事情要做。我在分析了他的数据库结构和PHP源代码之后,决定按照与客户端的通讯协议重写他的服务器端。为了能应付老板苛刻的时间限制,我打算使用正在学习的Ruby on Rails。后来,项目在功能上非常顺利地交付了。
两年过去了,随着客户端数量的不断增加、客户端功能的增加、与服务器端交互数据的增加、老板对功能的要求不断增加,我在这个项目上走了不少弯路,尤其是在部署——或者说是架构——方面。
我遇到的最大的问题就在于并发链接数上。服务器与客户端的每次交互的数据量并不大,但内容无法缓存。起初用的是Nginx/Apache+Mongrel 的部署方式,但当遇到大量并发请求时,常常会遇到Mongrel进程死掉的情况。而客户端的用户在无法登录客户端的时候,经常会反复尝试,加重了服务器的负担、导致最后所有的Mongrel进程都挂掉。
最后,经过不懈努力,在现有的3台低端服务器上,可以满足每天500万次的请求。在这里,我将我的一些心得和研究成果总结出来,与大家分享。

文档地址:http://docs.google.com/Doc?id=ddcvzh74_28f9xppqfh
文章发布在Google Docs上,欢迎大家在其上做批注,有意见和建议也可以在此留言。文章按照知识共享“署名 3.0 中国大陆”许可协议发布,欢迎转载。

Passenger (mod_rails)介绍

Passenger是一个用于Apache2的模块,用它可以方便、高效地部署Rails应用程序。但目前它还只能用于*nix的操作系统。
Passenger的安装十分简单,在装好了Ruby,有Rubygems的系统中输入:
gem install passenger
等待安装完成,然后输入:
passenger-install-apache2-module
然后安装程序便会自动查找编译需要的包(g++、Apache、APR、Rake)并进行编译,如果没有找到相应的包,安装程序还会根据操作系统告诉你要执行哪些操作来安装那些包。安装程序完成编译后,它会给出Apache配置中的指令,只需复制到Apache的配置文件中即可。
当Apache加载了Passenger模块后,Passenger会自动检测每个虚拟主机(VirtualHost)的文档根目录(DocumentRoot)是否是一个合格的Rails应用,如果是,那么它就会自动启动Rails的运行时(这个自动检测可以用RailsAutoDetect off指令来关闭)。
其他的指令和问题可以查看用户手册。
Passenger的运行机制和Apache的FastCGI比较类似,可以根据请求的数量动态产生Rails的运行时并接受请求,用户可以自己设定产生运行时的最大数量(RailsMaxPoolSize)。
总体上来说,Passenger的部署是所有Rails部署方式中最简单的,而且支持自动产生运行时实例可以解决很多Mongrel方式的并发问题,相信前途是一片光明。如果能直接进入Ubuntu、Gentoo、CentOS、Rpmforge等软件包仓库,那么还能进一步简化他的安装。唯一的缺憾便是它不支持Windows。

Rails中取消asset id

在Rails中使用helper链接应用中的JavaScript和CSS文件的时候,会在文件的路径后面添加一个问号和一个数字,如:

<link href=”/stylesheets/application.css?1200155369″ media=”all” rel=”stylesheet” type=”text/css” />

问号后面的数字被称为asset id,目的是为了一些容易被缓存的目标如JavaScript和CSS文件,在文件被更改后不会被浏览器或代理服务器强制使用缓存,这也用于某些Ajax请求中。
但是这种asset id的行为比较怪异,他是一个查询字符串,但是没有键值对。我的一个部署方式是在一个已经架设了Wordpress的域名下,使用反向代理向用户暴露某个控制器。因为给Wordpress配置了mod_rewrite,并启用了.htaccess,将所有不存在的文件重写到wordpress的脚本处理。而这时我使用了Alias来将Rails的public目录下的javascripts和stylesheets目录绑定到相应的URL路径下,Apache却在处理这个asset id上有些问题,直接访问/javascripts/prototype.js可以得到,但/javascripts/prototype.js?11223344却会跑到WordPress中,并且说这个文件不存在。这可能和Apache处理URL的次序有关。当然,将DocumentRoot配置到Rails的Public目录,则不会出现这个问题。
无论如何,我要去掉这个asset id,根据Rails 2.0的代码,大家可以在actionpack/lib/action_view/helpers/asset_tag_helper.rb中找到生成asset id的方法rails_asset_id,如下:

# Use the RAILS_ASSET_ID environment variable or the source’s
# modification time as its cache-busting asset id.
def rails_asset_id(source)
[...]

Redmine

Redmine是基于Ruby/Rails的一个项目管理软件。比较类似的则是基于Python的Trac,相比之下,Redmine有很多优势:

简单的安装、配置和部署Redmine利用rake、rails的db migration安装很方便,Trac则要用到命令行的trac-admin进行配置,以及每个项目有单独的ini配置文件
方便的用户和权限管理Redmine支持多LDAP认证、还支持用户自己注册,然后通过邮件激活。Trac依然需要使用trac-admin来配置,而且默认的用户登录方式是HTTP验证,基于cookie的还需通过插件实现
基于Web的多项目管理Trac创建和配置新项目需要使用trac-admin
AjaxRedmine通过Ajax在某些方面提供了更好的用户体验,如代码仓库的浏览
多语言包括简体中文
多种SCM包括SVN、CVS、Darcs、Mercurial、Bazaar,是通过调用它们的可执行文件来实现的。

trac的很多功能都需要通过trac-admin在命令行方式下进行配置,不易上手,这方面Redmine则十分方便。
其实功能方面,Redmine更多地是模仿Sourceforge的功能,比如新闻、文档、下载等,目的是建立这种适合团队协作的平台。我估计以后Rubyforge可能会迁移到Redmine上去。

Linux 上配置 Nginx + Mongrel cluster

Nginx不仅是一个小巧且高效的HTTP服务器,也可以做一个高效的负载均衡反向代理,通过它接受用户的请求并分发到多个Mongrel进程可以极大提高Rails应用的并发能力。下面介绍一下如何在一台服务器上配置Nginx + Mongrel cluster。
获得Nginx方法可以参考前一篇配置Nginx+PHP5 FastCGI的文章,这里我们假设大家是通过自己编译,并配置了默认的编译的参数,此处使用的是Nginx 0.5.x版。
配置Mongrel cluster
我们还需要获得Mongrel和其Cluster插件(用来方便得启动多个Mongrel进程),如下通过gem进行安装:
gem i -y mongrel mongrel_cluster

然后建立mongrel_cluster的配置文件。进入Rails应用即你的程序的根部目录(以下假设/usr/rails),运行:
mongrel_rails cluster::configure

然后mongrel_cluster便会在config目录下生成一个mongrel_cluster.yml,内容如下:

log_file: log/mongrel.log
port: 3000
pid_file: tmp/pids/mongrel.pid
servers: 2

我们可以通过修改其中的设置来更改mongrel_cluster的运行,这个范例配置省略了一些其他参数,具体的参数的含义如下:

address: 指定绑定的地址
port: 指定mongrel_cluster所运行的mongrel进程从哪个端口开始绑定
servers: 指定同时运行多少个mongrel进程,结合port参数,就是表示port到port+servers-1(含)的端口将被使用
environment: 指定Rails运行的配置环境
user: 指定mongrel进程以什么用户的身份运行
group: 指定mongrel进程以什么组的身份运行
cwd: 指定mongrel运行的根目录
log_file: 各个mongrel进程的输出日志的位置,相对于cwd的目录,会在文件的扩展名之前加上各进程对应的端口号
pid_file: 各个mongrel进程的pid文件的位置,相对于cwd的目录,会在文件的扩展名之前加上各进程对应的端口号

大家可以根据自己的具体情况进行修改。以下是一个完整的mongrel_cluster.yml配置文件:

user: apache
cwd: /usr/rails/
log_file: log/mongrel.log
port: 3000
environment: production
group: apache
address: 127.0.0.1
pid_file: tmp/pids/mongrel.pid
servers: 5

接下来便可以启动mongrel_cluster了,以下是控制mongrel_cluster的命令:
mongrel_rails cluster::start #启动
mongrel_rails cluster::restart #重启
mongrel_rails cluster::stop #停止
配置Nginx负载均衡反向代理
利用nginx的upstream指令配置哪些服务器需要进行负载均衡。在这里也可以说直接说告诉nginx mongrel_cluster在哪些地址和端口上,按照上面的mongrel_cluster的配置,在nginx中应该这样写:


http{

#upstream段要放在http段中
upstream mongrel {
[...]

Rails + Memcached = Undefined Class/Module?

问题描述:
当使用memcached并将Model对象保存在其中时,若要取出这个缓存对象时,可能会找不到Model的类,并抛出“Undefined Class/Module SomeClass”的错误。
例如:
if not (genres = Cache.get(key))
genres = Genre.find(:all, :condition => “platform_id = 1″)
Cache.put(key, genres, 60*60*24) # cache for 1 day
end
解决方案
要解决这个问题,可以在引用到该对象之间,先引用其类。比如,在前面的代码前面加入要引用的类Genre:

Genre
if not (genres = Cache.get(key))
genres = Genre.find(:all, :condition => “platform_id = 1″)
Cache.put(key, genres, 60*60*24) # cache for 1 day
end
还有更好地方法是在Controller的before_filter中加载所依赖的所有Model:
before_filter :preload_models
def preload_models
Model1
Model2
[...]

Rails 2.0: It’s done!

原文:http://weblog.rubyonrails.org/2007/12/7/rails-2-0-it-s-done
但是,目前仍应该将所有的必须的gems(除了Mongrel)freeze到正在维护的Rails应用中。请运行:
rake rals:freeze:gems