天天看點

Elasticsearch.net項目實戰

elasticsearch.net項目實戰

目錄

  • Elasticsearch+kibana
    • 環境搭建
      • windows 10環境配置
      • 安裝Elasticsearch
      • head安裝(非必需)
      • 安裝kibana
    • 基本概念
      • Index
      • Type
      • Document
    • DSL的基本使用
      • 增加
      • 修改
      • 查詢
      • 删除
  • Elasticsearch .Net
    • Low level client基本使用
    • 項目實戰
  • 總結
  • 參考

     Elasticsearch是一個基于Apache Lucene(TM)的開源搜尋引擎。無論在開源還是專有領域,Lucene可以被認為是迄今為止最

先進、性能最好的、功能最全的搜尋引擎庫。

     一說到全文搜尋,lucene久負盛名。早年間,因為項目需要,接觸過一個叫盤古分詞的開源項目,借助其中的分詞實作了分詞搜尋的功能。而盤古分詞就是lucence的.NET版本。據說這個開源項目已經恢複更新并支援. NET Core,有興趣的童鞋可以去圍觀一下(https://github.com/LonghronShen/Lucene.Net.Analysis.PanGu/tree/netcore2.0)。
      我想很多童鞋都聽過ELK,ELK是Elasticsearch、Logstash、Kibana。正好公司運維同僚引入了這樣一套體系,用于建立集中式日志收集系統,将所有節點上的日志統一收集,管理,通路。雖然能夠從一定程度上解決基本的問題,但是原生的kibana界面和查詢方式都不夠友好,很難推向廣大的開發人員。于是我在想,我們是否可以利用這個開源的庫內建到運維自動化平台當中,讓這把利劍發揮出更大的價值。

一、環境搭建

 

本文是基于windows 10作業系統的es環境的搭建。
  1. java環境安裝
     由于es是java語言開發的,是以這裡要安裝java環境。

     jdk下載下傳:

https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html
Elasticsearch.net項目實戰

     安裝完成之後就是配置環境變量:

Elasticsearch.net項目實戰

     檢視是否安裝成功:

Elasticsearch.net項目實戰

2.安裝Elasticsearch

    Elasticsearch版本已經比較多,初學者可能比較懵。特别是在安裝head和Kibana的時候,如果版本不比對,往往會導緻無法使用。這裡使用的是elasticsearch-5.6.11版本。

     elasticsearch-5.6.11下載下傳:

https://www.elastic.co/downloads/past-releases/elasticsearch-5-6-11
Elasticsearch.net項目實戰

解壓到C:\ELk 備用。

Elasticsearch.net項目實戰

3.head安裝(非必需)

    es 4.x 版本安裝head很簡單,隻需下載下傳head插件解壓到指定目錄即可。es 5.x+需要借助node安裝。

     head下載下傳:

https://github.com/mobz/elasticsearch-head

解壓到C:\ELk\elasticsearch-5.6.11

Elasticsearch.net項目實戰

     node下載下傳:

https://nodejs.org/dist/v8.12.0/node-v8.12.0-win-x64.zip
Elasticsearch.net項目實戰

安裝node

Elasticsearch.net項目實戰

檢查node和npm是否安裝成功

Elasticsearch.net項目實戰

path環境變量末尾 會自動增加 C:\Program Files\nodejs\

安裝 phantomjs

官網:http://phantomjs.org/下載下傳【配置環境變量】

安裝grunt

npm install -g grunt-cli

Elasticsearch.net項目實戰

執行C:\ELk\elasticsearch-5.6.11\bin\elasticsearch.bat

執行指令啟動 head

Elasticsearch.net項目實戰

浏覽器通路:http://localhost:9100/

Elasticsearch.net項目實戰

4.安裝kibana

    導緻為止,其實elasticsearch自身已經安裝完成。通過Head就能很友善的操作es,但是kibana內建了head類似功能,并提供了更加友好的通路界面。

     kibana-5.6.9-windows-x86下載下傳:

https://www.elastic.co/downloads/past-releases/kibana-5-6-9

下載下傳之後,解壓到C:\ELk\kibana-5.6.9-windows-x86

執行C:\ELk\kibana-5.6.9-windows-x86\bin\kibana.bat

浏覽器通路:http://localhost:5601

Elasticsearch.net項目實戰

二、基本概念

  • Cluster(叢集)
         叢集是一個或多個節點(伺服器)的集合,這些節點一起儲存整個資料,并在所有節點上提供聯合索引和搜尋功能。

         一個運作中的 Elasticsearch 執行個體稱為一個 節點,而叢集是由一個或者多個擁有相同 cluster.name 配置的節點組成, 它們共同承擔資料和負載的壓力。當有節點加入叢集中或者從叢集中移除節點時,叢集将會重新平均分布所有的資料。

          作為使用者,我們可以将請求發送到 叢集中的任何節點 ,包括主節點。 每個節點都知道任意文檔所處的位置,并且能夠将我們的請求直接轉發到存儲我們所需文檔的節點。 無論我們将請求發送到哪個節點,它都能負責從各個包含我們所需文檔的節點收集回資料,并将最終結果傳回給用戶端。 Elasticsearch 對這一切的管理都是透明的。

    Elasticsearch.net項目實戰
  • Node(節點)
    節點是叢集的一部分、存儲資料并參與叢集的索引和搜尋功能的單個伺服器。
    Elasticsearch.net項目實戰
  • 索引是具有相似特性的文檔集合。
    • 類似于關系型資料庫中"庫"的概念
  • Type是具有一組公共字段的文檔定義類型
         例如,假設您運作一個部落格平台并将所有資料存儲在一個索引中。在該索引中,可以定義使用者資料的類型、部落格資料的另一種類型以及注釋資料的另一種類型。
    • 類似于關系型資料庫中"表"的概念
  • 被索引資訊的基本單元。
    • 類似于關系型資料庫的一個記錄(行)
    • 會被壓縮成json格式
  • Shards & Replicas(分片&副本分片)
    索引可以潛在地存儲可以超過單個節點的硬體限制的大量資料。例如,占用1TB磁盤空間的十億個文檔的單個索引可能不适合單個節點的磁盤,或者可能太慢而無法單獨為來自單個節點的搜尋請求提供服務。
         分片的兩個主要原因:
    • 它允許您水準分割/縮放您的内容卷。
    • 它允許你配置設定和并行操作的碎片(可能在多個節點上)進而提高性能/吞吐量
    在網絡/雲環境中,在任何時候都可以預期到故障,在碎片/節點不知何故脫機或由于任何原因消失的情況下,非常有用,并且強烈建議使用故障轉移機制。為此,Elasticsearch允許您将一個或多個索引碎片的副本複制到稱為副本碎片(replica shards)或簡稱為副本(replica)中。
         複制是重要的兩個主要原因:
    • 在碎片/節點失敗的情況下,它提供了高可用性。由于這個原因,需要注意的是,副本碎片永遠不會配置設定到與原始/主碎片相同的節點上。
    • 它允許您擴充搜尋量/吞吐量,因為可以并行地在所有副本上執行搜尋。
         添加故障轉移
    當叢集中隻有一個節點在運作時,意味着會有一個單點故障問題——沒有備援。 幸運的是,我們隻需再啟動一個節點即可防止資料丢失。
         擁有兩個節點的叢集——所有主分片和副本分片都已被配置設定。
    Elasticsearch.net項目實戰

三、DSL的基本使用

elasticsearch也像mysql一樣提供了專門的文法來操作資料。Elasticsearch provides a full Query DSL (Domain Specific Language) based on JSON to define queries.
  • 建立文檔
PUT people/person/1?op_type=create
{
    "user" : "kimchy",
    "post_date" : "2009-11-15T14:12:12",
    "message" : "trying out Elasticsearch"
}
           
POST /user/guest/20/_update
{
    "doc": {
      "RealName":"LukyHuu20"
    }
}
           
GET /user/guest/_search
{
    "query": {
      "match": {
        "Id":22
      }
    }
}
           
DELETE /user/guest/15
{
  
}
           

四、Elasticsearch .Net

     elasticsearch是以restfulAPI方式對外提供接口,并提供用戶端給多種語言使用。Elasticsearch uses standard RESTful APIs and JSON. We also build and maintain clients in many languages such as Java, Python, .NET, SQL, and PHP. Plus, our community has contributed many more. They’re easy to work with, feel natural to use, and, just like Elasticsearch, don't limit what you might want to do with them.

參考(https://www.elastic.co/products/elasticsearch)

1.Low level client基本使用

     本文是介紹ES的.NET用戶端,Elasticsearch .Net - Low level client[5.x]

通過引入對應的版本的用戶端,便可通過C#操作ES。參考(https://www.elastic.co/guide/en/elasticsearch/client/net-api/5.x/elasticsearch-net.html)

連接配接

var settings = new ConnectionConfiguration(new Uri("http://example.com:9200"))
    .RequestTimeout(TimeSpan.FromMinutes(2));

var lowlevelClient = new ElasticLowLevelClient(settings);
           

插入文檔

var indexResponse = lowlevelClient.Index<byte[]>("user", "guest", user.Id.ToString(), user);
 byte[] responseBytes = indexResponse.Body;
           

更新文檔

var searchResponse = lowlevelClient.Update<string>("user", "guest", id.ToString(), new
                {
                    doc = new
                    {
                        RealName = realname,
                        Description = description
                    }
                });

bool successful = searchResponse.Success;
           
var searchResponse = lowlevelClient.Search<string>("user", "guest", new
                {
                    query = new
                    {
                        match = new
                        {
                            Id = id
                        }
                    }
                });

bool successful = searchResponse.Success;
           
var searchResponse = lowlevelClient.Delete<string>("user", "guest", id.ToString());

                bool successful = searchResponse.Success;
           

2.項目實戰

     前面大緻介紹了ES的安裝和基本使用。那麼,如何在項目中落地呢?

使用nuget安裝Elasticsearch.Net 5.6.4

Install-Package Elasticsearch.Net -Version 5.6.4
Elasticsearch.net項目實戰

安裝完後,

Elasticsearch.net項目實戰

基本的增删該查在項目中的實作上面已經有所介紹,這裡重點講一下查詢:

筆者使用的.NET MVC5 Web架構,對于傳回的結果筆者做了一個簡單封裝:

public class ESearchRoot<T>
    {
        /// <summary>
        /// 
        /// </summary>
        public int took { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public string timed_out { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public _shards _shards { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public Hits<T> hits { get; set; }
    }

    public class _shards
    {
        /// <summary>
        /// 
        /// </summary>
        public int total { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public int successful { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public int skipped { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public int failed { get; set; }
    }

    public class HitsItem<T>
    {
        /// <summary>
        /// 
        /// </summary>
        public string _index { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public string _type { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public string _id { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public string _score { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public T _source { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public List<int> sort { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public Highlight highlight { get; set; }
    }

    public class Hits<T>
    {
        /// <summary>
        /// 
        /// </summary>
        public int total { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public string max_score { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public List<HitsItem<T>> hits { get; set; }
    }

    public class Highlight
    {
        /// <summary>
        /// 
        /// </summary>
        public List<string> Description { get; set; }
    }

           

因為soure傳回的對象是不定的,是以使用了泛型。

本項目soure對應的類,user:

///<summary>
    /// 
    /// </summary>
    public class User
    {
        /// <summary>
        /// 
        /// </summary>
        public string Account { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public string Phone { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public string Email { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public string RealName { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public string CanReview { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public string CanExcute { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public string Avatar { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public string IsUse { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public int Id { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public string Name { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public string Description { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public DateTime CreateTime { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public DateTime ModifyTime { get; set; }
    }
    
           

項目使用了帶條件的分頁查詢:

public List<AdminUser> GetBySomeWhere(string keyword, int limit, int pageSize, out int total)
        {
            List<AdminUser> users = new List<AdminUser>();

            total = 0;
            try
            {
                var settings = new ConnectionConfiguration(new Uri("http://localhost:9200/"))
   .RequestTimeout(TimeSpan.FromMinutes(2));

                var lowlevelClient = new ElasticLowLevelClient(settings);

                //根據不同的參數 來建構不同的查詢條件
                var request = new object();
                if (!String.IsNullOrEmpty(keyword))
                {
                    request = new
                    {
                        from = limit,
                        size = pageSize,
                        query = new
                        {
                            match = new
                            {
                                Description = keyword
                            }
                        },
                        highlight = new
                        {
                            fields = new
                            {
                                Description = new { }
                            }
                        },
                        sort = new
                        {
                            Id = new
                            {
                                order = "desc"
                            }
                        }
                    };
                }
                else
                {
                    request = new
                    {
                        from = limit,
                        size = pageSize,
                        query = new
                        {
                            match_all = new
                            {

                            }
                        },
                        highlight = new
                        {
                            fields = new
                            {
                                Description = new { }
                            }
                        },
                        sort = new
                        {
                            Id = new
                            {
                                order = "desc"
                            }
                        }
                    };
                }


                var searchResponse = lowlevelClient.Search<string>("user", "guest", request);

                bool successful = searchResponse.Success;
                var responseJson = searchResponse.Body;

                if (!successful)
                {
                    return users;
                }

                ESearchRoot<User> root = JsonHelper.JSONStringObject<ESearchRoot<User>>(responseJson);
                if (root != null)
                {
                    total = root.hits.total;
                    foreach (HitsItem<User> item in root.hits.hits)
                    {
                        if (item._source != null)
                        {
                            string highlightDescription = String.Empty;
                            StringBuilder sbDs = new StringBuilder();
                            if (item.highlight != null && item.highlight.Description.Count > 0)
                            {
                                //ighlightDescription = item.highlight.Description[0];
                                foreach (var d in item.highlight.Description)
                                {
                                    sbDs.Append(d);
                                }
                                highlightDescription = sbDs.ToString();
                            }

                            AdminUser user = new AdminUser
                            {
                                Id = item._source.Id,
                                RealName = item._source.RealName,
                                Account = item._source.Account,
                                Email = item._source.Email,
                                Phone = item._source.Phone,
                                //IsUse=item._source.IsUse,
                                Avatar = item._source.Avatar,
                                Description = item._source.Description,
                                HighlightDescription = highlightDescription,
                                CreateTime = item._source.CreateTime,
                                ModifyTime = item._source.ModifyTime
                            };
                            users.Add(user);
                        }
                    }
                }

                return users;
            }
            catch (ElasticsearchClientException ex)
            {
                //Log4Helper.Error
            }
            return users;
        }
           

項目最終的效果如下:

Elasticsearch.net項目實戰
Elasticsearch.net項目實戰

五、總結

     elasticsearch是很強大的開源工具,在實作全文搜尋上有其獨到之處,也是大資料的分析方面利器,值得大家深入去研究和實踐。

六、參考

  •   Elasticsearch權威指南
  •   Elasticsearch.Net 5.x
  •   Elasticsearch Reference [5.6] » Document APIs
  •   elasticsearch-net-example
  •   ElasticSearch安裝及HEAD插件配置

繼續閱讀