搜索返回的多條結果中,包括自天然搜索結果和特型搜索結果。
天然搜索結果是針對用戶請求(query),返回互聯網上相關性較高的內容。是否知足用戶需求取決於互聯網內容及相關性算法。
特型搜索則是針對query,線下先對內容進行挖掘、整合(線下的方式有多種,其中知識圖譜 是一個方向),線上直接返回知足用戶需求的結果。好比當搜索"冰與火之歌"時,排在前面的百科、小說、影視等部分就屬於特型搜索結果。算法
除了和傳統搜索同樣有着嚴格的性能要求外,特型搜索服務還有兩個比較大的特色:
1. 開放:一次用戶請求的結果是由多個不一樣的服務來完成的,並且不一樣的服務每每由不一樣的小組負責研究。
2. 收斂:若是一次請求返回了多個特型結果,須要對結果進行聚合再返回。
因此特型搜索服務模型就是在葉子節點開放、非葉子結點收斂的一種樹形搜索結構:緩存
針對該服務模型,極端一點的作法可讓各個service自由發揮,而後經過rpc進行訪問就能夠了。但這樣作顯然是比較浪費和容易引發混亂的作法:
1. 接口標準:首先是接口問題,上游服務對下游服務進行進行聚合的時候,若是不能識別對方的返回結果,那"後來人"只能罵娘了。
2. 流量分發:下游服務雖然能夠獨立開發,但仍須要向上遊服務申請流量,也就是流量分發的功能,這實際上是能夠統一處理的。
3. 服務調用:若是service之間都是經過rpc調用,當調用層次比較深的時候,在對性能要求十分嚴格的環境下,是不可能知足要求的。
4. 組件開發:一些通用的組件,好比日誌、緩存、內存詞典等,若是讓每一個服務都去開發,是很是浪費的。
如下對"接口標準", "流量分發", "服務調用"展開說明一下。架構
接口標準是爲了下降服務調用和聚合的成本。
服務的接口是比較簡單的,用protobuf service的描述相似:函數
service ServiceAPI { rpc query(message.Request) returns (message.Response); };
其中Request表示用戶的一次請求信息,對各服務都是同樣的。
Response稍微複雜一些,由於每一個service的結果是不同的,必須提供擴展機制。固然用protobuf是比較容易作到的:性能
message Response { //common fields: ... ... repeated Result = 10; }; message Result { //common fields: ... ... extensions 128 to 512; };
固然也能夠不用extensions, 而是在Result裏面加入自定義Message,這樣能夠在Result裏面看到全部的message(若是某些因素(好比歷史遺留問題)要求你這麼作的話),咱們採用的就是後者。spa
流量分發是將用戶請求發送到服務的過程。要解決幾個問題:架構設計
若是是轉發,怎麼知道轉發給誰?是全量轉發仍是部分轉發?
轉發實際上是AggregatedService的職責,咱們的轉發機制也是集成在這個基類裏面的。主要提供了"服務註冊"和"流量分發"功能。
服務註冊能夠理解爲是"依賴倒置"pattern的一種應用,上游服務提供註冊接口,由下游服務發起註冊。這樣可使新開發服務對已有服務的影響最小。你可能會問"不是要在上游服務作聚合嗎"? 實際狀況是不少服務實際上是相對比較獨立的,聚合的狀況其實不會太多,也就是說一次請求不少狀況只有一個AtomicService完成響應就夠了。
流量分發也有不一樣的作法,一種作法是全量轉發,由下游服務本身決定是否/如何處理。這樣對下游服務來講是最好的,可是缺點是須要服務節點能承受全流量的壓力。 另外一種作法是在入口處先對query作必定分析,而後將服務id放入請求中,做爲後續轉發的一個依據。這種作法的缺陷是全部服務須要在一個地方完成query解析,也有單點帶來的維護等問題。綜合比較咱們採用的是後者。
註冊語意相似:設計
[@service]
name : any-service
address : 100.100.100.100:1234
accept_ids : [1, 2, 10 - 20]日誌
服務調用要解決的是服務加載和服務發現的問題。若是每一個服務都是單獨的進程,能夠直接加載到rpc server中,向上註冊的時候同時聲明本身的server地址及端口就能夠了,現成的rpc實現有grpc。但總有一些緣由(好比性能要求、或者由於部署機制跟不上),要求service能在一個進程裏面跑,同一個進程的service能夠經過函數call而不是rpc調用,那將service放入一個容器(container)中,是一個不錯的選擇。
在咱們的實現中,service是加載進一個container的,請求經過container進行轉發。當向上遊service註冊時,須要聲明本身在哪一個container(這樣作的一個問題是當service切換container時,須要修改上游。更好的作法能夠考慮註冊時只聲明服務名,service和container的關係經過ZK之類的東西進行管理。咱們沒這樣作是由於目前爲止service和container的關係是穩定的)。這樣container就能夠發現待調用service是在container內仍是外,若是是內部則經過函數call調用,外部則用rpc。
並非任何service均可以加載到相同的container中,這對service的隔離是一個很大的限制,實際使用中咱們會綜合考慮服務的穩定性、開發、維護等因素。code
在線服務每每會對流量進行抽樣而後進行小流量實驗,每個實驗其實就是一個不一樣的service,流量分發時除了service id,可能還會考慮到抽樣id,原理大體差很少。 一些通用組件的開發這裏先不作描述了。