前奏
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版本彻底移除了一个索引可以创建多个类型。
由于版本更新后,我们不可以再比喻成关系型数据库中的表,因为一个索引中只能创建一种类型。
安装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" : "<ALPHANUM>",
"position" : 0
},
{
"token" : "是",
"start_offset" : 7,
"end_offset" : 8,
"type" : "<IDEOGRAPHIC>",
"position" : 1
},
{
"token" : "啦",
"start_offset" : 9,
"end_offset" : 10,
"type" : "<IDEOGRAPHIC>",
"position" : 2
},
{
"token" : "啦",
"start_offset" : 10,
"end_offset" : 11,
"type" : "<IDEOGRAPHIC>",
"position" : 3
},
{
"token" : "啦",
"start_offset" : 11,
"end_offset" : 12,
"type" : "<IDEOGRAPHIC>",
"position" : 4
},
{
"token" : "啦",
"start_offset" : 12,
"end_offset" : 13,
"type" : "<IDEOGRAPHIC>",
"position" : 5
},
{
"token" : "啦",
"start_offset" : 13,
"end_offset" : 14,
"type" : "<IDEOGRAPHIC>",
"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
配置扩展词典和停用词典
网友评论