目前在系統中涉及到搜索的解決方案都是通過數據庫自帶特性解決,如通過mysql5.7自帶的全文檢索功能實現資源的搜索。
這樣實現的好處是方便,無需額外開發和維護成本。但是隨着業務的發展和數據的增加,性能和可擴展方面很容易出現瓶頸。
結合本次方案庫需求的契機,決定引入spring boot + solr 爲主要架構基礎的前置平臺用於應對日漸旺盛的搜索服務需求。
解決方案
前置平臺基於spring boot開發,主要看中spring boot的微服務思想,方便開發和部署。同時可以爲以後的微服務架構做技術熱身。
通過spring boot搭建的前置平臺會處理請求接入,限流,監控,安全等非功能性需求。
搜索端結合了最新的apache solr服務器端(6.6版本),使用自帶的smart-cn中文分詞組件,提供搜索服務的基礎支持。
架構實現
架構實現部分主要通過具體實踐來驗證架構的可行性,不涉及具體業務細節和實際數據,
架構實現的操作描述力求做到可複製。
Spring boot項目創建
首先確保本機安裝了jdk8+,然後就進入eclipse,創建一個maven project。
POM文件包含以下內容:
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.0.BUILD-SNAPSHOT</version> </parent> <properties> <spring.data.solr.version>2.1.1.RELEASE</spring.data.solr.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-solr</artifactId> <version>${spring.data.solr.version}</version> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-solr</artifactId> </dependency> </dependencies>
Application.java 對應了項目的入口,通過一段很簡單的代碼就可以啓動一個jetty服務了。
package app; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import sample.Example; @SpringBootApplication public class Application { public static void main(String[] args) throws Exception { SpringApplication.run(Example.class, args); } }
運行示意圖:
Solr服務端安裝和配置
首先到官方網站下載最新的安裝包,當前版本是6.6,下載zip包就可以了。
解壓後的文件夾有以下幾個目錄是首先需要關注的:
bin: 啓動腳本目錄,通過這裏面的命令來啓動關閉服務器
server:solr服務器目錄,配置文件和jar包以及索引數據都是在這個目錄裏面的
contrib:這個裏面放的是隨版本發佈的一些可選包,我們後面用到的中文分詞包就在裏面
由於我們是驗證架構所以所有的配置都是基於單機的。
下一步要做的是創建一個core,在solr-6.6.0\server\solr目錄下新建一個文件夾sample_solr,包含如下文件夾
conf 配置文件目錄,初始版本從solr-6.6.0\example\example-DIH\solr\db\conf 複製
data 數據文件目錄,手工創建
core.properties 手工創建目錄,內容如下:
name=sample_solr
創建完成core後需要修改一下core裏面的配置文件,
在solr-6.6.0\server\solr\sample_solr\conf目錄下面,有三個文件,按照順序修改
solrconfig.xml
找到data import的request handler修改爲<requestHandler name="/dataimport" class="solr.DataImportHandler"> <lst name="defaults"> <str name="config">solr-data-config.xml</str> </lst> </requestHandler>
solr-data-config.xml
通過這個文件來配置你的data source和文檔字段,下面是我的配置
有幾個地方需要注意的,
batchSize
官方文檔建議針對mysql數據庫使用-1來迫使mysql使用Integer.MIN_VALUE作爲fetch size,實際操作中設置成-1時我使用mysql-connector-java-5.1.24(這個驅動如果沒有需要自行下載併發到solr-6.6.0\server\lib目錄)作爲驅動會出現result set close的錯誤,所以這邊設置成10,具體有沒有用還待驗證
後來在mysql的bug list找到了一個相似的問題,供參考
https://bugs.mysql.com/bug.php?id=83027
entity的PK
在deltaQuery的時候會用到,雖然deltaQuery的語句是通過update_time> xxx來獲取增量數據,實際最後查詢的時候還是通過pk in (ids) 這樣的方式,所以如果需要用到deltaQuery,PK需要設置成數據庫表的主鍵
field的定義
這邊可以看到我定義了一個tag字段,可是當你去搜索的時候,結果裏面死活只出現id和name。
最後發現文檔中的字段都是要通過定義的,只不過solr爲自己的sample預留了一些字段定義剛好包含id和name,這也算是個相當坑的地方。managed-schema
上面說倒field需要通過定義,那麼這個文件就是用來定義文檔中使用到的字段和字段類型了。Solr正是通過scheme定義來構建索引。
這個scheme文件包含四種元素:
field type
字段的類型如文本,數字浮點等,定義貼切的類型有利於solr更準確的識別字段並輸出結果field
字段,用於組成solr文檔的的基本單位。如果從面向對象角度來看,一個文檔是一個對象,相應的字段就是這個對象的屬性dynamicField
動態字段,solr除了提供一些默認字段之外還預留了一些通配符字段定義,如下:<dynamicField name="*_txt" type="text_general" multiValued="true" indexed="true" stored="true"/>
結合我們上面的tag字段,如果我們覺得每個字段都要定義太麻煩,那麼可以在entity裏面直接使用dynamic field,
<field column="tag" name="tag_txt" type="string"/>
copyField
從名字就可以看出來這是一個複製字段功能,從定義來看也很明顯的發現有source有dest。一個主要的用途就是全文檢索,將需要檢索的字段都copy到一個字段,之後對這個字段的搜索不就是全文檢索了嗎?這個和早期數據庫裏面將幾個字段組合起來後來個模糊查詢就是全文搜索是一個道理。
這個要注意的是如果source有幾個字段,那麼目標字段的multiValued需要設置爲true
更詳細的含義建議參考官方文檔,我們主要涉及field type,field和copyField的定義,這裏就基於我們的例子來看看。
field type在一般情況下不需要擴展,因爲solr已經自帶了很多的類型了,這裏我們爲了支持中文分詞,新增一個字段類型如下
<fieldType name="text_smart" class="solr.TextField" positionIncrementGap="100"> <analyzer type="index"> <tokenizer class="solr.HMMChineseTokenizerFactory"/> <filter class="solr.CJKWidthFilterFactory"/> <filter class="solr.StopFilterFactory" words="org/apache/lucene/analysis/cn/smart/stopwords.txt"/> <filter class="solr.PorterStemFilterFactory"/> <filter class="solr.LowerCaseFilterFactory"/> </analyzer> <analyzer type="query"> <tokenizer class="solr.HMMChineseTokenizerFactory"/> <filter class="solr.CJKWidthFilterFactory"/> <filter class="solr.StopFilterFactory" words="org/apache/lucene/analysis/cn/smart/stopwords.txt"/> <filter class="solr.PorterStemFilterFactory"/> <filter class="solr.LowerCaseFilterFactory"/> </analyzer> </fieldType>
可以看出一個field type由兩部分構成,index和query。Index負責建立索引的時候對於字段的解析而query負責查詢的時候。同時爲了中文分詞我們需要從solr-6.6.0\contrib\analysis-extras\lucene-libs中拷貝lucene-analyzers-smartcn-6.6.0.jar這個jar包到webapp\WEB-INF\lib
field的配置如下:
1 | <field name="tag" type="text_smart" indexed="true" stored="true"/><field name="text" type="text_smart" multiValued="true" indexed="true" stored="false"/> |
主要是對需要支持中文的字段type配置成我們之前定義的text_smart
copyField
<copyField source="name" dest="text"/><copyField source="tag" dest="text"/> |
通過上面的配置我們一個solr服務器端就可以正常運作了,我們回顧一下我們的過程
Spring solr client
基於我們前面兩個章節的內容,這時候要做的就是在spring boot項目裏面提供solr服務器的搜索接口封裝以及索引的維護。
首先我們需要創建solr的配置,通過兩個文件
1, application.properties
在src/main/resource目錄下面新建一個properties文件,內容如下
spring.data.solr.host=http://127.0.0.1:8983/solr/sample_solr |
2, SolrConfig.java
通過註解將properties中定義的屬性注入到bean中
package sample; import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties(prefix = "spring.data.solr") public class SolrConfig { private String host; private String zkHost; private String defaultCollection; //getter setter }
接下來就是通過一個controller來通過restful的方式將用戶接口和solr script連接起來。
下面是我們的一個查詢controller
package sample; //import section @RestController @EnableAutoConfiguration public class Example { @Autowired private SolrClient client; @RequestMapping("/query/name/{name}") public String queryByName(@PathVariable String name) throws IOException, SolrServerException { ModifiableSolrParams params =new ModifiableSolrParams(); params.add("q",name); params.add("hl","on"); params.add("hl.fl","name,tag"); params.add("ws","json"); params.add("start","0"); params.add("rows","10"); QueryResponse response=null; try{ response=client.query(params); SolrDocumentList results = response.getResults(); for (SolrDocument document:results) { System.out.println( document); } }catch(Exception e){ e.getStackTrace(); } return response.toString(); } }
啓動spring boot服務,在瀏覽器輸入我們的查詢指令後就可以得到結果了
http://localhost:8080/query/name/測試
其他關注點
通過架構實現部分的驗證,基於spring boot + solr的前置服務架構是可行的。但是作爲一個前置平臺來說,我們還需要關注哪些點呢?
安全性
前置平臺很多時候是暴露在通用防火牆之外的,所以危險係數往往也是很高的。我們主要從兩個方面來加強防範
限流
通過限流和適當的服務降級,保證服務可用性以及避免可能的流量***。具體實施可以參照線程池的思想,不具體展開。認證
通過認證體系結合加密算法保證信息傳輸的保密性和完整性
可靠性
可靠性是指系統的可用程度,涵蓋系統持續運行時間,宕機時間,服務回覆時間等因素。我們通過預防和監控兩個途徑來保證。
預防
通過部署集羣服務從前置,搜索服務角度去除單點監控
通過獨立的監控系統對前置系統和搜索服務器進行監控,設定合理的閾值和報警點