使用ZIO-Streams的Redis Stream消費者和生產者實現原始碼

發表於2021-03-23

一個利用zio-streams和Redisson庫來使用和產生事件到Redis流的示例應用程式,基於Java 11,使用ZIO-Streams的Redis Stream消費者和生產者實現。點選標題見Github。

 

ZIO/ZIO Streams庫在Scala函數語言程式設計世界中非常令人著迷,Monad風格。與Akka流相比,ZIO流更簡單,更靈活且更具表現力。

以下是ZIO另外一個使用案例

要求:

  • 使用流逐行處理日誌
  • 過濾掉不是ERROR或WARN的訊息
  • 將流分成2個流:一個用於ERROR,另一個用於WARN訊息
  • 根據一定的時間表處理錯誤訊息,例如 每2秒
  • 批量處理WARN訊息。例如,將10條訊息分成一批

ZStream是ZIO Streams中的主要構造。首先,我們將從檔案輸入流建立ZStream:

ZStream.fromInputStream(fileInputStream)

您得到的是流的塊[T]。您可以將Chunk [T]視為Array [T]的不變版本,但效率更高。

.chunks
.aggregate(ZSink.utf8DecodeChunk)
.aggregate(ZSink.splitLines)
.mapConcatChunk(identity)

然後,我們將Chunk [String]的流轉換為String的流,並且每個元素都是日誌檔案中的一行訊息。

ZIO Streams具有.tap函式,您可以使用該函式使流中的訊息達到峰值。它是除錯流應用的好工具。

.tap(data => putStrLn(s"> $data"))

然後我們過濾掉不是ERROR或WARN的訊息:

.filter(isErrorWarning)

下一行程式碼基於isError謂詞返回true或false ,將當前流分成2個流。數字4是緩衝區大小,因此兩個子流可以以不同的速度執行直至緩衝區大小:

.partition(isError, 4)
val errorStream = leftStream
  .mapM(processError(_))
  .schedule(Schedule.fixed(2.second))

使用ZIO Schedule每2秒處理一次左流中的每個ERROR訊息。

對於正確流中的WARN訊息,我們將一次將它們分為6條訊息並進行批量處理:

val warningStream = rightStream
  .aggregate(ZSink.collectAllN[String](10))
  .mapM(processWarning(_))

如果您更容易理解,您可以認為ZSink是訊息生產者/使用者上下文中的訊息使用者。(並認為ZStream是訊息生成器)。ZSink通常用於ZIO Stream中的聚合功能。

最後,我們將2個流合併為一個並收集所有元素:

errorStream.merge(warningStream).runCollect

以下是完整程式碼:

import java.nio.file.{Files, Paths}

import zio._
import zio.console._
import zio.duration._
import zio.stream._

object LogStreamApp extends App {

  def isErrorWarning(data: String) = {
    data.contains("ERROR") || data.contains("WARN")
  }

  def isError(data: String): Boolean = {
    data.contains("ERROR")
  }

  def processError(data: String) = {
    putStrLn(s"process error message: ${data}") *>
      Task.succeed()
  }

  def processWarning(list: List[String]) = {
    putStrLn(s"process warning messages in batch: ${list.length} => $list") *>
      Task.succeed()
  }

  def run(args: List[String]): ZIO[ZEnv, Nothing, Int] = {
    val is = Files.newInputStream(Paths.get(ClassLoader.getSystemResource("prod_log.txt").toURI()))

    val theJob = (for {
      streams <- ZStream
        .fromInputStream(is)
        .chunks
        .aggregate(ZSink.utf8DecodeChunk)
        .aggregate(ZSink.splitLines)
        .mapConcatChunk(identity)
        .tap(data => putStrLn(s"> $data"))
        .filter(isErrorWarning)
        .partition(isError, 4)
    } yield streams).use {
      case (leftStream, rightStream) => {
        val errorStream = leftStream
          .mapM(processError(_))
          .schedule(Schedule.fixed(2.second))

        val warningStream = rightStream
          .aggregate(ZSink.collectAllN[String](10))
          .mapM(processWarning(_))

        errorStream.merge(warningStream).runCollect
      }
    }

    theJob.fold(_ => 1, _ => 0)
  }
}

the code at github.

相關文章