码完文章一看,都2014年了,在这里先祝大家新年快乐!O(∩_∩)O~~

看了@Ph4nt0m 菊苣写了一篇类似的文章,刚好我最近也在做这种事情,所以也就顺便写了篇文,欢迎交流~

第一次写文,为了篇幅够长够高端,所以我先来说说缘由吧。

其实目前来看,市面上无非就两种架构的数(ku)据(zi)查询系统。

第一种就是最常见的 文件 + 单行搜索格式。

第二种就是 数据库(常见是Mysql) + 搜索

上面两种的缺点就很明显了,第一种,数据无缓存,且非常依赖IO,而且不能方便的整理数据。一旦查询量增加,对IO的要求就会非常高,速度会下降的非常明显。

第二种,其实也可以分为两种,一种就像Ph4nt0m 菊苣一样,非常简单的 id - content一一对应,还有一种就是每个信息单独分开。这两种速度差其实并不大。由于mysql的like性能非常的低下,尤其是使用了通配符的,基本上索引是失效的。但是mysql内部还是有缓存的,所以当你查询别人查过内容以后,速度还是会比较快的。

其实ovear一开始也是使用的第二种方法,在数据量在100W以下的时候,mysql的like还勉勉强强能在0.5S内,但是一旦数据量开始增加,需要的时间就从0.5变到1 2 3 4,对于我们这种海量数据,显然是不行的。

好了,看来上面两种架构,基本上没有神马用途了。这时候肯定有童鞋跳出来问:“为毛不用nosql捏,现在nosql这么屌”。

嘛,这种架构我也是考虑过的,但是其中的一个问题就是他的key你怎么处理,而且使用nosql意味着你要自己写搜索算法,如果单纯暴力的直接搜索,那么跟第一种架构基本上没差别,而且速度还会更慢。而且你必须得保证你的key是唯一的,这样子看来麻烦的要死,自然也被我们放弃了。

那么目前较为妥善的解决方法就是,1、普通数据库+索引程序

2、nosql+索引

显然nosql+索引速度会更好一点,但为什么我最后还会采用普通的数据库呢?其实原因很简单,因为我懒(TAT)。(希望各位如果能有些出nosql版的记得发篇文章给我参考下~)

如果用nosql还得自己写一堆封装,还要自己写pool,使用常规数据库可以用市面上一堆sql pool,刚好上个项目又写了个manger,所以顺便就使用了常规数据库。

其实nosql的速度真的会快很多。如果有能力,强烈推荐换成nosql。至于nosql怎么选,mongodb和redis都是不错的选择。如果英语比较好推荐orientdb,原生支持分布式。

那么数据库决定了,就要决定索引程序了。市面上的索引器大概流行的就是lucene和sphinx。

我跟Ph4nt0m刚好相反,使用了lucene作为索引器。现在我来讲讲为什么会选lucene而不是sphinx。

首先lucene和sphinx的开发语言不同,lucene是用的java开发,sphinx是使用的c/c++开发,速度自然是sphinx快一些,但是这其实还是跟使用方法有关系的。而我刚好又会点java,所以就选择了sphinx。

Sphinx还有一个问题,就是索引更新问题,Sphinx索引不支持动态更新,所以不适合经常插数据库。Lucene则支持的比较好,而且像我们这类程序,不需要分词,直接搜索即可,所以理论上不分词的lucene效率会更好一点(呃)

另外java内部调用lucene不需要额外安装程序,直接使用一个jar就行了,而且不用使用api也是我选择lucene的主要原因之一。

接下来就是数据表结构了,这里我采用的是分析过后的多字段来表述,表结构如下

CREATE TABLE `NewTable` (
`id`  int(11) NOT NULL AUTO_INCREMENT ,
`username`  varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL ,
 `real_name`  varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '真实姓名' ,
 `password`  varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL ,
 `salt`  varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL ,
 `email`  varchar(60) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL ,
 `qq`  varchar(12) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL ,
 `phone`  varchar(12) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL ,
 `cell_phone`  varchar(12) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL ,
 `source`  varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL ,
 PRIMARY KEY (`id`),
UNIQUE INDEX `id` (`id`) USING BTREE
)
ENGINE=MyISAM
DEFAULT CHARACTER SET=utf8 COLLATE=utf8_general_ci
AUTO_INCREMENT=7703021
CHECKSUM=0
ROW_FORMAT=DYNAMIC
DELAY_KEY_WRITE=0
;

这样子细分的好处就是, 以后数据聚合起来比较方便(比如说人员关系分析啥的,咕嘿嘿,可以自己想象下)

为了处理数据库,我采用的方法是针对不同特征的数据库,采用不同的Loader,比如说discuzLoader等等,对于同一类的数据非常方便,但是对于自己写的系统就比较麻烦了,所以我现在正在写一个templateLoader(灵感来自@0x0F)

基本上就是输入特征自动入库

比如说 {USERNAME} {PASSWORD}这样规定文件格式,就可以轻松入库。

这里分享下discuzLoader

//TODO 增加分批入库
    @Override
    public boolean load(String source) {
        long start = System.currentTimeMillis();
        int offset = 0;
        String queryDiscuzSql = "select username,password,email from " + discuzTable;
         List<DiscuzBean> discuzInfo = QueryHelper.query(DiscuzBean.class, queryDiscuzSql);
         long end = System.currentTimeMillis();

        System.out.println("initial sql completed, spend [" + (end - start) + "]ms");

        String sql = "insert into " + newTable + "(username,password,email,source)values(?,?,?,?)";
         Iterator<DiscuzBean> iter = discuzInfo.iterator();
        DiscuzBean discuzBean;

        int handled = 0;
        start = System.currentTimeMillis();
        while(iter.hasNext()){
            discuzBean = iter.next();
            if(handled != 0 && handled % 10000 == 0){
                end = System.currentTimeMillis();
                System.out.println("handled [" + handled + "] spend [" + (end - start) + "]ms");
                 start = System.currentTimeMillis();
            }
            QueryHelper.update(sql, discuzBean.getUsername(), discuzBean.getPassword(),
                     discuzBean.getEmail(), source);

            handled++;
        }
        return true;
    }
}

看看时间不早了,明天再爬起来写第二张~(索引的详细用法、思路以及常见陷阱~)

希望大家能看过这系列文章后都有自己的高性能海量数(ku)据(zi)查询系统~

先来说说效果吧,lucene入库速度还算不错10000条可以控制在一秒作用,这样百万级别的数据还是可以非常快速地索引。

_(:з」∠)_搜完喽(耗时index [92ms], background [47ms], database [896ms]),结果如下:

可以看出,1000W的数据,原本查询需要5-6S,顺利的缩小到了1S内,但是目前还有比较大的问题,就是数据库还是不够快,但是查询时间已经基本可以控制在1S内了,比之前的动不动就是十几秒好多了,我也会继续看看怎么解决普通数据库查u型纽曼的问题的~

如果实在不行咱就再出一期nosql版~

我的mail是ovearj#gmail.com

欢迎通过email跟我交流,或者站内信~(wooyun有么?)

PS1:查询喜用采用Java语言开发。

PS2:http://s.atv.ac/ 使用的就是这套系统。但是目前耦合度比较高,暂时分离不出来,所以开源还得等一段时间。

[原文地址]

相关讨论:

1#

0x0F (..........................................................................................................................................................................................................................................................) | 2014-01-01 00:21

新年第一劈!

2#

Ovear | 2014-01-01 00:23

@0x0F 0菊苣!

3#

法海 | 2014-01-01 00:25

导入的时候 如果用

insert into " + newTable + "(username,password,email,source)values(?,?,?,?),(?,?,?,?),(?,?,?,?),(?,?,?,?),(?,?,?,?),....

会不会快些?

4#

x1aoh4i (---------------------------------------------------------------------------------------------1111111111111111111111111111111111111111111111111111111111111111111--------------------------------------------------------111111111111111111111111111111111111111) | 2014-01-01 00:30

String queryDiscuzSql = "select username,password,email from " + discuzTable;

语句能这样写么

5#

Ovear | 2014-01-01 00:35

@法海 晚点我去试试~其实是因为我这个玩意不支持这样生成数据,自己拼接sql我感觉不安全~所以就没试了~

6#

Ovear | 2014-01-01 00:36

@x1aoh4i 语句没问题的~

7#

if、so (no talk,but shell!!) | 2014-01-01 00:50

@Ovear 那就把你的海量数据做成种子发我吧

8#

0x0F (..........................................................................................................................................................................................................................................................) | 2014-01-01 01:01

顺便发下quickLoader这个工具需求吧,总觉得应该已经有人写出来了,但是没找到。路过码神感兴趣的可以试试共享?@Ovear

InsertFile:

File1.[*discuz].sql
[code]INSERT INTO `bbs_common_member` (`uid`, `email`, `username`, `password`, `status`, `emailstatus`, `avatarstatus`, `videophotostatus`, `adminid`, `groupid`, `groupexpiry`, `extgroupids`, `regdate`, `credits`, `notifysound`, `timeoffset`, `newpm`, `newprompt`, `accessmasks`, `allowadmincp`, `onlyacceptfriendpm`, `conisbind`) VALUES
(1, 'name@domain.com', 'admin', 'e10adc3949ba59abbe56e057f20f883e', 0, 0, 1, 0, 1, 1, 0, '', 1191386040, 85, 1, '9999', 0, 0, 0, 1, 0, 1),
......

File2.[*dede].sql
INSERT INTO `dedecms_member` (`mid`, `mtype`, `userid`, `pwd`, `uname`, `sex`, `rank`, `uptime`, `exptime`, `money`, `email`, `scores`, `matt`, `spacesta`, `face`, `safequestion`, `safeanswer`, `jointime`, `joinip`, `logintime`, `loginip`, `checkmail`) VALUES
(1, '个人', 'admin', 'e10adc3949ba59abbe56e057f20f883e', 'admin', '男', 100, 0, 0, 0, '', 10006, 10, 0, '', 0, '', 1343443904, '', 1371490073, '127.0.0.1', -1),
......

File3.[*].txt
ID UNAME EMAIL PWORD logintime face xxx
1 admin admin@admin.com password 123 123 123
......

Program:

Input是这样的。导入文本规则能够自定义,导入时导入文本,输入来源、特征文本格式和分隔符即可,特征文本如下。

File1: ([__RANDOM__], '[*Email*]', '[*Uname*]', '[*PWord*]', [__RRANDOM__] 特征字符: [ ), ]
File2: ([__RANDOM__], '[__RANDOM__]', '[*Uname*]', '[*PWord*]', [__RRANDOM__] 特征字符: [ ), ]
File3: [__RANDOM__] [*Uname*] [*Email*] [*PWord*] [__RRANDOM__] 特征字符: [ \r\n ]

特征文本识别[__RANDOM__]为一个不包含特殊字符的随机内容,而[__RRANDOM__]为任意字符,正则匹配取出要求字段,输出一段符合服务器数据库的sql或者其他格式。

不知道讲清楚了没有,费脑细胞啊。。各位新年快乐。

9#

Ovear | 2014-01-01 01:33

顺便做个广告~

http://s.atv.ac/ 使用的就是这套系统。但是目前耦合度比较高,暂时分离不出来,所以开源还得等一段时间。

10#

Ovear | 2014-01-01 01:35

@0x0F _(:з」∠)_ 0菊苣我正在写呢,sql语句分析暂时无能为力,在找现成的sql分析器

11#

Ph4nt0m | 2014-01-01 03:43

赞,元旦快乐!当初我没有细分表主要有两个原因.

1.怕细分后,有很多数据库很多字段用不上.浪费空间.

2.怕导入数据过程太麻烦,偷懒 :)

另外我比较头疼,究竟选择哪些字段较好.像有些裤子的登陆IP、时间,有时候这些信息都挺有帮助。最初我是选定了 数据库ID、用户名、昵称、密码、邮箱、salt、ext{合并所有不重要的东西}。最终还是选择了 id,数据库id,content这样的懒方法。

最后有个小建议,把source 换成 sourceid。库名单独一个表,节省空间又方便修改。

12#

核攻击 (统治全球,奴役全人类!毁灭任何胆敢阻拦的有机生物!) | 2014-01-01 10:42

mark...

留言评论(旧系统):

佚名 @ 2014-01-05 18:46:48

请在这里填写留言内容,最长不超过 1000 字。

本站回复:

[暂无回复]