Vert.x 的 Event Bus入门使用

434 阅读4分钟

Event Bus介绍

事件总线是什么:

vert.x 的事件总线是一种以异步方式发送和接收消息的方法。消息被发送到目的地并从目的地检索,目的地只是一个自由格式的字符串。

消息有一个正文,用于存储元数据的可选标头,以及一个过期时间戳,如果尚未处理,消息将被丢弃。

消息体通常使用 Vert.x JSON 表示形式进行编码。使用 JSON 的优点是它是一种序列化格式,可以轻松地通过网络传输,并且所有编程语言都可以理解它。

使用事件总线的优点:

  • 事件总线允许 verticle 之间的解耦。一个 Verticle 不需要访问另一个 Verticle 类——所需要做的只是就目标名称和数据表示达成一致。
  • 由于 Vert.x 是多语言的,因此事件总线允许用不同语言编写的 verticle 相互通信,而无需任何复杂的语言互操作层,无论是在同一 JVM 进程内还是通过网络进行通信。

Event Bus三种通信模型

  • 点对点消息传递
  • 请求-回复消息传递
  • 发布/订阅消息

事件总线是另外一种mq吗?

Vert.x 事件总线不是 Apache ActiveMQ、RabbitMQ、ZeroMQ 或 Apache Kafka 的替代品。它是应用程序内部用于 verticle 到 verticle 通信的事件总线,而不是用于应用程序到应用程序通信的消息总线。

具体来说,事件总线不会执行以下操作:

  • 支持消息确认
  • 支持消息优先级
  • 支持消息持久性以从崩溃中恢复
  • 提供路由规则
  • 提供转换规则(模式适应、分散/聚集等)

Event Bus API

如何获取eventbus的实例

得到一个 event bus:

EventBus eb = vertx.eventBus();

注册和注销handler

注册handler处理消息:

EventBus eb = vertx.eventBus();

eb.consumer("news.uk.sport", message -> {
  System.out.println("I have received a message: " + message.body());
});

这是简单的方式,用一个consumer直接处理消息。

也可以返回一个consumer,然后再绑定一个handler:

EventBus eb = vertx.eventBus();

MessageConsumer<String> consumer = eb.consumer("news.uk.sport");
consumer.handler(message -> {
  System.out.println("I have received a message: " + message.body());
});

注销handler:

consumer
  .unregister()
  .onComplete(res -> {
    if (res.succeeded()) {
      System.out.println("The handler un-registration has reached all nodes");
    } else {
      System.out.println("Un-registration failed!");
    }
  });

三种发送消息的模式

使用publish推送消息:

eventBus.publish("news.uk.sport", "Yay! Someone kicked a ball");

这个消息会被推送给所有注册了这个地址的handler。

使用send发送消息:

eventBus.send("news.uk.sport", "Yay! Someone kicked a ball");

使用send方法时,只会有一个handler接收到消息,这就是点对点模式。

使用request发送消息:

eventBus
  .request("news.uk.sport", "Yay! Someone kicked a ball across a patch of grass")
  .onComplete(ar -> {
    if (ar.succeeded()) {
      System.out.println("Received reply: " + ar.result().body());
    }
  });

使用request发送消息时,那么接收者在接收到消息的时候就可以给sender发送一个确认的消息。

MessageConsumer<String> consumer = eventBus.consumer("news.uk.sport");
consumer.handler(message -> {
  System.out.println("I have received a message: " + message.body());
  message.reply("how interesting!");
});

Event Bus使用示例

模拟温度传感器,并将温度传感器的数据实时推送到前端(示例中使用postman替代)。

Main.java

public class Main {
  public static void main(String[] args) {
    Vertx vertx = Vertx.vertx();
    vertx.deployVerticle("chapter3.HeatSensor", new DeploymentOptions().setInstances(4));
    vertx.deployVerticle("chapter3.Listener");
    vertx.deployVerticle("chapter3.SensorData");
    vertx.deployVerticle("chapter3.HttpServer");
  }
}

模拟热传感器生成数据:

public class HeatSensor extends AbstractVerticle {

  private final Random random = new Random();
  private final String sensorId = UUID.randomUUID().toString();
  private double temperature = 21.0;

  @Override
  public void start() {
    scheduleNextUpdate();
  }

  private void scheduleNextUpdate() {
    vertx.setTimer(random.nextInt(5000) + 1000, this::update);
  }

  private void update(long timerId) {
    temperature = temperature + (delta() / 10);
    JsonObject payload = new JsonObject()
      .put("id", sensorId)
      .put("temp", temperature);
    vertx.eventBus().publish("sensor.updates", payload);
    scheduleNextUpdate();
  }

  private double delta() {
    if (random.nextInt() > 0) {
      return random.nextGaussian();
    } else {
      return -random.nextGaussian();
    }
  }
}

更新和计算数据:

public class SensorData extends AbstractVerticle {

  private final HashMap<String, Double> lastValues = new HashMap<>();

  @Override
  public void start() {
    EventBus bus = vertx.eventBus();
    bus.consumer("sensor.updates", this::update);
    bus.consumer("sensor.average", this::average);
  }

  private void update(Message<JsonObject> message) {
    JsonObject json = message.body();
    lastValues.put(json.getString("id"), json.getDouble("temp"));
  }

  private void average(Message<JsonObject> message) {
    double avg = lastValues.values().stream()
      .collect(Collectors.averagingDouble(Double::doubleValue));
    JsonObject json = new JsonObject().put("average", avg);
    message.reply(json);
  }
}

一个普通的监听器:

public class Listener extends AbstractVerticle {

  private final Logger logger = LoggerFactory.getLogger(Listener.class);
  private final DecimalFormat format = new DecimalFormat("#.##");

  @Override
  public void start() {
    EventBus bus = vertx.eventBus();
    bus.<JsonObject>consumer("sensor.updates", msg -> {
      JsonObject body = msg.body();
      String id = body.getString("id");
      String temperature = format.format(body.getDouble("temp"));
      logger.info("{} reports a temperature ~{}C", id, temperature);
    });
  }
}

http服务:

public class HttpServer extends AbstractVerticle {
  private static final Logger log = LoggerFactory.getLogger(HttpServer.class);

  @Override
  public void start() {
    vertx.createHttpServer()
      .requestHandler(this::handler)
      .listen(config().getInteger("port", 8080));
  }

  private void handler(HttpServerRequest request) {
    if ("/".equals(request.path())) {
      request.response().sendFile("index.html");
    } else if ("/sse".equals(request.path())) {
      sse(request);
    } else {
      request.response().setStatusCode(404);
    }
  }

  private void sse(HttpServerRequest request) {
    HttpServerResponse response = request.response();
    response
      .putHeader("Content-Type", "text/event-stream")
      .putHeader("Cache-Control", "no-cache")
      .setChunked(true);

    MessageConsumer<JsonObject> consumer = vertx.eventBus().consumer("sensor.updates");
    consumer.handler(msg -> handleData(msg, response)).completionHandler(res -> {
      if (res.succeeded()) {
        log.info("成功处理消息");
      } else {
        log.info("处理消息失败");
      }
    });

    TimeoutStream ticks = vertx.periodicStream(1000);
    ticks.handler(id -> {
      vertx.eventBus().<JsonObject>request("sensor.average", "", reply -> {
        if (reply.succeeded()) {
          response.write("event: average\n");
          response.write("data: " + reply.result().body().encode() + "\n\n");
        }
      });
    });

    response.endHandler(v -> {
      consumer.unregister();
      ticks.cancel();
    });
  }

  private void handleData(Message<JsonObject> msg, HttpServerResponse response) {
    response.write("event: update\n");
    response.write("data: " + msg.body().encode() + "\n\n");
  }
}

服务端日志数据:

image.png

使用postman直接访问:localhost:8080/sse就可以得到如下的数据:

image1.png