全文搜索是目前非常常见的需求,而开源的elasticsearch(以下简称es)更是全文搜索引擎的首选。github,维基百科,Stack Overflow,百度等都在使用。

es底层的开源库是Lucence,但是我们直接使用Lucence,还要再学习很多Lucene相关的知识,所以我们需要用一个高级语言来调用它。而elasticsearch就是使用java对Lucence的封装,提供restful API接口,开箱即用。当然不仅仅只是封装接口而已,还是一个分布式的近实时的文档存储一个分布式的近实时的分析搜索引擎能扩展上百个服务节点,支持PB级别的结构化和非结构化的数据

既然这么牛逼,那我们要做的就是,用它!用它!用它!

一、elasticsearch基本概念

Node、Cluster

es是一个分布式的数据库,可以多台服务器协同工作,每台服务器可以运行多个es实例,每个实例是一个Node,一组Node构成一个集群Cluster

Index

es会索引所有字段,经过处理后生成一个反向索引(Inverted Index)。查找数据就是直接查找该索引。

Index相当于Mysql的数据库,每个Index的名字必须是小写的。

// 查看当前节点的所有Index
curl -X GET 'http://localhost:9200/_cat/indices?v'

Document

Index里面单条记录称为 Document (文档)。同一个Index里的document,结构(scheme)相同,有利于提高搜索效率。

Type

Document可以分组,分组叫做Type。不同的Type里相同的字段应该有相同的结构。比如id在type1里是int,那么在type2里就不能是string

es 6.x版开始只允许每个Index包含一个Type,7.x之后的版本就彻底移除Type了,也就是现在我们要学习的版本就没有Type这个概念了,Index里面直接就是Document。

二、新建和删除Index

// 新建一个名为 task 的 Index
curl -X PUT 'http://localhost:9200/task'

// 服务器返回一个JSON对象
{
  "acknowledged":true, //操作成功
  "shards_acknowledged":true
}

// 删除Index
curl -X DELETE 'http://localhost:9200/task'

三、中文分词设置

安装中文分词插件,这里选择 IK,国内比较常用的,当然还有其他的。

安装好了,重启es,就会自动加载插件。

// 凡是需要搜索的中文字段,都需要单独设置一下

$ curl -X PUT 'localhost:9200/task' -d '
{
  "mappings": {
    "_doc": {
      "properties": {
        "user": {
          "type": "text",
          "analyzer": "ik_max_word",
          "search_analyzer": "ik_max_word"
        },
        "title": {
          "type": "text",
          "analyzer": "ik_max_word",
          "search_analyzer": "ik_max_word"
        },
        "desc": {
          "type": "text",
          "analyzer": "ik_max_word",
          "search_analyzer": "ik_max_word"
        }
      }
    }
  }
}

上面的代码里,会新建一个名为task的Index,里面有一个_doc的type,es6.x以后不支持新增其他的type了。_doc里有三个字段 usertitledesc

这三个字段都是中文,类型都是text,所以需要指定中文分词器,es的分词器称为 analyzersearch_analyzer是搜索词的分词器。ik_max_word是分词器插件ik提供,可以对文本进行最大数量的分词。

四、Document操作

4.1 新增记录

两种方式新增,

  • PUT指定id
  • POST不指定id
// PUT指定id新增记录
$ curl -X PUT 'localhost:9200/task/_doc/1' -d '
{
  "createdBy": "张三",
  "product": "BIZ",
  "category": "ROBOT"
}
// 返回json
{
  "_index" : "task",  //索引
  "_type" : "_doc",   //type
  "_id" : "1",        //记录的id
  "_version" : 1,     //记录更新的版本数,
  "result" : "created",  // 再次指定id PUT,会变成updated
  "_shards" : {      //_shards表示索引操作的复制过程的信息
    "total" : 2,  //指示应在其上执行索引操作的分片副本(主分片和副本分片)的数量
    "successful" : 1,  //表示索引操作成功的分片副本数
    "failed" : 0
  },
  "_seq_no" : 2,      //序号,从0编码,第三次就是2
  "_primary_term" : 1
}

一个document只会存放在一个shard里面,而一个shard有个replica,而我这只有个shard,没有replica,所有只有一个successful。

_primary_term:_primary_term也和_seq_no一样是一个整数,每当Primary Shard发生重新分配时,比如重启,Primary选举等,_primary_term会递增1。

_primary_term主要是用来恢复数据时处理当多个文档的_seq_no一样时的冲突,比如当一个shard宕机了,raplica需要用到最新的数据,就会根据_primary_term和_seq_no这两个值来拿到最新的document。

注:Index不能写错,写错不会es不会报错,会直接生成指定的index

// POST不指定id 新增记录
$ curl -X POST 'localhost:9200/task/_doc' -d '
{
  "createdBy": "李四",
  "product": "BIZ",
  "category": "ROBOT"
}
// 返回的json
{
  "_index" : "task",
  "_type" : "_doc",
  "_id" : "uJYweXIB6lu8kgtqGLDa",   //没有指定id,id就是一个自动生成的随机字符串
  "_version" : 1,
  "result" : "created",
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 5,
  "_primary_term" : 1
}

4.2 查看记录

// 根据id查看
$ curl -X GET 'localhost:9200/task/_doc/1'

// 返回的json
{
  "_index" : "task",
  "_type" : "_doc",
  "_id" : "1",
  "_version" : 5,
  "_seq_no" : 4,
  "_primary_term" : 1,
  "found" : true,
  "_source" : {
    "createdBy" : "张三",
    "product" : "BIZ",
    "category" : "ROBOT"
  }
}

4.3 更新记录

更新记录的操作跟上面使用 PUT 指定 id 新增记录的操作是一样的,PUT指定id,有就更新,没有就新增

4.4 删除记录

$ curl -X DELETE 'localhost:9200/task/_doc/1'

// 返回的json
{
  "_index" : "task",
  "_type" : "_doc",
  "_id" : "1",
  "_version" : 6,
  "result" : "deleted",
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 7,
  "_primary_term" : 1
}

五、数据查询

5.1 查询所有记录

// 使用GET请求
$ curl 'localhost:9200/task/_doc/_search'

上面这个请求会报错,! Deprecation: [types removal] Specifying types in search requests is deprecated.因为type被移除了,所有不需要指定type了,改成下面这个写法

// 使用GET请求
$ curl 'localhost:9200/task/_search'

{
  "took" : 0,  //耗时,单位毫秒
  "timed_out" : false,  //是否差超时
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 2,  // 共几条记录,默认10000条,需要返回更多要修改配置
      "relation" : "eq"
    },
    "max_score" : 1.0,  //最高的匹配程度
    "hits" : [  //命中的所有记录
      {
        "_index" : "task",
        "_type" : "_doc",
        "_id" : "uJYweXIB6lu8kgtqGLDa",
        "_score" : 1.0, // 匹配的程序,默认是按照这个字段降序排列
        "_source" : {
          "createdBy" : "李四",
          "product" : "BIZ",
          "category" : "ROBOT"
        }
      },
      {
        "_index" : "task",
        "_type" : "_doc",
        "_id" : "uZYxeXIB6lu8kgtqErDo",
        "_score" : 1.0,
        "_source" : {
          "createdBy" : "李四",
          "product" : "BIZ",
          "category" : "ROBOT"
        }
      }
    ]
  }
}

5.2 全文搜索

es的查询需要使用特定的查询语法,叫Elasticsearch-DSL(Domain-specific language),特定领域语言

单关键词,使用 Match 查询

//使用 match 查询
//查询createdBy中包含”三“的记录
GET /task/_search
{
  "query": {
    "match": {
      "createdBy": "三"
    }
  },
  "from": 1,    //偏移  
  "size": 100   //不指定默认返回10条结果  
}
//查询字段里可以放多个词,空格隔开,默认的operator是or
GET /task/_search
{
  "query": {
    "match": {
      "createdBy": "四 三"
    }
  }
}
// 查询createdBy同时包含"三 四"两个词
{
  "query": {
    "match": {
      "createdBy": {
        "query": "张三 李四",
        "operator": "and"
      }
    }
  }
}

多个关键词,必须使用 bool查询

GET /task/_search
{
  "query": {
    "bool": {
      "must": [
        {"match": {"createdBy": "张三"}},
        {"match": {"category": "ROBOT"}}
        ]
    }
  }
}
  • must:必须同时满足,
  • must_not:表示不满足
  • minimum_should_match:表示最小匹配度
GET /task/_search
{
  "bool": {
    "should": [
      { "term": { "body": "how"}},
      { "term": { "body": "not"}},
      { "term": { "body": "to"}},
      { "term": { "body": "be"}}
    ],
    "minimum_should_match": 3
  }
}