<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      某電商平臺開發記要——全文檢索

      開發Web應用時,你經常要加上搜索功能。甚至還不知道要搜什么,就在草圖上畫了一個放大鏡。

      說到目前計算機的文字搜索在應用上的實現,象形文字天生就比拼音字母劣勢的多,分詞、詞性判斷、拼音文字轉換啥的,容易讓人香菇。

      首先我們來了解下什么是Inverted index,翻譯過來的名字有很多,比如反轉索引、倒排索引什么的,讓人不明所以,可以理解為:一個未經處理的數據庫中,一般是以文檔ID作為索引,以文檔內容作為記錄。而Inverted index 指的是將單詞或記錄作為索引,將文檔ID作為記錄,這樣便可以方便地通過單詞或記錄查找到其所在的文檔。并不是什么高深概念。

      oracle里常用的位圖索引(Bitmap index)也可認為是Inverted index。位圖索引對于相異基數低的數據最為合適,即記錄多,但取值較少。比如一個100W行的表有一個字段會頻繁地被當做查詢條件,我們會想到在這一列上面建立一個索引,但是這一列只可能取3個值。那么如果建立一個B*樹索引(普通索引)是不合適的,因為無論查找哪一個值,都可能會查出很多數據,這時就可以考慮使用位圖索引。位圖索引相對于傳統的B*樹索引,在葉子節點上采用了完全不同的結構組織方式。傳統B*樹索引將每一行記錄保存為一個葉子節點,上面記錄對應的索引列取值和行rowid信息。而位圖索引將每個可能的索引取值組織為一個葉子節點。每個位圖索引的葉子節點上,記錄著該索引鍵值的起始截止rowid和一個位圖向量串。如果不考慮起止rowid,那么就是取值有幾個,就有幾個索引,比如上例,雖說有100W條記錄,但是針對只有3個可取值的字段來說,索引節點只有3個,類似于下圖:

      需要注意的是,由于所有索引字段同值行共享一個索引節點,位圖索引不適用于頻繁增刪改的字段,否則可能會導致針對該字段(其它行)的增刪改阻塞(對其它非索引字段的操作無影響),是一種索引段級鎖。具體請參看 深入解析B-Tree索引與Bitmap位圖索引的鎖代價

      下面說說筆者知道的一些全文搜索的工具。

      文中綠色文字表示筆者并不確定描述是否正確,紅色表示筆者疑問,若有知道的同學請不吝賜教,多謝!


      ICTCLAS分詞系統

      本來想借著ICTCLAS簡單介紹下中文分詞的一些原理和算法,不過網上已有比較好的文章了,可參看 ICTCLAS分詞系統研究。中文分詞基本上是基于詞典,[可能]涉及到的知識 —— HMM(隱馬爾科夫鏈)、動態規劃、TF-IDF、凸優化,更基礎的就是信息論、概率論、矩陣等等,我們在讀書的時候可能并不知道所學何用,想較快重溫的同學可閱讀吳軍博士的《數學之美》。這些概念我會擇要在后續博文中介紹。下面我們就來看看分詞系統在數據庫中的具體應用。


      Postgresql的中文分詞

      在PostgreSQL中,GIN索引就是Inverted index,GIN索引存儲一系列(key, posting list)對, 這里的posting list是一組出現鍵的行ID。 每一個被索引的項目都可能包含多個鍵,因此同一個行ID可能會出現在多個posting list中。 每個鍵值只被存儲一次,因此在相同的鍵出現在很多項目的情況下,GIN索引是非常緊湊的(來自PostgreSQL 9.4.4 中文手冊)。顯然,將之應用到數組類型的字段上是非常合適的。全文檢索類型(tsvector)同樣支持GIN索引,可以加速查詢。聽說9.6版本出了一個什么RUM索引,對比GIN,檢索效率得到了很大的提升,可參看 PostgreSQL 全文檢索加速 快到沒有朋友 - RUM索引接口(潘多拉魔盒)

      幸運的是,阿里云RDS PgSQL已支持zhparser(基于SCWS)中文分詞插件。

      連接要分詞的數據庫,執行以下語句:

      -- 安裝擴展
      create extension zhparser;
      -- 查看該數據庫的所有擴展
      select * from pg_ts_parser; 
      -- 支持的token類型,即詞性,比如形容詞名詞啥的
      select ts_token_type('zhparser'); 
      -- 創建使用zhparser作為解析器的全文搜索的配置 
      CREATE TEXT SEARCH CONFIGURATION testzhcfg (PARSER = zhparser); 
      -- 往全文搜索配置中增加token映射,上面的token映射只映射了名詞(n),動詞(v),形容詞(a),成語(i),嘆詞(e)和習慣用語(l)6種,這6種以外的token全部被屏蔽。
      -- 詞典使用的是內置的simple詞典,即僅做小寫轉換。
      ALTER TEXT SEARCH CONFIGURATION testzhcfg ADD MAPPING FOR n,v,a,i,e,l WITH simple; 
      set zhparser.punctuation_ignore = t; -- 忽略標點符號

      現在我們就可以方便的進行中文分詞了,比如“select to_tsvector('testzhcfg','南京市長江大橋');”,會拆分為“'南京市':1 '長江大橋':2”。如果要分的更細粒度,那么可以設置復合分詞,復合分詞的級別:1~15,按位異或的 1|2|4|8 依次表示 短詞|二元|主要字|全部字,缺省不復合分詞,這是SCWS的配置選項,對應的zhparser選項為zhparser.multi_short、zhparser.multi_duality、zhparser.multi_zmain、zhparser.multi_zall。比如我們要設置短詞復合分詞,那么就set zhparser.multi_short=on;那么“select to_tsvector('testzhcfg','南京市長江大橋');”得到的分詞結果將是“'南京':2 '南京市':1 '大橋':5 '長江':4 '長江大橋':3”,這樣就可以匹配到更多的關鍵詞,當然檢索效率會變慢。

      短詞復合分詞是根據詞典來的,比如詞典中有'一次性'、'一次性使用'、’'一次性使用吸痰管'、'使用'、'吸痰管'5個詞語,當multi_short=off時,select to_tsvector('testzhcfg','"一次性使用吸痰管"');返回最大匹配的"一次性使用吸痰管",而為on時,返回的是"'一次性':2 '一次性使用吸痰管':1 '使用':3 '吸痰管':4",讓人困惑的是,結果里沒有提取出'一次性使用'這個詞,不知怎么回事。

      在產品表上建一列tsv存儲產品名稱的tsvector值,并對該列建GIN索引。

      CREATE OR REPLACE FUNCTION func_get_relatedkeywords(keyword text)
        RETURNS SETOF text[] AS
      $BODY$
      begin
          if (char_length(keyword)>0) then 
              RETURN QUERY select string_to_array(tsv::text,' ') from "Merchandises" where tsv @@ plainto_tsquery('testzhcfg',keyword);
          end if;    
      end
      $BODY$
        LANGUAGE plpgsql VOLATILE

      注意plainto_tsquery和to_tsquery稍微有點區別,比如前者不認識':*',而后者遇到空格會報錯。

      這會返回所有包含傳入關鍵詞的tsvector格式的字符串,所以我們要在業務層分解去重再傳遞給前端。

       1 public async Task<ActionResult> GetRelatedKeywords(string keyword)
       2 {
       3     var keywords = await MerchandiseContext.GetRelatedKeywords(keyword);
       4     if(keywords != null && keywords.Count>0)
       5     {
       6         //將所有產品的關鍵詞匯總去重
       7         var relatedKeywords = new List<string>();
       8         foreach(var k in keywords)
       9         {
      10             for(int i=0;i<k.Count();i++) //pg返回的是帶冒號的tsvector格式
      11             {
      12                 k[i] = k[i].Split(':')[0].Trim('\'');
      13             }
      14             relatedKeywords.AddRange(k);//k可以作為整體,比如多個詞語作為一個組合加入返回結果,更科學(這里是拆分后獨立加入返回結果)
      15         }
      16         //根據出現重復次數排序(基于重復次數多,說明關聯性高的預設)
      17         relatedKeywords = relatedKeywords.GroupBy(rk => rk).OrderByDescending(g => g.Count()).Select(g => g.Key).Distinct().ToList();
      18         relatedKeywords.RemoveAll(rk=>keyword.Contains(rk));
      19         return this.Json(new OPResult<IEnumerable<string>> { IsSucceed = true, Data = relatedKeywords.Take(10) }, JsonRequestBehavior.AllowGet);
      20     }
      21     return this.Json(new OPResult { IsSucceed = true }, JsonRequestBehavior.AllowGet);
      22 }

      now,我們就初步實現了類似各大電商的搜索欄關鍵詞聯想功能:

      然而,尚有一些值得考慮的細節。當數據庫中產品表越來越大,毫無疑問查詢時間會變長,雖然我們只需要前面10個關聯詞,但可能有重復詞,所以并不能簡單的在sql語句后面加limit 10。暫時縮小不了查詢范圍,可以減少相同關鍵詞的數據庫查詢頻率,即在上層加入緩存。key是關鍵詞或關鍵詞組合,value是關聯關鍵詞,關鍵詞多的話,加上各種組合那么數據量肯定很大,所以我們緩存時間要根據數據量和用戶搜索量定個合適時間。以redis為例:

       1 public static async Task SetRelatedKeywords(string keyword, IEnumerable<string> relatedKeywords)
       2 {
       3     var key = string.Format(RedisKeyTemplates.MERCHANDISERELATEDKEYWORDS, keyword);
       4     IDatabase db = RedisGlobal.MANAGER.GetDatabase();
       5     var count = await db.SetAddAsync(key, relatedKeywords.Select<string, RedisValue>(kw => kw).ToArray());
       6     if (count > 0)
       7         db.KeyExpire(key, TimeSpan.FromHours(14), CommandFlags.FireAndForget); //緩存
       8 }
       9 
      10 public static async Task<List<string>> GetRelatedKeywords(string keyword)
      11 {
      12     IDatabase db = RedisGlobal.MANAGER.GetDatabase();
      13     var keywords = await db.SetMembersAsync(string.Format(RedisKeyTemplates.MERCHANDISERELATEDKEYWORDS, keyword));
      14     return keywords.Select(kw => kw.ToString()).ToList();
      15 }

      當用戶在搜索欄里輸入的并非完整的關鍵詞——輸入的文字并未精確匹配到數據庫里的任一tsvector——比如就輸入一個“交”或者“鎖型”之類,并沒有提供用戶預期的自動補完功能(雖然自動補完和關鍵詞聯想本質上是兩個不同的功能,不過用戶可能并不這么想)。我們知道,在關鍵詞后加':*',比如“交:*”,那么是可以匹配到的,如:select '交鎖型:2 交鎖型股骨重建釘主釘:1 股骨:3 重建:4'::tsvector @@ to_tsquery('交:*'),返回的就是true。然而我們總不能讓用戶輸入的時候帶上:*,在代碼里給自動附加:*是一種解決方法(select to_tsquery('testzhcfg','股骨重建:*'),結果是"'股骨':* & '重建':*"),然而會帶來可能的效率問題,比如select to_tsquery('testzhcfg','一次性使用吸痰管:*'),它會拆分為"'一次性使用吸痰管':* & '一次性':* & '使用':* & '吸痰管':*",并且出于空格的考慮,我們用的是plainto_tsquery,而它是不認識:*的。

      當用戶輸入一些字符的時候,如何判斷是已完成的關鍵詞(進行關鍵詞聯想)還是未輸完的關鍵詞(自動補完),這是個問題。我們可以將用戶常搜的一些關鍵詞緩存起來(或者定期從tsv字段獲取),當用戶輸入匹配到多個(>1)緩存關鍵詞時,說明關鍵詞還未輸完整,返回關鍵詞列表供用戶選擇,否則(匹配數量<=1)時,則去查詢關聯關鍵詞。同樣用redis(很幸運,redis2.8版本后支持set集合的值正則匹配):

      /// <summary>
      /// 獲取關鍵詞(模糊匹配)
      /// </summary>
      public static List<string> GetKeywords(string keyword, int takeSize = 10)
      {
          IDatabase db = RedisGlobal.MANAGER.GetDatabase();
          //這里的pageSize表示單次遍歷數量,而不是說最終返回數量
          var result = db.SetScan(RedisKeyTemplates.SearchKeyword, keyword + "*", pageSize: Int32.MaxValue);
          return result.Take(takeSize).Select<RedisValue, string>(r => r).ToList();
      }

      當然,也有可能用戶輸入已經匹配到一個完整關鍵詞,但同時該關鍵詞是另外一些關鍵詞的一部分。我們可以先去緩存里面取關鍵詞,若數量少于10個(頁面上提示至多10個),那么就再去看是否有關聯關鍵詞補充。

      大部分網站搜索還支持拼音搜索,即按全拼或拼音首字母搜索。

      對關鍵詞[組合]賦予權重,權重計算可以依據搜索量、搜索結果等,每次返回給用戶最有效的前幾條。這以后再說吧。

      總的來說,數據庫自帶的全文檢索還是建立在字段檢索的基礎上,適合傳統SQL查詢場景,而且圍繞分詞系統的查詢方案和邏輯大部分需要自己處理,涉及到稍復雜的應用就力不從心,或者效率低下了(比如上述的自動補完功能),另外分布部署的時候也要在上層另做集群架構。


      Elasticsearch

      基于5.4版本

      節點:一個運行中的 Elasticsearch 實例稱為一個 節點。

      集群是由一個或者多個擁有相同 cluster.name 配置的節點組成, 它們共同承擔數據和負載的壓力。當有節點加入集群中或者從集群中移除節點時,集群將會重新平均分布所有的數據。一個集群只能有一個主節點。

      索引:作為名詞時,類似于傳統關系型數據庫中的一個數據庫。索引實際上是指向一個或者多個物理 分片邏輯命名空間 。一個索引應該是(非強制)因共同的特性被分組到一起的文檔集合, 例如,你可能存儲所有的產品在索引 products 中,而存儲所有銷售的交易到索引 sales 中。

      分片:一個分片是一個 Lucene 的實例(亦即一個 Lucene 索引 ),它僅保存了全部數據中的一部分。索引內任意一個文檔都歸屬于一個主分片,所以主分片的數目決定著索引能夠保存的最大數據量;副本分片作為硬件故障時保護數據不丟失的冗余備份,并為搜索和返回文檔等讀操作提供服務。

      類型:由類型名和mapping組成,mapping類似于數據表的schema,或者說類[以及字段的具體]定義。

      技術上講,多個類型可以在相同的索引中存在,只要它們的字段不沖突,即同名字段類型必須相同。但是,如果兩個類型的字段集是互不相同的,這就意味著索引中將有一半的數據是空的(字段將是 稀疏的 ),最終將導致性能問題。——導致這一限制的根本原因,是Lucene沒有文檔類型的概念,一個Lucene索引(ES里的分片)以扁平的模式定義其中所有字段,即假如該分片里有兩個類型A\B,A中定義了a\c兩個字符串類型的字段,B定義了b\c兩個字符串類型的字段,那么Lucene創建的映射包括的是a\b\c三個字符串類型的字段,如果A\B中c字段類型不一樣,那么配置這個映射時,將會出現異常。由此亦知,一個分片可包含不同類型的文檔。

      文檔:一個對象被序列化成為 JSON,它被稱為一個 JSON 文檔,指定了唯一 ID 。

      假如文檔中新增了一個未事先定義的字段,或者給字段傳遞了非定義類型的值,那么就涉及到動態映射的概念了。另外,盡管可以增加新的類型到索引中,或者增加新的字段到類型中,但是不能添加新的分析器或者對現有的字段做改動,遇到這種情況,我們可能需要針對此類文檔重建索引。

      在 Elasticsearch 中, 每個字段的所有數據 都是 默認被索引的 。 即每個字段都有為了快速檢索設置的專用倒排索引。

      樂觀并發控制,Elasticsearch 使用 version 版本號控制、處理沖突。

      Lucene中的[倒排]索引(在Lucene索引中表現為 段 的概念,Lucene索引除表示所有 的集合外,還有一個 提交點 的概念 ),[一旦創建]是不可變的,這有諸多好處:

      • 不需要鎖;
      • 重用索引緩存[,而非每次去磁盤獲取索引](即緩存不會失效,因為索引不變),進一步可以重用相同查詢[構建過程和返回的數據],而不需要每次都重新查詢;
      • 允許[索引被]壓縮;

      但是 數據/文檔 變化后,畢竟還是得更新 索引/段 的,那么怎么更新呢?—— 新的文檔和段會被創建,而舊的文檔和段被標記為刪除狀態,查詢時,后者會被拋棄。

      安裝Elasticsearch前需要安裝JRE(Java運行時,注意和JDK的區別),然后去到https://www.elastic.co/start里,根據提示步驟安裝運行即可。(筆者為windows環境)

      安裝完之后我們就可以在通過http://localhost:5601打開kibana的工作臺。為了讓遠程機子可以訪問,在啟動kibana之前要先設置kibana.yml中的server.host,改為安裝了kibana的機器的IP地址,即server.host: "192.168.0.119",注意中間冒號和引號之間要有空格,否則無效,筆者被此處坑成狗,也是醉了。同理,要elasticsearch遠程可訪問,需要設置elasticsearch.yml中的network.host。

      單機上啟動多個節點,文檔中說 “你可以在同一個目錄內,完全依照啟動第一個節點的方式來啟動一個新節點。多個節點可以共享同一個目錄。” 沒搞懂什么意思,試了下再開個控制臺進入es目錄執行命令行,會拋異常。所以還是老老實實按照網上其它資料提到的,拷貝一份es目錄先,要幾個節點就拷貝幾份。。

      ES官方給.Net平臺提供了兩個工具—— Elasticsearch.Net 和 NEST,前者較底層,后者基于前者基礎上進行了更高級的封裝以方便開發調用。

      NEST有個Connection pools,這跟我們平常認為的連接池不是同一個概念,而是一種策略——以什么方式連接到ES——有四種策略:

      • SingleNodeConnectionPool:每次連接指向到同一個節點(一般設置為主節點,專門負責路由)
      • StaticConnectionPool:如果知道一些節點Uri的話,那么每次就[隨機]連接到這些節點[中的一個]
      • SniffingConnectionPool:derived from StaticConnectionPool,a sniffing connection pool allows itself to be reseeded at run time。然而暫時并不知道具體用處。。。
      • StickyConnectionPool:選擇第一個節點作為請求主節點。同樣不知用這個有什么好處。。。

      下面我們使用ES實現自動補完的功能,順帶介紹涉及到的知識點。

      服務器根據用戶當前輸入返回可能的[用戶真正想輸的]字符串——"Suggest As You Type"。ES提供了四個Suggester API(可參看 Elasticsearch Suggester詳解,這篇文章沒有介紹第四個Context Suggester,我會在本節后面稍作描述),本文舉例的自動補完,適合使用Completion Suggester(后面會說到使用上存在問題)。

      我們先來看類型定義:

       1 public class ProductIndexES
       2 {
       3     public long Id { get; set; }
       4     public string ProductName { get; set; }
       5     /// <summary>
       6     /// 品牌標識
       7     /// </summary>
       8     public long BrandId { get; set; }
       9     public string BrandName { get; set; }
      10     /// <summary>
      11     /// 店鋪標識
      12     /// </summary>
      13     public long ShopId { get; set; }
      14     public string ShopName { get; set; }
      15     /// <summary>
      16     /// 價格
      17     /// </summary>
      18     public decimal Price { get; set; }
      19     /// <summary>
      20     /// 上架時間
      21     /// </summary>
      22     public DateTime AddDate { get; set; }
      23     /// <summary>
      24     /// 售出數量
      25     /// </summary>
      26     public long SaleCount { get; set; }
      27     //產品自定義屬性
      28     public object AttrValues { get; set; }
      29     public Nest.CompletionField Suggestions { get; set; }
      30 }

      若要使用Completion Suggester,類型中需要有一個CompletionField的字段,可以將原有字段改成CompletionField類型,比如ProductName,我們同樣可以針對CompletionField設置Analyzer,所以不影響該字段原有的索引功能CompletionField接受的是字符串數組Input字段,經測試也看不出Analyzer對它的作用(自動補完返回的字符串是Input數組中與用戶輸入起始匹配的字符串,對分詞后的字符串沒有體現),所以Analyzer配置項的作用是什么令人費解);或者另外加字段,用于專門存放Input數組,這就更加靈活了,本例采用的是后者。

      創建索引:

       1 var descriptor = new CreateIndexDescriptor("products")
       2     .Mappings(ms => ms.Map<ProductIndexES>("product", m => m.AutoMap()
       3         .Properties(ps => ps
       4         //string域index屬性默認是 analyzed 。如果我們想映射這個字段為一個精確值,我們需要設置它為 not_analyzed或no或使用keyword
       5         .Text(p => p
       6         .Name(e => e.ProductName).Analyzer("ik_max_word").SearchAnalyzer("ik_max_word")
       7         .Fields(f => f.Keyword(k => k.Name("keyword"))))//此處作為演示
       8         .Keyword(p => p.Name(e => e.BrandName))
       9         .Keyword(p => p.Name(e => e.ShopName))
      10         .Completion(p => p.Name(e => e.Suggestions)))));//此處可以設置Analyzer,但是看不出作用
      11 
      12 Client.CreateIndex(descriptor);

      第6、7行表示ProductName有多重配置,作為Text,它可以用作全文檢索,當然我們希望用戶在輸入產品全名時也能精確匹配到,所以又設置其為keyword表示是個關鍵詞,這種情況就是Multi fields。不過由于我們設置了SearchAnalyzer,和Analyzer一樣,用戶輸入會按同樣方式分詞后再去匹配,所以不管是全名輸入或者部分輸入,都可以通過全文檢索到。

      接著把對象寫入索引,方法如下:

       1 public void IndexProduct(ProductIndexES pi)
       2 {
       3     var suggestions = new List<string>() { pi.BrandName, pi.ShopName, pi.ProductName };
       4     var ar = this.Analyze(pi.ProductName);//分詞
       5     suggestions.AddRange(ar.Tokens.Select(t => t.Token));
       6     suggestions.RemoveAll(s => s.Length == 1);//移除單個字符(因為對自動補完來說沒有意義)
       7     pi.Suggestions = new CompletionField { Input = suggestions.Distinct() };
       8 
       9     //products是索引,product是類型
      10     Client.Index(pi, o => o.Index("products").Id(pi.Id).Type("product"));
      11 }

      假設我新插入了三個文檔,三個suggestions里的input分別是["產品"],["產家合格"],["產品測試","產品","測試"],顯然,根據上述方法的邏輯,最后那個數組中的后兩項是第一項分詞出來的結果。

      接下來就是最后一步,通過用戶輸入返回匹配的記錄:

      1 public void SuggestCompletion(string text)
      2 {
      3     var result = Client.Search<ProductIndexES>(d => d.Index("products").Type("product")
      4     .Suggest(s => s.Completion("prd-comp-suggest", cs => cs.Field(p => p.Suggestions).Prefix(text).Size(8))));
      5     Console.WriteLine(result.Suggest);
      6 }

      好,一切看似很完美,這時候用戶輸入“產”這個字,我們期望的是返回["產品","產家合格","產品測試"],次一點的話就再多一個"產品"(因為所有input中有兩個"產品")。然而結果卻出我意料,我在kibana控制臺里截圖:

      返回的是["產品","產品","產家合格"]。查找資料發現這似乎是ES團隊故意為之——如果結果指向同一個文檔(或者說_source的值相同),那么結果合并(保留其中一個)——所以Completion Suggester并不是為了自動補完的場景設計的,它的作用主要還是查找文檔,文檔找到就好,不管你的suggestions里是否還有其它與輸入匹配的input。這時聰明的同學可能會說要不不返回_source試試看,很遺憾,官方說_source meta-field must be enabled,而且并沒有給你設置的地方。之前有版本mapping時有個配置項是payloads,設置成false貌似可以返回所有匹配的input,還有output什么的,總之還是有辦法改變默認行為的,然而筆者試的這個版本把這些都去掉了,不知以后是否會有改變。。。

      Completion only retrieves one result when multiple documents share same output

      這么看來,Suggester更像自定義標簽(依據標簽搜索文檔,Completion Suggester只是可以讓我們只輸入標簽的一部分而已)。所以說自動補全的功能還是得另外實現咯?要么以后有精力看下ES的源碼看怎么修改吧。。

      在Completion Suggester基礎上,ES另外提供了Context Suggester,有兩種context:category 和 geo,在查詢時帶上context即可取得與之相關的結果。意即在標簽基礎上再加一層過濾。

      相關性:與之對應的重要概念就是評分,主要用在全文檢索時。Elasticsearch 的相似度算法 被定義為檢索詞頻率/反向文檔頻率, TF/IDF。默認情況下,返回結果是按相關性倒序排列的。

      緩存:當進行精確值查找時, 我們會使用過濾器(filters)。過濾器很重要,因為它們執行速度非常快 —— 不會計算相關度(直接跳過了整個評分階段)而且很容易被緩存。一般來說,在精確查找時,相關度是可以忽略的,排序的話我們更多的是根據某個字段自定義排序,所以為了性能考慮,我們應該盡可能地使用過濾器。

      數組:ES并沒有顯式定義數組的概念,你可以在一個string類型的字段賦值為"abc",也可以賦值為["abc","ddd"],ES會自動處理好。這在一些場景下很有用,比如產品屬于某個葉子類目,它的類目Id設為該葉子類目的Id,這樣用戶能搜索到該類目下的所有產品,但這樣會有問題:當用戶搜索父類目時將得不到任何產品。顯然這是不合理的,所以我們可以將產品的類目Id賦值包含從根類目到葉子類目的類目Id數組,用戶搜索其中任何類目都能得到該產品。 官方文檔


      Quartz.Net

      在給內容建索引時可以實時建立,也可以異步[批量]創建,后者的話我們常用計劃任務的方式,涉及到的工具比較常見的是Quartz.Net。

      以下對Quartz.Net的描述基于2.5版本。

      Quartz.Net支持多個trigger觸發同一個job,但不支持一個trigger觸發多個job,不明其意。

      Quartz.Net的job和trigger聲明方式有多種,可以通過代碼

      IJobDetail job = JobBuilder.Create<IndexCreationJob>().Build();
      ITrigger trigger = TriggerBuilder.Create().StartNow().WithSimpleSchedule(x => x.WithIntervalInSeconds(600).RepeatForever()).Build();
      
      _scheduler.ScheduleJob(job, trigger);

      或者通過xml文件。若是通過xml文件,則要指定是哪個xml文件,也可以設置xml文件的watch interval,還可以設置線程數量等等(大部分都有默認值,可選擇設置),同樣可以通過代碼

      XMLSchedulingDataProcessor processor = new XMLSchedulingDataProcessor(new SimpleTypeLoadHelper());
      ISchedulerFactory factory = new StdSchedulerFactory();
      IScheduler sched = factory.GetScheduler();
      processor.ProcessFileAndScheduleJobs(IOHelper.GetMapPath("/quartz_jobs.xml"), sched);

      以上代碼即表示讀取根目錄下的quartz.jobs.xml獲取job和trigger的聲明。還有另一種代碼方式:

      var properties = new NameValueCollection();
      properties["quartz.plugin.jobInitializer.type"] = "Quartz.Plugin.Xml.XMLSchedulingDataProcessorPlugin";
      properties["quartz.plugin.jobInitializer.fileNames"] = "~/quartz_jobs.xml";
      properties["quartz.plugin.jobInitializer.failOnFileNotFound"] = "true";
      properties["quartz.plugin.jobInitializer.scanInterval"] = "600";
      
      ISchedulerFactory sf = new StdSchedulerFactory(properties);
      _scheduler = sf.GetScheduler();

      以上600表示makes it watch for changes every ten minutes (600 seconds)

      當然我們可以通過配置文件(同聲明job和trigger的xml文件,兩者目的不同),如:

        <configSections>
          <section name="quartz" type="System.Configuration.NameValueSectionHandler"/>
        </configSections>
        <quartz>
          <add key="quartz.scheduler.instanceName" value="ExampleDefaultQuartzScheduler"/>
          <add key="quartz.threadPool.type" value="Quartz.Simpl.SimpleThreadPool, Quartz"/>
          <add key="quartz.threadPool.threadCount" value="10"/>
          <add key="quartz.threadPool.threadPriority" value="2"/>
          <add key="quartz.jobStore.misfireThreshold" value="60000"/>
          <add key="quartz.jobStore.type" value="Quartz.Simpl.RAMJobStore, Quartz"/>
          <!--*********************Plugin配置**********************-->
          <add key="quartz.plugin.xml.type" value="Quartz.Plugin.Xml.XMLSchedulingDataProcessorPlugin, Quartz" />
          <add key="quartz.plugin.xml.fileNames" value="~/quartz_jobs.xml"/>
        </quartz>

      或者單獨一個文件quartz.config:

      # You can configure your scheduler in either <quartz> configuration section
      # or in quartz properties file
      # Configuration section has precedence
      
      quartz.scheduler.instanceName = QuartzTest
      
      # configure thread pool info
      quartz.threadPool.type = Quartz.Simpl.SimpleThreadPool, Quartz
      quartz.threadPool.threadCount = 10
      quartz.threadPool.threadPriority = Normal
      
      # job initialization plugin handles our xml reading, without it defaults are used
      quartz.plugin.xml.type = Quartz.Plugin.Xml.XMLSchedulingDataProcessorPlugin, Quartz
      quartz.plugin.xml.fileNames = ~/quartz_jobs.xml
      
      # export this server to remoting context
      #quartz.scheduler.exporter.type = Quartz.Simpl.RemotingSchedulerExporter, Quartz
      #quartz.scheduler.exporter.port = 555
      #quartz.scheduler.exporter.bindName = QuartzScheduler
      #quartz.scheduler.exporter.channelType = tcp
      #quartz.scheduler.exporter.channelName = httpQuartz

      不需要特意指定是放在配置節中,還是quartz.config中,或者兩者皆有,Quartz.Net會自動加載配置項。代碼和配置方式也可以混著使用,總之給人的選擇多而雜,加之官方文檔并不完善,初次接觸容易讓人困惑。

       

      參考資料:

      Elasticsearch: 權威指南

      HBuilder處理git沖突,同 10_Eclipse中演示Git沖突的解決

      PostgreSQL的全文檢索插件zhparser的中文分詞效果

      SCWS 中文分詞

      聊一聊雙十一背后的技術 - 分詞和搜索

      詳細講解PostgreSQL中的全文搜索的用法

      Lucene 3.0 原理與代碼分析

       

       

      轉載請注明出處:http://www.rzrgm.cn/newton/p/6873508.html

      posted @ 2017-07-25 11:30  萊布尼茨  閱讀(4290)  評論(3)    收藏  舉報
      主站蜘蛛池模板: 精品国产中文字幕av| 国产午夜福利在线观看播放| 国产精品久久久一区二区三区| 亚洲AV无码一二区三区在线播放| 人妻换着玩又刺激又爽| 国内精品无码一区二区三区| a级免费视频| 欧美亚洲一区二区三区在线| 最新亚洲人成网站在线影院 | 国产午夜A理论毛片| 美腿丝袜亚洲综合第一页| 国产三级精品福利久久| AV无码免费不卡在线观看| 国产精品爽爽久久久久久竹菊| 无码人妻斩一区二区三区| 99久久99久久精品国产片| 色欲久久综合亚洲精品蜜桃| 国产色视频一区二区三区| 日韩亚洲中文图片小说| 亚洲理论在线A中文字幕| 国产精品制服丝袜无码| 日本高清视频网站www| 日本毛茸茸的丰满熟妇| 90后极品粉嫩小泬20p| 国产精品一区二区三区黄| 亚洲午夜伦费影视在线观看| 99中文字幕精品国产| 久久精品国产一区二区蜜芽| 亚洲午夜av久久久精品影院| 韩国免费a级毛片久久| 久久夜色精品国产亚洲av| 国产永久免费高清在线观看| 欧美性猛交xxxx免费看| 国产麻豆放荡av激情演绎| 丁香五月亚洲综合在线国内自拍| 石家庄市| 精品久久久久无码| 欧洲熟妇色自偷自拍另类| av在线网站手机播放| 精品一区二区三区在线观看l| 综合无码一区二区三区|