偶然看到了Robbin的一篇文章,说到了一些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框架
• 支持分页查询缓存,支持条件查询缓存
全文检索: