配置
客户端的每一项几乎都是可以配置的,大多数用户只需要配置一些参数以满足他们的需要。但是如果需要的话,完全可以替换大部分内部构建。
通过ClientBuilder helper对象在实例化客户端之前完成自定义配置。我们将遍历所有配置选项并显示示例代码以替换各种组件。
内联主机配置
最通用的配置方式是将集群中有多少个结点,各个结点的地址和端口信息告诉客户端。如果没有指定明确的主机信息,客户端将尝试连接默认主机 localhost:9200
。
可以通过调用 ClientBuilder
的 setHosts()
方法来改变这种默认行为。 setHosts()
方法接收一个数组作为参数,数组的每个元素相当于集群中的一个结点。主机的配置格式是多种多样的,可以根据你的需要设置,如IP或域名、端口、SSL等。
$hosts = [
'192.168.1.1:9200', // IP + 端口
'192.168.1.2', // 仅 IP
'mydomain.server.com:9201', // 域名 + 端口
'mydomain2.server.com', // 仅域名
'https://localhost', // 对 localhost 使用 SSL
'https://192.168.1.3:9200' // 对 IP + 端口 使用 SSL
];
$client = ClientBuilder::create() // 实例化 ClientBuilder
->setHosts($hosts) // 设置主机信息
->build(); // 构建客户端对象
ClientBuilder
对象不仅支持简洁的链式调用,也支持独立的调用各个方法,如下所示:
$hosts = [
'192.168.1.1:9200', // IP + 端口
'192.168.1.2', // 仅 IP
'mydomain.server.com:9201', // 域名 + 端口
'mydomain2.server.com', // 仅域名
'https://localhost', // 对 localhost 使用 SSL
'https://192.168.1.3:9200' // 对 IP + 端口 使用 SSL
];
$clientBuilder = ClientBuilder::create(); // 实例化 ClientBuilder
$clientBuilder->setHosts($hosts); // 设置主机信息
$client = $clientBuilder->build(); // 构建客户端对象
扩展主机配置
客户端还支持 扩展的 主机配置语法。上述内联配置方式依赖于 PHP 中的 filter_var()
和 parse_url()
方法,使用它们完成 URL 组成部分的过滤验证和解析提取。很不幸的是,这些内置方法在执行某些特殊案例时会引发问题。例如, filter_var()
不能处理带有下划线的 URL(这样的 URL 是否合法取决于你对 RFC 是如何理解的)。类似地, parse_url()
在处理 password
中包含特殊字符(如 #
和 ?
)的 Basic Auth
时会出现阻塞。
基于上述原因,客户端需要支持一种能够在主机初始化时提供更高级控制能力的配置语法。以此消除如域名中带有下划线这样的特殊案例可能引发的问题。
扩展语法是为每个主机提供一个参数数组,如下所示:
$hosts = [
// 等价于内联主机配置中使用 "https://username:password!#$?*abc@foo.com:9200/"
[
'host' => 'foo.com',
'port' => '9200',
'scheme' => 'https',
'user' => 'username',
'pass' => 'password!#$?*abc'
],
// 等价于内联主机配置中使用 "http://localhost:9200/"
[
'host' => 'localhost', // 只有 host 是必须的
]
];
$client = ClientBuilder::create() // 实例化 ClientBuilder
->setHosts($hosts) // 设置主机信息
->build(); // 构建客户端对象
对于每个配置的主机来说,只有 host
参数是必须的。如果不设置 port
参数,则默认使用 9200
。如果不设置 scheme
,则默认使用 http
。
授权和加密
想要了解更多关于 HTTP 授权和 SSL 加密知识,请移步至 Authorization and SSL。
配置重试次数
默认情况下,客户端会重试 n
次,这里的 n
等于你集群的节点数。只有操作结果发生了『严重的』异常才会触发重试。如:连接被拒绝,连接超时,DNS 解析超时等等。 4xx 和 5xx 系列的错误不被认为是可重试的事件,因为节点已经返回了操作响应。
假如你想禁用重试,或者改变重试次数,那么你可以通过 setRetries()
方法来达到目的:
$client = ClientBuilder::create()
->setRetries(2)
->build();
当客户端重试次数用完后,它将抛出最后接收到的异常。举个例子:如果你有十个活节点,并且调用了 setRetries(5)
,客户端将会最多尝试执行五次命令。假设有五个节点全部连接超时,客户端就会抛出 OperationTimeoutException
异常。取决于连接池的使用数量,那些节点也有可能会被标记为死亡。
为了帮助识别,那些由于超过最大重试次数而抛出的异常都将包裹一个 MaxRetriesException
异常。你可以捕获一个特定的 curl
异常,然后通过调用 getPrevious()
方法检查其是否包裹了 MaxRetriesException
异常:
$client = Elasticsearch\ClientBuilder::create()
->setHosts(["localhost:1"])
->setRetries(0)
->build();
try {
$client->search($searchParams);
} catch (Elasticsearch\Common\Exceptions\Curl\CouldNotConnectToHost $e) {
$previous = $e->getPrevious();
if ($previous instanceof 'Elasticsearch\Common\Exceptions\MaxRetriesException') {
echo "Max retries!";
}
}
另外, 所有“复杂的”请求异常 (CouldNotConnectToHost
, CouldNotResolveHostException
, OperationTimeoutException
) 都继承于 TransportException
。因此你可以先捕获 TransportException
然后检查在此之前的值:
$client = Elasticsearch\ClientBuilder::create()
->setHosts(["localhost:1"])
->setRetries(0)
->build();
try {
$client->search($searchParams);
} catch (Elasticsearch\Common\Exceptions\TransportException $e) {
$previous = $e->getPrevious();
if ($previous instanceof 'Elasticsearch\Common\Exceptions\MaxRetriesException') {
echo "Max retries!";
}
}
启用日志
Elasticsearch-PHP 支持日志,但出于性能考量,默认不启用。如果要使用日志,你需要选择一个日志实现工具,然后在客户端安装并启用它。推荐使用 Monolog,当然任何实现了PSR/Log
接口的工具都可以使用。
在安装 elasticsearch-php 期间你可能会注意到 Monolog是推荐安装的。要使用 Monolog,请将它加入到composer.json
文件:
{
"require": {
...
"elasticsearch/elasticsearch" : "~5.0",
"monolog/monolog": "~1.0"
}
}
然后更新 composer 执行安装:
php composer.phar update
一旦 Monolog(或其他的日志工具)安装完毕,你将需要创建一个日志对象并把它注入到客户端中。ClientBuilder
对象有一个静态方法来为你构建一个通用的 Monolog-based 对象。你只需提供日志的存放路径:
$logger = ClientBuilder::defaultLogger('path/to/your.log');
$client = ClientBuilder::create() // 实例化一个 ClientBuilder
->setLogger($logger) // 用默认 logger 设置 logger
->build(); // 构建客户端对象
你也可以指定日志的记录级别:
// 用第 2 个参数设置日志级别
$logger = ClientBuilder::defaultLogger('/path/to/logs/', Logger::INFO);
$client = ClientBuilder::create() // 实例化一个新的 ClientBuilder
->setLogger($logger) // 用默认 logger 设置 logger
->build(); // 构建客户端对象
defaultLogger()
方法只是一个助手方法,并非必须使用它。你也可以创建自己的日志对象,并将它注入:
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
$logger = new Logger('name');
$logger->pushHandler(new StreamHandler('path/to/your.log', Logger::WARNING));
$client = ClientBuilder::create() // 实例化一个新的 ClientBuilder
->setLogger($logger) // 设置自定义的 logger
->build(); // 构建客户端对象
配置 HTTP Handler
Elasticsearch-PHP 使用一个名为 RingPHP 的通用传输层。这允许客户端构建通用的 HTTP 请求,然后把它交给传输层执行。因为实际的执行细节隐藏于客户端,也是模块化的,所以你可以按需选择几个 HTTP handlers。
客户端默认使用的 handler 是一个组合型 handler。当在同步模式时,handler 使用CurlHandler
执行单个 curl 请求。这对于单一请求来说非常迅速。而在异步(Future)模式下,handler 会切换到CurlMultiHandler
,CurlMultiHandler
使用 curl_multi 接口执行多个 curl 请求。这涉及到更多的开销,但是允许批量 HTTP 请求的并行执行。
你可以使用以下几个助手方法设置 HTTP handler,或者提供自定义 handler:
$defaultHandler = ClientBuilder::defaultHandler();
$singleHandler = ClientBuilder::singleHandler();
$multiHandler = ClientBuilder::multiHandler();
$customHandler = new MyCustomHandler();
$client = ClientBuilder::create()
->setHandler($defaultHandler)
->build();
关于创建自定义 Ring handler 的详情,请查看 RingPHP 文档。
几乎在所有情形下都推荐使用默认 handler。它允许快速地同步执行请求,同时也保留了异步模式下并行处理批量请求。
设置连接池
客户端维护着一个连接池,连接池中的每个连接代表集群中的一个结点。可用的连接池实现多种多样,但是每种实现之间的行为表现略有不同(如可 ping 或 不可 ping 等)。连接池的配置是通过 setConnectionPool()
方法实现的:
$connectionPool = '\Elasticsearch\ConnectionPool\StaticNoPingConnectionPool';
$client = ClientBuilder::create()
->setConnectionPool($connectionPool)
->build();
更多细节信息请查阅 连接池配置 。
配置连接选择器
连接池用于管理与集群的连接,而连接选择器则是用来决定你的 API 请求要使用连接池中的哪个连接。这里有几个选择器可供选择。选择器可以通过 setSelector()
方法更改:
$selector = '\Elasticsearch\ConnectionPool\Selectors\StickyRoundRobinSelector';
$client = ClientBuilder::create()
->setSelector($selector)
->build();
更多详情请查阅 选择器配置 。
配置序列化器
客户端以关联数组的形式发送请求数据,但 Elasticsearch-PHP 接收 JSON 格式数据。序列化器的工作就是将 PHP 对象序列化为 JSON 。同时,它还可以将 JSON 反序列化为 PHP 数组。这看起来不是很重要,但是对于某些特殊的情况,把序列化器模块化非常有用。
默认的序列化器 SmartSerializer
一般来说不需要更改,如果真的想改变,可以通过 setSerializer()
方法更改:
$serializer = '\Elasticsearch\Serializers\SmartSerializer';
$client = ClientBuilder::create()
->setSerializer($serializer)
->build();
更多详情请查阅 序列化器配置.
设置自定义 ConnectionFactory
当请求池发送请求时,ConnectionFactory 会实例化新的连接对象。一个连接对象代表一个节点。由于客户端将实际的网络工作交给 RingPHP,因此连接对象的主要工作是保持连接:节点是否是活节点? Ping 的通吗?主机和端口是什么?
很少去自定义 ConnectionFactory ,如果你想那么做,你要提供一个完整的 ConnectionFactory 对象作为 setConnectionFactory()
方法的参数。这个对象应该实现 ConnectionFactoryInterface
接口。
class MyConnectionFactory implements ConnectionFactoryInterface
{
public function __construct($handler, array $connectionParams,
SerializerInterface $serializer,
LoggerInterface $logger,
LoggerInterface $tracer)
{
// 在这编写代码
}
/**
* @param $hostDetails
*
* @return ConnectionInterface
*/
public function create($hostDetails)
{
// 在这编写代码... 必须返回一个连接对象
}
}
$connectionFactory = new MyConnectionFactory(
$handler,
$connectionParams,
$serializer,
$logger,
$tracer
);
$client = ClientBuilder::create()
->setConnectionFactory($connectionFactory);
->build();
如上所述,如果你决定注入自定义 ConnectionFactory,你就要对它负责。ConnectionFactory 需要用到 HTTP handler,序列化器,日志和追踪。
设置 Endpoint 闭包
客户端使用 Endpoint 闭包发送 API 请求到正确的 Endpoint 对象上。一个命名空间对象会通过闭包构建一个新的 Endpoint ,这意味着如果你想拓展 API 的 endpoint,将会很方便的做到。
如下所示,我们新创建一个 endpoint:
$transport = $this->transport;
$serializer = $this->serializer;
$newEndpoint = function ($class) use ($transport, $serializer) {
if ($class == 'SuperSearch') {
return new MyProject\SuperSearch($transport);
} else {
// 默认 handler
$fullPath = '\\Elasticsearch\\Endpoints\\' . $class;
if ($class === 'Bulk' || $class === 'Msearch' || $class === 'MPercolate') {
return new $fullPath($transport, $serializer);
} else {
return new $fullPath($transport);
}
}
};
$client = ClientBuilder::create()
->setEndpoint($newEndpoint)
->build();
很明显,这样做你需要对现存的 endpoint 进行维护以保证其正常运行。同时你也需要确保端口和序列化都写入 endpoint 。
从 hash 配置中创建客户端
为了客户端的自动化创建能更加方便,所有的配置都可以用 hash 配置的形式来取代单一配置的方法。 这种配置方法可以通过 ClientBuilder::FromConfig()
方法来完成,这个静态方法可以接收一个数组,返回一个配置好的客户端。
数组的键名对应方法名,例如: retries
键对应 setRetries()
方法。
$params = [
'hosts' => [
'localhost:9200'
],
'retries' => 2,
'handler' => ClientBuilder::singleHandler()
];
$client = ClientBuilder::fromConfig($params);
为了帮助用户找出潜在的问题,未知参数会抛出异常, 如果你不想抛出异常(例如:你使用了一个 hash 配置来实现其他目的,但是这个键名却与 Elasticsearch 客户端无关),你可以在 fromConfig() 中设置 $quiet = true 来关闭。
$params = [
'hosts' => [
'localhost:9200'
],
'retries' => 2,
'imNotReal' => 5
];
// 设置 $quiet 的值为 true 来忽略未知的 `imNotReal` 键名
$client = ClientBuilder::fromConfig($params, true);