`

Discuz论坛分表以及memcache缓存优化

阅读更多
2011-07-14 12:24
大部分的论坛在数据量达到一定程序的时候就会出现浏览帖子,回帖能操作缓慢的情况,一般情况都是由于posts表过大导致的,本文也是从对Disucz增加和memcache和posts分表的方式出发.环境有限只有1台WEB和DB,都是非独享,所以posts到800W多的时候,压力就比较大了

1.memcache

Discuz以前的版本都是面向过程的方式(DiscuzX还没了解),例如对于posts,members基本是在论坛各个文件当中,这对加入memcache建立和更新机制增加了复杂度,所以我还是打算稍微改造一下db_mysql.class.php文件,增加一个query_memcache的函数来处理memcache

代码如下

    function query_memcache($sql,$memkey='',$type='') {
        include_once DISCUZ_ROOT.'/include/memcache.inc.php';
        $mem = MemCacheForBbs::getInstance();
        $diffsql = strtolower(substr($sql, 0, 6));
        if( $diffsql == 'select') {
            //批量删除的帖子的时候,记录删除时间,用于与缓存时间对比
            if(!$last_delpost = $mem->load('last_delpost')) {
                $last_delpost = time();
                $mem->set('last_delpost', $last_delpost);
            }
            $cache_time = $mem->load(md5($memkey));
            $cache_time = empty($cache_time) ? 0 : $cache_time;
            if(!($results = $mem->load(md5($sql))) || $last_delpost >= $cache_time){
                $results = array();
                $query = $this->query($sql,$type);
                while($item = $this->fetch_array($query)){
                    $results[] = $item;
                }
                $res = $mem->set(md5($sql),$results);
                $mem->set(md5($memkey), time());
            }
            return $results;
        } elseif($diffsql == 'delete' && $memkey == 'delpost'){
            $this->query($sql,$type);
            $mem->set('last_delpost', time());
        } else {
            $this->query($sql,$type);
            if(is_array($memkey)) {
                foreach($memkey as $v) {
                    $mem->del(md5($v));
                }
            } else {
                $mem->del(md5($memkey));
            }
        }
    }

通过函数可以看到我在这里增加了两个额外的参数,一个是memkey,一个是last_delpost

memkey主要用来保存这个SQL语句的关键更新字是什么

例如在viewthread.php中读取帖子列表

原来的操作是

    $query = $db->query("SELECT p.*, m.uid, m.username, m.groupid, m.adminid, m.regdate, m.lastactivity, m.posts, m.digestposts, m.oltime,
        m.pageviews, m.credits, m.extcredits1, m.extcredits2, m.extcredits3, m.extcredits4, m.extcredits5, m.extcredits6,
        m.extcredits7, m.extcredits8, m.email, m.gender, m.showemail, m.invisible, mf.nickname, mf.site,
        mf.icq, mf.qq, mf.yahoo, mf.msn, mf.taobao, mf.alipay, mf.location, mf.medals,
        mf.sightml AS signature, mf.customstatus, mf.spacename, mcp.isshow, mcp.showmedals $fieldsadd
        FROM {$tablepre}posts p
        LEFT JOIN {$tablepre}members m ON m.uid=p.authorid
        LEFT JOIN {$tablepre}memberfields mf ON mf.uid=m.uid
        LEFT JOIN {$tablepre}medalcp mcp ON m.uid=mcp.uid
        WHERE p.tid='$tid' AND p.invisible='0' $onlyauthoradd  $pageadd");

    while($post = $db->fetch_array($query)) {
        $postlist[$post['pid']] = viewthread_procpost($post);
    }

现在可以改成

    $query = $db->query_memcache("SELECT * FROM {$tablepre}posts p WHERE p.tid='$tid' AND p.invisible='0' $onlyauthoradd  $pageadd", "tid='$tid'");
    foreach($query as $post) {
        //自定义会员信息
        $post_member = $db->query_memcache("SELECT m.uid, m.username, m.groupid, m.adminid, m.regdate, m.lastactivity, m.posts, m.digestposts, m.oltime,
        m.pageviews, m.credits, m.extcredits1, m.extcredits2, m.extcredits3, m.extcredits4, m.extcredits5, m.extcredits6,
        m.extcredits7, m.extcredits8, m.email, m.gender, m.showemail, m.invisible, mf.nickname, mf.site,
        mf.icq, mf.qq, mf.yahoo, mf.msn, mf.taobao, mf.alipay, mf.location, mf.medals,
        mf.sightml AS signature, mf.customstatus, mf.spacename  FROM {$tablepre}members m LEFT JOIN {$tablepre}memberfields mf ON m.uid=mf.uid WHERE m.uid='$post[authorid]'", "uid='$post[authorid]'");
        if(!empty($post_member[0])) {
            $post = array_merge($post,$post_member[0]);
        }
        $postlist[$post['pid']] = viewthread_procpost($post);
    }

这样我就通过tid='$tid'这个memkey来判断这个SQL的缓存是否有效,例如增加回帖的时候,指定这个memkey,那么在读取这个帖子列表的时候,就会发现memkey失效需要重新读取数据了.

last_delpost主要用来批量删除帖子的时候怎么做更新操作

帖子在进行批量删除以后,我们无法方便的知道那个SQL缓存需要更新,我这里就采用memkey的时间与,最后一次last_delpost的时间进行对比,来进行帖子列表缓存更新,这样既不用大批量删除缓存,也不需要去主动建立缓存,当用户访问的时候,来被动生成缓存就可以了.

然后就是搜索一下posts,members中的update和insert的一些操作了

2.posts分表

现在网上介绍分表的一般有,按hash分表,数据库分区,通过merge建立联合分表,根据pid到一定量进行分表.

a.如果更据pid进行hash分表的话,到一定时间还是会出现表的数据量过大的问题

b.数据库分区,由于要动数据库文件,目前条件不允许

c.用merge建立联合分表这个没有具体的测试过不知道效果

d.据根pid的量分割的话就会出现一个主题可能跨很多表的问题.

结合论坛的一些实际情况,采用根据主题的第一个帖子的pid来进行分表,然后对于同一个主题的帖子保存在通过一个分表里的方式来分表处理.

建立一个tid,pid,first(我建立的表名threadindex)这三个字段的索引对应表来记录pid的自动增加id和是否是主题贴

a.在增加主题的时候,我可以根据pid的大小来建立分表

b.在回帖的时候,我可以根据主题的第一个帖子的pid来获取分表的表名

c.对单个帖子操作的时候,可以通过帖子pid获取主题tid,再根据tid获取第一个帖子的值,从而计算出分表名

d.在批量操作帖子的时候需要对所有的分表进行操作.

优点:不需要跨表获取数据,扩展性好.程序修改相对简单

缺点:论坛的统计功能可能需要根据需求重新编写了,数量分布不均匀

这是我分表的一些操作函数

/**
 * 创建分表名
 *
 * @return return_type
 */
function getTableByPid($pid){
    global $db, $tablepre, $mem;
    //获取当前的分表前缀
    $fix = ceil($pid/2000000);
    $table = $tablepre.'posts_'.$fix;
    $query = $db->query("SHOW TABLES LIKE '$table'");
    if(!$res = $db->fetch_array($query)){
        $sql = "CREATE TABLE $table (
          pid int(10) unsigned NOT NULL AUTO_INCREMENT,
          fid smallint(6) unsigned NOT NULL DEFAULT '0',
          tid mediumint(8) unsigned NOT NULL DEFAULT '0',
          `first` tinyint(1) NOT NULL DEFAULT '0',
          author varchar(64) NOT NULL,
          authorid mediumint(8) unsigned NOT NULL DEFAULT '0',
          `subject` varchar(80) NOT NULL DEFAULT '',
          dateline int(10) unsigned NOT NULL DEFAULT '0',
          message mediumtext NOT NULL,
          useip varchar(15) NOT NULL DEFAULT '',
          invisible tinyint(1) NOT NULL DEFAULT '0',
          anonymous tinyint(1) NOT NULL DEFAULT '0',
          usesig tinyint(1) NOT NULL DEFAULT '0',
          htmlon tinyint(1) NOT NULL DEFAULT '0',
          bbcodeoff tinyint(1) NOT NULL DEFAULT '0',
          smileyoff tinyint(1) NOT NULL DEFAULT '0',
          parseurloff tinyint(1) NOT NULL DEFAULT '0',
          attachment tinyint(1) NOT NULL DEFAULT '0',
          rate smallint(6) NOT NULL DEFAULT '0',
          ratetimes tinyint(3) unsigned NOT NULL DEFAULT '0',
          `status` tinyint(1) NOT NULL DEFAULT '0',
          PRIMARY KEY (pid),
          KEY fid (fid),
          KEY authorid (authorid),
          KEY dateline (dateline),
          KEY invisible (invisible),
          KEY displayorder (tid,invisible,dateline),
          KEY `first` (tid,`first`)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
        ";
        $db->query($sql);
        $mem->del(md5('AllPostTable'));
    }
    return $table;   
}

/**
 * 得到最新帖子ID
 *
 * @return return_type
 */
function getNewPid($tid,$first=0) {
    global $db, $tablepre, $mem;
    $db->query("INSERT INTO {$tablepre}threadindex (`tid`,`first`) VALUES ('$tid', '$first')");
    $mem->del(md5('threadindex-tid-'.$tid));
    $newPid = $db->insert_id();
    return $newPid;
}

function getTidbyPid($pid) {
    global $db,$tablepre, $mem;
    if(!$res_tid = $mem->load(md5('threadindex-pid-'.$pid))) {
        $res_tid =  $db->result_first("SELECT tid FROM {$tablepre}threadindex WHERE pid='$pid'");
        $mem->set(md5('threadindex-pid-'.$pid), $res_tid);
    }
    return $res_tid;
}


/**
 * 获取某个主题帖子对应的分表名
 *
 * @return return_type
 */
function getTableByTid($tid) {
    global $db, $tablepre, $mem;
    if(empty($tid)) {
        return 'cdb_posts';
    }
    if(!$item = $mem->load(md5('threadindex-tid-'.$tid))) {
        $item = $db->result_first("SELECT pid FROM {$tablepre}threadindex WHERE tid='$tid' AND first=1");
        if(empty($item)) {
            return 'cdb_posts';
        }
        $mem->set(md5('threadindex-tid-'.$tid), $item);
    }
    $fix = ceil($item/2000000);
    return $tablepre.'posts_'.$fix;;
}

/**
 * 返回所有的分表
 *
 * @return return_type
 */
function getAllTables() {
    global $db, $tablepre, $mem;
    if(!$tablearr = $mem->load(md5('AllPostTable'))) {
        $query = $db->query("SHOW TABLES LIKE '{$tablepre}posts_%'");
        $tablearr = array();
        while($table = $db->fetch_array($query, MYSQL_BOTH)) {
            $tablearr[] = $table['0'];
        }
        $mem->set(md5('AllPostTable'),$tablearr);
    }
    return $tablearr;
}

这里还可以对threadindex再建立一个分表threadindex_1,例如pid>10000000 || tid > 1000000,就把数据的获取和记录去threadindex_1里获取(需要确定最新的帖子的都要插入到threadindex_1里)
目前公司的论坛现在memcache在97%以上,论坛的浏览和回帖也很流畅

ps:

出现的问题:当编辑某个帖子第N页内容以后,不是跳转到当前,而是跳转到第一页的时候,会出现缓存不更新的情况

 

 2011/10/24

PS:

可以增加一个sqlkey的存储时间戳,用来和memkey比较进行更新,如果memcache和web不在一台服务器的话,可以把last_delpost的值保存在本地文件,从而减少memcache的网络请求

分享到:
评论

相关推荐

    Discuz!下Memcache缓存实现方法

    本文主要探讨如何通过Memcache缓存技术来减轻MySQL数据库的压力,从而优化系统性能。Memcache是一种高性能的分布式内存对象缓存系统,能够将数据库查询结果暂存于内存中,避免重复查询,从而提高网站响应速度。 ...

    SpringBoot集成常用开发中间件,分库分表,缓存,消息队列,定时器,权限管理等组件

    本文将深入探讨标题和描述中提及的几个关键知识点:中间件集成、分库分表、缓存、消息队列以及定时任务,以及权限管理。 首先,我们来谈谈"中间件"。中间件是连接软件系统的不同部分,提供通用服务的组件,如数据...

    Python+MySQL分表分库实战

    首先,我们需要明确什么是分库分表,以及为什么我们需要进行分库分表。分库分表是将原本存储在一个数据库中的数据按照某种规则分散存储到多个数据库或表中的过程。这种做法可以有效减轻单个数据库的压力,提高数据...

    MySQL分表及分表后插入sql

    MySQL分表及分表后插入sql语句,表为订单表,可以参考一下

    TP5+MySQL通用分表代码

    - 分表情况,2:日期分表,按照目标表里面的记录日期的字段,按照日期【日、周、月、年】拆分成多个表【本代码仅考虑unix时间戳来分表,其它不支持,您可以自己思考,自己修改代码】 - 注意,支持【子表】,填入...

    分表 JAVA 分表例子,带SQL文件

    在IT行业中,数据库分表是一种常见的优化策略,用于解决大数据量和高并发场景下的性能问题。本示例主要探讨了如何使用Java实现分表,并且提供了SQL文件作为辅助理解。以下将详细介绍Java分表的基本概念、重要性以及...

    分库分表自动建库表小工具

    为了应对这一挑战,分库分表成为了一种有效的数据库架构优化策略。本文将详细介绍一款能够自动创建分库分表的工具——“分库分表自动建库表小工具”,并探讨其功能、优势及最新更新内容。 首先,分库分表是一种...

    数据库分库分表

    因此,在实施分库分表时,需要综合考虑业务需求、数据规模、系统架构以及团队的技术能力,选择合适的分库分表策略,并配合相应的中间件(如MyCat、ShardingSphere等)来简化开发和维护工作。 总的来说,数据库分库...

    数据库优化之分表.avi

    mysql教程 数据库优化之分表.avi

    kettle对数据分表插入

    本篇文章将深入探讨如何利用Kettle进行数据分表插入,并结合Oracle数据库、Java脚本以及哈希算法来实现这一目标。 首先,让我们理解什么是数据分表。在大数据场景下,单一的大表可能会导致性能瓶颈,因此通常会采用...

    Mysql优化、MyCat搭建、分库分表、读写分离、负载均衡

    例如,调整innodb_buffer_pool_size来优化内存使用,调整max_connections控制并发连接数,以及优化query_cache_size等。每个参数都需要根据实际系统负载和硬件资源进行设定。 MyCat是一款开源的分布式数据库中间件...

    MySQL分库分表技术

    **MySQL分库分表技术** 随着互联网业务的快速发展,数据量呈现爆炸性增长,单个数据库的性能...实践中需要根据业务特性选择合适的分片策略,并结合其他优化措施,如缓存、读写分离等,共同构建高效稳定的数据库架构。

    数据分库分表之二叉树分库分表

    ### 数据分库分表之二叉树分库分表 #### 一、引言与背景 随着互联网技术的快速发展及用户需求的激增,单一数据库系统已难以应对日益增长的数据处理需求。为了提高系统的可扩展性和性能,数据分库分表成为了一种...

    hibernate动态分表

    1. 配置:在Hibernate的配置文件中,需要指定数据库连接信息,以及可能的分表相关的配置参数,如分片规则、分区字段等。这些配置可以根据具体的需求进行定制。 2. 实体类设计:实体类需要包含用于分表的属性,并...

    java分库分表源码

    Java 分库分表是一种在大数据量场景下优化数据库性能的技术,它通过将单一的大表分成多个小表,分散存储在不同的数据库中,从而降低单表数据量,提高查询效率,减轻数据库的压力。在这个"java分库分表源码"项目中,...

    大数据表的分表处理设计思想和实现(MySQL)

    为了解决这一问题,分表设计成为了数据库优化的重要手段。分表能够有效地分散数据库的压力,提高查询速度,并简化维护工作。 ### 一、为什么要分表? 1. **性能提升**:大型表在执行SQL时,由于数据量大,索引扫描...

    msyql分表sql,导入即可

    MySQL数据库在处理大数据量时,为了提高查询...总的来说,"msyql分表sql,导入即可"是一个实用的数据库优化方案,它旨在解决大数据量带来的性能问题。通过合理的设计和执行,可以显著提升数据库的处理能力和响应速度。

    spring动态数据源+mybatis分库分表

    在现代企业级应用中,随着数据量的增长,单表存储可能会遇到性能瓶颈,这时就需要引入分库分表的策略来优化数据库的性能。"spring动态数据源+mybatis分库分表"是一个针对大型数据库场景的解决方案,它利用Spring框架...

Global site tag (gtag.js) - Google Analytics