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環境的搭建。
- java環境安裝
由於es是java語言開發的,所以這里要安裝java環境。
jdk下載:
https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html

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

查看是否安裝成功:

2.安裝Elasticsearch
Elasticsearch版本已經比較多,初學者可能比較懵。特別是在安裝head和Kibana的時候,如果版本不匹配,往往會導致無法使用。這里使用的是elasticsearch-5.6.11版本。
elasticsearch-5.6.11下載:
https://www.elastic.co/downloads/past-releases/elasticsearch-5-6-11

解壓到C:\ELk 備用。

3.head安裝(非必需)
es 4.x 版本安裝head很簡單,只需下載head插件解壓到指定目錄即可。es 5.x+需要借助node安裝。
head下載:
解壓到C:\ELk\elasticsearch-5.6.11

node下載:

安裝node

檢查node和npm是否安裝成功

path環境變量末尾 會自動增加 C:\Program Files\nodejs\
安裝 phantomjs
官網:http://phantomjs.org/下載【配置環境變量】
安裝grunt
npm install -g grunt-cli

執行C:\ELk\elasticsearch-5.6.11\bin\elasticsearch.bat
執行命令啟動 head

瀏覽器訪問:http://localhost:9100/

4.安裝kibana
導致為止,其實elasticsearch自身已經安裝完成。通過Head就能很方便的操作es,但是kibana集成了head類似功能,並提供了更加友好的訪問界面。
kibana-5.6.9-windows-x86下載:
下載之后,解壓到C:\ELk\kibana-5.6.9-windows-x86
執行C:\ELk\kibana-5.6.9-windows-x86\bin\kibana.bat
瀏覽器訪問:http://localhost:5601

二、基本概念
-
Cluster(集群)
集群是一個或多個節點(服務器)的集合,這些節點一起保存整個數據,並在所有節點上提供聯合索引和搜索功能。
一個運行中的 Elasticsearch 實例稱為一個 節點,而集群是由一個或者多個擁有相同 cluster.name 配置的節點組成, 它們共同承擔數據和負載的壓力。當有節點加入集群中或者從集群中移除節點時,集群將會重新平均分布所有的數據。
作為用戶,我們可以將請求發送到 集群中的任何節點 ,包括主節點。 每個節點都知道任意文檔所處的位置,並且能夠將我們的請求直接轉發到存儲我們所需文檔的節點。 無論我們將請求發送到哪個節點,它都能負責從各個包含我們所需文檔的節點收集回數據,並將最終結果返回給客戶端。 Elasticsearch 對這一切的管理都是透明的。
-
Node(節點)
節點是集群的一部分、存儲數據並參與集群的索引和搜索功能的單個服務器。
-
Index
索引是具有相似特性的文檔集合。
- 類似於關系型數據庫中"庫"的概念
-
Type
Type是具有一組公共字段的文檔定義類型
例如,假設您運行一個博客平台並將所有數據存儲在一個索引中。在該索引中,可以定義用戶數據的類型、博客數據的另一種類型以及注釋數據的另一種類型。
- 類似於關系型數據庫中"表"的概念
-
Document
被索引信息的基本單元。
- 類似於關系型數據庫的一個記錄(行)
- 會被壓縮成json格式
-
Shards & Replicas(分片&副本分片)
索引可以潛在地存儲可以超過單個節點的硬件限制的大量數據。例如,占用1TB磁盤空間的十億個文檔的單個索引可能不適合單個節點的磁盤,或者可能太慢而無法單獨為來自單個節點的搜索請求提供服務。
分片的兩個主要原因:
- 它允許您水平分割/縮放您的內容卷。
- 它允許你分配和並行操作的碎片(可能在多個節點上)從而提高性能/吞吐量
在網絡/雲環境中,在任何時候都可以預期到故障,在碎片/節點不知何故脫機或由於任何原因消失的情況下,非常有用,並且強烈建議使用故障轉移機制。為此,Elasticsearch允許您將一個或多個索引碎片的副本復制到稱為副本碎片(replica shards)或簡稱為副本(replica)中。
復制是重要的兩個主要原因:
- 在碎片/節點失敗的情況下,它提供了高可用性。由於這個原因,需要注意的是,副本碎片永遠不會分配到與原始/主碎片相同的節點上。
- 它允許您擴展搜索量/吞吐量,因為可以並行地在所有副本上執行搜索。
添加故障轉移
當集群中只有一個節點在運行時,意味着會有一個單點故障問題——沒有冗余。 幸運的是,我們只需再啟動一個節點即可防止數據丟失。
擁有兩個節點的集群——所有主分片和副本分片都已被分配。
三、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

安裝完后,

基本的增刪該查在項目中的實現上面已經有所介紹,這里重點講一下查詢:
筆者使用的.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是很強大的開源工具,在實現全文搜索上有其獨到之處,也是大數據的分析方面利器,值得大家深入去研究和實踐。
六、參考
- Elasticsearch權威指南
- Elasticsearch.Net 5.x
- Elasticsearch Reference [5.6] » Document APIs
- elasticsearch-net-example
- ElasticSearch安裝及HEAD插件配置