簡體   English   中英

用 Spring 事件替換計划任務

[英]Replacing a scheduled task with Spring Events

在我的 Spring Boot 應用程序中,客戶可以提交文件。 每個客戶的文件都通過每分鍾運行的計划任務合並在一起。 由調度程序執行合並的事實有許多缺點,例如很難編寫端到端測試,因為在測試中您必須等待調度程序運行才能檢索合並結果。

因此,我想改用基於事件的方法,即

  1. 客戶提交文件
  2. 發布包含此客戶 ID 的事件
  3. 合並服務監聽這些事件並在事件 object 中為客戶執行合並操作

這將具有在有文件可用於合並后立即觸發合並操作的優點。

但是,這種方法存在許多問題,我需要一些幫助

並發

合並是一個相當昂貴的操作。 最多可能需要 20 秒,具體取決於所涉及的文件數量。 因此,合並必須異步進行,即不能作為發布合並事件的同一線程的一部分。 另外,我不想同時為同一個客戶執行多個合並操作,以避免出現以下情況

  1. 客戶 1 保存文件 2 觸發文件 1 和文件 2 的合並操作 2
  2. 很短的時間后,客戶 1 保存了文件 3,觸發了文件 1、文件 2 和文件 3 的合並操作 3
  3. 合並操作3完成保存合並文件3
  4. 合並操作2完成用merge-file2覆蓋merge-file3

為避免這種情況,我計划使用事件偵聽器中的鎖按順序處理同一客戶的合並操作,例如

@Component
public class MergeEventListener implements ApplicationListener<MergeEvent> {

    private final ConcurrentMap<String, Lock> customerLocks = new ConcurrentHashMap<>();

    @Override
    public void onApplicationEvent(MergeEvent event) {
        var customerId = event.getCustomerId();
        var customerLock = customerLocks.computeIfAbsent(customerId, key -> new ReentrantLock());
        customerLock.lock();
        mergeFileForCustomer(customerId);
        customerLock.unlock();
    }

    private void mergeFileForCustomer(String customerId) {
        // implementation omitted
    }
}

容錯

例如,如果應用程序在合並操作期間關閉或在合並操作期間發生錯誤,我該如何恢復?

計划方法的優點之一是它包含隱式重試機制,因為每次運行時它都會查找具有未合並文件的客戶。

概括

我懷疑我提出的解決方案可能正在(嚴重)重新實現此類問題的現有技術,例如 JMS。 我建議的解決方案是可取的,還是應該改用 JMS 之類的東西? 該應用程序托管在 Azure 上,因此我可以使用它提供的任何服務。

如果我的解決方案可取的,我應該如何處理容錯?

關於並發部分,如果每個客戶(在給定時間范圍內)提交的文件數量足夠小,我認為使用鎖的方法可以正常工作。

隨着時間的推移,您最終可以監控等待鎖定的線程數,以查看是否存在大量爭用。 如果有,那么也許您可以累積一些合並事件(在特定時間范圍內),然后運行並行合並操作,這實際上導致了類似於調度程序的解決方案。

在容錯方面,基於消息隊列的方法可以工作(沒有與 JMS 一起使用,但我看到它是消息隊列的實現)。

我會 go 與基於雲的消息隊列(例如SQS )僅僅因為可靠性的目的。 方法是:

  • 將合並事件推送到隊列中
  • 合並服務一次掃描一個事件並啟動合並作業
  • 合並作業完成后,從隊列中刪除消息

這樣,如果在合並過程中出現問題,消息將保留在隊列中,並在應用程序重新啟動時再次讀取。

經過一番考慮,我對這個問題的想法。

根據 OP 的規范,我將可能的解決方案限制為 Azure 托管服務提供的解決方案。

Azure Blob 存儲 Function 觸發器

因為這個問題是關於存儲文件的,所以讓我們從使用在文件創建時觸發的觸發器 function 探索 Blob 存儲開始。 根據文檔,Azure 函數最多可以運行 230 秒,並且默認重試次數為 5。

但是,此解決方案將要求來自單個客戶的文件以不會導致並發問題的方式到達,因此讓我們暫時保留此解決方案。

Azure 隊列存儲

不保證先進先出 (FIFO) 有序交付,因此不符合要求。

存儲隊列和服務總線隊列 - 比較和對比: https://docs.microsoft.com/en-us/azure/service-bus-messaging/service-bus-azure-and-service-bus-queues-compared-contrasted

Azure 服務總線

Azure Service Bus是一個 FIFO 隊列,似乎滿足要求。

https://docs.microsoft.com/en-us/azure/service-bus-messaging/service-bus-azure-and-service-bus-queues-compared-contrasted#compare-storage-queues-and-service-總線隊列

從上面的文檔中,我們看到大文件不適合作為消息負載。 為了解決這個問題,可以將文件存儲在 Azure Blob Storage 中,並且消息將包含在哪里可以找到文件的信息。


選擇Azure 服務總線Azure Blob 存儲后,讓我們討論實施注意事項。

隊列生產者

在 AWS 上,生產者端的解決方案是這樣的:

  1. 專用端點向客戶應用程序提供預簽名的 URL
  2. 客戶應用程序將文件上傳到 S3
  3. Lambda 由 S3 object 創建觸發將消息插入隊列

不幸的是,Azure 還沒有預簽名的 URL 等效項(它們具有不相等的共享訪問簽名),因此文件上傳必須通過端點完成,該端點又將文件存儲到 Z3A5150F142F898F3677 當需要文件上傳端點時,讓文件上傳端點也負責將消息插入隊列似乎是合適的。

隊列消費者

因為文件合並需要大量時間(約 20 秒),所以應該可以橫向擴展消費者端。 對於多個消費者,我們必須確保不超過一個消費者實例處理單個客戶。 這可以通過使用消息會話來解決: https://docs.microsoft.com/en-us/azure/service-bus-messaging/message-sessions

為了實現容錯,消費者應該在文件合並期間使用 peek-lock(而不是接收和刪除),並在文件合並完成時將消息標記為已完成。 當消息被標記為完成時,消費者可能負責刪除 Blob 存儲中的多余文件。

現有解決方案和未來解決方案可能存在的問題

如果客戶A開始上傳大文件#1 ,然后立即開始上傳小文件#2 ,則文件# 2的文件上傳可能在文件#1之前完成並導致亂序情況。

我認為這是通過使用某種鎖定機制或文件名約定在現有解決方案中解決的問題。

Spring-boot 搭配 Kafka 可以解決您的容錯問題。

Kafka 支持生產者-消費者 model。 讓客戶事件發布到 Kafka 生產者。

為 Kafka 配置復制功能,以免丟失任何事件。

使用可以為每個事件調用合並服務的消費者。

  1. 一旦消費者讀取了 customerId 的事件並合並然后提交偏移量。

  2. 如果在合並事件之間發生任何故障,則不會提交偏移量,因此當應用程序再次啟動時可以再次讀取它。

  3. 如果合並服務可以檢測到具有給定數據的重復事件,那么重新處理相同的消息應該不會導致任何問題(Kafka 承諾事件的單次傳遞)。 重復事件檢測是對已處理完整但未能提交到 Kafka 的事件的安全檢查。

首先,基於事件的方法對於這種情況是正確的。 您應該為發布-訂閱事件消息使用外部代理。

注意,默認情況下,Spring 發布事件是同步的。

假設您有 3 個服務:

  1. 應用服務
  2. 合並服務
  3. CDC 服務(變更數據捕獲)
  4. 經紀服務 (Kafka, RabbitMQ,...)

基於“發件箱模式”的主流:

  1. 應用服務將事件消息保存到發件箱消息表
  2. CDC Service 監視發件箱表並將事件消息從發件箱表發布到 Broker Servie
  3. Merge Service 訂閱 Broker Server 並接收事件消息(消息有序)
  4. Merge Servcie 執行合並操作

您可以為此流程使用eventuate lib。

此外,您可以將 DDD 應用到您的架構中。 使用 Axon 框架進行 CQRS 模式、公共領域事件並對其進行處理。

參考:

  1. 發件箱模式: https://microservices.io/patterns/data/transactional-outbox.html 在此處輸入圖像描述

聽起來您確實可以使用StreamETL工具來完成這項工作。 當您開發應用程序時,您有一些優先級/排隊/批處理要求,很容易看出如何使用Cron + SQL Database構建解決方案,可能有一個隊列將工作與生產工作分離。

這很可能是最容易構建的東西,因為您對這種方法有很多粒度和控制。 如果您認為您實際上可以通過這種方式快速且低風險地滿足您的要求,那么您可以這樣做。

有些軟件組件更適合這些任務,但它們確實有一些學習曲線,並且取決於您可能使用的 PAAS 或雲。 您將獲得開箱即用的監控、可擴展性和可用性彈性。 開源或雲服務將減輕您的管理負擔。

使用什么也取決於您的優先級和要求。 如果您想使用 go ETL 方法,該方法非常適合存儲工作,您可能需要使用 Glue t 之類的東西。 如果您想要優先級功能,您可能想要使用多個隊列,這真的取決於。 您還需要使用儀表板進行監控,以查看無論采用何種方法,您的合並都應等待多長時間。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM