├── .gitignore ├── breaking-changes.asciidoc ├── php-version-requirement.asciidoc ├── overview.asciidoc ├── index.asciidoc ├── LICENSE ├── namespaces.asciidoc ├── installation.asciidoc ├── selectors.asciidoc ├── security.asciidoc ├── community.asciidoc ├── php_json_objects.asciidoc ├── serializers.asciidoc ├── connection-pool.asciidoc ├── crud.asciidoc ├── futures.asciidoc ├── search-operations.asciidoc ├── quickstart.asciidoc ├── per-request-configuration.asciidoc ├── index-operations.asciidoc └── configuration.asciidoc /.gitignore: -------------------------------------------------------------------------------- 1 | html_docs/ 2 | -------------------------------------------------------------------------------- /breaking-changes.asciidoc: -------------------------------------------------------------------------------- 1 | [[_breaking_changes_from_5_x]] 2 | == Breaking changes from 5.x 3 | 4 | 没有!:) -------------------------------------------------------------------------------- /php-version-requirement.asciidoc: -------------------------------------------------------------------------------- 1 | [[_php_version_requirement]] 2 | == PHP 版本需求 3 | 4 | Elasticsearch-PHP 6.0 需要 PHP 7.0.0 或者更高版本。另外,它还需要原生 JSON 扩展的版本为 1.3.7 或者更高版本。。 5 | -------------------------------------------------------------------------------- /overview.asciidoc: -------------------------------------------------------------------------------- 1 | [[_overview]] 2 | == 概述 3 | 4 | 这是 Elasticsearch 官方的 PHP 客户端。我们把 Elasticsearch-PHP 设计成低级客户端(https://en.wikipedia.org/wiki/Low-level_design[低级设计模式]),使用时不会偏离 REST API 的用法。 5 | 6 | 客户端所有方法几乎都与 REST API 对应,而且也与其他编程语言的客户端(如 ruby, python 等)方法结构相似。我们希望这种对应方式可以方便开发者更加容易上手客户端,且以最小的代价快速从一种编程语言转换到另一种编程语言。 7 | 8 | 本客户端设计得很“灵活”。虽然有一些通用的细节添加进了客户端(集群状态嗅探,轮询调度请求等),但总的来说它是十分基础的。这也是有意这样设计。我们只是设计了基础方法,更多的复杂类库可以在此衍生出来。 9 | 10 | === 鸣谢 11 | 12 | 感谢 https://github.com/mosongxing[@Mosongxing] 贡献本手册的中文翻译。 13 | -------------------------------------------------------------------------------- /index.asciidoc: -------------------------------------------------------------------------------- 1 | 2 | = Elasticsearch-PHP 3 | 4 | include::overview.asciidoc[] 5 | 6 | include::quickstart.asciidoc[] 7 | 8 | include::installation.asciidoc[] 9 | 10 | include::configuration.asciidoc[] 11 | 12 | include::per-request-configuration.asciidoc[] 13 | 14 | include::futures.asciidoc[] 15 | 16 | include::php_json_objects.asciidoc[] 17 | 18 | include::index-operations.asciidoc[] 19 | 20 | include::crud.asciidoc[] 21 | 22 | include::search-operations.asciidoc[] 23 | 24 | include::namespaces.asciidoc[] 25 | 26 | include::security.asciidoc[] 27 | 28 | include::connection-pool.asciidoc[] 29 | 30 | include::selectors.asciidoc[] 31 | 32 | include::serializers.asciidoc[] 33 | 34 | include::php-version-requirement.asciidoc[] 35 | 36 | include::breaking-changes.asciidoc[] 37 | 38 | include::community.asciidoc[] 39 | 40 | include::build/classes.asciidoc[] 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Lawrence 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /namespaces.asciidoc: -------------------------------------------------------------------------------- 1 | [[_namespaces]] 2 | == 命名空间 3 | 4 | 客户端有许多“命名空间”,通常是一些公开的可管理功能。命名空间对应 Elasticsearch 中各种可管理的 endpoint。下面是全部的命名空间: 5 | 6 | |=== 7 | |*命名空间* |*功能* 8 | |`indices()`|索引数据统计和显示索引信息 9 | |`nodes()` |节点数据统计和显示节点信息 10 | |`cluster()`|集群数据统计和显示集群信息 11 | |`snapshot()`|对集群和索引进行拍摄快照或恢复数据 12 | |`cat()` |执行Cat API命令(通常在命令行中使用) 13 | |=== 14 | 15 | 一些方法在不同的命名空间下均可使用。虽然返回的是同样的信息但是却属于不同的上下文环境。想知道命名空间如何运行,请看 `_stats` 的输出信息: 16 | 17 | [source,php] 18 | -------------------------------------------------- 19 | $client = ClientBuilder::create()->build(); 20 | 21 | // Index Stats 22 | // Corresponds to curl -XGET localhost:9200/_stats 23 | $response = $client->indices()->stats(); 24 | 25 | // Node Stats 26 | // Corresponds to curl -XGET localhost:9200/_nodes/stats 27 | $response = $client->nodes()->stats(); 28 | 29 | // Cluster Stats 30 | // Corresponds to curl -XGET localhost:9200/_cluster/stats 31 | $response = $client->cluster()->stats(); 32 | -------------------------------------------------- 33 | 34 | 上面展示了在三个不同命名空间下都调用了 `stats()` 方法。有时这些方法需要参数,这些参数的写法跟客户端中其他方法的参数写法相同。 35 | 36 | 例如,我们可以请求一个索引或多个索引的统计信息: 37 | 38 | [source,php] 39 | -------------------------------------------------- 40 | $client = ClientBuilder::create()->build(); 41 | 42 | // Corresponds to curl -XGET localhost:9200/my_index/_stats 43 | $params['index'] = 'my_index'; 44 | $response = $client->indices()->stats($params); 45 | 46 | // Corresponds to curl -XGET localhost:9200/my_index1,my_index2/_stats 47 | $params['index'] = array('my_index1', 'my_index2'); 48 | $response = $client->indices()->stats($params); 49 | -------------------------------------------------- 50 | 51 | 另外一个例子是在一个现有索引中添加别名: 52 | 53 | [source,php] 54 | -------------------------------------------------- 55 | $params['body'] = array( 56 | 'actions' => array( 57 | array( 58 | 'add' => array( 59 | 'index' => 'myindex', 60 | 'alias' => 'myalias' 61 | ) 62 | ) 63 | ) 64 | ); 65 | $client->indices()->updateAliases($params); 66 | -------------------------------------------------- 67 | 68 | 注意上述例子中两个 `stats` 的调用和 `updateAlias` 的调用是接收不同格式的参数,每个方法的参数格式由相应的 API 需求来决定。`stats` API只需要一个 index 名,而 `updateAlias` 则需要一个 body,里面还要一个 actions 参数。 -------------------------------------------------------------------------------- /installation.asciidoc: -------------------------------------------------------------------------------- 1 | [[_installation_2]] 2 | == 安装 3 | 4 | Elasticsearch-php 的安装需要满足以下 4 个需求: 5 | 6 | * PHP 7.0.0 或更高版本 7 | * https://www.phpcomposer.com/[Composer] 8 | * http://php.net/manual/zh/book.curl.php[ext-curl]:PHP 的 Libcurl 扩展(详情查看下方注意事项) 9 | * 原生 JSON 扩展 (`ext-json`) 1.3.7或更高版本 10 | 11 | 其余的依赖会由 Composer 自动安装。Composer 是一个 PHP 包管理和依赖管理工具,使用 Composer 安装 elasticsearch-php 非常简单。 12 | 13 | [NOTE] 14 | .Libcurl 是可替代的 15 | ==== 16 | 与 Elasticsearch-php 客户端绑定的默认 HTTP handlers 需要 PHP 的 Libcurl 扩展,但客户端也并非一定要用 Libcurl 扩展。如果你有 17 | 一台主机没有安装 Libcurl 扩展,你可以使用基于 PHP streams 的 HTTP handler 来替代。但是性能会变差,因为 Libcurl 扩展要快得多。 18 | ==== 19 | 20 | === 版本矩阵 21 | 22 | Elasticsearch-PHP 的版本要和 Elasticsearch 版本适配。 23 | 24 | Elasticsearch-PHP 的 master 分支总是与 Elasticsearch 的 master 分支相一致,但不建议在生产环境代码中使用 dev-master 分支。 25 | 26 | [width="40%",options="header",frame="topbot"] 27 | |============================ 28 | |Elasticsearch Version | Elasticsearch-PHP Branch 29 | | >= 6.0 | `6.0` 30 | | >= 5.0, <= 6.0 | `5.0` 31 | | >= 1.0, <= 5.0 | `1.0`, `2.0` 32 | | <= 0.90.* | `0.4` 33 | |============================ 34 | 35 | === Composer 安装 36 | 37 | * 在 `composer.json` 文件中增加 elasticsearch-php。如果你是新建项目,那么把以下的代码复制粘贴到 `composer.json` 就行了。如果是在现有项目中添加 elasticsearch-php,那么把 elasticsearch-php 添加到其它的包名后面即可: 38 | + 39 | [source,js] 40 | -------------------------------------------------- 41 | { 42 | "require": { 43 | "elasticsearch/elasticsearch": "~6.0" 44 | } 45 | } 46 | -------------------------------------------------- 47 | 48 | * 使用 composer 安装客户端:首先要用下面第一个命令来安装 `composer.phar` ,然后使用第二个命令来执行安装程序。composer 会自动下载所有的依赖,把下载的依赖存储在 /vendor/ 目录下,并且创建一个 autoloader: 49 | + 50 | [source,sh] 51 | -------------------------------------------------- 52 | curl -s http://getcomposer.org/installer | php 53 | php composer.phar install --no-dev 54 | -------------------------------------------------- 55 | + 56 | 关于 Composer 的详情请查看https://www.phpcomposer.com/[Composer 中文网]。 57 | 58 | * 最后加载 autoload.php。如果你现有项目是用 Composer 安装的,那么 autoload.php 也许已经在某处加载了,你就不必再加载。最后实例化一个客户端对象: 59 | + 60 | [source,php] 61 | -------------------------------------------------- 62 | require 'vendor/autoload.php'; 63 | 64 | $client = Elasticsearch\ClientBuilder::create()->build(); 65 | -------------------------------------------------- 66 | + 67 | 客户端对象的实例化主要是使用静态方法 `create()` ,这里会创建一个 ClientBuilder 对象,主要是用来设置一些自定义配置。如果你配置完了,你就可以调用 `build()` 方法来创建一个 `Client` 对象。我们会在配置一节中详细说明配置方法。 68 | 69 | === --no-dev标志 70 | 71 | 你会注意到安装命令行指定了 `--no-dev` 。这里是防止 Composer 安装各种测试依赖包和开发依赖包。对于普通用户没有必要安装测试包。特别是开发依赖包包含了 Elasticsearch 的一套源码,这是为了以 REST API 的方式进行测试。然而这对于非开发者来说太大了,因此要使用 --no-dev。 72 | 73 | 如果你想帮助完善这个客户端类库,那就删掉 `--no-dev` 标志来进行测试吧。 -------------------------------------------------------------------------------- /selectors.asciidoc: -------------------------------------------------------------------------------- 1 | [[_selectors]] 2 | == 选择器 3 | 4 | 连接池维持一份连接清单,它决定节点在什么时候从活节点转变为死节点(或死节点转变为活节点)。然而连接池选择连接对象时是没有逻辑的,这份工作属于 Selector 类。 5 | 6 | 选择器(selector)的工作是从连接数组中返回一个连接。和连接池一样,也有几种选择器可供选择。 7 | 8 | === RoundRobinSelector(默认) 9 | 10 | 选择器通过轮询调度的方式来返回连接。例如在第一个请求中选择节点1,在第二请求中选择节点 2,以此类推。这确保集群中的节点平均负担流量。轮询调度是基于每个请求来执行的(例如,一个PHP脚本的所有请求轮流发送到不同的节点中)。 11 | 12 | `RoundRobinSelector` 是默认选择器,但如果你想明确地配置该选择器,你可以这样做: 13 | 14 | [source,php] 15 | -------------------------------------------------- 16 | $client = ClientBuilder::create() 17 | ->setSelector('\Elasticsearch\ConnectionPool\Selectors\RoundRobinSelector') 18 | ->build(); 19 | -------------------------------------------------- 20 | 21 | 注意:要通过命名空间加类名的方法来指定选择器。 22 | 23 | === StickyRoundRobinSelector 24 | 25 | 这个选择器具有“粘性”,它更喜欢重用同一个连接。例如,在第一个请求中选择节点1,选择器会重用节点1来发送随后的请求,直到节点请求失败。在节点1请求失败后,选择器会轮询至下一个可用节点,然后一直重用这个节点。 26 | 27 | 对许多 PHP 脚本来说,这是一个理想的策略。由于 PHP 脚本是无共享架构且会快速退出,为每个请求创建新连接通常是一种次优策略且会引起大量的开销。相反,在脚本运行期间“黏住”单个节点会更好。 28 | 29 | 这个选择器会默认会在初始化时把 hosts 随机打乱,但仍然保证集群中的节点平均负担流量。它动态地更改轮询方式,把轮询每个请求变成轮询每个脚本。 30 | 31 | 如果你使用 <<_future_mode>> ,这种选择器的“粘性”行为就不理想了,因为所有并行的请求会发送到集群中的同一个节点而非多个节点。当使用 Future 模式时,默认的 `RoundRobinSelector` 选择器会更好。 32 | 33 | 如果你要使用该选择器,你要这样做: 34 | 35 | [source,php] 36 | -------------------------------------------------- 37 | $client = ClientBuilder::create() 38 | ->setSelector('\Elasticsearch\ConnectionPool\Selectors\StickyRoundRobinSelector') 39 | ->build(); 40 | -------------------------------------------------- 41 | 42 | 注意:要通过命名空间加类名的方法来指定选择器。 43 | 44 | === RandomSelector 45 | 46 | 这种选择器仅仅返回一个随机的节点,不管节点是处于什么状态。这个选择器通常用做测试。 47 | 48 | 如果你要使用该选择器,你要这样做: 49 | 50 | [source,php] 51 | -------------------------------------------------- 52 | $client = ClientBuilder::create() 53 | ->setSelector('\Elasticsearch\ConnectionPool\Selectors\RandomSelector') 54 | ->build(); 55 | -------------------------------------------------- 56 | 57 | 注意:要通过命名空间加类名的方法来指定选择器。 58 | 59 | === 自定义选择器 60 | 61 | 你可以实现自定义选择器。自定义选择器必须实现 `SelectorInterface` 接口。 62 | 63 | [source,php] 64 | -------------------------------------------------- 65 | namespace MyProject\Selectors; 66 | 67 | use Elasticsearch\Connections\ConnectionInterface; 68 | use Elasticsearch\ConnectionPool\Selectors\SelectorInterface 69 | 70 | class MyCustomSelector implements SelectorInterface 71 | { 72 | 73 | /** 74 | * Selects the first connection 75 | * 76 | * @param array $connections Array of Connection objects 77 | * 78 | * @return ConnectionInterface 79 | */ 80 | public function select($connections) 81 | { 82 | // code here 83 | } 84 | 85 | } 86 | -------------------------------------------------- 87 | 88 | 然后你可以通过对象注入或命名空间实例化方式来使用自定义选择器: 89 | 90 | [source,php] 91 | -------------------------------------------------- 92 | $mySelector = new MyCustomSelector(); 93 | 94 | $client = ClientBuilder::create() 95 | ->setSelector($mySelector) // object injection 96 | ->setSelector('\MyProject\Selectors\FirstSelector') // or namespace 97 | ->build(); 98 | -------------------------------------------------- -------------------------------------------------------------------------------- /security.asciidoc: -------------------------------------------------------------------------------- 1 | [[_security]] 2 | == 安全 3 | 4 | Elasticsearch-PHP 客户端支持两种安全设置方式:HTTP 认证和 SSL 加密。 5 | 6 | === HTTP 认证 7 | 8 | 如果你的 Elasticsearch 是通过 HTTP 认证来维持安全,你就要为 Elasticsearch-PHP 客户端提供身份凭证(credentials),这样服务端才能认证客户端请求。在实例化客户端时,身份凭证(credentials)需要配置在 host 数组中: 9 | 10 | [source,php] 11 | -------------------------------------------------- 12 | $hosts = [ 13 | 'http://user:pass@localhost:9200', // HTTP Basic Authentication 14 | 'http://user2:pass2@other-host.com:9200' // Different credentials on different host 15 | ]; 16 | 17 | $client = ClientBuilder::create() 18 | ->setHosts($hosts) 19 | ->build(); 20 | -------------------------------------------------- 21 | 22 | 每个 host 都要添加身份凭证(credentials),这样的话每个 host 都拥有自己的身份凭证(credentials)。所有发送到集群中的请求都会根据访问节点来使用相应的身份凭证(credentials)。 23 | 24 | === SSL 加密 25 | 26 | 配置 SSL 会有些复杂。你要去识别 Certificate Authority (CA) 签名的证书或者自签名证书。 27 | 28 | [NOTE] 29 | .libcurl版本注意事项 30 | ==== 31 | 如果你觉得客户端已经正确配置 SSL,但是没有起效,请检查你的 libcurl 版本。在某些平台上,一些设置可能有效也可能无效,这取决于 32 | libcurl 版本号。例如直到 libcurl 7.37.1,OSX 平台的 libcurl 才添加 `--cacert` 选项。 `--cacert` 选项对应 PHP 的 `CURLOPT_CAINFO` 常量, 33 | 这就意味着自定义的证书在低版本下是无法使用的。 34 | 35 | 如果你现在正面临这个问题,请更新你的 libcurl,然后/或者查看 https://curl.haxx.se/changes.html[curl changelog] 有无增加该选项。 36 | ==== 37 | 38 | ==== 公共 CA 证书 39 | 40 | 如果你的证书是公共 CA 签名证书,且你的服务器用的是最新的根证书,你只需要在 host 中使用 https。客户端会自动识别 SSL 证书: 41 | 42 | [source,php] 43 | -------------------------------------------------- 44 | $hosts = [ 45 | 'https://localhost:9200' <1> 46 | ]; 47 | 48 | $client = ClientBuilder::create() 49 | ->setHosts($hosts) 50 | ->build(); 51 | -------------------------------------------------- 52 | <1> 注意:这里用的是 https 而非 http 53 | 54 | 如果服务器的根证书已经过期,你就要用证书 bundle。对于客户端来说,最好的方法是使用 https://github.com/composer/ca-bundle[composer/ca-bundle]。一旦安装好 ca-bundle,你要告诉客户端使用你提供的证书来替代系统的 bundle: 55 | 56 | [source,php] 57 | -------------------------------------------------- 58 | $hosts = ['https://localhost:9200']; 59 | $caBundle = \Composer\CaBundle\CaBundle::getBundledCaBundlePath(); 60 | 61 | $client = ClientBuilder::create() 62 | ->setHosts($hosts) 63 | ->setSSLVerification($caBundle) 64 | ->build(); 65 | -------------------------------------------------- 66 | 67 | ==== 自签名证书 68 | 69 | 自签名证书是指没有被公共 CA 签名的证书。自签名证书由你自己的组织来签名。在你确保安全发送自己的根证书前提下,自签名证书可用作内部使用的。当自签名证书暴露给公众客户时就不应该使用了,因为客户端容易受到中间人攻击。 70 | 71 | 如果你正使用自签名证书,你要给客户端提供证书路径。这与指定一个根 bundle 的语法一致,只是把根 bundle 替换为自签名证书: 72 | 73 | [source,php] 74 | -------------------------------------------------- 75 | $hosts = ['https://localhost:9200']; 76 | $myCert = 'path/to/cacert.pem'; 77 | 78 | $client = ClientBuilder::create() 79 | ->setHosts($hosts) 80 | ->setSSLVerification($myCert) 81 | ->build(); 82 | -------------------------------------------------- 83 | 84 | === 同时使用认证与 SSL 85 | 86 | 同时使用认证与 SSL 也是有可能的。在 URI 中指定 `https` 与身份凭证(credentials),同时提供 SSL 所需的自签名证书。例如下面的代码段就同时使用了 HTTP 认证和自签名证书: 87 | 88 | [source,php] 89 | -------------------------------------------------- 90 | $hosts = ['https://user:pass@localhost:9200']; 91 | $myCert = 'path/to/cacert.pem'; 92 | 93 | $client = ClientBuilder::create() 94 | ->setHosts($hosts) 95 | ->setSSLVerification($myCert) 96 | ->build(); 97 | -------------------------------------------------- 98 | -------------------------------------------------------------------------------- /community.asciidoc: -------------------------------------------------------------------------------- 1 | [[_community_dsls]] 2 | == 社区 DSLs 3 | 4 | === ElasticsearchDSL 5 | 6 | https://github.com/ongr-io/ElasticsearchDSL[Link: ElasticsearchDSL] 7 | 8 | [quote, ElasticsearchDSL] 9 | __________________________ 10 | 引入 Elasticsearch DSL 类库,目的是为 Elasticsearch bundle 和 Elasticsearch PHP 客户端提供对象查询语句构造器。你可以轻松地构造任何 Elasticsearch 查询语句且转变语句为数组。 11 | __________________________ 12 | 13 | === elasticsearcher 14 | 15 | https://github.com/madewithlove/elasticsearcher[Link: elasticsearcher] 16 | 17 | [quote, elasticsearcher] 18 | __________________________ 19 | 这个独立的包是位于 Elasticsearch PHP 客户端之上的一个轻量级的封装。它主要的目的是让应用中的查询语句和索引的构造更为简易。它并非要隐藏或替代 Elasticsearch PHP 客户端的功能。 20 | __________________________ 21 | 22 | == Community Integrations 23 | 24 | === Symfony 25 | 26 | ==== ONGR Elasticsearch Bundle 27 | 28 | https://github.com/ongr-io/ElasticsearchBundle[Link: ONGR Elasticsearch Bundle] 29 | 30 | [quote, ONGR Elasticsearch Bundle] 31 | __________________________ 32 | Elasticsearch Bundle was created in order to serve the need for professional elasticsearch 33 | integration with enterprise level Symfony 2 systems. This bundle is: 34 | 35 | - Supported by ONGR.io development team. 36 | - Uses the official elasticsearch-php client. 37 | - Ensures full integration with Symfony 2 framework. 38 | 39 | Technical goodies: 40 | 41 | - Provides nestable and DSL query builder to be executed by type repository services. 42 | - Uses Doctrine-like document / entities document-object mapping using annotations. 43 | - Query results iterators are provided for your convenience. 44 | - Registers console commands for index and types management and data import / export. 45 | - Designed in an extensible way for all your custom needs. 46 | __________________________ 47 | 48 | 49 | ==== FOS Elastica Bundle 50 | 51 | https://github.com/FriendsOfSymfony/FOSElasticaBundle[Link: FOS Elastica Bundle] 52 | 53 | [quote, FOS Elastica Bundle] 54 | __________________________ 55 | This bundle provides integration with https://github.com/ruflin/Elastica[Link: Elastica] for Symfony. Features include: 56 | 57 | - Integrates the Elastica library into a Symfony environment 58 | - Automatically generate mappings using a serializer 59 | - Listeners for Doctrine events for automatic indexing 60 | __________________________ 61 | 62 | 63 | === Drupal 64 | 65 | ==== Elasticsearch Connector 66 | 67 | https://www.drupal.org/project/elasticsearch_connector[Link: Elasticsearch Connector] 68 | 69 | [quote, Elasticsearch Connector] 70 | __________________________ 71 | Elasticsearch Connector is a set of modules designed to build a full Elasticsearch eco system in Drupal. 72 | __________________________ 73 | 74 | === Laravel 75 | 76 | ==== shift31/Laravel-Elasticsearch 77 | 78 | https://github.com/shift31/laravel-elasticsearch[Link: shift31/Laravel-Elasticsearch] 79 | 80 | [quote, Laravel-Elasticsearch] 81 | __________________________ 82 | This is a Laravel (4+) Service Provider for the official Elasticsearch low-level client. 83 | __________________________ 84 | 85 | 86 | ==== cviebrock/Laravel-Elasticsearch 87 | 88 | https://github.com/cviebrock/laravel-elasticsearch[Link: cviebrock/Laravel-Elasticsearch] 89 | 90 | [quote, Laravel-Elasticsearch] 91 | __________________________ 92 | An easy way to use the official Elastic Search client in your Laravel applications. 93 | __________________________ 94 | 95 | 96 | ==== Plastic 97 | 98 | https://github.com/sleimanx2/plastic[Link: Plastic] 99 | 100 | [quote, Plastic] 101 | __________________________ 102 | Plastic is an Elasticsearch ODM and mapper for Laravel. It renders the developer experience more enjoyable while using Elasticsearch, by providing a fluent syntax for mapping, querying, and storing eloquent models. 103 | __________________________ 104 | 105 | === Helper 106 | 107 | ==== Index Helper 108 | 109 | https://github.com/Nexucis/es-php-index-helper[Link: nexucis/es-php-index-helper] 110 | 111 | [quote, Index Helper] 112 | _____________________ 113 | This helper is a light library which wrap the official client elasticsearch-php. It will help you to manage your ES Indices with no downtime. 114 | This helper implements the philosophy described in the https://www.elastic.co/guide/en/elasticsearch/guide/master/index-aliases.html[official documentation] 115 | which can be summarized in a few words : *use alias instead of index directly* 116 | _____________________ 117 | -------------------------------------------------------------------------------- /php_json_objects.asciidoc: -------------------------------------------------------------------------------- 1 | [[php_json_objects]] 2 | == PHP 处理 JSON 数组或对象 3 | 4 | 客户端在关于 JSON 数组和 JSON 对象的处理和定义方面总是令人疑惑不已。尤其是由空对象和对象数组引起的问题。本节会展示一些 Elasticsearch JSON API 常见的数据格式,还会说明如何以 PHP 的语法来表达这些数据格式。 5 | 6 | === 空对象 7 | 8 | Elasticsearch API 在几个地方使用了空对象,这会对 PHP 造成影响。不像其它的语言,PHP 没有一个简便的符号来表示空对象,而许多开发者还不知道如何指定一个空对象。 9 | 10 | 设想在查询中增加 Highlight: 11 | 12 | [source,js] 13 | -------------------------------------------------- 14 | { 15 | "query" : { 16 | "match" : { 17 | "content" : "quick brown fox" 18 | } 19 | }, 20 | "highlight" : { 21 | "fields" : { 22 | "content" : {}<1> 23 | } 24 | } 25 | } 26 | -------------------------------------------------- 27 | <1> 这个空对象便会引起问题 28 | 29 | 问题就在于 PHP 会自动把 `"content" : {}` 转换成 `"content" : []` ,在 Elasticsearch DSL 中这样的数据格式是非法的。我们需要告诉 PHP 那个空对象就是一个空对象而非空数组。为了在查询中定义空对象,你需要这样做: 30 | 31 | [source,php] 32 | -------------------------------------------------- 33 | $params['body'] = array( 34 | 'query' => array( 35 | 'match' => array( 36 | 'content' => 'quick brown fox' 37 | ) 38 | ), 39 | 'highlight' => array( 40 | 'fields' => array( 41 | 'content' => new \stdClass()<1> 42 | ) 43 | ) 44 | ); 45 | $results = $client->search($params); 46 | -------------------------------------------------- 47 | <1> 使用 PHP 的 stdClass 对象来代表空对象,现在就可以解析为正确的 JSON 数据了。 48 | 49 | 通过使用一个 stdClass 对象,我们可以强制 `json_encode` 解析为空对象,而不是空数组。然而,这种冗余的写法是唯一解决 PHP 空对象的方法,没有简便的方法可以表示空对象。 50 | 51 | === 对象数组 52 | 53 | Elasticsearch DSL 的另一种常见的数据格式是对象数组。例如,假设在你的查询中增加排序: 54 | 55 | [source,js] 56 | -------------------------------------------------- 57 | { 58 | "query" : { 59 | "match" : { "content" : "quick brown fox" } 60 | }, 61 | "sort" : [ <1> 62 | {"time" : {"order" : "desc"}}, 63 | {"popularity" : {"order" : "desc"}} 64 | ] 65 | } 66 | -------------------------------------------------- 67 | <1> "sort" 内包含 JSON 对象数组。 68 | 69 | 这种形式很常见,但是在 PHP 中构建就稍微有些繁琐,因为这需要嵌套数组。用 PHP 写这种冗余的结构就让人读起来有点晦涩。为了构建对象数组,你要在数组中嵌套数组: 70 | 71 | [source,php] 72 | -------------------------------------------------- 73 | $params['body'] = array( 74 | 'query' => array( 75 | 'match' => array( 76 | 'content' => 'quick brown fox' 77 | ) 78 | ), 79 | 'sort' => array( <1> 80 | array('time' => array('order' => 'desc')), <2> 81 | array('popularity' => array('order' => 'desc')) <3> 82 | ) 83 | ); 84 | $results = $client->search($params); 85 | -------------------------------------------------- 86 | <1> 这里 encode 为 `"sort" : []` 87 | <2> 这里 encode 为 `{"time" : {"order" : "desc"}}` 88 | <3> 这里 encode 为 `{"popularity" : {"order" : "desc"}}` 89 | 90 | 如果你用的是 PHP5.4 及以上版本,我强烈要求你使用 `[]` 构建数组。这会让多维数组看起来易读些: 91 | 92 | [source,php] 93 | -------------------------------------------------- 94 | $params['body'] = [ 95 | 'query' => [ 96 | 'match' => [ 97 | 'content' => 'quick brown fox' 98 | ] 99 | ], 100 | 'sort' => [ 101 | ['time' => ['order' => 'desc']], 102 | ['popularity' => ['order' => 'desc']] 103 | ] 104 | ]; 105 | $results = $client->search($params); 106 | -------------------------------------------------- 107 | 108 | === 空对象数组 109 | 110 | 偶尔你会看到 DSL 需要上述两种数据格式。score 查询便是一个很好的例子,该查询有时需要一个对象数组,而有一些对象可能是一个空的 JSON 对象。 111 | 112 | 请看如下查询: 113 | 114 | [source,js] 115 | -------------------------------------------------- 116 | { 117 | "query":{ 118 | "function_score":{ 119 | "functions":[ 120 | { 121 | "random_score":{} 122 | } 123 | ], 124 | "boost_mode":"replace" 125 | } 126 | } 127 | } 128 | -------------------------------------------------- 129 | 130 | 我们用下面的 PHP 代码来构建这个查询: 131 | 132 | [source,php] 133 | -------------------------------------------------- 134 | $params['body'] = array( 135 | 'query' => array( 136 | 'function_score' => array( 137 | 'functions' => array( <1> 138 | array( <2> 139 | 'random_score' => new \stdClass() <3> 140 | ) 141 | ) 142 | ) 143 | ) 144 | ); 145 | $results = $client->search($params); 146 | -------------------------------------------------- 147 | <1> 这里 encode 为 `"functions" : []` 148 | <2> 这里 encode 为 `{ "random_score": {} }` 149 | <3> 这里 encode 为 `"random_score": {}` 150 | -------------------------------------------------------------------------------- /serializers.asciidoc: -------------------------------------------------------------------------------- 1 | [[_serializers]] 2 | == 序列化器 3 | 4 | 客户端有 3 种序列化器可用。你可能永远都不会更改序列化器,除非你有特殊需求或者要实现一个新的协议。 5 | 6 | 序列化器的工作是 encode 发送的请求体和 decode 返回的响应体。在 99% 的例子中,这就是一种简单转换为JSON数据或解析 JSON 数据的工具。 7 | 8 | 默认的序列化器是 `SmartSerializer` 。 9 | 10 | === SmartSerializer 11 | 12 | ==== Serialize() 13 | 14 | `SmartSerializer` 会先检查需要 encode 的数据。如果请求体是字符串,那么会直接发送到 Elasticsearch。这种方式允许用户提供原生JSON数据,或是字符串(提供给某些没有结构的 endpoint,例如 Analyze endpoint)。 15 | 16 | 如果数据是数组,则会被转换为 JSON 数据。如果数据是空数组,那么序列化器需要手动转换空数组( `[]` )为空对象( `{}` ),这样发送给 Elasticsearch 的请求体数据才是有效的 JSON 数据。 17 | 18 | ==== Deserialize() 19 | 20 | 当 decode 响应体数据时, `SmartSerializer` 会检测响应头的 `content_type` 来判断是否为合适的encode数据。假如数据 encode 为 JSON 数据,那么会用 `json_decode` 来解析 JSON 数据为数组。否则会以字符串的格式返回给客户端。 21 | 22 | 这个功能需要与 endpoint 协作,例如 `Cat` endpoints 会返回表格文本而非 JSON 数据。 23 | 24 | ==== 选择 SmartSerializer 25 | 26 | 客户端默认选择 `SmartSerializer` ,但如果你想手动地配置这个选择器,你可以在 ClientBuilder 对象中使用 `setSerializer()` 方法: 27 | 28 | [source,php] 29 | -------------------------------------------------- 30 | $client = ClientBuilder::create() 31 | ->setSerializer('\Elasticsearch\Serializers\SmartSerializer'); 32 | ->build(); 33 | -------------------------------------------------- 34 | 35 | 注意:要通过命名空间加类名的方法来配置序列化器。 36 | 37 | === ArrayToJSONSerializer 38 | 39 | ==== Serialize() 40 | 41 | `ArrayToJSONSerializer` 会先检查需要 encode 的数据。如果请求体是字符串,那么会直接发送到 Elasticsearch。这种方式允许用户提供原生 JSON 数据,或是字符串(提供给某些没有结构的 endpoint,例如 Analyze endpoint)。 42 | 43 | 如果数据是数组,则会被转换为 JSON 数据。如果数据是空数组,那么序列化器需要手动转换空数组( `[]` )为空对象( `{}` ),这样发送给 Elasticsearch 的请求体数据才是有效的 JSON 数据。 44 | 45 | ==== Deserialize() 46 | 47 | 当 decode 响应体数据时,所有数据都会 encode 由 JSON 数据 decode 为 JSON 数据。如果数据不是有效的 JSON 数据,那么会返回 `null` 给客户端。 48 | 49 | ==== 选择 ArrayToJSONSerializer 50 | 51 | 你可以通过使用 ClientBuilder 对象的 `setSerializer()` 方法来选择 `ArrayToJSONSerializer` : 52 | 53 | [source,php] 54 | -------------------------------------------------- 55 | $client = ClientBuilder::create() 56 | ->setSerializer('\Elasticsearch\Serializers\ArrayToJSONSerializer'); 57 | ->build(); 58 | -------------------------------------------------- 59 | 60 | 注意:要通过命名空间加类名的方法来配置序列化器。 61 | 62 | === EverythingToJSONSerializer 63 | 64 | ==== Serialize() 65 | 66 | `EverythingToJSONSerializer` 会把一切数据转换为JSON数据。 67 | 68 | 如果数据是空数组,那么序列化器需要手动转换空数组( `[]` )为空对象( `{}` ),这样发送给 Elasticsearch 的请求体数据才是有效的 JSON 数据。 69 | 70 | 如果数据不是数组且(或)没有转换为 JSON 数据,那么这个方法会返回 `null` 给客户端。 71 | 72 | ==== Deserialize() 73 | 74 | 当 decode 响应体数据时,所有数据都会 encode 由 JSON 数据 decode 为 JSON 数据。如果数据不是有效的 JSON 数据,那么会返回 `null` 给客户端。 75 | 76 | ==== 选择 EverythingToJSONSerializer 77 | 78 | 你可以通过使用 ClientBuilder 对象的 `setSerializer()` 方法来选择 `EverythingToJSONSerializer` : 79 | 80 | [source,php] 81 | -------------------------------------------------- 82 | $client = ClientBuilder::create() 83 | ->setSerializer('\Elasticsearch\Serializers\EverythingToJSONSerializer'); 84 | ->build(); 85 | -------------------------------------------------- 86 | 87 | 注意:要通过命名空间加类名的方法来配置序列化器。 88 | 89 | === 实现自定义序列化器 90 | 91 | 如果你想使用自定义序列器,你需要实现 `SerializerInterface` 接口。请记住,对于所有的 endpoint 和连接来说,客户端只使用一个序列器对象。 92 | 93 | [source,php] 94 | -------------------------------------------------- 95 | class MyCustomSerializer implements SerializerInterface 96 | { 97 | 98 | /** 99 | * Serialize request body 100 | * 101 | * @param string|array $data Request body 102 | * 103 | * @return string 104 | */ 105 | public function serialize($data) 106 | { 107 | // code here 108 | } 109 | 110 | /** 111 | * Deserialize response body 112 | * 113 | * @param string $data Response body 114 | * @param array $headers Response Headers 115 | * 116 | * @return array|string 117 | */ 118 | public function deserialize($data, $headers) 119 | { 120 | // code here 121 | } 122 | } 123 | -------------------------------------------------- 124 | 125 | 然后为了使用你自定义的序列化器,你可以通过使用 ClientBuilder 对象的 `setSerializer()` 方法来配置序列化器(命名空间加类名格式): 126 | 127 | [source,php] 128 | -------------------------------------------------- 129 | $client = ClientBuilder::create() 130 | ->setSerializer('\MyProject\Serializers\MyCustomSerializer'); 131 | ->build(); 132 | -------------------------------------------------- 133 | 134 | 如果你的序列化器在注入到客户端前已经实例化,或者序列化器对象需要进一步初始化,你可以通过以下方式来实例化序列化器对象并注入到客户端: 135 | 136 | [source,php] 137 | -------------------------------------------------- 138 | $mySerializer = new MyCustomSerializer($a, $b, $c); 139 | $mySerializer->setFoo("bar"); 140 | 141 | $client = ClientBuilder::create() 142 | ->setSerializer($mySerializer); 143 | ->build(); 144 | -------------------------------------------------- 145 | -------------------------------------------------------------------------------- /connection-pool.asciidoc: -------------------------------------------------------------------------------- 1 | [[_connection_pool]] 2 | == 连接池 3 | 4 | 连接池是客户端内的一个对象,主要是维持现有节点的连接。理论上来讲,节点只有死节点与活节点。 5 | 6 | 然而在现实世界中,事情绝不会这么明确。有时候节点是处在 _“可能挂了但还未确认”_ 、 _“连接超时但未知原因”_ 或 _“最近挂过但现在可用”_ 的灰色地带中。而连接池的工作就是管理这些无规则的连接,并为客户端提供最稳定的连接状态。 7 | 8 | 如果一个连接池找不到一个活节点来发送查询,那么就会返回一个 `NoNodesAvailableException` 异常给客户端。这里跟最大重连次数(retry)有所不同。假如有这么一个例子:你的集群中可能有 10 个节点。你发送一个请求,其中有 9 个节点因为连接超时而请求失败。而第 10 个节点发送请求成功并成功执行请求。在上述例子中,前 9 个节点会被标记为死节点(连接池处于使用状态才会被标记),且它们的“死亡”定时器会启动生效。 9 | 10 | 当要发送下一个请求时,节点1-9是被标记为死节点,所以请求会跳过这些节点。请求只会发送到唯一的活节点 10 中,而假如发送到这个节点也失败了,那么就会返回 `NoNodesAvailableException` 。你会留意到这里的发送次数比重连次数(retries)的值要少,因为重连次数只适用于活节点。在这种情况下,只有一个节点是活节点,请求失败后就会返回 `NoNodesAvailableException` 。 11 | 12 | 这里有几种连接池可供选择: 13 | 14 | === staticNoPingConnectionPool(默认) 15 | 16 | 连接池维持一个静态的 hosts 清单,这些 hosts 在客户端初始化时都被假定为活节点。如果一个节点处理请求失败,那么该节点会被标记为死节点并持续 60 秒,而请求会发送到下一个节点。60 秒过后,节点则会再生并加入请求轮询中。每增加一次请求失败次数都会导致死亡时间以指数级别增长。 17 | 18 | 请求成功一次后会重置 "failed ping timeout" 计数器。 19 | 20 | 如果你想明确的设置连接池为 `StaticNoPingConnectionPool` ,你可能要在ClientBuilder对象中使用 `setConnectionPool()` 方法: 21 | 22 | [source,php] 23 | -------------------------------------------------- 24 | $client = ClientBuilder::create() 25 | ->setConnectionPool('\Elasticsearch\ConnectionPool\StaticNoPingConnectionPool', []) 26 | ->build(); 27 | -------------------------------------------------- 28 | 29 | 注意:要通过命名空间加类名的方法来指定连接池。 30 | 31 | === StaticConnectionPool 32 | 33 | `StaticConnectionPool` 除了要在使用前 ping 节点来确定是否为活节点,其它的特性与 `StaticNoPingConnectionPool` 一致。这可能对于执行时间较长的脚本比较有用,但这往往会增加额外开销,因为对一般的PHP脚本来说这是不必要的。 34 | 35 | 使用 `StaticConnectionPool` 的方法: 36 | 37 | [source,php] 38 | -------------------------------------------------- 39 | $client = ClientBuilder::create() 40 | ->setConnectionPool('\Elasticsearch\ConnectionPool\StaticConnectionPool', []) 41 | ->build(); 42 | -------------------------------------------------- 43 | 44 | 注意:要通过命名空间加类名的方法来指定连接池。 45 | 46 | === SimpleConnectionPool 47 | 48 | `SimpleConnectionPool` 仅仅返回选择器(Selector)指定的下个节点信息,它不监测节点的“生死状态”。不管节点是活节点还是死节点,这种连接池都会返回节点信息给客户端。它仅仅是个简单的静态 host 连接池。 49 | 50 | `SimpleConnectionPool` 不建议常规使用,但是它是个有用的调试工具。 51 | 52 | 使用 `SimpleConnectionPool` 的方法: 53 | 54 | [source,php] 55 | -------------------------------------------------- 56 | $client = ClientBuilder::create() 57 | ->setConnectionPool('\Elasticsearch\ConnectionPool\SimpleConnectionPool', []) 58 | ->build(); 59 | -------------------------------------------------- 60 | 61 | 注意:要通过命名空间加类名的方法来指定连接池。 62 | 63 | === SniffingConnectionPool 64 | 65 | `SniffingConnectionPool` 与前面的两个静态连接池有所不同,它是动态的。用户提供 hosts 种子,而客户端则会嗅探这些 hosts 并发现集群的其余节点。 `SniffingConnectionPool` 通过 Cluster State API 来实现嗅探。当集群添加新节点或删除节点,客户端会更新连接池的活跃连接。 66 | 67 | 使用 `SniffingConnectionPool` 的方法: 68 | 69 | [source,php] 70 | -------------------------------------------------- 71 | $client = ClientBuilder::create() 72 | ->setConnectionPool('\Elasticsearch\ConnectionPool\SniffingConnectionPool', []) 73 | ->build(); 74 | -------------------------------------------------- 75 | 76 | 注意:要通过命名空间加类名的方法来指定连接池。 77 | 78 | === 自定义连接池 79 | 80 | 如果你要是实现自定义连接池,你的类则必须实现 `ConnectionPoolInterface` 接口: 81 | 82 | [source,php] 83 | -------------------------------------------------- 84 | class MyCustomConnectionPool implements ConnectionPoolInterface 85 | { 86 | 87 | /** 88 | * @param bool $force 89 | * 90 | * @return ConnectionInterface 91 | */ 92 | public function nextConnection($force = false) 93 | { 94 | // code here 95 | } 96 | 97 | /** 98 | * @return void 99 | */ 100 | public function scheduleCheck() 101 | { 102 | // code here 103 | } 104 | } 105 | -------------------------------------------------- 106 | 107 | 然后你要实例化自定义的连接池并注入到 ClientBuilder: 108 | 109 | [source,php] 110 | -------------------------------------------------- 111 | $myConnectionPool = new MyCustomConnectionPool(); 112 | 113 | $client = ClientBuilder::create() 114 | ->setConnectionPool($myConnectionPool, []) 115 | ->build(); 116 | -------------------------------------------------- 117 | 118 | 如果你的连接池只有较小的变更,你可以考虑扩展 `AbstractConnectionPool` ,它提供一些具体的助手方法。如果你不选择这种做法,你就要确保连接池拥有兼容的构造方法(因为在接口中没有定义好): 119 | 120 | [source,php] 121 | -------------------------------------------------- 122 | class MyCustomConnectionPool extends AbstractConnectionPool implements ConnectionPoolInterface 123 | { 124 | 125 | public function __construct($connections, SelectorInterface $selector, ConnectionFactory $factory, $connectionPoolParams) 126 | { 127 | parent::__construct($connections, $selector, $factory, $connectionPoolParams); 128 | } 129 | 130 | /** 131 | * @param bool $force 132 | * 133 | * @return ConnectionInterface 134 | */ 135 | public function nextConnection($force = false) 136 | { 137 | // code here 138 | } 139 | 140 | /** 141 | * @return void 142 | */ 143 | public function scheduleCheck() 144 | { 145 | // code here 146 | } 147 | } 148 | -------------------------------------------------- 149 | 150 | 假如你的构造方法与 `AbstractConnectionPool` 的构造方法相同,你可以用对象注入或命名空间实例化来设置连接池: 151 | 152 | [source,php] 153 | -------------------------------------------------- 154 | $myConnectionPool = new MyCustomConnectionPool(); 155 | 156 | $client = ClientBuilder::create() 157 | ->setConnectionPool($myConnectionPool, []) // object injection 158 | ->setConnectionPool('/MyProject/ConnectionPools/MyCustomConnectionPool', []) // or namespace 159 | ->build(); 160 | -------------------------------------------------- 161 | 162 | === 选择什么连接池?PHP 和连接池的关系 163 | 164 | 初看觉得 `sniffingConnectionPool` 似乎比较高级。对许多语言来说当然如此。但是 PHP 则有些不同。 165 | 166 | 因为 PHP 是无共享架构(share-nothing architecture),php 脚本实例化后无法维持一个连接池。这意味着每个脚本在重新执行时都要负责创建、维持和销毁连接。 167 | 168 | 嗅探是相对轻量的操作(调用一次API到 `/_cluster/state` ,然后 ping 每个节点),但是对于某些 PHP 程序来说,这可能是一笔不可忽视的开销。一般的 PHP 脚本可能会加载客户端,执行一些请求然后关闭。想象一下这个脚本每秒调用 1000 次: `SniffingConnectionPool` 会每秒执行嗅探和 ping 所有节点 1000 次。嗅探程序则会增加大量的开销。 169 | 170 | 在实际中,如果你的脚本只是执行一些请求,用嗅探就太粗暴了。嗅探对于常驻进程来说往往更加有用。 171 | 172 | 基于上述原因,默认连接池才设置为当前的 `staticNoPingConnectionPool` 。当然你可以更改默认连接池,但我们强烈建议你进行测试并确保连接池对于性能没有产生不良影响。 173 | -------------------------------------------------------------------------------- /crud.asciidoc: -------------------------------------------------------------------------------- 1 | [[_indexing_documents]] 2 | == 索引文档 3 | 4 | 当你要在 Elasticsearch 增加文档时,你就需要索引 JSON 文档。JSON 文档会映射 PHP 关联数组,因为 PHP 关联数组可以 encode 为 JSON 数据格式。 5 | 6 | 因此在 Elasticsearch-PHP 中你可以传递关联数组给客户端来索引文档。我们会概述几种方法来增加文档到 Elasticsearch。 7 | 8 | === 单一文档索引 9 | 10 | 当索引一个文档时,你可以提供一个 ID 或者让 Elasticsearch 自动生成。 11 | 12 | 提供 ID 值: 13 | 14 | [source,php] 15 | -------------------------------------------------- 16 | $params = [ 17 | 'index' => 'my_index', 18 | 'type' => 'my_type', 19 | 'id' => 'my_id', 20 | 'body' => [ 'testField' => 'abc'] 21 | ]; 22 | 23 | // Document will be indexed to my_index/my_type/my_id 24 | $response = $client->index($params); 25 | -------------------------------------------------- 26 | 27 | 不提供 ID 值: 28 | 29 | [source,php] 30 | -------------------------------------------------- 31 | $params = [ 32 | 'index' => 'my_index', 33 | 'type' => 'my_type', 34 | 'body' => [ 'testField' => 'abc'] 35 | ]; 36 | 37 | // Document will be indexed to my_index/my_type/ 38 | $response = $client->index($params); 39 | -------------------------------------------------- 40 | 41 | 如果你需要设置其他的参数,如 `routing` 的值,你可以指定这些参数到 `index` , `type` 等参数后。例如,索引一个新的文档时设置 routing 值和 timestamp 值: 42 | 43 | [source,php] 44 | -------------------------------------------------- 45 | $params = [ 46 | 'index' => 'my_index', 47 | 'type' => 'my_type', 48 | 'id' => 'my_id', 49 | 'routing' => 'company_xyz', 50 | 'timestamp' => strtotime("-1d"), 51 | 'body' => [ 'testField' => 'abc'] 52 | ]; 53 | 54 | 55 | $response = $client->index($params); 56 | -------------------------------------------------- 57 | 58 | === 批量(bulk)索引 59 | 60 | Elasticsearch 也支持批量(bulk)索引文档。bulk API 要求提供 JSON 格式的 action/元数据 键值对。在 PHP 中构建批量文档数据也是相似的。你首先要创建一个 action 数组对象(如 `index` 对象),然后你还要创建一个 body 对象。而 PHP 程序则重复上述操作构建文档数据。 61 | 62 | 一个简单的例子如下所示: 63 | 64 | [source,php] 65 | -------------------------------------------------- 66 | for($i = 0; $i < 100; $i++) { 67 | $params['body'][] = [ 68 | 'index' => [ 69 | '_index' => 'my_index', 70 | '_type' => 'my_type', 71 | ] 72 | ]; 73 | 74 | $params['body'][] = [ 75 | 'my_field' => 'my_value', 76 | 'second_field' => 'some more values' 77 | ]; 78 | } 79 | 80 | $responses = $client->bulk($params); 81 | -------------------------------------------------- 82 | 83 | 实际上在一次 bulk 请求中发送数量会比文档实际数量少。如果是这种情况,你就要设置批量值然后周期性地发送: 84 | 85 | [source,php] 86 | -------------------------------------------------- 87 | $params = ['body' => []]; 88 | 89 | for ($i = 1; $i <= 1234567; $i++) { 90 | $params['body'][] = [ 91 | 'index' => [ 92 | '_index' => 'my_index', 93 | '_type' => 'my_type', 94 | '_id' => $i 95 | ] 96 | ]; 97 | 98 | $params['body'][] = [ 99 | 'my_field' => 'my_value', 100 | 'second_field' => 'some more values' 101 | ]; 102 | 103 | // Every 1000 documents stop and send the bulk request 104 | if ($i % 1000 == 0) { 105 | $responses = $client->bulk($params); 106 | 107 | // erase the old bulk request 108 | $params = ['body' => []]; 109 | 110 | // unset the bulk response when you are done to save memory 111 | unset($responses); 112 | } 113 | } 114 | 115 | // Send the last batch if it exists 116 | if (!empty($params['body'])) { 117 | $responses = $client->bulk($params); 118 | } 119 | -------------------------------------------------- 120 | 121 | [[_getting_documents]] 122 | == 获取文档 123 | 124 | Elasticsearch 提供实时获取文档的方法。这意味着只要文档被索引且客户端收到消息确认后,你就可以立即在任何的分片中检索文档。Get 操作通过 `index/type/id` 方式请求一个文档信息: 125 | 126 | [source,js] 127 | -------------------------------------------------- 128 | $params = [ 129 | 'index' => 'my_index', 130 | 'type' => 'my_type', 131 | 'id' => 'my_id' 132 | ]; 133 | 134 | // Get doc at /my_index/my_type/my_id 135 | $response = $client->get($params); 136 | -------------------------------------------------- 137 | 138 | [[_updating_documents]] 139 | == 更新文档 140 | 141 | 更新文档操作既可以完全覆盖现存文档全部字段,又可以部分更新字段(更改现存字段,或添加新字段)。 142 | 143 | === 部分更新 144 | 145 | 如果你要部分更新文档(如更改现存字段,或添加新字段),你可以在 body 参数中指定一个 doc 参数。这样 doc 参数内的字段会与现存字段进行合并。 146 | 147 | [source,php] 148 | -------------------------------------------------- 149 | $params = [ 150 | 'index' => 'my_index', 151 | 'type' => 'my_type', 152 | 'id' => 'my_id', 153 | 'body' => [ 154 | 'doc' => [ 155 | 'new_field' => 'abc' 156 | ] 157 | ] 158 | ]; 159 | 160 | // Update doc at /my_index/my_type/my_id 161 | $response = $client->update($params); 162 | -------------------------------------------------- 163 | 164 | === script 更新 165 | 166 | 有时你要执行一个脚本来进行更新操作,如对字段进行自增操作或添加新字段。为了执行一个脚本更新,你要提供脚本命令和一些参数: 167 | 168 | [source,php] 169 | -------------------------------------------------- 170 | $params = [ 171 | 'index' => 'my_index', 172 | 'type' => 'my_type', 173 | 'id' => 'my_id', 174 | 'body' => [ 175 | 'script' => 'ctx._source.counter += count', 176 | 'params' => [ 177 | 'count' => 4 178 | ] 179 | ] 180 | ]; 181 | 182 | $response = $client->update($params); 183 | -------------------------------------------------- 184 | 185 | === Upserts 更新 186 | 187 | Upserts 操作是指“更新或插入”操作。这意味着一个 upsert 操作会先执行 script 更新,如果文档不存在(或是你更新的字段不存在),则会插入一个默认值。 188 | 189 | [source,php] 190 | -------------------------------------------------- 191 | $params = [ 192 | 'index' => 'my_index', 193 | 'type' => 'my_type', 194 | 'id' => 'my_id', 195 | 'body' => [ 196 | 'script' => 'ctx._source.counter += count', 197 | 'params' => [ 198 | 'count' => 4 199 | ], 200 | 'upsert' => [ 201 | 'counter' => 1 202 | ] 203 | ] 204 | ]; 205 | 206 | $response = $client->update($params); 207 | -------------------------------------------------- 208 | 209 | [[_deleting_documents]] 210 | == 删除文档 211 | 212 | 通过指定文档的 `/index/type/id` 路径可以删除文档: 213 | 214 | [source,php] 215 | -------------------------------------------------- 216 | $params = [ 217 | 'index' => 'my_index', 218 | 'type' => 'my_type', 219 | 'id' => 'my_id' 220 | ]; 221 | 222 | // Delete doc at /my_index/my_type/my_id 223 | $response = $client->delete($params); 224 | -------------------------------------------------- 225 | -------------------------------------------------------------------------------- /futures.asciidoc: -------------------------------------------------------------------------------- 1 | [[_future_mode]] 2 | == Future 模式 3 | 4 | 客户端提供 future 模式(或叫异步模式)。future 模式允许批量发送请求(并行发送到集群),这对于提高性能和生产力有极大帮助。 5 | 6 | PHP 是单线程的脚本语言,然而 libcurl 的 multi interface 功能使得像 PHP 这种单线程的语言可以批量发送请求,从而获得并发性特征。批量请求是通过底层的多线程 libcurl 库并行的发送请求给 Elasticsearch,而返回给PHP的数据也是批量的。 7 | 8 | 在单线程环境下,执行 `n` 个请求的时间等于 `n` 个请求时间相加。在 multi interface 功能下,执行 `n` 个请求的时间等于最慢的一个请求时间。 9 | 10 | 除此以外,multi-interface 功能允许批量请求同时发送到不同的主机,这意味着 Elasticsearch-PHP 可以更高效地利用集群。 11 | 12 | === 使用 Future 模式 13 | 14 | 使用这种模式相对简单,只是你要写多一点代码。为了开启 future 模式,在 client 选项中增加 `future` 参数,并设置值为 `'lazy'` : 15 | 16 | [source,php] 17 | -------------------------------------------------- 18 | $client = ClientBuilder::create()->build(); 19 | 20 | $params = [ 21 | 'index' => 'test', 22 | 'type' => 'test', 23 | 'id' => 1, 24 | 'client' => [ 25 | 'future' => 'lazy' 26 | ] 27 | ]; 28 | 29 | $future = $client->get($params); 30 | -------------------------------------------------- 31 | 32 | 这里会返回一个 _future_ 对象,而不是真正的响应数据。future 对象是待处理对象,它看起来就像是个占位符。你可以把 future 对象当成是普通对象在代码中传递使用。当你需要响应数据时,你可以解析 future 对象。如果 future 对象已经被解析,可以立即使用响应数据。如果 future 对象还没被解析完,那么解析动作会阻塞 PHP 脚本的执行,直到解析完成。 33 | 34 | 在实际应用中,你可以通过设置 `future: lazy` 键值对构造一个请求队列,而返回的 future 对象直到解析完成,程序才会继续执行。无论什么时候,全部的请求都是以并行方式发送到集群,以异步方式返回给 curl。 35 | 36 | 这听起来好复杂,但由于RingPHP的 `FutureArray` 接口,这些操作则变得很简单。它让 future 对象看起来像是一个关联数组。例如: 37 | 38 | [source,php] 39 | -------------------------------------------------- 40 | $client = ClientBuilder::create()->build(); 41 | 42 | $params = [ 43 | 'index' => 'test', 44 | 'type' => 'test', 45 | 'id' => 1, 46 | 'client' => [ 47 | 'future' => 'lazy' 48 | ] 49 | ]; 50 | 51 | $future = $client->get($params); 52 | 53 | $doc = $future['_source']; // This call will block and force the future to resolve 54 | -------------------------------------------------- 55 | 56 | 就像通常的响应数据那样,future 对象可以用迭代关联数组的方式解析特定的值(轮流解析未解析的请求和值)。这样就可以写成如下形式: 57 | 58 | [source,php] 59 | -------------------------------------------------- 60 | $client = ClientBuilder::create()->build(); 61 | $futures = []; 62 | 63 | for ($i = 0; $i < 1000; $i++) { 64 | $params = [ 65 | 'index' => 'test', 66 | 'type' => 'test', 67 | 'id' => $i, 68 | 'client' => [ 69 | 'future' => 'lazy' 70 | ] 71 | ]; 72 | 73 | $futures[] = $client->get($params); //queue up the request 74 | } 75 | 76 | 77 | foreach ($futures as $future) { 78 | // access future's values, causing resolution if necessary 79 | echo $future['_source']; 80 | } 81 | -------------------------------------------------- 82 | 83 | 请求队列会并行执行,执行后赋值给 futures 数组。每批请求默认为 100 个。 84 | 85 | 如果你想强制解析 future 对象,但又不立刻获取响应数据。你可以用 future 对象的 `wait()` 方法来强制解析: 86 | 87 | [source,php] 88 | -------------------------------------------------- 89 | $client = ClientBuilder::create()->build(); 90 | $futures = []; 91 | 92 | for ($i = 0; $i < 1000; $i++) { 93 | $params = [ 94 | 'index' => 'test', 95 | 'type' => 'test', 96 | 'id' => $i, 97 | 'client' => [ 98 | 'future' => 'lazy' 99 | ] 100 | ]; 101 | 102 | $futures[] = $client->get($params); //queue up the request 103 | } 104 | 105 | //wait() forces future resolution and will execute the underlying curl batch 106 | $futures[999]->wait(); 107 | -------------------------------------------------- 108 | 109 | === 更改批量值 110 | 111 | 默认的批量值为 100 个,这意味着在客户端强制 future 对象解析前(执行 `curl_multi` 调用),队列可以容纳 100 个请求。批量值可以更改,取决于你的需求。批量值的调整是通过配置 HTTP handler 时设置 `max_handles` 参数来实现: 112 | 113 | [source,php] 114 | -------------------------------------------------- 115 | $handlerParams = [ 116 | 'max_handles' => 500 117 | ]; 118 | 119 | $defaultHandler = ClientBuilder::defaultHandler($handlerParams); 120 | 121 | $client = ClientBuilder::create() 122 | ->setHandler($defaultHandler) 123 | ->build(); 124 | -------------------------------------------------- 125 | 126 | 上面的设置会更改批量发送数量为 500。注意:不管队列数量是否为最大批量值,强制解析 future 对象都会引起底层的 curl 执行批量请求操作。在如下的示例中,只有 499 个对象加入队列,但最后的 future 对象被解析会引起强制发送批量请求: 127 | 128 | [source,php] 129 | -------------------------------------------------- 130 | $handlerParams = [ 131 | 'max_handles' => 500 132 | ]; 133 | 134 | $defaultHandler = ClientBuilder::defaultHandler($handlerParams); 135 | 136 | $client = ClientBuilder::create() 137 | ->setHandler($defaultHandler) 138 | ->build(); 139 | 140 | $futures = []; 141 | 142 | for ($i = 0; $i < 499; $i++) { 143 | $params = [ 144 | 'index' => 'test', 145 | 'type' => 'test', 146 | 'id' => $i, 147 | 'client' => [ 148 | 'future' => 'lazy' 149 | ] 150 | ]; 151 | 152 | $futures[] = $client->get($params); //queue up the request 153 | } 154 | 155 | // resolve the future, and therefore the underlying batch 156 | $body = $future[499]['body']; 157 | -------------------------------------------------- 158 | 159 | === 各种批量执行 160 | 161 | 队列里面允许存在各种请求。比如,你可以把 get 请求、index 请求和 search 请求放到队列里面: 162 | 163 | [source,php] 164 | -------------------------------------------------- 165 | $client = ClientBuilder::create()->build(); 166 | $futures = []; 167 | 168 | $params = [ 169 | 'index' => 'test', 170 | 'type' => 'test', 171 | 'id' => 1, 172 | 'client' => [ 173 | 'future' => 'lazy' 174 | ] 175 | ]; 176 | 177 | $futures['getRequest'] = $client->get($params); // First request 178 | 179 | $params = [ 180 | 'index' => 'test', 181 | 'type' => 'test', 182 | 'id' => 2, 183 | 'body' => [ 184 | 'field' => 'value' 185 | ], 186 | 'client' => [ 187 | 'future' => 'lazy' 188 | ] 189 | ]; 190 | 191 | $futures['indexRequest'] = $client->index($params); // Second request 192 | 193 | $params = [ 194 | 'index' => 'test', 195 | 'type' => 'test', 196 | 'body' => [ 197 | 'query' => [ 198 | 'match' => [ 199 | 'field' => 'value' 200 | ] 201 | ] 202 | ], 203 | 'client' => [ 204 | 'future' => 'lazy' 205 | ] 206 | ]; 207 | 208 | $futures['searchRequest'] = $client->search($params); // Third request 209 | 210 | // Resolve futures...blocks until network call completes 211 | $searchResults = $futures['searchRequest']['hits']; 212 | 213 | // Should return immediately, since the previous future resolved the entire batch 214 | $doc = $futures['getRequest']['_source']; 215 | -------------------------------------------------- 216 | 217 | === 警告 218 | 219 | 使用 future 模式时需要注意几点。最大也是最明显的问题是:你要自己去解析 future 对象。这挺麻烦的,而且偶尔会引起一些意料不到的状况。 220 | 221 | 例如,假如你手动使用 `wait()` 方法解析,在需要重新构建 future 对象并解析的情况下,你也许要调用好几次 `wait()` 方法。这是因为每次重新构造 future 对象都会引起 future 对象的重新赋值(覆盖解析结果),所以每个 future 对象都要重新解析获取结果。 222 | 223 | 如果你使用 ArrayInterface 返回的结果( `$response['hits']['hits']` )则不用进行额外处理。然而 FutureArrayInterface 就要全面解析 future 对象才能使用响应数据。 224 | 225 | 另外一点是一些方法会失效。比如 exists 方法( `$client->exists()` , `$client->indices()->exists` , `$client->indices->templateExists()` 等)在正常情况下会返回 true 或 false。 226 | 227 | 当使用 future 模式时,future 对象还未封装好,这代表客户端无法检测响应结果和返回 true 或 false。所以你会得到从 Elasticsearch 返回的未封装响应数据,而你不得不对这些数据进行处理。 228 | 229 | 这些注意事项也适用于 `ping()` 方法。 -------------------------------------------------------------------------------- /search-operations.asciidoc: -------------------------------------------------------------------------------- 1 | [[_search_operations]] 2 | == 搜索操作 3 | 4 | 呃......这个项目如果没有什么特别之处就不叫 elasticsearch 了!现在一起来聊聊客户端的搜索操作。 5 | 6 | 在命名方案规范的前提下,客户端拥有一切的查询权限,也拥有获取 REST API 公开的一切参数的权限。现在来看看一些示例,方便你熟悉这些语法规则。 7 | 8 | === Match查询 9 | 10 | 以下是 Match 查询的标准 curl 格式: 11 | 12 | [source,js] 13 | -------------------------------------------------- 14 | curl -XGET 'localhost:9200/my_index/my_type/_search' -d '{ 15 | "query" : { 16 | "match" : { 17 | "testField" : "abc" 18 | } 19 | } 20 | }' 21 | -------------------------------------------------- 22 | 23 | 而这里则是客户端构建的同样的查询: 24 | 25 | [source,php] 26 | -------------------------------------------------- 27 | $params = [ 28 | 'index' => 'my_index', 29 | 'type' => 'my_type', 30 | 'body' => [ 31 | 'query' => [ 32 | 'match' => [ 33 | 'testField' => 'abc' 34 | ] 35 | ] 36 | ] 37 | ]; 38 | 39 | $results = $client->search($params); 40 | -------------------------------------------------- 41 | 42 | 这里要注意 PHP 数组的结构与层次是怎样与 curl 中的 JSON 请求体格式相应对的。这种方式使得 JSON 的写法转换为 PHP 的写法变得十分简单。一个快速检测 PHP 数组是否为预期结果的方法,就是 encode 为 JSON 格式,然后进行检查: 43 | 44 | [source,php] 45 | -------------------------------------------------- 46 | $params = [ 47 | 'index' => 'my_index', 48 | 'type' => 'my_type', 49 | 'body' => [ 50 | 'query' => [ 51 | 'match' => [ 52 | 'testField' => 'abc' 53 | ] 54 | ] 55 | ] 56 | ]; 57 | 58 | print_r(json_encode($params['body'])); 59 | 60 | 61 | {"query":{"match":{"testField":"abc"}}} 62 | -------------------------------------------------- 63 | 64 | .使用原生JSON 65 | ************************************************** 66 | 有时使用原生 JSON 来进行测试会十分方便,或者用原生 JSON 来进行不同系统的移植也同样方便。你可以在 body 中用原生 JSON 字符串,这样客户端会自动进行检查操作: 67 | 68 | [source,php] 69 | -------------------------------------------------- 70 | $json = '{ 71 | "query" : { 72 | "match" : { 73 | "testField" : "abc" 74 | } 75 | } 76 | }'; 77 | 78 | $params = [ 79 | 'index' => 'my_index', 80 | 'type' => 'my_type', 81 | 'body' => $json 82 | ]; 83 | 84 | $results = $client->search($params); 85 | -------------------------------------------------- 86 | ************************************************** 87 | 88 | 搜索结果与 Elasticsearch 的响应结果一致,唯一不同的是 JSON 格式会转换成 PHP 数组。处理这些数据与数组迭代一样简单: 89 | 90 | [source,php] 91 | -------------------------------------------------- 92 | $params = [ 93 | 'index' => 'my_index', 94 | 'type' => 'my_type', 95 | 'body' => [ 96 | 'query' => [ 97 | 'match' => [ 98 | 'testField' => 'abc' 99 | ] 100 | ] 101 | ] 102 | ]; 103 | 104 | $results = $client->search($params); 105 | 106 | $milliseconds = $results['took']; 107 | $maxScore = $results['hits']['max_score']; 108 | 109 | $score = $results['hits']['hits'][0]['_score']; 110 | $doc = $results['hits']['hits'][0]['_source']; 111 | -------------------------------------------------- 112 | 113 | === Bool查询 114 | 115 | 利用客户端可以轻松构建 Bool 查询。例如以下查询: 116 | 117 | [source,js] 118 | -------------------------------------------------- 119 | curl -XGET 'localhost:9200/my_index/my_type/_search' -d '{ 120 | "query" : { 121 | "bool" : { 122 | "must": [ 123 | { 124 | "match" : { "testField" : "abc" } 125 | }, 126 | { 127 | "match" : { "testField2" : "xyz" } 128 | } 129 | ] 130 | } 131 | } 132 | }' 133 | -------------------------------------------------- 134 | 135 | 会构建为这样子(注意方括号位置): 136 | 137 | [source,php] 138 | -------------------------------------------------- 139 | $params = [ 140 | 'index' => 'my_index', 141 | 'type' => 'my_type', 142 | 'body' => [ 143 | 'query' => [ 144 | 'bool' => [ 145 | 'must' => [ 146 | [ 'match' => [ 'testField' => 'abc' ] ], 147 | [ 'match' => [ 'testField2' => 'xyz' ] ], 148 | ] 149 | ] 150 | ] 151 | ] 152 | ]; 153 | 154 | $results = $client->search($params); 155 | -------------------------------------------------- 156 | 157 | 这里注意 must 语句接收的是数组。这里会转化为 JSON 数组,所以最后的响应结果与 curl 格式的响应结果一致。想了解 PHP 中数组和对象的转换,请查看link:php_json_objects.html[用PHP处理JSON数组和JSON对象]。 158 | 159 | === 更为复杂的示例 160 | 161 | 这里构建一个有点复杂的例子:一个 bool 查询包含一个 filter 过滤器和一个普通查询。这在 elasticsearch 的查询中非常普遍,所以这个例子会非常有用。 162 | 163 | curl 格式的查询: 164 | 165 | [source,js] 166 | -------------------------------------------------- 167 | curl -XGET 'localhost:9200/my_index/my_type/_search' -d '{ 168 | "query" : { 169 | "bool" : { 170 | "filter" : { 171 | "term" : { "my_field" : "abc" } 172 | }, 173 | "should" : { 174 | "match" : { "my_other_field" : "xyz" } 175 | } 176 | } 177 | } 178 | }' 179 | -------------------------------------------------- 180 | 181 | 而在 PHP 中: 182 | 183 | [source,php] 184 | -------------------------------------------------- 185 | $params = [ 186 | 'index' => 'my_index', 187 | 'type' => 'my_type', 188 | 'body' => [ 189 | 'query' => [ 190 | 'bool' => [ 191 | 'filter' => [ 192 | 'term' => [ 'my_field' => 'abc' ] 193 | ], 194 | 'should' => [ 195 | 'match' => [ 'my_other_field' => 'xyz' ] 196 | ] 197 | ] 198 | ] 199 | ] 200 | ]; 201 | 202 | 203 | $results = $client->search($params); 204 | -------------------------------------------------- 205 | 206 | === Scrolling(游标)查询 207 | 208 | 在用 bulk 时,经常要用 Scrolling 功能对文档进行分页处理,如输出一个用户的所有文档。这比常规的搜索要高效,因为这里不需要对文档执行性能消耗较大的排序操作。 209 | 210 | Scrolling 会保留某个时间点的索引快照数据,然后用快照数据进行分页。游标查询窗口允许持续分页操作,即使后台正在执行索引文档、更新文档和删除文档。首先,你要在发送搜索请求时增加 scroll 参数。然后就会返回一个文档“页数”信息,还有一个用来获取 hits 分页数据的 scroll_id。 211 | 212 | 更多详情请查看https://www.elastic.co/guide/cn/elasticsearch/guide/current/scroll.html[游标查询]。 213 | 214 | 以下代码更为深入的操作的示例: 215 | 216 | [source,php] 217 | -------------------------------------------------- 218 | $client = ClientBuilder::create()->build(); 219 | $params = [ 220 | "scroll" => "30s", // how long between scroll requests. should be small! 221 | "size" => 50, // how many results *per shard* you want back 222 | "index" => "my_index", 223 | "body" => [ 224 | "query" => [ 225 | "match_all" => new \stdClass() 226 | ] 227 | ] 228 | ]; 229 | 230 | // Execute the search 231 | // The response will contain the first batch of documents 232 | // and a scroll_id 233 | $response = $client->search($params); 234 | 235 | // Now we loop until the scroll "cursors" are exhausted 236 | while (isset($response['hits']['hits']) && count($response['hits']['hits']) > 0) { 237 | 238 | // ** 239 | // Do your work here, on the $response['hits']['hits'] array 240 | // ** 241 | 242 | // When done, get the new scroll_id 243 | // You must always refresh your _scroll_id! It can change sometimes 244 | $scroll_id = $response['_scroll_id']; 245 | 246 | // Execute a Scroll request and repeat 247 | $response = $client->scroll([ 248 | "scroll_id" => $scroll_id, //...using our previously obtained _scroll_id 249 | "scroll" => "30s" // and the same timeout window 250 | ] 251 | ); 252 | } 253 | -------------------------------------------------- 254 | -------------------------------------------------------------------------------- /quickstart.asciidoc: -------------------------------------------------------------------------------- 1 | [[_quickstart]] 2 | == 快速开始 3 | 4 | 这一节会概述一下客户端以及客户端的一些主要方法的使用规则。 5 | 6 | === 安装 7 | 8 | * 在 composer.json 文件中引入 elasticsearch-php: 9 | + 10 | [source,js] 11 | -------------------------------------------------- 12 | { 13 | "require": { 14 | "elasticsearch/elasticsearch": "~6.0" 15 | } 16 | } 17 | -------------------------------------------------- 18 | 19 | * 用 composer 安装客户端: 20 | + 21 | [source,sh] 22 | -------------------------------------------------- 23 | curl -s http://getcomposer.org/installer | php 24 | php composer.phar install --no-dev 25 | -------------------------------------------------- 26 | 27 | * 在项目中引入自动加载文件(如果还没引入),并且实例化一个客户端: 28 | + 29 | [source,php] 30 | -------------------------------------------------- 31 | require 'vendor/autoload.php'; 32 | 33 | use Elasticsearch\ClientBuilder; 34 | 35 | $client = ClientBuilder::create()->build(); 36 | -------------------------------------------------- 37 | 38 | === 索引一个文档 39 | 40 | 在 elasticsearch-php 中,几乎一切操作都是用关联数组来配置。REST 路径(endpoint)、文档和可选参数都是用关联数组来配置。 41 | 42 | 为了索引一个文档,我们要指定4部分信息:index,type,id 和一个 body。构建一个键值对的关联数组就可以完成上面的内容。body 的键值对格式与文档的数据保持一致性。(译者注:如 ["testField" => "abc"] 在文档中则为 {"testField" : "abc"}): 43 | 44 | [source,php] 45 | -------------------------------------------------- 46 | $params = [ 47 | 'index' => 'my_index', 48 | 'type' => 'my_type', 49 | 'id' => 'my_id', 50 | 'body' => ['testField' => 'abc'] 51 | ]; 52 | 53 | $response = $client->index($params); 54 | print_r($response); 55 | -------------------------------------------------- 56 | 57 | 收到的响应数据表明,你指定的索引中已经创建好了文档。响应数据是一个关联数组,里面的内容是 Elasticsearch 返回的decoded JSON 数据: 58 | 59 | [source,php] 60 | -------------------------------------------------- 61 | Array 62 | ( 63 | [_index] => my_index 64 | [_type] => my_type 65 | [_id] => my_id 66 | [_version] => 1 67 | [result] => created 68 | [_shards] => Array 69 | ( 70 | [total] => 2 71 | [successful] => 1 72 | [failed] => 0 73 | ) 74 | 75 | [_seq_no] => 0 76 | [_primary_term] => 1 77 | ) 78 | -------------------------------------------------- 79 | 80 | === 获取一个文档 81 | 82 | 现在获取刚才索引的文档: 83 | 84 | [source,php] 85 | -------------------------------------------------- 86 | $params = [ 87 | 'index' => 'my_index', 88 | 'type' => 'my_type', 89 | 'id' => 'my_id' 90 | ]; 91 | 92 | $response = $client->get($params); 93 | print_r($response); 94 | -------------------------------------------------- 95 | 96 | 响应数据包含一些元数据(如 index,type 等)和 `_source` 属性, 97 | 这是你发送给 Elasticsearch 的原始文档数据。 98 | 99 | [source,php] 100 | -------------------------------------------------- 101 | Array 102 | ( 103 | [_index] => my_index 104 | [_type] => my_type 105 | [_id] => my_id 106 | [_version] => 1 107 | [found] => 1 108 | [_source] => Array 109 | ( 110 | [testField] => abc 111 | ) 112 | ) 113 | -------------------------------------------------- 114 | 115 | === 搜索一个文档 116 | 117 | 搜索是 elasticsearch 的一大特色,所以我们试一下执行一个搜索。我们准备用 Match 查询来作为示范: 118 | 119 | [source,php] 120 | -------------------------------------------------- 121 | $params = [ 122 | 'index' => 'my_index', 123 | 'type' => 'my_type', 124 | 'body' => [ 125 | 'query' => [ 126 | 'match' => [ 127 | 'testField' => 'abc' 128 | ] 129 | ] 130 | ] 131 | ]; 132 | 133 | $response = $client->search($params); 134 | print_r($response); 135 | -------------------------------------------------- 136 | 137 | 这个响应数据与前面例子的响应数据有所不同。这里有一些元数据(如 `took`, `timed_out` 等)和一个 `hits` 的数组,这代表了你的搜索结果。而 `hits` 内部也有一个 `hits` 数组,内部的 `hits` 包含特定的搜索结果: 138 | 139 | [source,php] 140 | -------------------------------------------------- 141 | Array 142 | ( 143 | [took] => 16 144 | [timed_out] => 145 | [_shards] => Array 146 | ( 147 | [total] => 5 148 | [successful] => 5 149 | [skipped] => 0 150 | [failed] => 0 151 | ) 152 | 153 | [hits] => Array 154 | ( 155 | [total] => 1 156 | [max_score] => 0.2876821 157 | [hits] => Array 158 | ( 159 | [0] => Array 160 | ( 161 | [_index] => my_index 162 | [_type] => my_type 163 | [_id] => my_id 164 | [_score] => 0.2876821 165 | [_source] => Array 166 | ( 167 | [testField] => abc 168 | ) 169 | ) 170 | ) 171 | ) 172 | ) 173 | -------------------------------------------------- 174 | 175 | === 删除一个文档 176 | 177 | 好了,现在我们看一下如何把之前添加的文档删除掉: 178 | 179 | [source,php] 180 | -------------------------------------------------- 181 | $params = [ 182 | 'index' => 'my_index', 183 | 'type' => 'my_type', 184 | 'id' => 'my_id' 185 | ]; 186 | 187 | $response = $client->delete($params); 188 | print_r($response); 189 | -------------------------------------------------- 190 | 191 | 你会注意到删除文档的语法与获取文档的语法是一样的。唯一不同的是 `delete` 方法替代了 `get` 方法。下面响应数据代表文档已被删除: 192 | 193 | [source,php] 194 | -------------------------------------------------- 195 | Array 196 | ( 197 | [_index] => my_index 198 | [_type] => my_type 199 | [_id] => my_id 200 | [_version] => 2 201 | [result] => deleted 202 | [_shards] => Array 203 | ( 204 | [total] => 2 205 | [successful] => 1 206 | [failed] => 0 207 | ) 208 | 209 | [_seq_no] => 1 210 | [_primary_term] => 1 211 | ) 212 | -------------------------------------------------- 213 | 214 | === 删除一个索引 215 | 216 | 由于 elasticsearch 的动态特性,我们创建的第一个文档会自动创建一个索引,同时也会把 settings 里面的参数设定为默认参数。由于我们在后面要指定特定的 settings,所以现在要删除掉这个索引: 217 | 218 | [source,php] 219 | -------------------------------------------------- 220 | $deleteParams = [ 221 | 'index' => 'my_index' 222 | ]; 223 | $response = $client->indices()->delete($deleteParams); 224 | print_r($response); 225 | -------------------------------------------------- 226 | 227 | 响应数据是: 228 | 229 | [source,php] 230 | -------------------------------------------------- 231 | Array 232 | ( 233 | [acknowledged] => 1 234 | ) 235 | -------------------------------------------------- 236 | 237 | === 创建一个索引 238 | 239 | 由于数据已被清空,我们可以重新开始了,现在要添加一个索引,同时要进行自定义 settings: 240 | 241 | [source,php] 242 | -------------------------------------------------- 243 | $params = [ 244 | 'index' => 'my_index', 245 | 'body' => [ 246 | 'settings' => [ 247 | 'number_of_shards' => 2, 248 | 'number_of_replicas' => 0 249 | ] 250 | ] 251 | ]; 252 | 253 | $response = $client->indices()->create($params); 254 | print_r($response); 255 | -------------------------------------------------- 256 | 257 | Elasticsearch会创建一个索引,并配置你指定的参数值,然后返回一个消息确认: 258 | 259 | [source,php] 260 | -------------------------------------------------- 261 | Array 262 | ( 263 | [acknowledged] => 1 264 | [shards_acknowledged] => 1 265 | [index] => my_index 266 | ) 267 | -------------------------------------------------- 268 | 269 | === 本节结语 270 | 271 | 这里只是概述了一下客户端以及它的语法。如果你很熟悉 elasticsearch,你会注意到这些方法的命名跟 REST 路径(endpoint)是一样的。 272 | 273 | 你也注意到了客户端的参数配置从某种程度上讲也是方便你的IDE易于搜索。$client 对象下的所有核心方法(索引,搜索,获取等)都是可用的。索引管理和集群管理分别在 `$client->indices()` 和 `$client->cluster()` 中。 274 | 275 | 请查询文档的其余内容以便知道整个客户端的运作机制。 276 | -------------------------------------------------------------------------------- /per-request-configuration.asciidoc: -------------------------------------------------------------------------------- 1 | [[_per_request_configuration]] 2 | == 按请求配置 3 | 4 | 除了配置连接层和客户端层,还可以基于每次请求来进行相关设置。具体来说是在请求体中指定相关参数数组。 5 | 6 | === 忽略异常 7 | 8 | Elasticsearch-PHP 的类库是会对普通的问题抛出异常的。这些异常跟 Elasticsearch 返回的 HTTP 响应码一一对应。例如,获取一个不存在的文档会抛出 `MissingDocument404Exception` 。 9 | 10 | 异常对于处理一些问题(如找不到文档、语法错误、版本冲突等)十分有用。但是有时候你只是想要处理返回的数据而不想捕获异常。 11 | 12 | 如果你想忽略异常,你可以配置 `ignore` 参数。ignore 参数要作为 `client` 的参数配置在请求体中。例如下面的示例会忽略 `MissingDocument404Exception` ,返回的是 Elasticsearch 提供的 JSON 数据。 13 | 14 | [source,php] 15 | -------------------------------------------------- 16 | $client = ClientBuilder::create()->build(); 17 | 18 | $params = [ 19 | 'index' => 'test_missing', 20 | 'type' => 'test', 21 | 'id' => 1, 22 | 'client' => [ 'ignore' => 404 ] <1> 23 | ]; 24 | echo $client->get($params); 25 | 26 | > {"_index":"test_missing","_type":"test","_id":"1","found":false} 27 | -------------------------------------------------- 28 | <1> 这里会忽略 404 异常 29 | 30 | 你可以通过数组的方式指定忽略多个 HTTP 状态码: 31 | 32 | [source,php] 33 | -------------------------------------------------- 34 | $params = [ 35 | 'index' => 'test_missing', 36 | 'type' => 'test', 37 | 'client' => [ 'ignore' => [400, 404] ] <1> 38 | ]; 39 | echo $client->get($params); 40 | 41 | > No handler found for uri [/test_missing/test/] and method [GET] 42 | -------------------------------------------------- 43 | <1> `ignore` 参数也接收数组。在这个示例中, `BadRequest400Exception` 和 `MissingDocument404Exception` 都会被忽略。 44 | 45 | 注意,返回的数据是字符串格式,而不是 JSON 数据。而在第一个示例中返回的是 JSON 数据,客户端会 decode 该 JSON 数据为数组。 46 | 47 | 一旦客户端无法得知返回的异常数据格式,客户端就不会 decode 返回结果。 48 | 49 | === 自定义查询参数 50 | 51 | 有时候你要自己提供自定义参数,比如为第三方插件或代理提供认证 token。在 Elasticsearch-php 的白名单中存储着所有的查询参数,这是为了防止你指定一个参数,而 Elasticsearch 却不接收。 52 | 53 | 如果你要自定义参数,你就要忽略掉这种白名单机制。为了达到这种效果,请增加 `custom` 参数: 54 | 55 | [source,php] 56 | -------------------------------------------------- 57 | $client = ClientBuilder::create()->build(); 58 | 59 | $params = [ 60 | 'index' => 'test', 61 | 'type' => 'test', 62 | 'id' => 1, 63 | 'parent' => 'abc', // white-listed Elasticsearch parameter 64 | 'client' => [ 65 | 'custom' => [ 66 | 'customToken' => 'abc', // user-defined, not white listed, not checked 67 | 'otherToken' => 123 68 | ] 69 | ] 70 | ]; 71 | $exists = $client->exists($params); 72 | -------------------------------------------------- 73 | 74 | === 返回详细输出 75 | 76 | 客户端默认只返回响应体数据。如果你需要更多信息(如头信息、相应状态码等),你可以让客户端返回更多详细信息。通过 `verbose` 参数可以开启这个功能。 77 | 78 | 没有返回详细信息,你看到的返回是这样的: 79 | 80 | [source,php] 81 | -------------------------------------------------- 82 | $client = ClientBuilder::create()->build(); 83 | 84 | $params = [ 85 | 'index' => 'test', 86 | 'type' => 'test', 87 | 'id' => 1 88 | ]; 89 | $response = $client->get($params); 90 | print_r($response); 91 | 92 | 93 | Array 94 | ( 95 | [_index] => test 96 | [_type] => test 97 | [_id] => 1 98 | [_version] => 1 99 | [found] => 1 100 | [_source] => Array 101 | ( 102 | [field] => value 103 | ) 104 | 105 | ) 106 | -------------------------------------------------- 107 | 108 | 如果加上参数: 109 | 110 | [source,php] 111 | -------------------------------------------------- 112 | $client = ClientBuilder::create()->build(); 113 | 114 | $params = [ 115 | 'index' => 'test', 116 | 'type' => 'test', 117 | 'id' => 1, 118 | 'client' => [ 119 | 'verbose' => true 120 | ] 121 | ]; 122 | $response = $client->get($params); 123 | print_r($response); 124 | 125 | 126 | Array 127 | ( 128 | [transfer_stats] => Array 129 | ( 130 | [url] => http://127.0.0.1:9200/test/test/1 131 | [content_type] => application/json; charset=UTF-8 132 | [http_code] => 200 133 | [header_size] => 86 134 | [request_size] => 51 135 | [filetime] => -1 136 | [ssl_verify_result] => 0 137 | [redirect_count] => 0 138 | [total_time] => 0.00289 139 | [namelookup_time] => 9.7E-5 140 | [connect_time] => 0.000265 141 | [pretransfer_time] => 0.000322 142 | [size_upload] => 0 143 | [size_download] => 96 144 | [speed_download] => 33217 145 | [speed_upload] => 0 146 | [download_content_length] => 96 147 | [upload_content_length] => -1 148 | [starttransfer_time] => 0.002796 149 | [redirect_time] => 0 150 | [redirect_url] => 151 | [primary_ip] => 127.0.0.1 152 | [certinfo] => Array 153 | ( 154 | ) 155 | 156 | [primary_port] => 9200 157 | [local_ip] => 127.0.0.1 158 | [local_port] => 62971 159 | ) 160 | 161 | [curl] => Array 162 | ( 163 | [error] => 164 | [errno] => 0 165 | ) 166 | 167 | [effective_url] => http://127.0.0.1:9200/test/test/1 168 | [headers] => Array 169 | ( 170 | [Content-Type] => Array 171 | ( 172 | [0] => application/json; charset=UTF-8 173 | ) 174 | 175 | [Content-Length] => Array 176 | ( 177 | [0] => 96 178 | ) 179 | 180 | ) 181 | 182 | [status] => 200 183 | [reason] => OK 184 | [body] => Array 185 | ( 186 | [_index] => test 187 | [_type] => test 188 | [_id] => 1 189 | [_version] => 1 190 | [found] => 1 191 | [_source] => Array 192 | ( 193 | [field] => value 194 | ) 195 | ) 196 | ) 197 | -------------------------------------------------- 198 | 199 | === Curl 超时设置 200 | 201 | 通过 `timeout` 和 `connect_timeout` 参数可以配置每个请求的 Curl 超时时间。这个配置主要是控制客户端的超时时间。 `connect_timeout` 参数控制在连接阶段完成前,curl 的等待时间。而 `timeout` 参数则控制整个请求完成前,最多等待多长时间。 202 | 203 | 如果超过超时时间,curl 会关闭连接并返回一个致命错误。两个参数都要用 *秒* 作为参数。 204 | 205 | 注意:客户端超时并 *不* 意味着 Elasticsearch 中止请求。Elasticsearch 会继续执行请求直到请求完成。在慢查询或是 bulk 请求下,操作会在后台继续执行,对客户端来说这些动作是隐蔽的。如果客户端在超时后立即断开连接,然后又立刻发送另外一个请求。由于客户端没有处理服务端回压(译者注:这里国内翻译成背压,但是https://www.zhihu.com/question/49618581?from=profile_question_card[知乎]有文章指出这个翻译不够精准,会造成程序员难以理解,所以这里翻译成回压)的机制,这有可能会造成服务端过载。遇到这种情况,你会发现线程池队列会慢慢变大,当队列超出负荷,Elasticsearch 会发送 `EsRejectedExecutionException` 的异常。 206 | 207 | [source,php] 208 | -------------------------------------------------- 209 | $client = ClientBuilder::create()->build(); 210 | 211 | $params = [ 212 | 'index' => 'test', 213 | 'type' => 'test', 214 | 'id' => 1, 215 | 'client' => [ 216 | 'timeout' => 10, // ten second timeout 217 | 'connect_timeout' => 10 218 | ] 219 | ]; 220 | $response = $client->get($params); 221 | -------------------------------------------------- 222 | 223 | === 开启 Future 模式 224 | 225 | 客户端支持异步方式批量发送请求。通过 client 选项的 `future` 参数可以开启(HTTP handler 要支持异步模式): 226 | 227 | [source,php] 228 | -------------------------------------------------- 229 | $client = ClientBuilder::create()->build(); 230 | 231 | $params = [ 232 | 'index' => 'test', 233 | 'type' => 'test', 234 | 'id' => 1, 235 | 'client' => [ 236 | 'future' => 'lazy' 237 | ] 238 | ]; 239 | $future = $client->get($params); 240 | $results = $future->wait(); // resolve the future 241 | -------------------------------------------------- 242 | 243 | Future 模式有两个参数可选: `true` 或 `lazy` 。关于异步执行方法以及如何处理返回结果的详情,请到 <<_future_mode>> 中查看。 244 | 245 | === SSL 加密 246 | 247 | 在创建客户端时,一般需要指定 SSL 配置,因为通常所有的请求都需要加密(查询 <<_security>> 一节获取更多详情)。然而,在每个请求中配置 SSL 加密也是有可能的。例如,如果你需要在某个特定的请求中使用自签名证书,你可以通过在 client 选项中配置 `verify` 参数: 248 | 249 | [source,php] 250 | -------------------------------------------------- 251 | $client = ClientBuilder::create()->build(); 252 | 253 | $params = [ 254 | 'index' => 'test', 255 | 'type' => 'test', 256 | 'id' => 1, 257 | 'client' => [ 258 | 'verify' => 'path/to/cacert.pem' //Use a self-signed certificate 259 | ] 260 | ]; 261 | $result = $client->get($params); 262 | -------------------------------------------------- 263 | -------------------------------------------------------------------------------- /index-operations.asciidoc: -------------------------------------------------------------------------------- 1 | [[_index_management_operations]] 2 | == 索引管理 3 | 4 | 索引管理操作可以让你管理集群中的索引,例如创建、删除和更新索引和索引的映射/配置。 5 | 6 | === 创建一个索引 7 | 8 | 索引操作包含在一个特定的命名空间内,与其它直接从属于客户端对象的方法隔离开来。让我们创建一个索引作为示例: 9 | 10 | [source,php] 11 | -------------------------------------------------- 12 | $client = ClientBuilder::create()->build(); 13 | $params = [ 14 | 'index' => 'my_index' 15 | ]; 16 | 17 | // Create the index 18 | $response = $client->indices()->create($params); 19 | -------------------------------------------------- 20 | {zwsp} + 21 | 22 | 你可以在一个创建索引 API 中指定任何参数。所有的参数通常会注入请求体中的 `body` 参数下: 23 | 24 | [source,php] 25 | -------------------------------------------------- 26 | $client = ClientBuilder::create()->build(); 27 | $params = [ 28 | 'index' => 'my_index', 29 | 'body' => [ 30 | 'settings' => [ 31 | 'number_of_shards' => 3, 32 | 'number_of_replicas' => 2 33 | ], 34 | 'mappings' => [ 35 | 'my_type' => [ 36 | '_source' => [ 37 | 'enabled' => true 38 | ], 39 | 'properties' => [ 40 | 'first_name' => [ 41 | 'type' => 'string', 42 | 'analyzer' => 'standard' 43 | ], 44 | 'age' => [ 45 | 'type' => 'integer' 46 | ] 47 | ] 48 | ] 49 | ] 50 | ] 51 | ]; 52 | 53 | 54 | // Create the index with mappings and settings now 55 | $response = $client->indices()->create($params); 56 | -------------------------------------------------- 57 | {zwsp} + 58 | 59 | === 创建一个索引(复杂示例) 60 | 61 | 这是一个以更为复杂的方式创建索引的示例,示例中展示了如何定义 analyzers,tokenizers,filters 和索引的 settings。虽然创建方式与之前的示例本质一样,但是这个复杂示例对于理解客户端的使用方法具有莫大帮助,因为这种特定的语法结构很容易被混淆。 62 | 63 | [source,php] 64 | -------------------------------------------------- 65 | $params = [ 66 | 'index' => 'reuters', 67 | 'body' => [ 68 | 'settings' => [ 69 | 'number_of_shards' => 1, 70 | 'number_of_replicas' => 0, 71 | 'analysis' => [ 72 | 'filter' => [ 73 | 'shingle' => [ 74 | 'type' => 'shingle' 75 | ] 76 | ], 77 | 'char_filter' => [ 78 | 'pre_negs' => [ 79 | 'type' => 'pattern_replace', 80 | 'pattern' => '(\\w+)\\s+((?i:never|no|nothing|nowhere|noone|none|not|havent|hasnt|hadnt|cant|couldnt|shouldnt|wont|wouldnt|dont|doesnt|didnt|isnt|arent|aint))\\b', 81 | 'replacement' => '~$1 $2' 82 | ], 83 | 'post_negs' => [ 84 | 'type' => 'pattern_replace', 85 | 'pattern' => '\\b((?i:never|no|nothing|nowhere|noone|none|not|havent|hasnt|hadnt|cant|couldnt|shouldnt|wont|wouldnt|dont|doesnt|didnt|isnt|arent|aint))\\s+(\\w+)', 86 | 'replacement' => '$1 ~$2' 87 | ] 88 | ], 89 | 'analyzer' => [ 90 | 'reuters' => [ 91 | 'type' => 'custom', 92 | 'tokenizer' => 'standard', 93 | 'filter' => ['lowercase', 'stop', 'kstem'] 94 | ] 95 | ] 96 | ] 97 | ], 98 | 'mappings' => [ 99 | '_default_' => [ 100 | 'properties' => [ 101 | 'title' => [ 102 | 'type' => 'string', 103 | 'analyzer' => 'reuters', 104 | 'term_vector' => 'yes', 105 | 'copy_to' => 'combined' 106 | ], 107 | 'body' => [ 108 | 'type' => 'string', 109 | 'analyzer' => 'reuters', 110 | 'term_vector' => 'yes', 111 | 'copy_to' => 'combined' 112 | ], 113 | 'combined' => [ 114 | 'type' => 'string', 115 | 'analyzer' => 'reuters', 116 | 'term_vector' => 'yes' 117 | ], 118 | 'topics' => [ 119 | 'type' => 'string', 120 | 'index' => 'not_analyzed' 121 | ], 122 | 'places' => [ 123 | 'type' => 'string', 124 | 'index' => 'not_analyzed' 125 | ] 126 | ] 127 | ], 128 | 'my_type' => [ 129 | 'properties' => [ 130 | 'my_field' => [ 131 | 'type' => 'string' 132 | ] 133 | ] 134 | ] 135 | ] 136 | ] 137 | ]; 138 | $client->indices()->create($params); 139 | -------------------------------------------------- 140 | 141 | === 删除一个索引 142 | 143 | 删除一个索引十分简单: 144 | 145 | [source,php] 146 | -------------------------------------------------- 147 | $params = ['index' => 'my_index']; 148 | $response = $client->indices()->delete($params); 149 | -------------------------------------------------- 150 | {zwsp} + 151 | 152 | === Put Settings API 153 | 154 | Put Settings API 允许你更改索引的配置参数: 155 | 156 | [source,php] 157 | -------------------------------------------------- 158 | $params = [ 159 | 'index' => 'my_index', 160 | 'body' => [ 161 | 'settings' => [ 162 | 'number_of_replicas' => 0, 163 | 'refresh_interval' => -1 164 | ] 165 | ] 166 | ]; 167 | 168 | $response = $client->indices()->putSettings($params); 169 | -------------------------------------------------- 170 | {zwsp} + 171 | 172 | === Get Settings API 173 | 174 | Get Settings API 可以让你知道一个或多个索引的当前配置参数: 175 | 176 | [source,php] 177 | -------------------------------------------------- 178 | // Get settings for one index 179 | $params = ['index' => 'my_index']; 180 | $response = $client->indices()->getSettings($params); 181 | 182 | // Get settings for several indices 183 | $params = [ 184 | 'index' => [ 'my_index', 'my_index2' ] 185 | ]; 186 | $response = $client->indices()->getSettings($params); 187 | -------------------------------------------------- 188 | {zwsp} + 189 | 190 | === Put Mappings API 191 | 192 | Put Mappings API 允许你更改或增加一个索引的映射。 193 | 194 | [source,php] 195 | -------------------------------------------------- 196 | // Set the index and type 197 | $params = [ 198 | 'index' => 'my_index', 199 | 'type' => 'my_type2', 200 | 'body' => [ 201 | 'my_type2' => [ 202 | '_source' => [ 203 | 'enabled' => true 204 | ], 205 | 'properties' => [ 206 | 'first_name' => [ 207 | 'type' => 'string', 208 | 'analyzer' => 'standard' 209 | ], 210 | 'age' => [ 211 | 'type' => 'integer' 212 | ] 213 | ] 214 | ] 215 | ] 216 | ]; 217 | 218 | // Update the index mapping 219 | $client->indices()->putMapping($params); 220 | -------------------------------------------------- 221 | {zwsp} + 222 | 223 | === Get Mappings API 224 | 225 | Get Mappings API 返回索引和类型的映射细节。你可以指定一些索引和类型,取决于你希望检索什么映射。 226 | 227 | [source,php] 228 | -------------------------------------------------- 229 | // Get mappings for all indexes and types 230 | $response = $client->indices()->getMapping(); 231 | 232 | // Get mappings for all types in 'my_index' 233 | $params = ['index' => 'my_index']; 234 | $response = $client->indices()->getMapping($params); 235 | 236 | // Get mappings for all types of 'my_type', regardless of index 237 | $params = ['type' => 'my_type' ]; 238 | $response = $client->indices()->getMapping($params); 239 | 240 | // Get mapping 'my_type' in 'my_index' 241 | $params = [ 242 | 'index' => 'my_index' 243 | 'type' => 'my_type' 244 | ]; 245 | $response = $client->indices()->getMapping($params); 246 | 247 | // Get mappings for two indexes 248 | $params = [ 249 | 'index' => [ 'my_index', 'my_index2' ] 250 | ]; 251 | $response = $client->indices()->getMapping($params); 252 | -------------------------------------------------- 253 | {zwsp} + 254 | 255 | === 索引命名空间下的其他 API 256 | 257 | 索引命名空间下还有一些 API 允许你管理你的索引(add/remove templates, flush segments, close indexes等)。 258 | 259 | 如果你使用一个自动检索的 IDE,你应该可以轻易发现索引的命名空间: 260 | 261 | [source,php] 262 | -------------------------------------------------- 263 | $client->indices()-> 264 | -------------------------------------------------- 265 | 266 | 这里可以查看可用方法清单。而浏览 `\Elasticsearch\Namespaces\Indices.php` 文件则会看到所有可调用的方法清单。 267 | -------------------------------------------------------------------------------- /configuration.asciidoc: -------------------------------------------------------------------------------- 1 | [[_configuration]] 2 | == 配置 3 | 4 | 几乎所有应用(译者注:如 mysql、redis 等)的客户端都可以配置。大多数用户只需配置一些参数来满足他们的需求,但是也有可能需要修改大量的内核代码来满足需求。 5 | 6 | 在客户端对象实例化前就应该通过 ClientBuilder 对象来完成自定义配置。我们会概述一下所有的配置参数,并且展示一些代码示例。 7 | 8 | === Inline Host 配置法 9 | 10 | 最常见的配置是告诉客户端有关集群的信息:有多少个节点,节点的ip地址和端口号。如果没有指定主机名,客户端会连接 `localhost:9200` 。 11 | 12 | 利用 `ClientBuilder` 的 `setHosts()` 方法可以改变客户端的默认连接方式。 `setHosts()` 方法接收一个一维数组,数组里面每个值都代表集群里面的一个节点信息。值的格式多种多样,主要看你的需求: 13 | 14 | [source,php] 15 | -------------------------------------------------- 16 | $hosts = [ 17 | '192.168.1.1:9200', // IP + Port 18 | '192.168.1.2', // Just IP 19 | 'mydomain.server.com:9201', // Domain + Port 20 | 'mydomain2.server.com', // Just Domain 21 | 'https://localhost', // SSL to localhost 22 | 'https://192.168.1.3:9200' // SSL to IP + Port 23 | ]; 24 | $client = ClientBuilder::create() // Instantiate a new ClientBuilder 25 | ->setHosts($hosts) // Set the hosts 26 | ->build(); // Build the client object 27 | -------------------------------------------------- 28 | 29 | 注意 `ClientBuilder` 对象允许链式操作。当然也可以分别调用上述的方法: 30 | 31 | [source,php] 32 | -------------------------------------------------- 33 | $hosts = [ 34 | '192.168.1.1:9200', // IP + Port 35 | '192.168.1.2', // Just IP 36 | 'mydomain.server.com:9201', // Domain + Port 37 | 'mydomain2.server.com', // Just Domain 38 | 'https://localhost', // SSL to localhost 39 | 'https://192.168.1.3:9200' // SSL to IP + Port 40 | ]; 41 | $clientBuilder = ClientBuilder::create(); // Instantiate a new ClientBuilder 42 | $clientBuilder->setHosts($hosts); // Set the hosts 43 | $client = $clientBuilder->build(); // Build the client object 44 | -------------------------------------------------- 45 | 46 | === Extended Host 配置法 47 | 48 | 客户端也支持 Extended Host 配置语法。Inline Host 配置法依赖 PHP 的 `filter_var()` 函数和 `parse_url()` 函数来验证和提取一个 URL 的各个部分。然而,这些 php 函数在一些特定的场景下会出错。例如, `filter_var()` 函数不接收有下划线的 URL。同样,如果 Basic Auth 的密码含有特定字符(如#、?),那么 `parse_url()` 函数会报错。 49 | 50 | 因而客户端也支持 Extended Host 配置语法,从而使客户端实例化更加可控: 51 | 52 | [source,php] 53 | -------------------------------------------------- 54 | $hosts = [ 55 | // This is effectively equal to: "https://username:password!#$?*abc@foo.com:9200/" 56 | [ 57 | 'host' => 'foo.com', 58 | 'port' => '9200', 59 | 'scheme' => 'https', 60 | 'user' => 'username', 61 | 'pass' => 'password!#$?*abc' 62 | ], 63 | 64 | // This is equal to "http://localhost:9200/" 65 | [ 66 | 'host' => 'localhost', // Only host is required 67 | ] 68 | ]; 69 | $client = ClientBuilder::create() // Instantiate a new ClientBuilder 70 | ->setHosts($hosts) // Set the hosts 71 | ->build(); // Build the client object 72 | -------------------------------------------------- 73 | 74 | 每个节点只需要配置 `host` 参数。如果其它参数不指定,那么默认的端口是 `9200` ,默认的 scheme 是 `http` 。 75 | 76 | === 认证与加密 77 | 78 | 想了解 HTTP 认证和 SSL 加密的内容,请查看 link:_security.html[认证与加密]。 79 | 80 | === 设置重连次数 81 | 82 | 在一个集群中,如果操作抛出如下异常:connection refusal, connection timeout, DNS lookup timeout 等等(不包括4xx和5xx),客户端便会重连。客户端默认重连 `n` (n=节点数)次。 83 | 84 | 如果你不想重连,或者想更改重连次数。你可以使用 `setRetries()` 方法: 85 | 86 | [source,php] 87 | -------------------------------------------------- 88 | $client = ClientBuilder::create() 89 | ->setRetries(2) 90 | ->build(); 91 | -------------------------------------------------- 92 | 93 | 假如客户端重连次数超过设定值,便会抛出最后接收到的异常。例如,如果你有 10 个节点,设置 `setRetries(5)` ,客户端便会最多发送 5 次连接命令。如果 5 个节点返回的结果都是 connection timeout,那么客户端会抛出 `OperationTimeoutException` 。由于连接池处于使用状态,这些节点也可能会被标记为死节点。 94 | 95 | 为了识别是否为重连异常,抛出的异常会包含一个 `MaxRetriesException` 。例如,你可以在 catch 内使用 `getPrevious()` 来捕获一个特定的 curl 异常,以便查看是否包含 `MaxRetriesException` 。 96 | 97 | [source,php] 98 | -------------------------------------------------- 99 | $client = Elasticsearch\ClientBuilder::create() 100 | ->setHosts(["localhost:1"]) 101 | ->setRetries(0) 102 | ->build(); 103 | 104 | try { 105 | $client->search($searchParams); 106 | } catch (Elasticsearch\Common\Exceptions\Curl\CouldNotConnectToHost $e) { 107 | $previous = $e->getPrevious(); 108 | if ($previous instanceof 'Elasticsearch\Common\Exceptions\MaxRetriesException') { 109 | echo "Max retries!"; 110 | } 111 | } 112 | -------------------------------------------------- 113 | 114 | 由于所有 curl 抛出的异常(`CouldNotConnectToHost`, `CouldNotResolveHostException`, `OperationTimeoutException`)都继承 `TransportException` 。这样你就能够用 `TransportException` 来替代如上3种异常: 115 | 116 | [source,php] 117 | -------------------------------------------------- 118 | $client = Elasticsearch\ClientBuilder::create() 119 | ->setHosts(["localhost:1"]) 120 | ->setRetries(0) 121 | ->build(); 122 | 123 | try { 124 | $client->search($searchParams); 125 | } catch (Elasticsearch\Common\Exceptions\TransportException $e) { 126 | $previous = $e->getPrevious(); 127 | if ($previous instanceof 'Elasticsearch\Common\Exceptions\MaxRetriesException') { 128 | echo "Max retries!"; 129 | } 130 | } 131 | -------------------------------------------------- 132 | 133 | === 开启日志 134 | 135 | Elasticsearch-PHP 支持日志记录,但由于性能原因,所以默认没有开启。如果你希望开启日志,你就要选择一个日志记录工具并安装它,然后在客户端中开启日志。推荐使用 https://github.com/Seldaek/monolog[Monolog],不过任何实现 PSR/Log 接口的日志记录工具都可以使用。 136 | 137 | 你会发现在安装 elasticsearch-php 时会建议安装 Monolog。为了使用 Monolog,请把它加入 `composer.json` : 138 | 139 | [source,js] 140 | -------------------------------------------------- 141 | { 142 | "require": { 143 | ... 144 | "elasticsearch/elasticsearch" : "~6.0", 145 | "monolog/monolog": "~1.0" 146 | } 147 | } 148 | -------------------------------------------------- 149 | 150 | 然后用 composer 更新: 151 | 152 | [source,sh] 153 | -------------------------------------------------- 154 | php composer.phar update 155 | -------------------------------------------------- 156 | 157 | 一旦安装好 Monolog(或其他日志记录工具),你就要创建一个日志对象并且注入到客户端中。 `ClientBuilder` 对象有一个静态方法来构建一个通用的 Monolog-based 日志对象。你只需要提供存放日志路径就行: 158 | 159 | [source,php] 160 | -------------------------------------------------- 161 | $logger = ClientBuilder::defaultLogger('path/to/your.log'); 162 | 163 | $client = ClientBuilder::create() // Instantiate a new ClientBuilder 164 | ->setLogger($logger) // Set the logger with a default logger 165 | ->build(); // Build the client object 166 | -------------------------------------------------- 167 | 168 | 你也可以指定记录的日志级别: 169 | 170 | [source,php] 171 | -------------------------------------------------- 172 | // set severity with second parameter 173 | $logger = ClientBuilder::defaultLogger('/path/to/logs/', Logger::INFO); 174 | 175 | $client = ClientBuilder::create() // Instantiate a new ClientBuilder 176 | ->setLogger($logger) // Set the logger with a default logger 177 | ->build(); // Build the client object 178 | -------------------------------------------------- 179 | 180 | `defaultLogger()` 方法只是一个辅助方法,不要求你使用它。你可以自己创建日志对象,然后注入: 181 | 182 | [source,php] 183 | -------------------------------------------------- 184 | use Monolog\Logger; 185 | use Monolog\Handler\StreamHandler; 186 | 187 | $logger = new Logger('name'); 188 | $logger->pushHandler(new StreamHandler('path/to/your.log', Logger::WARNING)); 189 | 190 | $client = ClientBuilder::create() // Instantiate a new ClientBuilder 191 | ->setLogger($logger) // Set your custom logger 192 | ->build(); // Build the client object 193 | -------------------------------------------------- 194 | 195 | === 配置 HTTP Handler 196 | 197 | Elasticsearch-PHP 使用的是可替代的 HTTP 传输层——https://github.com/guzzle/RingPHP/[RingPHP]。这允许客户端构建一个普通的 HTTP 请求,然后通过传输层发送出去。真正的请求细节隐藏在客户端内,并且这是模块化的,因此你可以根据你的需求来选择 HTTP handlers。 198 | 199 | 客户端使用的默认 handler 是结合型 handler(combination handler)。当使用同步模式,handler 会使用 `CurlHandler` 来一个一个地发送 curl 请求。这种方式对于单一请求(single requests)来说特别迅速。当异步(future)模式开启,handler 就转换成使用 `CurlMultiHandler` , `CurlMultiHandler` 以 curl_multi 方式来发送请求。这样会消耗更多性能,但是允许批量 HTTP 请求并行执行。 200 | 201 | 你可以从以下一些助手函数中选择一个来配置 HTTP handler,或者你也可以自定义 HTTP handler: 202 | 203 | [source,php] 204 | -------------------------------------------------- 205 | $defaultHandler = ClientBuilder::defaultHandler(); 206 | $singleHandler = ClientBuilder::singleHandler(); 207 | $multiHandler = ClientBuilder::multiHandler(); 208 | $customHandler = new MyCustomHandler(); 209 | 210 | $client = ClientBuilder::create() 211 | ->setHandler($defaultHandler) 212 | ->build(); 213 | -------------------------------------------------- 214 | 215 | 想要了解自定义 Ring handler 的细节,请查看 http://ringphp.readthedocs.io/en/latest/[RingPHP文档]。 216 | 217 | 在所有的情况下都推荐使用默认的 handler。这不仅可以以同步模式快速发送请求,而且也保留了异步模式来实现并行请求。 如果你觉得你永远不会用到 future 模式,你可以考虑用 `singleHandler` ,这样会间接节省一些性能。 218 | 219 | === 设置连接池 220 | 221 | 客户端会维持一个连接池,连接池内每个连接代表集群的一个节点。这里有好几种连接池可供使用,每个的行为都有些细微差距。连接池可通过 `setConnectionPool()` 来配置: 222 | 223 | [source,php] 224 | -------------------------------------------------- 225 | $connectionPool = '\Elasticsearch\ConnectionPool\StaticNoPingConnectionPool'; 226 | $client = ClientBuilder::create() 227 | ->setConnectionPool($connectionPool) 228 | ->build(); 229 | -------------------------------------------------- 230 | 231 | 更多细节请查询 link:_connection_pool.html[连接池配置]。 232 | 233 | === 设置选择器(Selector) 234 | 235 | 连接池是用来管理集群的连接,但是选择器则是用来确定下一个 API 请求要用哪个连接。这里有几个选择器可供选择。选择器可通过 `setSelector()` 方法来更改: 236 | 237 | [source,php] 238 | -------------------------------------------------- 239 | $selector = '\Elasticsearch\ConnectionPool\Selectors\StickyRoundRobinSelector'; 240 | $client = ClientBuilder::create() 241 | ->setSelector($selector) 242 | ->build(); 243 | -------------------------------------------------- 244 | 245 | 更多细节请查询 link:_selectors.html[选择器配置]。 246 | 247 | === 设置序列化器(Serializer) 248 | 249 | 客户端的请求数据是关联数组,但是 Elasticsearch 接受 JSON 数据。序列化器是指把 PHP 数组序列化为 JSON 数据。当然 Elasticsearch 返回的 JSON 数据也会反序列化为 PHP 数组。这看起来有些繁琐,但把序列化器模块化对于处理一些极端案例有莫大帮助。 250 | 251 | 大部分人不会更改默认的序列化器( `SmartSerializer` ),但你真的想改变,那可以通过 `setSerializer()` 方法: 252 | 253 | [source,php] 254 | -------------------------------------------------- 255 | $serializer = '\Elasticsearch\Serializers\SmartSerializer'; 256 | $client = ClientBuilder::create() 257 | ->setSerializer($serializer) 258 | ->build(); 259 | -------------------------------------------------- 260 | 261 | 更多细节请查询 link:_serializers.html[序列化器配置]。 262 | 263 | === 设置自定义 ConnectionFactory 264 | 265 | 当连接池发送请求时,ConnectionFactory 就会实例化连接对象。一个连接对象代表一个节点。因为 handler(通过RingPHP)才是真正的执行网络请求,那么连接对象的主要工作就是维持连接:节点是活节点吗?ping 的通吗?host 和端口是什么? 266 | 267 | 很少会去自定义 ConnectionFactory,但是如果你想做,那么你要提供一个完整的 ConnectionFactory 对象作为 `setConnectionFactory()` 方法的参数。这个自定义对象需要实现 ConnectionFactoryInterface 接口。 268 | 269 | [source,php] 270 | -------------------------------------------------- 271 | class MyConnectionFactory implements ConnectionFactoryInterface 272 | { 273 | 274 | public function __construct($handler, array $connectionParams, 275 | SerializerInterface $serializer, 276 | LoggerInterface $logger, 277 | LoggerInterface $tracer) 278 | { 279 | // Code here 280 | } 281 | 282 | 283 | /** 284 | * @param $hostDetails 285 | * 286 | * @return ConnectionInterface 287 | */ 288 | public function create($hostDetails) 289 | { 290 | // Code here...must return a Connection object 291 | } 292 | } 293 | 294 | 295 | $connectionFactory = new MyConnectionFactory( 296 | $handler, 297 | $connectionParams, 298 | $serializer, 299 | $logger, 300 | $tracer 301 | ); 302 | 303 | $client = ClientBuilder::create() 304 | ->setConnectionFactory($connectionFactory); 305 | ->build(); 306 | -------------------------------------------------- 307 | 308 | 如上所述,如果你想注入自定义的 ConnectionFactory,你自己就要负责写对它。自定义 ConnectionFactory 需要用到 HTTP handler,序列化器,日志和追踪。 309 | 310 | === 设置 Endpoint 闭包 311 | 312 | 客户端使用 Endpoint 闭包来发送 API 请求到 Elasticsearch 的 Endpoint 对象。一个命名空间对象会通过闭包构建一个新的 Endpoint,这个意味着如果你想扩展 API 的 Endpoint,你可以很方便的做到。 313 | 314 | 例如,我们可以新增一个 endpoint: 315 | 316 | [source,php] 317 | -------------------------------------------------- 318 | $transport = $this->transport; 319 | $serializer = $this->serializer; 320 | 321 | $newEndpoint = function ($class) use ($transport, $serializer) { 322 | if ($class == 'SuperSearch') { 323 | return new MyProject\SuperSearch($transport); 324 | } else { 325 | // Default handler 326 | $fullPath = '\\Elasticsearch\\Endpoints\\' . $class; 327 | if ($class === 'Bulk' || $class === 'Msearch' || $class === 'MPercolate') { 328 | return new $fullPath($transport, $serializer); 329 | } else { 330 | return new $fullPath($transport); 331 | } 332 | } 333 | }; 334 | 335 | $client = ClientBuilder::create() 336 | ->setEndpoint($newEndpoint) 337 | ->build(); 338 | -------------------------------------------------- 339 | 340 | 很明显,如果你这样做的话,那么你就要负责对现存的 Endpoint 进行维护,以确保所有的方法都能正常运行。同时你也要确保端口和序列化都写入每个 Endpoint。 341 | 342 | === 从 hash 配置中创建客户端 343 | 344 | 为了更加容易的创建客户端,所有的配置都可以用 hash 形式来替代单一配置方法。这种配置方法可以通过静态方法 `ClientBuilder::FromConfig()` 来完成,它接收一个数组,返回一个配置好的客户端。 345 | 346 | 数组的键名对应方法名(如 retries 对应 setRetries() 方法): 347 | 348 | [source,php] 349 | -------------------------------------------------- 350 | $params = [ 351 | 'hosts' => [ 352 | 'localhost:9200' 353 | ], 354 | 'retries' => 2, 355 | 'handler' => ClientBuilder::singleHandler() 356 | ]; 357 | $client = ClientBuilder::fromConfig($params); 358 | -------------------------------------------------- 359 | 360 | 为了帮助用户找出潜在的问题,未知参数会抛出异常。如果你不想要抛出异常,你可以在 fromConfig() 中设置 $quiet = true 来关闭异常: 361 | 362 | [source,php] 363 | -------------------------------------------------- 364 | $params = [ 365 | 'hosts' => [ 366 | 'localhost:9200' 367 | ], 368 | 'retries' => 2, 369 | 'imNotReal' => 5 370 | ]; 371 | 372 | // Set $quiet to true to ignore the unknown `imNotReal` key 373 | $client = ClientBuilder::fromConfig($params, true); 374 | -------------------------------------------------- 375 | --------------------------------------------------------------------------------