Don 发布的文章

开启Memcache缓存后后台社区QQ群搜索不到帖子的问题排查

问题描述:

此问题发现于Discuz! X2.5版本。
开启了Memcache缓存,后台社区QQ群推送消息页面按tid搜索不到帖子或搜索出来的帖子不对,如图。
开启Memcache缓存后后台社区QQ群按tid搜索帖子出现其他帖子

问题分析:

找到source\admincp\cloud\cloud_qqgroup.php文件,有如下代码:

if($srchtid) {
	$threads = C::t('forum_thread')->fetch_all_by_tid_displayorder($srchtid, 0);
}

这里会调用fetch_all_by_tid_displayorder方法,找到source\class\table\table_forum_thread.php文件,有如下代码:

public function fetch_all_by_tid_displayorder($tids, $displayorder = null, $glue = '>=', $fids = array(), $closed = null) {
    $data = array();
    if(!empty($tids)) {
        $data = $this->fetch_all_by_tid($tids);
        $fids = $fids && !is_array($fids) ? array($fids) : $fids;
        foreach($data as $tid => $value) {
            if($displayorder !== null && !(helper_util::compute($value['displayorder'], $displayorder, $glue))) {
                unset($data[$tid]);
            } elseif(!empty($fids) && !in_array($value['fid'], $fids)) {
                unset($data[$tid]);
            } elseif($closed !== null && $value['closed'] != $closed) {
                unset($data[$tid]);
            }
        }
    }
    return $data;
}

注意这个函数的参数:$tids应该为数组。这里调用了fetch_all_by_tid方法,还是这个文件,有如下代码:

public function fetch_all_by_tid($tids, $start = 0, $limit = 0, $tableid = 0) {
    $data = array();
    if(($data = $this->fetch_cache($tids)) === false || count($tids) != count($data)) {
        if(is_array($data) && !empty($data)) {
            $tids = array_diff($tids, array_keys($data));
        }
        if($data === false) $data = array();
        if(!empty($tids)) {
            $parameter = array($this->get_table_name($tableid), $tids);
            $query = DB::query("SELECT * FROM %t WHERE tid IN(%n)".DB::limit($start, $limit), $parameter);
            while($value = DB::fetch($query)) {
                $data[$value['tid']] = $value;
                $this->store_cache($value['tid'], $value, $this->_cache_ttl);
            }
        }
    }
    return $data;
}

注意这个函数的参数:$tids应该为数组。这里又调用到fetch_cache和store_cache方法。找到source\class\discuz\discuz_table.php文件,里面有这两个方法的定义,

public function fetch_cache($ids, $pre_cache_key = null) {
    $data = false;
    if($this->_allowmem) {
        if($pre_cache_key === null)	$pre_cache_key = $this->_pre_cache_key;
        $data = memory('get', $ids, $pre_cache_key);
    }
    return $data;
}

public function store_cache($id, $data, $cache_ttl = null, $pre_cache_key = null) {
    $ret = false;
    if($this->_allowmem) {
        if($pre_cache_key === null)	$pre_cache_key = $this->_pre_cache_key;
        if($cache_ttl === null)	$cache_ttl = $this->_cache_ttl;
        $ret = memory('set', $id, $data, $cache_ttl, $pre_cache_key);
    }
    return $ret;
}

这两个方法最终调用缓存类的get和set方法,找到source\class\discuz\discuz_memory.php文件,有如下代码:

public function get($key, $prefix = '') {
    static $getmulti = null;
    $ret = false;
    if($this->enable) {
        if(!isset($getmulti)) $getmulti = method_exists($this->memory, 'getMulti');
        $this->userprefix = $prefix;
        if(is_array($key)) {
            if($getmulti) {
                $ret = $this->memory->getMulti($this->_key($key));
                //格式化数组的KEY,去掉表前缀
                if($ret !== false && !empty($ret)) {
                    $_ret = array();
                    foreach((array)$ret as $_key => $value) {
                        $_ret[$this->_trim_key($_key)] = $value;
                    }
                    $ret = $_ret;
                }
            } else {
                $ret = array();
                $_ret = false;
                //循环取值
                foreach($key as $id) {
                    if(($_ret = $this->memory->get($this->_key($id))) !== false && isset($_ret)) {
                        $ret[$id] = $_ret;
                    }
                }
            }
            //无值返回false
            if(empty($ret)) $ret = false;
        } else {
            $ret = $this->memory->get($this->_key($key));
            if(!isset($ret)) $ret = false;
        }
    }
    return $ret;
}

public function set($key, $value, $ttl = 0, $prefix = '') {

    $ret = false;
    if($value === false) $value = '';
    if($this->enable) {
        $this->userprefix = $prefix;
        $ret = $this->memory->set($this->_key($key), $value, $ttl);
    }
    return $ret;
}

注意get方法里,有对$key进行是否为数组的判断,如下代码:

if(is_array($key)) {

这里如果是数组则返回的结果为二维数组,key => value的格式;反之返回的结果是一维数组,只有value。
通过记录log发现source\admincp\cloud\cloud_qqgroup.php文件调用fetch_all_by_tid_displayorder方法的返回结果为一维数组,只有value,而正常来说fetch_all_by_tid_displayorder方法返回的结果应该是二维数组结构。

所以推断是由于source\admincp\cloud\cloud_qqgroup.php文件调用fetch_all_by_tid_displayorder方法时传递的$srchtid参数类型非数组导致的问题,进一步分析发现是由于Discuz! 所有要求参数为数组的地方没有进行是否是数组类型的判断导致。

解决方法:

不给出暂时的解决方法了,静待Discuz! 修复。

Windows下安装Memcache

昨天有人反应开启Memcache后社区QQ群的搜索功能不正常,于是乎在本机搞了个Memcache。

Windows下安装Memcache所需要的文件:
http://url.cn/3YxNfr

安装服务端的Memcache服务:

下载前面的附件,解压后将其中的memcached.exe传到对应的Memcache目录,如C:\memcache。
然后进入cmd命令行窗口,执行下面的命令:

C:\memcache\memcached.exe -d install

执行后如果没有报错就安装成功了,然后开启Memcache服务,执行下面命令:

C:\memcache\memcached.exe -d start

启动后可以在系统的服务里找到memcache服务。

安装PHP的Memcache扩展:

将前面下载的附件里的php_memcache.dll传到PHP的扩展目录,如C:\php\ext\.
找到php.ini文件,搜索下面这句

;extension=php_memcache.dll  

改为

extension=php_memcache.dll  

如果搜索不到,则直接添加这一句。

修改后重启Apache,然后开始测试你的Memcache吧。

参考文章:
http://www.ccvita.com/258.html (中文,可惜里面提到的链接都已经失效)
http://www.leonardaustin.com/technical/how-to-install-memcached-on-xampp-on-windows-7 (英文)

[一种声音]爸爸,对不起!我北大毕业,但我没能挣大钱、当大官

转载自http://www.dapenti.com/blog/more.asp?name=xilei&id=63955

《爸爸,对不起,我北大毕业,但我没能挣大钱、当大官》。北京大学未名BBS的匿名板块(SecretGarden)上的一个帖子,引发大家纷纷吐槽。这是一个沉重而永恒的话题……“父母跟人拼了20多年儿女,学习都是最好的,他们觉得赢了。儿女毕业工作后他们才知道,生活上还是拼不过煤老板的儿女。”

[caption id="attachment_976" align="alignnone" width="440"]爸爸,对不起!我北大毕业,但我没能挣大钱、当大官 爸爸,对不起!我北大毕业,但我没能挣大钱、当大官[/caption]

Discuz! X1.5版本的缓存丢失问题排查

下午有同事反映之前升级的两个站点开通了QQ互联后出现缓存丢失问题,有一个站点排查发现是由于QQ昵称的特殊字符导致,整理排查过程。

问题描述:

使用QQ登录注册后(QQ昵称带有特殊字符,假设为),论坛缓存出现丢失情况,手动更新后恢复正常,丢失情况类似下图:
[caption id="attachment_971" align="alignnone" width="300"]Discuz! 缓存丢失后的页面 Discuz! 缓存丢失后的页面[/caption]

排查分析:

首先看前台显示,发现注册的“精€€ぷ灵€€”用户名显示成了“精”,进入数据库里的pre_common_member表中确定当前注册的用户名是什么,如图:
[caption id="attachment_972" align="alignnone" width="300"]用户表记录 用户表记录[/caption]

从图可以看出来,注册的“精€€ぷ灵€€”用户名到数据库里变成了“精”,推测是由于Mysql把特殊字符后面的所有东西全部干掉导致。

注册用户时会更新缓存表里的userstats记录,进入pre_common_syscache表里找到对应记录,如图:
[caption id="attachment_973" align="alignnone" width="300"]缓存表记录 缓存表记录[/caption]

从记录可以看出来,这里的序列化结果断掉了,同样的也是从特殊字符开始后面的所有东西都没有了。

至于为什么手动更新缓存就会好,是由于手动更新缓存的时候,最后注册会员的名字是从用户表中查询出来然后再序列化写入缓存表,用户表中的用户名是没有特殊字符的,所以写入缓存表的数据结构是完整的,所以缓存没有问题,有兴趣的童鞋可以参考source\function\cache\cache_userstats.php文件代码。

原因猜测:

个人猜测是由于Mysql不支持特殊字符,插入的带有特殊字符数据,Mysql会将特殊字符及后面的数据全部扔掉。
由于用户名中带有特殊字符,所以缓存表里的记录的序列化值中也会含有特殊字符,同样的抛弃特殊字符及后面的数据,就会导致序列化结构不完整,进而导致前台显示的时候由于反序列化失败导致缓存不完整。

解决方法:

修复普通注册:找到source\module\member\member_register.php文件,搜索下面的代码:

$totalmembers = DB::result_first("SELECT COUNT(*) FROM ".DB::table('common_member'));
$userstats = array('totalmembers' => $totalmembers, 'newsetuser' => $username);

save_syscache('userstats', $userstats);

改为:

require_once libfile('cache/userstats', 'function');
build_cache_userstats();

修复QQ互联注册(仅针对Discuz! X1.5的QQ互联插件):找到source\module\connect\connect_register.php文件,搜索代码:

$totalmembers = DB::result_first("SELECT COUNT(*) FROM ".DB::table('common_member'));
$userstats = array('totalmembers' => $totalmembers, 'newsetuser' => $username);

save_syscache('userstats', $userstats);

改为:

require_once libfile('cache/userstats', 'function');
build_cache_userstats();

搜索代码:

$_G['setting']['lastmember'] = $username;
save_syscache('setting', $_G['setting']);

删除这段代码。

以上的方法可以解决缓存丢失的问题,但是根本的解决方法应该是在用户注册时过滤特殊字符。