在前面的篇章中,我们把重点放在livy的REPL
功能的展示和源码分析。这篇我们探索一下livy
Using the Programmatic API功能。
与REPL
不同的是,Programmatic API
提供了在一个“已经存在”的SparkContext上执行处理程序的机制。用户需要实现Job
接口:
public interface Job extends Serializable {
T call(JobContext jc) throws Exception;
}
JobContext
对象可以访问到SparkContext
和SQLContext
,所以,用户不用关心SparkContext
的创建。
当用户编写了实现Job
接口的类后,打包成jar包,就可以使用LivyClient
上传jar包、提交Job了。
这里做个补充livyUrl
是指http://host:port/sessions/xxxx
,即为对应session的RESTful地址。livy的例子文档中,没有写清楚的是livyUrl
是什么。
livy服务端,会在对应session的SparkContext上运行Job。
源码分析
客户端代码
LivyClient
和LivyClientFactory
在livy源码中是接口,而实现是HttpClientFactory
:
/**
* Factory for HTTP Livy clients. */
public final class HttpClientFactory implements LivyClientFactory {
@Override
public LivyClient createClient(URI uri, Properties config) {
if (!"http".equals(uri.getScheme()) && !"https".equals(uri.getScheme())) {
return null;
}
return new HttpClient(uri, new HttpConf(config));
}
}
而LivyClientBuilder
使用ServiceLoader加载LivyClientFactory
的实现类。
HttpClient会向如下几个接口地址请求:
- /sessions/%d/upload-jar
- /sessions/%d/add-jar
- /sessions/%d/upload-file
- /sessions/%d/add-file
- /sessions/%d/submit-job
- /sessions/%d/run-job
例如,当调用sumbit时,会请求/sessions/%d/submit-job
。我们重点看submit-job
,最终会调用sendJob
:
// command就是submit-job或run-job
private JobHandleImpl sendJob(final String command, Job job) {
final ByteBuffer serializedJob = serializer.serialize(job);
JobHandleImpl handle = new JobHandleImpl(config, conn, sessionId, executor, serializer);
handle.start(command, serializedJob);
return handle;
}
这里看到,对job类的实例进行了序列化
。这里用的序列化方式就是前面篇章中提到的kryo
。序列化最终会把类转化成byte[]
。稍后在服务端,可以看到会使用反序列化恢复Job类。
服务端代码
客户端的http请求首先会到达livyServer,入口主要在InteractiveSessionServlet
:
上面的代码接收到客户端请求,选择到对应的session,并调用session的submitJob
。
在第三篇中,我们知道livyServer中的一个session本质上对应了一个正在运行的driver程序。并且session会连接到driver,创建出一条链路,进而与driver通信。
上述submitJob最终会通过这条链路,向driver发送消息,那么发送的具体是什么消息呢?通过下面代码可以看到,其实发送的是BypassJobRequest
,并且这里还没有对Job进行反序列化
:
String jobId = UUID.randomUUID().toString();
Object msg = new BypassJobRequest(jobId, jobType, BufferUtils.toByteArray(serializedJob), sync);
...
if (driverRpc.isSuccess()) {
try {
return driverRpc.get().call(msg, retType);
} catch (Exception ie) {
throw Utils.propagate(ie);
}
}
根据这条线索,以及第六篇中关于RPC框架的结论。我们在RSCDriver
里面找到了对应的handle
:
进一步在BypassJob
中看到,对序列化的job对象进行反序列化,并执行call
的地方:
总结
本篇介绍了livy
的Programmatic API,这部分官方文档目前比较少。因此,更多从源码角度做个分析,以便后续采坑。
下图总结了Job提交运行的基本流程:
- Client端首先对Job对象采用kryo进行序列化,通过http接口调用到livyServer
- livyServer封装请求为RPC消息
BypassJobRequest
,发送给对应的driver - driver侧对Job执行反序列化,并调用其
call