连接池
连接池是客户端内置的一个对象,主要维持当前的节点列表。从理论上来说,节点只有死节点和活节点。
然而,在现实世界,事情绝不会这么清晰。有时候节点可能处在 「 可能挂了但是没有确定 」, 「 超时但是未知原因 」 或者 「 最近挂了但是现在好了 」的灰色地带。连接池的工作是管理一系列不稳定的连接并尝试给客户端提供最稳定的连接。
如果连接池找不到一个活节点来发送查询,将会返回一个 NoNodesAvailableException
异常。这与最大重试次数有所不同。例如,你的集群有 10 个节点,你发送一个请求,其中 9 个请求因为连接超时而请求失败。而第十个请求发送成功并且成功执行请求。则前九个请求会被标记为死节点(连接池处于使用状态才会被标记),并且这些节点的「 死亡 」定时器将会被触发。
当发送下一个请求给客户端时,这九个节点任然会被认为是 「 死节点 」 ,所以请求会跳过这些节点。请求只会发送到唯一的活节点(#10)中,假如发送到这个节点也失败了,那么就会返回 NoNodesAvailableException
。你会发现这里的发送次数比 retries
的值少,因为 retries
的值只适用于活节点。在这种情况下,只有一个节点是活节点,请求失败后就会返回 NoNodesAvailableException
。
这有几种连接池的实现可供选择:
staticNoPingConnectionPool (默认)
连接池维持一个静态的 host 列表,这些 host 会在客户端初始化时被假定为活节点。如果一个节点处理请求失败,这个节点会被标记位 dead
节点并维持 60 秒,而请求将会重试下一个节点。60 秒后,这个节点会被重新激活并且会加入请求轮询中。每增加一次请求失败次数都会导致死亡时间以指数级别增长。
一个成功的请求将会重置 failed ping timeout
计数器。
如果你想明确地设置连接池为 StaticNoPingConnectionPool
,你可以调用 ClientBuilder 对象的 setConnectionPool()
方法:
$client = ClientBuilder::create()
->setConnectionPool('\Elasticsearch\ConnectionPool\StaticNoPingConnectionPool', [])
->build();
注意:要通过命名空间加类名的方法来指定连接池。
staticConnectionPool
除了要在使用前 ping 节点是否为活节点,其他的特性与 StaticNoPingConnectionPool
相同。这对长时间执行的脚本有用,但是这将会添加额外的开销,因为这对 PHP 不是必须的。
使用 StaticConnectionPool
:
$client = ClientBuilder::create()
->setConnectionPool('\Elasticsearch\ConnectionPool\StaticConnectionPool', [])
->build();
注意:要通过命名空间加载类名的方法来指定连接池。
simpleConnectionPool
SimpleConnectionPool
仅仅会返回选择器指定的下一个节点信息,它不会追踪节点的 「生死状态」。不管节点是活节点还是死节点,这个连接池都将会返回其信息。它仅仅是一个简单的静态 host
连接池。
SimpleConnectionPool
不推荐在常规情况下使用,但是它是个有用的调试工具。
使用 SimpleConnectionPool
:
$client = ClientBuilder::create()
->setConnectionPool('\Elasticsearch\ConnectionPool\SimpleConnectionPool', [])
->build();
注意:要通过命名空间加载类名的方法来指定连接池。
sniffingConnectionPool
不同于之前的两个静态连接池,sniffingConnectionPool
连接池是动态的。 用户提供主机的种子列表,客户端使用该列表进行「嗅探」并发现群集的其余部分,它通过 Cluster State API 实现了这一点。在集群中添加或删除节点时,客户端将更新期活动连接池。
使用 SniffingConnectionPool
:
$client = ClientBuilder::create()
->setConnectionPool('\Elasticsearch\ConnectionPool\SniffingConnectionPool', [])
->build();
请注意,类的实现是通过指定命名空间路径来完成的。
自定义连接池
如果您希望实现自己编写的自定义连接池,类必须实现 ConnectionPoolInterface
接口:
class MyCustomConnectionPool implements ConnectionPoolInterface
{
/**
* @param bool $force
*
* @return ConnectionInterface
*/
public function nextConnection($force = false)
{
// 此处代码
}
/**
* @return void
*/
public function scheduleCheck()
{
// 此处代码
}
}
然后,你可以实例化 ConnectionPool 并将它注入到 ClientBuilder:
$myConnectionPool = new MyCustomConnectionPool();
$client = ClientBuilder::create()
->setConnectionPool($myConnectionPool, [])
->build();
如果连接池只是做少量修改, 你可以考虑扩展 AbstractConnectionPool
, 它提供了一些辅助的具体方法。 如果你选择这个方法,你需要确保 ConnectionPool 的实现具有兼容的构造函数 (由于它未曾在接口中定义):
class MyCustomConnectionPool extends AbstractConnectionPool implements ConnectionPoolInterface
{
public function __construct($connections, SelectorInterface $selector, ConnectionFactory $factory, $connectionPoolParams)
{
parent::__construct($connections, $selector, $factory, $connectionPoolParams);
}
/**
* @param bool $force
*
* @return ConnectionInterface
*/
public function nextConnection($force = false)
{
// 此处代码
}
/**
* @return void
*/
public function scheduleCheck()
{
// 此处代码
}
}
如果构造函数与 AbstractConnectionPool 匹配,你也可以使用对象注入或命名空间实例化:
$myConnectionPool = new MyCustomConnectionPool();
$client = ClientBuilder::create()
->setConnectionPool($myConnectionPool, []) // 对象注入
->setConnectionPool('/MyProject/ConnectionPools/MyCustomConnectionPool', []) // 或 命名空间
->build();
选择什么连接池?PHP 与连接池之间的关系
初看起来,似乎 sniffingConnectionPool
比较高级。对于当多数语言来说的确如此。在 PHP 中则略有不同。
因为 PHP 是无共享架构,PHP 脚本实例化后无法维持一个连接池。这意味着每一个脚本再次运行时需要创建,维护和销毁连接。
嗅探是一个相对轻量级的操作(调用一次 API 到 /_cluster/state
,然后 ping 每个节点),但是对于 PHP 应用来说,这可能是一个不可忽略的开销。一般的 PHP 脚本会加载客户端,执行一些请求然后关闭。想象一下这个脚本每秒钟要被调用 1000 次,嗅探连接池将会每秒执行嗅探和 ping 所有的节点 1000 次。嗅探程序则会增加很大的开销。
事实上,如果你的脚本仅仅执行一些请求,用嗅探就太粗暴了。嗅探对于常驻进程来说往往更加有用。
基于上述原因,才将默认的连接池设置为 staticNoPingConnectionPool
。当然,你可以改变默认设置,但是我们强烈的建议你进行测试和验证确保连接池对性能没有产生不良的影响。