ELK高级搜索(二)

文章目录

    • [7.Java api 文档管理](#7.Java api 文档管理)
      • [7.1 es技术特点](#7.1 es技术特点)
      • [7.2 获取数据](#7.2 获取数据)
      • [7.3 文档查询](#7.3 文档查询)
      • [7.4 文档新增](#7.4 文档新增)
      • [7.5 文档修改](#7.5 文档修改)
      • [7.6 文档删除](#7.6 文档删除)
      • [7.7 文档bulk](#7.7 文档bulk)
    • 8.图解es内部机制
      • [8.1 es分布式基础](#8.1 es分布式基础)
      • [8.2 分片shard、副本replica](#8.2 分片shard、副本replica)
      • [8.3 单node环境创建index](#8.3 单node环境创建index)
      • [8.4 多node环境replica shard](#8.4 多node环境replica shard)
      • [8.5 横向扩容](#8.5 横向扩容)
      • [8.6 es容错机制 master选举 replica容错 数据恢复](#8.6 es容错机制 master选举 replica容错 数据恢复)
    • 9.图解文档存储机制
      • [9.1 数据路由](#9.1 数据路由)
      • [9.2 文档增删改内部机制](#9.2 文档增删改内部机制)
      • [9.3 文档查询内部机制](#9.3 文档查询内部机制)
      • [9.4 bulk api奇特的json格式](#9.4 bulk api奇特的json格式)
    • 10.Mapping映射入门
      • [10.1 什么是mapping映射](#10.1 什么是mapping映射)
      • [10.2 精确匹配与全文搜索](#10.2 精确匹配与全文搜索)
      • [10.3 全文检索下倒排索引原理](#10.3 全文检索下倒排索引原理)
      • [10.4 分词器 analyzer](#10.4 分词器 analyzer)
      • [10.5 query string根据字段分词](#10.5 query string根据字段分词)
      • [10.6 mapping回顾总结](#10.6 mapping回顾总结)
      • [10.7 mapping的核心数据类型以及dynamic mapping](#10.7 mapping的核心数据类型以及dynamic mapping)
      • [10.8 手动管理mapping](#10.8 手动管理mapping)
      • [10.9 复杂数据类型](#10.9 复杂数据类型)

7.Java api 文档管理

7.1 es技术特点

1 es技术比较特殊,不像其他分布式、大数据,es代码层面很好写,难的是概念的理解。

2 es最重要的是他的rest api。跨语言的。在真实生产中,探查数据、分析数据,使用rest更方便。

7.2 获取数据

java api 文档 https://www.elastic.co/guide/en/elasticsearch/client/java-rest/7.3/java-rest-overview.html

  • low : 偏向底层。

  • high:高级封装。

1 导包

xml 复制代码
    <dependency>
        <groupId>org.elasticsearch.client</groupId>
        <artifactId>elasticsearch-rest-high-level-client</artifactId>
        <version>7.3.0</version>
        <exclusions>
            <exclusion>
                <groupId>org.elasticsearch</groupId>
                <artifactId>elasticsearch</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>org.elasticsearch</groupId>
        <artifactId>elasticsearch</artifactId>
        <version>7.3.0</version>
    </dependency>

2 代码

java 复制代码
    //获取连接客户端
    RestHighLevelClient client = new RestHighLevelClient(
            RestClient.builder(
                    new HttpHost("localhost", 9200, "http")));
    //构建请求
    GetRequest getRequest = new GetRequest("book", "1");
    // 执行
    GetResponse getResponse = client.get(getRequest, RequestOptions.DEFAULT);
    // 获取结果
    if (getResponse.isExists()) {
        long version = getResponse.getVersion();
        String sourceAsString = getResponse.getSourceAsString();//检索文档(String形式)
        System.out.println(sourceAsString);
    }

7.3 文档查询

1 导包

xml 复制代码
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
        <version>2.0.6.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
        <version>2.0.6.RELEASE</version>
    </dependency>

2 配置 application.yml

yaml 复制代码
spring:
  application:
    name: service-search
heima:
  elasticsearch:
    hostlist: 127.0.0.1:9200 #多个结点中间用逗号分隔

3 代码

java 复制代码
@SpringBootTest
@RunWith(SpringRunner.class)
//查询文档
@Test
public void testGet() throws IOException {
    //构建请求
    GetRequest getRequest = new GetRequest("test_post", "1");

    //========================可选参数 start======================
    //为特定字段配置_source_include
    //        String[] includes = new String[]{"user", "message"};
    //        String[] excludes = Strings.EMPTY_ARRAY;
    //        FetchSourceContext fetchSourceContext = new FetchSourceContext(true, includes, excludes);
    //        getRequest.fetchSourceContext(fetchSourceContext);

    //为特定字段配置_source_excludes
    //        String[] includes1 = new String[]{"user", "message"};
    //        String[] excludes1 = Strings.EMPTY_ARRAY;
    //        FetchSourceContext fetchSourceContext1 = new FetchSourceContext(true, includes1, excludes1);
    //        getRequest.fetchSourceContext(fetchSourceContext1);

    //设置路由
    //        getRequest.routing("routing");

    // ========================可选参数 end=====================


    //查询
    //同步查询
    GetResponse getResponse = client.get(getRequest, RequestOptions.DEFAULT);

    //异步查询
    //        ActionListener<GetResponse> listener = new ActionListener<GetResponse>() {
    //            //查询成功时的立马执行的方法
    //            @Override
    //            public void onResponse(GetResponse getResponse) {
    //                long version = getResponse.getVersion();
    //                String sourceAsString = getResponse.getSourceAsString();//检索文档(String形式)
    //                System.out.println(sourceAsString);
    //            }
    //
    //            //查询失败时的立马执行的方法
    //            @Override
    //            public void onFailure(Exception e) {
    //                e.printStackTrace();
    //            }
    //        };
    //        //执行异步请求
    //        client.getAsync(getRequest, RequestOptions.DEFAULT, listener);
    //        try {
    //            Thread.sleep(5000);
    //        } catch (InterruptedException e) {
    //            e.printStackTrace();
    //        }

    // 获取结果
    if (getResponse.isExists()) {
        long version = getResponse.getVersion();

        String sourceAsString = getResponse.getSourceAsString();//检索文档(String形式)
        System.out.println(sourceAsString);
        byte[] sourceAsBytes = getResponse.getSourceAsBytes();//以字节接受
        Map<String, Object> sourceAsMap = getResponse.getSourceAsMap();
        System.out.println(sourceAsMap);
    }else {

    }
}

7.4 文档新增

rest api

PUT test_post/_doc/2
{
  "user":"tomas",
  "postDate":"2019-07-18",
  "message":"trying out es1"
}

代码:

java 复制代码
@Test
public void testAdd() throws IOException {
    //1 构建请求
    IndexRequest request=new IndexRequest("test_posts");
    request.id("3");
    //=======================构建文档============================
    //构建方法1
    String jsonString="{\n" +
        "  \"user\":\"tomas J\",\n" +
        "  \"postDate\":\"2019-07-18\",\n" +
        "  \"message\":\"trying out es3\"\n" +
        "}";
    request.source(jsonString, XContentType.JSON);

    //        构建方法2
    //        Map<String,Object> jsonMap=new HashMap<>();
    //        jsonMap.put("user", "tomas");
    //        jsonMap.put("postDate", "2019-07-18");
    //        jsonMap.put("message", "trying out es2");
    //        request.source(jsonMap);

    //        构建方法3
    //        XContentBuilder builder= XContentFactory.jsonBuilder();
    //        builder.startObject();
    //        {
    //            builder.field("user", "tomas");
    //            builder.timeField("postDate", new Date());
    //            builder.field("message", "trying out es2");
    //        }
    //        builder.endObject();
    //        request.source(builder);
    
    //        构建方法4
    //        request.source("user","tomas",
    //                    "postDate",new Date(),
    //                "message","trying out es2");
    //
    //        ========================可选参数===================================
    //设置超时时间
    request.timeout(TimeValue.timeValueSeconds(1));
    request.timeout("1s");

    //自己维护版本号
    //        request.version(2);
    //        request.versionType(VersionType.EXTERNAL);

    //2 执行
    //同步
    IndexResponse indexResponse = client.index(request, RequestOptions.DEFAULT);
    //异步
    //        ActionListener<IndexResponse> listener=new ActionListener<IndexResponse>() {
    //            @Override
    //            public void onResponse(IndexResponse indexResponse) {
    //
    //            }
    //
    //            @Override
    //            public void onFailure(Exception e) {
    //
    //            }
    //        };
    //        client.indexAsync(request,RequestOptions.DEFAULT, listener);
    //        try {
    //            Thread.sleep(5000);
    //        } catch (InterruptedException e) {
    //            e.printStackTrace();
    //        }

    //3 获取结果
    String index = indexResponse.getIndex();
    String id = indexResponse.getId();
    //获取插入的类型
    if(indexResponse.getResult()== DocWriteResponse.Result.CREATED){
        DocWriteResponse.Result result=indexResponse.getResult();
        System.out.println("CREATED:"+result);
    }else if(indexResponse.getResult()== DocWriteResponse.Result.UPDATED){
        DocWriteResponse.Result result=indexResponse.getResult();
        System.out.println("UPDATED:"+result);
    }

    ReplicationResponse.ShardInfo shardInfo = indexResponse.getShardInfo();
    if(shardInfo.getTotal()!=shardInfo.getSuccessful()){
        System.out.println("处理成功的分片数少于总分片!");
    }
    if(shardInfo.getFailed()>0){
        for (ReplicationResponse.ShardInfo.Failure failure:shardInfo.getFailures()) {
            String reason = failure.reason();//处理潜在的失败原因
            System.out.println(reason);
        }
    }
}

7.5 文档修改

rest api

post /test_posts/_doc/3/_update 
{
   "doc": {
      "user":"tomas J"
   }
}

代码:

java 复制代码
@Test
public void testUpdate() throws IOException {
    //1 构建请求
    UpdateRequest request = new UpdateRequest("test_posts", "3");
    Map<String, Object> jsonMap = new HashMap<>();
    jsonMap.put("user", "tomas JJ");
    request.doc(jsonMap);
    //===============================可选参数==========================================
    //超时时间
    request.timeout("1s");

    //重试次数
    request.retryOnConflict(3);

    //设置在继续更新之前,必须激活的分片数
    //        request.waitForActiveShards(2);
    //所有分片都是active状态,才更新
    //        request.waitForActiveShards(ActiveShardCount.ALL);

    //2 执行
    //同步
    UpdateResponse updateResponse = client.update(request, RequestOptions.DEFAULT);
    
    //异步

    //3 获取数据
    updateResponse.getId();
    updateResponse.getIndex();
    //判断结果
    if (updateResponse.getResult() == DocWriteResponse.Result.CREATED) {
        DocWriteResponse.Result result = updateResponse.getResult();
        System.out.println("CREATED:" + result);
    } else if (updateResponse.getResult() == DocWriteResponse.Result.UPDATED) {
        DocWriteResponse.Result result = updateResponse.getResult();
        System.out.println("UPDATED:" + result);
    }else if(updateResponse.getResult() == DocWriteResponse.Result.DELETED){
        DocWriteResponse.Result result = updateResponse.getResult();
        System.out.println("DELETED:" + result);
    }else if (updateResponse.getResult() == DocWriteResponse.Result.NOOP){
        //没有操作
        DocWriteResponse.Result result = updateResponse.getResult();
        System.out.println("NOOP:" + result);
    }
}

7.6 文档删除

rest api

DELETE /test_posts/_doc/3

代码

java 复制代码
@Test
public void testDelete() throws IOException {
    //1 构建请求
    DeleteRequest request =new DeleteRequest("test_posts","3");
    //可选参数

    //2 执行
    DeleteResponse deleteResponse = client.delete(request, RequestOptions.DEFAULT);

    //3 获取数据
    deleteResponse.getId();
    deleteResponse.getIndex();
    
    DocWriteResponse.Result result = deleteResponse.getResult();
    System.out.println(result);
}

7.7 文档bulk

rest api

POST /_bulk
{"action": {"metadata"}}
{"data"}

代码

java 复制代码
@Test
public void testBulk() throws IOException {
    //1 创建请求
    BulkRequest request = new BulkRequest();
    //        request.add(new IndexRequest("post").id("1").source(XContentType.JSON, "field", "1"));
    //        request.add(new IndexRequest("post").id("2").source(XContentType.JSON, "field", "2"));

    request.add(new UpdateRequest("post","2").doc(XContentType.JSON, "field", "3"));
    request.add(new DeleteRequest("post").id("1"));

    //2 执行
    BulkResponse bulkResponse = client.bulk(request, RequestOptions.DEFAULT);

    for (BulkItemResponse itemResponse : bulkResponse) {
        DocWriteResponse itemResponseResponse = itemResponse.getResponse();

        switch (itemResponse.getOpType()) {
            case INDEX:
                IndexResponse indexResponse = (IndexResponse) itemResponseResponse;
                indexResponse.getId();
                System.out.println("index:" + indexResponse.getResult());
                break;
            case CREATE:
                IndexResponse createResponse = (IndexResponse) itemResponseResponse;
                indexResponse.getId();
                System.out.println("create:" + indexResponse.getResult());
                break;
            case UPDATE:
                UpdateResponse updateResponse = (UpdateResponse) itemResponseResponse;
                updateResponse.getIndex();
                System.out.println("update:" + updateResponse.getResult());
                break;
            case DELETE:
                DeleteResponse deleteResponse = (DeleteResponse) itemResponseResponse;
                System.out.println("delete:" + deleteResponse.getResult());
                break;
        }
    }
}

8.图解es内部机制

8.1 es分布式基础

8.1.1 es对复杂分布式机制的透明隐藏特性

  • 分布式机制:分布式数据存储及共享
  • 分片机制:数据存储到哪个分片,副本数据写入
  • 集群发现机制:cluster discovery。新启动es实例,自动加入集群
  • shard负载均衡:大量数据写入及查询,es会将数据平均分配
  • shard副本:新增副本数,分片重分配

8.1.2 Elasticsearch的垂直扩容与水平扩容

垂直扩容:使用更加强大的服务器替代老服务器。单机存储及运算能力有上线,且成本直线上升。如10t服务器1万,单个10T服务器可能20万。

水平扩容:采购更多服务器,加入集群。大数据。

8.1.3 增加或减少节点时的数据rebalance

新增或减少es实例时,es集群会将数据重新分配。

8.1.4 master节点

功能:

  • 创建删除节点
  • 创建删除索引

8.1.5 节点对等的分布式架构

  • 节点对等,每个节点都能接收所有的请求
  • 自动请求路由
  • 响应收集

8.2 分片shard、副本replica

8.2.1 shard&replica机制

(1)每个index包含一个或多个shard

(2)每个shard都是一个最小工作单元,承载部分数据,lucene实例,完整的建立索引和处理请求的能力

(3)增减节点时,shard会自动在nodes中负载均衡

(4)primary shard和replica shard,每个document肯定只存在于某一个primary shard以及其对应的replica shard中,不可能存在于多个primary shard

(5)replica shard是primary shard的副本,负责容错,以及承担读请求负载

(6)primary shard的数量在创建索引的时候就固定了,replica shard的数量可以随时修改

(7)primary shard的默认数量是1,replica默认是1,默认共有2个shard,1个primary shard,1个replica shard

注意:es7以前primary shard的默认数量是5,replica默认是1,默认有10个shard,5个primary shard,5个replica shard

(8)primary shard不能和自己的replica shard放在同一个节点上(否则节点宕机,primary shard和副本都丢失,起不到容错的作用),但是可以和其他primary shard的replica shard放在同一个节点上

8.3 单node环境创建index

(1)单node环境下,创建一个index,有3个primary shard,3个replica shard

(2)集群status是yellow

(3)这个时候,只会将3个primary shard分配到仅有的一个node上去,另外3个replica shard是无法分配的

(4)集群可以正常工作,但是一旦出现节点宕机,数据全部丢失,而且集群不可用,无法承接任何请求

PUT /test_index1
{
   "settings" : {
      "number_of_shards" : 3,
      "number_of_replicas" : 1
   }
}

8.4 多node环境replica shard

(1)replica shard分配:3个primary shard,3个replica shard,1 node

(2)primary ---> replica同步

(3)读请求:primary/replica

8.5 横向扩容

  • 分片自动负载均衡,分片向空闲机器转移
  • 每个节点存储更少分片,系统资源给与每个分片的资源更多,整体集群性能提高
  • 扩容极限:节点数大于整体分片数,则必有空闲机器
  • 超出扩容极限时,可以增加副本数,如设置副本数为2,总共3*3=9个分片。9台机器同时运行,存储和搜索性能更强,容错性更好
  • 容错性:只要一个索引的所有主分片在,集群就就可以运行

8.6 es容错机制 master选举 replica容错 数据恢复

以3分片,2副本数,3节点为例介绍。

  • master node宕机,自动master选举,集群为red
  • replica容错:新master将replica提升为primary shard,yellow
  • 重启宕机node,master copy replica到该node,使用原有的shard并同步宕机后的修改,green

9.图解文档存储机制

9.1 数据路由

9.1.1 文档存储如何路由到相应分片

一个文档,最终会落在主分片的一个分片上,到底应该在哪一个分片?这就是数据路由。

9.1.2 路由算法

shard = hash(routing) % number_of_primary_shards

哈希值对主分片数取模。

举例:

对一个文档经行crud时,都会带一个路由值 routing number。默认为文档_id(可能是手动指定,也可能是自动生成)

存储1号文档,经过哈希计算,哈希值为2,此索引有3个主分片,那么计算2%3=2,就算出此文档在P2分片上。

决定一个document在哪个shard上,最重要的一个值就是routing值,默认是_id,也可以手动指定,相同的routing值,每次过来,从hash函数中,产出的hash值一定是相同的

无论hash值是几,无论是什么数字,对number_of_primary_shards求余数,结果一定是在0~number_of_primary_shards-1之间这个范围内的。0,1,2。

9.1.3 手动指定 routing number

PUT /test_index/_doc/15?routing=num
{
  "num": 0,
  "tags": []
}

场景:在程序中,架构师可以手动指定已有数据的一个属性为路由值,好处是可以定制一类文档数据存储到一个分片中。缺点是设计不好,会造成数据倾斜。

所以,不同文档尽量放到不同的索引中。剩下的事情交给es集群自己处理。

9.1.4 主分片数量不可变

涉及到以往数据的查询搜索,所以一旦建立索引,主分片数不可变。

9.2 文档增删改内部机制

增删改可以看做update,都是对数据的改动。一个改动请求发送到es集群,经历以下四个步骤:

(1)客户端选择一个node发送请求过去,这个node就是coordinating node(协调节点)

(2)coordinating node,对document进行路由,将请求转发给对应的node(有primary shard)

(3)实际的node上的primary shard处理请求,然后将数据同步到replica node

(4)coordinating node,如果发现primary node和所有replica node都搞定之后,就返回响应结果给客户端

9.3 文档查询内部机制

1、客户端发送请求到任意一个node,成为coordinate node

2、coordinate node对document进行路由,将请求转发到对应的node,此时会使用round-robin随机轮询算法,在primary shard以及其所有replica中随机选择一个,让读请求负载均衡

3、接收请求的node返回document给coordinate node

4、coordinate node返回document给客户端

5、特殊情况:document如果还在建立索引过程中,可能只有primary shard有,任何一个replica shard都没有,此时可能会导致无法读取到document,但是document完成索引建立之后,primary shard和replica shard就都有了

9.4 bulk api奇特的json格式

json 复制代码
POST /_bulk
{"action": {"meta"}}\n
{"data"}\n
{"action": {"meta"}}\n
{"data"}\n

[
    {
        "action":{
            "method":"create"
        },
        "data":{
            "id":1,
            "field1":"java",
            "field1":"spring",
        }
    },
      {
        "action":{
            "method":"create"
        },
        "data":{
            "id":2,
            "field1":"java",
            "field1":"spring",
        }
    }       
]

1、bulk中的每个操作都可能要转发到不同的node的shard去执行

2、如果采用比较良好的json数组格式

允许任意的换行,整个可读性非常棒,读起来很爽,es拿到那种标准格式的json串以后,要按照下述流程去进行处理

(1)将json数组解析为JSONArray对象,这个时候,整个数据,就会在内存中出现一份一模一样的拷贝,一份数据是json文本,一份数据是JSONArray对象

(2)解析json数组里的每个json,对每个请求中的document进行路由

(3)为路由到同一个shard上的多个请求,创建一个请求数组。100请求中有10个是到P1

(4)将这个请求数组序列化

(5)将序列化后的请求数组发送到对应的节点上去

3、耗费更多内存,更多的jvm gc开销

我们之前提到过bulk size最佳大小的那个问题,一般建议说在几千条那样,然后大小在10MB左右,所以说,可怕的事情来了。假设说现在100个bulk请求发送到了一个节点上去,然后每个请求是10MB,100个请求,就是1000MB = 1GB,然后每个请求的json都copy一份为jsonarray对象,此时内存中的占用就会翻倍,就会占用2GB的内存,甚至还不止。因为弄成jsonarray之后,还可能会多搞一些其他的数据结构,2GB+的内存占用。

占用更多的内存可能就会积压其他请求的内存使用量,比如说最重要的搜索请求,分析请求,等等,此时就可能会导致其他请求的性能急速下降。

另外的话,占用内存更多,就会导致java虚拟机的垃圾回收次数更多,跟频繁,每次要回收的垃圾对象更多,耗费的时间更多,导致es的java虚拟机停止工作线程的时间更多。

4、现在的奇特格式

POST /_bulk
{ "delete": { "_index": "test_index",  "_id": "5" }} \n
{ "create": { "_index": "test_index",  "_id": "14" }}\n
{ "test_field": "test14" }\n
{ "update": { "_index": "test_index",  "_id": "2"} }\n
{ "doc" : {"test_field" : "bulk test"} }\n

(1)不用将其转换为json对象,不会出现内存中的相同数据的拷贝,直接按照换行符切割json

(2)对每两个一组的json,读取meta,进行document路由

(3)直接将对应的json发送到node上去

5、最大的优势在于,不需要将json数组解析为一个JSONArray对象,形成一份大数据的拷贝,浪费内存空间,尽可能地保证性能

10.Mapping映射入门

10.1 什么是mapping映射

概念:自动或手动为index中的_doc建立的一种数据结构和相关配置,简称为mapping映射。

插入几条数据,让es自动为我们建立一个索引

PUT /website/_doc/1
{
  "post_date": "2019-01-01",
  "title": "my first article",
  "content": "this is my first article in this website",
  "author_id": 11400
}

PUT /website/_doc/2
{
  "post_date": "2019-01-02",
  "title": "my second article",
  "content": "this is my second article in this website",
  "author_id": 11400
}
 
PUT /website/_doc/3
{
  "post_date": "2019-01-03",
  "title": "my third article",
  "content": "this is my third article in this website",
  "author_id": 11400
}

对比数据库建表语句

create table website(
     post_date date,
     title varchar(50),     
     content varchar(100),
     author_id int(11) 
 );

动态映射:dynamic mapping,自动为我们建立index,以及对应的mapping,mapping中包含了每个field对应的数据类型,以及如何分词等设置。

重点:也可以手动在创建数据之前,先创建index,以及对应的mapping

GET  /website/_mapping/
{
  "website" : {
    "mappings" : {
      "properties" : {
        "author_id" : {
          "type" : "long"
        },
        "content" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        },
        "post_date" : {
          "type" : "date"
        },
        "title" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        }
      }
    }
  }
}

尝试各种搜索

GET /website/_search?q=2019        0条结果             
GET /website/_search?q=2019-01-01           1条结果
GET /website/_search?q=post_date:2019-01-01     1条结果
GET /website/_search?q=post_date:2019          0 条结果

搜索结果为什么不一致,因为es自动建立mapping的时候,设置了不同的field不同的data type。不同的data type的分词、搜索等行为是不一样的。所以出现了_all field和post_date field的搜索表现完全不一样。

10.2 精确匹配与全文搜索

10.2.1 exact value 精确匹配

2019-01-01,exact value,搜索的时候,必须输入2019-01-01,才能搜索出来

如果你输入一个01,是搜索不出来的

select * from book where name= 'java'

10.2.2 full text 全文检索

搜"笔记电脑",笔记本电脑词条会不会出现。

select * from book where name like '%java%'

(1)缩写 vs. 全称:cn vs. china

(2)格式转化:like liked likes

(3)大小写:Tom vs tom

(4)同义词:like vs love

2019-01-01,2019 01 01,搜索2019,或者01,都可以搜索出来

china,搜索cn,也可以将china搜索出来

likes,搜索like,也可以将likes搜索出来

Tom,搜索tom,也可以将Tom搜索出来

like,搜索love,同义词,也可以将like搜索出来

就不是说单纯的只是匹配完整的一个值,而是可以对值进行拆分词语后(分词)进行匹配,也可以通过缩写、时态、大小写、同义词等进行匹配。深入 NPL,自然语义处理。

10.3 全文检索下倒排索引原理

doc1:I really liked my small dogs, and I think my mom also liked them.

doc2:He never liked any dogs, so I hope that my mom will not expect me to liked him.

分词,初步的倒排索引的建立

term doc1 doc2
I * *
really *
liked * *
my * *
small *
dogs *
and *
think *
mom * *
also *
them *
He *
never *
any *
so *
hope *
that *
will *
not *
expect *
me *
to *
him *

演示了一下倒排索引最简单的建立的一个过程

搜索

mother like little dog,不可能有任何结果

mother

like

little

dog

这不是我们想要的结果。同义词mom\mother在我们人类看来是一样。想进行标准化操作。

重建倒排索引

normalization正规化,建立倒排索引的时候,会执行一个操作,也就是说对拆分出的各个单词进行相应的处理,以提升后面搜索的时候能够搜索到相关联的文档的概率。时态的转换,单复数的转换,同义词的转换,大小写的转换

mom ―> mother

liked ―> like

small ―> little

dogs ―> dog

重新建立倒排索引,加入normalization,再次用mother liked little dog搜索,就可以搜索到了

term doc1 doc2 normalization
I * *
really *
like * * liked ―> like
my * *
little * small ―> little
dog * dogs ―> dog
and *
think *
mother * * mom ―> mother
also *
them *
He *
never *
any *
so *
hope *
that *
will *
not *
expect *
me *
to *
him *

重新搜索

搜索:mother liked little dog,

对搜索条件经行分词 normalization

mother

liked -> like

little

dog

doc1和doc2都会搜索出来

10.4 分词器 analyzer

10.4.1 什么是分词器 analyzer

作用:切分词语,normalization(提升recall召回率)

给你一段句子,然后将这段句子拆分成一个一个的单个的单词,同时对每个单词进行normalization(时态转换,单复数转换)

recall,召回率:搜索的时候,增加能够搜索到的结果的数量

analyzer 组成部分:

1、character filter:在一段文本进行分词之前,先进行预处理,比如说最常见的就是,过滤html标签(hello --> hello),& --> and(I&you --> I and you)

2、tokenizer:分词,hello you and me --> hello, you, and, me

3、token filter:lowercase,stop word,synonymom,dogs --> dog,liked --> like,Tom --> tom,a/the/an --> 干掉,mother --> mom,small --> little

stop word 停用词: 了 的 呢。

一个分词器,很重要,将一段文本进行各种处理,最后处理好的结果才会拿去建立倒排索引。

10.4.2 内置分词器的介绍

例句:Set the shape to semi-transparent by calling set_trans(5)

standard analyzer标准分词器:set, the, shape, to, semi, transparent, by, calling, set_trans, 5(默认的是standard)

simple analyzer简单分词器:set, the, shape, to, semi, transparent, by, calling, set, trans

whitespace analyzer:Set, the, shape, to, semi-transparent, by, calling, set_trans(5)

language analyzer(特定的语言的分词器,比如说,english,英语分词器):set, shape, semi, transpar, call, set_tran, 5

官方文档:

https://www.elastic.co/guide/en/elasticsearch/reference/7.4/analysis-analyzers.html

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3YzSYWgr-1693035792655)(E:/2023最新Java学习路线图/学习资料/第4阶段---中间键&服务框架/10、ELK搜索技术Elasticsearch/资料-ELK高级搜索/资料/ELK高级搜索/img/1568978200919.png)]

10.5 query string根据字段分词

10.5.1 query string分词

query string必须以和index建立时相同的analyzer进行分词

query string对exact value和full text的区别对待

如: date:exact value 精确匹配

​ text: full text 全文检索

10.5.2 测试分词器

GET /_analyze
{
  "analyzer": "standard",
  "text": "Text to analyze 80"
}

返回值:

{
  "tokens" : [
    {
      "token" : "text",
      "start_offset" : 0,
      "end_offset" : 4,
      "type" : "<ALPHANUM>",
      "position" : 0
    },
    {
      "token" : "to",
      "start_offset" : 5,
      "end_offset" : 7,
      "type" : "<ALPHANUM>",
      "position" : 1
    },
    {
      "token" : "analyze",
      "start_offset" : 8,
      "end_offset" : 15,
      "type" : "<ALPHANUM>",
      "position" : 2
    },
    {
      "token" : "80",
      "start_offset" : 16,
      "end_offset" : 18,
      "type" : "<NUM>",
      "position" : 3
    }
  ]
}

token 实际存储的term 关键字

position 在此词条在原文本中的位置

start_offset/end_offset字符在原始字符串中的位置

10.6 mapping回顾总结

(1)往es里面直接插入数据,es会自动建立索引,同时建立对应的mapping。(dynamic mapping)

(2)mapping中就自动定义了每个field的数据类型

(3)不同的数据类型(比如说text和date),可能有的是exact value,有的是full text

(4)exact value,在建立倒排索引的时候,分词的时候,是将整个值一起作为一个关键词建立到倒排索引中的;full text,会经历各种各样的处理,分词,normaliztion(时态转换,同义词转换,大小写转换),才会建立到倒排索引中

(5)同时呢,exact value和full text类型的field就决定了,在一个搜索过来的时候,对exact value field或者是full text field进行搜索的行为也是不一样的,会跟建立倒排索引的行为保持一致;比如说exact value搜索的时候,就是直接按照整个值进行匹配,full text query string,也会进行分词和normalization再去倒排索引中去搜索

(6)可以用es的dynamic mapping,让其自动建立mapping,包括自动设置数据类型;也可以提前手动创建index和tmapping,自己对各个field进行设置,包括数据类型,包括索引行为,包括分词器,等

10.7 mapping的核心数据类型以及dynamic mapping

10.7.1 核心的数据类型

string:text and keyword

byte,short,integer,long,float,double

boolean

date

详见:https://www.elastic.co/guide/en/elasticsearch/reference/7.3/mapping-types.html

下图是ES7.3核心的字段类型如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NLfk6Mzf-1693035792656)(E:/2023最新Java学习路线图/学习资料/第4阶段---中间键&服务框架/10、ELK搜索技术Elasticsearch/资料-ELK高级搜索/资料/ELK高级搜索/img/1568989192034.png)]

10.7.2 dynamic mapping 推测规则

true or false --> boolean

123 --> long

123.45 --> double

2019-01-01 --> date

"hello world" --> text/keywod

10.7.3 查看mapping

GET /index/_mapping/

10.8 手动管理mapping

10.8.1 查询所有索引的映射

GET /_mapping

10.8.2 创建映射 !!

创建索引后,应该立即手动创建映射

PUT book/_mapping
{
	"properties": {
           "name": {
                  "type": "text"
            },
           "description": {
              "type": "text",
              "analyzer":"english",
              "search_analyzer":"english"
           },
           "pic":{
             "type":"text",
             "index":false
           },
           "studymodel":{
             "type":"text"
           }
    }
}

Text 文本类型

1)analyzer

通过analyzer属性指定分词器

上边指定了analyzer是指在索引和搜索都使用english,如果单独想定义搜索时使用的分词器则可以通过search_analyzer属性

2)index

index属性指定是否索引

默认为index=true,即要进行索引,只有进行索引才可以从索引库搜索到

但是也有一些内容不需要索引,比如:商品图片地址只被用来展示图片,不进行搜索图片,此时可以将index设置为false

删除索引,重新创建映射,将pic的index设置为false,尝试根据pic去搜索,结果搜索不到数据

3)store

是否在source之外存储,每个文档索引后会在 ES中保存一份原始文档,存放在"_source"中,一般情况下不需要设置store为true,因为在_source中已经有一份原始文档了

测试

PUT book/_mapping
{
		"properties": {
           "name": {
                  "type": "text"
            },
           "description": {
              "type": "text",
              "analyzer":"english",
              "search_analyzer":"english"
           },
           "pic":{
             "type":"text",
             "index":false
           },
           "studymodel":{
             "type":"text"
           }
    }
}

插入文档:

PUT /book/_doc/1
{
  "name":"Bootstrap开发框架",
  "description":"Bootstrap是由Twitter推出的一个前台页面开发框架,在行业之中使用较为广泛。此开发框架包含了大量的CSS、JS程序代码,可以帮助开发者(尤其是不擅长页面开发的程序人员)轻松的实现一个不受浏览器限制的精美界面效果。",
  "pic":"group1/M00/00/01/wKhlQFqO4MmAOP53AAAcwDwm6SU490.jpg",
  "studymodel":"201002"
}

Get /book/_search?q=name:开发

Get /book/_search?q=description:开发

Get /book/_search?q=pic:group1/M00/00/01/wKhlQFqO4MmAOP53AAAcwDwm6SU490.jpg

Get /book/_search?q=studymodel:201002

通过测试发现:name和description都支持全文检索,pic不可作为查询条件

keyword关键字字段

目前已经取代了"index": false。上边介绍的text文本字段在映射时要设置分词器,keyword字段为关键字字段,通常搜索keyword是按照整体搜索,所以创建keyword字段的索引时是不进行分词的,比如:邮政编码、手机号码、身份证等。keyword字段通常用于过虑、排序、聚合等。

date日期类型

日期类型不用设置分词器。

通常日期类型的字段用于排序。

format

通过format设置日期格式

例子:

下边的设置允许date字段存储年月日时分秒、年月日及毫秒三种格式。

{

​ "properties": {

​ "timestamp": {

​ "type": "date",

​ "format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd"

​ }

​ }

}

插入文档:

Post book/doc/3

{

"name": "spring开发基础",

"description": "spring 在java领域非常流行,java程序员都在用。",

"studymodel": "201001",

"pic":"group1/M00/00/01/wKhlQFqO4MmAOP53AAAcwDwm6SU490.jpg",

"timestamp":"2018-07-04 18:28:58"

}

数值类型

下边是ES支持的数值类型

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e6CLOsAx-1693035792657)(E:/2023最新Java学习路线图/学习资料/第4阶段---中间键&服务框架/10、ELK搜索技术Elasticsearch/资料-ELK高级搜索/资料/ELK高级搜索/img/1568990520717.png)]

1、尽量选择范围小的类型,提高搜索效率

2、对于浮点数尽量用比例因子,比如一个价格字段,单位为元,我们将比例因子设置为100这在ES中会按 分 存储,映射如下:

"price": {
        "type": "scaled_float",
        "scaling_factor": 100
  },

由于比例因子为100,如果我们输入的价格是23.45则ES中会将23.45乘以100存储在ES中。

如果输入的价格是23.456,ES会将23.456乘以100再取一个接近原始值的数,得出2346。

使用比例因子的好处是整型比浮点型更易压缩,节省磁盘空间。

如果比例因子不适合,则从下表选择范围小的去用:

更新已有映射,并插入文档:

PUT book/doc/3
{
"name": "spring开发基础",
"description": "spring 在java领域非常流行,java程序员都在用。",
"studymodel": "201001",
 "pic":"group1/M00/00/01/wKhlQFqO4MmAOP53AAAcwDwm6SU490.jpg",
 "timestamp":"2018-07-04 18:28:58",
 "price":38.6
}

10.8.3 修改映射

只能创建index时手动建立mapping,或者新增field mapping,但是不能update field mapping。

因为已有数据按照映射早已分词存储好。如果修改,那这些存量数据怎么办。

新增一个字段mapping

PUT /book/_mapping/
{
  "properties" : {
    "new_field" : {
      "type" :    "text",
     "index":    "false"
    }
  }
}

如果修改mapping,会报错

PUT /book/_mapping/
{
  "properties" : {
    "studymodel" : {
     "type" :    "keyword"
    }
  }
}

返回:

{
  "error": {
    "root_cause": [
      {
        "type": "illegal_argument_exception",
        "reason": "mapper [studymodel] of different type, current_type [text], merged_type [keyword]"
      }
    ],
    "type": "illegal_argument_exception",
    "reason": "mapper [studymodel] of different type, current_type [text], merged_type [keyword]"
  },
  "status": 400
}

10.8.4 删除映射

通过删除索引来删除映射

delete book

10.9 复杂数据类型

10.9.1 multivalue field

{ "tags": [ "tag1", "tag2" ]}

建立索引时与string是一样的,数据类型不能混

10.9.2 empty field

null,[],[null]

10.9.3 object field

PUT /company/_doc/1
{
  "address": {
    "country": "china",
    "province": "guangdong",
    "city": "guangzhou"
  },
  "name": "jack",
  "age": 27,
  "join_date": "2019-01-01"
}

address:object类型

查询映射

GET /company/_mapping
{
  "company" : {
    "mappings" : {
      "properties" : {
        "address" : {
          "properties" : {
            "city" : {
              "type" : "text",
              "fields" : {
                "keyword" : {
                  "type" : "keyword",
                  "ignore_above" : 256
                }
              }
            },
            "country" : {
              "type" : "text",
              "fields" : {
                "keyword" : {
                  "type" : "keyword",
                  "ignore_above" : 256
                }
              }
            },
            "province" : {
              "type" : "text",
              "fields" : {
                "keyword" : {
                  "type" : "keyword",
                  "ignore_above" : 256
                }
              }
            }
          }
        },
        "age" : {
          "type" : "long"
        },
        "join_date" : {
          "type" : "date"
        },
        "name" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        }
      }
    }
  }
}

object

{
  "address": {
    "country": "china",
    "province": "guangdong",
    "city": "guangzhou"
  },
  "name": "jack",
  "age": 27,
  "join_date": "2017-01-01"
}

底层存储格式

{
    "name":            [jack],
    "age":          [27],
    "join_date":      [2017-01-01],
    "address.country":         [china],
    "address.province":   [guangdong],
    "address.city":  [guangzhou]
}

对象数组:

{
    "authors": [
        { "age": 26, "name": "Jack White"},
        { "age": 55, "name": "Tom Jones"},
        { "age": 39, "name": "Kitty Smith"}
    ]
}

存储格式:

{
    "authors.age":    [26, 55, 39],
    "authors.name":   [jack, white, tom, jones, kitty, smith]
}
相关推荐
2301_806131367 小时前
elk日志分析系统
elk
必叫你大败而归2 天前
ELK日志分析系统
elk
確定饿的猫2 天前
ELK环境部署
elk
YCyjs2 天前
ELK 企业级日志分析系统
elk
码农郁郁久居人下2 天前
ELK 企业级日志分析系统
elk
史努比.4 天前
ELK 企业级日志分析系统
elk
檀越剑指大厂4 天前
【Elasticsearch系列四】ELK Stack
大数据·elk·elasticsearch
2401_840192274 天前
ELFK日志分析平台,架构和通信
elk·elasticsearch·架构
ygqygq26 天前
ElK 8 收集 Nginx 日志
nginx·elk
纪佰伦9 天前
ELK在Linux服务器下使用docker快速部署(超详细)
linux·服务器·elk