.NET Core接入ElasticSearch 7.5

写在前面

最近一段时间,团队在升级ElasticSearch(以下简称ES),从ES 2.2升级到ES 7.5。也是这段时间,我从零开始,逐步的了解了ES,中间也踩了不少坑,所以特地梳理和总结一下相关的技术点。

ES小趣闻:

多年前,一个叫做Shay Banon的刚结婚不久的开发者,由于妻子要去伦敦学习厨师,他便跟着也去了。在他找工作的过程中,为了给妻子构建一个食谱的搜索引擎,他开始使用Lucene进行尝试。直接基于Lucene工作会比较困难,所以Shay开始抽象Lucene代码以便可以在应用中添加搜索功能。他发布了他的第一个开源项目,叫做“Compass”。后来Shay找到一份工作,这份工作处在高性能和内存数据网格的分布式环境中,因此高性能的、实时的、分布式的搜索引擎也是理所当然需要的。然后他决定重写Compass库使其成为一个独立的服务叫做Elasticsearch。Shay的妻子依旧等待着她的食谱搜索……

由此看见,一个成功的男人背后总是站着一个女人,所以程序员们要早点找到对象,可程序员找到女朋友又谈何容易,程序猿注定悲伤-_-||。

ElasticSearch前期准备

EElasticsearch是一个开源的分布式、RESTful 风格的搜索和数据分析引擎,ES底层基于开源库Apache Lucene,不过Lucene使用门槛太高,ES隐藏了Lucene使用时的复杂性,使得分布式实时文档搜索、实时分析引擎、高扩展性变得更加容易。

安装

安装ES,首先要配置Java SDK,然后配置一下环境变量即可。然后再从官网下载ES安装包,可以选用默认配置,点击下一步—>安装。

在浏览器上输入http://localhost:9200/,显示如下文本,就意味着安装成功了。

{

 "name" : "XXXXXXXXXX",

 "cluster_name" : "elasticsearch",

 "cluster_uuid" : "mB04ov3OTvSz7OSe0GtZ_A",

 "version" : {

  "number" : "7.5.2",

  "build_flavor" : "unknown",

  "build_type" : "unknown",

  "build_hash" : "8bec50e1e0ad29dad5653712cf3bb580cd1afcdf",

  "build_date" : "2020-01-15T12:11:52.313576Z",

  "build_snapshot" : false,

  "lucene_version" : "8.3.0",

  "minimum_wire_compatibility_version" : "6.8.0",

  "minimum_index_compatibility_version" : "6.0.0-beta1"

 },

  "tagline" : "You Know, for Search"

}

部分基本概念

节点 & 集群

集群由多个节点组成,其中一个节点为主节点,主节点由内部选举算法选举产生。当然主节点是相对的,是相对于内部而言的。ES去中心化,这是相对于外部而言的,从逻辑上说,与任何一个节点的的通信和与集群通信是没有区别的。如下图所示。

索引

索引保存相关数据的地方,是指向一个或者多个物理分片的逻辑命名空间 。另外,每个Index的名字必须是小写。

文档

Document的核心元数据有三个:_index、_type(7.X已经弱化了,8.0开始就会移除)、_id。Document 使用 JSON 格式表示。

分片

一个分片是一个底层的工作单元,它仅保存了全部数据中的一部分。我们的文档被存储和索引到分片内,但是应用程序是直接与索引而不是与分片进行交互。

Elasticsearch 是利用分片将数据分发到集群内各处的。分片是数据的容器,文档保存在分片内,分片又被分配到集群内的各个节点里。当你的集群规模扩大或者缩小时, Elasticsearch 会自动的在各节点中迁移分片,使得数据仍然均匀分布在集群里。

一个分片可以是主分片或者副本分片。索引内任意一个文档都归属于一个主分片,所以主分片的数目决定着索引能够保存的最大数据量。

一个副本分片只是一个主分片的拷贝。副本分片作为硬件故障时保护数据不丢失的冗余备份,并为搜索和返回文档等读操作提供服务。

在索引建立的时候就已经确定了主分片数,但是副本分片数可以随时修改。

理论上一个主分片最大能够存储Integer.MAX_VALUE^128 个文档。

写操作探讨

文档会被保存到主分片,那么在多个分片的情况下是如何写入和精确搜索的。实际上这是通过以下公式确定的:

shard = hash(routing) % number_of_primary_shards

以上的routing的值是一个任意的字符串,它默认被设置成文档的_id字段,但是也可以被设置成其他指定的值。这个routing字符串会被传入到一个哈希函数(Hash Function)来得到一个数字,然后该数字会和索引中的主要分片数进行模运算来得到余数。这个余数的范围应该总是在0和number_of_primary_shards - 1之间,它就是一份文档被存储到的分片的号码。

这就解释了为什么索引中的主要分片数量只能在索引创建时被指定,并且将来都不能在被更改:如果主要分片数量在索引创建后改变了,那么之前的所有路由结果都会变地不正确,从而导致文档不能被正确地获取。那么如何水平扩展呢,可以移步Designing for scale。

所有的文档API(get, index, delete, bulk, update)都接受一个routing参数,它用来定制从文档到分片的映射。一个特定的routing值能够确保所有相关文档 - 比如属于相同用户的所有文档 - 都会被存储在相同的分片上。

写操作原理图:写入的请求流程如图所示(此图源自《Elasticsearch权威指南》):写入到磁盘流程如下图所示:由此可见ES的实时并非是完全的实时,而是一种准实时(Near-Real-Time)。

读操作探讨

读操作分为两个阶段,查询阶段(Query Phrase)以及聚合提取阶段(Fetch Phrase)。

查询阶段

协调节点接受到读请求,并将请求分配到相应的分片上(有可能是主分片或是副本分片,这个机制后续会提及),默认情况下,每个分片创建10个结果(仅包含 document_id 和 Scores)的优先级队列,并以相关性排序,返回给协调节点。

查询阶段如果不特殊指定,落入的分片有可能是 primary 也有可能是 replicas,这个根据协调节点的负载均衡算法来确定。

聚合提取阶段

假设查询落入的分片数为 N,那么聚合阶段就是对 N*10 个结果集进行排序,然后再通过已经拿到的 document_id 查到对应的 document 并组装到队列里,组装完毕后将有序的数据返回给客户端。

  • 客户端发送请求到任意一个Node,成为Coordinating node

  • Coordinating node对Document进行路由,将请求转发到对应的Node上,此时会使用Round-Robin随机轮询算法,在Primary Shard以及其所有Replica中随机选择一个,让读请求负载均衡

  • 接收请求的node返回Document给Coordinating node

  • Coordinating node返回Document给客户端

ElasticSearch实战

ES在.NET平台上的官方客户端是NEST,以下操作都是基于该package的。

常用操作

以下操作均基于ES-Head,该工具是一个Chrome插件,非常简单实用,而且可以在GitHub上搜到源码,方便个性化开发。

写入数据

返回的数据中,可以看到Id是一段字符串,这是因为在写入的过程中并没有指定,所以会由ES默认生成。当然可以指定:

更新数据

_version值会随着操作次数,逐渐迭代。

删除数据

cluster

查询操作:

cluster

项目升级过程中遇到的问题

分页查询过慢

初次的查询使用了深度分页(from-size)查询,当数据达到百万千万级别时,已经慢的让人忍无可忍。所谓深度查询就是涉及到大量 shard 的查询时,直接跳页到几千甚至上万页的数据,协调节点就有宕机的风险,毕竟协调节点需要将大量数据汇总起来进行排序,耗费大量的内存和 CPU 资源。所以慎用!尽可能用 Scroll API ,即只允许拿到下一页的信息,不允许跳页的情况出现,会避免这种情况的发生。

后来改用了快照分页(scroll),整个查询过程非常稳定,方差几乎可以忽略。该查询会自动返回一个_scroll_id,通过这个id(经过base64编码)可以继续查询。查询语句如下:http://localhost:9200/_search/scroll?scroll=1m&scroll_id=c2MkjsjskMkkssllasKKKOzM0NDg1ODpksksks5566HHsaskLLLqi692215。这个语句虽然很快,但是无法做到跳页查询,只能一页一页的查询。

快照分页参考代码如下:

var searchResponse = client.Search(p =>

                p.Query(t =>t.Bool(l => l.Filter(f => f.DateRange(m => m.GreaterThanOrEquals(startTime).Field(d => d.PostDate))))).From(0).Size(Configurations.SyncSize).Index("archive").Sort(s => s.Ascending(a => a.PostDate)).Scroll("60s"));

while(某条件)

{

searchResponse = client.Scroll<ElasticsearchTransaction>("60s", searchResponse.ScrollId);//跳出循环的条件

}

模糊查询

该场景涉及到多个字段的模糊查询,当然,这种查询是十分消耗效率的,使用的时候要慎重,同时还要控制模糊关键字的数量,以尽可能在满足业务的情况下,提升查询效率,参考代码如下:

public static List<IHit> GetDataByFuzzy(ElasticClient client920>0) {

string[] fieldList =
{"filed1","filed2","filed3","filed4","filed5","filed6","filed7","filed8","filed9"
};string term = string.Concat("*", string.Join("* *", "i u a n".Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)), "*");
var result = client9200.Search<TModel>(p => p.Query(q => q.Bool(b=>b.Must(t=>t.QueryString(c => c.Fields(fieldList).Query(term).Boost(1.1).Fuzziness(Fuzziness.Auto).MinimumShouldMatch(2).FuzzyRewrite(MultiTermQueryRewrite.ConstantScoreBoolean).TieBreaker(1).Lenient())).Filter(f=>f.Term(t=>t.Field(d=>d.AccountKey).Value("123456789"))))).ScriptFields(sf => sf.ScriptField("datetime1",sc => sc.Source("doc['datetime1'].value == null?doc['datetime2'].value: doc['datetime1'].value"))).Source(true).Index("archive").From(0).Size(10000).Sort(s => s.Descending(a => a.CreateDate)));return result.Hits.Select(p=>p.Source).ToList();

}

关于排序

在本次的ES优化升级过程中,关于排序的操作可以说是很纠结的。按照业务要求,要根据两个时间类型的字段进行排序,如果某个为空,就按照不为空的排序,使得其排序结果达到穿插的效果,而不是像SQL语句那样order field1, field2的排序结果那样。

找出解决方案的过程很痛苦,因为官方的demo无法运行,经过多方尝试,终于在查看ElasticSearch源代码的情况下,找到了解决方案。

Github地址:https://github.com/elastic/elasticsearch/blob/master/server/src/main/java/org/elasticsearch/script/JodaCompatibleZonedDateTime.java,第411行

查询语句如下:

{

"from": 0,
"query": {"bool": {"filter": [{"term": {"UserId": {"value": "123456789"}}}]}
},
"size": 10,
"sort": [{"_script": {"script": {"source": "doc.DateTime1.empty?doc.DateTime2.value.toInstant().toEpochMilli():doc.DateTime1.value.toInstant().toEpochMilli()"},"type": "number","order": "desc"}}
]

}

C#参考代码如下:

var searchResponse = _elasticsearchClient.Search(s => s

                .Query(q => q.Bool(b => b.Filter(m => m.Term(t => t.Field(f => f.UserId).Value(userId)),m => m.QueryString(qs => qs.Fields(fieldList).Query(term.PreProcessQueryString()))))).Index(indexName).ScriptFields(sf => sf.Source(true).Sort(s=>s.Script(sr=>sr.Script(doc => doc.Source("doc.DateTime1.empty ? doc.DateTime2.value.toInstant().toEpochMilli() : doc.DateTime1.value.toInstant().toEpochMilli()")))).From(startIndex).Size(pageSize));

参考链接:

https://www.dazhuanlan.com/2020/02/13/5e44f118b75cb/ https://www.toutiao.com/i6824365055832752653

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/309926.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[C++11]字符串原始字面量

代码如下: #include <iostream> #include <string> using namespace std;int main() {string str1 R"(D:\hello\world\test.txt)";cout << str1 << endl;string str2 R"(dsdasasdasasda asdagdfhadagd)";cout << str2 &l…

ASP.NET Core on K8s学习之旅(13)Ocelot API网关接入

【云原生】| 作者/Edison Zhou这是恰童鞋骚年的第232篇原创文章上一篇介绍了Ingress的基本概念和Nginx Ingress的基本配置和使用&#xff0c;考虑到很多团队都在使用Ocelot作为API网关&#xff08;包括我司&#xff09;做了很多限流和鉴权的工作&#xff0c;因此本篇介绍一下如…

数据结构期末复习

1.完全二叉树的第5层有9个节点&#xff0c;该完全二叉树总计有多少个节点( B ). A.41 B.24 C.40 D.25 2.具有21个顶点的无向图至少有多少条边才能形成连通图 ( B ). A.21 B.20 C.22 D.21…

C++实现拓扑排序(vector模拟邻接表存储,优先队列实现)

代码如下: #include <iostream> #include <queue> #include <vector> using namespace std; const int N 10010; int in[N]; vector<int>v[N]; vector<int>print;//存放拓扑序列 int main() {int n, m;//n为点的个数&#xff0c;m为边的条数,点…

ASP.NET Core分布式项目实战(运行Consent Page)--学习笔记

任务21&#xff1a;运行Consent Page修改 Config.cs 中的 RequireConsent 为 true&#xff0c;这样登录的时候就会跳转到 Consent 页面修改 ConsentController 的 Index 为异步[HttpGet] public async Task<IActionResult> Index(string returnUrl) {var model await Bu…

[C++11]指针空值类型nullptr

代码如下: #include <iostream> using namespace std;void func(char *p) {cout << "void func(char *p)" << endl; }void func(int p) {cout << "void func(int p)" << endl; }int main() {func(10);func(NULL);return 0;…

[C++11]常量表达式函数

constexpr修饰函数。 普通函数/类成员函数。 1.函数必须要有返回值&#xff0c;并且return返回的表达式必须是常量表达式。 代码如下: #include <iostream> using namespace std;//error 不是常量表达式函数 constexpr void func1() {int a 200;cout << a &l…

Magicodes.IE Csv导入导出

说明本章主要说明如何使用Magicodes.IE.Csv进行Csv导入导出.主要步骤1.安装包Magicodes.IE.CsvInstall-Package Magicodes.IE.Csv2.使用Magicodes.IE.Csv导出Csv通过如下代码片段我们将导出的内容通过相应的特性做出相应的处理.ExporterHeaderAttributeDisplayName&#xff1a;…

微软 Build 2020开发者大会发来一张英雄帖,邀您速来赴约!

&#xff08;本文阅读时间&#xff1a;2 分钟&#xff09;微软 Build 2020开发者大会将于北京时间5 月 19 日晚上 23:00正式开启。大会将以Teams Live Event的形式面向全球开发者免费注册&#xff0c;并增设中文专家面对面&技术专场&#xff0c;方便开发者从48小时的连续技…

[最全操作指南] 在线六个项目全部迁移Linux

&#xff08;书山有路勤为径&#xff0c;学海无涯苦作舟&#xff09;开源也两年了&#xff0c;没想到自己在宣传.NetCore全栈的时候&#xff0c;也慢慢的做出了几个产品&#xff0c;毕竟也是一行一行的敲出来的&#xff0c;也是一天一夜的改出来的&#xff0c;希望每个人都能在…

[C++11]推荐使用auto的场景

推荐使用auto的场景&#xff1a; 1.用于STL的容器遍历。 代码如下: #include <string>#include <iostream> #include <map> using namespace std;int main() {map<int, string>mp;mp.insert(make_pair(1, "Tom"));mp.insert(make_pair(2,…

【壹刊】Azure AD 保护的 ASP.NET Core Web API (下)

一&#xff0c;引言上一节讲到如何在我们的项目中集成Azure AD 保护我们的API资源&#xff0c;以及在项目中集成Swagger&#xff0c;并且如何把Swagger作为一个客户端进行认证和授权去访问我们的WebApi资源的&#xff1f;本节就接着讲如何在我们的项目中集成 Azure AD 保护我们…

linux aspnet服务器,在Linux中安装ASPNET.Core3.0运行时的示例代码

摘要&#xff1a;# 以下示例适用于x64位runtime v3.0.0mkdir /runtimescd /runtimeswget https://... # 以下示例适用于x64位runtime v3.0.0mkdir /runtimescd /runtimeswget https://download.visualstudio.microsoft.com/download/pr/b0c44e05-b7a1-4221-94ec-a0c0d3a11eed/a…

And Then There Was One POJ - 3517(变形约瑟夫环+规律)

题意&#xff1a; 约瑟夫问题的变式。先指定第m个人必须死&#xff0c;然后每隔k个人死一个。求最后那个死的人的编号是什么。 题目 Let’s play a stone removing game. Initially, n stones are arranged on a circle and numbered 1, …, n clockwise (Figure 1). You a…

明明可以靠技术吃饭,现在却非要出来当编剧!

这是头哥侃码的第199篇原创前不久&#xff0c;有位读者深夜时在后台留言说&#xff1a;“我在某技术大会现场听过你的分享。”“说实话&#xff0c;第一次见你&#xff0c;真的很难想象一幅猛男外表的人竟然会有这样的经历和谈吐&#xff0c;这两点让我很钦佩。”这还不算完&am…

linux不能更改密码,Linux服务器无法更改密码的解决办法--passwd: User not known

上面仅是告知我&#xff0c;这些帐号并没有家目录&#xff0c;由于那些帐号绝大部分都是系统帐号&#xff0c; 确实也不需要家目录的&#xff0c;所以&#xff0c;那是‘正常的错误&#xff01;’,相对应的群组检查可以使用 grpck 这个指令。pwck 确保系统鉴认信息的完整性,pwc…

PowerBIDeskTop报表元数据批量更新(可用于翻译场景)

PowerBI在多国语言场景上有极大的缺陷&#xff0c;原有的Sqlserver的SSAS和Azure的AS模型层翻译功能&#xff0c;在Excel和PowerBIDeskTop客户端上均可完美适配。但到了PowerBI Pro的Service网页端时&#xff0c;竟然不支持。这个问题已经明确是官方给出的答复&#xff0c;起码…

[C++11]decltype在泛型编程中的使用举例

关于decltype的应用多出现在泛型编程中&#xff0c;比如我们编写一个类模板&#xff0c;在里面添加遍历容器的函数&#xff0c;操作如下: 代码如下: #include <iostream> #include <list>using namespace std;template<typename T> class Container { publ…

追了多年的开发框架,你还认识指针吗?

一&#xff1a;背景1. 讲故事高级语言玩多了&#xff0c;可能很多人对指针或者汇编都淡忘了&#xff0c;本篇就和大家聊一聊指针&#xff0c;虽然C#中是不提倡使用的&#xff0c;但你能说指针在C#中不重要吗&#xff1f;你要知道FCL内库中大量的使用指针&#xff0c;如String,E…

linux apt-get 安装 根目录,技术|apt-get 和 apt-cache 命令实例展示

apt-get和apt-cache是Ubuntu Linux中的命令行下的包管理工具。 apt-get的GUI版本是Synaptic包管理器。本篇中我们会展示apt-get和apt-cache命令的15个不同例子。示例&#xff1a;1 列出所有可用包linuxtechilocalhost:~$ apt-cache pkgnamesaccount-plugin-yahoojpceph-fusedvd…