【Solr】存在的问题与优化措施

详单查询项目中,采用solr作为详单索引,但是由于solr的索引结构是为搜索引擎文本检索而设计的,应用到详单查询百亿量级的数据索引上,产生了比较大的性能问题。这些问题是由于lucene索引结构本身的特点导致的,直接应用于详单数据表的索引构建,会产生非常大的性能损耗,以下逐一分析

查询性能损耗

以实际的话单数据索引表为例,数据表按月份进行分片,每个月份的数据还会按照service_ID哈希值再进行分片。假设话单索引表每个分片由以下字段组成:

ID 记录ID,全局唯一

service_ID 服务ID,查询时必须指定这个ID,一个号码对应一个服务ID

date 日期,取值1-31,具体几月由分片本身决定

type 通话类型:呼入/呼出

fee 通话费用

(其它字段略,以下分析只用以上字段作为例子)

之前的项目中,在solr中建索引是这么建的:

ID列 建立string值类型的索引

service_ID列 建立string值类型的索引

date列 建立string值类型的索引

type 列 建立string值类型的索引

fee列 建立string值类型的索引

按照当前的数据规模,话单表一个月的数据量大约是10亿条记录。按照一个月数据做二十个分片,一个分片数据量约五千万条。

以一个典型的统计查询为例

统计指定分片中某个service_ID在日期1至20日所有呼入话单的费用总和。

对于集团大客户,一个月的话单记录数量在上万的量级,假定我们要查询的service_ID在这个分片中包含1万条话单记录,呼入呼出记录各占一半,同时平均分布在每个月1-31日。

按照上述的建立solr索引的方式,solr执行这个查询的时候会进行以下步骤:

1.通过建立好的service_ID索引获取这个service_ID的所有docID(这个docID是lucene内部生成的),生成一个大小为1万的docID列表,记为L1

2.通过建立好的date索引获取查询条件指定的1-20号的所有docID,注意,这时候获取的docID的数量大约是50000000*(20/31)。因为这个date索引无法过滤出匹配service_ID的docID,只能全部获取。这个docID列表记为L2。大致计算以下,一个docID至少占用4个字节,总共需要占用133MB,也就是说,光这一步就要到磁盘获取约133MB的数据量。

3.通过建立好的type索引获取查询条件指定的呼入话单所有docID,和上述类似的原因,这一步读取大约50000000*1/2个docID,需要到索引文件中读取大约100MB的数据量。呵呵。这个docID列表记为L3

4.合并docID列表,L1,L2,L3,对这三个列表做交集,合并后得到的docID数量估计:10000*(20/31)*(1/2),约为3300个docID。

5.对第4步3300个docID,获取这3300个doc数据。这一步是决定查询性能的最关键因素,依照数据在硬盘上的分布方式,耗费的时间差距可能达到100倍以上。

原因在于,为了通过docID查到doc数据,solr建立了两个文件,.fdx和.fdt,.fdt存放的是doc的数据这个文件是最大的,单个分片数GB的大小。.fdx存放docID(排好序,查询时通过二分查找定位)以及对应的doc数据在.fdt文件中的位置,相当于一个正向索引。那么性能的关键在于doc数据在fdt文件中的存储顺序。这个顺序是由doc入库顺序决定的。那么按照每条话单生成的时间顺序来入库的话,获取这3300个docID对应的记录,这些记录会离散的分布在数GB大小的fdt文件中,会触发硬盘产生接近3300次的IO动作,对于机械硬盘,每次IO动作的开销在10ms的量级,SSD会好很多,但是一样也禁不起这种方式的暴力使用。 但是如果把这个serviceID对于的约一万条记录在.fdt文件中连续存放,硬盘只需要连续读取数百KB的数据量就能全部获取到这些记录数据。硬盘IO读取连续的数据块是非常快的,数百KB的量,至多需要100ms(机械硬盘,SSD更快)。

6.对第五步获取的所有doc,取出里面的fee列值,求和,结果返回。

你可能感兴趣的:(大数据,solr)