全文检索工具elasticsearch:第一章:理论知识

2022-09-28 14:52:24 浏览数 (2)

搜索

什么是搜索, 计算机根据用户输入的关键词进行匹配,从已有的数据库中摘录出相关的记录反馈给用户。 

常见的全网搜索引擎,像百度、谷歌这样的。但是除此以外,搜索技术在垂直领域也有广泛的使用,比如淘宝、京东搜索商品,万芳、知网搜索期刊,csdn中搜索问题贴。也都是基于海量数据的搜索。

如何处理搜索

****用传统关系性数据库

弊端:  

1、 对于传统的关系性数据库对于关键词的查询,只能逐字逐行的匹配,性能非常差。

2、匹配方式不合理,比如搜索“小密手机” ,如果用like进行匹配, 根本匹配不到。但是考虑使用者的用户体验的话,除了完全匹配的记录,还应该显示一部分近似匹配的记录,至少应该匹配到“手机”。

专业 全文索引是怎么处理的

     全文搜索引擎目前主流的索引技术就是倒排索引的方式。

****传统的保存数据的方式都是

      记录→单词

而倒排索引的保存数据的方式是

   单词→记录

例如

  搜索“红海行动”

但是数据库中保存的数据如图:

那么搜索引擎是如何能将两者匹配上的呢?

基于分词技术构建倒排索引:

首先每个记录保存数据时,都不会直接存入数据库。系统先会对数据进行分词,然后以倒排索引结构保存。如下:

然后等到用户搜索的时候,会把搜索的关键词也进行分词,会把“红海行动”分词分成:红海和行动两个词。

这样的话,先用红海进行匹配,得到id=1和id=2的记录编号,再用行动匹配可以迅速定位id为1,3的记录。

那么全文索引通常,还会根据匹配程度进行打分,显然1号记录能匹配的次数更多。所以显示的时候以评分进行排序的话,1号记录会排到最前面。而2、3号记录也可以匹配到。

****全文检索工具elasticsearch

********lucene与elasticsearch

咱们之前讲的处理分词,构建倒排索引,等等,都是这个叫lucene的做的。那么能不能说这个lucene就是搜索引擎呢?

还不能。lucene只是一个提供全文搜索功能类库的核心工具包,而真正使用它还需要一个完善的服务框架搭建起来的应用。

好比lucene是类似于jdk,而搜索引擎软件就是tomcat 的。

目前市面上流行的搜索引擎软件,主流的就两款,elasticsearch和solr,这两款都是基于lucene的搭建的,可以独立部署启动的搜索引擎服务软件。由于内核相同,所以两者除了服务器安装、部署、管理、集群以外,对于数据的操作,修改、添加、保存、查询等等都十分类似。就好像都是支持sql语言的两种数据库软件。只要学会其中一个另一个很容易上手。

从实际企业使用情况来看,elasticSearch的市场份额逐步在取代solr,国内百度、京东、新浪都是基于elasticSearch实现的搜索功能。国外就更多了 像维基百科、GitHub、Stack Overflow等等也都是基于ES的

********elasticSearch的使用场景

  1. 为用户提供按关键字查询的全文搜索功能。
  2. 著名的ELK框架(ElasticSearch,Logstash,Kibana),实现企业海量日志的处理分析的解决方案。大数据领域的重要一份子。

********elasticSearch的安装

全文检索工具elasticsearch:第二章:安装配置

********e lasticsearch 的基本概念

cluster

整个elasticsearch 默认就是集群状态,整个集群是一份完整、互备的数据。

node

集群中的一个节点,一般只一个进程就是一个node

shard

分片,即使是一个节点中的数据也会通过hash算法,分成多个片存放,默认是5片。

index

相当于rdbms的database, 对于用户来说是一个逻辑数据库,虽然物理上会被分多个shard存放,也可能存放在多个node中。

type

类似于rdbms的table,但是与其说像table,其实更像面向对象中的class , 同一Json的格式的数据集合。

document

类似于rdbms的 row、面向对象里的object

field

相当于字段、属性

利用ki b ana学习 elasti csearch restful api (DSL)

执行bin目录下的kibana程序:

cd /opt/kibana-5.6.4-linux-x86_64/bin

 ./kibana

************es中保存的数据结构

public class  Movie { String id;     String name;     Double doubanScore;     List<Actor> actorList;} public class Actor{String id;String name;}

这两个对象如果放在关系型数据库保存,会被拆成2张表,但是elasticsearch是用一个json来表示一个document。

所以他保存到es中应该是:

{  “id”:”1”,  “name”:”operation red sea”,  “doubanScore”:”8.5”,  “actorList”:[  {“id”:”1”,”name”:”zhangyi”},{“id”:”2”,”name”:”haiqing”},{“id”:”3”,”name”:”zhanghanyu”}]}

对数据的操作增删改查

查看es中有哪些索引

GET /_cat/indices?v

es 中会默认存在一个名为.kibana的索引

代码语言:javascript复制
health status index   uuid                   pri rep docs.count docs.deleted store.size pri.store.size
yellow open   .kibana sBDZ-v6YQMWx9GaQOmSQQg   1   1          1            0      3.2kb          3.2kb

表头的含义

health

green(集群完整) yellow(单点正常、集群不完整) red(单点不正常)

status

是否能使用

index

索引名

uuid

索引统一编号

pri

主节点几个

rep

从节点几个

docs.count

文档数

docs.deleted

文档被删了多少

store.size

整体占空间大小

pri.store.size

主节点占

增加一个索引

PUT /movie_index

代码语言:javascript复制
{
  "acknowledged": true,
  "shards_acknowledged": true,
  "index": "movie_index"
}

删除一个索引

      ES 是不删除也不修改任何数据

DELETE /movie_index

代码语言:javascript复制
{
  "acknowledged": true
}

新增文档****

格式 :PUT /index/type/id

PUT /movie_index/movie/1{ "id":1,  "name":"operation red sea",  "doubanScore":8.5,  "actorList":[  {"id":1,"name":"zhang yi"},{"id":2,"name":"hai qing"},{"id":3,"name":"zhang han yu"}]}PUT /movie_index/movie/2{  "id":2,  "name":"operation meigong river",  "doubanScore":8.0,  "actorList":[  {"id":3,"name":"zhang han yu"}]} PUT /movie_index/movie/3{  "id":3,  "name":"incident red sea",  "doubanScore":5.0,  "actorList":[  {"id":4,"name":"zhang chen"}]}

如果之前没建过index或者type,es 会自动创建。

********直接用id查找

GET movie_index/movie/1

**********修改 ** 整体替换

和新增没有区别

PUT /movie_index/movie/3{  "id":"3",  "name":"incident red sea",  "doubanScore":"5.0",  "actorList":[  {"id":"1","name":"zhang chen"}]}

修改 —某个字段

POST movie_index/movie/3/_update{  "doc": {    "doubanScore":"7.0"  }}

**修改 —某个字段和 修改 ** 整体替换二者选一,否则:

删除一个document

DELETE movie_index/movie/3

代码语言:javascript复制
{
  "found": true,
  "_index": "movie_index",
  "_type": "movie",
  "_id": "3",
  "_version": 18,
  "result": "deleted",
  "_shards": {
    "total": 2,
    "successful": 1,
    "failed": 0
  }
}

搜索type全部数据

GET movie_index/movie/_search

结果

{  "took": 2,    //耗费时间 毫秒  "timed_out": false, //是否超时  "_shards": {    "total": 5,   //发送给全部5个分片    "successful": 5,    "skipped": 0,    "failed": 0  },  "hits": {    "total": 3,  //命中3条数据    "max_score": 1,   //最大评分    "hits": [  // 结果      {        "_index": "movie_index",        "_type": "movie",        "_id": 2,        "_score": 1,        "_source": {          "id": "2",          "name": "operation meigong river",          "doubanScore": 8.0,          "actorList": [            {              "id": "1",              "name": "zhang han yu"            }          ]        }          。。。。。。。。          。。。。。。。。      }

按条件查询(全部)

GET movie_index/movie/_search{  "query":{    "match_all": {}  }}

按分词查询****

GET movie_index/movie/_search{  "query":{    "match": {"name":"red"}  }}

注意结果的评分

按分词子属性查询****

GET movie_index/movie/_search{  "query":{    "match": {"actorList.name":"zhang"}  }}

 结果:

代码语言:javascript复制
{
  "took": 2,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": 2,
    "max_score": 1,
    "hits": [
      {
        "_index": "movie_index",
        "_type": "movie",
        "_id": "2",
        "_score": 1,
        "_source": {
          "id": 2,
          "name": "operation meigong river",
          "doubanScore": 8,
          "actorList": [
            {
              "id": 3,
              "name": "zhang han yu"
            }
          ]
        }
      },
      {
        "_index": "movie_index",
        "_type": "movie",
        "_id": "1",
        "_score": 1,
        "_source": {
          "id": 1,
          "name": "operation red sea",
          "doubanScore": 8.5,
          "actorList": [
            {
              "id": 1,
              "name": "zhang yi"
            },
            {
              "id": 2,
              "name": "hai qing"
            },
            {
              "id": 3,
              "name": "zhang han yu"
            }
          ]
        }
      }
    ]
  }
}

match phrase

GET movie_index/movie/_search{    "query":{      "match_phrase": {"name":"operation red"}    }}

按短语查询,不再利用分词技术,直接用短语在原始数据中匹配

我就不发结果了,太长的博客也不好看。

********fuzzy查询

GET movie_index/movie/_search{    "query":{      "fuzzy": {"name":"rad"}    }}

校正匹配分词,当一个单词都无法准确匹配,es通过一种算法对非常接近的单词也给与一定的评分,能够查询出来,但是消耗更多的性能。

过滤--查询后过滤

GET movie_index/movie/_search{    "query":{      "match": {"name":"red"}    },    "post_filter":{      "term": {        "actorList.id": 3      }    }}

先查询后过滤效率慢,好比,我先从全国所有人中先过滤其他省份的留下广东的,再查询比先查询全国所有人再过滤广东的

过滤--查询前过滤(推荐)

GET movie_index/movie/_search{    "query":{        "bool":{          "filter":[ {"term": {  "actorList.id": "1"  }},                     {"term": {  "actorList.id": "3"  }}           ],           "must":{"match":{"name":"red"}}         }    }}

过滤--按范围过滤

GET movie_index/movie/_search{   "query": {     "bool": {       "filter": {         "range": {            "doubanScore": {"gte": 8}         }       }     }   }}

关于范围操作符:

gt

大于

lt

小于

gte

大于等于

lte

小于等于

排序

GET movie_index/movie/_search{  "query":{    "match": {"name":"red sea"}  }  , "sort": [    {      "doubanScore": {        "order": "desc"      }    }  ]}

这个先按名称后按red sea排序

分页查询

GET movie_index/movie/_search{  "query": { "match_all": {} },  "from": 1,  "size": 1}

指定查询的字段

GET movie_index/movie/_search{  "query": { "match_all": {} },  "_source": ["name", "doubanScore"]}

太多了,但我坚持,希望尽最大努力把我知道的全部写出来。

高亮

GET movie_index/movie/_search{    "query":{      "match": {"name":"red sea"}    },    "highlight": {      "fields": {"name":{} }    }  }

聚合

取出每个演员共参演了多少部电影

GET movie_index/movie/_search{  "aggs": {    "groupby_actor": {      "terms": {        "field": "actorList.name.keyword"        }    }  }}

每个演员参演电影的平均分是多少,并按评分排序

GET movie_index/movie/_search{  "aggs": {    "groupby_actor_id": {      "terms": {        "field": "actorList.name.keyword" ,        "order": {          "avg_score": "desc"          }      },      "aggs": {        "avg_score":{          "avg": {            "field": "doubanScore"          }        }       }    }  }}

关于mapping的类型

之前说type可以理解为table,那每个字段的数据类型是如何定义的呢

查看看mapping

GET movie_index/_mapping/movie

代码语言:javascript复制
{
  "movie_index": {
    "mappings": {
      "movie": {
        "properties": {
          "actorList": {
            "properties": {
              "id": {
                "type": "long"
              },
              "name": {
                "type": "text",
                "fields": {
                  "keyword": {
                    "type": "keyword",
                    "ignore_above": 256
                  }
                }
              }
            }
          },
          "doubanScore": {
            "type": "float"
          },
          "id": {
            "type": "long"
          },
          "name": {
            "type": "text",
            "fields": {
              "keyword": {
                "type": "keyword",
                "ignore_above": 256
              }
            }
          }
        }
      }
    }
  }
}

实际上每个type中的字段是什么数据类型,由mapping定义。

但是如果没有设定mapping系统会自动,根据一条数据的格式来推断出应该的数据格式。

  1. true/false → boolean
  2. 1020  →  long
  3. 20.1 → double
  4. “2017-02-01” → date
  5. “hello world” → text keyword

默认只有text会进行分词,keyword是不会分词的字符串。

mapping除了自动定义,还可以手动定义,但是只能对新加的、没有数据的字段进行定义。一旦有了数据就无法再做修改了。

注意:虽然每个Field的数据放在不同的type下,但是同一个名字的Field在一个index下只能有一种mapping定义。

中文分词

elasticsearch本身自带的中文分词,就是单纯把中文一个字一个字的分开,根本没有词汇的概念。但是实际应用中,用户都是以词汇为条件,进行查询匹配的,如果能够把文章以词汇为单位切分开,那么与用户的查询条件能够更贴切的匹配上,查询速度也更加快速。

分词器下载网址:https://github.com/medcl/elasticsearch-analysis-ik

安装

下载好的zip包,请解压后放到 /usr/share/elasticsearch/plugins/

 cp /opt/elasticsearch-analysis-ik-5.6.4.zip  elastic

unzip elastic

rm elastic

然后重启es

service elasticsearch stop

service elasticsearch start

测试使用

使用默认

GET movie_index/_analyze{    "text": "我是中国人"}

    请观察结果

 使用分词器

GET movie_index/_analyze{  "analyzer": "ik_smart",  "text": "我是中国人"}

请观察结果

   另外一个分词器

    ik_max_word

GET movie_index/_analyze{  "analyzer": "ik_max_word",  "text": "我是中国人"}

请观察结果

能够看出不同的分词器,分词有明显的区别,所以以后定义一个type不能再使用默认的mapping了,要手工建立mapping, 因为要选择分词器。

基于中文分词搭建索引

1、建立mapping

PUT movie_chn{  "mappings": {    "movie":{      "properties": {        "id":{          "type": "long"        },        "name":{          "type": "text"          , "analyzer": "ik_smart"        },        "doubanScore":{          "type": "double"        },        "actorList":{          "properties": {            "id":{              "type":"long"            },            "name":{              "type":"keyword"            }          }        }      }    }  }}

插入数据

PUT /movie_chn/movie/1{ "id":1,  "name":"红海行动",  "doubanScore":8.5,  "actorList":[    {"id":1,"name":"张译"},  {"id":2,"name":"海清"},  {"id":3,"name":"张涵予"} ]}PUT /movie_chn/movie/2{  "id":2,  "name":"湄公河行动",  "doubanScore":8.0,  "actorList":[  {"id":3,"name":"张涵予"}]} PUT /movie_chn/movie/3{  "id":3,  "name":"红海事件",  "doubanScore":5.0,  "actorList":[  {"id":4,"name":"张晨"}]}

查询测试

GET /movie_chn/movie/_search{  "query": {    "match": {      "name": "红海战役"    }  }} GET /movie_chn/movie/_search{  "query": {    "term": {      "actorList.name": "张译"    }  }}

自定义词库

修改/usr/share/elasticsearch/plugins/ik/config/中的IKAnalyzer.cfg.xml

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"><properties>        <comment>IK Analyzer 扩展配置</comment>        <!--用户可以在这里配置自己的扩展字典 -->        <entry key="ext_dict"></entry>         <!--用户可以在这里配置自己的扩展停止词字典-->        <entry key="ext_stopwords"></entry>        <!--用户可以在这里配置远程扩展字典 -->         <entry key="remote_ext_dict">http://192.168.67.163/fenci/myword.txt</entry>        <!--用户可以在这里配置远程扩展停止词字典-->        <!-- <entry key="remote_ext_stopwords">words_location</entry> --></properties>

按照标红的路径利用nginx发布静态资源

在nginx.conf中配置

server {        listen  80;        server_name  192.168.67.163;        location /fenci/ {           root es;    }   }

并且在/usr/local/nginx/下建/es/fenci/目录,目录下加myword.txt

myword.txt中编写关键词,每一行代表一个词。

然后重启es服务器,重启nginx。

在kibana中测试分词效果

更新完成后,es只会对新增的数据用新词分词。历史数据是不会重新分词的。如果想要历史数据重新分词。需要执行:

POST movies_index_chn/_update_by_query?conflicts=proceed

有点长了,分章节吧,第三章继续写。

1 人点赞