本文对Rserve的设计初衷、运行机制、核心对象和方法进行探讨,尝试为利用Rserve做深度产品开发的开发者提供初始帮助。主要分为四个章节,首先介绍研究Rserve代码需要的基础知识和Rserve的常用名词,第二章介绍了Rserve的工作流程、QAP1消息协议和主要的Command,第三章分析了Rserve服务器端的代码结构和核心函数(内容有些冗长),最后简要罗列Rserve的Java/C++/PHP客户端。本文对Rserve的设计初衷、运行机制、核心对象和方法进行探讨,尝试为利用Rserve做深度产品开发的开发者提供初始帮助。主要分为四个章节,首先介绍研究Rserve代码需要的基础知识和Rserve的常用名词,第二章介绍了Rserve的工作流程、QAP1消息协议和主要的Command,第三章分析了Rserve服务器端的代码结构和核心函数(内容有些冗长),最后简要罗列Rserve的Java/C++/PHP客户端。
作为一个的数据分析工具软件,R提供了多种对外接口或扩展机制,1)针对算法扩展,提供了R package开发 (采用R / C/ C++语言),2)针对批次执行或单次大计算量情形,提供了Batch命令行模式,3)对于应用整合,提供了C/Fortran API,方便第三方应用嵌入利用R的分析功能。第3)种模式不仅限定了编程语言,还要求应用开发人员处理R动态库初始化、内存处理、错误机制等技术细节(很容易出bug),针对这样的,一种Client-Server的框架还是很有必要的,消除了开发阶段的技术和门槛,在运行状态,规避了R动态库初始化、结束等处理的时间开销。RServe就是基于TCP/IP的R语言Client-Server框架的一种实现。
从2002年第一个版本发布,RServe目前已经演化到1.8-4。在RServe中,每个连接(Connection)有一个的工作空间(workspace)和工作目录,支持远程链接、安全认证、文件传输等功能。Rserve工具包也实现了Java、C++、PHP等客户端,方便其他编程语言与R数据结构的友好转换,通过SDK接口函数(或通信协议)将需要的数据加载到R,按照客户端指令进行相应的计算,并将结果返回到客户端,所有的数据和对象在连接期间一直是保持(persistent)的(在连接期间是有状态的)。下面的Java代码演示了调用本地Rserve服务,生成一个长度为10的正态分布数组。
如果把RServe作为一个工具,仅需了解如何利用RServe来开发第三方应用,可以看张丹的书或者RServe本身带的例子。但若要深度利用RServe,关心其运行机制与性能,则需要了解RServe的设计初衷及其设计机制,首先阅读RServe作者Simon Urbanek在2003年一篇论文(时间久远了一些,但基本思想和技术线没有变化),再根据需要补充一些前置知识,最后去研究其源代码。本文对RServe源代码进行了解读,为研究RServe代码提供一些初始帮助(RServe的使用方法本文不再赘述),也欢迎大家补充更正,一起加深理解,更好地利用RServe。
RServe源代码阅读的难度主要来自于3个方面,1)RServe涉及R、动态链接库、网络编程、多线程等多方面的技术知识(以及不同操作系统下的差异细节),要求高;2)RServe的系统性的技术设计资料缺乏,除了Urbanek关于设计机制的论文外,很多技术细节散落在RServe官网的技术说档、RServe代码和RServe的版本演化说件;3)RServe源代码复杂,文件多,代码长(Rserv.c有5000余行代码),为处理不同操作系统和服务端口的差异,用了大量的宏(如#ifdef unix),在抽象时还用了不少函数指针(源代码分析工具很难提供帮助)。为此,下面首先列举一下背景知识和RServe代码常见术语,然后介绍RServe的设计工作逻辑与对象,在此基础上,对服务端的核心源代码逐步分解,最后简要介绍一下客户端的封装逻辑。
RServe其本质还是利用R本身的动态链接库的能力,外加Connection、Session、线程/进程的处理逻辑。因此,对RServe的深入了解前提基础是1)R内部的机制;2)网络及Socket编程,3)多线程编程。
了解R的内置数据类型(SEXP),以及R动态链接库提供的一些基础函数(比如R_tryEval),大家可以阅读[R Internal](、[R External](文档的相关章节。一个简单的测试条件就是能读懂如下程序:
对网络编程,特别是socket编程,包括客户端socket、服务器端socket、安全(如会话管理、SSLServerSocket)及非阻塞I/O机制(如缓冲区、通道、就绪选择)等。
另外,熟悉一下unix下的fork()函数知识,了解进程创建过程、父子进程间关系等基础知识。
RServe可以在R或命令行(R CMD Rserve)启动,附带对应的配置参数或配置文件(详情请[RServe的说档]()。Rserve代码中调用了R动态库,在RServe启动期间,初始化R。
当一个新的连接请求接受后,Rserve用fork()创建一个新的进程(这样每个连接是一个的数据空间),并在RServe工作目录(默认是/tmp/Rserv)下为该连接创建一个子工作目录(目录名字格式为conn*X*,其中*X*是连接唯一ID)。相对于Unix操作系统,Rserve在Windows操作系统下有一定的局限性。主要是因为Windows下没有类似fork的命令来快速复制创建一个新的RServe进程。Windows版本的RServe实例在一个时刻只支持一个连接,同一个RServe实例的所有连接有一个共同的工作目录。当然,在Windows下可以用外部的分布式计算框架,启动多个RServe实例来支持多个连接。
RServe完成连接的初始化后,将向客户端返回一个32字节的消息,描述了RServe的能力,每个属性4个字节,采用(不能用特殊字符)。消息的格式如下
当连接关闭后,连接子工作目录如果为空,RServe将自动回收,但若非空,RServe将不会移除该目录,因为该目录中可能存放着其他本地应用(如Web服务器)需要访问的数据资源(比如图片),有本地应用负责该工作目录的移除工作。
CMD_ctrlEval、CMD_ctrlSource这2个控制命令的执行结果(可能会改变一些变量的数值)将影响所有后续的其他客户端连接。另外,控制命令是异步执行的(返回状态RESP_OK仅仅表示成功进入执行队列,并不代表执行成功),当完成当前客户请求后才可能开始执行,在控制命令执行期间,新的客户连接将被放入队列。因此,控制命令最好是执行时间比较短,避免造成大量的客户请求被缓存。
OC模式是为了执行指令的安全而设立,表达式只在闭包(closure)内的执行。在此模式下,除了CMD_OCcall,其他命令都被。在一个新连接建立后,服务端并不像普通模式下返回一个32字节的ID字符串,而是返回一个正常的QAP1消息(CMD_OCinit),消息至少有16个字节,客户端可以像普通ID字符串类似处理。每个CMD_OCcall是DT_SEXP(对LANGSXP编码)和一个OCref对象构成的闭包,在对表达式计算之前,Rserve需根据OCref对象进行表达式重构(de-reference)。
推荐:
网友评论 ()条 查看