:::

PHP也可以用R!R Remote API / R Remote API for PHP

image

我在這篇為之前發佈的RStudio Server加上了可供遠端使用的R Remote API,我們只要把R Script以POST方式傳到伺服器,就能夠取得R的計算結果或是圖片。R Remote API的使用分成伺服器端的架設、客戶端的使用、R Script的設置。R Remote API已經發佈到GitHub供大家使用:


系統架構 / System Architecture

image

整個系統架構很簡單,分成左邊的「系統」跟右邊的「R環境」。兩邊是個別獨立的兩臺伺服器,可以不需要綁在一起。

「系統」是指需要調用R功能的伺服器,上面運作的是PHP,或是其他任何可以用POST傳送資料、取得資料的伺服器。「系統」若要調用R環境,可以使用R Remote API的Client端函式來調用。Client端函式會將R Script以POST方式傳送到「R環境」,並取得R執行完成的結果。

另一方面,「R環境」則是可以運作R的伺服器。上面裝載了R Remote API的Server端接口。這個Server端接口接受來自「系統」Client端函式送來的R Script,執行R Script的內容,再顯示執行結果。值得一提的是,考量到很多時候我們是用R來繪製圖表(plot),我實作時也讓Server端能夠回傳圖表結果。

因為R環境上的R Remote API Server端設置比較複雜,我直接建置成RStudio Server虛擬機器,請到以下網址下載:

這也是「開箱即用的R運作環境!RStudio Server OpenVZ虛擬機器分享」這篇所使用的虛擬機器喔。

可分離的系統架構 /  Two Seperated System

必須注意的是,這並非單純的在「同一臺伺服器上」用的架構。大部分用PHP呼叫R的教學,像是「PHP 呼叫 R 整合教學,線上資料分析與繪圖工具開發」這篇、或是「php-r」,他們的做法都是在「同一臺伺服器上」用PHP呼叫R。單一伺服器的架構比較簡單、高效率,但我認為這背後有很多問題。

原因有四個。第一個原因在於若要應用在高負載的工作環境下,可分離的架構比較容易實作負載平衡,我們可以輕易地用虛擬機器架設各別的伺服器,並透過R Remote API把它們整合在一起。

另一個原因是R環境配置非常地複雜,光是版本跟套件的問題就足以讓許多人打退堂鼓(今天就聽到學弟想做文本探勘,但因為套件安裝問題所以放棄),要在運作中的伺服器配置可以使用的R環境,這可能會是一場噩夢。光是架設一個R環境來做文本探勘,我就用上了整個週末了。如果哪臺伺服器說要用R、要依序再安裝一次,那可真的令人吃不消。而我這篇把R換成可遠端使用的R Remote API,這樣就可以讓很多台不同的伺服器都能用上R的功能,也讓我們省下在各伺服器配置R的困擾。

第三個原因是我想讓更多程式語言都能使用R。雖然這篇是以PHP語言來呼叫R Remote API,但實際上這個架構走的是標準的HTTP Post方法。只要程式語言能夠用Post傳送資料,任何語言都能夠使用R Remote API。不過JavaScript這次不在考量範圍內。這是因為JavaScript通常是給客戶端,並不適合直接傳送R Script到R環境中計算,這樣會導致各種資安風險。正式架構應該是透過後端程式語言(如PHP)跟R Remote API互動,而JavaScript查詢的是後端程式語言的結果才比較合理。雖然一開始我很順手地作出了能夠搭配JavaScript $.getJSON()的架構,但仔細想過上面的問題之後,還是毅然決然地把它刪掉了。

第四個原因更是關鍵,分離式架構可以讓我們同時用不同版本的R環境。做到這篇的時候我發現R版本跟套件之間影響實在是太大了。如果要用我寫的文本探勘腳本,那麼就得用R 3.0.2的版本,不能用新版的R。那麼如果我們要用到舊版的功能、又想要用新版的套件,那怎麼辦?這時候分離式的架構就可以讓我們依照需求使用不同版本的環境。我們可以用原本我發佈的RStudio Server上的R 3.0.2,也可以參考這篇教學將R升級到最新版本。怎麼搭配都行!

基於以上原因,我這次實作的是就是一個分離式的R Remote API。請讀者不要把它跟其他PHP呼叫R的單一伺服器做法一概而論了。


在R環境安裝R Remote API / Install R Remote API in R Sever

基本配置要求是:

  1. 在R環境中安裝Apache,確保R環境可以用普通的網頁瀏覽器開啟。
  2. r-remote-api.php檔案放到Apache根目錄中。
  3. 修改r-remote-api.php的設定部分。

 

r-remote-api.php的設定部分只有兩個參數:

  • $rscript_exec:R執行的路徑。在Linux中是「Rscript」,在Windows中則是要明確指定安裝R的路徑,例如「C:/Program Files/R/R-3.0.2/bin/RScript.exe」
  • $whitelist:只有陣列中列出的IP可以使用R Remote API,IP的第四部分可以用萬用字元「*」。預設值如下,表示可以接受來自本機端、192.168.56.*以及192.168.11.*等範圍的IP連線:
    $whitelist = array("::1", "192.168.56.*", "192.168.11.*");
其他系統設定 / Server Configuration

但實際上還要根據R環境的配置進行許多調整,很多細節我也記不起來了。大致上我在Ubuntu 14.04中調整過以下內容:

  1. Apache使用者設成跟RStudio Server相同使用者,例如「rstudio」
    請修改Apache環境設定「/etc/apache2/envvars」
    修改以下設定
    export APACHE_RUN_USER=rstudio 
    export APACHE_RUN_GROUP=rstudio
  2. R本身加上預設語系
    修改R的環境設定「/etc/R/Renviron」
    加入以下設定:

    LANG = "en_US.UTF-8"
  3. 在Linux安裝中文字形。字型檔案請存到以下資料夾「/usr/share/fonts/」,再執行快取字型的指令

    fc-cache -fv

至於要如何在Windows環境下安裝R Remote API……那又是另外一個難題了。

我最推薦的做法就是直接架設我這篇的RStudio Server虛擬機器,這樣子最沒問題。但是就算用這個虛擬機器,也需要修改白名單 $whitelist 的設定,這樣讓它才能正常運作。


在系統使用R Remote API Client端函式 / R Remote API Client Function Usage

R Remote API Server端的操作可以用R Remote API Client端函式:「r-remote-api-client.php」。使用前都需要先include它:

include 'r-remote-api-client.php';

Client端函式「rRemoteAPI」需要輸入三個參數:

  • $remote_api_url:R Remote API Server端的網址,就是上面配置Server端程式的網址
  • $rscript : 就是R Script,以字串形態輸入。但是這個R Script有特殊格式,下面會仔細說明。
  • $parameters (選填):要輸入到R Script裡面的參數。

回傳資料格式是String字串。如果R Script運算結果是輸出文字訊息,那麼就會回傳單純的文字;如果R Script最後是輸出圖表,那麼就會回傳將圖片編碼成Base64格式的字串。

輸入到R Remote API的R Script需要輸入來自指令端的參數,不是普通的R Script。因此我們不能只是單純認識rRemoteAPI()的用法,還需要同時瞭解講解注意輸入的R Script的內容。

以下我介紹以R Remote API的兩種用法:輸入參數並計算平均數、取得亂數圖表。示範的程式碼都寫在demo.php中,可以直接參考。

計算平均數 / Calculate Parameters’ Mean

在PHP程式碼中輸入parameters參數及呼叫函式的語法如下:

// 載入R Remote API Client端函式
include 'r-remote-api-client.php';

// R Remote API Server端接口
$remote_api_url = "http://192.168.56.152/r-remote-api.php";

$rscript = file_get_contents("demo-message.R"); // 取得R Script
$parameters = array(20, 80); // 計算兩個數值的平均

// 取得平均數計算結果
echo "Result: " . rRemoteAPI($remote_api_url, $rscript, $parameters);

這段程式碼取得了「demo-message.R」的R Script,檔案內容如下。需要特別注意的是,因為args[1]是給圖表使用的檔案路徑,所以parameters輸入的參數得從args[2]開始算起。而且就算原本在PHP中輸入的資料是「整數」,在R Script中還是會當做「字串」處理。所以我們必須要在R Script裡面用strtoi()將字串轉換成整數。

# 取得圖表路徑跟輸入的parameters
args <- commandArgs(TRUE)

# 因為args[1]是圖表的檔案路徑及名稱,在此不能使用
# 實際上paramters是從args[2]開始計算

# 將parameters第一個參數設為最小值,用strtoi()將字串轉換成整數
min<-strtoi(args[2])
# 將parameters第二個參數設為最大值,用strtoi()將字串轉換成整數
max<-strtoi(args[3])

# 用c()組成陣列
data<-c(min:max)

# 用mean()計算平均,並以cat()輸出。用cat()輸出則不會顯示row name
cat(mean(data))

最後結果是:

Result: 50
取得圖表 / Get Plot

另一個顯示圖表的例子並不需要輸入parameters,所以只有取得R Script「demo-plot.R」,然後最後把結果放到<img />的src屬性中顯示。PHP程式碼如下:

// 載入R Remote API Client端函式
include 'r-remote-api-client.php';

// R Remote API Server端接口
$remote_api_url = "http://192.168.56.152/r-remote-api.php";

$rscript_plot = file_get_contents("demo-plot.R"); // 取得R Script

// 取得圖表結果
echo '<img src="' . rRemoteAPI($remote_api_url, $rscript_plot) . '" />';

這裡面使用的R Script「demo-plot.R」檔案全文如下。需要注意是要講圖表輸出到args[1]裡面的路徑,這樣才能正確取得圖表結果。

# 取得圖表路徑跟輸入的parameters
args <- commandArgs(TRUE)

# 將args[1]設為圖表的檔案路徑及名稱
plot.filename <- args[1]
# 設置將圖表儲存到以下路徑
png(plot.filename)

# 用亂數繪製直方圖
x <- rnorm(100,0,1)
hist(x, col="lightblue")

最後取得的結果如下圖所示:

下載


結語 / Conclusion

這篇就這樣草草的交待完了,其實這篇的架構只能說可用,但還不能真的放到正式運作的環境中。為什麼呢?因為這樣子呼叫R的做法非常地消耗資源。當R Script計算量一大,速度就變得很慢。如果要改進的話,應該要在R Remote API的Server端加入快取暫存機制,然後讓Client端函式自己決定快取保存的期限。若啟用快取、並以之前相同的R Script進行計算,這時候Server端就應該回傳之前運算的結果,這樣就可以省下大量重複計算的時間。

我以前也在PHP File Converter裡面做過類似的快取架構,對我而言不算什麼技術問題。但是要做到上述的架構,也需要耗費一番心力跟時間。我花在R上面的時間已經大幅超越我原本的預期,其實早該停手,回到原本的工作上了。

因此我想了想,好吧,那就做到這邊為止。這篇草草的介紹其實也不是要給一般的讀者看,而是讓「未來的自己」有跡可循。等到未來真的要將R整合到系統使用的時候,我再來把R Remote API好好改造吧。