Elastic Search 浅浅认识 快速使用 keyword 和 text 的区别之处 spring boot 集成案例 es 增删改查

Posted T_Antry

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Elastic Search 浅浅认识 快速使用 keyword 和 text 的区别之处 spring boot 集成案例 es 增删改查相关的知识,希望对你有一定的参考价值。

很早就想写,严重拖延症,好在出品了。不完善,后续再补充。

文章目录


前言

____________.                    ___.        .__     __. .____________. .________    .___      ___.
|████████████|                   /    \\      |   \\   |  | |████████████| |   ___   \\   \\  \\    /  /
     |  |                       /  /\\  \\     |    \\  |  |      |  |      |  |    )  |   \\  \\  /  /
     |  |                      /  /__\\  \\    |  .   \\|  |      |  |      |  |___/   /    \\  \\/  /
     |  |                     /  ______  \\   |  |\\   `  |      |  |      |   ___  <       \\    /
     |  |     .____________. |  |      |  |  |  |  \\    |      |  |      |  |   \\  \\       |  |
     |__|     |████████████| /__/      \\__\\  |__|    \\__|      |__|      |__|    \\__\\      |__|

最近项目使用了 elastic search ,为了更好地认识这个组件,同时也是想记录部分笔记,采用了撰写博客的方式推动由浅入深的学习。本系列并不能解决所有使用遇到的问题,主要是为了学会在使用的过程中思考和解决遇到的问题,分享一些我遇到且注意到的内容,同时结交一群志同道合的朋友,我觉得这是一件有意义的事情。


喜欢可收藏,现在比较懒了,佛系出品。

什么是ES,为何使用ES?

es 课先理解为一种数据库,kibana是es引擎的一个可视化平台

部分加粗的字体都是后续章节需要挖掘的内容。

它是一个 分布式高扩展高实时搜索 与数据分析 引擎 。它能很方便地使大量数据具有搜索、分析和探索的能力。充分利用Elasticsearch的水平伸缩性,能使数据在生产环境变得更有价值。

Elasticsearch 的 实现原理 主要分为以下几个步骤:

  • 首先用户将数据提交到Elasticsearch 数据库中
  • 再通过分词控制器去将对应的语句 分词
  • 将其 权重分词 结果 一并存入数据
  • 当用户搜索数据时候,再根据权重将结果排名打分
  • 再将返回结果呈现给用户

Elasticsearch 是一个企业级海量数据的搜索引擎,提供了全文搜索的功能,适用于电商商品搜索、App搜索、企业内部信息搜索、IT系统搜索等。

如果你没有环境,请安装一个先吧,我始终相信实践才能够带来更为深刻的认知,踏出第一步安装。
es&kibana 安装教程 安装的部分,网上教程很多了。

dev tools 使用

上kibana感受一下,这其实就是发送CURL 命令的工具,例如要查看es版本,只要访问根路径就可以了。


既然是get请求,在浏览器的地址栏访问跟路径也可以达到同样的效果

http://localhost:9200 //localhost为安装es的主机ip port 默认9200 如有更改需要调整


如果有配置账号密码的

http://账号:密码@localhost:9200 

索引

创建索引

es的索引可以理解为数据库的表
创建一个user 索引user-index,其中包含三个属性:nameage , password

PUT /user-index

  "settings": 
		"number_of_shards": 1,
		"number_of_replicas": 1
	,
  "mappings": 
    "properties": 
      "name": 
        "type": "text",
        "fields": 
          "keyword": 
            "type": "keyword",
            "ignore_above": 256
          
        
      ,
      "age": 
        "type": "long"
      ,
      "password": 
        "type": "text"
      
    
  

setting 部分可以配置属性,属性分为两种

索引的配置项按是否可以更改分为静态属性动态属性,所谓的静态配置即索引创建后不能修改。

静态配置项作用其他
index.number_of_shards索引分片的数量默认值5,ES支持的最大分片数默认为1024
index.shard.check_on_startup分片在打开之前是否检查该分片是否损坏,当检测到分片损坏时,将阻止打开。可选值:(默认值):false:不检测 checksum:只检查物理结构 true:检查物理结构和路基损坏,相对比较消耗CPU
ndex.codec数据存储的压缩算法默认值LZ4,可选值best_compression,比LZ4可以获得更好的压缩比例(占用较小的磁盘空间,但是存储性能比LZ4低)。
index.routing_partition_size路由分区数如果设置了该参数,其路由算法为:(hash(_routing) + hash(_id) % index.routing_parttion_size ) % number_of_shards 如果没有设置,路由算法为:hash(_routing) % number_of_shardings _routing默认值为 _id

ES中分片是用来解决节点容量上限问题,通过主分片,可以将数据分布到集群的所有节点,作为测试用例,无需考虑这些问题配1足矣。

静态配置项作用其他
index.number_of_replicas索引复制分片的个数默认值1,一个副本分片只是一个主分片的拷贝。副本分片作为硬件故障时保护数据不丢失的冗余备份,并为搜索和返回文档等读操作提供服务。
index.auto_expand_replicas基于可用节点的数量自动分配副本数量默认为 false(即禁用此功能),可设置为:0-all
index.refresh_interval执行刷新操作的频率,该操作可以对索引的最新更改对搜索可见默认1s。可以设置-1禁止刷新
index.max_result_window控制分页搜索的总记录数from+size的大小不能超过该值,默认为10000
index.max_inner_result_window用于控制top aggregations默认100。内部命中和顶部命中聚合占用堆内存,并且时间与from+size成正比,这限制了内存
还有…配置的参数查看文档吧…这个部分其实不只是几个字这么简单,后面应该单独拿出来研究…

成功创建在dev tools右侧能够看到响应。

通过

GET /user-index

查看索引的信息

其中 aliases 是别名,别名的作用是,支持通过别名对索引进行查询,由于创建索引的时候没有配置别名,因此别名是空的。 mapping 是配置各个字段的数据类型,具体类型有哪些,一切以官方为准。

官方数据类型说明 官方文档非常全,百度翻译也很好。

不同: 相比数据库,新增数据时允许含没有预先配置的字段类型,甚至在创建索引时,不做任何配置。如:

PUT /user-index

删除索引

使用的时候就不要删除了,如已删除,请重新创建索引

DELETE /user-index

增删改查

新增/修改

POST /索引/类型/ID
类型默认为_doc
id不给的话能够默认生成
可见多写了个money字段,依然能够插入成功,应证了上面所介绍同数据库的不同。

POST /user-index/_doc/1

   "name": "Antry",
   "age":  44,
   "pasword":"antry44",
   "money":1000000000

给定id的方式,如果id已经存在,就会修改该条数据。

查询

GET /user-index/_search

查询结果


  "took" : 0,
  "timed_out" : false,
  "_shards" : 
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  ,
  "hits" : 
    "total" : 
      "value" : 1,
      "relation" : "eq"
    ,
    "max_score" : 1.0,
    "hits" : [
      
        "_index" : "user-index",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 1.0,
        "_source" : 
          "name" : "Antry",
          "age" : 44,
          "pasword" : "antry44",
          "money" : 1000000000
        
      
    ]
  

返回时有其他参数,这些参数很有意义,了解这些参数能够让我们进一步理解它的特性

参数说明
took表示整个搜索请求花费了多少毫秒
hits.total本次搜索,返回了几条结果
hits.max_score本次搜索的所有结果中,最大的相关度分数是多少。每一条document对于search的相关度,越相关,_score分数越大,排位越靠前。(计算这些分数是es的强大之一)
hits.hits默认查询出前10条数据,完整数据,_score降序排序
timeout默认无timeout,latency平衡completeness,手动指定timeout,timeout查询执行机制

由于新增时多给了money字段,再次查询一下mapping

GET /user-index/_mapping

结果


  "user-index" : 
    "mappings" : 
      "properties" : 
        "age" : 
          "type" : "long"
        ,
        "money" : 
          "type" : "long"
        ,
        "name" : 
          "type" : "text",
          "fields" : 
            "keyword" : 
              "type" : "keyword",
              "ignore_above" : 256
            
          
        ,
        "password" : 
          "type" : "text"
        
      
    
  

多了个money的字段,且自动识别为long类型
查询当然还有条件查询,有很多特性,还要后面详细介绍

删除

DELETE 索引/类型/ID

DELETE user-index/_doc/1

同样删除也是有条件删除

spring boot 集成

这里使用的客户端是RestHighLevelClient
pom
要注意的是版本一定要和安装的es版本相同

<dependency>
   <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
    <version>7.1.0</version>
    <scope>compile</scope>
</dependency>

yml
地址 端口 账号 密码这些信息一般不写死在配置类中,因为不同的环境,参数也不同

elasticSearch:
  hosts: localhost
  user: elastic
  password: elastic

配置类
将RestHighLevelClient 注入ioc


@Configuration
public class ElasticSearchClient 
    @Value("$elasticSearch.hosts")
    private String hosts;
    @Value("$elasticSearch.user")
    private String userName;
    @Value("$elasticSearch.password")
    private String password;

    @SuppressWarnings("deprecation")
    @Bean
    public RestHighLevelClient getClient() 
        String[] hosts = this.hosts.split(",");
        HttpHost[] httpHosts = new HttpHost[hosts.length];
        for(int i=0;i<hosts.length;i++) 
            httpHosts[i] = new HttpHost(hosts[i], 9200, "http");
        

        final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
        credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(userName, password));

        RestClientBuilder builder = RestClient.builder(httpHosts).setRequestConfigCallback(new RestClientBuilder.RequestConfigCallback() 
            @Override
            public RequestConfig.Builder customizeRequestConfig(RequestConfig.Builder requestConfigBuilder) 
                requestConfigBuilder.setConnectTimeout(-1);
                requestConfigBuilder.setSocketTimeout(-1);
                requestConfigBuilder.setConnectionRequestTimeout(-1);
                return requestConfigBuilder;
            
        ).setHttpClientConfigCallback(new RestClientBuilder.HttpClientConfigCallback() 
            @Override
            public HttpAsyncClientBuilder customizeHttpClient(HttpAsyncClientBuilder httpClientBuilder) 
                httpClientBuilder.disableAuthCaching();
                return httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
            
        );

        RestHighLevelClient client = new RestHighLevelClient(builder);
        return client;
    

测试类

/**
 * @author T_Antry
 * @date 2022-09-01 19:35
 */
@RunWith(SpringRunner.class)
@SpringBootTest
public class ESTest 

    @Autowired
    private RestHighLevelClient restHighLevelClient;


创建索引

//创建索引
@Test
public void testCreateIndex() throws IOException 
    CreateIndexRequest createIndexRequest = new CreateIndexRequest("java-user-index");
    CreateIndexResponse response = restHighLevelClient.indices().create(createIndexRequest, RequestOptions.DEFAULT);

删除索引

/**
 * 删除索引
 */
@Test
public void deleteIndex() throws IOException 
    DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest("java-user-index");
    AcknowledgedResponse delete = restHighLevelClient.indices().delete(deleteIndexRequest, RequestOptions.DEFAULT);
    System.out.println(delete.isAcknowledged());

索引是否存在

/**
* 索引是否存在
*/
@Test
public void testIsExist() throws IOException 
   GetRequest getRequest = new GetRequest("java-user-index", "1");
   //不获取返回的source的上下文
   getRequest.fetchSourceContext(new FetchSourceContext(false));
   getRequest.storedFields("_none_");
   boolean exists = restHighLevelClient.exists(getRequest, RequestOptions.DEFAULT);
   System.out.println(exists);

新增数据

/**
 *
 * 新增数据
 * @throws IOException
 */
@Test
public void createDocument() throws IOException 
    Map<String,Object> map = new HashMap<>();
    map.put("name","T_Antry");
    map.put("age",18);
    map.put("password","123455Antry");
    IndexRequest request = new IndexRequest("java-user-index");
    request.id("1");
    request.timeout(TimeValue.timeValueSeconds(1));
    //将我们的数据放入请求,json
    request.source(JSON.toJSONString(map), XContentType.JSON);
    //客服端发送请求
    IndexResponse response = restHighLevelClient.index(request, RequestOptions.DEFAULT);
    //对应我们的命令返回状态
    System.out.println(response.status());

批量新增Bulk

在新增数量非常大的情况下,每次新增都发起一次请求是需要大量连接的
因此es同样提供了bulk批量新增的功能,bulk的使用在大数据的场景更多。可以配置n条提交一次,或者一定时间提交一次,等多种可配条件。

/**
 * 批量插入数据
 * @throws IOException
 */
@Test
public void testBulkRequest() throws IOException 
    BulkRequest bulkRequest = new BulkRequest();
    bulkRequest.timeout("10s");
    for (int i = 0; i < 100; i++) 
        Map map = new HashMap<>();
        map.put("name","T_Antry"+i);
        map.put("age",i);
        map.put("password","Antry888888-"+i);
        bulkRequest.add(
                new IndexRequest("java-user-index")
                        .id("" + i + 1)
                        .source(JSON.toJSONString(map), XContentType.JSON)
        );
    
    BulkResponse bulk = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
    System.out.println(bulk.status());


查询

/**
 *
 * 查询
 * @throws IOException
 */
@Test
public void testGetDocument() throws IOException 
    GetRequest getRequest = new GetRequest("java-user-index", "1");
    GetResponse response = restHighLevelClient.get(getRequest, RequestOptions.DEFAULT);
    //打印文档信息
    System.out.println(response.getSourceAsString());
  

分页查询

/**
* 分页查询
* @throws IOException
*/
@Test
public void testPageRequest() throws IOException 
    SearchRequest searchRequest = new SearchRequest("java-user-index");
    SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
    //分页
    sourceBuilder.from(0);
    sourceBuilder.size(10);
    //执行搜索
    searchRequest.source(sourceBuilder);
    SearchResponse search = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
    //解析结果
    for (SearchHit hit : search.getHits().getHits()) 
        System.out.println(hit.getSourceAsMap());
    

条件查询

查询条件是id为1

/**
 * 条件查询
 * @throws IOException
 */
@Test
public void testCondRequest() throws IOException 
    SearchRequest searchRequest = new SearchRequest("java-user-index");
    SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
    TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("_id","1");
    sourceBuilder.query(termQueryBuilder);
    //执行搜索
    searchRequest.source(sourceBuilder);
    SearchResponse search = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
    //解析结果
    for (SearchHit hit : search.getHits().getHits()) 
        System.out.println(hit.getSourceAsMap());
    

我想有的朋友肯定还会尝试修改条件为name,password等
就会发现有查询不到的情况,这是因为创建索引时,没有指定对应字段为keyword的原因。keyword类型不分词,text类型分词

Keyword类型

对于keyword类型,由于Elasticsearch不会使用分析器对其进行分析,所以你输入什么文本,索引就会按照原样进行保存。下图为文本在倒排索引中存储的样子。
假设数据为T_Antry

TermcountDocument
T_Antry1example

Text类型

对于text类型,Elasticsearch会先使用分析器对文本进行分析,再存储到倒排索引中。Elasticsearch默认使用标准分析器(standard analyzer),先对文本分词再转化为小写。
标准分析器对文本进行分析后的结果

假设数据为Antry888888-1

TermcountDocument
antry8888881example
11example

这个结果并不是我瞎写的,是有依据的

从上面的结果,可以看出,在使用text的时候,由于被分词,特殊符号“-”似乎丢失,所以通过“Antry888888-1”去查询password字段时,什么都查不到,这个时候用antry888888却可以查到。

mapping的修改

  • 索引创建之后,mapping中已经存在的字段不可以修改其定义
  • 新增字段是可以的

如果一定要修改,百度一下,网友很强大。

全文检索

对所有字段进行搜索

/**
 * 全文检索
 * @throws IOException
 */
@Test
public void testCondRequest() throws IOException 
    SearchRequest searchRequest = new SearchRequest("java-user-index");
    SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
    QueryStringQueryBuilder queryStringQueryBuilder = QueryBuilders.queryStringQuery("Antry1");
    sourceBuilder.query(queryStringQueryBuilder);
    //执行搜索
    searchRequest.source(sourceBuilder);
    SearchResponse search = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
    //解析结果
    for (SearchHit hit : search.getHits().getHits()) 
        System.out.println(hit.getSourceAsMap());
    

其他条件查询

es的查询功能非常强大,模糊,分组等等。

修改

/**
*
* 修改
* @throws IOException
*/
@Test
public void testUpdateDocument() throws IOException 
   UpdateRequest request = new UpdateRequest("java-user-index", "1");
   request.timeout("1s");
   Map<String,Object> map = new HashMap<>();
   map.put("name","T_Antry");
   map.put("age",18);
   map.put("password","Antry123456");
   request.doc(JSON.toJSONString(map),XContentType.JSON);
   UpdateResponse update = restHighLevelClient.update(request, RequestOptions.DEFAULT);
   System.out.println(update.status());

删除

/**
*
* 删除文档
* @throws IOException
*/
@Test
public void testDeleteDocument() throws IOException 
   DeleteRequest request = new DeleteRequest("java-user-index", "1");
   request.timeout("10s");
   DeleteResponse update = restHighLevelClient.delete(request, RequestOptions.DEFAULT);
   System.out.println(update.status());

结束语

以上就是本期对 elastic search 的学习,喜欢就点个关注吧。

以上是关于Elastic Search 浅浅认识 快速使用 keyword 和 text 的区别之处 spring boot 集成案例 es 增删改查的主要内容,如果未能解决你的问题,请参考以下文章

Elastic Search (ES)

手把手教学:使用Elastic search和Kibana进行数据探索(Python语言)

Elastic Search和Kibana入门

Docker 部署 分布式搜索引擎 Elastic Search

Elastic Search - 它是否适合分析团队的DB?

微服务 分布式搜索引擎 Elastic Search RestAPI