Showing posts with label php_memcached. Show all posts
Showing posts with label php_memcached. Show all posts

Monday, March 29, 2010

关于Libmemcached 编译

上一篇相关的,/2010/02/libmemcached-undefined-reference-to.html
这个是在CentOS 5.4上的编译。

但是这次是在 CentOS 4.6 上。GCC 版本 3.4.6。用上篇提供的方法,依旧是错。
这里测试了 0.37 和 0.38 两个版本。
编译参数如下:
./configure  --with-libevent-prefix=/usr/local/libevent --with-memcached --disable-64bit CFLAGS="-O3 -march=i686"


CCLD   clients/memslap
clients/ms_conn.o(.text+0x24b): In function `ms_conn_close':
clients/ms_conn.c:685: undefined reference to `__sync_fetch_and_sub'
clients/ms_conn.o(.text+0xc8e): In function `ms_setup_conn':
clients/ms_conn.c:380: undefined reference to `__sync_fetch_and_add'
clients/ms_conn.o(.text+0x11af): In function `ms_reconn_socks':
clients/ms_conn.c:1051: undefined reference to `__sync_fetch_and_add'
clients/ms_conn.o(.text+0x1351): In function `ms_verify_value':
clients/ms_conn.c:1808: undefined reference to `__sync_fetch_and_add'
clients/ms_conn.o(.text+0x13f8):clients/ms_conn.c:1767: undefined reference to `__sync_fetch_and_add'
clients/ms_conn.o(.text+0x17f4): In function `ms_build_udp_headers':
clients/ms_conn.c:194: undefined reference to `__sync_fetch_and_add'
clients/ms_conn.o(.text+0x1d46):clients/ms_conn.c:919: more undefined references to `__sync_fetch_and_add' follow
collect2: ld returned 1 exit status
make[2]: *** [clients/memslap] Error 1
make[2]: Leaving directory `/data2/space/libmemcached-0.38'
make[1]: *** [all-recursive] Error 1
make[1]: Leaving directory `/data2/space/libmemcached-0.38'
make: *** [all] Error 2


和上次的错误略有不同。但是出错都是在 memslap 这个文件的编译上。
往上翻了翻,看到,所有的Warnings和Errors都是在 clients 下的文件编译上,而libmemcached 和 libhashkit 没问题。

于是,解决方案自然是把 clients 下所有的应用程序的编译干掉。
一开始直接去改configure出来的Makefile,太费劲了。于是只得去改 Makefile.am 然后去 automake 重新生产 Makefile.in
但是 CentOS 4.6 和 5.4 的两个系统的 autoconf 都是 2.59 的版本,改过的 Makefile.am
autoconf
aclocal.m4:20: warning: this file was generated for autoconf 2.63.
You have another version of autoconf.  It may work, but is not guaranteed to.
If you have problems, you may need to regenerate the build system entirely.
To do so, use the procedure documented by the package, typically `autoreconf'.
configure.ac:16: error: Autoconf version 2.62 or higher is required
aclocal.m4:530: AM_INIT_AUTOMAKE is expanded from...
m4/pandora_canonical.m4:246: PANDORA_CANONICAL_TARGET is expanded from...
configure.ac:16: the top level
autom4te: /usr/bin/m4 failed with exit status: 63


换到一台 Ubuntu Lucid Lynx 的机器上,autoconf 2.69, 修改 Makefile.am,注释掉了
include clients/include.am
include tests/include.am
include example/include.am
include support/include.am


重新 aclocal; autoreconf; automake .
打包扔到开发机上,编译顺利完成。


另外,下午验证了,libmemcached 0.38 + pecl memcached 1.0.1 在 CentOS 4.6上也出现了连接无法结束的情况/2010/03/tests-about-using-tokyotyrant-with-pecl.html

Tests about using tokyotyrant with pecl memcached.

CentOS: 5.4
PHP: 5.2.13
Apache: 2.2.14
TokyoTyrant: 1.1.39

libmemcached pecl memcached result
0.37 1.0.0 works perfectly.

0.37 1.0.1 works perfectly on cli mode
zend_mm_heap corrupted in apache log(non-debug php compilation)
apache SegFaults (php --enable-debug)

0.37 1.0.2(?) works perfect.
======================================================================

0.38 1.0.0 Cannot disconnect after script execution

0.38 1.0.1 Cannot disconnect after script execution

0.38 1.0.2(?) Cannot disconnect after script execution


1.0.2(?) is the github version from http://github.com/tricky/php-memcached by Teddy(commit 078544ae226313dbdf56775bc004136c7e6b6e28).

Friday, March 12, 2010

pecl memcached 处理默认参数就是一悲剧

这错误也怪我,之前在这个扩展的不同的地方栽了一下,后来还没注意。
代码贴在了 pastebin.com 。。。 可怜的 pastebin.ca,也在墙外了。。。

虽然贴了代码,还是有必要简要提一下,要不然哪天pastebin.com也在墙外了。
<?php
class spdLibMemcached {
private $mc;
public function __construct($persistent_id=null) {
$this->mc = new Memcached($persistent_id);
}
public function addServer($host, $port, $weight=null) {
return $this->mc->addServer($host, $port, $weight);
}
public function getServerList() {
return $this->mc->getServerList();
}
}


$a = new memcached;
$b = new memcached;
$a->addServer('10.1.0.55', 22201);
$b->addServer('10.1.3.52', 11211);

$c = new spdlibmemcached;
$d = new spdlibmemcached;
$c->addServer('10.1.0.55', 22201);
$d->addServer('10.1.3.52', 11211);

var_dump(
$a->getServerList(),
$b->getServerList(),
$c->getServerList(),
$d->getServerList()
);

你会发现 $c 和 $d 取得的结果是 。。。 两个值。。。。

刚才还去麻烦小天天看代码,也是没看出问题。

最后索性继续精简代码,写了个最简单的例子,居然是好的。一细看:
new Memcached; 和 new Memcached(null);
根本不是一回事。。。

于是构造的时候必须

<?php
class spdLibMemcached {
private $mc;
public function __construct($persistent_id=null) {
if ($persistent_id === null) {
$this->mc = new Memcached;
} else {
$this->mc = new Memcached($persistent_id);
}
}
}



继续改其他的去。。。

Sunday, February 21, 2010

使用 php_memcached 连 memcacheq 遇到的诡异问题

使用 php_memcached 连 memcacheq 遇到的诡异问题


服务器环境:
Centos 5.4 http://centos.org/
PHP 5.3.1 http://www.php.net/
php_memcached 1.0.0 http://pecl.php.net/package/memcached
libmemcached 0.37 http://tangent.org/552/libmemcached.html
libevent 1.4.13 http://www.monkey.org/~provos/libevent/
Memcacheq 0.2.0 http://memcachedb.org/memcacheq/

给 php_memcached 做了一次封装。其中有个方法是这样写的

<?php

/**
* Memcached Class
*
*/
class spAMLMemcached
{
private $mc;

......
/**
* get方法,增加批量模式
*
* @param string|array $key
* @param callback $cache_cb 批量模式不可用
* @param float|array $cas_token
* @param int $flags
* @return mixed
*/
public function get($key, $cache_cb=null, &$cas_token=null, $flags=null)
{
if (is_array($key)) {
return $this->mc->getMulti($key, $cas_token, $flags);
} else {
return $this->mc->get($key, $cache_cb, $cas_token);
}
}
......
}


方法看起来貌似没问题。
关于 Memcached::get 的说明:link to php manual;

Memcached::get

(PECL memcached >= 0.1.0)

Memcached::get — Retrieve an item

Description

public mixed Memcached::get ( string $key [, callback $cache_cb [, float &$cas_token ]] )
Memcached::get() returns the item that was previously stored under the key . If the item is found and cas_token variable is provided, it will contain the CAS token value for the item. See Memcached::cas for how to use CAS tokens. Read-through caching callback may be specified via cache_cb parameter.

Parameters

key
The key of the item to retrieve.

cache_cb
Read-through caching callback or NULL.

cas_token
The variable to store the CAS token in.

Return Values

Returns the value stored in the cache or FALSE otherwise. The Memcached::getResultCode will return Memcached::RES_NOTFOUND if the key does not exist.



从这里看也没啥问题。
但是,实际调用的时候,用了这样的代码。
<?php
$a = new spAMLMemcached;
$a->addServer('10.**.*.**', 22201);
var_dump($a->get('example'), $a->getResultCode());
var_dump($a->get('example', null), $a->getResultCode());


结果很遗憾,两个返回的都是false。
getResultCode 出来的结果是 8 即 Memcached::RES_PROTOCOL_ERROR 协议错误。

无法理解。
以前也没遇到过这个问题啊。
tcpdump 抓包看了看
tcpdump -XXAfN -ttt -i eth3  "ip dst 10.**.*.**"

包的数据居然是 。。。
..gets.example..


gets ...

如果仅仅是对 memcached 而言,这个没问题。高版本的memcached 都支持这个。
但是。。。用的是 Memcacheq
根本就不支持这个。
于是协议错误。。。


好吧,查原因。

最后写了个简单的测试代码。
<?php
$s = new Memcached;
$s->addServer('10.**.*.**', 22201);
var_dump($s->get('example'), $s->getResultCode());
var_dump($s->get('example', null), $s->getResultCode());

输出一切正常。
于是不由得怀疑起封装时对 &$cas_token=null 的处理。

做了如下的测试代码。

<?php
$q = new Memcached;
$q->addServer('10.**.*.**', 22201);
var_dump($q->get('example'), $q->getResultCode());
var_dump($q->get('example', null), $q->getResultCode());


echo "\nmcdgettest: \n";
var_dump(mcdgettest('example'), $q->getResultCode());
var_dump(mcdgettest('example', null), $q->getResultCode());

echo "\nmcdgettest1: \n";
var_dump(mcdgettest1('example'), $q->getResultCode());

echo "\nmcdgettest2: \n";
var_dump(mcdgettest2('example'), $q->getResultCode());

echo "\nmcdgettest3: \n";
var_dump(mcdgettest3('example'), $q->getResultCode());

echo "END\n\n";
unset($q);

$q = new Memcached;
$q->addServer('10.**.*.**', 22201);
var_dump($q->get('example'), $q->getResultCode());
var_dump($q->get('example', null), $q->getResultCode());

echo "\nmcdgettest3: \n";
var_dump(mcdgettest3('example'), $q->getResultCode());

echo "\nmcdgettest2: \n";
var_dump(mcdgettest2('example'), $q->getResultCode());

echo "\nmcdgettest1: \n";
var_dump(mcdgettest1('example'), $q->getResultCode());

echo "\nmcdgettest: \n";
var_dump(mcdgettest('example'), $q->getResultCode());
var_dump(mcdgettest('example', null), $q->getResultCode());

echo "END\n\n";
unset($q);

$q = new Memcached;
$q->addServer('10.**.*.**', 22201);
var_dump($q->get('example'), $q->getResultCode());
var_dump($q->get('example', null), $q->getResultCode());

echo "\nmcdgettest3: \n";
var_dump(mcdgettest3('example'), $q->getResultCode());

echo "\nmcdgettest1: \n";
var_dump(mcdgettest1('example'), $q->getResultCode());

echo "\nmcdgettest2: \n";
var_dump(mcdgettest2('example'), $q->getResultCode());

echo "\nmcdgettest: \n";
var_dump(mcdgettest('example'), $q->getResultCode());
var_dump(mcdgettest('example', null), $q->getResultCode());

echo "END\n\n";
unset($q);



function mcdgettest($key, $cache_cb=null, &$cas_token=null) {
global $q;
return $q->get($key, $cache_cb, $cas_token);
}

function mcdgettest1($key, $cache_cb=null) {
global $q;
return $q->get($key, $cache_cb);
}

function mcdgettest2($key, &$cas_token=null) {
global $q;
return $q->get($key, null, $cas_token);
}

function mcdgettest3($key) {
global $q;
return $q->get($key);
}



结果如下:

[root@localhost 3.53]: /data1/www/htdocs/i.sina.com.cn/source/apps/daemon/cron
0> php testMemcachedGet.php
int(140)
int(0)
int(141)
int(0)

mcdgettest:
bool(false)
int(8)
bool(false)
int(8)

mcdgettest1:
bool(false)
int(8)

mcdgettest2:
bool(false)
int(8)

mcdgettest3:
bool(false)
int(8)
END

int(142)
int(0)
int(143)
int(0)

mcdgettest3:
int(144)
int(0)

mcdgettest2:
bool(false)
int(8)

mcdgettest1:
bool(false)
int(8)

mcdgettest:
bool(false)
int(8)
bool(false)
int(8)
END

int(145)
int(0)
int(146)
int(0)

mcdgettest3:
int(147)
int(0)

mcdgettest1:
int(148)
int(0)

mcdgettest2:
bool(false)
int(8)

mcdgettest:
bool(false)
int(8)
bool(false)
int(8)
END


另外一边抓包

[root@localhost 3.53]: /data1/www/htdocs/i.sina.com.cn/source/datadrv/memcacheq
130> tcpdump -XXAfN -ttt -i eth3 "ip dst 10.**.*.**" | grep get
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth3, link-type EN10MB (Ethernet), capture size 96 bytes
0x0040: b19b 6765 7420 6578 616d 706c 6520 0d0a ..get.example...
0x0040: b19c 6765 7420 6578 616d 706c 6520 0d0a ..get.example...
0x0040: b19c 6765 7473 2065 7861 6d70 6c65 200d ..gets.example..
0x0040: b19d 6765 7473 2065 7861 6d70 6c65 200d ..gets.example..
0x0040: b19d 6765 7473 2065 7861 6d70 6c65 200d ..gets.example..
0x0040: b19e 6765 7473 2065 7861 6d70 6c65 200d ..gets.example..
0x0040: b19e 6765 7473 2065 7861 6d70 6c65 200d ..gets.example..
0x0040: b19f 6765 7420 6578 616d 706c 6520 0d0a ..get.example...
0x0040: b19f 6765 7420 6578 616d 706c 6520 0d0a ..get.example...
0x0040: b19f 6765 7420 6578 616d 706c 6520 0d0a ..get.example...
0x0040: b1a0 6765 7473 2065 7861 6d70 6c65 200d ..gets.example..
0x0040: b1a0 6765 7473 2065 7861 6d70 6c65 200d ..gets.example..
0x0040: b1a1 6765 7473 2065 7861 6d70 6c65 200d ..gets.example..
0x0040: b1a1 6765 7473 2065 7861 6d70 6c65 200d ..gets.example..
0x0040: b1a2 6765 7420 6578 616d 706c 6520 0d0a ..get.example...
0x0040: b1a2 6765 7420 6578 616d 706c 6520 0d0a ..get.example...
0x0040: b1a2 6765 7420 6578 616d 706c 6520 0d0a ..get.example...
0x0040: b1a2 6765 7420 6578 616d 706c 6520 0d0a ..get.example...
0x0040: b1a3 6765 7473 2065 7861 6d70 6c65 200d ..gets.example..
0x0040: b1a3 6765 7473 2065 7861 6d70 6c65 200d ..gets.example..
81 packets captured
82 packets received by filter
0 packets dropped by kernel


对着一行行看吧。

第一个结论很明显:对于每组测试,只要有一次进入 gets 那后边全都是 gets。
第二个结论就得一点点比较分析了,结论是:只要对 $cas_token 这个引用传值的参数进行了封装,后续的结果就会出错。

为了验证这个错误会不会对最基本的调用产生影响,我在 第二组测试的 321 的 2后边 直接 调用了 $q->get('example') 很遗憾,又是一个 protocol error.

而且,不知道大家注意了没有,从测试脚本输出的最后的false有三个,而抓包只有两个gets。我试着不在结束的时候ctrl+c,然后对跑了两次测试,终于最后位置有 三个 gets。


从源码分析。
首先看 libmemcached。因为 gets 的操作命令来自这里。
get.c 中:Line200 memcached_mget_by_key_real() 中

if (ptr->flags.support_cas)
{
get_command= "gets ";
get_command_length= 5;
}

这是唯一的有 gets 的地方。
于是下一步查哪儿修改了 ptr->flags.support_cas 的这个标志位。
定位到 behavior.c line 81 memcached_behavior_set() 函数内

case MEMCACHED_BEHAVIOR_SUPPORT_CAS:
ptr->flags.support_cas= set_flag(data);
break;


从 php_memcached 的 php_memcached.c 中 找到几次 MEMCACHED_BEHAVIOR_SUPPORT_CAS 的设置。
分别在

static void php_memc_get_impl(INTERNAL_FUNCTION_PARAMETERS, zend_bool by_key)
static void php_memc_getMulti_impl(INTERNAL_FUNCTION_PARAMETERS, zend_bool by_key)
static void php_memc_getDelayed_impl(INTERNAL_FUNCTION_PARAMETERS, zend_bool by_key)

第一个是 Memcached::get 方法所在的位置。



if (cas_token) {

uint64_t orig_cas_flag;

/*
* Enable CAS support, but only if it is currently disabled.
*/
orig_cas_flag = memcached_behavior_get(i_obj->memc, MEMCACHED_BEHAVIOR_SUPPORT_CAS);
if (orig_cas_flag == 0) {
memcached_behavior_set(i_obj->memc, MEMCACHED_BEHAVIOR_SUPPORT_CAS, 1);
}
......

/*
* Restore the CAS support flag, but only if we had to turn it on.
*/
if (orig_cas_flag == 0) {
memcached_behavior_set(i_obj->memc, MEMCACHED_BEHAVIOR_SUPPORT_CAS, orig_cas_flag);
}
return;

} else {
......

如果 $cas_token 这个变量传入了 就会进到这个if判断里。首先会强制开启 MEMCACHED_BEHAVIOR_SUPPORT_CAS 这个选项,当操作完整结束的时候,再将这个选项恢复。
这个的唯一的问题在于,Line 395

if (php_memc_handle_error(status TSRMLS_CC) < 0) {
memcached_result_free(&result);
RETURN_FALSE;
}

出错退出的时候,直接 free result 然后 return false,并未将 这个选项恢复。
等到下一次取这个对象的时候,因为这次已经设为真,即便下一次没走cas_token 的这个值,但是调用 memcached_mget_by_key 的时候,原始这个选项还是会污染新的操作,因为这一次不涉及到恢复这个选项的问题。
这就能解释 为什么 按 3 2 1 的顺序执行的代码 1 会被污染成protocol error.
解决方案是在
php_memcached.c line 395 :


if (php_memc_handle_error(status TSRMLS_CC) < 0) {
memcached_result_free(&result);
RETURN_FALSE;
}


RETURN_FALSE 前 做一次恢复。



if (php_memc_handle_error(status TSRMLS_CC) < 0) {
memcached_result_free(&result);

/*
* Restore the CAS support flag, but only if we had to turn it on.
*/
if (orig_cas_flag == 0) {
memcached_behavior_set(i_obj->memc, MEMCACHED_BEHAVIOR_SUPPORT_CAS, orig_cas_flag);
}

RETURN_FALSE;
}