Solr 18 - 通過SolrJ區域性更新Solr中的文件 (原子操作、非覆蓋操作)

瘦風發表於2019-07-04

1 需求分析

(1) 需求:

向Solr中的文件新增新的欄位並賦值, 或者修改已有的欄位, 對不修改的要保持原值, 也就是不能進行完全覆蓋操作.

(2) 前提:

新增的欄位(field)要提前在schema.xml檔案中定義, 否則Solr無法處理這些欄位, 肯定會導致新增失敗.
關於schema.xml檔案的配置, 可參考: Solr的schema.xml模式檔案解讀 (Solr的模式設計與優化)

(3) 分析: 我們可以使用Solr提供的原子更新, 來實現相關需求:

Solr支援的原子更新:
set: 修改指定文件中該field的值, 如果這個field已經存在, 則更新, 如果不存在, 則追加到這個文件中 —— 可以是單值, 也可以是multi-valued;
add: 向指定文件中的field欄位新增值, 這個field必須是multi-valued型別的, 否則將出錯 —— 只能是multi-valued;
inc: 對指定文件中數值型別的值進行自增操作 —— 只能是數值型別, 包括int、long、float、double.

2 需求實現

2.1 pom.xml依賴

<!-- 專案較早, 使用的是4.10.4版本的Solr -->
<dependency>
    <groupId>org.apache.solr</groupId>
    <artifactId>solr-solrj</artifactId>
    <version>4.10.4</version>
</dependency>

2.2 Java程式碼示例

(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());

3 補充說明

3.1 關於文件中_version_的取值說明

(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就會拒絕修改; 值相同時才執行修改.

3.2 store=true/false的區別

(1) 如果某個欄位在schema.xml中指定了store=false, 那麼即使這個欄位有值, 在更新的時候也會被Solr丟棄, 而指定為store=true的欄位則不會;

(2) 對於multi-field(多值)欄位, 如果指定其store=false, 則在原子更新使用add的時候會級聯清除該欄位之前的資料.

參考資料

solr的原子更新/區域性更新

版權宣告

作者: 馬瘦風(https://healchow.com)

出處: 部落格園 馬瘦風的部落格(https://www.cnblogs.com/shoufeng)

感謝閱讀, 如果文章有幫助或啟發到你, 點個[好文要頂?] 或 [推薦?] 吧?

本文版權歸博主所有, 歡迎轉載, 但 [必須在文章頁面明顯位置標明原文連結], 否則博主保留追究相關人員法律責任的權利.

相關文章