W3Cschool
恭喜您成為首批注冊(cè)用戶(hù)
獲得88經(jīng)驗(yàn)值獎(jiǎng)勵(lì)
在大多數(shù)搜索應(yīng)用程序中,“top” 匹配結(jié)果(按分?jǐn)?shù)或其他標(biāo)準(zhǔn)排序)將顯示給某些用戶(hù)。
在許多應(yīng)用程序中,這些排序結(jié)果的用戶(hù)界面以“頁(yè)面”顯示給用戶(hù),其中包含固定數(shù)量的匹配結(jié)果,而用戶(hù)通常不會(huì)查看經(jīng)過(guò)前幾頁(yè)結(jié)果的結(jié)果。
在 Solr 中,使用 start 和 rows 參數(shù)支持這種基本的分頁(yè)搜索,通過(guò)使用 queryResultCache 和根據(jù)預(yù)期的頁(yè)面大小調(diào)整 queryResultWindowSize 配置選項(xiàng),可以調(diào)整這種常見(jiàn)行為的性能。
談到基礎(chǔ)的分頁(yè),最簡(jiǎn)單的方法就是將所需的頁(yè)碼乘以每頁(yè)的行數(shù) (將第一頁(yè)的頁(yè)碼視為 "0")。如在以下偽代碼中所示:
function fetch_solr_page($page_number, $rows_per_page) {
$start = $page_number * $rows_per_page
$params = [ q = $some_query, rows = $rows_per_page, start = $start ]
return fetch_solr($params)
}
Solr 請(qǐng)求中指定的 start 參數(shù)指示客戶(hù)端希望 Solr 用作當(dāng)前“頁(yè)面”開(kāi)頭的完整排序匹配列表中的絕對(duì) “偏移量”。
如果索引修改 (如添加或刪除文檔) 影響與查詢(xún)匹配的有序文檔的順序,則會(huì)在客戶(hù)端的兩個(gè)請(qǐng)求之間發(fā)生,從而導(dǎo)致后續(xù)頁(yè)的結(jié)果,那么這些修改可能會(huì)產(chǎn)生在多個(gè)頁(yè)上返回的同一文檔,或者當(dāng)結(jié)果集收縮或增大時(shí),文檔被 "跳過(guò)"。
例如,考慮一個(gè)包含 26 個(gè)文檔的索引,如下所示:
ID | 名稱(chēng) |
---|---|
1 |
A |
2 |
B |
... |
... |
26 |
Z |
后跟以下請(qǐng)求和索引修改交錯(cuò):
q=:&rows=5&start=0&sort=name asc
帶有
1-5 的 ID 的文檔將返回給客戶(hù)端q=:&rows=5&start=5&sort=name asc
文件 7-11 將被退回;
已跳過(guò)文檔 6,因?yàn)樗F(xiàn)在是所有匹配結(jié)果的排序集合中的第5個(gè)文檔 - 它將在 “page#1” 的新請(qǐng)求上返回。
q=:&rows=5&start=10&sort=name asc
文檔9、10和11已在 page #2 和 page #3 中返回,因?yàn)樗鼈円频搅伺判蚪Y(jié)果列表中的更遠(yuǎn)的后面。在典型的情況下,從索引更改對(duì)分頁(yè)搜索的影響不會(huì)顯著影響用戶(hù)體驗(yàn) - 因?yàn)樗鼈冊(cè)谙喈?dāng)靜態(tài)的集合中極少發(fā)生,或者是因?yàn)橛脩?hù)認(rèn)識(shí)到數(shù)據(jù)集合不斷發(fā)展并期望看到文檔在結(jié)果集中上下移動(dòng)。
在某些情況下,Solr 搜索的結(jié)果不適用于簡(jiǎn)單的分頁(yè)用戶(hù)界面。
當(dāng)您希望從 Solr 中獲取大量的排序結(jié)果,并將其輸入到外部系統(tǒng)中時(shí),為 startor rows 參數(shù)使用非常大的值可能是非常低效的。分頁(yè)使用 start 和 rows 不僅要求 Solr 計(jì)算(和排序)在內(nèi)存中應(yīng)為當(dāng)前頁(yè)面提取的所有匹配文檔,而且還需要在以前的頁(yè)面上出現(xiàn)的所有文檔。
雖然請(qǐng)求 start=0&rows=1000000 可能顯然是低效率的,因?yàn)樗?Solr 維護(hù)和排序一百萬(wàn)份文檔,同樣 start=999000&rows=1000,由于同樣的原因,請(qǐng)求同樣是低效的。Solr 無(wú)法計(jì)算出排序順序中的哪個(gè)匹配文檔是 999001 個(gè)結(jié)果,而無(wú)需先確定前 999000 個(gè)匹配排序結(jié)果是什么。
如果索引是分布式的(在 SolrCloud 模式下運(yùn)行時(shí)常見(jiàn)),則從每個(gè)分片中檢索一百萬(wàn)個(gè)文檔。對(duì)于十個(gè)分片索引,必須檢索和排序一千萬(wàn)個(gè)條目以找出與這些查詢(xún)參數(shù)匹配的 1000 個(gè)文檔。
作為增加 “start” 參數(shù)以請(qǐng)求后續(xù)頁(yè)的排序結(jié)果的替代方法,Solr 支持使用 “Cursor” 掃描結(jié)果。
Solr 中的 Cursor 是一個(gè)邏輯概念,不涉及在服務(wù)器上緩存任何狀態(tài)信息。而是使用返回給客戶(hù)端的最后一個(gè)文檔的排序值來(lái)計(jì)算表示排序值的有序空間中的邏輯點(diǎn)的“mark”。這個(gè)“mark”可以在隨后的請(qǐng)求參數(shù)中指定,告訴 Solr 在哪里繼續(xù)。
要在 Solr 中使用 Cursor ,請(qǐng)指定具有 \* 值的 cursorMark 參數(shù)。您可以把這 start=0 看作是告訴 Solr “在我的排序結(jié)果開(kāi)始處開(kāi)始”的一種方法,它也告訴 Solr 您想使用一個(gè) Cursor。
除了返回前 N 個(gè)排序結(jié)果(可以使用 rows 參數(shù)控制 N )之外,Solr 響應(yīng)還將包括一個(gè)名為 nextCursorMark 的編碼字符串。然后從響應(yīng)中取 nextCursorMark 字符串值,并將其作為cursorMark 參數(shù)傳遞回 Solr 作為下一個(gè)請(qǐng)求。您可以重復(fù)這個(gè)過(guò)程,直到您已經(jīng)獲取盡可能多的文檔,或者直到返回的 nextCursorMark 與已指定的 cursorMark 匹配為止,這表示沒(méi)有更多的結(jié)果。
在 Solr 請(qǐng)求中使用 cursorMark 參數(shù)時(shí)需要注意一些重要的約束條件:
游標(biāo)標(biāo)記值是根據(jù)結(jié)果中每個(gè)文檔的排序值計(jì)算出來(lái)的,這意味著如果多個(gè)具有相同排序值的文檔中的一個(gè)是結(jié)果頁(yè)面上的最后一個(gè)文檔,則會(huì)產(chǎn)生相同的 Cursor 標(biāo)記值。在這種情況下,使用 cursorMark 的后續(xù)請(qǐng)求將不知道具有相同標(biāo)記值的哪個(gè)文檔應(yīng)該被跳過(guò)。要求將 uniqueKey 字段作為排序標(biāo)準(zhǔn)中的一個(gè)子句使用,可以確保返回一個(gè)確定性排序,并且每個(gè) cursorMark值都將標(biāo)識(shí)文檔序列中的一個(gè)唯一點(diǎn)。
此處顯示的偽代碼顯示了使用 Cursor 獲取與查詢(xún)匹配的所有文檔時(shí)涉及的基本邏輯:
// when fetching all docs, you might as well use a simple id sort
// unless you really need the docs to come back in a specific order
$params = [ q => $some_query, sort => 'id asc', rows => $r, cursorMark => '*' ]
$done = false
while (not $done) {
$results = fetch_solr($params)
// do something with $results
if ($params[cursorMark] == $results[nextCursorMark]) {
$done = true
}
$params[cursorMark] = $results[nextCursorMark]
}
使用 SolrJ,這個(gè)偽代碼將是:
SolrQuery q = (new SolrQuery(some_query)).setRows(r).setSort(SortClause.asc("id"));
String cursorMark = CursorMarkParams.CURSOR_MARK_START;
boolean done = false;
while (! done) {
q.set(CursorMarkParams.CURSOR_MARK_PARAM, cursorMark);
QueryResponse rsp = solrServer.query(q);
String nextCursorMark = rsp.getNextCursorMark();
doCustomProcessingOfResults(rsp);
if (cursorMark.equals(nextCursorMark)) {
done = true;
}
cursorMark = nextCursorMark;
}
如果您想用 curl 手工完成,請(qǐng)求的順序看起來(lái)是這樣的:
$ curl '...&rows=10&sort=id+asc&cursorMark=*'
{
"response":{"numFound":32,"start":0,"docs":[
// ... 10 docs here ...
]},
"nextCursorMark":"AoEjR0JQ"}
$ curl '...&rows=10&sort=id+asc&cursorMark=AoEjR0JQ'
{
"response":{"numFound":32,"start":0,"docs":[
// ... 10 more docs here ...
]},
"nextCursorMark":"AoEpVkRCREIxQTE2"}
$ curl '...&rows=10&sort=id+asc&cursorMark=AoEpVkRCREIxQTE2'
{
"response":{"numFound":32,"start":0,"docs":[
// ... 10 more docs here ...
]},
"nextCursorMark":"AoEmbWF4dG9y"}
$ curl '...&rows=10&sort=id+asc&cursorMark=AoEmbWF4dG9y'
{
"response":{"numFound":32,"start":0,"docs":[
// ... 2 docs here because we've reached the end.
]},
"nextCursorMark":"AoEpdmlld3Nvbmlj"}
$ curl '...&rows=10&sort=id+asc&cursorMark=AoEpdmlld3Nvbmlj'
{
"response":{"numFound":32,"start":0,"docs":[
// no more docs here, and note that the nextCursorMark
// matches the cursorMark param we used
]},
"nextCursorMark":"AoEpdmlld3Nvbmlj"}
由于從 Solr 的角度來(lái)看,游標(biāo)是無(wú)狀態(tài)的,所以一旦您確定有足夠的信息,您的客戶(hù)端代碼就可以停止獲取額外的結(jié)果:
while (! done) {
q.set(CursorMarkParams.CURSOR_MARK_PARAM, cursorMark);
QueryResponse rsp = solrServer.query(q);
String nextCursorMark = rsp.getNextCursorMark();
boolean hadEnough = doCustomProcessingOfResults(rsp);
if (hadEnough || cursorMark.equals(nextCursorMark)) {
done = true;
}
cursorMark = nextCursorMark;
}
與基本分頁(yè)不同,Cursor 分頁(yè)不依賴(lài)于在完成的匹配文檔的排序列表中使用絕對(duì)“偏移量”。相反,請(qǐng)求中指定的 cursorMark 將根據(jù)該文檔的絕對(duì)排序值封裝返回的上一個(gè)文檔的相對(duì)位置信息。這意味著,與基本分頁(yè)相比,使用 Cursor 時(shí),索引修改的影響要小得多??紤]在討論基本分頁(yè)時(shí)所描述的相同示例索引:
ID | 名稱(chēng) |
---|---|
1 |
A |
2 |
B |
... |
... |
26 |
Z |
q=:&rows=5&start=0&sort=name asc, id asc&cursorMark=*
帶有 1-5 的 ID 的文檔將返回給客戶(hù)端
簡(jiǎn)而言之:當(dāng)獲取與使用 cursorMark 匹配的查詢(xún)的所有結(jié)果時(shí),索引修改的唯一方式可能導(dǎo)致被跳過(guò)的文檔或返回兩次,如果文檔的排序值發(fā)生更改。
確保文檔永遠(yuǎn)不會(huì)被返回的一種方法是將 uniqueKey 字段用作主要(因此是唯一有效的)排序標(biāo)準(zhǔn)。
在這種情況下,您將保證每個(gè)文檔只返回一次,無(wú)論它如何在使用 Cursor 時(shí)被修改。
由于 Cursor 請(qǐng)求是無(wú)狀態(tài)的,并且 cursorMark 值封裝了從搜索返回的上一個(gè)文檔的絕對(duì)排序值,所以可以“繼續(xù)”從已經(jīng)達(dá)到其結(jié)尾的 Cursor 獲取附加結(jié)果。如果添加新文檔(或更新現(xiàn)有文檔)到結(jié)果的末尾。
您可以把它看作類(lèi)似于在 Unix 中使用 “tail -f” 的東西。如何在索引中添加/更新文檔時(shí),如果有 "時(shí)間戳" 字段記錄,則最常見(jiàn)的示例是如何使用此方法。客戶(hù)端應(yīng)用程序可以使用匹配查詢(xún)的文檔的 sort=timestamp asc, id asc 連續(xù)輪詢(xún) Cursor,并且在添加或更新符合請(qǐng)求條件的文檔時(shí)總是會(huì)收到通知。
另一個(gè)常見(jiàn)的例子是,當(dāng)您創(chuàng)建新文檔時(shí) uniqueKey 值始終增加,并且您可以使用 sort=id asc 連續(xù)輪詢(xún)游標(biāo)以獲得有關(guān)新文檔的通知。
拖放 Cursor 的偽代碼只是我們?cè)缙谔幚砼c查詢(xún)匹配的所有文檔的一個(gè)小修改:
while (true) {
$doneForNow = false
while (not $doneForNow) {
$results = fetch_solr($params)
// do something with $results
if ($params[cursorMark] == $results[nextCursorMark]) {
$doneForNow = true
}
$params[cursorMark] = $results[nextCursorMark]
}
sleep($some_configured_delay)
}
對(duì)于某些特殊情況,/ export 處理程序可以是一個(gè)選擇。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號(hào)-3|閩公網(wǎng)安備35020302033924號(hào)
違法和不良信息舉報(bào)電話(huà):173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號(hào)
聯(lián)系方式:
更多建議: