前言
在數字化轉型持續加速的背景下,企業越來越多地將業務邏輯以服務化方式部署至云端。阿里云函數計算(Function Compute,簡稱FC)作為一種無服務器計算平臺,屏蔽了底層資源運維的復雜性,使開發者能夠專注于核心邏輯的開發與交付。只需上傳代碼,即可通過HTTP請求或事件驅動靈活觸發函數執行,實現彈性伸縮、按需計費的函數式計算能力。
在實際應用中,函數計算往往需要處理復雜的數據處理任務,如多源數據的匯聚、清洗與分析。然而,使用傳統Java編寫這類邏輯不僅代碼冗長、調試困難,還缺乏靈活性,微小的邏輯變更通常也需要重新構建、打包并上傳整個函數代碼包。
SPL(Structured Process Language)專注于結構化數據處理,具備簡潔的語法、豐富的計算函數,特別適合表達各類復雜的數據邏輯。更重要的是,SPL支持將數據處理邏輯以外部腳本形式運行,修改邏輯時只需替換腳本文件,無需重新構建或部署函數代碼包,即可在下一次調用時自動生效。這種“輕量級熱切換”特性,非常契合函數計算輕量、快速迭代的特性,有效提升了云端函數邏輯的可維護性與敏捷性。
此外,SPL原生支持多種數據源(如支持JDBC的數據源、NAS文件、JSON等),并具備可視化分步調試能力,是Serverless架構中應對動態數據邏輯、數據服務編排的高效利器。
本文將介紹如何結合Micronaut框架與SPL腳本,以Fat-JAR方式部署至阿里云FC2.0,構建一個通過RESTful API調用、支持邏輯熱更新的無服務器計算服務。示例將展示SPL如何訪問并分析MySQL數據庫和NAS文件系統,幫助讀者快速構建可擴展、易維護的Serverless數據服務方案。
SPL在阿里云FC中的架構圖
函數開發與部署
創建項目
使用 Micronaut CLI 快速創建 Maven 項目:
mn create-app micronaut-spl --build maven
創建完成后,可在集成開發環境(如 IntelliJ IDEA)中打開項目繼續開發。
集成SPL
添加依賴
在 pom.xml 中引入 SPL 與數據庫驅動:
<dependency><groupId>com.scudata.esproc</groupId><artifactId>esproc</artifactId><version>20250605</version>
</dependency><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><version>8.3.0</version>
</dependency>
提示:中央倉庫的更新頻率有限,推薦從[SPL 下載地址]下載標準版,并通過私有Maven倉庫同步更新。
相關jar包位于安裝目錄esProc/lib/,其中兩個基礎jar包為:
· esproc-bin-xxxx.jar:SPL 引擎及 JDBC 驅動
· icu4j-60.3.jar:國際化支持庫
準備SPL配置文件
raqsoftConfig.xml 為 SPL 的核心配置文件,負責數據源定義、腳本路徑管理等。本文中RDS MySQL數據源與腳本路徑的示例配置如下:
……
<DBList encryptLevel="0"><DB name="rds_mysql"><property name="url" value="jdbc:mysql://rm-xxx.mysql.rds.aliyuncs.com:3306/spltest?useCursorFetch=true"></property><property name="driver" value="com.mysql.cj.jdbc.Driver"></property><property name="type" value="10"></property><property name="user" value="dms_user"></property><property name="password" value="password"></property><property name="batchSize" value="0"></property><property name="autoConnect" value="true"></property><property name="useSchema" value="false"></property><property name="addTilde" value="false"></property><property name="caseSentence" value="false"></property></DB>
</DBList>
<esProc>
……<splPathList><splPath>/opt</splPath></splPathList>
……
</esProc>
……
特別注意:需加上useCursorFetch=true,否則JDBC驅動可能將整個結果集加載至內存,容易在 FC 的內存限制下導致連接斷開或錯誤。
與傳統項目部署方式不同,這里我們先將raqsoftConfig.xml打成zip包(raqsoftConfig.xml.zip),以供后續作為自定義層上傳使用。
SPL 通用接口實現
使用 SPL 提供的 JDBC 接口,在 Micronaut 中構建統一的函數調用入口。通過 HTTP POST 請求傳入參數,執行指定 SPL 腳本,并將結果以 JSON 格式返回。
該接口接收兩個參數:
· splxName:腳本文件名(不帶擴展名)
· jsonParam:SPL腳本所需參數(JSON字符串)
返回值中包括狀態碼、消息提示及腳本執行結果數據。
package micronaut.spl;import io.micronaut.http.annotation.*;
import io.micronaut.http.MediaType;import java.sql.*;
import java.util.*;@Controller("/spl")
public class SPLExecutionController {@Post(uri = "/call", consumes = MediaType.APPLICATION_JSON, produces = MediaType.APPLICATION_JSON)public Map<String, Object> execute(@Body Map<String, String> request) {Map<String, Object> response = new HashMap<>();String splxName = request.get("splxName");String jsonParam = request.get("jsonParam");if (splxName == null || splxName.isBlank()) {response.put("code", 400);response.put("message", "Missing splxName");return response;}try {Class.forName("com.esproc.jdbc.InternalDriver");try (Connection con = DriverManager.getConnection("jdbc:esproc:local://?config=/opt/raqsoftConfig.xml");CallableStatement st = con.prepareCall("call " + splxName + "(?)")) {if (jsonParam != null && !jsonParam.isEmpty()) {st.setString(1, jsonParam);} else {st.setNull(1, Types.VARCHAR);}boolean hasResult = st.execute();List<List<Object>> allResults = new ArrayList<>();while (true) {if (hasResult) {try (ResultSet rs = st.getResultSet()) {ResultSetMetaData metaData = rs.getMetaData();int columnCount = metaData.getColumnCount();List<Object> currentResult = new ArrayList<>();while (rs.next()) {if (columnCount == 1) {currentResult.add(rs.getObject(1));} else {Map<String, Object> row = new LinkedHashMap<>();for (int i = 1; i <= columnCount; i++) {row.put(metaData.getColumnLabel(i), rs.getObject(i));}currentResult.add(row);}}if (!currentResult.isEmpty()) {allResults.add(currentResult);}}}if (!st.getMoreResults() && st.getUpdateCount() == -1) {break;}hasResult = true;}if (!allResults.isEmpty()) {response.put("code", 200);response.put("message", "success");response.put("data", allResults.size() == 1 ? allResults.get(0) : allResults);} else {response.put("code", 404);response.put("message", "No result data");}}} catch (Exception e) {e.printStackTrace();response.put("code", 500);response.put("message", "Execution failed: " + e.getMessage());}return response;}
}
打包項目后,再打成zip包(micronaut-spl-0.1.jar.zip)
上傳部署Fat-JAR
在阿里云 FC 控制臺中創建函數,推薦配置如下:
· 創建方式:使用自定義運行時創建
· 運行環境:Java 21
· 上傳方式:ZIP 上傳(micronaut-spl-0.1.jar.zip)
· 啟動命令:java -jar micronaut-spl-0.1.jar
· 認證方式:無需認證(測試用)
點擊創建后,即可完成函數部署。
添加配置層
在函數詳情中點擊“編輯層”,上傳打包好的 raqsoftConfig.xml.zip,新建名為 config 的自定義層。
編寫SPL腳本
使用 MySQL 表數據計算
在阿里云 RDS 中創建 MySQL 實例及 orders 表,導入 TPC-H 示例數據(建庫建表和導入過程略)。以下腳本 rds-mysql.splx 演示如何按訂單年份與狀態分組統計訂單總額:
A | |
1 | =connect("rds_mysql") |
2 | =A1.cursor@x("SELECT O_ORDERDATE, O_ORDERSTATUS, O_TOTALPRICE FROM ORDERS") |
3 | =A2.groups(year(O_ORDERDATE):year,O_ORDERSTATUS:status;sum(O_TOTALPRICE):amount) |
將腳本打zip包后,添加并創建自定義層(splx),即可在/opt下訪問到該腳本。
調用示例
請求行
POST https://sss-fff-xxx.cn-xxx.fcapp.run/spl/call
Content-Type: application/json
請求體
{"splxName": "rds-mysql","jsonParam": ""
}
返回
{"code": 200,"data": [{"year": 1992,"status": "F","amount": 34330674052.43},{"year": 1993,"status": "F","amount": 34340410079.03},{"year": 1994,"status": "F","amount": 34416369052.97},{"year": 1995,"status": "F","amount": 6614961429.26},{"year": 1995,"status": "O","amount": 20822054361.33},{"year": 1995,"status": "P","amount": 7109117393.01},{"year": 1996,"status": "O","amount": 34609364760.86},{"year": 1997,"status": "O","amount": 34373633413.04},{"year": 1998,"status": "O","amount": 20212721905.53}],"message": "success"
}
使用 NAS 文件系統
通過阿里云函數配置掛載 NAS 路徑(如 /mnt/nas),可在 SPL 中直接讀寫該路徑下的文件,實現持久化與共享。
以下為 w.splx(寫)和 r.splx(讀)兩個腳本,模擬數據生成與讀取處理過程。
w.splx如下:
A | |
1 | =path=json(jsonParams).path |
2 | =connect("rds_mysql") |
3 | =A2.cursor@x("SELECT O_ORDERDATE, O_ORDERSTATUS, O_TOTALPRICE FROM ORDERS") |
4 | =file(path/"orders.btx").export@b(A3) |
5 | return "btx exported." |
r.splx如下:
A | |
1 | >p=json(jsonParam),path=p.path,n=p.n,m=p.m |
2 | =file(path/"orders.btx").cursor@b() |
3 | =A2.skip(n) |
4 | =A2.fetch(m) |
5 | >A2.close() |
6 | return A4 |
寫腳本,通過數據庫游標讀取MySQL實例中的orders表,并寫入orders.btx文件(path可使用配置的NAS路徑,實現持久化或與其他服務共享數據)。
讀腳本,通過讀取指定路徑文件的游標,跳過前n條記錄,展示接下來的m條記錄。
也可將腳本文件本身存放在 NAS 中,配合 raqsoftConfig.xml 中增加路徑:
…
<splPathList><splPath>/opt;/mnt/nas</splPath>
</splPathList>
…
寫入調用示例
請求行
POST https://sss-fff-xxx.cn-xxx.fcapp.run/spl/call
Content-Type: application/json
請求體
POST https://sss-fff-xxx.cn-xxx.fcapp.run/spl/call
Content-Type: application/json
返回
{"splxName": "w","jsonParam": "{path:/mnt/nas/}"
}
讀取調用示例
請求行
POST https://sss-fff-xxx.cn-xxx.fcapp.run/spl/call
Content-Type: application/json
請求體
{"splxName": "r","jsonParam": "{path:/mnt/nas/,n:99,m:2}"
}
返回
{"code": 200,"data": [{"O_ORDERDATE": "1992-12-16","O_ORDERSTATUS": "F","O_TOTALPRICE": 198800.71},{"O_ORDERDATE": "1994-02-17","O_ORDERSTATUS": "F","O_TOTALPRICE": 2519.40}],"message": "success"
}
業務邏輯變更
我們將“使用 MySQL 表數據計算”的“按訂單年份與狀態分組統計訂單總額”改為“按訂單年月與狀態分組統計訂單總額”,只需要將 A3 中的 year(O_ORDERDATE):year 改為 month@y(O_ORDERDATE):YearMonth 即可。由于 SPL 支持將數據邏輯以外部腳本形式運行,此類修改無需重新構建和部署函數代碼包,只需更新腳本內容即可在下一次函數調用時自動生效。這種“輕量級熱切換”方式完美契合 Serverless 架構短生命周期、按需執行的特點,顯著提升了云端數據服務的響應速度與維護效率,尤其適合頻繁調整的數據分析類場景。
總結
借助 Micronaut 的輕量級框架特性與 SPL 的靈活腳本執行能力,我們成功構建了一個具備結構化數據處理能力的 Serverless 應用,并以 Fat-JAR 方式部署至阿里云函數計算 FC2.0。函數啟動后可通過 REST 接口按需觸發,訪問 MySQL 或 NAS 等多種數據源,執行復雜的計算邏輯。
相比傳統 Java 開發,SPL 顯著簡化了數據處理過程,使業務邏輯更清晰、更易維護。尤其在 Serverless 架構下,SPL 的“腳本熱切換”能力無需重構或重新部署函數,即可在下次調用中自動生效,極大提升了系統的靈活性和運維效率。結合 NAS 文件系統,還能實現函數之間的數據共享與持久化,滿足更多元的業務場景需求。
這種架構適用于需要快速迭代、規則頻繁調整的數據服務場景,是企業在 Serverless 架構下構建高效、可擴展數據計算服務的有力方案。