本文简单对比下solr与mysql的查询性能速度。
测试数据量:10407608 num docs: 10407608
这里对mysql的查询时间都包含了从mysql server获取数据的时间。
在项目中一个最常用的查询,查询某段时间内的数据,sql查询获取数据,30s左右
对collecttime建立索引后,同样的查询,2s,快了很多。
solr索引数据:
solr查询,同样的条件,72ms
"status": 0,
"qtime": 72,
"params": {
"indent": "true",
"q": "collecttime:[2014-12-06t00:00:00.000z to 2014-12-10t21:31:55.000z]",
"_": "1434617215202",
"wt": "json"
}
好吧,查询性能提高的不是一点点,用solrj代码试试:


solrj查询并获取结果集,结果集大小为220296,返回5个字段,时间为12s左右。
为什么需要这么长时间?上面的"qtime"只是根据索引查询的时间,如果要从solr服务端获取查询到的结果集,solr需要读取stored的字段(磁盘io),再经过http传输到本地(网络io),这两者比较耗时,特别是磁盘io。
时间对比:
查询条件
时间
mysql(无索引)
30s
mysql(有索引)
2s
solrj(select查询)
12s
如何优化?看看只获取id需要的时间:
sql查询只返回id,没有对collecttime建索引,10s左右:
sql查询只返回id,同样的查询条件,对collecttime建索引,0.337s,很快。
solrj查询只返回id,7s左右,快了一点。
id size: 220296
time: 7340
查询条件(只获取id)
10s
0.337s
7s
继续优化。。
关于solrj获取大量结果集速度慢的一些类似问题:
http://stackoverflow.com/questions/28181821/solr-performance#
http://grokbase.com/t/lucene/solr-user/11aysnde25/query-time-help
http://lucene.472066.n3.nabble.com/solrj-performance-bottleneck-td2682797.html
这个问题没有好的解决方式,基本的建议都是做分页,但是我们需要拿到大量数据做一些比对分析,做分页没有意义。
偶然看到一个回答,solr默认的查询使用的是"/select" request handler,可以用"/export" request handler来export结果集,看看solr对它的说明:
it's possible to export fully sorted result sets using a special rank query parser and response writer specifically designed to work together to handle scenarios that involve sorting and exporting millions of records. this uses a stream sorting techniquethat begins to send records within milliseconds and continues to stream results until the entire result set has been sorted and exported.
solr中已经定义了这个requesthandler:


使用/export需要字段使用docvalues建立索引:
使用docvalues必须要有一个用来sort的字段,且只支持下列类型:
sort fields must be one of the following types: int,float,long,double,string
docvalues支持的返回字段:
export fields must either be one of the following types: int,float,long,double,string
使用solrj来查询并获取数据:


一个bug:
org.apache.solr.client.solrj.impl.httpsolrclient$remotesolrexception: error from server at http://192.8.125.30:8985/solr/hotspot: expected mime type application/octet-stream but got application/json.
solrj没法正确解析出结果集,看了下源码,原因是solr server返回的contenttype和solrj解析时检查时不一致,solrj的binaryresponseparser这个content_type是定死的:
一时半会也不知道怎么解决这个bug,还是自己写个http请求并获取结果吧,用httpclient写了个简单的客户端请求并解析json获取数据,测试速度:


同样的查询条件获取220296个结果集,时间为2s左右,这样的查询获取数据的效率和mysql建立索引后的效果差不多,暂时可以接受。
为什么使用docvalues的方式获取数据速度快?
docvalues是一种按列组织的存储格式,这种存储方式降低了随机读的成本。
传统的按行存储是这样的:
1和2代表的是docid。颜色代表的是不同的字段。
改成按列存储是这样的:
按列存储的话会把一个文件分成多个文件,每个列一个。对于每个文件,都是按照docid排序的。这样一来,只要知道docid,就可以计算出这个docid在这个文件里的偏移量。也就是对于每个docid需要一次随机读操作。
那么这种排列是如何让随机读更快的呢?秘密在于lucene底层读取文件的方式是基于memory mapped byte buffer的,也就是mmap。这种文件访问的方式是由操作系统去缓存这个文件到内存里。这样在内存足够的情况下,访问文件就相当于访问内存。那么随机读操作也就不再是磁盘操作了,而是对内存的随机读。
那么为什么按行存储不能用mmap的方式呢?因为按行存储的方式一个文件里包含了很多列的数据,这个文件尺寸往往很大,超过了操作系统的文件缓存的大小。而按列存储的方式把不同列分成了很多文件,可以只缓存用到的那些列,而不让很少使用的列数据浪费内存。
注意export fields只支持int,float,long,double,string这几个类型,如果你的查询结果只包含这几个类型的字段,那采用这种方式查询并获取数据,速度要快很多。
下面是solr使用“/select”和“/export”的速度对比。
solrj(export查询)
项目中如果用分页查询,就用select方式,如果一次性要获取大量查询数据就用export方式,这里没有采用mysql对查询字段建索引,因为数据量每天还在增加,当达到亿级的数据量的时候,索引也不能很好的解决问题,而且项目中还有其他的查询需求。
我们来看另一个查询需求,假设要统计每个设备(deviceid)上数据的分布情况:
用sql,需要33s:
同样的查询,在对collecttime建立索引之后,只要14s了。
看看solr的facet查询,只要540ms,快的不是一点点。


查询条件(统计)
33s
14s
solrj(facet查询)
0.54s
如果我们要查询某台设备在某个时间段上按“时”、“周”、“月”、“年”进行数据统计,solr也是很方便的,比如以下按天统计设备号为1013上的数据:


这里为什么solr/lucene的facet(聚合)查询会这么快呢?
想想solr/lucene的索引数据的方式就清楚了:倒排索引。对于某个索引字段,该字段下有哪几个值,对于每个值,对应的文档集合是建立索引的时候就清楚的,做聚合操作的时候“统计”下就知道结果了。
如果通过docvalues建立索引,对于这类facet查询会更快,因为这时候索引已经通过字段(列)分割好了,只需要去对应文件中查询统计就行了,如上文所述,通过“内存映射”,将该索引文件映射到内存,只需要在内存里统计下结果就出来了,所以就非常快。
水平拆分表:
由于本系统采集到的大量数据和“时间”有很大关系,一些业务需求根据“时间”来查询也比较多,可以按“时间”字段进行拆分表,比如按每月一张表来拆分,但是这样做应用层代码就需要做更多的事情,一些跨表的查询也需要更多的工作。综合考虑了表拆分和使用solr来做索引查询的工作量后,还是采用了solr。
总结:在mysql的基础上,配合lucene、solr、elasticsearch等搜索引擎,可以提高类似全文检索、分类统计等查询性能。
参考:
http://wiki.apache.org/solr/
https://lucidworks.com/blog/2013/04/02/fun-with-docvalues-in-solr-4-2/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
http://www.cnblogs.com/luxiaoxun/p/4696477.html