目前在系統(tǒng)中涉及到搜索的解決方案都是通過數(shù)據(jù)庫自帶特性解決,如通過MySQL5.7自帶的全文檢索功能實(shí)現(xiàn)資源的搜索。
這樣實(shí)現(xiàn)的好處是方便,無需額外開發(fā)和維護(hù)成本。但是隨著業(yè)務(wù)的發(fā)展和數(shù)據(jù)的增加,性能和可擴(kuò)展方面很容易出現(xiàn)瓶頸。
站在用戶的角度思考問題,與客戶深入溝通,找到東西湖網(wǎng)站設(shè)計(jì)與東西湖網(wǎng)站推廣的解決方案,憑借多年的經(jīng)驗(yàn),讓設(shè)計(jì)與互聯(lián)網(wǎng)技術(shù)結(jié)合,創(chuàng)造個(gè)性化、用戶體驗(yàn)好的作品,建站類型包括:網(wǎng)站設(shè)計(jì)制作、做網(wǎng)站、企業(yè)官網(wǎng)、英文網(wǎng)站、手機(jī)端網(wǎng)站、網(wǎng)站推廣、主機(jī)域名、網(wǎng)絡(luò)空間、企業(yè)郵箱。業(yè)務(wù)覆蓋東西湖地區(qū)。
結(jié)合本次方案庫需求的契機(jī),決定引入spring boot + solr 為主要架構(gòu)基礎(chǔ)的前置平臺(tái)用于應(yīng)對(duì)日漸旺盛的搜索服務(wù)需求。
前置平臺(tái)基于spring boot開發(fā),主要看中spring boot的微服務(wù)思想,方便開發(fā)和部署。同時(shí)可以為以后的微服務(wù)架構(gòu)做技術(shù)熱身。
通過spring boot搭建的前置平臺(tái)會(huì)處理請(qǐng)求接入,限流,監(jiān)控,安全等非功能性需求。
搜索端結(jié)合了最新的apache solr服務(wù)器端(6.6版本),使用自帶的smart-cn中文分詞組件,提供搜索服務(wù)的基礎(chǔ)支持。
架構(gòu)實(shí)現(xiàn)部分主要通過具體實(shí)踐來驗(yàn)證架構(gòu)的可行性,不涉及具體業(yè)務(wù)細(xì)節(jié)和實(shí)際數(shù)據(jù),
架構(gòu)實(shí)現(xiàn)的操作描述力求做到可復(fù)制。
首先確保本機(jī)安裝了jdk8+,然后就進(jìn)入eclipse,創(chuàng)建一個(gè)maven project。
POM文件包含以下內(nèi)容:
org.springframework.boot spring-boot-starter-parent 2.0.0.BUILD-SNAPSHOT 2.1.1.RELEASE org.springframework.data spring-data-solr ${spring.data.solr.version} org.springframework.boot spring-boot-starter-web org.springframework.data spring-data-solr
Maven結(jié)構(gòu)搭好后就是搭建你的項(xiàng)目架構(gòu),如圖:
Application.java 對(duì)應(yīng)了項(xiàng)目的入口,通過一段很簡單的代碼就可以啟動(dòng)一個(gè)jetty服務(wù)了。
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); } }
運(yùn)行示意圖:
首先到官方網(wǎng)站下載最新的安裝包,當(dāng)前版本是6.6,下載zip包就可以了。
解壓后的文件夾有以下幾個(gè)目錄是首先需要關(guān)注的:
bin: 啟動(dòng)腳本目錄,通過這里面的命令來啟動(dòng)關(guān)閉服務(wù)器
server:solr服務(wù)器目錄,配置文件和jar包以及索引數(shù)據(jù)都是在這個(gè)目錄里面的
contrib:這個(gè)里面放的是隨版本發(fā)布的一些可選包,我們后面用到的中文分詞包就在里面
由于我們是驗(yàn)證架構(gòu)所以所有的配置都是基于單機(jī)的。
下一步要做的是創(chuàng)建一個(gè)core,在solr-6.6.0\server\solr目錄下新建一個(gè)文件夾sample_solr,包含如下文件夾
conf 配置文件目錄,初始版本從solr-6.6.0\example\example-DIH\solr\db\conf 復(fù)制
data 數(shù)據(jù)文件目錄,手工創(chuàng)建
core.properties 手工創(chuàng)建目錄,內(nèi)容如下:
name=sample_solr |
創(chuàng)建完成core后需要修改一下core里面的配置文件,
在solr-6.6.0\server\solr\sample_solr\conf目錄下面,有三個(gè)文件,按照順序修改
solrconfig.xml
找到data import的request handler修改為
solr-data-config.xml
solr-data-config.xml
通過這個(gè)文件來配置你的data source和文檔字段,下面是我的配置
有幾個(gè)地方需要注意的,
batchSize
官方文檔建議針對(duì)mysql數(shù)據(jù)庫使用-1來迫使mysql使用Integer.MIN_VALUE作為fetch size,實(shí)際操作中設(shè)置成-1時(shí)我使用mysql-connector-java-5.1.24(這個(gè)驅(qū)動(dòng)如果沒有需要自行下載并發(fā)到solr-6.6.0\server\lib目錄)作為驅(qū)動(dòng)會(huì)出現(xiàn)result set close的錯(cuò)誤,所以這邊設(shè)置成10,具體有沒有用還待驗(yàn)證
后來在mysql的bug list找到了一個(gè)相似的問題,供參考
https://bugs.mysql.com/bug.php?id=83027
entity的PK
在deltaQuery的時(shí)候會(huì)用到,雖然deltaQuery的語句是通過update_time> xxx來獲取增量數(shù)據(jù),實(shí)際最后查詢的時(shí)候還是通過pk in (ids) 這樣的方式,所以如果需要用到deltaQuery,PK需要設(shè)置成數(shù)據(jù)庫表的主鍵
field的定義
這邊可以看到我定義了一個(gè)tag字段,可是當(dāng)你去搜索的時(shí)候,結(jié)果里面死活只出現(xiàn)id和name。
最后發(fā)現(xiàn)文檔中的字段都是要通過定義的,只不過solr為自己的sample預(yù)留了一些字段定義剛好包含id和name,這也算是個(gè)相當(dāng)坑的地方。
managed-schema
上面說倒field需要通過定義,那么這個(gè)文件就是用來定義文檔中使用到的字段和字段類型了。Solr正是通過scheme定義來構(gòu)建索引。
這個(gè)scheme文件包含四種元素:
field type
字段的類型如文本,數(shù)字浮點(diǎn)等,定義貼切的類型有利于solr更準(zhǔn)確的識(shí)別字段并輸出結(jié)果
field
字段,用于組成solr文檔的的基本單位。如果從面向?qū)ο蠼嵌葋砜?,一個(gè)文檔是一個(gè)對(duì)象,相應(yīng)的字段就是這個(gè)對(duì)象的屬性
dynamicField
動(dòng)態(tài)字段,solr除了提供一些默認(rèn)字段之外還預(yù)留了一些通配符字段定義,如下:
結(jié)合我們上面的tag字段,如果我們覺得每個(gè)字段都要定義太麻煩,那么可以在entity里面直接使用dynamic field,
重新建立索引后,我們的搜索結(jié)果如圖:
copyField
從名字就可以看出來這是一個(gè)復(fù)制字段功能,從定義來看也很明顯的發(fā)現(xiàn)有source有dest。一個(gè)主要的用途就是全文檢索,將需要檢索的字段都copy到一個(gè)字段,之后對(duì)這個(gè)字段的搜索不就是全文檢索了嗎?這個(gè)和早期數(shù)據(jù)庫里面將幾個(gè)字段組合起來后來個(gè)模糊查詢就是全文搜索是一個(gè)道理。
這個(gè)要注意的是如果source有幾個(gè)字段,那么目標(biāo)字段的multiValued需要設(shè)置為true
更詳細(xì)的含義建議參考官方文檔,我們主要涉及field type,field和copyField的定義,這里就基于我們的例子來看看。
field type在一般情況下不需要擴(kuò)展,因?yàn)閟olr已經(jīng)自帶了很多的類型了,這里我們?yōu)榱酥С种形姆衷~,新增一個(gè)字段類型如下
可以看出一個(gè)field type由兩部分構(gòu)成,index和query。Index負(fù)責(zé)建立索引的時(shí)候?qū)τ谧侄蔚慕馕龆鴔uery負(fù)責(zé)查詢的時(shí)候。同時(shí)為了中文分詞我們需要從solr-6.6.0\contrib\analysis-extras\lucene-libs中拷貝lucene-analyzers-smartcn-6.6.0.jar這個(gè)jar包到webapp\WEB-INF\lib
field的配置如下:
1 |
主要是對(duì)需要支持中文的字段type配置成我們之前定義的text_smart
copyField
通過上面的配置我們一個(gè)solr服務(wù)器端就可以正常運(yùn)作了,我們回顧一下我們的過程
最后我們進(jìn)入命令行,通過solr.cmd start來啟動(dòng)
基于我們前面兩個(gè)章節(jié)的內(nèi)容,這時(shí)候要做的就是在spring boot項(xiàng)目里面提供solr服務(wù)器的搜索接口封裝以及索引的維護(hù)。
首先我們需要?jiǎng)?chuàng)建solr的配置,通過兩個(gè)文件
1, application.properties
在src/main/resource目錄下面新建一個(gè)properties文件,內(nèi)容如下
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 }
接下來就是通過一個(gè)controller來通過restful的方式將用戶接口和solr script連接起來。
下面是我們的一個(gè)查詢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(); } }
啟動(dòng)spring boot服務(wù),在瀏覽器輸入我們的查詢指令后就可以得到結(jié)果了
http://localhost:8080/query/name/測試
通過架構(gòu)實(shí)現(xiàn)部分的驗(yàn)證,基于spring boot + solr的前置服務(wù)架構(gòu)是可行的。但是作為一個(gè)前置平臺(tái)來說,我們還需要關(guān)注哪些點(diǎn)呢?
安全性
前置平臺(tái)很多時(shí)候是暴露在通用防火墻之外的,所以危險(xiǎn)系數(shù)往往也是很高的。我們主要從兩個(gè)方面來加強(qiáng)防范
限流
通過限流和適當(dāng)?shù)姆?wù)降級(jí),保證服務(wù)可用性以及避免可能的流量***。具體實(shí)施可以參照線程池的思想,不具體展開。
認(rèn)證
通過認(rèn)證體系結(jié)合加密算法保證信息傳輸?shù)谋C苄院屯暾?/p>
可靠性
可靠性是指系統(tǒng)的可用程度,涵蓋系統(tǒng)持續(xù)運(yùn)行時(shí)間,宕機(jī)時(shí)間,服務(wù)回復(fù)時(shí)間等因素。我們通過預(yù)防和監(jiān)控兩個(gè)途徑來保證。
預(yù)防
通過部署集群服務(wù)從前置,搜索服務(wù)角度去除單點(diǎn)
監(jiān)控
通過獨(dú)立的監(jiān)控系統(tǒng)對(duì)前置系統(tǒng)和搜索服務(wù)器進(jìn)行監(jiān)控,設(shè)定合理的閾值和報(bào)警點(diǎn)