问题
深分页的问题是很清楚。Solr必须为返回的搜索结果准备一个列表,并返回它的一部分。如果该部分来源于该列表的前面并不难。但如果我们想返回第10000页(每页20条记录)的数据,Solr需要准备一个包含大小为200000(10000 * 20)的列表。这样,它不仅需要时间,还需要内存。

令人高兴的是,Solr 4.7的发布改变了这一状况,引入了游标的概念。游标是一个动态结构,不需要存储在服务器上。游标包含了查询的结果的偏移量,因此,Solr的不再需要每次从头开始遍历结果直到我们想要的记录,游标的功能可以大幅提升深翻页的性能。

用法
游标的使用非常简单。在第一个查询中,我们需要传递一个额外的参数- cursorMark = *,告诉Solr返回游标。在返回中除了搜索结果,我们还可以得到nextCursorMark信息。看看下面这个例子。

查询

我们从一个简单的查询开始:

2
curl 'localhost:8983/solr/select?q=*:*&rows=1&sort=score+desc,id+asc&cursorMark=*'

这里我们传入一个cursorMark = *参数,告诉Solr的,我们要使用的光标。

搜索结果

上面的查询将返回以下搜索结果:

<?xml version="1.0" encoding="UTF-8"?>
<response>
 <lst name="responseHeader">
  <int name="status">0</int>
  <int name="QTime">33</int>
  <lst name="params">
   <str name="sort">score desc,id asc</str>
   <str name="start">0</str>
   <str name="q">*:*</str>
   <str name="cursorMark">*</str>
   <str name="rows">1</str>
  </lst>
 </lst>
<result name="response" numFound="32" start="0">
 <doc>
  <str name="id">0579B002</str>
  <str name="name">Canon PIXMA MP500 All-In-One Photo Printer</str>
  <str name="manu">Canon Inc.</str>
  <str name="manu_id_s">canon</str>
  <arr name="cat">
   <str>electronics</str>
   <str>multifunction printer</str>
   <str>printer</str>
   <str>scanner</str>
   <str>copier</str>
  </arr>
  <arr name="features">
   <str>Multifunction ink-jet color photo printer</str>
   <str>Flatbed scanner, optical scan resolution of 1,200 x 2,400 dpi</str>
   <str>2.5" color LCD preview screen</str>
   <str>Duplex Copying</str>
   <str>Printing speed up to 29ppm black, 19ppm color</str>
   <str>Hi-Speed USB</str>
   <str>memory card: CompactFlash, Micro Drive, SmartMedia, Memory Stick, Memory Stick Pro, SD Card, and MultiMediaCard</str>
  </arr>
  <float name="weight">352.0</float>
  <float name="price">179.99</float>
  <str name="price_c">179.99,USD</str>
  <int name="popularity">6</int>
  <bool name="inStock">true</bool>
  <str name="store">45.19214,-93.89941</str>
  <long name="_version_">1461375031699308544</long></doc>
 </result>
 <str name="nextCursorMark">AoIIP4AAACgwNTc5QjAwMg==</str>
</response>

我们看到,除了平时返回的结果外,还多了一个游标数据nextCursorMark,使用这个值作为我们翻下一页的参数。

下一个查询

提交下面这个查询看一下:

curl 'localhost:8983/solr/select?q=*:*&rows=1&sort=score+desc,id+asc&cursorMark=AoIIP4AAACgwNTc5QjAwMg=='

结果如下:

<?xml version="1.0" encoding="UTF-8"?>
<response>
 <lst name="responseHeader">
  <int name="status">0</int>
  <int name="QTime">2</int>
  <lst name="params">
   <str name="sort">score desc,id asc</str>
   <str name="indent">true</str>
   <str name="q">*:*</str>
   <str name="cursorMark">AoIIP4AAACgwNTc5QjAwMg==</str>
   <str name="rows">1</str>
  </lst>
 </lst>
<result name="response" numFound="32" start="0">
 <doc>
  <str name="id">100-435805</str>
  <str name="name">ATI Radeon X1900 XTX 512 MB PCIE Video Card</str>
  <str name="manu">ATI Technologies</str>
  <str name="manu_id_s">ati</str>
  <arr name="cat">
   <str>electronics</str>
   <str>graphics card</str>
  </arr>
  <arr name="features">
   <str>ATI RADEON X1900 GPU/VPU clocked at 650MHz</str>
   <str>512MB GDDR3 SDRAM clocked at 1.55GHz</str>
   <str>PCI Express x16</str>
   <str>dual DVI, HDTV, svideo, composite out</str>
   <str>OpenGL 2.0, DirectX 9.0</str>
  </arr>
  <float name="weight">48.0</float>
  <float name="price">649.99</float>
  <str name="price_c">649.99,USD</str>
  <int name="popularity">7</int>
  <bool name="inStock">false</bool>
  <date name="manufacturedate_dt">2006-02-13T00:00:00Z</date>
  <str name="store">40.7143,-74.006</str>
  <long name="_version_">1461375031846109184</long></doc>
 </result>
 <str name="nextCursorMark">AoIIP4AAACoxMDAtNDM1ODA1</str>
</response>

现在,返回的nextCursorMark变化了,这是新的游标。

进一步查询

接下来的查询就很清楚了,使用cursorMark参数不断翻页,再来一次:

curl 'localhost:8983/solr/select?q=*:*&rows=1&sort=score+desc,id+asc&nextCursorMark=AoIIP4AAACoxMDAtNDM1ODA1'

总结

Solr的4.7引入的这个游标参数非常简单,大大提升了翻页的效果,详细的测试报告看这里:

http://searchhub.org/2013/12/12/coming-soon-to-solr-efficient-cursor-based-iteration-of-large-result-sets

java实现:

  1. static void deepPaging() throws SolrServerException{  
  2.         HttpSolrServer server = new HttpSolrServer("http://192.168.238.133:8080/solr/collection1");  
  3.         server.setSoTimeout(10000);  
  4.         server.setConnectionTimeout(10000);  
  5.         server.setDefaultMaxConnectionsPerHost(12);  
  6.         server.setAllowCompression(true);  
  7.         SolrQuery query = new SolrQuery();  
  8.         query.setQuery( "*:*" );  
  9.         query.setRows(4);  
  10.         query.addSort("price",ORDER.desc).addSort("id", ORDER.desc);  
  11.         query.set(CursorMarkParams.CURSOR_MARK_PARAM, "*");  
  12.         QueryResponse rsp = server.query( query );  
  13.         List<CursorMark> beans = rsp.getBeans(CursorMark.class);  
  14.         System.out.println(rsp.getNextCursorMark());//得到下一个游标  
  15.         for (CursorMark cursorMark : beans) {  
  16.             System.out.println(cursorMark);  
  17.         }         
  18.     }