偶然看到了 Robbin 的一则胶片 《JavaEye 网站架构解密》,说到了一些 JavaEye 的一些实现,那就来看看有哪些有意思的东西。
我正在参与做的一个项目,在某某地方上线,需要几十块单板集群;在某某地方上线,又需要怎样的一个集群组网。咋听起来兴许觉得能有怎样的业务逻辑处理和怎样的用户量呢?可是 JavaEye 让我很吃惊,我先前只知道与 CSDN 比起来,JavaEye 确实是一个小规模一些的网站,专业一些的网站,可是服务器呢?只有两台!
这是那台 Web Server:
• AMD Opteron 2.4GHz 单核 * 2 颗 • 8G 内存 • 146G SCSI 硬盘
这是那台 DBServer:
• AMD Opteron 2.0GHz 单核 * 2 颗 • 4G 内存 • 73G SCSI 硬盘
实在不能说有多么优秀的硬件配置,JavaEye 又得面对怎样的访问量呢?
150 万动态请求/天
这个是 JavaEye 封杀网络爬虫的简单匹配表达式:
JavaEye 采用 Ruby 作为实现语言,看来 Ruby 很慢是没有说头的,看看 Google Adplanner Data:
这张图表就很有意思了:
CSDN 拥有 JavaEye 的 3.5 倍访问量,但使用了三十多台服务器集群,中国最大的几个 IT 站点,使用 ASP.NET、Ruby、PHP 的都有,但看起来 JavaEye 的性能或许是最佳的。
—————————————————————————————————————-
JavaEye 网站架构进化:
(1)2006 年 9 月
• lighttpd • ruby 1.8.4, rails 1.1.2, 以 fastcgi 方式运行 • mysql5.0
FastCGI 像是一个常驻 (long-live) 型的 CGI,它可以一直执行着,只要激活后,不会每次都要花费时间去 fork 一次 (这是 CGI 最为人诟病的 fork-and-execute 模式)。因为是多进程,所以比 CGI 多线程消耗更多的服务器内存,举例来说,PHP-CGI 解释器每进程消耗 7 至 25 兆内存,将这个数字乘以 50 或 100 就是很大的内存数。
其实小网站来说,使用 FastCGI+Lighttpd 是一个非常优秀的组合。
(2)2007 年 1 月
• 添加了第 2 台服务器 • 把 web 和 DB 分开 • 系统瓶颈在数据库 IO 端
系统瓶颈出现在 DB IO 上面是符合预期的(虽然我自己这边的项目经常遇到在 Java 侧锁瓶颈,一方面是性能测试的用例未必能反映现网真实情况导致,另一方面我还是觉得当整个架构过于复杂,远程方法过多,就会导致这样的问题)。
(3)2007 年 2 月
• 把 posts 表的大字段剥离出来 • posts 表的 select count 操作从 30 秒减少到 0.1 秒
把大表的大字段剥离出来,这是一种基于性能考虑的常用的 DB 重构方法。
剥离前:
• posts(id, …, body) • 磁盘存储空间 2GB
剥离后:
• posts(id, post_text_id,…) 50MB • post_texts(id, body) 2GB
(4)2007 年 3 月
• 数据库瓶颈仍然存在 • 引入 memcached 和 CachedModel • 自己编写了简单的查询缓存 • 240 sql query/s 下降到 140 sql query/s • memcached 缓存命中率在 75%
这一次的改进主要在缓存上面,其实在做性能优化的时候,需要经常关注的一个东西就是缓存命中率。
(5)2007 年 9 月
• 引入全文检索 • 使用 ruby 的 ferret • 中文分词使用单字拆分法
主要是对搜索引擎的优化。
(6)2008 年 1 月
• JavaEye 网站代码重写 • 缓存框架改用 cache_fu • 缓存命中率上升到 84% • sql query 下降到 50 条/s
回去打算去了解一下 cache_fu,这里有两篇文章可以参考:
http://weekface.javaeye.com/blog/133797
http://iceskysl.1sters.com/?tag=cache_fu
• cache_fu 不对 AR 对象进行任何拦截,全部交给用户编程 • 用户有完全的控制权,但所有的缓存代码要自己手工编写
(7)2008 年 5 月
• 中文分词算法改用 rmmseg-cpp
(8)2008 年 10 月
• 自制山寨 cache plugin • 缓存命中率上升到 96% 以上
• 抛弃 ferret,自己编写全文检索服务器 • 使用 Java 的 lucene 作为全文检索引擎 • 自己实现 C/S 架构的内部调用
(8)2008 年 11 月
• 实现博客,新闻制作 PDF 功能
(9)2009 年 3 月
• SNS feed 功能 • twitter 绑定功能 • 开放 API
• 废弃 Google Analytics • 自己编写简单的网站流量分析系统
(10)2009 年 12 月
• 添加 Web IM • 添加一台服务器 • 合理规划服务器
一个生命周期较长的 WEB 应用每发展到一定阶段一定要面对的是架构上的重组,有时哪怕牺牲一些性能的代价,有时则是牺牲可维护性的代价,带来的是结构层次清晰,便于短期内扩展等好处。这个过程每次都可能是痛苦的,但又是不可避免的。同时,我认为,在项目初期不应当也不可能把架构的融合性和扩展性考虑得太远,那样反而作茧自缚。而在应用发展过程中不断地重构却是更有价值的。
—————————————————————————————————————-
进化总结:
(1)对象缓存原则:
• 数据库表的设计要细颗粒度 • 把有冗余字段的大表拆分为 n 个互相外键关联的小表 • ORM 的性能瓶颈不在于表关联,而在于大表的全表扫描 • 尽量避免 join 查询,多制造 n+1 条 SQL
上面第一条我觉得还是要看表容量而定,第四条我深有体会,记得在 iBatis 的使用中还有这样一个专题。
(2)对象缓存的意义:
• Web 应用很容易通过集群方式实现横向扩展,系统的瓶颈往往出现在数据库 • 数据库的瓶颈往往出现在磁盘 IO 读写 • 因此要避免数据库的全表扫描和大表的数据扫描操作 • 如何避免:拆表和臭名昭著的 n+1 条 SQL
……
• memcached 缓存命中率 96% • cache get : sql query = 4 : 1
另外,Robbin 还提到,Ruby 的字符串处理,尤其是正则表达式处理性能不好,解决方法也是使用缓存。
cache_money:
• 出自 twitter 开发团队之手 • 可能是目前最强大的 ruby cache 框架 • 支持分页查询缓存,支持条件查询缓存
全文检索:
—————————————————————————————————————-
后附,关于 JavaEye 后来的衰落:
其实严格说用“ 衰落” 这个词语是不很恰当的,但是于我看来,就如同“ 校内网” 变成“ 人人网” 一样,很多网站在发展的过程中,都把自己能应付的战线拉长,让那些原来看起来不属于自己的用户收纳进来,JavaEye 也一样,更名为 ITEye(当然,其直接原因还是来自于 Oracle 的压力,你不能免费用着 Java 的名号啊),但是这样带来副作用,尤其对一个技术社区来说,就是良莠不齐、鱼龙混杂。
如今的,实际人气已经没有那么高了,但是却成为了很多程序员小白的乐园,也就是说,已经丢失了帖子文章的质量,丢失了网站原本的生命,还有一票牛人。Robbin 自己也提到了一些客观原因:
JavaEye 在 04 年 05 年确实有一些比较火爆的帖子,但是要看到当时的时代背景:EJB2 逐渐被人唾弃,Hibernate/Spring 强势崛起,CMM 开始被骂,敏捷开发在国内悄悄普及,AJAX 技术也在 Google 推动下一夜成名。从整个 Java 行业来说,那几年可以炒作的体裁很多,可以争论的话题很多,观点的冲突很多。这在客观上造就了论坛的火爆。
在 ITEye 被 CSDN 收购以后,再加上那次著名的密码泄露的拖库事件,事件以后 Robbin 跳出来解释问题和撇清责任,我已经彻底对 ITEye 失去了信心。