我是这么设计高性能海量数(ku)据(zi)查询系统的(一)

哎呀,一不小心就2014年了呢~回来看看博客,都长草了_(:з」∠)_。一转眼又要考试了,于是匆匆忙忙码了篇文出来(尼不就是想炫耀下你的裤子么)

另外在乌云也发了篇~希望各位菊苣也去看看~

群众:尼的Java反编译系列教程呢?那边还没坑玩,怎么有开了个坑!!

Ovear:啊哈哈哈哈,今天的天气真不错啊,来看文

群众:滚尼玛(西瓜皮,胖次,带血的胖次)

Ovear:呃,别扔了。咦等等,欧尼sama带血的胖次,啧啧,欧尼sama的血,舔 咳咳咳。。。

6467271020120811120612017

码完文章一看,都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 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 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/ 使用的就是这套系统。但是目前耦合度比较高,暂时分离不出来,所以开源还得等一段时间。

乌云地址:http://zone.wooyun.org/content/9590

我是这么设计高性能海量数(ku)据(zi)查询系统的(一) 有 17 个评论

发表评论

电子邮件地址不会被公开。 必填项已用*标注