ImageMagick 的php扩展 imagick段错误解决
imagick 是 PHP 下针对 ImageMagick 这个强大软件包的 API 接口,如果你在编译 ImageMagick 的时候将 IMAGEMAGICK_JPEG2000 编译进去了,你的 PHP 在启用 MagickWand 模块后会发生段错误,无法正常使用 PHP。
解决方法也就显而易见,在通过 源码安装的时候加上配置 --without-jp2,即:
./configure --without-jp2,安装即可。
Support NginxWiki-20110303.chm to download
Nginx more and more popular, but official wiki have too much time to chm[NginxWiki-20090731.chm], so I compiled the latest sync to the wiki into chm, available to the offline-read students who needed.
Download:
NginxWiki-20110303.chm.tar.gz
【转】设置自动重连的ssh代理通道
我目前常用的翻墙办法就是拿ssh搭个代理通道,然后chrome + switch!插件一起配合,这就算翻墙了。这法子只要拿个机器跑一小脚本,比如:
ssh -D 7070 -qnN [username]@[server]
但是ssh通道如果闲置了一段时间,就会自动断连,等我需要用到代理的时候往往又得蛋疼的重新跑一遍,非常麻烦。所以我刻苦学习前辈的经验,找到一个解决办法,在mac或linux下都可使用,分享如下:
- 把ssh配置为免密码登录,这个一搜一大把,略过不提
-
在/etc/inittab的最后一行加上:
tunl:345:respawn:/usr/bin/ssh -D 7070 -qnN [username]@[server] > /dev/null 2>&1
-
让修改的inittab马上生效
sudo init q
然后这个ssh通道就会自动重连了。
【转自:Volcano】
[转]PHP数组交集的优化
假设我们正在运营一个手机相关的网站,用户可以通过指定若干参数(如操作系统,屏幕分辨率,摄像头像素等等)来筛选自己想要的手机。不过由于手机的 参数多,且不同的手机其参数差异大,所以参数表结构通常是纵表(一个参数是一行),而不是横表(一个参数是一列),此时使用若干参数来取结果,通常就是把 每个单独参数来取结果,再一起取交集。
假定每个参数会包含一千个左右的产品ID(int),以此为前提来模拟生成一些数据:
<?php
$rand = function() {
$result = array();
for ($i = 0; $i < 1000; $i++) {
$result[] = mt_rand(1, 10000);
}
return $result;
};
$param_a = $rand();
$param_b = $rand();
?>
注意:如果测试数据集过小的话,结论可能会出现不一致。
先看看通过PHP内置方法array_intersect实现的性能:
<?php
$time = microtime(true);
$result = array_intersect($param_a, $param_b);
$time = microtime(true) - $time;
echo "array_intersect: {$time}n";
?>
在优化之前,我们先来看看array_intersect一些特殊的地方:
</pre> <span style="font-family: 宋体; font-size: 12pt;"><?php</span> <span style="font-family: 宋体; font-size: 12pt;">$param_a = array(1, 2, 2);</span> <span style="font-family: 宋体; font-size: 12pt;">$param_b = array(1, 2, 3);</span> <span style="font-family: 宋体; font-size: 12pt;">var_dump(</span> <span style="font-family: 宋体; font-size: 12pt;"> array_intersect($param_a, $param_b),</span> <span style="font-family: 宋体; font-size: 12pt;"> array_intersect($param_b, $param_a)</span> <span style="font-family: 宋体; font-size: 12pt;">);</span> <span style="font-family: 宋体; font-size: 12pt;">?></span> <span style="font-family: 宋体; font-size: 12pt;">
- array_intersect($param_a, $param_b): 1, 2, 2
- array_intersect($param_b, $param_a): 1, 2
也就是说,如果在第一个数组参数中有重复元素的话,则array_intersect会返回所有满足条件的重复元素。改写array_intersect的时候最好兼容这些功能。
下面看看通过自定义方法int_array_intersect实现的性能:
<?php
function int_array_intersect()
{
if (func_num_args() < 2) {
trigger_error('param error', E_USER_ERROR);
}
$args = func_get_args();
foreach ($args AS $arg) {
if (!is_array($arg)) {
trigger_error('param error', E_USER_ERROR);
}
}
$intersect = function($a, $b) {
$result = array();
$length_a = count($a);
$length_b = count($b);
for ($i = 0, $j = 0; $i < $length_a && $j < $length_b; null) {
if($a[$i] < $b[$j] && ++$i) {
continue;
}
if($a[$i] > $b[$j] && ++$j) {
continue;
}
$result[] = $a[$i];
if (isset($a[$next = $i + 1]) && $a[$next] != $a[$i]) {
++$j;
}
++$i;
}
return $result;
};
$result = array_shift($args);
sort($result);
foreach ($args as $arg) {
sort($arg);
$result = $intersect($result, $arg);
}
return $result;
}
$time = microtime(true);
$result = int_array_intersect($param_a, $param_b);
$time = microtime(true) - $time;
echo "int_array_intersect: {$time}n";
?>
直觉上,我们肯定会认为内置函数快于自定义函数,但本例中结果恰恰相反:
- array_intersect: 0.023918151855469
- int_array_intersect: 0.0026049613952637
为什么?原因在于int_array_intersect操作的都是整数,而array_intersect操作的都是字符串,即便你传给它整数,也会当做字符串处理。
注:测试结果基于PHP5.3.5,不同版本结论可能存在差异
【转自:http://huoding.com/2011/01/30/43】
I've written a set-intersection implementation which is MUCH (5+ times) faster than array_intersect()
PHP Code:
function intersect($s1,$s2) {
$i=0;
$j=0;
$N = count($s1);
$M = count($s2);
$intersection = array();
while($i<$N && $j<$M) {
if($s1[$i]<$s2[$j]) $i++;
else if($s1[$i]>$s2[$j]) $j++;
else {
$intersection[] = $s1[$i];
$i++;
$j++;
}
}
return $intersection;
}
Both arrays must be pre-sorted, but I did the performance comparison including this time, just to be fair
.
Just thought I'd share this one.
I should probably have been clearer about the differences. In my case I had suitable arrays, and need the speed more than the generality. Its is definately significantly faster for my data at any rate.
Perhaps I'll take a look at the array_intersect source at some point.
Thanks for the comments guys
[转]Git和Repo扫盲——如何取得Android源代码
Git 是 Linux Torvalds 为了帮助管理 Linux 内核开发而开发的一个开放源码的分布式版本控制软件,它不同于Subversion、CVS这样的集中式版本控制系统。在集中式版本控制系统中只有一个仓库(repository),许多个工作目录(working copy),而像Git这样的分布式版本控制系统中(其他主要的分布式版本控制系统还有BitKeeper、Mercurial、GNU Arch、Bazaar、Darcs、SVK、Monotone等),每一个工作目录都包含一个完整仓库,它们可以支持离线工作,本地提交可以稍后提交到服务器上。分布式系统理论上也比集中式的单服务器系统更健壮,单服务器系统一旦服务器出现问题整个系统就不能运行了,分布式系统通常不会因为一两个节点而受到影响。
因为Android是由kernel、Dalvik、Bionic、prebuilt、build等多个Git项目组成,所以Android项目编写了一个名为Repo的Python的脚本来统一管理这些项目的仓库,使得Git的使用更加简单。
这几天William为了拿Android最新的sourcecode,学习了一下git和repo的一些基本操作,整理了一个如何取得Android代码的How-To,今天把他贴上来。
1、Git的安装
在Ubuntu 8.04上安装git只要设定了正确的更新源,然后使用apt-get就可以了,有什么依赖问题,就让它自己解决吧。其中cURL是一个利用URL语法在命令行下工作的文件传输工具,会在后面安装Repo的时候用到。
sudo apt-get install git-core curl
2、安装Repo
首先确保在当前用户的主目录下创建一个/bin目录(如果没有的话),然后把它(~/bin)加到PATH环境变量中
接下来通过cURL来下载Repo脚本,保存到~/bin/repo文件中
curl http://android.git.kernel.org/repo >~/bin/repo
别忘了给repo可执行权限
chmod a+x ~/bin/repo
3、初始化版本库
如果是想把Android当前主线上最新版本的所有的sourcecode拿下来,我们需要repo的帮助。
先建立一个目录,比如~/android,进去以后用repo init命令即可。
repo init -u git://android.git.kernel.org/platform/manifest.git
这个过程会持续很长的时间(至少可以好好睡一觉),具体要多少时间就取决于网络条件了
最后会看到 repo initialized in /android这样的提示,就说明本地的版本库已经初始化完毕,并且包含了当前最新的sourcecode。
如果想拿某个branch而不是主线上的代码,我们需要用-b参数制定branch名字,比如:
repo init -u git://android.git.kernel.org/platform/manifest.git -b cupcake
另一种情况是,我们只需要某一个project的代码,比如kernel/common,就不需要repo了,直接用Git即可。
git clone git://android.git.kernel.org/kernel/common.git
这也需要不少的时间,因为它会把整个Linux Kernel的代码复制下来。
如果需要某个branch的代码,用git checkout即可。比如我们刚刚拿了kernel/common.get的代码,那就先进入到common目录,然后用下面的命令:
git checkout origin/android-goldfish-2.6.27 -b goldfish
这样我们就在本地建立了一个名为goldfish的android-goldfish-2.6.27分支,代码则已经与android-goldgish-2.6.27同步。我们可以通过git branch来列出本地的所有分支。
4、同步版本库
使用epo sync命令,我们把整个Android代码树做同步到本地,同样,我们可以用类似
repo sync project1 project2 …
这样的命令来同步某几个项目
如果是同步Android中的单个项目,只要在项目目录下执行简单的
git pull
即可。
5、通过GitWeb下载代码
另外,如果只是需要主线上某个项目的代码,也可以通过GitWeb下载,在shortlog利用关键字来搜索特定的版本,或者找几个比较新的tag来下载还是很容易的。
Git最初是为Linux内核开发而设计,所以对其他平台的支持并不好,尤其是Windows平台,必须要有Cygwin才可以。现在,得益于msysgit项目,我们已经可以不需要Cygwin而使用Git了。另外,Git Extensions是一个非常好用的Windows Shell扩展,它能与资源管理器紧密集成,甚至提供了Visual Studio插件。它的官方网站上有一分不错的说明文档,感兴趣的朋友可以看一看。
至于Git的参考文档,我推荐Git Magic,这里还有一个Git Magic的中文版。
源文档 <http://www.williamhua.com/2009/04/29/git-and-repo-for-dummies/>
[转]git svn实战
我之前写了几个wordpress插件,比如inline-javascript, code-prettify。这些插件都托管在wordpress.org提供的svn服务器上,但是我实在太喜欢在git下活动了,因此动了点心思,想把插件代码传到github上,开发完之后利用git-svn传到wordpress的svn服务上。
照着这个思路,捋起袖子就开干了。
用git-svn抓取插件代码
wordpress的插件svn库大且缓慢,如果直接用git-svn去clone代码,一定会慢死,所以我按照以前的笔记,用git从大型svn快速clone代码。
以code-prettify插件为例,首先需要读取这个插件创建时的版本号
svn log http://svn.wp-plugins.org/code-prettify|tail -4|head -1
得到了如下信息,获得一个版本号 318479
r318479 | plugin-master | 2010-12-03 20:12:29 +0800 (五, 03 12 2010) | 1 line
开始clone代码
git svn clone -s --prefix=svn/ -r318479:HEAD http://svn.wp-plugins.org/code-prettify
设置git仓库
首先把代码传了一份到github: https://github.com/volca/code-prettify
然后操作本地git仓库
git branch -m svn
git remote add origin git@github.com:volca/code-prettify.git
git checkout master
本地svn分支对应svn的远程仓库,本地master分支对应github的远程仓库
Happy time
现在可以按照平常的习惯在git下更改代码,然后用git push到github上。
如果需要更新代码到svn上,按这个流程操作就可以了:
git checkout svn
git merge master
git svn dcommit
如果需要发布wordpress插件的新版本,这个在svn里就是一个打tag的过程,用git-svn操作非常简单,下面的例子表示发布code-prettify插件的0.3版本:
git svn tag 0.3
[转自:http://www.ooso.net/archives/576]
用git从svn里clone最后几个版本
一般情况下git svn clone这个操作会从第一个版本开始同步,如果版本号已经到了好几万(或更高?),这个操作会相当的费时。
当时还想着能不能hack一下git-svn脚本,其实后来看看文档,clone操作可以使用参数-r$REVNUMBER:HEAD检出指定版本后的代码,因此,更好的步骤应该是这样:
- svn info http://your-svn, 并记录最后的版本号,假设是260
- 假设要检出最后10个版本,做个简单的减法: 260 – 10 = 250
-
开始clone操作了
git svn clone -r250:HEAD --prefix=svn/ http://your-svn
按这个办法,clone的时间的确是减少了许多。
[转]基于PECL OAuth打造微博应用
最近,国内主要门户网站相继开放了微博平台,对开发者而言这无疑是个利好消息,不过在实际使用中却发现平台质量良莠不齐,有很多不完善的地方,就拿PHP版SDK来说吧,多半都是用TwitterOAuth改的,一旦多平台集成,很容易出现命名冲突之类的问题。
既然官方SDK不给力,那我们只能发扬自力更生的革命精神了!好消息是PHP本身已经有了一个标准的OAuth实现:PECL OAuth!下面以此为例来讲解一下如何实现微博应用:
说明:首先需要对OAuth概念有一定的了解,如不清楚可以参考我以前写的文章:OAuth那些事儿,其次需要注册成为各个微博平台(新浪,腾讯,搜狐,网易)的开发者,拿到属于你自己的CONSUMER_KEY和CONSUMER_SECRET(有时也被称作APP_*)。
下面开始!假定我们要开发一个类似Follow5和微博通的应用,简单点说就是把消息同时发送到多个微博平台,出于安全性的考虑,不会使用HTTP Basic,而会使用OAuth,这就需要我们先拿到Access Token和Access Token Secret。
以新浪微博为例,大致的代码如下:
<?php
session_start();
$request_token_url = 'http://api.t.sina.com.cn/oauth/request_token';
$authorize_url = 'http://api.t.sina.com.cn/oauth/authorize';
$access_token_url = 'http://api.t.sina.com.cn/oauth/access_token';
$oauth = new OAuth(
'YOUR_CONSUMER_KEY',
'YOUR_CONSUMER_SECRET',
OAUTH_SIG_METHOD_HMACSHA1,
OAUTH_AUTH_TYPE_FORM
);
if (empty($_GET['oauth_verifier'])) {
$callback_url = "http://{$_SERVER['HTTP_HOST']}{$_SERVER['REQUEST_URI']}";
$request_token = $oauth->getRequestToken($request_token_url);
$_SESSION['oauth_token_secret'] = $request_token['oauth_token_secret'];
$param = array(
'oauth_token' => $request_token['oauth_token'],
'oauth_callback' => $callback_url
);
header("Location: {$authorize_url}?" . http_build_query($param));
exit;
}
$oauth->setToken($_GET['oauth_token'], $_SESSION['oauth_token_secret']);
$access_token = $oauth->getAccessToken(
$access_token_url, null, $_GET['oauth_verifier']
);
var_dump($access_token);
?>
腾讯微博相比较而言有点特殊,大致代码如下:
<?php
session_start();
$request_token_url = 'https://open.t.qq.com/cgi-bin/request_token';
$authorize_url = 'https://open.t.qq.com/cgi-bin/authorize';
$access_token_url = 'https://open.t.qq.com/cgi-bin/access_token';
$oauth = new OAuth(
'YOUR_CONSUMER_KEY',
'YOUR_CONSUMER_SECRET',
OAUTH_SIG_METHOD_HMACSHA1,
OAUTH_AUTH_TYPE_FORM
);
$oauth->setNonce(md5(mt_rand()));
if (empty($_GET['oauth_verifier'])) {
$callback_url = "http://{$_SERVER['HTTP_HOST']}{$_SERVER['REQUEST_URI']}";
$request_token = $oauth->getRequestToken($request_token_url, $callback_url);
$_SESSION['oauth_token_secret'] = $request_token['oauth_token_secret'];
$param = array(
'oauth_token' => $request_token['oauth_token']
);
header("Location: {$authorize_url}?" . http_build_query($param));
exit;
}
$oauth->setToken($_GET['oauth_token'], $_SESSION['oauth_token_secret']);
$access_token = $oauth->getAccessToken(
$access_token_url, null, $_GET['oauth_verifier']
);
var_dump($access_token);
?>
注意:参数nonce和callback的设置,详见:使用 PECL 的 OAuth 库访问 QQ 微博 API。
照猫画虎就能得到搜狐和网易的Access Token和Access Token Secret了,我就不罗嗦了。
下面继续做我们的微博应用,发消息一般都是文本形式的,不过有中国特色的微博开放平台支持文本加图片的方式:图片上传到服务器,但本身并不参与签名。这和标准OAuth是冲突的,所以要扩展一下PECL OAuth,并且尽可能兼容原类的使用方法和习惯:
<?php
class MicroblogOAuth extends OAuth
{
public $consumer_key;
public $signature_method;
public $auth_type;
public $nonce;
public $timestamp;
public $token;
public $version;
public $request_engine;
public $last_response;
public function setAuthType($auth_type)
{
if (parent::setAuthType($auth_type)) {
$this->auth_type = $auth_type;
return true;
}
return false;
}
public function setNonce($nonce)
{
if (parent::setNonce($nonce)) {
$this->nonce = $nonce;
return true;
}
return false;
}
public function setTimestamp($timestamp)
{
if (parent::setTimestamp($timestamp)) {
$this->timestamp = $timestamp;
return true;
}
return false;
}
public function setToken($token, $token_secret)
{
if (parent::setToken($token, $token_secret)) {
$this->token = $token;
return true;
}
return false;
}
public function setVersion($version)
{
if (parent::setVersion($version)) {
$this->version = $version;
return true;
}
return false;
}
public function setRequestEngine($request_engine)
{
try {
parent::setRequestEngine($request_engine);
$this->request_engine = $request_engine;
} catch(OAuthException $e) {
echo $e->getMessage();
}
}
public function getLastResponse()
{
return parent::getLastResponse() ?: $this->last_response;
}
public function upload($url, $file, $param = array(), $header = array())
{
$boundary = sprintf('%010d', mt_rand());
$header[] = "Content-Type: multipart/form-data; boundary={$boundary}";
$oauth = array(
'oauth_consumer_key' => $this->consumer_key,
'oauth_nonce' => $this->nonce,
'oauth_signature_method' => $this->signature_method,
'oauth_timestamp' => $this->timestamp,
'oauth_token' => $this->token,
'oauth_version' => $this->version,
);
if ($this->auth_type == OAUTH_AUTH_TYPE_FORM) {
$param += $oauth;
$param['oauth_signature'] = $this->generateSignature(
OAUTH_HTTP_METHOD_POST, $url, $param
);
} else {
$oauth_header = array();
$oauth['oauth_signature'] = $this->generateSignature(
OAUTH_HTTP_METHOD_POST, $url, $param
);
foreach ($oauth AS $name => $value) {
$oauth_header[] = $name . '="' . $value . '"';
}
$header[] = 'Authorization: OAuth ' . implode(', ', $oauth_header);
}
$content_disposition = function($name, $filename = null) {
$result = 'Content-Disposition: form-data; name="' . $name . '"';
if ($filename !== null) {
$result .= '; filename="' . $filename . '"';
}
return $result;
};
$content = array();
foreach ($file as $name => $value) {
$filename = pathinfo($value, PATHINFO_BASENAME);
switch(strtolower(pathinfo($filename, PATHINFO_EXTENSION))) {
case 'gif';
$mime = 'image/gif';
break;
case 'jpeg':
case 'jpg':
$mime = 'image/jpg';
break;
case 'png';
$mime = 'image/png';
break;
default:
$mime = 'application/octet-stream';
}
$content_type = "Content-Type: {$mime}";
$content[] = "--{$boundary}";
$content[] = $content_disposition($name, $filename);
$content[] = $content_type;
$content[] = '';
$content[] = file_get_contents($value);
}
ksort($param);
foreach ($param as $name => $value) {
$content[] = "--{$boundary}";
$content[] = $content_disposition($name);
$content[] = '';
$content[] = $value;
}
$content[] = "--{$boundary}--";
$content[] = '';
$content = implode("\r\n", $content);
if ($this->request_engine == OAUTH_REQENGINE_CURL) {
$header[] = 'Expect:';
$curl = curl_init();
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_POSTFIELDS, $content);
curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($curl);
curl_close($curl);
} else {
$header[] = 'Connection: close';
$context = stream_context_create(array(
'http' => array(
'protocol_version' => '1.1',
'method' => 'POST',
'content' => $content,
'header' => implode("\r\n", $header),
)
));
$response = file_get_contents($url, false, $context);
}
if ($response) {
$this->last_response = $response;
return true;
}
return false;
}
}
?>
注意:为了让代码潮一点,用了一些PHP5.3以上版本才有的特性,你可以改写成低版本。
如果使用CURL方式发送请求的话,最好发送一个空的Expect头,否则如果POST数据大于1K,CURL会自作主张发送 Expect:100-continue头,对多数Web服务器来说这没问题,但低版本Lighttpd(如1.4)则会出现HTTP 417错误。
详见:'Expect' header gives HTTP error 417
如果使用PHP Streams方式发送请求的话,缺省使用的是HTTP1.0版本,可以通过tcpdump命令来检测这种现象,大致方法如下:
shell> tcpdump -A host foo.com
此时,某些防火墙会过滤掉非标准HTTP1.0的请求头,如Host请求头,从而造成错误。
详见:由于 HTTP request 不规范导致的被防火墙拦截。
新类MicroblogOAuth直接扩展自PECL的OAuth类!随着PHP内核API的逐渐类化,这样的扩展方式将会越来越常见,值得开发人员重视。
为了让调用方式更统一,使用工厂方法包装MicroblogOAuth的实例化过程:
<?php
function OAuth($consumer_key, $consumer_secret, $signature_method, $auth_type)
{
$instance = new MicroblogOAuth(
$consumer_key,
$consumer_secret,
$signature_method,
$auth_type
);
$instance->consumer_key = $consumer_key;
$instance->signature_method = $signature_method;
$instance->setAuthType($auth_type);
$instance->setNonce(md5(mt_rand()));
$instance->setTimestamp(time());
$instance->setVersion('1.0');
if (extension_loaded('curl')) {
$instance->setRequestEngine(OAUTH_REQENGINE_CURL);
} else {
$instance->setRequestEngine(OAUTH_REQENGINE_STREAMS);
}
$instance->last_response = null;
return $instance;
}
?>
先看看搜狐是如何发送文本加图片消息的:
<?php
$text = 'hello, world.';
$image = 'http://www.foo.com/bar.gif';
$oauth = OAuth(
'YOUR_CONSUMER_KEY',
'YOUR_CONSUMER_SECRET',
OAUTH_SIG_METHOD_HMACSHA1,
OAUTH_AUTH_TYPE_AUTHORIZATION
);
$oauth->setToken(
'YOUR_ACCESS_TOKEN',
'YOUR_ACCESS_TOKEN_SECRET'
);
$oauth->upload(
'http://api.t.sohu.com/statuses/upload.json',
array('pic' => $image),
array('status' => oauth_urlencode($text))
);
$result = json_decode($oauth->getLastResponse(), true);
var_dump($result);
?>
说明:搜狐要求文本要先编码,然后和图片一起发送,这点不同于其它微博开放平台。
再看看网易是如何发送文本加图片消息的:
<?php
$text = 'hello, world.';
$image = 'http://www.foo.com/bar.gif';
$oauth = OAuth(
'YOUR_CONSUMER_KEY',
'YOUR_CONSUMER_SECRET',
OAUTH_SIG_METHOD_HMACSHA1,
OAUTH_AUTH_TYPE_AUTHORIZATION
);
$oauth->setToken(
'YOUR_ACCESS_TOKEN',
'YOUR_ACCESS_TOKEN_SECRET'
);
$oauth->upload(
'http://api.t.163.com/statuses/upload.json',
array('pic' => $image)
);
$result = json_decode($oauth->getLastResponse(), true);
if (isset($result['upload_image_url'])) {
$text .= " {$result['upload_image_url']}";
}
$oauth->fetch(
'http://api.t.163.com/statuses/update.json',
array('status' => $text),
OAUTH_HTTP_METHOD_POST
);
$result = json_decode($oauth->getLastResponse(), true);
var_dump($result);
?>
说明:网易发送文本加图片消息是分两步实现的,先上传图片,然后把图片的URL附加在文本信息的后面再发送到服务器,这点不同于其它微博开放平台。
收工!微博开放平台的使用并没有太多复杂的地方,仔细看文档调试,一般的问题都很容易解决。有了上面的基础代码,只要再使用适配器模式分别包装一下各个微博平台,很容易就能实现一套通用SDK,搞定新浪,腾讯,搜狐,网易!
【转自:http://huoding.com/2011/01/16/42】
【转】php加速 PHP APC 浅析
PHP APC提供两种缓存功能,即缓存Opcode(目标文件),我们称之为apc_compiler_cache。同时它还提供一些接口用于PHP开发人员将用户数据驻留在内存中,我们称之为apc_user_cache。我们这里主要控讨php-apc的配置。
安装PHP APC
作为测试环境,我们这里使用的是CentOS5.3(2.6.18-128.el5PAE) + Apache2.0(prefork) + php5.2。我们可以去pecl apc下载APC-3.0.19.tgz
[cc lang='bash']
# tar -xzvf APC-3.0.19.tgz
#cd APC-3.0.19
# /usr/bin/phpize
# ./configure --enable-apc --enable-mmap --enable-apc-spinlocks --disable-apc-pthreadmutex
#make
#make install
[/cc]
注意:我们这里支持mmap,同时采用spinlocks自旋锁。Spinlocks是Facebook推荐使用,同时也是APC开发者推荐使用的锁机制。
PHP APC 配置参数
如果你使用的系统环境跟我的测试环境是一样的话,可以在/etc/php.d目录下创建文件apc.ini,并且相关配置写入/etc/php.d/apc.ini文件。这里,我们挑了一些常用到的配置,并进行探讨。把相关的配置放在一起解释。
apc.enabled=1
apc.enabled默认值是1,你可设成0禁用APC。如果你设置为0的时候,同样把extension=apc.so也注释掉(这样可以节约内存资源)。一旦启用了APC功能,则会缓存Opcodes到共享内存。
APC既然把数据缓存在内存里面,我们就有必要对它进行内存资源限定。通过这二个配置可以限定APC可以使用的内存空间大小。apc.shm_segments指定了使用共享内存块数,而apc.shm_size则指定了一块共享内存空间大小,单位是M。所以,允许APC使用的内存大小应该是 apc.shm_segments * apc.shm_size = 30M。你可以调整一块共享内存的大小空间。当然,一块共享内存最大值是受操作系统限制的,即不能超过/proc/sys/kernel/shmmax大小。否则APC创建共享内存的时候,会失败。在apc.shm_size达到了上限的时候,你可以通过设置apc.shm_segments来允许APC使用更多的内存空间。我们推荐,如果调用APC使用内存空间的话,先考滤apc.shm_size,后考滤apc.shm_segments。具体数值,可以根据apc.php监控情况进行规划与调整。值得注意的是,每一次调整需要重启httpd守护进程,这样可以重新加载apc.so模块。跟随着httpd守护进程启动,apc.so模块就会加载。apc.so加载初始化的时候,通过mmap请求分配内存指定大小的内存,即apc.shm_size * apc.shm_segments。而且,这里使用的是匿名内存映射方式,通过映射一个特殊设备/dev/zero,提供一个"大型"的,填满了零的内存供APC管理。
为了验证以上陈述,我们注释掉apc.ini配置,并且写了以下php脚本观察apc.so模块初始化的分配的内存空间。
[cc lang='php']
//@file: apc_load.php
if (!extension_loaded('apc')) {
dl('apc.so'); #加载apc.so模块
echo posix_getpid(); #//输出当前进程的pid,我这里这里输出的是14735
ob_flush();
flush();
sleep(3600); #让进程进入休眠状态.这样,我们可以观察内存分配情况
}
?>
[/cc]
[cc lang='bash']
#strace -p `cat /var/run/httpd.pid`
open("/var/www/html/apc_load.php", O_RDONLY) = 13
...
mmap2(NULL, 31457280, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0) = 0xb5ce7000
...
nanosleep({3600, 0},
[/cc]
红色部分,我们可以看出。通过mmap系统内核调用分配了30M(31457280/1024/1024)内存空间。PROT_READ|PROT_WRITE表示该内存空间可供读取与写入。MAP_SHARED表示该内存空间与其它进程是共享的,即其它进程也可以进行读取与写入,我们可以通过apc.php进行管理该块内存空间亦是受益于此设定。MAP_ANONYMOUS则表示匿名映射。其中fd=-1表示忽略,因为这里映射的特殊设备/dev/zero。最后的0表示无偏移量。我们还可以通过进程映像文件查看该块内存的具体情况
[cc lang='bash']
#cat /proc/14735/smaps
b5ce7000-b7ae7000 rw-s 00000000 00:08 633695 /dev/zero (deleted)
Size: 30720 kB
Rss: 44 kB
Shared_Clean: 0 kB
Shared_Dirty: 0 kB
Private_Clean: 0 kB
Private_Dirty: 44 kB
[/cc]
可以很容易地发现起始地址0xb5ce7000与上面mmap系统内核调用返回的地址一样。该块内存是可读写rw,并与其它进程共享s。而/dev/zero则是映射文件,该文件节点是633695。其中,size表示进程可以使用的内存空间,而rss则表示实际分配的内存空间,且由Private_Dirty可以看出,实际分配的44kb内存是由当前进程自己分配的。
apc.num_files_hint = 1000
apc.user_entries_hint = 4096
这二配置指定apc可以有多少个缓存条目。apc.num_files_hint说明你估计可能会有多少个文件相应的opcodes需要被缓成,即大约可以有多少个apc_compiler_cache条目。另外apc.user_entries_hint则说明你估计可能会有多少个apc_userdata_cache条目需要被缓存。如果项目中不使用apc_store()缓存用户数据的话,该值可以设定得更小。也就是说apc.num_files_hint与apc.user_entries_hint之和决定了APC允许最大缓存对象条目的数量。准确地设置这二个值可以得到最佳查询性能。当然,如果你不清楚要进行多少缓存(缓存对象实例)的情况下,你可以不必修改这二项配置。
其中apc.user_entries_hint要根据项目实际开发使用了apc_store()条目估计其值大小。相较而言,apc.num_files_hint可以通过find命令,更容易地估计其大小。比如我们的web根目是/var/vhosts,则使用下面的find命令可以大致地统计当前apc.num_files_hint数目.
[cc lang='bash']
#find /var/vhosts \( -name “*.php” -or -name “*.inc” \) -type f -print |wc -l
1442
[/cc]
apc.stat = 1
apc.stat_ctime = 0
这二个参数,只跟apc_compiler_cache缓存相关,并不影响apc_user_cache。我们前面提到过apc_complier_cache,它缓存的对象是php源文件一一对应的opcodes(目标文件)。PHP源文件存放在磁盘设备上,与之相对应的Opcodes目标文件位置内存空间(共享内存),那么当php源文件被修改以后,怎么通知更新内存空间的opcodes呢?每次接收到请求后,APC都会去检查打开的php源文件的最后修改时间,如果文件的最后修改时间与相应的内存空间缓存对象记录的最后修改时间不一致的话,APC则会认为存放在内存空间的Opcode目标文件(缓存对象)已经过期了,acp会将缓存对象清除并且保存新解析得到的Opcode。我们关心的是,即便没有更新任何php源文件,每次接受到http请求后,APC都会请求系统内核调用stat()来获取php源文件最后修改时。我们可以通过将apc.stat设置为0,要求APC不去检查Opcodes相对应的php源文件是否更新了。这样可以获得最佳的性能,我们也推荐这么做。不过,这样做有一点不好的就是,一旦有PHP源文件更新了之后,需要重启httpd守护进程或者调用apc_cache_clear()函数清空APC缓存来保证php源文件与缓存在内存空间的Opcodes相一致。
[cc lang='php']
define('ROOTP', dirname(__FILE__) . '/');
include(ROOTP . 'i1.php');
require(ROOTP . 'i2.php');
include_once(ROOTP . 'i3.php');
require_once(ROOTP . 'i4.php');
require(ROOTP . 'i5.php');
include(ROOTP . 'i6.php');
?>
[/cc]
[cc lang='bash']
# strace -e trace=file -p `cat /var/run/httpd.pid`
getcwd("/var/www/html", 4096) = 14
stat64("/var/www/html/i1.php", {st_mode=S_IFREG|0644, st_size=39, ...}) = 0
stat64("/var/www/html/i2.php", {st_mode=S_IFREG|0644, st_size=39, ...}) = 0
lstat64("/var", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
lstat64("/var/www", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
lstat64("/var/www/html", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
lstat64("/var/www/html/i3.php", {st_mode=S_IFREG|0644, st_size=39, ...}) = 0
open("/var/www/html/i3.php", O_RDONLY) = 12
stat64("/var/www/html/i3.php", {st_mode=S_IFREG|0644, st_size=39, ...}) = 0
lstat64("/var", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
lstat64("/var/www", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
lstat64("/var/www/html", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
lstat64("/var/www/html/i4.php", {st_mode=S_IFREG|0644, st_size=39, ...}) = 0
open("/var/www/html/i4.php", O_RDONLY) = 12
stat64("/var/www/html/i4.php", {st_mode=S_IFREG|0644, st_size=39, ...}) = 0
stat64("/var/www/html/i5.php", {st_mode=S_IFREG|0644, st_size=39, ...}) = 0
stat64("/var/www/html/i6.php", {st_mode=S_IFREG|0644, st_size=39, ...}) = 0
chdir("/tmp") = 0
# strace -e trace=file -p `cat /var/run/httpd.pid`
getcwd("/var/www/html", 4096) = 14
open("/var/www/html/i3.php", O_RDONLY) = 12
open("/var/www/html/i4.php", O_RDONLY) = 12
chdir("/tmp") = 0
[/cc]
对比可见,当apc.stat=0时,省了很多系统内核调用,我们没有看到系统内核调用stat64了。其中,i3.php和i4.php分别是php的include_once和require_once函数调用,它要交给fstat()系统内核调用来检查文件是否打开过。单从性能角度出发的话,require比require_once性能更佳。
设置apc.stat_ctime的意义并是很大。如果apc.stat_ctime值为1时,仅当php源文件的创建时间(ctime)大于php源文件的最后修改时间(mtime)时,缓存对象的mtime时间会被php源文件的ctime所代替,否则缓存对象的mtime依然记录为php源文件的mtime。这样做是防止通过cvs, svn或者rsync等工具刷新php源文件的mtime,这样会导致APC通过比对php源文件的创建时间ctime来决定缓存对象有没有过期。我们推荐该保持默认值,即apc.stat_ctime = 0
apc.ttl=0
apc.user_ttl=0
缓存对象的生命周期。其中ttl表示Time To Live,意味着指定时间后缓存对象会被清除。其中0表示永不过期。我们前面提过,APC能缓存的条目是受限定的,如果你把ttl设置永不过期的话,当缓存条目已满或者缓存空间不够,之后的缓存都将失败。
其中apc.ttl作用于apc_compiler_cache。当apc.ttl大于0时,每次请求都会对比这次的请求时间与上一次请求时间之差是不是大于apc.ttl,如果大于apc.ttl,则会被认缓存条目过期了,会被清理。
比较有意思的是apc.user_ttl,它主要作用于apc_user_cache缓存。我们知道,这种类型的缓存是通过apc_store($key, $var, $ttl = 0)创建的缓存对象。函数apc_store()中指定的$ttl与php.ini中设定的apc.user_ttl有什么异同,是我们比较关心的。因为它们同样作用于apc_userdata_cache缓存。经过分析,我们知道:判断apc_user_cache缓存过期的依据是,当apc.user_ttl大于0,且这次http请求时间与上一次http请求时间之差大于apc.user_ttl,则认为相应的缓存条目已过期;或者,user.data.ttl(php函数apc_store()中指定的$ttl)大于0,且这次http请求时间与缓存对象创建时间ctime之差大于user.data.ttl,则同样认为缓存条目已过期,会被清除。
我们推荐,如果你的项目较为稳定,并且apc.stat设置为0。同时apc.shm_size、apc.num_files_hint设置合理的话,apc.ttl建议设置为0。即apc_compiler_cache永不回收,直到重启httpd守护进程或者调用函数apc_cache_clear()清缓存。至于apc.user_ttl,建议设置为0,由开发人员调用apc_store()函数的时候,设置$ttl来指定该缓存对象的生命周期。
apc.slam_defense=0
apc.write_lock=1
apc.file_update_protection=2
之所以把这三个配置放在一起解释,是因为他们的意义很相近。其中apc.file_update_protection最好理解,它的单位是时间单位秒。如果当前http请求时间与php源文件最好修改时间mtime之差小于apc.file_update_protection时间,APC则不会缓存该php源文件与之对应的Opcodes,直到接下来的某次访问,并且访问时间与php源文件的最后修改时间大于apc.file_update_protection时间,相之相应的Opcodes才会被缓存到共享内存空间。这样做的好处是,不容易被用户访问到你正在修改的源文件。我们推荐在开发环境,该值可以设置得更大一点,但在运营环境,我们推荐保留默认值即可。
当你的网站并发量很大的时候,可能出现由http守护进程fork的多个子进程同时缓存同一份Opcodes的情况。通过apc.slam_defense则可以减少这种事情的发生机率。比如,apc.slam_defense值设置为60的时候,当遇到未缓存的Opcodes,每100次有60次是不缓存的。对于并发量不大的网站,我们推荐该值设定为0,对于并发量高的网站我们可以根据统计适当地调整该值。而apc.write_lock是一个布尔值,当该值设置为1的时候,当多个进程同时缓存同一份Opcodes时,仅当最先那个进程缓存有效,其它的无效。通过apc.write_lock设置,有效地避免了缓存写竞争的出现。
apc.max_file_size=1M
apc.filters = NULL
apc.cache_by_default=1
这三个配置放在一起,是因为他们都用于限制缓存。其中apc.max_file_size表示如果php源文件超过了1M,则与之对应的opcodes不被缓存。而apc.filters指定一个文件过滤列表,以逗号(,)隔开。当apc.cache_by_default等于1时,与apc.filters列表中指定的文件名相匹配的文件不会被缓存。相反,apc.cache_by_default等于0时,仅缓存与acp.filters列表中指定的文件相匹配的文件。
总结
1,使用Spinlocks锁机制,能够达到最佳性能。
2,APC提供了apc.php,用于监控与管理APC缓存。不要忘记修改管理员名和密码
3,APC默认通过mmap匿名映射创建共享内存,缓存对象都存放在这块"大型"的内存空间。由APC自行管理该共享内存
4,我们需要通过统计调整apc.shm_size、apc.num_files_hints、apc.user_entries_hint的值。直到最佳
5,好吧,我承认apc.stat = 0 可以获得更佳的性能。要我做什么都可以接受.
6,PHP预定义常量,可以使用apc_define_constants()函数。不过据APC开发者介绍说pecl hidef性能更佳,抛异define吧,它是低效的。
7,函数apc_store(),对于系统设置等PHP变量,生命周期是整个应用(从httpd守护进程直到httpd守护进程关闭),使用APC比Memcached会更好。必竟不要经过网络传输协议tcp。
8,APC不适于通过函数apc_store()缓存频繁变更的用户数据,会出现一些奇异现象。
【转自:http://www.perfgeeks.com/?p=298】
[转]memcached分布式hash策略测试
版权声明:原创作品,允许转载,转载时请务必以超链接形式标明文章原始出版、作者信息和本声明。否则将追究法律责任。http://blog.csdn.net/mayongzhan - 马永占,myz,mayongzhan
配合memcache监控程序,测试memcache分布式,文章参考了网上某位牛人的文章。
测试crc32 fnv standard consistent 组合情况下的item分布情况及添加新服务器对现有item命中率的影响。
先开memcached服务器
#!/bin/bash
##############################
# MyZ 2009-07-12
# File:memcached_12.sh
##############################
port=11212
for (( i=11212; i<11224; i++ ))
do
/usr/local/bin/memcached -u root -p $i -m 10 &
done
./memcached.sh
结论
使用memcache这种客户端下的方法保证分配均匀的基础下,存货率做高的是crc32+ consistent。
测试结果
//crc32 standard
192.168.1.101:11212 => items:9812
192.168.1.101:11213 => items:10153
192.168.1.101:11214 => items:9895
192.168.1.101:11215 => items:9985
192.168.1.101:11216 => items:10206
192.168.1.101:11217 => items:9958
192.168.1.101:11218 => items:10159
192.168.1.101:11219 => items:9941
192.168.1.101:11220 => items:9924
192.168.1.101:11221 => items:9967
192.168.1.101:11222 => items:0%
192.168.1.101:11223 => items:0%

//fnv standard
192.168.1.101:11212 => items:9911
192.168.1.101:11213 => items:9978
192.168.1.101:11214 => items:9928
192.168.1.101:11215 => items:10053
192.168.1.101:11216 => items:9952
192.168.1.101:11217 => items:10055
192.168.1.101:11218 => items:10013
192.168.1.101:11219 => items:10089
192.168.1.101:11220 => items:9947
192.168.1.101:11221 => items:10074
192.168.1.101:11222 => items:0
192.168.1.101:11223 => items:0

//crc32 consistent
192.168.1.101:11212 => items:12466
192.168.1.101:11213 => items:10175
192.168.1.101:11214 => items:8436
192.168.1.101:11215 => items:9118
192.168.1.101:11216 => items:11757
192.168.1.101:11217 => items:9208
192.168.1.101:11218 => items:10343
192.168.1.101:11219 => items:8417
192.168.1.101:11220 => items:8716
192.168.1.101:11221 => items:11364
192.168.1.101:11222 => items:0
192.168.1.101:11223 => items:0

//fnv consistent
192.168.1.101:11212 => items:9014
192.168.1.101:11213 => items:13178
192.168.1.101:11214 => items:10053
192.168.1.101:11215 => items:4302
192.168.1.101:11216 => items:10875
192.168.1.101:11217 => items:4906
192.168.1.101:11218 => items:15593
192.168.1.101:11219 => items:9993
192.168.1.101:11220 => items:8093
192.168.1.101:11221 => items:13993
192.168.1.101:11222 => items:0
192.168.1.101:11223 => items:0

用于检测memcache的程序配置部分
for ($i = 11212; $i < 11222; $i++) {
$MEMCACHE_SERVERS[] = '192.168.1.101:'.$i; // add more as an array
}
$MEMCACHE_SERVERS[] = '192.168.1.101:11222'; // add more as an array
$MEMCACHE_SERVERS[] = '192.168.1.101:11223'; // add more as an array
测试代码
[cc lang='php']
/**
* @name test.php
* @date Thu Jul 12 21:18:59 CST 2009
* @copyright 马永占(MyZ)
* @author 马永占(MyZ)
* @link http://blog.csdn.net/mayongzhan/
*/
set_time_limit(0);
//crc32 standard
//ini_set('memcache.hash_function','crc32');
//ini_set('memcache.hash_strategy','standard');
//crc32 consistent
ini_set('memcache.hash_function','crc32');
ini_set('memcache.hash_strategy','consistent');
//fnv standard
//ini_set('memcache.hash_function','fnv');
//ini_set('memcache.hash_strategy','standard');
//fnv consistent
//ini_set('memcache.hash_function','fnv');
//ini_set('memcache.hash_strategy','consistent');
$memcache_server_ip = '192.168.1.101';
for ($i = 11212; $i < 11222; $i++) {
$memcache_servers_ports[] = $i;
}
$memcache_servers_ports_add[] = 11222;
$memcache_servers_ports_add[] = 11223;
$mem = new Memcache;
//add servers
foreach ($memcache_servers_ports as $value) {
$mem->addServer($memcache_server_ip, $value);
}
//set items
for($i = 0; $i < 100000; $i++) {
$mem->set($i, $i, 0, 3600);
}
//add 2 new servers
foreach ($memcache_servers_ports_add as $value) {
$mem->addServer($memcache_server_ip, $value);
}
//get items
for($i = 0; $i < 100000; $i++) {
$mem->get($i);
}
$status = $mem->getExtendedStats();
foreach ($status as $key=>$value) {
echo $key
. ' => items:'
. $value['curr_items']
. '
';
}
$mem->close();
?>
[/cc]