网上Presto有两个版本:prestosql/prestodb,大家喜欢称为社区版和Facebook版,除了包名,现在看还没多大的区别。但是这两家都在称自己是***offical***的,惊喜不?主要原因是两者是同一波人搞的。原Facebook Presto的三位核心另外拉了一个项目。为啥呢?facebook虽然开源了presto,但是并不怎么搭理开源社区的各位,还是围着自家转,文档什么的都是少到可怜。那三位有些不爽了,毕竟脱离了广大群众的软件不是好软件。总体来说,Presto也算是起了个早床,赶了个晚集。
大家引入Presto的时候感觉真是爽,查询速度飞起。突然有一天发现Presto抛出异常,报出的错误是和某某节点通信异常。鬼就鬼在这个节点ip不存在,后来发现是因为presto在k8s上的worker pod重启了,上面的ip呢是重启前的ip地址。其根源是冬冬在bbs上有讲的内存设置问题,同时也引发了另一个思考:Presto on k8s时不能按ip来进行通信,因为ip是浮动的。 那么有没有办法用域名呢?
要想解决问题,首先就得把问题了解透彻了,怎么知道集群中有哪些worker?worker怎么标识的?
Presto的页面上能看到有几个节点,可以从证明Presto的coordinate肯定知道这个信息。圈里一般的玩法是提供个api或者是存储位置让用户看。正如瓜中所述,Presto的使用文档格式清晰,排版漂亮,但都是些基础的配置,你想找点高深的配置或者接口,就得费点心了。Presto的源码里面有个文档文件夹,绝对的宝库。
presto-docs/src/main/sphinix/
子文件夹rest中包含了rest接口的所有说明,可知通过接口 /v1/node 能拿到coordinater上worker的信息。标准的返回:
{
"uri":"http://10.209.57.156:8080",
"recentRequests":25.181940555111073,
"recentFailures":0.0,
"recentSuccesses":25.195472984170983,
"lastRequestTime":"2013-12-22T13:32:44.673-05:00",
"lastResponseTime":"2013-12-22T13:32:44.677-05:00",
"age":"14155.28ms",
"recentFailureRatio":0.0,
"recentFailuresByType":{}
}
各返回值的含义:
字段 | 含义 |
---|---|
uri | worker的标致 |
recentRequests | 一个特殊运算的单位窗口内request次数统计值 |
recentFailures | 一个特殊运算的单位窗口内request失败的次数统计值 |
recentSuccesses | 一个特殊运算的单位窗口内request成功的次数统计值 |
lastRequestTime | 上次request时间 |
lastResponseTime | 上次Response时间 |
age | worker的生存时间 |
recentFailureRatio | 失败频率,等于recentFailures/ recentRequests |
recentFailuresByType | 根据具体失败的原因进行分类统计 |
上述值的统计和计算逻辑,可以在如下类中找到:
io.prestosql.failuredetector.HeartbeatFailureDetector
提到的特殊统计方法比较复杂,有兴趣的可以看两个地方:
上一章节中,我们可以发现Worker的标识是一个uri,这个uri默认采用的是ip地址。一进步我们发现整个Presto的服务发现机制都不在本项目内,而是引用了facebook的另一个开源项目airlift。没错,冷门,网上零文档。你搜索的时候不加个“apache”都不一定能搜索到这个项目。
大数据圈儿里服务发现也不是啥稀罕事儿,一般会有默认值同时提供可配置。开始从airlift的源码中捞。
First of all,搞明白个术语:在presto集群中管工作节点叫worker,但是在airlift中,大家都叫node。基本将配置文件锁定在NodeConfig类中。但是真正解释着几个配置的地方却在另一个地方的方法的注释中:NodeInfo。恕我不翻译了。
/**
* The internal network address the server should use when announcing its location to other machines.
* This address should available to all machines within the environment, but may not be globally routable.
* If this is not set, the following algorithm is used to choose the public address:
*
*
* - InetAddress.getLocalHost() if good IPv4
* - First good IPv4 address of an up network interface
* - First good IPv6 address of an up network interface
* - InetAddress.getLocalHost()
*
* An address is considered good if it is not a loopback address, a multicast address, or an any-local-address address.
*/
@Managed
public String getInternalAddress()
{
return internalAddress;
}
/**
* The IP address the server should use when binding a server socket.
*
* If this is not set, this will be the IPv4 any local address (e.g., 0.0.0.0).
*/
@Managed
public InetAddress getBindIp()
{
return bindIp;
}
private static String findInternalAddress(AddressSource addressSource)
{
switch (addressSource) {
case IP:
return InetAddresses.toAddrString(findInternalIp());
case HOSTNAME:
return getLocalHost().getHostName();
case FQDN:
return getLocalHost().getCanonicalHostName();
default:
throw new IllegalArgumentException();
}
}
至此,基本我们可以确认我们需要通过internal Address来指定成域名。注意上段代码中的最后一个方法,也就说有一种办法是可以直接去hostname的。But,这种方式并不适合k8s环境,为啥呢?K8s中pod的域名是{host name}.{service name},直接通过hostname是没法访问的。另外,咱们还得把presto k8s的部署方式设置为StatefuleSet,让hostname不会变来变去。
上文中我们已经零星的提到了服务的注册配置信息。那么整个服务发现是怎么玩的呢?Presto并不依赖其他的组件,没有共享DB,没有zookeeper等等,基本就是基于网络的服务发现。我们罗列一下现有的信息,查漏补缺:
服务的发现和保活是两套机制,注册完全依靠的是airlift,airlift也会定时汇报本地信息,但是频率较低。Presto会以较高的频次ping workder节点并更新worker的状态。