林紫寒

总结 web 应用中常用的各种 cache

from : https://ruby-china.org/topics/19389总结web应用中常用的各种cachecache是提高应用性能重要的一个环节,写篇文章总结一下用过的各种对于动态内容的cache。文章以Nginx,Rails,Mysql,Redis作为例子,换成其他web服务器,语言,数据库,缓存服务都是类似的。以下是3层的示意图,方便后续引用: +-------+1 | Nginx | +-+-+-+-+ | | | +---------------+ | +---------------+ | | | +---+---+ +---+---+ +---+---+2 |Unicorn| |Unicorn| |Unicorn| +---+---+ +---+---+ +---+---+ | | | | | | | +---+---+ |3 +-------------+ D B +-------------+ +-------+1. 客户端缓存一个客户端经常会访问同一个资源,比如用浏览器访问网站首页或查看同一篇文章,或用app访问同一个api,如果该资源和他之前访问过的没有任何改变,就可以利用http规范中的304 Not Modified 响应头(http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5 ),直接用客户端的缓存,而无需在服务器端再生成一次内容。在Rails里面内置了fresh_when这个方法,一行代码就可以完成:class ArticlesController def show @article = Article.find(params[:id]) fresh_when :last_modified => @article.updated_at.utc, :etag => @article endend下次用户再访问的时候,会对比request header里面的If-Modified-Since和If-None-Match,如果相符合,就直接返回304,而不再生成response body。但是这样会遇到一个问题,假设我们的网站导航有用户信息,一个用户在未登陆专题访问了一下,然后登陆以后再访问,会发现页面上显示的还是未登陆状态。或者在app访问一篇文章,做了一下收藏,下次再进入这篇文章,还是显示未收藏状态。解决这个问题的方法很简单,将用户相关的变量也加入到etag的计算里面: fresh_when :etag => [@article.cache_key, current_user.id] fresh_when :etag => [@article.cache_key, current_user_favorited]另外提一个坑,如果nginx开启了gzip,对rails执行的结果进行压缩,会将rails输出的etag header干掉,nginx的开发人员说根据rfc规范,对proxy_pass方式处理必须这样(因为内容改变了),但是我个人认为没这个必要,于是用了粗暴的方法,直接将src/http/modules/ngx_http_gzip_filter_module.c这个文件里面的这行代码注释掉,然后重新编译nginx: //ngx_http_clear_etag(r);或者你可以选择不改变nginx源代码,将gzip off掉,将压缩用Rack中间件来处理: config.middleware.use Rack::Deflater除了在controller里面指定fresh_when以外,rails框架默认使用Rack::ETag middleware,它会自动给无etag的response加上etag,但是和fresh_when相比,自动etag能够节省的只是客户端时间,服务器端还是一样会执行所有的代码,用curl来对比一下。Rack::ETag自动加入etag:curl -v http://localhost:3000/articles/1< Etag: "bf328447bcb2b8706193a50962035619"< X-Runtime: 0.286958curl -v http://localhost:3000/articles/1 --header 'If-None-Match: "bf328447bcb2b8706193a50962035619"'< X-Runtime: 0.293798用fresh_when:curl -v http://localhost:3000/articles/1…

开源的电子邮件模板

开源的电子邮件模板,质量相当的高.网址: https://www.sendwithus.com/resources/templates&nbsp;

Web应用的缓存设计模式

from: http://robbinfan.com/blog/38/orm-cache-sumupORM缓存引言从10年前的2003年开始,在Web应用领域,ORM(对象-关系映射)框架就开始逐渐普及,并且流行开来,其中最广为人知的就是Java的开源ORM框架Hibernate,后来Hibernate也成为了EJB3的实现框架;2005年以后,ORM开始普及到其他编程语言领域,其中最有名气的是Ruby on rails框架的ORM - ActiveRecord。如今各种开源框架的ORM,乃至ODM(对象-文档关系映射,用在访问NoSQLDB)层出不穷,功能都十分强大,也很普及。然而围绕ORM的性能问题,也一直有很多批评的声音。其实ORM的架构对插入缓存技术是非常容易的,我做的很多项目和产品,但凡使用ORM,缓存都是标配,性能都非常好。而且我发现业界使用ORM的案例都忽视了缓存的运用,或者说没有意识到ORM缓存可以带来巨大的性能提升。ORM缓存应用案例我们去年有一个老产品重写的项目,这个产品有超过10年历史了,数据库的数据量很大,多个表都是上千万条记录,最大的表记录达到了9000万条,Web访问的请求数每天有300万左右。老产品采用了传统的解决性能问题的方案:Web层采用了动态页面静态化技术,超过一定时间的文章生成静态HTML文件;对数据库进行分库分表,按年拆表。动态页面静态化和分库分表是应对大访问量和大数据量的常规手段,本身也有效。但它的缺点也很多,比方说增加了代码复杂度和维护难度,跨库运算的困难等等,这个产品的代码维护历来非常困难,导致bug很多。进行产品重写的时候,我们放弃了动态页面静态化,采用了纯动态网页;放弃了分库分表,直接操作千万级,乃至近亿条记录的大表进行SQL查询;也没有采取读写分离技术,全部查询都是在单台主数据库上进行;数据库访问全部使用ActiveRecord,进行了大量的ORM缓存。上线以后的效果非常好:单台MySQL数据库服务器CPU的IO Wait低于5%;用单台1U服务器2颗4核至强CPU已经可以轻松支持每天350万动态请求量;最重要的是,插入缓存并不需要代码增加多少复杂度,可维护性非常好。总之,采用ORM缓存是Web应用提升性能一种有效的思路,这种思路和传统的提升性能的解决方案有很大的不同,但它在很多应用场景(包括高度动态化的SNS类型应用)非常有效,而且不会显著增加代码复杂度,所以这也是我自己一直偏爱的方式。因此我一直很想写篇文章,结合示例代码介绍ORM缓存的编程技巧。今年春节前后,我开发自己的个人网站项目,有意识的大量使用了ORM缓存技巧。对一个没多少访问量的个人站点来说,有些过度设计了,但我也想借这个机会把常用的ORM缓存设计模式写成示例代码,提供给大家参考。我的个人网站源代码是开源的,托管在github上:robbin_siteORM缓存的基本理念我在2007年的时候写过一篇文章,分析ORM缓存的理念:ORM对象缓存探讨 ,所以这篇文章不展开详谈了,总结来说,ORM缓存的基本理念是:以减少数据库服务器磁盘IO为最终目的,而不是减少发送到数据库的SQL条数。实际上使用ORM,会显著增加SQL条数,有时候会成倍增加SQL。数据库schema设计的取向是尽量设计 细颗粒度 的表,表和表之间用外键关联,颗粒度越细,缓存对象的单位越小,缓存的应用场景越广泛尽量避免多表关联查询,尽量拆成多个表单独的主键查询,尽量多制造 n + 1 条查询,不要害怕“臭名昭著”的 n + 1 问题,实际上 n + 1 才能有效利用ORM缓存利用表关联实现透明的对象缓存在设计数据库的schema的时候,设计多个细颗粒度的表,用外键关联起来。当通过ORM访问关联对象的时候,ORM框架会将关联对象的访问转化成用主键查询关联表,发送 n + 1条SQL。而基于主键的查询可以直接利用对象缓存。我们自己开发了一个基于ActiveRecord封装的对象缓存框架:second_level_cache ,从这个ruby插件的名称就可以看出,实现借鉴了Hibernate的二级缓存实现。这个对象缓存的配置和使用,可以看我写的ActiveRecord对象缓存配置 。下面用一个实际例子来演示一下对象缓存起到的作用:访问我个人站点的首页。 这个页面的数据需要读取三张表:blogs表获取文章信息,blog_contents表获取文章内容,accounts表获取作者信息。三张表的model定义片段如下,完整代码请看models :class Account < ActiveRecord::Base acts_as_cached has_many :blogsendclass Blog < ActiveRecord::Base acts_as_cached belongs_to :blog_content, :dependent => :destroy belongs_to :account, :counter_cache => trueendclass BlogContent < ActiveRecord::Base acts_as_cachedend传统的做法是发送一条三表关联的查询语句,类似这样的:SELECT blogs.*, blog_contents.content, account.name FROM blogs LEFT JOIN blog_contents ON blogs.blog_content_id = blog_contents.id LEFT JOIN accounts ON blogs.account_id = account.id往往单条SQL语句就搞定了,但是复杂SQL的带来的表扫描范围可能比较大,造成的数据库服务器磁盘IO会高很多,数据库实际IO负载往往无法得到有效缓解。我的做法如下,完整代码请看home.rb :@blogs = Blog.order('id DESC').page(params[:page])这是一条分页查询,实际发送的SQL如下:SELECT * FROM blogs ORDER BY id DESC LIMIT 20转成了单表查询,磁盘IO会小很多。至于文章内容,则是通过blog.content的对象访问获得的,由于首页抓取20篇文章,所以实际上会多出来20条主键查询SQL访问blog_contents表。就像下面这样:DEBUG - BlogContent Load (0.3ms) SELECT `blog_contents`.* FROM `blog_contents` WHERE `blog_contents`.`id` = 29 LIMIT 1DEBUG - BlogContent Load (0.2ms) SELECT `blog_contents`.*…

GistBox 代码管理工具

GistBox 提供一种漂亮的方式来组织代码片段。将你的库保存到云端进行备份,再也不用担心丢失。GistBox采用标准的HTML5技术构建。GistBox使用GitHub的后端,但增加了自己的标签和搜索功能层。使用Github账号登陆Gistbox可以将你的代码直接同步进来,反过来,你在GB上的所有改动也都会同步到Github上;GistBox的结构设 计清晰,从左至右分别是主导航(新建Gist,Gists入口,收藏入口-Labels)、Gists列表(Public/Private)、具体代码 区,亲们可以用Label给代码加上各种分辨标签,方便分类整理,在检索代码时可以用顶部的搜索栏,输入关键词或Label可以更快的搜索到目标代码。网址: http://www.gistboxapp.com/

光棍节程序员闯关秀 小游戏

光棍节程序员闯关秀,没有几把刷子,还真是很难玩下去的,我承认我没玩完,按照攻略才完成的。挑战一下自己吧!看看能过几关。游戏地址:http://segmentfault.com/game/  攻略地址:http://www.cnblogs.com/partoo/archive/2012/11/11/2765070.html

忧伤

忧伤凄冷幽黑的雨夜我站在古老的梧桐树下聆听梧桐细雨的忧伤细雨跌落在梧桐的叶子上冥冥之中我看到一珠充满忧愁的泪水那何曾是我拥有的哀愁我美丽的忧伤是这千年古树的造化守者一方宁静的沃土最终我也不免是你忧伤的结局

我也不知道

我也不知道我也不知道我们何时才能相见只记得每每你总说不会给我机会的无奈又无力的等候心存侥幸的期待如果可以我一定会让你成为幸福的人我不知道你美丽的头颅是怎么思考的为什么总是这样一次次的拒绝我也不知道这个世界上能遇见钟情的人吗也许我们都没有看到明媚的阳光总是对于这所谓的美好心存芥蒂你说我是一个不可依靠的人我也不知道只是时常质问自己你很失败你的生命里只有未写完的诗稿笔和纸以及一些烟头爱情对于你这样的人来说永远都是奢侈品我看不到你就看着自己顾影自怜多想听一下你的声音看一下你美丽的容颜感受一下你心灵的呼吸我不知道偶然的相识是对还是错不过我很清楚一个人的期盼终会无济于事为什么不去做一个勇敢的人尝试一下美妙的情怀呵呵多情的人啊你就自己去幻想吧等待着下一个春天降临吧

故乡

故乡始皇的銮驾在此停歇已有几千年了悠远的天空依旧湛蓝一条河静静的躺着我的故乡在河岸边小的时候爷爷会牵着牛带我去河边看水鸟若干年过去了爷爷已不在了河里的水几近于干涸裸露的河床和两岸的断崖诉说了什么印证了什么我的童年是和那里的泉水长大的多少年了记忆中静静的村庄和天空的飞鸟总是让人撕心裂肺一直在漂泊不知为了什么没有情人 没有爱人只是拖着沉重的影子偶尔的时候会想起村庄里我的亲人也会在每个节日来临的时候心里默默的祝福故乡在我心里褪色了或者是我在故乡的心里黯淡了我们从起初的相濡以沫到现在的若即若离就好比一段难以释怀的恋情出入于故乡的心脏每一次都会血流不止回归故乡的时候总会有着一种无以言说的悸动回来了 总是两手空空没有追随的亲信没有光耀门楣的荣耀只有西风 斜阳和流动的空气拽着我的影子故乡的我始终是一个长不大的孩子我也幻想着是一个盲童阳光不至于灼伤我的眼睛也让我永远不羞愧于我的村庄我曾经乘凉的大树旁边又多了几棵我曾经收割的那片麦地已经长出了新的庄稼我的脚步和声音滞留过的那片土地注定会有鸿鹄飞过和我一起数星星的小女孩已嫁做人妻 做了人母我的故乡 我的村庄我会静静的守候着你们天涯海角 相伴永远待到另一个春暖花开的日子我会带着恋人目睹你的芳颜

惬意

终于逮到了一个可以睡到自然醒的周末了,可是自己没那么幸运,可以睡到自然醒。和平时一样,早上6点多就醒来了,一直就这么躺着,起床做什么啊?今早的天气也是很不错,太阳都照到床上了。裹紧被子,还是继续睡觉吧!&nbsp;自己一直认为最爽的时候就是吃饱饭的时候和早上刚刚醒来,躺在床上,什么事也不用担心,什么事也不用想,唯一要做的就是静静的躺着,看着窗外的好天气。有再多的烦心事也在此时一无所在了。好好享受此刻,悠哉。。。游哉。。。