软件开发: 09 2007存档

mdbm和bdb

| | Comments (5) | TrackBacks (0)
       简单的测试了一下mdbm和BerkeleyDB:往里面写一千万条记录,再全部遍历读一遍。读mdbm花了14秒,而读BerkeleyDB花了70秒,看来bdb确实比mdbm慢了许多,毕竟bdb只是把记录cache到内存,不像mdbm直接就把记录放在内存上(名副其实的内存数据库)。
       奇怪的是:bdb使用Hash索引比使用btree索引慢了三倍多!难道是Hash算法做的太差?不太可能吧....

       还特别发现了两个BerkeleyDB的工具db_load和db_dump,使用非常简便:
       db_dump url.bdb > url.dat              //从bdb导出为明文数据到url.dat
       db_load -f url.dat url_new.bdb         //从明文数据(必须是db_dump导出的格式)导入为bdb
       结果重新load的bdb变为原来bdb的三分之一大小了,仔细一看数据并没有变化,看来立中同志说的“BerkeleyDB为了快速写硬盘采用了比较浪费的格式,文件撑到了一定程度需要整理”非常正确,而这个dump&load也算是个离线整理数据的好办法。


    //Mutex.h
    class Mutex
    {
    public:
        Mutex(int resouce);

        //....
    };

    //Thread.h
    class Thread
    {
    public:
        void run(void)
        {
            //....
        }

    private:
        Mutex m_mutex;
    };

    int main(void)
    {
        Thread thr;
    }
        这段代码用 gcc 3.4.6 编译后产生的编译错误提示是:
        test.cpp: In function `int main()':
        test.cpp:25: error: no matching function for call to `Thread::Thread()'
        test.cpp:12: note: candidates are: Thread::Thread(const Thread&)
        *** Error code 1
       乍一看还以为是Thread的构造函数不对,找了半天才发现是m_mutex做为Thread的成员变量必须在Thread的构造中掉用自身的构造函数Mutex(int resouce) 。我的文件可不像这段代码这样明显,它们分散在好几个文件里,结果这个编译提示造成了极大的困惑。以后要小心编译器的误导。
       不过,编译器也并没有说错,确实可以直接用Thread的拷贝构造函数来创建一个新的Thread实例,gcc只是做了一件它认为正确的事而已....

BerkeleyDB

| | Comments (0) | TrackBacks (0)

      做项目时遇到要存储大量的key/value对数据。mdbm可以,但它太浪费内存,同事建议用BerkeleyDB,它一样能存key/value对,而且对内存没有那么苛刻的要求。

      BerkeleyDB把主要的数据都存在硬盘,用cache来对付高度频繁的读写操作,但它不保证cache中的“脏数据”一定会立刻同步到硬盘上(除非你调用它的API同步函数)。这就意味着如果你的数据像黄金一样珍贵(比如银行交易数据),就不适宜用BerkeleyDB;但如果你的数据只是“比较”珍贵,丢个几十秒的数据没关系,那BerkeleyDB还是不错的选择。

      BerkeleyDB还支持Hash/Btree/Queue等不同的索引方式,由用户在建库时自己选择。它只能在建库时指定cache的大小,以后不能再改,通用性稍微差了些,但对于后端的底层存储系统,这不是个大问题。

      BerkeleyDB以前由sleepycat负责维护,06年oracle收购了BerkeleyDB。

      自己写程序试了试,结果coredump了,就在创建数据库的那个函数pdb->open()里,gdb又跟不进去,后来找了立中同志才知道:redhat下都默认装了bdb,我没必要下载一个自己编译,那样反而链接错动态库了。最后ok,bdb存数据确实很省。

 

高贵的愤怒

| | Comments (5) | TrackBacks (0)
        今天看到的最“火爆”的新闻就是Linux之父Linus Torvalds和Dmitry争论c与c++的优劣,Torvalds认为c++太过繁复,被一堆特殊的语言机制和编译器补丁所压倒;而Dmitry却认为Torvalds是“旧语言时代的恐龙”,认为c语言太简陋而不能适应未来愈加复杂的软件开发。两边开始还只是技术性的讨论,后来就变得有些火药味了,但总算是平息了下来——Dmitry低姿态的结束了这场争论。
        我其实是个c++ fans,但是不知道为什么,我总觉得这件事上Torvalds更能说服我  :)
       也许我成为c++ fans只是因为跟java比,c++更像c一些。
        c++确实有那么一点“臃肿”,构造函数、析构函数、拷贝构造函数、多态、模板....可比c要啰嗦得多,不像c那么精瘦那么底层,和OS靠得那么近。即使有STL,Boost这样成熟的库,c++的开发依然是艰难的——只要想想当你编译c++模板出错时编译器报的那些天书一样的信息就明白了。我现在能看懂大部分模板编译错误的信息,但是我怎能奢求其他语言的开发者能适应并喜欢上这些错误提示呢?
       c++长成这样其实也是被迫的——如今的软件越来越庞大,越来越复杂,但对性能的要求却还在提高。如果用c开发像ACE这样的网络通信框架、如果用c来开发QT这样强大而通用的界面包,恐怕只有超级程序员才能完成,但你不能指望所有的程序员都是超人。所以尽管c++复杂,尽管c++难学难用,尽管在c程序员看来c++就像个怪胎、像个异形,但它长成异形何尝不是因为计算机行业的需要呢?
       我们羡慕Torvalds,他一直以来都用c在开发操作系统,一个人一生只用一种语言开发一类软件是何等的幸福,就好像你可以跟某个你爱的人厮守一样。也许正因为如此,Torvalds对其它语言毫无感觉——反正他也没必要用。相反别的程序员,尤其是国内的同志们却更喜欢吹嘘自己用过多少种语言....这是一种荣誉吗?不,这是一种悲哀,只是大家还没有认识到。如果从国内有计算机开始你就是个程序员,那你就要先学汇编和c语言,到了90年代初,系统软件在国内活不下去了,你又只能去学foxbase学basic学c++,但是数据库系统ERP系统也活不下去了,你只能又去学java学.net....我们原来做过如此多类型的软件,学过如此多类型的语言!为什么中国的程序员就需要这么“博学”,这么“心态开放”?那是因为中国的计算机行业说到底也不过是美国计算机行业的跟班,这个行业的原创力和推动力还是远在美利坚,我们就是在不停的气喘吁吁的跟在后面跑而已。
       Torvalds对c++的愤怒应该说其实是对软件行业浮躁之风的愤怒——大家只记得去追逐更方便更容易产生“产品”的快餐语言,而忘了去追求计算机科学更本质的东西——简约和求新。
        只不过这次他怒错了方向,c++成了刀下鬼,如果让Torvalds谈谈java谈谈.net谈谈脚本语言,他估计会疯掉的。

        做项目时碰到要查询和统计海量数据,所以想到了Hadoop,但同事反映Hadoop的性能不稳定,处理离线数据还行,处理实时数据就不保险了。又看了看其它的分布式文件系统,比如mogilefslustre等,它们只能分布式存储文件,不能分布式计算,换句话说:它们只能分摊IO,不能分摊CPU。最后决定离线数据还是用Hadoop。
       国内网站介绍Hadoop的文章都会提到MapReduce这个google的”招牌技术“。我是想不明白:GFS那套存储技术,很多做存储的公司都做得比它更好;而MapReduce做为分布式算法中的一个特例,很多互联网公司都有。但为什么大家都”误以为“这两门技术是来源于google的?因为以前做这些技术的公司都没什么名气,也没怎么宣传,而google拿出来写成论文大肆宣传。所以大家都把它当宝。
        大家都以为蒸汽机是瓦特发明的,其实不是,同时代的很多工程师都做出了蒸汽机,但瓦特是第一个拿它去申请专利的,所以大家都以为是他发明的。你可以认为瓦特是个有商业眼光的先行者,也可以认为他是一个利用同时代人智慧的窃贼。就看你怎么想了。


====== 2010.7.22 ======

看来很多同学对我这篇三年前的文章提出了质疑。我原想死撑一下,后来唐骏的事情发生了,我领悟了:有错就要认。
于是,在此,我承认:我的看法有问题,GFS的技术内涵,的确超过了很多做存储的公司,目前还没有规模和性能都比GFS高的分布式文件系统。

另外,说“MapReduce是分布式算法中的一个特列”,这应该不算错的,map-reduce算法并非google发明,但能从晦涩的教科书里找出实用的算法,精简之,工程化之,使之成为庞大而坚固的系统,这应该是google技术实力的强悍之处。
         一段简单的代码:

#include <map>
#include <ext/hash_map>

using namespace std;
using namespace __gnu_cxx;

struct Node
{
        size_t a;
       	hash_map<int,Node> subNode;
};

int main(void)
{}
        用g++编译时却出错:
....
test.cpp:10:   instantiated from here
/usr/lib/gcc/i386-redhat-linux/3.4.6/../../../../include/c++/3.4.6/bits/stl_pair.h:74: error:
 `std::pair<_T1, _T2>::second' has incomplete type test.cpp:8: error: forward declaration of `struct Node'
        看样子是Node的定义还没有完成就让hash_map实例化它,所以无法成功。但是把hash_map换成map就可以编译通过了....即使是就用hash_map,上面的代码在VC2003.net环境上也可以编译通过。
        我估计原因是gcc里的hash_map的实现依赖于pair的大小,所以如果_T2(即Node)类型不完整,pair就无法实例化,hash_map的大小也就无法确定。map其实也有这个问题,如果在编译时加上-D_GLIBCXX_CONCEPT_CHECKS参数,就可以看到编译错误。但为什么默认关掉这个参数?八成这是gcc做的一个“跳线逻辑”,免得开发者老抱怨 :)
        至于VC2003能顺利编译,这是因为微软的一贯作风:宁可将就用户,不可将就标准。这和gcc是两个风格。
        要解决hash_map的这个问题有两个办法,一个是使用继承:

struct BaseNode
{
};

struct Node:
       	public BaseNode
{
       	size_t a;
        hash_map<int,BaseNode> subNode;
};

另一个是使用指针:

struct Node
{
       	size_t a;
        hash_map<int,Node> *subNode;
};
前段时间由于工作需要编写了一个blowfish的php module,从blowfish的主页上下载的加密解密实现代码,自己加密、自己解密都没啥问题,但到了和别人联调的时候,对方用java写的blowfish解密程序却死活解不出来我加密的串。
查看对方的java代码,没想到blowfish除了密钥,还有那么多可配参数,java代码里写的解密模式为"cfb/nopadding",这是什么?仔细查了手册,除了cfb,还有ecb,cbc....我晕,blowfish主页上提供的代码都没有这些东西啊!
跑去问计算机安全方面的“伪专家”,他答道:
“这些只是加密模式,任何对称加密算法都有这几种模式”
“那我应该用哪种模式才能让别人的java代码正确解密我的串?”
“不同模式跟加密算法没关系,加密出来的串都一样,你就用blowfish主页上的那些代码就行了”
说了等于没说。
我改为自己找资料,最后有了结果:不同对称加密算法(像DES、blowfish)确实都有那几种加密模式(ecb、cbc、cfb等),但并不是跟加密算法没关系,使用不同的加密模式,加密出来的串都是不同的
使用什么加密模式,解密时也必须使用相同的模式,这就是对方无法解密我的串的原因——我压根就没使用任何加密模式...直接用Bruce Schneier老兄写的blowfish代码没戏了,我只能转而使用mcrypt,这个开源包不仅提供了各种对称加密算法,还提供了不同的加密模式,好用多了。
下载、编译成动态链接库libmcrypt.so,和我的测试代码链接运行,发现当我加密字符串“hello”,得出来得还是“hello”,再次抓狂,仔细看了mcrypt的示例代码??唉,我怎么稀里糊涂的把被加密的字符串声明成const了....最后能顺利编译运行的代码是:

	char text[1024];
char *m_crypt_iv = "0808008"; char *m_dest = "hello"; char *m_key = "abcdefg"; MCRYPT td; td=mcrypt_module_open("blowfish",NULL,"cfb",NULL); if(td==MCRYPT_FAILED) printf("wrong!\n"); i=mcrypt_generic_init(td,m_key,strlen(m_key),m_crypt_iv); if(i<0) printf("wrong!\n"); memset(text,0,sizeof(text)); memmove(text,m_dest,strlen(m_dest)); mcrypt_generic(td,text,text_len); mcrypt_generic_deinit(td); mcrypt_module_close(td);
 
其中的m_crypt_iv是cfb加密模式使用的初始向量,这段代码之所以不对被加密串进行64bit的填充(即nopadding),是因为cfb模式不用填充。
加密算法本身解决了,在编译PHP module时把libmcrypt.a加上,编出来的module就可以直接使用了。