Elastic Search 浅浅认识 快速使用 keyword 和 text 的区别之处 spring boot 集成案例 es 增删改查 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

同样删除也是有条件删除

数据迁移

有时候因为mapping有问题
但是es的mapping创建完索引之后又不能直接修改
因此需要创建新的索引进行数据迁移

POST _reindex
{
  "source": {
    "index": "oldIndex",
    "size":1000,
    "query": {
      "match_all": {} 
    }
   },
  "dest": {
    "index": "newIndex"
  }
}

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 的学习,喜欢就点个关注吧。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值