June 17th, 2010 by 张磊
在Twitter上看到盛大举办的校园牛人大赛,其中有“云计算脚本比赛”。看了下题目,开始悔恨大学里没好好学编译原理。但又手痒,就想用Racc试试。做题过程居然很顺利,从学习Racc到做完前三道题花了两三个小时,于是写一点有关Racc的示例,算是学习笔记。 以“云计算脚本比赛”第一题为例,要求执行如下脚本后,得到“Hello, 老赵”。 SET name = “老赵” RETURN “Hello, ” + name 简单来说,用Racc写成解释器,可以把上面的脚本,转化为Ruby代码并执行。 安装Racc: gem install racc 创建一个level_1.y文件,定义一个JeffParser类。 class JeffParser token T_SET T_RETURN T_STRING T_VAR rule lines: | lines T_SET T_VAR ‘=’ T_STRING {@local_vars[val[2]] = val[4] } | lines T_RETURN exp {result = val[2]} exp: exp ‘+’ exp {result = val[0] + val[2] } | [...]
February 11th, 2010 by 张磊
我在一个Rails应用中使用了sphinx做全文检索(用的是coreseek提供的版本),但在搜索一些词(比如“环境”)时,Rails日志中会有“comparison of Fixnum with nil failed”的错误,Sphinx的query.log/sphinx.log却无任何记录。此问困扰了我很久,也没搜到什么解决方案。今天最终解决了,于是把查找问题的过程,写在这里。 一、问题初析 仅从Rails的报错信息来看,是将一个数字与nil作比较,导致ruby报错(谁再把脚本语言和弱类型语言混在一起我跟他急)。出错的代码在ultrasphinx-1.11/vendor/riddle/lib/riddle/client.rb的433行: 430 header = socket.recv(8) 431 status, version, length = header.unpack(‘n2N’) 432 433 while response.length < length 434 part = socket.recv(length – response.length) 435 response << part if part 436 end 经验证,length是nil导致ruby报错,而length是从431行的header.unpack得到的。进一步debug发现,header是一个空串,也就是说,430行的socket.recv根本没有从服务器获得响应。 最初想得很简单,在header之外用了一个死循环。只要header为空,就继续调用socket.recv。没想到这直接导致ruby进程陷入死循环,也就是说,对某些query,根本无法收到服务器的响应。 二、tcpdump未果 ruby不停报错,于是想到抓包,看看究竟是哪里出问题了。 祭出tcpdump,自己功力不深,感觉dump出的内容极其晦涩,根本无助于定位问题。 三、初试gdb 仔细看了ruby代码后,感觉Riddle中使用的Socket,是对C语言中Socket操作的简单封装,出问题的可能性不大。因此我把目光放在了sphinx的后台服务searchd上面。 服务器上还留有编译时的C++代码,选了几个看看,没能理出头绪。开了gdb也实在不知该从何处下断点。 到此时,我已经到了放弃的边缘。我一直觉得,是自己对Socket不熟悉于是不能深入地追踪此问题。直到想到去查一下dmesg。 四、从coredump找到线索 dmesg是从一同事处学来的,没想到今天帮我了大忙。服务器的dmesg显示,searchd进程出现了segfault: searchd[16818]: segfault at 0000000000000000 rip 000000000047242c [...]
February 8th, 2010 by 张磊
最近我的Linode服务器硬盘吃紧,一个16G的分区,使用率达到99%,就快无立锥之地了。某个Rails应用,生成的静态文件占用了超过9G的空间,并且还在不断增长。最初没想到好办法,想,难道要被逼升级服务器?后来联想到WP-SuperCache的原理,计上心来:用GZip压缩这些静态文件。 压缩文件做缓存有什么好处? 文本压缩的比例够大,这样做节约 压缩好之后,无需Web服务器再做压缩 那,会有什么问题? 不支持GZip的客户端,只能看到一堆乱码。据我观察,YSlurp疑似不支持GZip。 CPU的开销会增加 第一个问题也可以通过web服务器的一些逻辑判断,给不支持GZip的客户端返回未经压缩的内容(WP-SuperCache就是这么做的)——但个人感觉意义不大,大部分客户端都支持GZip,Google的SPDY(自备翻墙工具)都强制压缩强制SSL了。对第二个问题,VPS上主要是内存不够,CPU资源相对富裕。 在Rails中,做到这点非常轻松。原Controller中代码大致如下,无需改动这些代码,即可把静态页面缓存改为GZip格式。 cache_pages函数原型在action_pack/lib/action_controller/caching/pages.rb 中,大致的调用顺序为: 其中,class.cache_page函数进行了写入文件的操作,写入的位置是由class.page_cache_path决定的。看到这里,难道要撸起袖子hack Rails框架?No,只要在ApplicationController中加入几行代码,覆盖这两个函数即可做到。 def self.cache_page content, path # 若目录不存在,则创建,以免写入时报错 FileUtils.makedirs(File.dirname(page_cache_path(path))) Zlib::GzipWriter.open(page_cache_path(path)) do |gz| gz.write content end end # 使用super可以调用父类的同名方法 def self.page_cache_path path super + “.gz” end 将上面代码加入后,生成的静态缓存,就变成.gz后缀的压缩文件了。此外,Ruby的super函数真是一个很棒的玩意。 但这仅做了一半,我们还需要配置WEB服务器以支持直接发送GZip压缩文件。对Apache来说,可以大部分照搬WP-SuperCache的Rewrite规则;对nginx来说,还需要写一些配置。下一篇文章,将谈谈在nginx中如何做配置以支持直接发送GZip文件。
January 22nd, 2009 by 张磊
我曾经写过两篇关于和web开发中处理图片有关的文章,一篇是在PHP中如何裁剪图片,对比了ECShop/Babel这两套系统的做法,侧重于对图片本身的操作;另一篇是在rails项目中如何上传图片,侧重于上传并保存的策略,即使上传的是pdf文档,也一样适用。 最近我手头的项目里需要提供用户上传图片作为头像的功能,于是需要有一些后端的处理过程,比如裁剪、缩放。项目基于Rails框架,所以得找ruby上的实现。有人会推荐用attachment_fu/file_column这些插件,都显得太麻烦,我要的只是用户上传之后,经过适当裁剪,然后缩放到想要的尺寸。仅此而已。花了半天时间来研究,中文的参考资料有限,我把我的收获写下来,希望能对大家有帮助。 原来想用RMagick,但据说内存泄露的问题比较厉害,作为替代品MiniMagick不存在内存泄露的问题。而二者都是使用ImageMagick的,所以需要下载并安装ImageMagick。之后使用”gem install mini_magick“安装好MiniMagick,基础设施就搭建好了。 在开始之前,先思考一下,用于做用户头像的图片应该如何裁剪?我的考虑是所有用户采用同样的尺寸,比如48×48。但用户上传的图片往往不是正方形的,如果直接缩小到正方形,人像必然会变形。所以需要先切除一部分,把图片切成正方形,然后再缩小到想要的尺寸。 上面是原始图片(尺寸为2048×1536),先把它变成正方形。我能想到的最好办法就是在左右两边各截掉一部分,使处理后宽度和高度相等,这样能留下中间主体部分。如图(阴影部分为裁去部分,示意图不保证精确): 对于高度大于宽度的图片这招一样适用。得到中间的部分再进行缩放,就可以保证图片不失真。下面看看具体实现。 MiniMagick中Image对象有一个shave方法,正好可以满足这个需求。在irb中运行: require ‘mini_magick’ img = MiniMagick::Image.from_file “1.jpg” #取得宽度和高度 w,h = img[:width],img[:height] #=> [2048, 1536] shaved_off = ((w-h)/2).round #=> 256 img.shave “#{shaved_off}x0″ #此处表示宽度上左右各截取256个像素,高度上截取0像素 img.write “2.jpg” 在使用shave方法处理后,图片已经是正方形了(像素数1536×1536)。此时保存得到的2.jpg如图: 之后想得到不同尺寸的缩略图,只消调用resize方法。 img.resize 100 img.write “3.jpg” 得到的3.jpg如下图: 当然,想要其他尺寸也可以多次调用resize。这个示例只是在irb中操作,在实际的项目中,应该先判断一下宽度和高度哪个大,决定从哪个维度上截取,也需要考虑一下存储策略。但总体来说,只要调用一次shave,调用一次resize,就能达到目的。 参考:http://atomgiant.com/2006/07/19/resizing-images-with-minimagick/
June 3rd, 2008 by 张磊
晚上睡前在robbin的blog翻看,看到以前没多留意的一篇关系模型和对象模型方面的文章。文章是从一本《MySQL 5 权威指南》说起的,我也读过这本书,确实,对其中三大范氏的描述很感兴趣(比我们在课上讲得深入多了),但没有像robbin思考的这么深入。 摘录部分robbin的文章: 关系模型和对象模型存在概念上的阻抗不匹配,但是在关系数据库的存储模型上是一致的,无论你从关系模型的三大范式理论出发,还是从对象模型的ORM理论出发,最终一定会得到一致的数据库表设计。 嗯,这个容易理解,但做数据库设计的时候,难免会犯这样的毛病: 传统的数据库应用软件开发,程序员很难从符合三大范式的数据模型当中获得有效的查询性能。符合三大范式就意味着数据库表会拆分的很细,表间关联很多,统计 分析查询就不可避免的导致n张表的联合查询,在没有有效的应用层缓存的情况下,这种查询无可避免的性能低下。这使得程序员宁肯违背三大范式,而选择查询性 能优先的数据库设计。 但下面这群数据,让人惊讶: JavaEye使用了Rails的ActiveRecord ORM,表设计符合三大范式,所有页面都是动态页面,要对数据库发送大量查询,很多Web页面至少要向数据库发送50条以上的SQL语句。根据对数据库和 Memcached Server的统计数据表明:JavaEye网站平均每秒向数据库发送140条SQL语句,平均每秒向Memcached Server发送250次缓存查询,缓存命中率大概为85%,也就是说缓存服务器要比数据库服务器繁忙将近一倍,而Ruby应用程序的数据有60%是来自 Memcached Server,而只有40%是直接来自MySQL的。 WP每个页面查询几十次数据库,我自己都有点心疼服务器,这么看来是符合了三范氏设计的结果。WP如果可以出来类似rails中的缓存机制就好了(貌似Rails中的缓存,也不是那么完善)。上面的数据很给我信心,规范的设计+适当的缓存策略,应该可以获得好的性能。 最后,我的结论就是对象模型和关系模型在数据库存储上不存在阻抗不匹配,面向对象的程序设计和面向数据库的程序设计应该是一致的,而不应该是对立和冲突的,请不要把面向对象和面向数据库对立起来,不是他们对立,而是你不了解什么才是真正良好的设计。 UPDATE:这些东西,也只有在真正有过实践之后,才能有体会。若是没有实践,推测是推不出来的。
April 24th, 2008 by 张磊
看到Fenng说今天(or 昨天?)是世界读书日,正好最近都打算写写自己看的书。内容比较多,所以打算分2、3天来写。今天先写写技术方面我最近读的一些书,明天打算写一下其他方面的书。 最近颇为关注Ruby和Rails,学得也比以前更深入。 Ferret 这本书是讲ferret的,是一个ruby下面Lucene的移植(现在的数据已经不和lucene兼容了)。国内没有Ferret影印版或者中文版,我看的是英文版CHM电子书,有兴趣的朋友可以搜一下。这本书是ferret的作者写的,所以谈得很不错。不过要用ferret来索引中文,分词方面推荐使用浙大pluskid写的Rmmseg。PS:看到pluskid谈到学软件工程,没准和我一样是05级的呢。 Ruby Cookbook (影印版) (中文版) 以前推荐过第二版的Web开发敏捷之道 ,不过这本ruby cookbook更侧重谈ruby。我想肯定有这样的人,在开始照着别的书书写基于rails的程序,自己却不知道为什么要这么些。从这本Ruby Cookbook中不难发现,ruby很多时候都是轻快而便捷。书中的一些示例也很不错,比如用贝叶斯过滤器进行文本分类(这个地方需要说一下,可能对中文来说得搭配相应分词器),比如创建PDF、编辑图片。 Rails Cookbook (影印版) (中文版) 两本都是cookbook,不过这本当然是以rails为主。和 Web开发敏捷之道不同的是,这本书并没有一开始花大篇幅做例子。可能已经不需要用示例来说明rails是多么激动人心了 。我关注这本书里较多的是restful development 、performance 和 rails的部署,特别是部署这部分。我没有什么经验,而rails的部署方式也太多了(apache基本被淘汰,后端用Fastcgi或是mongrel争论不休)。下个月可能会做好手里的第一个rails项目,正好用到书里面的知识。 Ruby for Rails 这本在卓越亚马逊只找到了中文版,算是比较早的谈ruby的书。厚度上没有两本cookbooks厚,但是读起来感觉不错。比起ruby cookbook谈了更多rails,比起rails cookbook谈了更多ruby 打算读的书: 算法导论 虽然以前也在校ACM集训队呆过,但总觉得自己在算法方面还不够好。说实话现在很多时候算法已经被数据库这些东西给解决了,可还是需要自己有所了解。我学东西一向的思路是明白道理就好,真正用的时候还可以查。
February 18th, 2008 by 张磊
早就想写的,不过今天才有点时间。最近多线繁忙,好不充实。 在Project Lucas中需要做图片上传的功能,参考了《应用rails进行敏捷web开发》这本书,果然就成功了。我写了一个详细的例子在这里:http://www.googlecto.com/2008/02/18/how-to-upload-pictures-in-rails/
January 29th, 2008 by 张磊
最近在用rails做Lucas,遇到解析JSON的问题。在变化飞快的领域,能找到的文档、资料,一不小心就过时了。最先找到一个Geek自己写的解析,复杂的正则表达式一下就把我弄晕了。最牛的是,我把它的Parser复制过来后,居然出了语法错误。 后来在这里发现ruby解析JSON有现成的库,用gem install json就可以安装。乐了,装吧,可是没法子装,提示“ERROR: Failed to build gem native extension.” 于是拿这句话来搜,某个江湖郎中说用一下gem update –system 升级gem先,照做。发现连gem都启动不了了。提示“uninitialized constant Gem::GemRunner (NameError)” 偷鸡不成蚀把米,头大。用这句话搜搜来了曙光,原来这个错误在ubuntu下比较常见,gem不能用了,但能用gem1.8。果然可以!再试gem1.8 install json,又回到了“Failed to build gem native extension”。 折腾良久,返回原地。不过这次运气好,看到有人说,只要用apt装一下ruby-dev就好了。apt-get install ruby-dev,提示没这个软件包。出主意这人不厚道,肯定不是东北人。后来我类比了一下,试着安装ruby1.8-dev,成了! 接下来装gem1.8 install json,一切顺利。再用require “json”时,程序已经不报错了。
December 17th, 2007 by 张磊
最近用ruby on rails做东西,但也不指望能用啥IDE了,找个趁手的文本编辑器就够了。因为在ubuntu下,默认的是gedit,可这东西有一个不好,就是每次我改了一个文件,它都会自作聪明地做一个备份。这导致我目录里几乎每个文件都有两份,删掉又会再来,好不麻烦。 于是就找有没东西可以替代。Kate和Kedit就算了,在我用的Gnome上不太好使。komodo edit貌似是免费的,可也太大了,下载都得好久。后来居然找到一个叫做Scribes的,被交口称赞,于是装来用用。安装很简单: apt-get install scribes 装好打开一看,怎么这么简陋。连菜单栏都没有,只有个工具栏。但实际的使用中才感觉到它的强大。作为一个text editor,Scribes居然有代码提示、自动完成的功能,这可是一般放在IDE中才有的。于是写起ruby来也更有感觉了。当然,它是没有语法错误提示的,这个需要开发者细心一点了。
December 17th, 2007 by 张磊
最近看rails如痴如醉阿,上一篇写过两点收获,今天又有很多。感觉之前看书就像听老师讲课,很系统地领会了一次。而现在动手做的过程,无疑更刺激。 用render :partial生成小块(部分)内容 在做软件工程作业时,需要在里面加一个搜索框。首页需要一个,搜索结果页面也需要放一个。当然可以分别在两个地方写一次,也没多少。可是敏感的人们一定能感觉到,这个违背了rails所提倡的DRY的精神。没关系,rails早就想到了。可以用render :partial来生成小部分内容。(坦白说,若不是rails,我也不会这么积极地寻找能遵守DRY原则的方法) 需要用这样的形式来使用:render :partial => ‘path/filename’。应当注意的是,相应的模板(.rhtml)文件该保存为“_”开头的。比如我的模板文件在app/views/book/_search_form.rhtml,若在BookController的方法中调用时,直接用 render :partial => ‘search_form’ 若在别的控制器中,则需要 render :partial => ‘book/search_form’ 用content_for()填充预留块 同样是部分内容。在有的情况下,可能不同页面的同一部分需要不同的内容,比如侧边栏在不同的页面上需要体现不同内容,这时可以用yield先预留一个位置: <div id=”sidebar”> <% yield :sidebar %> </div> 然后在具体页面上可以这样做: <% content_for :sidebar %> sidebar content <% end %> 这样就能把中间“sidebar content”输出到刚才预留的位置。 懒得装那些用于代码高亮的插件了,用简单的缩进也能描述清楚。先写这两个吧,其实还有很多,慢慢写