首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

Grails 遠程調用 Action 解決大批量數據處理 OOM 問題

     編輯:Java 綜合教學

問題背景與根因分析

在 Grails 批量處理業務中,單個 Action 處理大量數據記錄會導致 Hibernate Session 一級緩存累積過大,最終觸發 OOM (OutOfMemoryError)。即使使用 MongoDB Plugin 底層連接操作,同樣面臨 Session 緩存無法及時釋放的問題。

核心機制

  • 單次 HTTP 請求對應一個 Hibernate Session
  • 所有持久化操作的實體均存放在 Session 一級緩存
  • 請求結束前 Session 不會 flush/clear,記憶體持續增長
  • 大批量寫入導致緩存撐爆堆內存

解決方案:遠程 Action 調用拆分 Session

將大批量邏輯拆分為多個小批次,每個批次通過 內部 HTTP 調用 獨立 Action 執行,每個 Action 擁有獨立 Session,執行完畢自動釋放。

目標 Action(單次處理少量數據)

def updateData() {
    println "classId:${params.classId} usersId:${params.usersId}"
    // 單次處理邏輯:保存少量記錄、更新狀態等
    // Session 自動提交並關閉
    render "ok"
}

遠程調用服務實現

class DataService {

    /**
     * 獲取協議+主機+端口基礎 URL
     * 例:https://localhost:9090/blog/bkoff/test → https://localhost:9090
     */
    private String getHostUrl(request) {
        String requestUrl = request.requestURL.toString()
        String separator = "://"
        int index = requestUrl.indexOf(separator)
        index = requestUrl.indexOf("/", index + separator.length())
        return requestUrl.substring(0, index)
    }

    /**
     * 遠程調用指定 Action
     * @param request 當前請求(用於構建基礎 URL)
     * @param actionUrl createLink 生成的相對路徑
     * @return 響應文本
     */
    def String remoteCall(request, actionUrl) {
        def hostUrl = getHostUrl(request)
        def url = new URL(hostUrl + actionUrl)
        return url.text // 同步阻塞獲取響應
    }
}

批量處理協調邏輯

class BatchController {

    def dataService

    def processLargeDataset() {
        def classId = "test1"
        def userIds = (1..10000).collect { "user_${it}" } // 模擬 1 萬條數據
        
        // 分批處理:每批 100 條
        userIds.collate(100).eachWithIndex { batch, index ->
            def actionUrl = g.createLink(
                controller: 'batch', 
                action: 'updateData', 
                params: [classId: classId, usersId: batch.join(',')]
            )
            
            def result = dataService.remoteCall(request, actionUrl)
            println "Batch ${index + 1} completed: ${result}"
            
            // 可選:避免過載,短暫休眠
            Thread.sleep(100)
        }
        
        render "All batches processed"
    }
}

關鍵技術點解析

createLink 生成正確 URL

// 在 Controller/GSP/Service 中均可使用
g.createLink(action: 'updateData', params: [classId: "test1", usersId: 'usersIdxxx'])
// 生成:/batch/updateData?classId=test1&usersId=usersIdxxx

request.requestURL 獲取完整請求 URL

包含協議、域名、端口、路徑,不含查詢參數。通過字符串截取獲取基礎 URL。

URL.text 同步阻塞調用

Groovy JDK 增強方法,等同於:

// Java 等價代碼
HttpURLConnection conn = (HttpURLConnection) new URL(fullUrl).openConnection();
conn.setRequestMethod("GET");
try (BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()))) {
    return in.lines().collect(Collectors.joining("\n"));
}

優化與生產環境建議

異步非阻塞調用(推薦)

// 使用 GPars 或 CompletableFuture 並行調用
import groovyx.gpars.GParsPool

GParsPool.withPool(10) {
    userIds.collate(100).collectParallel { batch ->
        def actionUrl = g.createLink(action: 'updateData', params: [usersId: batch.join(',')])
        dataService.remoteCallAsync(request, actionUrl) // 返回 Future
    }.each { future ->
        println "Batch result: ${future.get()}"
    }
}

錯誤處理與重試機制

def String remoteCallWithRetry(request, actionUrl, int maxRetries = 3) {
    def hostUrl = getHostUrl(request)
    def url = new URL(hostUrl + actionUrl)
    
    (1..maxRetries).each { attempt ->
        try {
            return url.text
        } catch (Exception e) {
            if (attempt == maxRetries) throw e
            println "Attempt ${attempt} failed, retrying... ${e.message}"
            Thread.sleep(1000 * attempt) // 指數退避
        }
    }
}

安全性考量

  • 內網隔離:確保調用僅限內部網路,防止 SSRF 攻擊
  • Token 驗證:Action 內驗證調用來源合法性
  • 頻率限制:避免大量並發調用衝垮應用自身

替代方案對比

方案優點缺點適用場景
遠程 Action 調用Session 完全隔離、代碼改動小、邏輯清晰網絡開銷、同步阻塞、需處理分佈式事務中等批量、現有 Action 復用
手動 Session 管理無網絡開銷、單進程、性能高需深度理解 Hibernate、代碼侵入性強大批量、高性能要求
無狀態 Session無緩存、內存恆定失去一級緩存優勢、不支援級聯操作純批量寫入、無關聯實體
分頁游標處理標準 JPA/Hibernate 模式需手動 clear/flush、代碼較複雜讀密集型批量處理

總結

遠程調用 Action 是解決 Grails 大批量處理 OOM 的低侵入性、快速見效方案。通過 HTTP 內部調用將大事務拆分為多個小事務,利用容器 Session 生命週期管理自動釋放記憶體。生產環境建議結合異步並行、重試機制、監控告警進一步增強穩健性。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved