MongoDB practice: My Perl GridFS Wrapper
简单写了一个Perl版本的GriFS的wrapper:
package CZone::GridFS;
use strict;
use MongoDB::GridFS;
use Path::Class;
use Digest::file qw(digest_file_hex);
use Digest::MD5 qw(md5_hex);
use IO::File;
use Data::Dumper;
use Any::Moose;
has database => (
isa => ‘MongoDB::Database’,
is => ‘ro’,
required => 1
);
has _gridfs => (
isa => ‘MongoDB::GridFS’,
is => ‘ro’,
lazy => 1,
builder => ‘_build__gridfs’,
);
has _file_collection => (
isa => ‘MongoDB::Collection’,
is => ‘ro’,
lazy => 1,
builder => ‘_build__file_collection’
);
sub _build__gridfs {
my $self = shift;
return $self->database->get_gridfs;
}
sub _build__file_collection {
my $self = shift;
return $self->database->get_collection(‘fs.files’);
}
sub get_bytes {
my ($self, $id ) = @_;
my $file = $self->_gridfs->find_one({_id => $id });
my $bytes;
my $fh = new IO::File \$bytes,’>';
$file->print($fh);
return $bytes;
}
sub store_file {
my ($self, $file_path) = @_;
my $file = file($file_path)->absolute;
return undef unless -e $file;
my $md5 = digest_file_hex($file,’MD5′);
my $fh = $file->open(‘r’) or return undef;
return $self->_store_fh($fh,$md5);
}
sub _store_fh {
my ($self,$fh,$md5) = @_;
# $grid_file isa MongoDB::GridFS::File
my $grid_file = $self->_gridfs->find_one({ ‘md5′ => $md5});
if ($grid_file) {
$self->_inc_refs($grid_file->info->{_id});
return $grid_file->info->{_id};
}
else {
my $oid = $self->_gridfs->insert($fh,{
refs => 1,
md5 => $md5,
});
return $oid;
}
}
sub store_bytes {
my ($self, $bytes) = @_;
my $md5 = md5_hex($bytes);
my $fh = new IO::File \$bytes,’<';
# my $fh = FileHandle->new;
# $fh->open(\$bytes,’<');
return $self->_store_fh($fh,$md5);
}
sub unlink {
my ($self, $id ) = @_;
$self->_dec_refs(MongoDB::OID->new(value =>”$id”));
}
sub _inc_refs {
my ($self,$id) = @_;
$self->_file_collection->update({_id => $id },{ ‘$inc’ => { refs => 1}});
}
sub _dec_refs {
my ($self,$id) = @_;
$self->_file_collection->update({_id => $id },{ ‘$inc’ => { refs => -1}});
}
sub gc {
my $self = shift;
$self->_gridfs->remove({refs => 0});
}
no Any::Moose;
__PACKAGE__->meta->make_immutable;
1;
__END__
这是从czone项目中的PHP代码移植过来的。
方便将gridfs中的文件读写到scalar中。同时,通过检查存储文件的md5值,并记录相同文件的引用计数,相同文件只存储一个copy,节省空间。(BSON格式对于空间的需求是非常大的)
Notes: CentOS5.4编译Perl IO::Tty的处理
直接编译IO::Tty,会导致:
Can’t load ‘/root/.cpan/build/IO-Tty-1.08-PWZkbn/blib/arch/auto/IO/Tty/Tty.so’ for module IO::Tty:
undefined symbol: strlcpy at …..
快速修正, 打开Makefile
DEFINE = -DHAVE_DEV_PTMX -DHAVE_GETPT -DHAVE_GRANTPT -DHAVE_OPENPTY -DHAVE_POSIX_OPENPT -DHAVE_PTSNAME -DHAVE_PTSNAME_R -DHAVE_PTY_H -DHAV
E_SIGACTION -DHAVE_STRLCPY -DHAVE_SYS_STROPTS_H -DHAVE_TERMIOS_H -DHAVE_TERMIO_H -DHAVE_TTYNAME -DHAVE_UNLOCKPT -DHAVE__GETPTY
去掉 中间的-DHAVE_STRLCPY和最后的-DHAVE__GETPTY
重新编译, make test.
ok!
PHP编写命令行脚本和后台运行程序的注意事项
在一些场合(如开发,测试), 可能需要使用PHP编写一些命令行的处理脚本,或者是长时间
后台运行的任务, 需要注意以下准则:
准则1. 尽量避免使用PHP编写后台运行程序, 尤其是类似while(true){….} 这种循环的处理脚本.
比如,有时候我们需要定期检查数据库,然后有数据进行处理,没有数据等待.
我强烈建议不要使用PHP编写这样的类似service的脚本. php的gc并不稳定, 当运行到一定时候,
会异常退出. 另外, PHP也不擅长做命令行脚本. 看看Phing, 号称PHP port的Ant, 但是性能
糟糕的一塌糊涂, 远不如使用几个unix工具+shell来的快捷. (更别提windows,那基本是不可用).
使用Perl,Python来完成相应的任务吧, 那会让你很愉快, 或者,Ruby也不错.
准则2 编译特殊版本的php
如果一定要沉迷于PHP解决, 那么首先, 重新编译PHP,去掉所有不实用的模块. 增加PHP的稳定性.
准则3 不要直接使用fork 或者 nohup
不要在PHP中直接使用fork来进入后台运行, 也不要直接使用nohup来运行直接运行php 循环脚本.
准则4 使用 shell guard 来完成PHP的后台循环运行
php的脚本只需要处理一次数据后马上退出,不要使用任何while(true) 这样的脚本来挂起等待.
相反的, 可以使用以下的shell gurad 来完成上述工作:
night9.cn# cat guard.sh
DIR=`pwd`
while true; do
echo “start php script ..”
php $DIR/thumbnail_worker.php
echo “respawn the worker…”
上面这种脚本我称之为shell guard.这样的好处是你的后台脚本永远可以可靠的运行,一旦因为php自身出现问题(如内存溢出),那么马上就可以立即重新执行.
对于检查,执行,休眠模式,可以使用和以下类似的:
while true; do
php ./realtime_data_worker.php
echo “paused 10s”
sleep 10
done
上述shell guard的另一个好处就是一旦你更新了Php文件, 下次运行时就是新的了. 而无须kill掉在重启.
特别适合频繁变更的情况,减少你的工作量.
准则5 可以在你PHP脚本中监控执行的情况, 当执行了一定次数或者内存消耗到一定,则exit, 释放占用的内存,
防止内存泄露.
这条一定要和shell guard来配合.
如果你用windows, 抱歉, 这不在我考虑的范围之内.
准则6 如果以上仍然无法解决一些问题, 那么请参考第一条,使用Perl/Python重写. 立刻会药到病除
PS: 某些copy & paste的人儿, 转载我的笔记麻烦给个出处. 我现在知道某些人为何使用我不称我,而使用类似night9.cn认为这样的第三人称说法, 都是某些热衷把转载当自己原创使用的人害得,但每次我看到这样的第三人称总会稀稀拉拉掉一地鸡皮疙瘩.
我写的所有笔记和心得都是自己实践, 主要是为自己备忘使用,都是原创,无须声明.
有时候很纳闷, 转载有必要么?
google可以告诉你一切. 减少点碳排放吧.
Mouse,Moose和MooseX::Declare
如果你是一个Perl的开发者, 现在还不知道Moose那么你对Perl的了解基本上还停留在10年前了.
虽然国内Perl的开发者寥寥无几, 但Perl的强大远远超越一般人的想象空间.
我使用Perl是让自己更愉快,因为很多事情变得很简单.
Perl的OO一般人很难理解, 但是却用了最简单和巧妙的方式实现了,想想, 一个bless搞定, 再看看
PHP之类的,多么臃肿.
有了Moose,你会发现, 不仅仅OO,AOP这些东西实现起来是多么的轻松.
当看到MooseX::Declare, 你更会惊叹, “这还是Perl么?”.
use MooseX::Declare;
class BankAccount {
has ‘balance’ => ( isa => ‘Num’, is => ‘rw’, default => 0 );
method deposit (Num $amount) {
$self->balance( $self->balance + $amount );
}
method withdraw (Num $amount) {
my $current_balance = $self->balance();
( $current_balance >= $amount )
|| confess “Account overdrawn”;
$self->balance( $current_balance – $amount );
}
}
和教条的Python相比我喜欢Perl的哲学, 同样的结果可以有不同的选择, 如何做,取决你自己.
几个很有用的TextMate bundle
TextMate的这几个bundle对我来说非常有用:
1. CTags
和vim中的类似,可以ctrl+] 可以快速列出和跳转到symbol所在的文件位置. 有点遗憾的是不能想vim那样返回.
适合多数的编程语言.
2. Devel::IntelliPerl
Perl的自动解析和语法提示,能够根据指定变量的上下文给出语法提示,主要是列出可以调用的方法. 很cool.
除了TextMate,还支持vim. 由于使用Moose进行反射分析,可以显示出继承的方法. 比单纯的正则分析
要准确的多.
3. Act
可以使用正则表达式快速搜索项目中的文件. 使用Perl编写, 是一个快速和强大的grep 替代品.
TextMate中的search in project可以休息了. 如果你用TextMate,就知道在大的项目中使用search in project是
一种煎熬. 我很多次不长记性的调用,看着高起的CPU和风扇声,被迫kill掉textmate. 使用Act可以省去find+grep
的诸多不便. 而且, 由于是Perl编写,你可以使用完整的PCRE, 而不是GNU的精简版, 甚至你可以只搜索某种
编程语言的.很不错的.
其他如project plus,git bundle这些都是标配了.
技巧:用ETag产生更为有效的304 Not Modified
对于大流量的网站, 尽可能使用304 Not Modified, 这将大大节约带宽的使用,也节省你的资金投入.
通常,对于静态文件, 一般都无须配置, 多数的http server能够产生有效的last-modifed-time和Expires header,
同时下次请求的时候自动判断并输出304.
但是对于动态输出的页面, 依赖时间戳并不是一个有效的方式. 很多时候, 我们需要频繁更新文件,但是
文件内容并没有改变. 这是就要用到ETag了.
关于ETag即Entity Tag是W3C在HTTP 1.1中定义的. 对于动态输出,我们可以使用md5值作为etag的值, 这样
可以当内容没有变更,那么就可以直接返回304了.
结合MongoDB的GridFS,ETag更加有用. 当文件被存储到db中的时候, 计算文件的md5值,保存. 当输出gridfs中的文件时候,
同时输出日期和etag.
以下是一个简单的PHP类, 用于检查是否刷新, 这个类首先检查过期时间, 同时检查etag, 并且etag的优先级高于日期检查:
/**
* Cache Validator
*
* A helper class to mainipulate/validate cache.
*
* The code is ported from my oooold project(eps2004),but works!
* @author night
*/
class CZone_Core_Util_HttpCacheValidator {
/**
* Validate client headers and check wheather to send 304 header.
*
* @param int $lastModified timestamp to validate
* @param string $tag ETag to validate
*/
public static function is_expired($lastModified,$tag,$headers){
// $now = time();
// $headers = getallheaders();
$refresh=TRUE;
if(isset($headers["If-Modified-Since"])) {
$arraySince = explode(";", $headers["If-Modified-Since"]);
$since = strtotime($arraySince[0]);
if($since >= $lastModified) $refresh=FALSE;
}
/**
* Check Entity Tag(ETag)
*
* Entity tags are used for comparing two or more entities from the same requested resource.
* HTTP/1.1 uses entity tags in the ETag (section 14.19), If-Match (section 14.24),
* If-None-Match (section 14.26), and If-Range (section 14.27) header
* fields. The definition of how they are used and compared as cache
* validators is in section 13.3.3. An entity tag consists of an opaque
* quoted string, possibly prefixed by a weakness indicator.
*
* entity-tag = [ weak ]
* opaque-tag weak = “W/”
* opaque-tag =quoted-string
*
* A “strong entity tag” MAY be shared by two entities of
* a resource only if they are equivalent by octet equality. A “weak
* entity tag,” indicated by the “W/” prefix, MAY be shared by two
* entities of a resource only if the entities are equivalent and could
* be substituted for each other with no significant change in
* semantics. A weak entity tag can only be used for weak comparison.An
* entity tag MUST be unique across all versions of all entities
* associated with a particular resource. A given entity tag value MAY
* be used for entities obtained by requests on different URIs. The use
* of the same entity tag value in conjunction with entities obtained by
* requests on different URIs does not imply the equivalence of those
* entities.
*
* See HTTP/1.1(W3C)
*
*/
if(isset($headers["If-None-Match"])) { // check ETag
if(strcmp($headers["If-None-Match"], $tag) == 0 ){
$refresh=FALSE;
}
else {
$refresh=TRUE;
}
}
if(isset($headers["If-Match"])) { // check ETag
if(strcmp($headers["If-Match"], $tag) == 0 )
$refresh=FALSE;
else
$refresh=TRUE;
}
//firefox style,resume download
if(isset($headers["If-Range"])) { // check ETag
if(strcmp($headers["If-Range"], $tag) == 0 )
$refresh=FALSE;
else
$refresh=TRUE;
}
return $refresh;
}
?>
MongoDB的Perl driver的中文乱码问题
Perl下面向mongodb插入中文字符串会出现乱码.
根据MongoDB的文档, MongoDB支持UTF-8的编码. 但在Perl中,
如果直接使用utf8的字符串,也会出现问题.
测试代码:
my $mongo_dbh = $mongo_connection->get_database( $mongo_db );
my $t = $mongo_dbh->get_collection(‘test’);
my $word = ‘测试’;
$t->insert({ title => $word });
my $row = $t->find_one();
say “title:”,$row->{title};
$t->remove();
输出结果是乱码. 在mongo shell和PHP中得到的也是乱码.
我初步判断是perl driver没有能够识别utf8编码而是强制encode成utf8编码后存储.
修改如下:
my $mongo_dbh = $mongo_connection->get_database( $mongo_db );
my $t = $mongo_dbh->get_collection(‘test’);
my $word = ‘测试’;
$t->insert({ title => decode_utf8($word) });
my $row = $t->find_one();
say “title:”,$row->{title};
$t->remove();
输出正常. 判断正确, 问题解决. 希望Kristina能够修改就无须多此一举(当然,如果是非utf8编码还是需要转换的),
也许并不是bug而是个feature?
UPDATE: Kristina的回复很迅速, 一觉醒来, master里已经加入判断是否为utf8的代码. CPAN .27(下周2发布)以上不会存在这个问题.
但是, 其他格式的编码仍然需要转换为utf8编码,因为BSON只支持UTF8编码.
Patch for build gmagick on mac osx 10.6(snow leopard)
在mac osx 10.6.2(snow leopard)编译gmagick失败. 错误如下:
ld: duplicate symbol _php_gmagick_sc_entry in .libs/gmagick_methods.o and .libs/gmagick_helpers.o
collect2: ld returned 1 exit status
make: *** [gmagick.la] Error 1
感觉很奇怪,因为在centos上没问题. 检查了下gmagick_methods.c和gmagic_helpers.c 也没有重复定义啊.
$ nm .libs/gmagick_methods.o |grep _php_gmagick_sc_entry
000000000000f570 S _php_gmagick_sc_entry
$ nm .libs/gmagick_helpers.o |grep _php_gmagick_sc_entry
00000000000034b8 S _php_gmagick_sc_entry
Oh, ld没错, 的确是重复定义了,由于类型是S,那么还是php_gmagick_sc_entry的声明有问题.
再仔细查看了一下,果然. 由于php_gmagick_sc_entry是在php_gmagick.h中声明,而在gmagick_methods.c
和gmagic_helpers.c中都include了这个文件. 由于没有显示声明为exten导致了问题. 重新加入exten修饰符,
ok.
===========Patch=============
— php_gmagick.h 1970-01-01 17:13:08.000000000 +0800
+++ php_gmagick.h.ns 2009-12-03 01:17:52.000000000 +0800
@@ -18,7 +18,7 @@
*/
#ifndef HAVE_PHP_GMAGICK_H
-# define HAVE_PHP_GMAGICK_H
+#define HAVE_PHP_GMAGICK_H
/* Define Extension Properties */
#define PHP_GMAGICK_EXTNAME “gmagick”
@@ -107,12 +107,12 @@
#endif
/* Class entries */
-zend_class_entry *php_gmagick_sc_entry;
-zend_class_entry *php_gmagickdraw_sc_entry;
-zend_class_entry *php_gmagickpixel_sc_entry;
-zend_class_entry *php_gmagick_exception_class_entry;
-zend_class_entry *php_gmagickdraw_exception_class_entry;
-zend_class_entry *php_gmagickpixel_exception_class_entry;
+extern zend_class_entry *php_gmagick_sc_entry;
+extern zend_class_entry *php_gmagickdraw_sc_entry;
+extern zend_class_entry *php_gmagickpixel_sc_entry;
+extern zend_class_entry *php_gmagick_exception_class_entry;
+extern zend_class_entry *php_gmagickdraw_exception_class_entry;
+extern zend_class_entry *php_gmagickpixel_exception_class_entry;
/* Forward declarations */
PHP_METHOD(gmagick, __construct);
— gmagick.c 1970-01-01 17:13:08.000000000 +0800
+++ gmagick.c.ns 2009-12-03 01:44:20.000000000 +0800
@@ -27,6 +27,13 @@
static zend_object_handlers gmagickdraw_object_handlers;
static zend_object_handlers gmagickpixel_object_handlers;
+zend_class_entry *php_gmagick_sc_entry;
+zend_class_entry *php_gmagickdraw_sc_entry;
+zend_class_entry *php_gmagickpixel_sc_entry;
+zend_class_entry *php_gmagick_exception_class_entry;
+zend_class_entry *php_gmagickdraw_exception_class_entry;
+zend_class_entry *php_gmagickpixel_exception_class_entry;
+
/* {{{ static void php_gmagick_object_free_storage(void *object TSRMLS_DC)
*/
static void php_gmagick_object_free_storage(void *object TSRMLS_DC)
===========END PATCH============
UPDATE: 作者Vito回信很迅速啊, 他只使用Linux,patch已经被采纳了.
解决GraphicsMagick 和 ImageMagick冲突(PHP imagick and gmagick extension)
发现PHP imagick or magickwand无法正确加载. 经过测试发现是由于和gmagick冲突. 解决, 在编译GraphicsMagick时候加入:
–enable-symbol-prefix
重新编译后正常.
Php-fpm 0.6+PHP 5.2.11+nginx 0.8.28 设置PATH_INFO
从5.2.6升级到5.2.11后PATH_INFO突然无法使用了(no input file). 不知道是谁的问题(新的php-fpm导致). 解决:
必须使用NGINX的fastcgi_split_path_info.
location ~ .*\.php(.*)$ {
fastcgi_split_path_info ^(.+\.php)(.*)$;
include fastcgi_params;
fastcgi_param PATH_INFO $fastcgi_path_info;
}
之前,以下配置是能工作的(设置php.ini=>cgi.fix_pathinfo =1):
fastcgi_param PATH_INFO $fastcgi_script_name;
升级真麻烦, 真不如回到5.2.6吧.