AnyMongo – MongoDB driver for AnyEvent application

最近做了一个mongoDB在AnyEvent上驱动原型,大部分代码来自官方的perl driver,部分则来自ruby driver。我觉得ruby driver
比较有意思。 替换了普通版本的sock相关的操作,使用AE的handler替代,增加了BSON的decode和encode,重写了write_***函数。
同时,提供了一个AnyMongo::Compat兼容包, 原先使用MongoDB驱动的代码应该可以直接运行,因为这个包可以通过官方0.36的
大部分测试 t/perl-driver-api/*t . 对于使用了一些底层函数比如write/recv的代码则无法兼容。

由于是prototype,所以目前版本谨供学习和参考,不可能在正式使用,因为还缺少很多功能,包括authentication, paris/replica-set
支持,reconnect, timeout 等等。

性能上也不好不坏, 对于crud操作,要比官方的快20-30%, 但对于cursor操作,则相反要慢30%1%左右。官方版本的cursor的性能也
还是有点问题,因为当cursor遍历的记录数在10万以上时,双方的差距缩小到5%1%左右。

anymongo性能提升的空间还是很大,目前还是先把缺少的功能补上,同时也要测试Coro的兼容性,
最后再考虑解决cursor的性能问题。

计划在0.10左右可以投入生产使用,正好可以用在我们新项目上。

AnyMongo使用Perl license,源码:

http://github.com/nightsailer/any-mongo

Updated:
简单做了profiling,发现瓶颈在于一些调试代码和moose。去除调试代码,启用moose inlined destruction,
现在anymongo的cursor和官方版本几乎一样了,只有不到1%的差距。

MongoX v0.002 release

新增:

- with_context

with_context {
#your code
} db => ‘db1′

with_context可以创建一个context沙箱并运行代码block。 沙箱的概念是block中对于context_db/connection/collection的改变不会影响到代码外。这是对context对象local化了。

- for_connections
- for_dbs
- for_collections

在不同的dbs,collections,connections上循环执行代码段。当代码需要在不同的数据库和collection中执行时有用。

MongoDB commands的封装:

封装了以下commands :
普通的commands:
db_list_commands
db_stats
db_is_master
db_eval
db_add_user
db_remove_user
db_auth
db_create_collection
db_convert_to_capped
db_ping
db_repair_database
db_run_command
db_current_op
db_re_index
db_filemd5
db_map_reduce
db_distinct
db_group
db_insert
db_count
db_remove
db_update
db_update_set
db_find_one

db_find
db_find_all
db_find_and_modify
db_increment

这些是用于admin的commands:
admin_fsync_lock
admin_unlock
admin_server_status
admin_shutdown_server
admin_build_info
admin_get_cmd_line_opts
admin_log_rotate
admin_logout
admin_resync
admin_sharding_state
admin_unset_sharding
admin_diag_logging

下载:
http://github.com/nightsailer/mongo-x/tarball/v0.002

CPAN:

http://search.cpan.org/~doggy/MongoX-0.002/

I’m back,Perl!

在相当长的一段时间,我的首选开发语言是PHP,开发框架是Doggy,从PHP3 到 PHP 5.2.11,基本完备。
混合模式下,曾经使用过的其他的开发语言包括Perl, Java, C, C++。

PHP 是不错的Web开发的工具,但目前我彻底有些厌倦,尤其是对5.3系列,非常失望。和Ruby和Python不一样,
现在从PHP体会不到一丝新意,也感受不到乐趣。 Ruby 和 Python 虽然一直默默关注,但从未实践过,缺乏痛苦
的实践,就没有发言权。

Perl我一直作为后端处理的工具,这些年也从未丢过。CPAN给我提供了无限可能的资源,尽管有时候筛选可用的
模块也是比较麻烦的事情。Moose、Plack、Daner 这些让我看到Perl敏捷开发的新意。虽然Perl6还遥不可及,
但Perl5的开发终于重新启动,这让我有些兴奋。所以,今后一段时间,我会更关注Perl。

在目前计划的产品中,仍然使用MongoDB作为标配,Perl则不仅仅用于后台处理,将尝试进入前端,
Daisy 将是我近期开发的重点,这是基于Plack, Moose, MongoDB,类似Dancer的轻量级web framework。
和Doggy的封闭不同,Daisy将使用Perl许可进行开发,我会放到github上。

除了Perl, Object-C也将是我的兴趣点。iPad app是年内计划完成的事。

MongoX 0.001

发布了 MongoX-0.001, 封装了几个简单的syntax。

懒惰是程序员的美德,MongoX 就是解决我犯懒的小东西。
经常在用Perl做一些小工具进行MongoDB的维护。我希望能够更加简单清爽的使用,就像在mongo shell
里操作那样简单。于是,就封装了几个简单的类似DSL的语法。

# 最快速模式,连接到127.0.0.1,默认数据库 test
use MongoX host => ‘mongodb://127.0.0.1′,db => ‘test’;
# 按部就班的来
use MongoX;
#添加一个默认的connection
add_connection host => ‘127.0.0.1′;
#启用默认的connection
use_connection;
#切换到数据库 test
use_db ‘test’;
#添加另一个数据库连接,连接id为remote2
add_connection host => ‘192.168.1.1′,id => ‘remote2′;
#切换到remote2 连接
use_connection ‘remote2′;
#返回当前数据库中 foo这个collection
my $foo = get_collection ‘foo’;
#将 ‘foo’ 设置为默认的collection
use_collection ‘foo’;
#context_collection是默认collection
say ‘total rows:’,context_collection->count();
my $id = context_collection->insert({ name => ‘Pan’, home => ‘Beijing’ });
# context_db是当前默认的数据库,即 remote2连接中的foo
my $gridfs = context_db->get_gridfs;

当前就这些,哪天犯懒再把常用的mongo shell的一些helper整进来。

MongoX 目前已经上传到CPAN,可以
cpanm MongoX
就可以使用了。
源码库在 Github:

http://github.com/nightsailer/mongo-x

欢迎fork!

不同数据中心同步处理

很久之前看过facebook关于不同数据中心之间的延迟处理。

目前我们也遇到类似的问题。这次我们使用的是mongodb。
由于国内恶劣的环境和昂贵的价格,我们被迫将一些大量消耗带宽的服务迁移到国外的主机。这导致了大量的网络延迟。
大概200-400ms。 这样,仅仅依靠mongodb的同步是无法解决数据的一致性了。

由于数据同步量很大,又通过VPN,主数据中心和国外同步的时间周期要以分钟甚至小时计算了。

没办法,只能土法了。

当前解决方案是:

1. 数据一律从国内dc进行更新,此时设置更新数据状态为未同步
2. 数据更新后,发送一个消息到国外dc的队列,对应的worker则开始监控此数据的同步情况
3. 数据未同步前,所有的数据均从国内的主数据进行分发处理
4. 同步完毕,国外dc通知国内dc,将数据状态更改为已同步
5. 一旦同步,后续的数据处理工作自动转发到国外,从而实现分流

缺点:

监控worker和队列的工作状态需要监控。否则会导致流量无法分发。

虽然是土法,但也能解决眼前的问题。

6月小结:世界杯

四年一度的世界杯马上要曲终人散了。生活又要归于平淡的忙碌。

6月份几乎没有写什么东西,连推也很少上。最大的乐趣是每天和同事们下注赌球,然后半夜一起看球。

有幸被忽悠成了公司世界杯观球会的首个冠名赞助商,代价是1箱青岛啤酒,回报是,每次进入会议室,
可以有掌声鼓励,可以坐投影下的最前排,呵呵,和同事一起开投影看球,当然少不了赌注,嗯,在公司的
几次都赢了。

让我悲情的是阿根廷对德国,虽然从投机角度,我押了赔率最高的德国赢,但仍希望是阿根廷出现,老马再
得意一场,最终的结果,钱赢了,心却不忍。

技术方面,这个月个人关注的是Perl方面的东西,还有几个microframework,如Dancer,Sinatra 之类的。正写一个自己的micro framework, 希望能介于dancer和catalyst之间。国内目前用Perl的已经绝迹了,但我仍然力推
Perl作为我们未来产品的首选开发语言,这就是因地制宜了。

年初兼任了社区产品的产品经理,目前正在做社区产品的改版。希望这次能够有些进步。做产品和做技术研究,以及做项目差别很大,
杯具的是,我现在需要把自己的精力在三者进行协调。

我是一个典型的宅人,这次公司的海边游玩,争取到最后终于可以不去,安静在家里看看书,写写code,这些都是平日无法去作的,乐得其所,真是畅快呀!

骄阳似火,7月注定又是忙碌的一个月,希望当世界杯激情消退后,这些看似简单而令人乏味,枯燥但颇具挑战的工作能带来同样的快乐。

“get File(): bad file number value (corrupt db?): run repair”

好好的端午节,看球看的昏天黑地,押注正欢之际,机房来了个电话,告知,由于空开跳闸,我们的半个机柜歇菜了,有台r710还把电源线烧了。 查了一下,杯具啊, 主要的mysql 集群和mongodb 竟然都在这半个机柜中。

没办法,在乌乌祖啦的嗡嗡中,开始恢复数据。

Mysql 是轻车熟路了,直接把主从数据手动复制后重新启动mmm就好了。

MongoDB大部分都正常,不过有个节点出了点小差错,在validate的时候报错:

get File(): bad file number value (corrupt db?): run repair

嗯,看来也需要修复数据库了。
注意:
如果有类似现象的朋友,一定要把节点的local/admin数据库也repair. local用于master.

然后,把slave节点重新resync一下。

Non-blocking/Evented/IO 将会改变我们传统的编程模式

最近Node.js 很火, 我对此很有兴趣,我自己也在使用Perl的AnyEvent。
Event/IO的一个要点是non-blocking。 Nginx能够解决c10k的问题,一个关键是使用
Event Loop取代传统的thread/pre-fork。要想充分发挥nginx的优势,需要后端和对应
模块都能够non-blocking才行。

然而,现实多数的架构都是blocking的,比如PHP/FastCGI, 即便nginx的前端再好,后端的应用
依然是瓶颈。

所以,如何使用Event/IO实现non-blocking编程,是我们要关注的一个领域。

Ruby的EventMachine, Python的Twisted, Perl的AnyEvent都是不错的开始。

Node.js则是另一个选择。 Javascript似乎是天生的适合作Event/IO的编程语言。 上手轻松,简单。

对于我们来说,最困难的是开始,如何适应新的基于callback的处理模式,这是最大的挑战。

在未来一段时间内,我将把注意力集中到node.js和AnyEvent. 对于我目前正在规划的视觉中国C10平台中,这将是一个非常有趣解决方案。

推荐关于node.js的slide:

http://nodejs.org/cinco_de_node.pdf

Why You Should Pay Attention to Node.Js

MongoDB的备份方式

简要说一下MongoDB的备份方式:

1. mogodump / mongorestore

这2命令将mongodb的数据dump为BSON格式,需要的时候可以恢复。
这种方式作为小的数据库还适用。但如果是sharding或者几百G数据以上的话
就几乎不可用了。因为BSON及其占用空间。

2. Slave Replication

这是最适合和可靠的,适合生产环境。MongoDb支持master+多个slave。因此可以很方便的起一个slave来进行备份。
但是需要注意的是如果数据量很大的话,如果是新建的slave,速度又没有保证的话,一定要调高 oplogSize 的大小,对于一个300G的数据库,
可以调到60G以上。这样避免同步到中间出现oplogSize不足等异常。

3. 关于增量热备份

对于某些热衷单独文件备份的同志,也可以考虑使用“增量热”备份的方式作为最后的屏障。
做法就是,最开始将某个slave暂时shutdown,然后将数据库文件复制到另一个目录作为增量备份的起点。

每次备份的时候,在这个目录上起一个slave, 使用–fastsync参数,同步完毕即可tar这个目录就可以了。

这种方式的一个缺点就是如果备份周期的过长的话,空间浪费会非常大,尤其是频繁删除的情况下,
目前1.6以前的版本对于删除的空间回收机制有bug。这个问题应该在1.6版本发布时候解决。

要想回收这些空间需要repairDatabase,速度比较慢,不如直接从头clone一个新的db了。(其实Repair的过程也就是先clone然后copy回来)。

综上所述, 最靠谱和安全的备份方式就是用mongodb自身的同步复制机制.
最经典也最小的生产环境是:
1 master + 2 slaves

Gearman::Driver是不错的Geamman worker管理器

最近发现了Gearman::Driver。可以方便的编写gearman的worker。由于使用了Moose和POE,扩展起来相当简便。比如可以对某个class的method
添加一个Job attribute,就能够实现worker了:

package # hide from PAUSE
Gearman::Driver::Test::Live::RuntimeOption;

use base qw(Gearman::Driver::Test::Base::All);
use Moose;

sub job1 : Job :MinProcesses(2): MaxProcesses(10) {
my ( $self, $job, $workload ) = @_;
return $self->foo;
}

1;

job1就自动注册为:’ Gearman::Driver::Test::Live::RuntimeOption::job1′ 了。MinProcesses, MaxProcesses则定义worker要spawn的进程数。

Gearman::Driver还实现了一个console,通过telnet可以动态的更改worker的进程数,非常的方便。

美中不足的是,Gearman::Driver的worker 无法通过配置文件来配置,在部署的时候很不方便。 对于一些运行时要读取某些配置的worker,
只能通过hard code或者$ENV来传递。

于是,我fork后做了写改进,添加了一些选项,启动时使用–configfile就可以从对应的配置文件读取了。

这是我github上fork的Gearman::Driver

Next Page »