使用Lucene 构建强大的discuz 论坛搜索模块

作者 : admin 于 2010年07月27日, 14:21:18
2010
07-27

在我搞完公司的论坛优化后,我一直想写一个圈套的dz性能优化的方案。当时的全文检索使用的是公司内部某人开发的检索系统,没有开源,所以我做此方案来实现。
此文刚打完草稿,处于调试通过的状态。没有形成具体可用的用户文档。希望在这个底稿的基础上,朋友能给予测试和支持,以鼓励我做出一套完整的方案。

下载:lucene_dz

欢迎加入QQ讨论群:41886598

discuz7.2针对session表的分析和优化

作者 : admin 于 2010年07月07日, 17:04:54
2010
07-7

广告时间:新建一个qq群,欢迎大家进来讨论discuz的性能优化 qq群 41886598 接头暗号:dz

引子

最近一个朋友的论坛由discuz转化为phpwind,因为discuz在负载较大的情况下频频当机,但在我观察,瓶颈基本是数据库,并且负载最高的表定位为sessions表。之前我们论坛在进行了大的改进后,除了分表这些基本优化,改动最大的就是sessions表。我们使用张宴写的tcsql来存储用户的session信息,经推算,系统负载应该能支撑10~20w人同时在线。

这个10~20w人只是推测,因为原来我们优化sessions表但没有切换到tcsql的时候,最高支撑到4~6w人-也就是某次游戏临时维护,用户大量涌向论坛,在大概4~6w用户的情况下,论坛崩溃。而tcsq的读写效率远远高于mysql,因此推算tcsql代替sessions表的情况下,论坛至少可以承载10~20w人同时在线。
毕竟tcsql用C进行开发,而且没有开源。去年的文章发表后,一直有朋友在探讨dz的优化问题,但那几个方案都依赖了第三方的应用。这次,我想到的是在不依赖其他应用的前提下,完成dz的sessions表优化。

这里,我对sessions表的访问情况作了一个统计:

一个没访问过的论坛,我作为一个用户:1、进入首页 2、ajax登录 3、进入某板块 4、点击某帖子。4个操作,统计session表的操作:select 5次,insert 2次,delete 2次,update,3次,select联查,4次,一共16次操作。对于一个成熟的网站,大部分用户的操作连起来,统计数据跟我统计的会有区别,但从全局看来,sessions表的确是mysql查询数的一个瓶颈。

以子之矛

对于这个问题,dz程序其实是有解决方案的。

  1. if($pvfrequence && $discuz_uid) {
  2. if($spageviews >= $pvfrequence) {
  3. $pageviewsadd = ', pageviews=\'0\'';
  4. $db->query("UPDATE {$tablepre}members SET pageviews=pageviews+'$spageviews' WHERE uid='$discuz_uid'", 'UNBUFFERED');
  5. } else {
  6. $pageviewsadd = ', pageviews=pageviews+1';
  7. }
  8. } else {
  9. $pageviewsadd = '';
  10. }

这个功能是统计用户浏览量的一个小东西,$spageviews是保存在用户session表的一个变量,如果每次浏览都更新用户表的浏览数,member表的负担是很大的。dz的程序员就用客户端更新数据这种方式,把数据更新动作放在了session表,每$spageviews个周期更新一次,有效降低了member表压力,而且此配置在网站后台作出了用户接口。

为什么这个问题可以解决,只不过把压力进行了转移,而sessions表的压力为什么不能解决一下呢?我百思不得其解。

攻子之盾

member的压力可以转移到sessions表,当然sessions表的压力就不能再转移给别的表了,这样处理,不管转移到哪里,都是数据库的压力,所以,得想办法转移到数据库之外。

再考虑为什么member的压力可以转移:因为用户浏览数这个统计项,用户每必要也不需要实时看到更新。

而用户的session信息是用户实时要用,并且有部分权限控制方面的东西,是非常必要的,所以,必须实时获取。既然其他表的压力也都来了sessions表,那更新也必然需要在sessions表中做。这个事情,不就是php的session应该做的事情么?

再考虑sessions表除模拟php的session功能外的其他功能:

1、状态传递:需要把用户的当前状态实时传递给其他用户。在线状态、用户所在位置等。

2、统计分析:在dz可以看到当前多少用户在线,对于单纯使用php session功能是无法满足的,故dz采用sessions表来替代这个功能。

现在就设计一个机制,既满足用户session的功能,又满足统计分析的功能。当然,用php+mysql了。需要及时更新的信息存储于php的session,而需要统计的信息分阶段更新到mysql。

流程如此 用户创建一个mysql记录,创建一个session,session的过期机制类似于mysql表。每次访问,取session信息,如果session信息不存在,取sessions表中的信息。像用户浏览次数这类数据,可以每过一段时间更新到mysql中。用户session每次更新,也只是更新session数据,而session机制的并发性和效率,远远高于mysql的。如此解决,既不阉割原有的功能,又不会对环境造成很严重的依赖,只要支持phpsession的环境即可。

总结

在修改完成后,我又进行了测试,这次session表的访问频度已经降到了原来的1/3之下,我想,对于大型的论坛,起码能节约1/3的服务器吧。这样改动也并不是没有缺点的:统计就会有延迟和偏差。因为中间用户突然离开,会损失sessions表更新的及时性甚至会损失这部分数据。如果严重依赖论坛自己某些统计功能的话,那这种方式欠妥,但对于已经使用第三方统计或者对那部分数据不很敏感的论坛用户,这种方案还是非常适合的。

附言:dz的大多数产品设计都采用sessions表这种设计思路,因此,这个思路可以优化几乎所有的dz产品。

tips:如何定位sessions表的负载: 在mysql数据类的query方法里增加如下代码,数据少可以用肉眼数,数据多可以写个脚本去分析

  1. $handle = fopen("sql.log","a+");
  2. fwrite($handle,str_replace("\r\n","",$sql)."\r\n");
  3. fclose($handle);

附上代码:

dz

discuz论坛sessions表最终优化方案

作者 : admin 于 2010年01月17日, 23:00:29
2010
01-17

最近一直在折腾dz的sessions表优化。经过某群好友的各种方法提示和一些高数据量用户论坛的鼎力支持,总结以下优化方案。部分方案是在某些论坛正在使用的,部分方案是我发散思维总结的,没有经过大数据量和大负载下的应用,只是作为一个备选的方案,当然欢迎朋友们拿去实践。

1、分库

这个方法至少两三个注册用户百万级至千万级的论坛在使用。实施也比较简单,只需要把sessions表放在其他的库中,跟论坛主库分离,这样,就可以用多台服务器来分担论坛压力。sessions表查询的地方,如果直接查询,则连sessions表所在的库,如果是联查,则分别查询后,合并插叙结果。

2、砍功能

这个方法虽然不实用,但的确有效。仔细看看dz的一些sql语句,就知道砍掉某些功能摆脱sessions表的约束,性能会有多大的提高。砍功能,最终还是为了提升性能。但如果不砍功能又提升性能,才是终极目的。此方法适用于对某些统计功能要求不高的论坛使用。

3、memcached存储sessions,异步统计用户的在线数据

此方法其实还是砍掉了论坛的用户在线统计功能而独立开发一套统计系统。此思路来源于我们的统计服务器。如果统计服务器已经统计了部分信息,就没必要再去耗费大量的sql效率去进行统计。当时我们负责统计的吴同学正好也要开发一套针对不同系统的统计代码,我可以直接把要统计的数据提交给统计服务器,再由统计服务器返回统计结果。以异步的方式来统计,化解dz sessions表的性能瓶颈。而统计信息的异步提交,可以提交到本地数据库,可以提交到第三方统计系统,当然也可以根据需要独立开发一套小型统计系统。

方案3是本人原创方案,方案的修改代码在以下附件中: 异步session统计信息同步,欢迎大家使用。

由于dz的开发考虑的是大众市场,功能冗余代码逻辑复杂。但优化dz并不是那么的困难,而优化的思路,也是跟其他系统优化相似:mysql瓶颈。一个即将崩溃的系统,或者频繁崩溃的系统,是进行优化的最好示例:减少sql查询,提高sql语句查询效率。dz考虑大众市场,为了提高产品兼容性,并没有引入太多的第三方软件:比如memcached,bdb,ttserver等,虽然内部已经为主从预留了接口,但未见很明显的文档支持。
第三个方案优化的思路也很明显,减少sessions表的慢查询而改为memcached高速的数据存储,把统计功能做接口留出,此功能给第三方来做。这样sessions表的瓶颈即可消除。

使用无模式数据库来改造dz的session

作者 : admin 于 2009年12月29日, 09:26:58
2009
12-29

前两天还不知道啥是无模式数据库,不过最近迫切需要修改discuz bbs的session表,所以研究了一下相关的资料,发现了无模式这个名字。

具体无模式是啥,这里找了两篇文章:

关系数据库的末日是否已经来临
关系数据库的根本问题分析及数据库革命之走向

关于无模式的应用案例,很典型的一个,就是康盛uchome的feed表。

这里简单介绍几个无模式数据库,供大家参考:

mongodb : http://www.mongodb.org/display/DOCS/Home

mongodb的php扩展 http://www.mongodb.org/display/DOCS/Installing+the+PHP+Driver#InstallingthePHPDriver-PECL

这个数据库已经在淘宝上配合10gen做了云计算的session(名字很操蛋),具体资料在这里 http://rdc.taobao.com/blog/dw/archives/410。具有了实际应用的东西应该很不错,不过我没尝试过。

Tokyo Tyrant:http://1978th.net/

tt的php扩展 http://www.php.net/manual/en/book.tokyo-tyrant.php

这个大家就比较熟悉了,SB日本鬼子写的东西,应用很广,sina,qq等公司里都在大量应用,sina的研发团队在此基础上还做了个分布式的东东,很是不错。据说这玩意还得到了很多公司的赞助,shit。

TCSQL:张宴基于tt开发的一套东西,内部做了很多算法优化,具体可参考此文章 http://blog.s135.com/tcsql/

总结:康盛使用mysql来存储session,不能说是一种错,但对于dz的负载绝对是第一个瓶颈。用这种方式,在线人数5w对数据库绝对是个坎。很多负载比较大的论坛,都做了此方面的优化,比如,给session表单独一个数据库,或者干脆阉割此功能。不错,独立的session表,或者分库,访问量大的时候,瓶颈依然在mysql上;阉割此功能,有些心疼,而且很多运营数据会丢失。所以,保持原功能而平滑提升性能,是一个不错的解决方向。

康盛,这么做是不是有点过火了

作者 : admin 于 2009年12月10日, 01:02:25
2009
12-10

使用开源产品,的确能加快我们的产品实现进度,我们也感谢那些提供免费开源产品的人。当然,我个人也喜欢开源技术。

但如果开源的产品中给你下个后门,抓取你的数据,你还能高兴么?

现在分析 discuz 7.0 的几段代码:

  1. if($adminid == 1 && $action == 'home') {
  2. echo '<sc '.'ript language="Jav'.'aScript" src="ht'.'tp:/'.'/cus'.'tome'.'r.disc'.'uz.n'.'et/n'.'ews'.'.p'.'hp?'.bbsinformation().'">';
  3. //echo '</sc><sc '.'ript language="Jav'.'aScript" src="http://localhost/com/n'.'ews'.'.p'.'hp?'.bbsinformation().'">';
  4. }
  5. </sc>

恩,看见了没,我告诉大家一个写木马常用的方式 eval ,比如asp中查找木马,通常查找fso等关键字,木马一般都给他写成 eval(’f'+’s’,'o’),写php的应该很熟悉这个函数了。这段代码当然不是木马,咱看看他具体做了什么:

  1. function bbsinformation() {
  2.  
  3. global $db, $timestamp, $tablepre, $charset, $bbname, $_SERVER, $siteuniqueid, $save_mastermobile, $msn;
  4. $update = array('uniqueid' => $siteuniqueid, 'version' => DISCUZ_VERSION, 'release' => DISCUZ_RELEASE, 'php' => PHP_VERSION, 'mysql' => $db->version(), 'charset' => $charset, 'bbname' => $bbname, 'mastermobile' => $save_mastermobile);
  5.  
  6. $updatetime = @filemtime(DISCUZ_ROOT.'./forumdata/updatetime.lock');
  7. if(empty($updatetime) || ($timestamp - $updatetime > 3600 * 4)) {
  8. @touch(DISCUZ_ROOT.'./forumdata/updatetime.lock');
  9. $update['members'] = $db->result_first("SELECT COUNT(*) FROM {$tablepre}members");
  10. $update['threads'] = $db->result_first("SELECT COUNT(*) FROM {$tablepre}threads");
  11. $update['posts'] = $db->result_first("SELECT COUNT(*) FROM {$tablepre}posts");
  12. $query = $db->query("SELECT special, count(*) AS spcount FROM {$tablepre}threads GROUP BY special");
  13. while($thread = $db->fetch_array($query)) {
  14. $thread['special'] = intval($thread['special']);
  15. $update['spt_'.$thread['special']] = $thread['spcount'];
  16. }
  17. if($msn['on'] && $msn['domain']) {
  18. $update['msn_domain'] = $msn['domain'];
  19. }
  20. }
  21.  
  22. $data = '';
  23. foreach($update as $key => $value) {
  24. $data .= $key.'='.rawurlencode($value).'&';
  25. }
  26. return 'update='.rawurlencode(base64_encode($data)).'&md5hash='.substr(md5($_SERVER['HTTP_USER_AGENT'].implode('', $update).$timestamp), 8, 8).'&timestamp='.$timestamp;
  27. }

看见了吧,看他提交的几个关键字段:$update['members'],$update['threads'],$update['posts'],$thread['special'],$update['spt_'.$thread['special']],$update['msn_domain'],这些字段提交过去,也许对我们系统安全并无影响,可看看6.0版本的论坛还提交了什么:

uniqueid=**&version=6.0.0&release=20081117&php=5.2.6&mysql=5.0.22&charset=utf-8&bbname=**&mastermobile=****&members=4965&threads=883&posts=25921&spt_0=879&spt_1=4&

mastermobile 看见这个字段你会汗不?

康盛应该为此行为买单不?不会 。

看下边:

III 有限担保和免责声明
1. 本软件及所附带的文件是作为不提供任何明确的或隐含的赔偿或担保的形式提供的。
2. 用户出于自愿而使用本软件,您必须了解使用本软件的风险,在尚未购买产品技术服务之前,我们不承诺提供任何形式的技术支持、使用担保,也不承担任何因使用本软件而产生问题的相关责任。
3. 康盛创想(北京)科技有限公司不对使用本软件构建的论坛中的文章或信息承担责任。

您必须了解使用本软件的风险–用户了解信息泄露的风险么?
康盛创想(北京)科技有限公司不对使用本软件构建的论坛中的文章或信息承担责任。其实,我也不愿意承担。

本文纯属月经文,如有难受,过几天自然恢复。