译:vertx-kotlin-coroutine | Lucas's blog

2,242 阅读5分钟
原文链接: zhenfeng-zhu.github.io

尝试翻译vertx的文档。尊重原文,部分使用自己的理解。

Vert.x的kotlin协程提供了async/await或者和go类似的channel。这使得你能够以熟悉的顺序风格写垂直代码。

vertx-lang-kotlin-coroutines集成了kotlin协程,用于执行异步操作和处理事件。这导致了一个看起来像是同步代码的编程模型,但它不会阻塞内核线程。

简介

vert.x与许多旧的应用平台相比的一个主要优势是它几乎完全是非阻塞的(内核线程)。这允许基于vert.x的程序使用极少数的内核线程处理大量的并发(例如:许多连接和消息),可以获得更好的伸缩性。

vert.x的非阻塞特性形成了非阻塞API。非阻塞API可以采用多种形式来实现,包括回调函数,promise,fibers或者响应式扩展。vert.x的核心API使用回调函数的风格,但是它也支持其他模型,如RxJava 1和2。

在某些情况下,使用异步的API编程可能比使用经典的顺序代码风格更具有挑战性,特别是需要按照顺序完成若干操作。另外,使用异步API时,错误的传播也更为复杂。

vertx-lang-kotlin-coroutines使用协程。协程是非常轻量级的线程,而且不与底层的内核线程对应。所以当协程需要“阻塞”时,它会暂停并释放当前的内核线程,使得另一个协程可以处理事件。

vertx-lang-kotlin-coroutines使用kotlinx.coroutines来实现协程。

vertx-lang-kotlin-coroutines目前仅适用于kotlin,而且是kotlin1.1的一个实验特性。

从一个vertx.x的contex中启动协程

导入io.vertx.kotlin.coroutines.VertxCoroutine,launch(协程生成器)方法中允许运行一段代码作为可以暂停的协程:

val vertx = Vertx.vertx()
vertx.deployVerticle(ExampleVerticle())

launch(vertx.dispatcher()) {
  val timerId = awaitEvent<Long> { handler ->
    vertx.setTimer(1000, handler)
  }
  println("Event fired from timer with id ${timerId}")
}

vertx.dispatcher()返回一个使用vert.x的事件循环执行协程的disptacher。

awaitEvent函数暂停协程的执行直到定时器触发为止,并使用赋给handler的值恢复协程。

有关handlers,events和事件流的更多细节,将在下一节中给出。

继承CoroutineVerticle

你可以将代码部署为io.vertx.kotlin.coroutines.CoroutineVerticle的实例,这是kotlin协程的专用类型。你应该重载verticle的start()方法,stop()方法的重载是可选的:

class MyVerticle : CoroutineVerticle() {
  suspend override fun start() {
    // ...
  }
}

获得一次性的异步结果

vert.x的许多异步操作都采用Handler<AsyncResult >作为最后一个参数。一个例子就是使用vert.x的mongo client执行对象检索,或者是发送一个事件总线消息之后等待回复。

这是通过awaitResult方法来实现,它返回一个值或者抛出一个异常。

协程会一直处于暂停的状态知道事件被处理,并且这时没有内核线程被阻塞。

The method is executed by specifying the asynchronous operation that needs to be executed in the form of a block that is passed to the handler at run-time.

这个方法是通过指定一个异步操作来执行,这个异步操作需要以块的形式执行,而这个异步操作块在运行时会被传给handler。

这里是一个例子:

suspend fun awaitResultExample() {
  val consumer = vertx.eventBus().localConsumer<String>("a.b.c")
  consumer.handler { message ->
    println("Consumer received: ${message.body()}")
    message.reply("pong")
  }

  // Send a message and wait for a reply
  val reply = awaitResult<Message<String>> { h ->
    vertx.eventBus().send("a.b.c", "ping", h)
  }
  println("Reply received: ${reply.body()}")
}

当此块产生失败时,调用者可以使用try/catch结构处理异常。

suspend fun awaitResultFailureExample() {
  val consumer = vertx.eventBus().localConsumer<String>("a.b.c")
  consumer.handler { message ->
    // The consumer will get a failure
    message.fail(0, "it failed!!!")
  }

  // Send a message and wait for a reply
  try {
    val reply: Message<String> = awaitResult<Message<String>> { h ->
         vertx.eventBus().send("a.b.c", "ping", h)
       }
  } catch(e: ReplyException) {
    // Handle specific reply exception here
    println("Reply failure: ${e.message}")
  }
}

获取一次性事件

使用awaitEvent函数处理一次性事件(而不是下一次出现的事件):

suspend fun awaitEventExample() {
  val id = awaitEvent<Long> { h -> vertx.setTimer(2000L, h) }
  println("This should be fired in 2s by some time with id=$id")
}

获取一次性worker的结果

使用awaitBlocking函数处理阻塞计算:

suspend fun awaitBlockingExample() {
  val s = awaitBlocking<String> {
    Thread.sleep(1000)
    "some-string"
  }
}

事件流

在vert.x API的很多地方,事件流都是通过handler来处理。这些例子包括事件消息总线的使用者和http服务器的请求。

ReceiveChannelHandler类允许通过(suspendable)receive方法接收事件:

suspend fun streamExample() {
  val adapter = vertx.receiveChannelHandler<Message<Int>>()
  vertx.eventBus().localConsumer<Int>("a.b.c").handler(adapter)

  // Send 15 messages
  for (i in 0..15) vertx.eventBus().send("a.b.c", i)

  // Receive the first 10 messages
  for (i in 0..10) {
    val message = adapter.receive()
    println("Received: ${message.body()}")
  }
}

等待vert.x的future的完成

vert.x的future类实例的扩展方法await,可以暂停协程直到他们完成。在这种情况下,该方法返回相应的AsyncResult 对象。

suspend fun awaitingFuture() {
  val httpServerFuture = Future.future<HttpServer>()
  vertx.createHttpServer()
    .requestHandler { req -> req.response().end("Hello!") }
    .listen(8000, httpServerFuture)

  val httpServer = httpServerFuture.await()
  println("HTTP server port: ${httpServer.actualPort()}")

  val result = CompositeFuture.all(httpServerFuture, httpServerFuture).await()
  if (result.succeeded()) {
    println("The server is now running!")
  } else {
    result.cause().printStackTrace()
  }
}

通道

channel和java的BlockingQueue类似,只是Channel不会阻塞而是暂停协程。

  • 将值发送到满了的Channel会暂停协程
  • 从一个空Channel中接收值也会暂停协程

使用toChannel的扩展方法可以将vert.x的ReadStream 和 WriteStream适配成通道。

这些适配器负责管理背压和流终端:

  • ReadStream 适配为 ReceiveChannel
  • WriteStream 适配为 SendChannel

接收数据

当你需要处理一系列相关值的时候,channel可能非常有用:

suspend fun handleTemperatureStream() {
  val stream = vertx.eventBus().consumer<Double>("temperature")
  val channel = stream.toChannel(vertx)

  var min = Double.MAX_VALUE
  var max = Double.MIN_VALUE

  // Iterate until the stream is closed
  // Non-blocking
  for (msg in channel) {
    val temperature = msg.body()
    min = Math.min(min, temperature)
    max = Math.max(max, temperature)
  }

  // The stream is now closed
}

他也可以用于解析协议。我们将构建一个非阻塞的http请求解析器来展示通道的功能。

我们将依靠RecordParser将以\r \n分割的buffer流进行分割。

这是解析器的初始版本,它只处理http的请求行。

val server = vertx.createNetServer().connectHandler { socket ->

  // The record parser provides a stream of buffers delimited by \r\n
  val stream = RecordParser.newDelimited("\r\n", socket)

  // Convert the stream to a Kotlin channel
  val channel = stream.toChannel(vertx)

  // Run the coroutine
  launch(vertx.dispatcher()) {

    // Receive the request-line
    // Non-blocking
    val line = channel.receive().15    [native code]
}">toString().split(" ")
    val method = line[0]
    val uri = line[1]

    println("Received HTTP request ($method, $uri)")

    // Still need to parse headers and body...
  }
}

解析请求行就像在channel上调用receive一样简单。

下一步是通过接收块来解析http头,直到我们得到一个空块为止。

// Receive HTTP headers
val headers = HashMap<String, String>()
while (true) {

  // Non-blocking
  val header = channel.receive().7    [native code]
}">toString()

  // Done with parsing headers
  if (header.isEmpty()) {
    break
  }

  val pos = header.indexOf(':')
  headers[header.substring(0, pos).toLowerCase()] = header.substring(pos + 1).trim()
}

println("Received HTTP request ($method, $uri) with headers ${headers.keys}")

最后,我们通过处理可选的请求体来终止解析器。

// Receive the request body
val transferEncoding = headers["transfer-encoding"]
val contentLength = headers["content-length"]

val body : Buffer?
if (transferEncoding == "chunked") {

  // Handle chunked encoding, e.g
  // 5\r\n
  // HELLO\r\n
  // 0\r\n
  // \r\n

  body = Buffer.buffer()
  while (true) {

    // Parse length chunk
    // Non-blocking
    val len = channel.receive().20    [native code]
}">toString().toInt(16)
    if (len == 0) {
      break
    }

    // The stream is flipped to parse a chunk of the exact size
    stream.fixedSizeMode(len + 2)

    // Receive the chunk and append it
    // Non-blocking
    val chunk = channel.receive()
    body.appendBuffer(chunk, 0, chunk.length() - 2)

    // The stream is flipped back to the \r\n delimiter to parse the next chunk
    stream.delimitedMode("\r\n")
  }
} else if (contentLength != null) {

  // The stream is flipped to parse a body of the exact size
  stream.fixedSizeMode(contentLength.toInt())

  // Non-blocking
  body = channel.receive()
} else {
  body = null
}

println("Received HTTP request ($method, $uri) with headers ${headers.keys} and body with size ${body?.length() ?: 0}")

发送数据

使用channel发送数据也非常直接:

suspend fun sendChannel() {
  val stream = vertx.eventBus().publisher<Double>("temperature")
  val channel = stream.toChannel(vertx)

  while (true) {
    val temperature = readTemperatureSensor()

    // Broadcast the temperature
    // Non-blocking but could be suspended
    channel.send(temperature)

    // Wait for one second
    awaitEvent<Long> { vertx.setTimer(1000, it)  }
  }
}

SendChannel#send 和 WriteStream#write都是非阻塞操作。不像当channel满的时候SendChannel#send可以停止执行,而等效WriteStream#writ的无channel操作可能像这样:

// Check we can write in the stream
if (stream.writeQueueFull()) {

  // We can't write so we set a drain handler to be called when we can write again
  stream.drainHandler { broadcastTemperature() }
} else {

  // Read temperature
  val temperature = readTemperatureSensor()

  // Write it to the stream
  stream.write(temperature)

  // Wait for one second
  vertx.setTimer(1000) {
    broadcastTemperature()
  }
}

延迟,取消和超时

借助于vert.x的定时器,vert.x的调度器完全支持协程的delay函数:

launch(vertx.dispatcher()) {
  // Set a one second Vertx timer
  delay(1000)
}

定时器也支持取消:

val job = launch(vertx.dispatcher()) {
  // Set a one second Vertx timer
  while (true) {
    delay(1000)
    // Do something periodically
  }
}

// Sometimes later
job.cancel()

取消是合作的。

你也可以使用withTimeout函数安排超时。

launch(vertx.dispatcher()) {
  try {
    val id = withTimeout<String>(1000) {
      return awaitEvent<String> { anAsyncMethod(it) }
    }
  } catch (e: TimeoutCancellationException) {
    // Cancelled
  }
}

Vert.x支持所有的协程构建器:launch,async和runBlocking。runBlocking构建器不能再vert.x的时间循环线程中使用。

协程的互操作性

vert.x集成协程被设计为完全可以和kotlin协程互操作。

kotlinx.coroutines.experimental.sync.Mutex被执行在使用vert.x调度器的事件循环线程。

RxJava的互操作性

虽然vertx-lang-kotlin-coroutines模块没有与RxJava特定集成,但是kotlin协程提供了RxJava的集成。RxJava可以和vertx-lang-kotlin-coroutines很好的协同工作。

你可以阅读响应流和协程的指南。