ES

前奏

RestFul

全文检索

关于全文检索,我们需要知道哪些以下这些:

  • 只处理文本
  • 不处理语义(不是人工智能,检索:你是谁,只会出现关于你是谁三个关键字的相关内容,而不会出现回答)
  • 搜索时英文不区分大小写,可以尝试一下百度,大小写搜出来的东西是一样的
  • 结果列表有相关度排序

什么是ElasticSearch

简称ES,Elasticsearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。Elasticsearch是用Java语言开发的,并作为Apache许可条款下的开放源码发布,是一种流行的企业级搜索引擎。Elasticsearch用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。官方客户端在Java、.NET(C#)、PHP、Python、Apache Groovy、Ruby和许多其他语言中都是可用的。根据DB-Engines的排名显示,Elasticsearch是最受欢迎的企业搜索引擎,其次是Apache Solr,也是基于Lucene。

ES应用场景:支持站内搜索,主要以轻量级的json作为数据存储格式,这点和mongdb类似,但是在性能上要优于mongdb。同样也支持地理查询,还方便地理位置和文本混合查询。在统计、日志类的数据存储和分析,可视化这方面是引领者。

  • 国外:GitHub、维基百科在使用ES
  • 国内:百度、新浪、阿里巴巴也在使用

ES安装

1、安装jdk8

rpm -ivh jdk-8u181 -linux-x64.rpm

2、由于ES不能以Root用户进行安装,所以我们应该先创建普通用户和组

# 添加es组
groupadd es
# 添加用户 将用户szw指向es组
useradd szw -g es
# 设置用户的密码
passwd szw

3、我们这里为了学习方便,使用docker进行安装es和可视化工具。参考以下的博客进行docker方式安装:

https://www.cnblogs.com/gyyyblog/p/11596707.html

4、我们启动完es后,需要在服务器内部查看es是否启动成功。因为需要开启远程服务权限(不适用docker方式的安装需要配置,修改配置文件中的network.host)

[root@iZ2zed97t0sgast08g54toZ ~]# curl http://localhost:9200
{
  "name" : "6f95bf5faf11",
  "cluster_name" : "docker-cluster",# es比较特殊,单个即集群
  "cluster_uuid" : "TZto9fKBRQesKb7309Hqvw",
  "version" : {
    "number" : "7.3.0",
    "build_flavor" : "default",
    "build_type" : "docker",
    "build_hash" : "de777fa",
    "build_date" : "2019-07-24T18:30:11.767338Z",
    "build_snapshot" : false,
    "lucene_version" : "8.1.0",
    "minimum_wire_compatibility_version" : "6.8.0",
    "minimum_index_compatibility_version" : "6.0.0-beta1"
  },
  "tagline" : "You Know, for Search"
}

9200是es的web端口。9300是内部tcp访问端口。

值得注意的是,es默认的分配的运行内存是1g,我们这里可以在配置文件中进行修改256m,这样才可以使es与kibana同时运行!

就绪

接近实时:es是一个接近实时的搜索平台,这意味着,从索引一个文档直到这个文档能够被搜索到有一个轻微的延迟。
步骤:索引数据创建索引,直到可以被搜索,需要1s中左右。

索引

什么是索引?

便于理解,我们可以把一个索引理解成关系型数据库(如mysql)的一个库,我们在es中可以创建任意多个索引。索引必须全部都是小写字母,并且使唯一标识。

类型

在一个索引中,你可以定义一种或多个类型。一个类型就类似于mysql中的表。假设:你要开发一款网上购物系统,用户的信息保存为一个类型,商品的信息保存为一个类型。

映射

Mapping是ES中一个很重要的概念,相当于传统数据库中的约束(暂且这样比喻,实际是不正确的!),用于定义一个索引中的类型的数据的结构。我们可以手动的创建,ES也可以根据自动插入的数据自动创建type及mapping。mapping主要包括字段名、字段数据类型和字段索引类型。

文档

相当于关系型数据库中的一条记录,在es中是以json格式进行存储。

ES5.x的模型:一个索引可以定义多个类型,6.x之后的版本也可以兼容,但是不推荐,7、8版本彻底移除了一个索引可以创建多个类型。

类比.png

由于版本更新后,我们不可以再比喻成关系型数据库中的表,因为一个索引中只能创建一种类型。

安装Kibana

注意:由于学生机的配置比较低,无法同时运行es和kinana,至少需要3gb左右的运行内存才可以同时运行。

这里推荐使用在本地创建虚拟机再去安装Kibana。

正文

索引

创建索引
Put /szw # 注意我们的索引名必须是小写,可以写数字但是不推荐

返回结果:

{
  "acknowledged" : true,
  "shards_acknowledged" : true,
  "index" : "szw"
}

我们在创建索引的时候也可以去创建一些配置:

# 创建索引
PUT /szw
{
  "settings": {
    "number_of_replicas": 1,
    "number_of_shards": 1
  }
}
查看索引
# 查看索引
GET /_cat/indices

返回结果:

green open .kibana_task_manager         Phv_k8FHRuu4Rw-pu9zn1g 1 1     2 0  54.1kb   31.4kb
green open szw                          EDGWPYufRNWX6e9rG0jzeQ 1 1     0 0    566b     283b
green open .kibana_1                    EndCRzm-TKOSvWmUt1nVFA 1 1   116 1   1.9mb 1017.7kb

我们可以发现,在每个索引的开头含有green,其实还有另外两个red和yello,返回的就相当于一个表格,我们可以通过以下命令将详细的表格表头显示出来。

# 查看索引
GET /_cat/indices?v

返回结果:

health status index                        uuid                   pri rep docs.count docs.deleted store.size pri.store.size
green  open   test                         od_x1RJmSIGJjb_i6jQqYA   1   1          1            0      8.5kb          4.1kb
green  open   test2                        VhhcPIYfQDqppivy7uvJZA   1   1          1            0        7kb          3.5kb
green  open   .kibana_task_manager         Phv_k8FHRuu4Rw-pu9zn1g   1   1          2            0     54.1kb         31.4kb
green  open   szw                          EDGWPYufRNWX6e9rG0jzeQ   1   1          0            0       566b           283b

我们可以看到health,就代表该条索引的健康状态,index代表索引的名字,uuid代表唯一标识,pri代表分片数量。docs代表文档的相关属性。

green代表绿色:健壮

yello代表黄色:不健壮,但是可用

red代表索引不完整的,不可用的

删除索引

Delete /szw

返回结果:

{
  "acknowledged" : true
}

默认带的两个kibana的索引不要删除,删除后kibana不能使用,需要重启。

类型操作

创建szw索引中的person类型:

# 这是6版本的书写方式
PUT /szw
{
  "mappings": {
    "person":{
      "properties":{
        "id":{"type":"String"},
        "name":{"type":"String"},
        "age":{"type":"integer"},
        "bir":{"type":"date"}
      }
    }
  }
}
# 7版本的由于只能创建一个类型,并且没有了string类型
PUT /szw
{
  "mappings": {
      "properties":{
        "id":{"type":"keyword"},
        "name":{"type":"keyword"},
        "age":{"type":"integer"},
        "bir":{"type":"date"}
      }
    }
}

查看创建的索引以及类型中的映射:

Get /szw/_mapping

文档操作

# 文档操作:插入一条文档,put /索引/类型/1
PUT /szw/person/1
{
  "id":1,
  "name":"szw",
  "age":21,
  "bir":"1999-01-01"
}


# 我们也可以让rest中的1忽略不写,不过就要使用post请求,这样就意味先让es帮我们创建一个id,再去修改上面的值

POST /szw/person
{
  "id":2222,
  "name":"szw",
  "age":21,
  "bir":"1999-01-01"
}

返回的result:


{
  "_index" : "szw",
  "_type" : "person",
  "_id" : "3kjB1nUBPN-1jv0gnoHm", # 我们可以看到这里的id是自动生成的
  "_version" : 1,
  "result" : "created",
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 1,
  "_primary_term" : 1
}


# 查询文档中的一条记录

GET /szw/person/1


# 删除一条文档
DELETE /szw/person/1

# 更新一条文档

---1、这种情况会丢失原有的数据,先删除,再添加,我门再去查这个数据,未修改的数据就不存在了

POST /szw/person/1
{
  "name":"xiaomengzi"
}

--2、会保留原有的数据,并且如果你在更新的时候添加了新字段,不报错,es也会自动帮你生成,_update和doc都需要加上

POST /szw/person/4/_update
{
  "doc":{
    "name":"qqqq",
    "sex":"mele"
  }
}

--3、脚本更新

# 基于脚本的更新

POST /szw/person/4/_update
{
  "script":"ctx._source.age+=3"
}

批量操作

# 文档的批量操作 _bulk批量操作,index 添加、delete删除、update修改
PUT /szw/person/_bulk
{"index":{"_id":"10"}}
  {"name":"新增的文档","age":"18"}
{"delete":{"_id":"1"}}
{"update":{"_id":"4"}}
  {"doc":{"name":"hhhh","age":"222"}}

我们可以看一下返回的数据,操作的每条文档都会显示

#! Deprecation: [types removal] Specifying types in bulk requests is deprecated.
{
  "took" : 66,
  "errors" : false,
  "items" : [
    {
      "index" : {
        "_index" : "szw",
        "_type" : "person",
        "_id" : "10",
        "_version" : 1,
        "result" : "created",
        "_shards" : {
          "total" : 2,
          "successful" : 1,
          "failed" : 0
        },
        "_seq_no" : 11,
        "_primary_term" : 1,
        "status" : 201
      }
    },
    {
      "delete" : {
        "_index" : "szw",
        "_type" : "person",
        "_id" : "1",
        "_version" : 4,
        "result" : "deleted",
        "_shards" : {
          "total" : 2,
          "successful" : 1,
          "failed" : 0
        },
        "_seq_no" : 12,
        "_primary_term" : 1,
        "status" : 200
      }
    },
    {
      "update" : {
        "_index" : "szw",
        "_type" : "person",
        "_id" : "4",
        "_version" : 4,
        "result" : "updated",
        "_shards" : {
          "total" : 2,
          "successful" : 1,
          "failed" : 0
        },
        "_seq_no" : 13,
        "_primary_term" : 1,
        "status" : 200
      }
    }
  ]
}

我们可以看到,批量操作返回的并不是一条结果,而是多条结果。所以这个批量操作不是一个原子性的操作,删除、修改等各部分间不受影响,后续执行与之前没关系。

为什么不是原子操作?

因为es我们通常是用来做检索的,所以通常会弱化事务或者是根本没有事务。

高级搜索Query

要知道:全文检索:索 对文档创建索引的过程。检索:查询条件

检索的两种方式_search

es查询方式
  • 第一种适用于简单的查询,将参数直接挂在url上,但是无法书写复杂的查询
  • 第二种是将查询的条件以json的格式书写,这种格式会十分的公正

使用语法

# URL查询:get/索引/类型/_search/参数

# DSL查询: get/索引/类型/_search
{
    “query”
}

首先批量插入数据

PUT /szw/_bulk
{"index":{"_id":"11"}}
  {"name":"邵哈哈哈","age":"18","id":"2"}
{"index":{"_id":"10"}}
  {"name":"qqq","age":"18","id":"4"}
{"index":{"_id":"8"}}
  {"name":"szw","age":"19","id":"5"}
{"index":{"_id":"5"}}
  {"name":"ssss","age":"20","id":"8"}

# 也可以不指定索引文档id
PUT /szw/_bulk
{"index":{}}
  {"name":"随机的","age":"18","id":"2"}
{"index":{}}
  {"name":"随机","age":"18","id":"4"}
{"index":{}}
  {"name":"szw","age":"19","id":"5"}
{"index":{}}
  {"name":"ssss","age":"20","id":"8"}

URL检索

# 基于age进行排序,默认升序asc,des是降序
GET /szw/_search?q=*

GET /szw/_search?q=*&sort=age:des

# 我们可以将上面批量添加数据的id去除掉,这样可以批量添加很多条数据
# 再经过查询,我们可以发现每次es会给我们返回10条数据,这就很像分页了,那么我们如何做到es中的分页呢?

GET /szw/_search?q=*&sort=age:asc&size=2&from=3

# 上面这个就代表,按每页2条记录分页,查询第几条数据,(当前页-1)*size

GET /szw/_search?q=*&sort=age:asc&size=2&_source=name
# 上面这种查询的source代表 只返回name字段的数据

DSL格式查询

# dsl查询 match_all默认每页返回十条,不进行分页

GET /szw/_search
{
  "query": {
    "match_all": {}
  }
}

# del 查询带排序,排序不可以排text类型的字段,因为该类型的需要进行分词!
GET /szw/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "name": {
        "order": "desc"
      }
    },
     {
      "age": {
        "order": "desc"
      }
    }
  ]
}
# del查询 分页 size form,form作用是第几条开始查询
GET /szw/_search
{
  "query": {
    "match_all": {}
  },
  "size": 3,
  "from": 0
}

# 指定返回的字段返回
GET /szw/_search
{
  "query": {
    "match_all": {}
  },
  "_source": "name"
}


# 指定返回多个字段,_source中就要放入一个数组
GET /szw/_search
{
  "query": {
    "match_all": {}
  },
  "_source": ["name","age"]
}


返回值介绍

{
  "took" : 3,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 9,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "szw",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 1.0,
        "_source" : {
          "name" : "szw",
          "age" : 21
        }
      },
      }
    ]
  }
}

took:查询结果的毫秒ms数”
time_out:是否超时,成功查询到就是false
shards:分片
max_score:查询的最大分数,由于这里做的查询是查询所有,所以每个文档的分数都是1

重要!
基于关键词查询:

# 使用term查询  基于关键词

# 结果:可以查到
GET szw/_search
{
  "query": {"term": {
    "name": {
      "value": "邵哈哈哈"
    }
  }}
}


# 查不到结果
GET szw/_search
{
  "query": {"term": {
    "name": {
      "value": "哈哈"
    }
  }}
}


  • es中关键字查询中,用的是标准分词,中文是单字分词,英文是单词分词,但是要注意的是只有text类型才分词, 其他类型都是不分词的!上面查不到结果的原因就是这个!

查看默认的分词效果

GET _analyze
{
  "text":"ssssss 是 啦啦啦啦啦"
}

返回结果

我们可以看到返回结果ssss作为单词是没有分的,而中文都是按字分词的。

{
  "tokens" : [
    {
      "token" : "ssssss",
      "start_offset" : 0,
      "end_offset" : 6,
      "type" : "",
      "position" : 0
    },
    {
      "token" : "是",
      "start_offset" : 7,
      "end_offset" : 8,
      "type" : "",
      "position" : 1
    },
    {
      "token" : "啦",
      "start_offset" : 9,
      "end_offset" : 10,
      "type" : "",
      "position" : 2
    },
    {
      "token" : "啦",
      "start_offset" : 10,
      "end_offset" : 11,
      "type" : "",
      "position" : 3
    },
    {
      "token" : "啦",
      "start_offset" : 11,
      "end_offset" : 12,
      "type" : "",
      "position" : 4
    },
    {
      "token" : "啦",
      "start_offset" : 12,
      "end_offset" : 13,
      "type" : "",
      "position" : 5
    },
    {
      "token" : "啦",
      "start_offset" : 13,
      "end_offset" : 14,
      "type" : "",
      "position" : 6
    }
  ]
}

范围查询

rang :范围查询 age,大于等于10小于等于20,e代表等于,gt大于。lt小于

GET /szw/_search
{
  "query": {
    "range": {
      "age": {
        "gte": 10,
        "lte": 20
      }
    }
  },
  "_source": "age"
}

前缀查询

这种查询不管你是否分词,只要前缀匹配正确,就会返回结果

GET /szw/_search
{
  "query": {
    "prefix": {
      "name": {
        "value": "sz"
      }
    }
  }
}

通配符查询

wildcard:通配符查询,?代表匹配一个关键字,*代表匹配多个关键字。
注意:这种查询通配符不可以放在前面

GET szw/_search
{
  "query": {
    "wildcard": {
      "name": {
        "value": "s?"      # 这样的形式查不到name为szw的结果,而s*可以。
      }
    }
  }
}

多个ids查询

关键字:ids

GET szw/_search
{
  "query": {
    "ids": {
      "values": ["1","8","5"]
    }
  }
}

模糊查询

关键字:fuzzy
当输入的搜索词,长度小于等于2,不允许错误出现
长度为3-5,允许一个出错
长度为6或以上,允许两个出错

GET /szw/_search
{
  "query": {
    "fuzzy": {
      "name": "lsss"
    }
  }
}
# 该例子为搜索es中的name为ssss的文档时,允许出错一个数据,即lsss也可以搜索到name为ssss的数据。

布尔查询

关键字:bool、must_not、should、must

GET /szw/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "range": {
            "age": {
              "gte": 10,
              "lte": 20
            }
          }
        },
        {
          "prefix": {
            "name": {
              "value": "邵"
            }
          }
        }
      ]
    }
  }
}
# must是必须包含所有条件
must not是查询除了条件之外的所有结果
should是查询条件至少包含一个的结果

高亮查询

高亮查询不会影响原有的数据,而是附带上高亮数据返回出来

GET /szw/_search
{
  "query": {
    "fuzzy": {
      "name": {
        "value": "小红爱吃水"
      }
    },
  "highlight": {
    "fields": {
      "name":{}
    }
  }
}

# 我们还可以使用pre_tags和post_tags进行对高亮数据的渲染样式。默认只有斜体。

我们可以来看下查询数据:可以发现高亮的数据是单独分离出来进行显示的


数据

多字段分词查询

# 多字段分词查询

GET  szw/_search
{
  "query": {
    "multi_match": {
      "query": "小",
      "fields": ["name","date"]
    }
  }
}


# 和上面的无差别使用,但是这个可以选择分词器
GET szw/_search
{
  "query": {
    "query_string": {
      "default_field": "name",
      "query": "小红爱吃水果",
      "analyzer": ""
    }
  }
}

ES索引的底层原理

当我们在创建一条文档的时候,文档首先去建立索引,建立索引的时候就会进行分词(text类型),默认的分词器,英文按单词分词,中文按每个字分词。除了text类型,其他不分词,将文档中的数据处理完之后,保存到索引区,而完整的数据存放在元数据区,当我们进行搜索时,会先进行找索引区的索引,然后再根据索引命中元数据区中的文档,返回结果。另外图中的显示的索引区只是一个简化,索引区还会存放着文档相关的长度、等等数据。


es索引库原理

ik分词器

默认的分词器对于我们中文很不友好,我们可以使用ik分词器进行分词,首先我们应该先去安装ik分词器,然后才可以进行使用。

# 首先进入docker的es容器中

docker exec -it  容器号 bash


# 安装ik分词器,注意这里应该与自己es版本号对应
elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.3.0/elasticsearch-analysis-ik-7.3.0.zip 

# 安装结束后,进行重启容器

安装完ik分词器插件后,我们可以在es的根目录下的plugins看到ik。
进行测试:


# 测试ik分词器

GET /_analyze
{
  "text": "中科软科技股份有限公司",
  "analyzer": "ik_max_word"
}

那么对于网络热词,我们又该怎么处理呢?
我们可以参考下github上的教程:https://github.com/medcl/elasticsearch-analysis-ik
在config目录下的ik中进行配置。

-rw-rw---- 1 elasticsearch root     625 Nov 28 03:10 IKAnalyzer.cfg.xml
-rw-rw---- 1 elasticsearch root 5225922 Nov 28 03:10 extra_main.dic
-rw-rw---- 1 elasticsearch root   63188 Nov 28 03:10 extra_single_word.dic
-rw-rw---- 1 elasticsearch root   63188 Nov 28 03:10 extra_single_word_full.dic
-rw-rw---- 1 elasticsearch root   10855 Nov 28 03:10 extra_single_word_low_freq.dic
-rw-rw---- 1 elasticsearch root     156 Nov 28 03:10 extra_stopword.dic
-rw-rw---- 1 elasticsearch root 3058510 Nov 28 03:10 main.dic
-rw-rw---- 1 elasticsearch root     123 Nov 28 03:10 preposition.dic
-rw-rw---- 1 elasticsearch root    1824 Nov 28 03:10 quantifier.dic
-rw-rw---- 1 elasticsearch root     164 Nov 28 03:10 stopword.dic
-rw-rw---- 1 elasticsearch root     192 Nov 28 03:10 suffix.dic
-rw-rw---- 1 elasticsearch root     752 Nov 28 03:10 surname.dic

配置扩展词典和停用词典

你可能感兴趣的:(ES)