八、elasticsearch在net6的使用

一、背景

  在酒店项目系统中,酒店数量庞大,而且需要模糊查询,根据搜索词模糊匹配酒店名,地址和描述;那么如果用sql里面的like问题在就在于1.数据量大存在搜索慢和数据库压力大的问题;2.拆分词部分要后端实现;3.实现起来复杂代码量多;所以用Elasticsearch来解决这些问题。

二、Elasticsearch介绍

  Elasticsearch是一个基于Lucene库的开源搜索引擎,它提供分布式的实时文件存储和搜索,可扩展性好,并且支持通过restful接口交互,数据以JSON格式展示。

  1.索引

  在ES中索引的概念和MySQL里面的概念不太一样,这里列出一下ES和MySQL的对应的概念

ElasticSearch

MySQL

Index

Document

Field

Mapping

表结构

  2.优势

优势 描述
分布式 可横向拓展,增加服务器可直接配置在急群中
高可用 提供了复制功能,具有容错机制,能自动发现新的或失败的节点,重组和重新平衡节点数据
易接入 json 格式的 RESTful 风格
全文检索 基于 lucene 的强大的全文检索能力
搜索快 索引被分为多个分片(Shard),利用多台服务器,使用了分而治之的思想提升处理效率

  3.效率快的es核心

  3.1分片

  分片其实就是把document以分片的形式进行存储,在创建索引时会默认自动创建分片数,也可以手动设置主片分片和副本个数,主片分担搜索压力,而副本则是承担高可用,以至于在集群环境下能更好的分摊服务器压力。es查询分为2个阶段是:分散阶段和合并阶段,简单理解为在从多个分片中找到数据后,聚合成一个数据集合返回;

  3.2倒排索引

意思是以字或词的方式记录有多少个符合的key值;在搜索时会进行搜索词与倒排索引的词匹配,匹配成功的会返回对应的key值,而相应的匹配度高的往前排。

下面简单来看一下是如何进行索引的

词组 id
我要去上学 1
我要去读书 2

 

进入倒排索引的变化

term docid
1,2
1,2
1,2
上学 1
读书 2

 

如上图所示,这样的索引结构可以快速的查找到我们需要的数据;

  3.3 doc values

  对于分组、聚合、排序等某些功能来说,倒排索引的方式并不是最佳选择,这类功能操作的是文档而不是词项,这个时候就得把倒排索引逆转过来成正排索引,

  这么做会有两个缺点:

  1.构建时间长

  2.内存占用大,易OutOfMemory,且影响垃圾回收   

  Lucene 4.0之后版本引入了doc values和额外的数据结构来解决上面得问题,目前有五种类型的doc values:NUMERIC、BINARY、SORTED、SORTED_SET、SORTED_NUMERIC,针对每种类型Lucene都有特定的压缩方法。   

  doc values是列式存储的正排索引,通过docID可以快速读取到该doc的特定字段的值,列式存储存储对于聚合计算有非常高的性能。

  3.4内存读取

  Elasticsearch是基于Lucene, 而Lucene被设计为可以利用操作系统底层机制来缓存内存数据结构。

三、使用场景

  1.全文检索,常用于搜索使用,类似于百度,谷歌;

  2.日志系统,常见的是elk;

  3.监控系统,利用 es 高性能查询的特性,收集系统的监控数据,近实时展现监控数据,同时也方便用户对监控数据进行关键字排查。

四、那么接下来我们在net6应该如何使用呢?

  1.引入依赖

nest

  2.创建连接

 1   public class ElasticSearchConnection
 2     {
 3         public static ElasticClient _esClient;        //private readonly ILogger_logger;        //public ElasticSearchConnection(ILoggerlogger)        //{        //    _logger = logger;        //}
 4 
 5         static  ElasticSearchConnection()
 6         {
 7             var url = AppHelper.ReadAppSettings("ES", "Url");
 8             var settings = new ConnectionSettings(new Uri(url));
 9             _esClient = new ElasticClient(settings);
10             
11 
12         }
13 
14     }
创建连接

  3.增删改查

  1 [Route("/api")]
  2     [ApiController]
  3     public class ESController : ControllerBase
  4     {
  5         /// <summary>
  6         /// 新增
  7         /// </summary>
  8         /// <param name="param"></param>
  9         [HttpPost("IndexAsync")]
 10         public async Task<dynamic> IndexAsync([FromBody] Product param)
 11         {
 12             var response = ElasticSearchConnection._esClient.Index(param, x => x.Index("producttest"));
 13             //var response = ElasticSearchConnection._esClient.Create(param, a => a.Index("producttest"));
 14             return response.IsValid ? Ok(response.Id) : StatusCode(500);
 15         }
 16         /// <summary>
 17         /// 查询单个
 18         /// </summary>
 19         /// <param name="param"></param>
 20         [HttpGet("GetAsync")]
 21         public async Task<dynamic> GetAsync(string id)
 22         {
 23             var response = ElasticSearchConnection._esClient.GetAsync<Product>(id, x => x.Index("producttest"));
 24             return response.Result.IsValid ? Ok(response.Result.Source) : NotFound();
 25         }
 26 
 27         /// <summary>
 28         /// 更新
 29         /// </summary>
 30         /// <param name="id"></param>
 31         /// <param name="value"></param>
 32         /// <returns></returns>
 33         [HttpPut("UpdateAsync")]
 34         public async Task<dynamic> UpdateAsync(string id, [FromBody] Product value)
 35         {
 36             var response = await ElasticSearchConnection._esClient.UpdateAsync<Product>(id, x => x.Index("producttest").Doc(value));
 37             return response.IsValid ? Ok(response.Id) : StatusCode(500);
 38         }
 39 
 40         /// <summary>
 41         /// 删除
 42         /// </summary>
 43         /// <param name="id"></param>
 44         /// <returns></returns>
 45         [HttpDelete("DeleteAsync")]
 46         public async Task<dynamic> DeleteAsync(string id)
 47         {
 48             var response = await ElasticSearchConnection._esClient.DeleteAsync<Product>(id, x => x.Index("producttest"));
 49             return response.IsValid;
 50         }
 51         /// <summary>
 52         /// 分页查询
 53         /// </summary>
 54         /// <param name="param"></param>
 55         [HttpPost("SearchAsync")]
 56         public async Task<dynamic> SearchAsync(string keyword,int From, int Size)
 57         {
 58             var request = new SearchRequest
 59             {
 60                 From = From,
 61                 Size = Size,
 62                 Query = new MatchQuery { Field = "name", Query = keyword } //||
 63                         //new MatchQuery { Field = "content", Query = keyword }
 64             };
 65             var response = await ElasticSearchConnection._esClient.SearchAsync<Product>(request);
 66             return Ok(response.Documents);
 67         }
 68       
 69         /// <summary>
 70         /// 查询单个
 71         /// </summary>
 72         /// <param name="param"></param>
 73         [HttpGet("Mapping")]
 74         public async Task<dynamic> Mapping()
 75         {
 76             var response1 = ElasticSearchConnection._esClient.IndexDocument("producttest");
 77             var response = ElasticSearchConnection._esClient.Map<Product>(m => m.Properties(p => p.Text(t => t.Name("timestamp"))).AutoMap());
 78             return response.IsValid ? Ok() : NotFound();
 79         }
 80         /// <summary>
 81         /// 第一次分页查询
 82         /// </summary>
 83         /// <param name="param"></param>
 84         [HttpPost("FirstSearchAsync")]
 85         public async Task<dynamic> FirstSearchAsync()
 86         {
 87             var from = 0;
 88             var size = 20;
 89             var response = await ElasticSearchConnection._esClient.SearchAsync<Product>(s => s
 90                
 91                 .From(from)
 92                 .Size(size)
 93                 .Index("producttest")
 94                 .Sort(ss => ss
 95                     .Descending(f => f.Timestamp)
 96                 ).Pretty()
 97             );
 98 
 99             return Ok(response.Documents);
100         }
101         /// <summary>
102         /// 深分页查询 根据上一页的id和时间戳来查询下也要
103         /// </summary>
104         /// <param name="param"></param>
105         [HttpPost("keywordSearchAsync")]
106         public dynamic keywordSearchAsync( int Size,  long timestamp)//string keyword,
107         {
108             //var mustQuerys = new List<Func<QueryContainerDescriptor<Product>, QueryContainer>>();
109             //if (!string.IsNullOrEmpty(keyword))
110             //    mustQuerys.Add(a => a.Term(t => t.Field(f => f.Name).Value(keyword)));
111             var response = ElasticSearchConnection._esClient.Search<Product>(a =>
112                a.Index("producttest")
113                    .Size(Size)
114                    //.Query(q => q.Bool(b => b.Must(mustQuerys)))
115                    .SearchAfter(timestamp)
116                    .Sort(s => s.Field(f => f.Timestamp, SortOrder.Descending)));
117             return Ok(response.Documents);
118         }
119         /// <summary>
120         /// 深分页查询 根据上一页的id和时间戳来查询下也要
121         /// </summary>
122         /// <param name="param"></param>
123         [HttpPost("SSearchAsync")]
124         public async Task<dynamic> SSearchAsync(int Size, string id, long timestamp)
125         {
126             var sorts = new List<ISort>();
127             sorts.Add(new FieldSort { Field = Infer.Field<Product>(p => p.Timestamp), Order = SortOrder.Descending });
128 
129             var searchRequest = new SearchRequest<Product>()
130             {
131                
132                 Size = Size,
133                 Version = true,
134                 Sort = sorts
135             };
136             
137             var searchResponse = await ElasticSearchConnection._esClient.SearchAsync<Product>(searchRequest);
138             return Ok(searchResponse.Documents);
139         }
140     }
增删改查

  下面我们看看效果

 

   4.分页注意

  在实际场景中药注意一个问题,就是分页的问题,分页问题存在深分页问题,如果使用size和form进行分页,数据量过大会出现内存溢出,原因是会通过所有数据遍历后进行取前几行;所以对于数据量大的数据一般不做跳转页面处理,用滚动分页处理,我们可以使用es的SearchAfter特性进行处理,这个特性是要先知道上一页的id,然后在下次查询时在SearchAfter中传入id即可知道下一页;

  5.酒店基础方案落地

  

  上面是实现的基础架构,具体实现细节就不贴出来

总结:

  ElasticSearch用处很多,我们如果使用云主机的情况下,可以直接购买es集群,这样就可以减少运维成本了。

 

posted @ 2022-08-08 13:29  冼润伟  阅读(606)  评论(0)    收藏  举报