本文來自于生産環境的一個需求: 需要向已有的文檔中補充更多的字段, 而不是采取完全覆寫的方式, 實作思路是: 通過SolrJ(Solr的Java API), 向Solr中已存在的文檔添加新的字段并指派, 或者修改已有的字段, 對不修改的要保持原值.
目錄
- 1 需求分析
- 2 需求實作
- 2.1 pom.xml依賴
- 2.2 Java代碼示例
- 3 補充說明
- 3.1 關于文檔中_version_的取值說明
- 3.2 store=true/false的差別
- 參考資料
- 版權聲明
(1) 需求:
向Solr中的文檔添加新的字段并指派, 或者修改已有的字段, 對不修改的要保持原值, 也就是不能進行完全覆寫操作.
(2) 前提:
添加的字段(field)要提前在schema.xml檔案中定義, 否則Solr無法處理這些字段, 肯定會導緻添加失敗.
關于schema.xml檔案的配置, 可參考: Solr的schema.xml模式檔案解讀 (Solr的模式設計與優化)
(3) 分析: 我們可以使用Solr提供的原子更新, 來實作相關需求:
Solr支援的原子更新:: 修改指定文檔中該field的值, 如果這個field已經存在, 則更新, 如果不存在, 則追加到這個文檔中 —— 可以是單值, 也可以是multi-valued;
set
: 向指定文檔中的field字段添加值, 這個field必須是multi-valued類型的, 否則将出錯 —— 隻能是multi-valued;
add
: 對指定文檔中數值類型的值進行自增操作 —— 隻能是數值類型, 包括int、long、float、double.
inc
<!-- 項目較早, 使用的是4.10.4版本的Solr -->
<dependency>
<groupId>org.apache.solr</groupId>
<artifactId>solr-solrj</artifactId>
<version>4.10.4</version>
</dependency>
(1) 先擷取Solr連接配接:
String zkHost= "ip:port,ip:port,ip:port";
// 擴大并發連接配接數
ModifiableSolrParams params = new ModifiableSolrParams();
params.set(HttpClientUtil.PROP_MAX_CONNECTIONS, 1000);
params.set(HttpClientUtil.PROP_MAX_CONNECTIONS_PER_HOST, 100);
HttpClient client = HttpClientUtil.createClient(params);
LBHttpSolrServer lbServer = new LBHttpSolrServer(client);
CloudSolrServer solrServer = new CloudSolrServer(zkHost, lbServer);
// 為 Solr 連接配接設定預設的 Collection
solrServer.setDefaultCollection("C_Book");
// 設定ZooKeeper連接配接逾時時間
solrServer.setZkClientTimeout(18000);
solrServer.setZkConnectTimeout(36000);
(2) 準備需要處理的Solr文檔, 相關注意事項已經在代碼注釋中作了詳細說明:
// 為了提高效率, 可以使用批量操作
Collection<SolrInputDocument> updateDocList = new ArrayList<>();
for (int i = 0; i < 5; ++i) {
SolrInputDocument doc = new SolrInputDocument();
// 局部更新需要指定文檔的id(在schema.xml中配置的主鍵),
// 主鍵不需要添加set、add等資訊, 其他需要原子更新的field需要構造為Map
doc.addField("id", i);
// 局部更新需要借助Map, 這個Map的Key必須是“set”
Map<String, String> publisherMap = new HashMap<>();
publisherMap.put("set", "人民郵電出版社");
// 修改圖書的出版社, key是field, value是上述的Map
doc.addField("publisher", publisherMap);
// 在已有倉庫的基礎上, 再添加多個倉庫, 注意: 此field必須是multi-valued類型
Map<String, List<String>> stockCityMap = new HashMap<>();
List<String> list = new ArrayList();
list.add("廣州");
list.add("深圳");
// 局部添加需要借助Map, 這個Map的Key必須是“add”
stockCityMap.put("add", list);
// 修改圖書的倉庫城市, key是field, value是上述的Map
doc.addField("stockCity", stockCityMap);
// 在已有圖書價格的基礎上: 每本增加9.50元, 注意: 此field必須是數值類型
Map<String, Long> priceMap = new HashMap<>();
// 局部自增需要借助Map, 這個Map的Key必須是“inc”
priceMap.put("inc", 9.50L);
// 修改圖書的價格, key是field, value是上述的Map
doc.addField("price", priceMap);
// _version_值為0: 如果待修改的文檔存在, 則修改; 如果不存在, 則添加
doc.addField("_version_", 0);
updateDocList.add(doc);
}
(3) 向SolrCloud中送出批量添加請求:
// 連接配接SolrCloud
solrServer.connect();
// 添加送出文檔List
UpdateResponse rsp = solrServer.add(updateDocList);
System.out.println("操作狀态: " + rsp.getStatus() + ", 操作時間:" + rsp.getQTime());
// 送出政策: 不用手動送出, 交由Solr服務根據配置自動進行軟送出;
// 如果要手動送出, 不要使用無參方法, 推薦指定送出政策: 是否等待重新整理(建議不等待: 會阻塞)、等待可搜尋(建議不等待: 會阻塞)、軟送出
UpdateResponse rspCommit = solrServer.commit(false, false, true);
System.out.println("送出狀态: " + " result:" + rspCommit.getStatus() + ", 操作時間: " + rspCommit.getQTime());
(1)
version < 0
: 如果待修改的文檔存在, Solr會拒絕修改; 如果不存在, 就添加這個文檔.
(2)
version = 0
: 如果待修改的文檔存在, 就更新這個文檔; 如果不存在, 就添加這個文檔.
(3)
version = 1
: 如果待修改待文檔存在, 就更新這個文檔; 如果不存在, Solr會拒絕修改它, 并抛出類似的錯誤資訊:
version conflict for 1 expected=1 actual=-1
(4)
version > 1
: 如果文檔的
_version_
值和傳入的
_version_
值不同, Solr就會拒絕修改; 值相同時才執行修改.
(1) 如果某個字段在schema.xml中指定了
store=false
, 那麼即使這個字段有值, 在更新的時候也會被Solr丢棄, 而指定為
store=true
的字段則不會;
(2) 對于multi-field(多值)字段, 如果指定其
store=false
, 則在原子更新使用
add
的時候會級聯清除該字段之前的資料.
solr的原子更新/局部更新
作者: 馬瘦風(https://healchow.com)
出處: 部落格園 馬瘦風的部落格(https://www.cnblogs.com/shoufeng)
感謝閱讀, 如果文章有幫助或啟發到你, 點個[好文要頂👆] 或 [推薦👍] 吧😜
本文版權歸部落客所有, 歡迎轉載, 但 [必須在文章頁面明顯位置标明原文連結], 否則部落客保留追究相關人員法律責任的權利.