八、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集群,这样就可以减少运维成本了。

浙公网安备 33010602011771号