使用Rqueue框架基於Redis和Spring Boot執行非同步任務 -sonus21

banq發表於2021-05-20

在本文中,我們將學習如何使用Spring Boot 2.x和Redis執行非同步任務,最後的程式碼演示了本文中描述的步驟。
一個典型的API呼叫包括五件事:
  1. 執行一個或多個資料庫(RDBMS / NoSQL)查詢。
  2. 在某些快取系統(記憶體中,分散式等)上的一項或多項操作。
  3. 一些計算(可能是做一些數學運算的一些資料處理)。
  4. 呼叫其他服務(內部/外部)。
  5. 安排一個或多個任務在以後或立即在後臺執行。

出於多種原因,可以在以後的某個時間安排任務。例如,必須在訂單建立或裝運後7天生成發票。同樣,不需要立即傳送電子郵件通知,因此我們可以延遲它們。 
考慮到這些實際示例,有時,我們需要非同步執行任務以減少API響應時間。例如,我們收到一個立即刪除1K +記錄的請求,如果我們在同一API呼叫中刪除所有這些記錄,那麼肯定會增加API響應時間。為了減少API響應時間,我們可以在後臺執行一個任務,以刪除這些記錄。 
 

Cron計劃缺點
每當我們計劃任務以給定時間或特定間隔執行時,我們就會使用計劃在特定時間或間隔進行的cron作業。我們可以使用UNIX風格的crontabs,Chronos等其他工具來執行計劃任務。如果我們使用的是Spring框架,則可以使用現成的Scheduled註釋。 
大多數cron作業會查詢何時需要採取特定措施的記錄,例如,在7天后查詢所有發貨以及未生成發票的記錄。這些排程機制中的大多數都存在縮放問題,因為我們在其中掃描資料庫以查詢相關的行/記錄。
在許多情況下,這會導致全表掃描表現很差,想象一下實時應用程式和此批處理系統使用相同資料庫的情況。
由於它不可擴充套件,因此我們需要一些可擴充套件的系統,該系統可以在給定的時間或間隔執行任務,而不會出現任何效能問題。
有許多以這種方式擴充套件的方法,例如以批處理方式執行任務或在使用者/區域的特定子集上執行任務。另一種方法是在給定時間執行特定任務,而不依賴於其他任務,例如無伺服器功能。一個延遲佇列可以的情況下,一旦定時器達到預定時間的作業將被觸發使用。有許多可用的排隊系統/軟體,但很少有提供此功能的系統,例如SQS 它提供15分鐘的延遲,而不是7個小時或7天之類的任意延遲。
 

Rqueue框架

Rqueue是為Spring框架構建的訊息代理,將資料儲存在Redis中,並提供了一種在任意延遲下執行任務的機制。由於Redis與其他廣泛使用的排隊系統(例如Kafka或SQS)相比,具有一些優勢,因此Rqueue得到了Redis的支援。在大多數Web應用程式的後端中,Redis用於儲存快取的資料或其他目的。在當今世界上,有8.4% 的Web應用程式正在使用Redis資料庫。
通常,對於佇列,我們​​使用Kafka,SQS或其他一些系統。這些系統帶來了不同維度的額外開銷,例如,使用Rqueue和Redis可以將金錢減少為零。
除了成本外,如果我們使用Kafka,那麼我們需要進行基礎架構設定,維護,即需要更多操作,因為大多數應用程式已經在使用Redis,因此我們不會有操作開銷。實際上,相同的Redis伺服器/群集可與Rqueue一起使用,因為 Rqueue支援任意延遲。
這篇文章的完整程式碼可以在我的GitHub repo中找到。 
 

訊息傳遞
Rqueue保證至少一次傳送訊息,因為長時間的資料不會在資料庫中丟失。您可以在這裡閱讀更多有關此內容:Rqueue簡介
我們將需要的工具:

  • Any IDE
  • Gradle 
  • Java
  • Redis 

依賴:
  1. Spring Data Redis
  2. Spring Web
  3. Lombok 

我們將使用Rqueue庫以任意延遲執行任何任務。Rqueue是基於Spring的非同步任務執行器,可以在任何延遲下執行任務。它是由Spring訊息傳遞庫構建的,並由Redis支援。
我們將新增Rqueue Spring Boot starter 2.7.0依賴項:

dependencies {  
  implementation 'org.springframework.boot:spring-boot-starter-data-redis'
  implementation 'org.springframework.boot:spring-boot-starter-web'
  implementation 'com.github.sonus21:rqueue-spring-boot-starter:2.0.0-RELEASE'
  compileOnly 'org.projectlombok:lombok'   
  annotationProcessor 'org.projectlombok:lombok'
  providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
  testImplementation('org.springframework.boot:spring-boot-starter-test') {
    exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'  
  }
}
出於測試目的,我們將啟用Spring Web MVC功能,以便我們可以傳送測試請求。 
 

建立任務
使用Rqueue新增任務非常簡單,我們只需要使用RqueueListener註釋一個方法即可。RqueuListener批註具有多個可以根據用例設定的欄位,例如,設定deadLetterQueue可以將任務推送到另一個佇列,否則在失敗時將丟棄該任務。我們還可以使用numRetries欄位設定任務應重試多少次。
建立一個Java檔名MessageListener並新增一些方法來執行任務。

import com.github.sonus21.rqueue.annotation.RqueueListener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

@Component
@Slf4j
public class MessageListener {

  @RqueueListener(value = "${email.queue.name}")
  public void sendEmail(Email email) {
    log.info("Email {}", email);
  }

  @RqueueListener(value = "${invoice.queue.name}")
  public void generateInvoice(Invoice invoice) {
    log.info("Invoice {}", invoice);
  }
}

 

任務提交
可以使用RqueueMessageEnqueuer bean提交任務。 它有多種方法可以根據用例排隊任務,例如重試使用,重試計數和延遲任務的延遲。
我們需要AutoWire RqueueMessageEnqueuer或使用建構函式來注入此bean。
建立用於測試目的的控制器:
我們將計劃在接下來的30秒內完成發票生成,為此,我們將提交一個延遲30000(毫秒)的任務。另外,我們將嘗試傳送將在後臺完成的電子郵件。為此,我們將新增兩個GET方法sendEmail和generateInvoice,我們也可以使用POST。

@RestController
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
@Slf4j
public class Controller {
  private @NonNull RqueueMessageEnqueuer rqueueMessageEnqueuer;

  @Value("${email.queue.name}")
  private String emailQueueName;

  @Value("${invoice.queue.name}")
  private String invoiceQueueName;

  @Value("${invoice.queue.delay}")
  private Long invoiceDelay;

  @GetMapping("email")
  public String sendEmail(
      @RequestParam String email, @RequestParam String subject, @RequestParam String content) {
    log.info("Sending email");
    rqueueMessageEnqueuer.enqueue(emailQueueName, new Email(email, subject, content));
    return "Please check your inbox!";
  }

  @GetMapping("invoice")
  public String generateInvoice(@RequestParam String id, @RequestParam String type) {
    log.info("Generate invoice");
    rqueueMessageEnqueuer.enqueueIn(invoiceQueueName, new Invoice(id, type), invoiceDelay);
    return "Invoice would be generated in " + invoiceDelay + " milliseconds";
  }
}


application.properties:

email.queue.name=email-queue
invoice.queue.name=invoice-queue
# 30 seconds delay for invoice
invoice.queue.delay=300000


執行測:http://localhost:8080/email?email=xample@exampl.com&subject=%22test%20email%22&content=%22testing%20email%22

30秒後發票:

http://localhost:8080/invoice?id=INV-1234&type=PROFORMA
 
總之,我們可以使用Rqueue排程任務,而無需花費很多鍋爐程式碼。在配置和使用Rqueue庫時,我們需要考慮一些事項。重要的一項是任務是否是延遲的任務。預設情況下,假定任務需要儘快執行。
完整的程式碼可以在我的Github帳戶中找到https://github.com/sonus21/rqueue-task-exector
Rqueue庫程式碼:https://github.com/sonus21/rqueue

相關文章