Discuz!官方免费开源建站系统

 找回密码
 立即注册
搜索

DZ能否不要把简单问题复杂化呢?

[复制链接]
mark35 发表于 2012-7-3 21:32:54 | 显示全部楼层 |阅读模式
本帖最后由 mark35 于 2012-12-3 21:03 编辑

技术是用来解决问题,不是用来堆砌的

Discuz X2.5的新构架似乎是为了构架而构架,为了技术而技术。本来一个很简单功能非要整些“高科技”实现,却得不偿失

先看说明:
• 点击数优化

问题:

频繁的写表操作导致锁表

方法:

增加forum_threadaddviews表,记录每一个TID的增量点击数;

查看帖子时,如果增量点击数到100,则使用进程锁将数据更新到thread表并更新增量点击数为0;

回贴时将增量点击数和回复数一起更新到thread表,并更新增量点击数为0;

执行计划任务:每天3点,5分钟一次,一次取500条数据更新到thread表, 并删除此500条数据,以减少forum_threadaddviews表的大小;

http://dev.discuz.org/wiki/index ... 0.E4.BC.98.E5.8C.96

代码:

  1. function viewthread_updateviews($tableid) {
  2.     global $_G;

  3.     if(!$_G['setting']['preventrefresh'] || $_G['cookie']['viewid'] != 'tid_'.$_G['tid']) {
  4.         if(!$tableid && $_G['setting']['optimizeviews']) {
  5.             if($_G['forum_thread']['addviews']) {
  6.                 if($_G['forum_thread']['addviews'] < 100) {
  7.                     C::t('forum_threadaddviews')->update_by_tid($_G['tid']);
  8.                 } else {
  9.                     if(!discuz_process::islocked('update_thread_view')) {
  10.                         $row = C::t('forum_threadaddviews')->fetch($_G['tid']);
  11.                         C::t('forum_threadaddviews')->update($_G['tid'], array('addviews' => 0));  // <------------------ 归零
  12.                         C::t('forum_thread')->increase($_G['tid'], array('views' => $row['addviews']+1), true);
  13.                         discuz_process::unlock('update_thread_view');
  14.                     }
  15.                 }
  16. ......
  17. }
复制代码
/source/module/forum/forum_viewthread.php line=909

为了解决一个在并发时addviews值在两个表同时正确更新的问题,竟然用上了进程锁。原因有2:
1、myisam不能行锁,如果用表锁就会导致对一个tid操作时所有tid的操作都被阻塞。结果是所谓点击优化反而导致性能恶化。

2、因为既不能表锁,也不可能行锁,所以在并发时会出现两个线程同时对同一个tid的点击数进行更新情况,如果不能锁住在临时表中的行值,那么就会出现点击数翻倍更新的情况(见上面PHP代码,是先取出值更新到主表,再临时表该行归零)。所以就使用memcache功能实现了数据库本来该有的行锁功能。

可是,难道就没有简单的解决办法了么?

先看看库表结构吧
  1. CREATE TABLE pre_forum_threadaddviews (
  2.   tid mediumint(8) unsigned NOT NULL DEFAULT '0',
  3.   addviews int(10)  UNSIGNED NOT NULL DEFAULT '0',  -- <-------- UNSIGNED!
  4.   PRIMARY KEY (tid)
  5. ) TYPE=MyISAM;
复制代码
/install/data/install.sql

好像mysqler 最喜欢或者只会使用 unsigned 类型,你觉得代码中归零就不用考虑负值溢出啦。可你不懂财会基础知识,不知道负值在这儿可是真正的银弹。

好吧,废话少说,以下是省时省力低碳的解决办法:

1、表结构改为 addviews int(10) NOT NULL DEFAULT '0'
2、在viewthread_updateviews函数中抛弃进程锁,读出多少减去多少

  1.                 } else {
  2.              //       if(!discuz_process::islocked('update_thread_view')) {
  3.                         $row = C::t('forum_threadaddviews')->fetch($_G['tid']);
  4.                         C::t('forum_threadaddviews')->decrease($_G['tid'], array('addviews' => $row['addviews']));  // <------------
  5.                         C::t('forum_thread')->increase($_G['tid'], array('views' => $row['addviews']+1), true);
  6.                //         discuz_process::unlock('update_thread_view');
  7.                     }
  8.                 }
复制代码
这保证了即便不使用锁机制,也不用担心并发操作造成错误数据——主表那儿加多了?不要紧,临时表那儿就是相同差值的负数,这就是有借有贷,借贷平衡

当然,上面的代码不能直接这么修改。因为只是屏蔽了进程锁的检测、解锁代码,并未屏蔽加锁。当前这种类方法调用格式无法通过传递参数的方式决定是否加锁,除非使用单独的全局变量或者类属性来设置是否加锁,或者修改C类静态方法让其接受第二个参数决定是否使用进程锁。

把一个简单事情做复杂,很简单,
把一个复杂事情做简单,很难。









评分

1

查看全部评分

回复

使用道具 举报

dengfeng0217 发表于 2012-7-3 22:05:33 | 显示全部楼层
悲剧,不知道2.0是否也这样
回复

使用道具 举报

枯心树 发表于 2012-7-4 00:25:43 | 显示全部楼层
额。不懂技术。有知道的看看那个方案更好?
回复

使用道具 举报

小森我爱你 发表于 2012-7-4 00:55:07 | 显示全部楼层
很想高手。来个超高高手判断下
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|Discuz! 官方站 ( 皖ICP备16010102号 )star

GMT+8, 2025-9-9 19:20 , Processed in 0.133760 second(s), 16 queries , Gzip On.

Powered by Discuz! X3.4

Copyright © 2001-2023, Tencent Cloud.

快速回复 返回顶部 返回列表