[关闭]
@dungan 2021-04-14T06:06:07.000000Z 字数 17989 阅读 182

Elasticsearch

Elasticsearch 基础


Elasticsearch 是一个开源的高扩展的分布式全文检索引擎,全文搜索引擎的技术原理被称为倒排索引(Inverted index),其基本原理是建立单词到文档的索引。之所以被称为倒排索引,是和正排索引相对的,正排索引 基本原理是建立文档到单词的索引。

例如

系统根据搜索关键词搜索到文档ID ,然后系统根据文档ID去查询文档名称展示给用户,此时用的是 倒排索引。
当用户单击具体的某篇文档时,系统根据文档ID查询文档内容井展示给用户, 此时用的是 正排索引。

Elasticsearch 常用于数据聚合统计,实时检索数据,全文索引等业务场景;但它不适用于对事物有强一致要求的业务场景。

对比 Mysql

如果对 "订单测试专用" 这句话我们要求从 goods_desc, texture, style 这三个字段中查找关键字, 并且对查找到的结果行按照关键字的匹配度对结果行进行排序,那么在 mysql 中我们就需要写这种复杂的 sql 语句来获取结果。

  1. SELECT
  2. *
  3. FROM
  4. (
  5. SELECT
  6. *, CASE
  7. WHEN CONCAT(goods_desc, texture, style) LIKE '%订单测试专用%' THEN 1
  8. WHEN CONCAT(goods_desc, texture, style) LIKE '%订单%' THEN 2
  9. WHEN CONCAT(goods_desc, texture, style) LIKE '%测试%' THEN 3
  10. WHEN CONCAT(goods_desc, texture, style) LIKE '%专用%' THEN 4
  11. END AS rn
  12. FROM
  13. products
  14. WHERE
  15. CONCAT(goods_desc, texture, style) LIKE '%订单测试专用%'
  16. OR CONCAT(goods_desc, texture, style) LIKE '%订单%'
  17. OR CONCAT(goods_desc, texture, style) LIKE '%测试%'
  18. OR CONCAT(goods_desc, texture, style) LIKE '%专用%'
  19. ) AS k
  20. ORDER BY
  21. rn;

而在 Elasticsearch 中,你只需要在新建一个 Index 时指定需要分词的字段,然后查询时他会自动为你排好序。

分词会将文本分割成一系列被称为语汇单元(token)的独立原子元素,每个token大致能与自然语言中的"单词"对应起来。

  1. # 分词
  2. $ curl -X PUT 'localhost:9200/products' -d '
  3. {
  4. "mappings": {
  5. "info": {
  6. "properties": {
  7. "goods_desc": {
  8. "type": "text",
  9. "analyzer": "ik_max_word",
  10. "search_analyzer": "ik_max_word"
  11. },
  12. "texture": {
  13. "type": "text",
  14. "analyzer": "ik_max_word",
  15. "search_analyzer": "ik_max_word"
  16. },
  17. "style": {
  18. "type": "text",
  19. "analyzer" : "ik_max_word",
  20. "search_analyzer": "ik_max_word"
  21. }
  22. }
  23. }
  24. }
  25. }'
  26. # 查询
  27. $ curl 'localhost:9200/products/info/_search' -d '
  28. {
  29. "query" :{
  30. "should" :{
  31. "goods_desc" : "订单测试专用",
  32. "texture" : "订单测试专用",
  33. "style" : "订单测试专用"
  34. }
  35. }
  36. }

概念说明

索引(Index),类型(Type),文档(Document)

  • Type 是虚拟的逻辑分组,用来过滤 Document;6.x版只允许每个Index包含一个Type,7.x 版将会彻底移除 Type。
  • Index 里面单条的记录称为 Document(文档),许多条 Document 构成了一个 Index。同一个 Index 里面的 Document,不要求有相同的结构(scheme),但是最好保持相同,这样有利于提高搜索效率。
  • mapping 类似 mysql 中的 schema(表结构),mapping 可以显示的定义,也可以在 document 被索引时自动生成,如果有新的 field,Elasticsearch 会自动推测出 field 的type并加到mapping中。

存储数据到 Elasticsearch 是构建索引的过程

索引是一种数据结构,它允许对存储在其中的单词进行快速随机访问。当需要从大量文本中快速检索文本目标时,必须首先将文本内容转换成能够进行快速搜索的格式,以建立针对文本的索引数据结构,此即为索引过程。

集群(cluster)与节点(node)

集群 :由一个或多个节点组成, 并通过集群名称与其他集群进行区分。

节点 :即单个ES实例,通常一个实例运行在一个隔离的容器或虚拟机中,一台机器上可能会有多个实例。

集群状态有三种

  • Green:所有主分片和备份分片都准备就绪(分配成功),即使有一台机器挂了(假设一台机器一个实例),数据都不会丢失,但会变成Yellow状态。
  • Yellow:所有主分片准备就绪,但存在至少一个主分片(假设是A)对应的备份分片没有就绪,此时集群属于警告状态,意味着集群高可用和容灾能力下降,如果刚好A所在的机器挂了,并且你只设置了一个备份(已处于未就绪状态),那么A的数据就会丢失(查询结果不完整),此时集群进入Red状态。
  • Red:至少有一个主分片没有就绪(直接原因是找不到对应的备份分片成为新的主分片),此时查询的结果会出现数据丢失(不完整)。

为什么要有分片

一个索引不可以存储超出单个结点硬件限制的大量数据。比如,一个具有10亿文档的索引占据1TB的磁盘空间,而任一节点可能没有这样大的磁盘空间来存储或者单个节点处理搜索请求,响应会太慢。为了解决这个问题,Elasticsearch提供了将索引划分成多片的能力,这些片叫做分片。

分片(Shard)和副本(Replica)

分片:每个索引有一个或多个分片,索引的数据(文档)被分配到各个分片上,分片又被分配到集群内的各个节点里,每个分片仅保存全部数据的一部分,一个分片默认最大文档数量是20亿。分片有助于横向扩展,N个分片会被尽可能平均地(rebalance)分配在不同的节点上;

例如你有2个节点,4个主分片(不考虑备份),那么每个节点会分到2个分片,后来你增加了2个节点,那么你这4个节点上都会有1个分片,这个过程叫relocation,ES感知后自动完成。

副本:ES默认为一个索引创建5个主分片, 并为每个主分片创建一个副本分片,这样每个索引总共就有10个分片, 主分片和备分片不会出现在同一个节点上 (防止单点故障),这就意味着你的集群中至少应该有两个节点。如果你只有一个节点,那么5个副本都无法分配),此时cluster status会变成Yellow。

只有主分片才能处理创建索引的请求,一旦创建完成,主分片的数量将不可更改,而副本的数量可动态更改,ES 会根据需要自动增加或减少这些副本的数量。

上图中 ES 集群有两个节点,并使用了默认的分片配置,ES自动把这5个主分片分配到2个节点上,而它们分别对应的副本则在完全不同的节点上。

分片存取 document 的原理

当创建 document 时,Elasticsearch通过对docID进行hash来确定其放在哪个shard上面,然后在shard上面进行索引存储。

当查询 document 时,如果提供了查询的docID,Elasticsearch通过hash就知道 document 存在哪个shard上面,再通过routing-table查询就知道在哪个node上面,然后去node上面去取就好了。如果不提供docID,那么Elasticsearch会在该Index(indics)shards所在的所有node上执行搜索预警,然后返回搜索结果,由coordinating node gather之后返回给用户。

安装

下载 并解压到任一目录后,运行如下命令将 elasticsearch 安装成 windows 服务。

  1. elasticsearch-service.bat install

之后就可以通过如下选项管理 ElasticSearch 了。

  1. #install:安装服务|remove:删除服务|start:启动|stop:暂停|manager:打开服务管理器
  2. elasticsearch-service.bat install|remove|start|stop|manager

使用 start 开启服务后打开浏览器访问 http://localhost:9200/

默认情况下,elastic 只允许本机访问,如果需要远程访问,可以修改 Elastic 安装目录的config/elasticsearch.yml文件,去掉network.host的注释,将它的值改成 0.0.0.0,然后重新启动 elastic;线上服务不要这样设置,要设成具体的 IP。

相关插件

ElasticHD

管理工具推荐下载 ElasticHD ,它支持 监控、实时搜索,Index template快捷替换修改,索引列表信息查看, SQL converts to DSL 等功能。

ElasticHD.exe -p 127.0.0.1:9800

启动它后会打开浏览器,就可以看到如图所示的界面了。

Kibana

Kibana 是 Elasticsearch 的可视化插件 ,下载后运行 kibana.bat,然后在浏览器中访问 http://localhost:5601 就可以看到 kibana 管理界面了。

  1. E:\soft\kibana-6.8.0\bin>kibana.bat
  2. log [12:08:00.425] [info][status][plugin:kibana@6.8.0] Status changed from uninitialized to green - Ready
  3. log [12:08:00.455] [info][status][plugin:elasticsearch@6.8.0] Status changed from uninitialized
  4. ...

Elasticsearch 使用

客户端配置

最常见的配置是告诉客户端有关集群的信息:有多少个节点,节点的ip地址和端口号。如果没有指定主机名,客户端会连接 localhost:9200 。

Inline Host 配置法

  1. $hosts = [
  2. '192.168.1.1:9200', // IP + Port
  3. '192.168.1.2', // Just IP
  4. 'mydomain.server.com:9201', // Domain + Port
  5. 'mydomain2.server.com', // Just Domain
  6. 'https://localhost', // SSL to localhost
  7. 'https://192.168.1.3:9200' // SSL to IP + Port
  8. ];
  9. $client = ClientBuilder::create() // Instantiate a new ClientBuilder
  10. ->setHosts($hosts) // Set the hosts
  11. ->build();

Extended Host 配置法

Inline Host 配置法依赖 PHP 的 filter_var() 函数和 parse_url() 函数来验证和提取一个 URL 的各个部分。然而,这些 php 函数在一些特定的场景下会出错。例如, filter_var() 函数不接收有下划线的 URL。同样,如果 Basic Auth 的密码含有特定字符(如#、?),那么 parse_url() 函数会报错。

因此使用Extended Host 配置语法,从而使客户端实例化更加可控。

  1. $hosts = [
  2. // This is effectively equal to: "https://username:password!#$?*abc@foo.com:9200/"
  3. [
  4. 'host' => 'foo.com',
  5. 'port' => '9200',
  6. 'scheme' => 'https',
  7. 'user' => 'username',
  8. 'pass' => 'password!#$?*abc'
  9. ],
  10. // This is equal to "http://localhost:9200/"
  11. [
  12. 'host' => 'localhost', // Only host is required
  13. ]
  14. ];
  15. $client = ClientBuilder::create() // Instantiate a new ClientBuilder
  16. ->setHosts($hosts) // Set the hosts
  17. ->build(); // Build the client object

忽略异常

如果你想忽略异常,你可以配置 ignore 参数。ignore 参数要作为 client 的参数配置在请求体中,例如你想忽略因为文档不存在而报出的 404 异常MissingDocument404Exception。

  1. $client = ClientBuilder::create()->build();
  2. $params = [
  3. 'index' => 'test_missing',
  4. 'type' => 'test',
  5. 'id' => 1,
  6. 'client' => [ 'ignore' => [400, 404] ]
  7. ];
  8. echo $client->get($params);
  9. > {"_index":"test_missing","_type":"test","_id":"1","found":false}

自定义查询参数

在 Elasticsearch-php 的白名单中存储着所有的查询参数,因此才有了自定义查询参数,这样就能避免你指定一个参数,而 Elasticsearch 却不接收,为了达到这种效果,请增加 custom 参数。

  1. $client = ClientBuilder::create()->build();
  2. $params = [
  3. 'index' => 'test',
  4. 'type' => 'test',
  5. 'id' => 1,
  6. 'parent' => 'abc', // white-listed Elasticsearch parameter
  7. 'client' => [
  8. 'custom' => [
  9. 'customToken' => 'abc', // user-defined, not white listed, not checked
  10. 'otherToken' => 123
  11. ]
  12. ]
  13. ];
  14. $exists = $client->exists($params);

返回详细输出

客户端默认只返回响应体数据。如果你需要更多信息(如头信息、相应状态码等),你可以让客户端返回更多详细信息。通过 verbose 参数可以开启这个功能。

  1. # 简单输出
  2. client = ClientBuilder::create()->build();
  3. $params = [
  4. 'index' => 'test',
  5. 'type' => 'test',
  6. 'id' => 1
  7. ];
  8. $response = $client->get($params);
  9. print_r($response);
  10. Array
  11. (
  12. [_index] => test
  13. [_type] => test
  14. [_id] => 1
  15. [_version] => 1
  16. [found] => 1
  17. [_source] => Array
  18. (
  19. [field] => value
  20. )
  21. )
  22. # 详细输出
  23. $client = ClientBuilder::create()->build();
  24. $params = [
  25. 'index' => 'test',
  26. 'type' => 'test',
  27. 'id' => 1,
  28. 'client' => [
  29. 'verbose' => true
  30. ]
  31. ];
  32. $response = $client->get($params);
  33. print_r($response);
  34. Array
  35. (
  36. [transfer_stats] => Array
  37. (
  38. ...
  39. )
  40. [curl] => Array
  41. (
  42. [error] =>
  43. [errno] => 0
  44. )
  45. [effective_url] => http://127.0.0.1:9200/test/test/1
  46. [headers] => Array
  47. (
  48. ...
  49. )
  50. [status] => 200
  51. [reason] => OK
  52. [body] => Array
  53. (
  54. [_index] => test
  55. [_type] => test
  56. [_id] => 1
  57. [_version] => 1
  58. [found] => 1
  59. [_source] => Array
  60. (
  61. [field] => value
  62. )
  63. )
  64. )

Curl 超时设置

通过 timeout 和 connect_timeout 参数可以配置每个请求的 Curl 超时时间。这个配置主要是控制客户端的超时时间。 connect_timeout 参数控制在连接阶段完成前,curl 的等待时间。而 timeout 参数则控制整个请求完成前,最多等待多长时间。

如果超过超时时间,curl 会关闭连接并返回一个致命错误。两个参数都要用作为参数。

  1. $client = ClientBuilder::create()->build();
  2. $params = [
  3. 'index' => 'test',
  4. 'type' => 'test',
  5. 'id' => 1,
  6. 'client' => [
  7. 'timeout' => 10, // ten second timeout
  8. 'connect_timeout' => 10
  9. ]
  10. ];
  11. $response = $client->get($params);

空对象

PHP 通过对关联数组进行 json_encode 能够构造出一个有数据的 json 对象,但是却无法对一个空数组 json_encode 后构出处一个空对象{},而Elasticsearch API 在几个地方使用了空对象,这会对 PHP 造成影响,因此在 PHP 如果要构造空对象,请使用 new \stdClass()

  1. echo json_encode([]); // []
  2. echo json_encode(new stdClass()); // {}

例如某个 API 可能需要这种带有空对象的参数

  1. {
  2. "query" : {
  3. "match" : {
  4. "content" : "quick brown fox"
  5. }
  6. },
  7. "highlight" : {
  8. "fields" : {
  9. "content" : {}
  10. }
  11. }
  12. }

那么我们实际传参时就这样构造

  1. $params['body'] = [
  2. 'query' => [
  3. 'match' => [
  4. 'content' => 'quick brown fox'
  5. ]
  6. ],
  7. 'highlight' => [
  8. 'fields' => [
  9. 'content' => new \stdClass()
  10. ]
  11. ]
  12. ];
  13. $results = $client->search($params);

索引管理

创建索引

你可以在一个创建索引 API 中指定任何参数。所有的参数通常会注入请求体中的 body 参数下,body包含settings和map

  1. $client = ClientBuilder::create()->build();
  2. $params = [
  3. 'index' => 'my_index',
  4. 'body' => [
  5. 'settings' => [
  6. 'number_of_shards' => 3,
  7. 'number_of_replicas' => 2
  8. ],
  9. 'mappings' => [
  10. 'my_type' => [
  11. '_source' => [
  12. 'enabled' => true
  13. ],
  14. 'properties' => [
  15. 'first_name' => [
  16. 'type' => 'string',
  17. 'analyzer' => 'standard'
  18. ],
  19. 'age' => [
  20. 'type' => 'integer'
  21. ]
  22. ]
  23. ]
  24. ]
  25. ]
  26. ];
  27. // Create the index with mappings and settings now
  28. $response = $client->indices()->create($params);

删除索引

  1. $deleteParams = [
  2. 'index' => 'db'
  3. ];
  4. $response = $client->indices()->delete($deleteParams);

修改索引 settings

  1. $params = [
  2. 'index' => 'my_index',
  3. 'body' => [
  4. 'settings' => [
  5. 'number_of_replicas' => 0,
  6. 'refresh_interval' => -1
  7. ]
  8. ]
  9. ];
  10. $response = $client->indices()->putSettings($params);

获取索引 settings

  1. // Get settings for one index
  2. $params = ['index' => 'my_index'];
  3. $response = $client->indices()->getSettings($params);
  4. // Get settings for several indices
  5. $params = [
  6. 'index' => [ 'my_index', 'my_index2' ]
  7. ];
  8. $response = $client->indices()->getSettings($params);

修改索引 mappings

  1. // Set the index and type
  2. $params = [
  3. 'index' => 'my_index',
  4. 'type' => 'my_type2',
  5. 'body' => [
  6. 'my_type2' => [
  7. '_source' => [
  8. 'enabled' => true
  9. ],
  10. 'properties' => [
  11. 'first_name' => [
  12. 'type' => 'string',
  13. 'analyzer' => 'standard'
  14. ],
  15. 'age' => [
  16. 'type' => 'integer'
  17. ]
  18. ]
  19. ]
  20. ]
  21. ];
  22. // Update the index mapping
  23. $client->indices()->putMapping($params);

获取索引 mappings

  1. // Get mappings for all indexes and types
  2. $response = $client->indices()->getMapping();
  3. // Get mappings for all types in 'my_index'
  4. $params = ['index' => 'my_index'];
  5. $response = $client->indices()->getMapping($params);
  6. // Get mappings for all types of 'my_type', regardless of index
  7. $params = ['type' => 'my_type' ];
  8. $response = $client->indices()->getMapping($params);
  9. // Get mapping 'my_type' in 'my_index'
  10. $params = [
  11. 'index' => 'my_index'
  12. 'type' => 'my_type'
  13. ];
  14. $response = $client->indices()->getMapping($params);
  15. // Get mappings for two indexes
  16. $params = [
  17. 'index' => [ 'my_index', 'my_index2' ]
  18. ];
  19. $response = $client->indices()->getMapping($params);

映射(Mapping)

映射相当于数据表的表结构,用来定义一个文档所包含的字段以及字段的类型,分词器及属性等等.

映射可以分为动态映射和静态映射:

  • 动态映射:不需要事先定义映射,文档写入 es 时,会根据文档字段自动识别字段类型,这种机制称之为动态映射。
  • 静态映射:需要事先定义好映射,即文档的各个字段及其类型等,这种方式称之为静态映射。

由于动态映射的自动类型推测功能并不是 100% 正确的,因此最好使用静态映射来显式定义文档的字段类型。

注意:user_name 被索引了两次,意思是在 user_name 上既可以全文搜索(full text),也可以精确搜索(exact value)。

  1. # 创建 mapping
  2. PUT /website
  3. {
  4. "mappings": {
  5. "properties": {
  6. "author_id": {
  7. "type": "long"
  8. },
  9. "user_name": {
  10. "type": "text",
  11. "analyzer": "ik_max_word",
  12. "search_analyzer": "ik_max_word"
  13. "fields": {
  14. "keyword": {
  15. "type": "keyword",
  16. "ignore_above": 256
  17. }
  18. }
  19. }
  20. "title": {
  21. "type": "text",
  22. "analyzer": "standard"
  23. },
  24. "content": {
  25. "type": "text"
  26. },
  27. "post_date": {
  28. "type": "date"
  29. },
  30. "publisher_id": {
  31. "type": "keyword"
  32. }
  33. }
  34. }
  35. }

已创建的 Mapping 支持添加新的字段,但是不支持修改原有的字段,这就好比在 mysql 中,如果将已有记录的字段 user_name 从 char 改成 int,在 mysql 中是会报错的。

  1. # mapping 修改字段
  2. PUT /website
  3. {
  4. "mappings": {
  5. "properties": {
  6. "author_id": {
  7. "type": "text"
  8. }
  9. }
  10. }
  11. }
  12. {
  13. "error": {
  14. "root_cause": [
  15. {
  16. "type": "resource_already_exists_exception",
  17. "reason": "index [website/5xLohnJITHqCwRYInmBFmA] already exists",
  18. "index_uuid": "5xLohnJITHqCwRYInmBFmA",
  19. "index": "website"
  20. }
  21. ],
  22. "type": "resource_already_exists_exception",
  23. "reason": "index [website/5xLohnJITHqCwRYInmBFmA] already exists",
  24. "index_uuid": "5xLohnJITHqCwRYInmBFmA",
  25. "index": "website"
  26. },
  27. "status": 400
  28. }
  29. # mapping 新增字段
  30. PUT /website/_mapping
  31. {
  32. "properties": {
  33. "new_field": {
  34. "type": "text"
  35. }
  36. }
  37. }
  38. {
  39. "acknowledged" : true
  40. }

Mapping 类型

字符型

tring :从ElasticSearch 5.x开始不再支持string,由text和keyword类型替代。
text :适用于全文搜索(full text)的字段,比如Email内容、产品描述,应该使用text类型。text类型的字段不用于排序,很少用于聚合。
keyword:适用于精确(exact value)查找的字段,比如email地址,主机名,状态码和标签,keyword类型的字段只能通过精确值(exact value)搜索到。

数字类型

byte,short,integer,long。

浮点数

float,double。

日期

date: es 内部会将日期数据转换为 UTC 时间。

数组和对象

数组[],对象{} 其底层都是 text 字符串类型。

分词器

分词器的主要功能如下:

  • character filter:在一段文本进行分词之前,先进行预处理,比如说最常见的就是,过滤html标签(hello --> hello),& --> and(I&you --> I and you)
  • tokenizer:分词。例如:hello you and me --> hello, you, and, me
  • token filter:大小写的转换,停用词,同义词的转换等

Es 内置了一些分析器,无需进一步配置即可在任何索引中使用:

  • standard analyzer: standard分析器将文本分为在字边界条件,由Unicode的文本分割算法所定义的。它删除了大多数标点符号,小写术语,并支持删除停用词。
  • Simple analyzer: simple 分析仪将文本分为方面每当遇到一个字符是不是字母。然后全部变为小写。
  • whitespace analyzer: whitespace 只要遇到任何空格字符 ,分析器就会将文本划分为术语。它不会进行小写转换。
  • stop analyzer: stop分析器像 simple,而且还支持去除停止词。
  • keyword analyzer: keyword 分析器是一个“空操作”分析器,接受任何文本它被赋予并输出完全相同的文本作为一个单一的术语,也就是不会分词,进行精确匹配。
  • pattern analyzer: pattern 分析器使用一个正则表达式对文本进行拆分。它支持小写转换和停用字。
  • language analyzer: Es 提供了许多特定于语言的分析器,如english 或 french。
  • fingerprint analyzer: fingerprint 分析器是一种专业的指纹分析器,它可以创建一个指纹,用于重复检测。

借助 _analyze api我们可以查看分析器的分词行为

  1. GET /_analyze
  2. {
  3. "analyzer": "ik_max_word",
  4. "text": "9新 iPhone 7 Plus 128G玫瑰金"
  5. }
  6. # 输出
  7. {
  8. "tokens": [
  9. {
  10. "token": "9",
  11. "start_offset": 0,
  12. "end_offset": 1,
  13. "type": "ARABIC",
  14. "position": 0
  15. },
  16. {
  17. "token": "新",
  18. "start_offset": 1,
  19. "end_offset": 2,
  20. "type": "CN_CHAR",
  21. "position": 1
  22. },
  23. {
  24. "token": "iphone",
  25. "start_offset": 3,
  26. "end_offset": 9,
  27. "type": "ENGLISH",
  28. "position": 2
  29. },
  30. {
  31. "token": "7",
  32. "start_offset": 10,
  33. "end_offset": 11,
  34. "type": "ARABIC",
  35. "position": 3
  36. },
  37. {
  38. "token": "plus",
  39. "start_offset": 12,
  40. "end_offset": 16,
  41. "type": "ENGLISH",
  42. "position": 4
  43. },
  44. {
  45. "token": "128g",
  46. "start_offset": 17,
  47. "end_offset": 21,
  48. "type": "LETTER",
  49. "position": 5
  50. },
  51. {
  52. "token": "128",
  53. "start_offset": 17,
  54. "end_offset": 20,
  55. "type": "ARABIC",
  56. "position": 6
  57. },
  58. {
  59. "token": "g",
  60. "start_offset": 20,
  61. "end_offset": 21,
  62. "type": "ENGLISH",
  63. "position": 7
  64. },
  65. {
  66. "token": "玫瑰",
  67. "start_offset": 21,
  68. "end_offset": 23,
  69. "type": "CN_WORD",
  70. "position": 8
  71. },
  72. {
  73. "token": "金",
  74. "start_offset": 23,
  75. "end_offset": 24,
  76. "type": "CN_CHAR",
  77. "position": 9
  78. }
  79. ]
  80. }

文档管理

创建文档

要指定4部分信息:index,type,id 和一个 body,即你要操作的数据库,表以及要插入的数据。

如果不设置id,Elasticsearch 会为id自动生成随机数,建议 id 设计为自增并且唯一

  1. $client = ClientBuilder::create()->build();
  2. # 创建单一文档
  3. $params = [
  4. 'index' => 'db',
  5. 'type' => 'table',
  6. 'id' => 'table_id',
  7. 'body' => [
  8. 'name' => 'tcl',
  9. 'age' => 28,
  10. 'sex' => 'man'
  11. ]
  12. ];
  13. $response = $client->index($params);
  14. # 批量创建文档
  15. for($i = 0; $i < 100; $i++) {
  16. $params['body'][] = [
  17. 'index' => [
  18. '_index' => 'my_index',
  19. '_type' => 'my_type',
  20. ]
  21. ];
  22. $params['body'][] = [
  23. 'my_field' => 'my_value',
  24. 'second_field' => 'some more values'
  25. ];
  26. }
  27. $responses = $client->bulk($params);

获取文档

获取一篇文档时必须指定id。响应数据包含一些元数据(如 index,type 等)和 _source 属性, 这是你发送给 Elasticsearch 的原始文档数据。

  1. $client = ClientBuilder::create()->build();
  2. $params = [
  3. 'index' => 'db',
  4. 'type' => 'table',
  5. 'id' => 'table_id'
  6. ];
  7. $response = $client->get($params);
  8. print_r($response);
  9. //输出如下
  10. Array
  11. (
  12. [_index] => db
  13. [_type] => table
  14. [_id] => table_id
  15. [_version] => 1
  16. [_seq_no] => 0
  17. [_primary_term] => 1
  18. [found] => 1
  19. [_source] => Array
  20. (
  21. [name] => tcl
  22. [age] => 28
  23. [sex] => man
  24. )
  25. )

更新文档

如果你要部分更新文档(如更改现存字段,或添加新字段),你可以在 body 参数中指定一个 doc 参数。这样 doc 参数内的字段会与现存字段进行合并。

  1. $client = ClientBuilder::create()->build();
  2. $params = [
  3. 'index' => 'db',
  4. 'type' => 'table',
  5. 'id' => 'table_id',
  6. 'body' => [
  7. 'doc' => [
  8. 'name' => 'tcl_update',
  9. 'age' => 28,
  10. 'sex' => 'man'
  11. ],
  12. ]
  13. ];
  14. $response = $client->update($params);
  15. print_r($response);
  16. //输出如下,对比上面新增文档的输出,可以看到记录的 Id 没变,但是版本(version)从1变成2,
  17. //操作类型(result)从created变成updated,created字段变成false,因为这次不是新建记录
  18. Array
  19. (
  20. [_index] => db
  21. [_type] => table
  22. [_id] => table_id
  23. [_version] => 2
  24. [result] => updated
  25. [_shards] => Array
  26. (
  27. [total] => 2
  28. [successful] => 1
  29. [failed] => 0
  30. )
  31. [_seq_no] => 1
  32. [_primary_term] => 1
  33. )

删除文档

  1. $params = [
  2. 'index' => 'db',
  3. 'type' => 'table',
  4. 'id' => 'table_id',
  5. ];
  6. // /my_index/my_type/my_id
  7. $response = $client->delete($params);

查询

es 的查询主要有两类,分别是 filter 和 query:

  • filter 正如其字面意思“过滤”所说的,是起过滤的作用,任何一个document 对 filter 来说,就是match 与否的问题,是个二值问题,0和1,没有 scoring 的过程。
  • 使用query的时候,是表示 match 程度问题,有 scroing 过程。

普通查询

match_all : 查询所有文档。

  1. {"match_all":{}}

例如我们获取 index=db,type=table 中的所有记录

  1. curl -XGET 'localhost:9200/db/table/_search' -d '{
  2. "query":{
  3. "match_all":{}
  4. }
  5. }'
  6. # 输出
  7. # hits.total:返回记录数,本例是1条。
  8. # hits.max_score:最高的匹配程度,本例是1.0。
  9. # hits.hits:返回的记录组成的数组,hits 内部也有一个 hits 数组,内部的 hits 包含特定的搜索结果。
  10. # hits.hits._score:文档的分数,用来衡量搜索结果跟我们指定的关键字的相关程度;分数越高,说明这个文档的相关性越大,分数越低,说明这个文档的相关性越小。
  11. {
  12. "took": 0,
  13. "timed_out": false,
  14. "_shards": {
  15. "total": 5,
  16. "successful": 5,
  17. "skipped": 0,
  18. "failed": 0
  19. },
  20. "hits": {
  21. "total": 1,
  22. "max_score": 1,
  23. "hits": [
  24. {
  25. "_index": "db",
  26. "_type": "table",
  27. "_id": "table_id",
  28. "_score": 1,
  29. "_source": {
  30. "name": "tcl_update",
  31. "age": 28,
  32. "sex": "man"
  33. }
  34. }
  35. ]
  36. }
  37. }

默认情况下,会返回所有字段,但如果你只想要其中的某几个字段,可以通过 _source 指定。

  1. {
  2. "query":{
  3. "match_all":{}
  4. },
  5. "_source": ["name", "age"]
  6. }

match : 模糊查询,类似 mysql 的 like,一般在全文检索时使用,首先利用 analyzer 对具体查询字符串进行分析,然后进行查询;如果是在数值型字段、日期类型字段、布尔字段或not_analyzed 的字符串上进行查询时,不对查询字符串进行分析,表示精确匹配。

如果要查找的字符串是用空格分隔的,相当于查找用空格分隔的多个值,如果你确实要查一个包含空格的字符串,那么就应该使用 match_phrase。

  1. # 这里是返回 name 字段中包含'aaa'或者'bbb' 的所有文档
  2. { "match": { "name": "aaa bbb" }}
  3. # match_phrase 会精确匹配 name 字段中包含 'aaa bbb' 这个短语所有文档
  4. { "match_phrase": { "name": "aaa bbb" }}

term:term 用于精确查找,类似 mysql 中的 = ,当使用term时,不会对查询字符串进行分析,进行的是精确查找。

  1. { "term": { "date": "2014-09-01" }}

terms:多值查询,类似 mysql 中的 in。

  1. { "terms": { "tag": [ "search", "full_text", "nosql" ] }}

range:范围查找,类似mysql中的 >,>=,<,<=。

  1. # es 中表示 >,>=,<,<= 的操作符分别是 gt,gte,lt,lte
  2. {
  3. "range": {
  4. "age": {
  5. "gte": 20,
  6. "lt": 30
  7. }
  8. }
  9. }

分页和排序

Elastic 默认一次返回10条结果,size 字段可以设置每页返回条数,from 字段可以指定位移(默认从0开始)。

  1. # 这里我们按照创建时间升序读取第二页数据,每页返回2条结果
  2. {
  3. "query": {
  4. "match_all": {}
  5. },
  6. "from": 1,
  7. "size": 2,
  8. "sort": {
  9. "created_at": {
  10. "order": "desc"
  11. },
  12. "tag.keyword": {
  13. "order": "desc"
  14. }
  15. }
  16. }

注意:由于在 text 类型(全文索引)字段上排序会报错,这里给 text 类型字段一个 .keyword 就可以避免报错。

复合查询

有了上面介绍的查询子句后,借助 Bool 表达式我们就可以将各种子句组合起来实现复合查询,这种复合查询就类似在 mysql 中你使用 and/or 将各种原子查询条件组装起来进行复杂的查询。

Bool 表达式的子句主要包括

  • must : 表示必须匹配。
  • must_not : 表示一定不能匹配。
  • should : 表示可以匹配,类似于布尔运算里的"或"。如果bool 子句里没有must子句,那么 should 子句里至少匹配一个, 如果有 must 子句,那么 should 子句至少匹配零个。可以使用minimum_should_match 来对最小匹配数进行设置。

我们可以在一个bool子句中组合另一个bool来模拟任何复杂的多重布尔逻辑。

  1. {
  2. "bool" : {
  3. // 查询所有 name 属性中包含 "张" 和 "三" 的文档
  4. "must" : [
  5. { "match": { "first_name": "张" } },
  6. { "match": { "last_name": "三" } }
  7. ],
  8. // 查询所有 tag 属性中等于 "A" 或 "B" 的文档
  9. "should" : [
  10. { "term" : { "tag" : "A" } },
  11. { "term" : { "tag" : "B" } }
  12. ],
  13. // 查询 age 不在 10-20 这个区间的所有文档
  14. "must_not" : {
  15. "range" : {
  16. "age" : { "from" : 10, "to" : 20 }
  17. }
  18. },
  19. "minimum_should_match" : 1,
  20. "boost" : 1.0
  21. }
  22. }
  23. # 在 php 中参数构造如下
  24. $param = [
  25. 'index' => 'my_index',
  26. 'type' => 'my_type',
  27. 'body' =>
  28. [
  29. 'query' => [
  30. 'bool' =>
  31. [
  32. 'must' =>
  33. [
  34. ['match' => ['first_name' => '张']],
  35. ['match' => ['last_name' => '三']],
  36. ],
  37. 'must_not' =>
  38. [
  39. 'range' => ['age' => ['from' => 10, 'to' => 20]]
  40. ],
  41. 'should' =>
  42. [
  43. ['term' => ['tag' => 'A']],
  44. ['term' => ['tag' => 'B']]
  45. ],
  46. 'minimum_should_match' => 1,
  47. 'boost' => 1.0
  48. ]
  49. ]
  50. ]
  51. ];

含有 filter 过滤器的复合查询

使用 filter 对结果进行过滤时,它不会去计算分值,因此效率也就更高一些.

  1. # 姓氏为Smith且年龄大于30的结果
  2. curl -XGET 'localhost:9200/my_index/my_type/_search' -d '{
  3. "query" : {
  4. "bool" : {
  5. "must" : {
  6. "match" : {
  7. "last_name" : "Smith"
  8. }
  9. },
  10. "filter": {
  11. "range" : {
  12. "age" : { "gt" : 30 }
  13. }
  14. }
  15. }
  16. }
  17. }'
  18. # 在 php 中参数构造如下
  19. $params = [
  20. 'index' => 'my_index',
  21. 'type' => 'my_type',
  22. 'body' => [
  23. 'query' => [
  24. 'bool' => [
  25. 'must' => [
  26. 'match' => [ 'last_name' => 'Smith' ]
  27. ],
  28. 'filter' => [
  29. 'range' => [ 'age' => ['gt'=>30]]
  30. ]
  31. ]
  32. ]
  33. ]
  34. ];

把 filter 中条件移到 must 中也可以实现相同的效果,例如

  1. curl -XGET 'localhost:9200/my_index/my_type/_search' -d '{
  2. "query": {
  3. "bool": {
  4. "must": [
  5. {
  6. "match": {
  7. "last_name": "Smith"
  8. }
  9. },
  10. {
  11. "range": {
  12. "age": {
  13. "gt": 30
  14. }
  15. }
  16. }
  17. ]
  18. }
  19. }
  20. }'

调试

查看 Mapping

  1. GET /my_index/_mapping?pretty
  2. {
  3. "my_index": {
  4. "mappings": {
  5. "my_type": {
  6. "properties": {
  7. "updated_at": {
  8. "type": "date",
  9. "format": "yyyy-MM-dd HH:mm:ss||epoch_millis"
  10. },
  11. "useful_num": {
  12. "type": "integer"
  13. },
  14. "user_id": {
  15. "type": "integer"
  16. },
  17. "user_name": {
  18. "type": "text",
  19. "fields": {
  20. "keyword": {
  21. "type": "keyword",
  22. "ignore_above": 256
  23. }
  24. }
  25. }
  26. }
  27. }
  28. }
  29. }
  30. }

定位不合法的查询

如果某个查询的条件比较复杂,可以先用 validate api 去验证一下搜索是否合法。

  1. GET /my_index/_validate/query?explain
  2. {
  3. "query": {
  4. "bool": {
  5. "must": [
  6. {
  7. "term": {
  8. "audit": 1
  9. }
  10. },
  11. {
  12. "term": {
  13. "model_id": 127
  14. }
  15. },
  16. {
  17. "term": {
  18. "product_type": 1
  19. }
  20. }
  21. ]
  22. }
  23. }
  24. }
  25. # 输出
  26. {
  27. "_shards": {
  28. "total": 1,
  29. "successful": 1,
  30. "failed": 0
  31. },
  32. "valid": true,
  33. "explanations": [
  34. {
  35. "index": "my_index",
  36. "valid": true,
  37. "explanation": "+(+audit:[1 TO 1] +model_id:[127 TO 127] +product_type:[1 TO 1]) #*:*"
  38. }
  39. ]
  40. }

参考

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注