Vert.x,Core - Verticle and Event Bus

Verticle

什么是verticle

Verticle是由Vert.x部署和运行的代码块。Verticle实例可使用任意Vert.x支持的编程语言编写,可以将Verticle想成Actor Model中的Actor。可以将Verticle理解成一个可以独立热部署的组件(模块),一个应用程序通常是由在同一个Vert.x实例中同时运行的多个Verticle实例组合而成。 不同的Verticle实例通过向Event Bus(后面会介绍)上发送消息来相互通信。这个模型是可选的,Vert.x并不强制使用这种方式创建应用程序。

Actor Model(参与者模型),它将系统中的所有组件都视为Actor(参与者),参与者之间通过发送消息(Message)来实现交互,消息是通过异步方式传递的。通过异步消息发送(调用)-接收(处理/返回)机制,Actor之间实现松耦合, 并简化系统并行执行难度,更容易实现分布式系统。

编写Verticle

Verticle的实现类必须实现Verticle接口,通常直接从抽象类AbstractVerticle继承更简单。有两种Verticle:

  • Standard Verticles: 最常用的一类Verticle,它们永远运行在Event Loop线程;
  • Worker Verticles: 这类Verticle会运行在Worker Pool中的线程上,Verticle使用Worker Pool 来执行阻塞式行为;

无论哪种Verticle,通常都需要override start方法,在该方法中执行初始化工作。当Vert.x部署Verticle时,它的start方法将被调用,这个方法执行完成后Verticle就变成已启动状态。

可以override stop方法,当Vert.x撤销一个Verticle时它会被调用, 可以在stop方法中执行清理工作,如释放资源。这个方法执行完成后Verticle就变成已停止状态了。

 AbstractVerticle
// If your verticle does a simple, synchronous start-up then override this method and 
// put your start-up code in here. 
public void start() throws Exception

// If your verticle has simple synchronous clean-up tasks to complete then override this 
// method and put your clean-up code in here.
public void stop() throws Exception

有些时候Verticle启动会耗费一些时间,而且start方法中出现阻塞等待, 会阻塞其他的Verticle部署,这样做会破坏黄金法则。这时我们可以通过重写异步版本的start方法来实现,它接收一个Promise参数。 方法执行完时,Verticle实例并没有部署好(状态不是deployed)。同样的,这儿也有一个异步版本的stop方法,如果您想做一些耗时的Verticle清理工作, 您可以使用它。

 AbstractVerticle
public void start(Promise<Void> startPromise) throws Exception;
public void stop(Promise<Void> stopPromise) throws Exception ;

部署Verticle

可以使用deployVerticle方法部署Verticle,并传入一个Verticle(类)名或Verticle实例。

// 通过Verticle实例方式, Java Verticle only
vertx.deployVerticle(new com.mycompany.MyOrderProcessorVerticle());
// 通过Verticle类名(class name)方式
vertx.deployVerticle("com.mycompany.MyOrderProcessorVerticle");
// 部署JavaScript的Verticle
vertx.deployVerticle("verticles/myverticle.js");
// 部署Ruby的Verticle
vertx.deployVerticle("verticles/my_verticle.rb");

Verticle是异步部署的,如果您想要在部署完成时收到通知,则可以指定一个完成处理器:

Future<String> deploy1Result = vertx.deployVerticle(new Verticle1());
deploy1Result.onComplete(ar -> {
	if (ar.succeeded()) {
		// deploy succeeded.
		// 如果部署成功,这个完成处理器的结果中将会包含部署ID,这个部署ID可以用于撤销部署。
		String deploymentId = ar.result();
	} else {
		// deploy failed.
	}
});

撤销Verticle

可以通过undeploy方法来撤销部署好的Verticle。撤销操作也是异步的,因此若您想要在撤销完成后收到通知,则可以指定一个完成处理器:

vertx.undeploy(deploymentID, res -> {
  if (res.succeeded()) {
    System.out.println("Undeployed ok");
  } else {
    System.out.println("Undeploy failed!");
  }
});

运行Verticle (命令行方式)

通常将部署Verticle放在我们编写的main方法中来运行verticle应用。Vert.x也支持通过命令行方式执行Verticle,前提是必须下载并安装Vert.x 的发行版,4.x版本的发行版在Maven Cen­tral中发布。下载并解压vertx-stack-manager-x.x.x-full.zip,在解压的bin目录下就包含运行Verticle的脚本(vertx|vertx.bat)

# 运行JavaScript的Verticle
vertx run my_verticle.js

# 运行Ruby的Verticle
vertx run a_n_other_verticle.rb

# 使用集群模式运行Groovy的Verticle
vertx run FooVerticle.groovy -cluster

# 直接执行Java源代码
vertx run SomeJavaSourceFile.java

通过查看vertx命令可以知道,实际是vertx是通过Vert.x Core的“io.vertx.core.Launcher”启动Verticle,vertx-stack-manager-*-full是全量包,4.5.10版本大概有130MB+,其实也可以自己编写命令行命令,例如:

## 通过maven命令行根据需要下载依赖的jar到lib目录
D:\eclipse-workspace\vertx1> mvn dependency:copy-dependencies "-DoutputDirectory=./lib" -Ddep.group="io.vertx" -Ddep.artifact="vertx-core" -Ddep.version="[4.5.10,)"
## 执行Launcher class,获取帮助信息
D:\eclipse-workspace\vertx1>java -cp ".\lib\*;."  io.vertx.core.Launcher --help

## 通过Lancher执行Verticle1
D:\eclipse-workspace\vertx1>javac -cp ".\lib\*;." Verticle1.java
D:\eclipse-workspace\vertx1>java -cp ".\lib\*;."  io.vertx.core.Launcher run Verticle1
9月 26, 2024 11:18:58 上午 io.vertx.core.impl.launcher.commands.VertxIsolatedDeployer
信息: Succeeded in deploying verticle
...

编写一个Verticle案例

简单的Verticle。

import java.util.logging.Logger;

import io.vertx.core.AbstractVerticle;

public class Verticle1 extends AbstractVerticle {
	private static final Logger LOGGER = Logger.getLogger(Verticle1.class.getName());
	private long timerId = -1;
	@Override
	public void start() {
		//this.vertx, 这里的vertx是AbstractVerticle的成员变量
		timerId = vertx.setPeriodic(1000, event -> {
			LOGGER.info("Verticle1 Running.");
		});
		// 执行结束部署完成, 如果抛异常,则部署失败。
		//throw new RuntimeException("Verticle1 start failed");
	}
	
	@Override
	public void stop() {
		if (timerId < 0)
			vertx.cancelTimer(timerId);
	}
}

我们可以通过Promise控制异步部署的结果。

import java.util.logging.Logger;

import io.vertx.core.AbstractVerticle;
import io.vertx.core.Promise;
import io.vertx.core.json.JsonObject;

public class Verticle2 extends AbstractVerticle {
	private static final Logger LOGGER = Logger.getLogger(Verticle2.class.getName());
	private long timerId = -1;
	@Override
	public void start(Promise<Void> startPromise) {
		JsonObject config = this.config();
		long delay = config.getLong("delay", 1000L);
		boolean fail = config.getBoolean("fail");
		if (fail) {
			startPromise.fail(new Exception("Verticle2 start failed!"));
		} else {
			timerId = vertx.setPeriodic(delay, timerId -> {
				LOGGER.info("Verticle2 Running.");
			});
			startPromise.complete();
		}
	}
	
	@Override
	public void stop(Promise<Void> stopPromise) {
		if (timerId < 0)
			vertx.cancelTimer(timerId);
	}
}

通过编写main方法执行他们:

import java.util.Objects;
import java.util.logging.Level;
import java.util.logging.Logger;

import io.vertx.core.DeploymentOptions;
import io.vertx.core.Future;
import io.vertx.core.Vertx;
import io.vertx.core.json.JsonObject;

public class Core3 {
	private static final Logger LOGGER = Logger.getLogger(Core3.class.getName());
	public static void main(String[] args) {
		Vertx vertx = Vertx.vertx();
		Future<String> deploy1Result = vertx.deployVerticle(new Verticle1());
		vertx.setTimer(4000, timerId -> { // 定时4秒后undeploy Verticle1
			if(deploy1Result.isComplete()) {
				String deploymentId = deploy1Result.result();
				if(Objects.nonNull(deploymentId)) {
					LOGGER.info("undeploy deploymentId: " + deploymentId);
					vertx.undeploy(deploymentId);
				}
			}
		});
		// 可以通过DeploymentOptions传递部署参数, AbstractVerticle的config方法可以接收部署时候传递的参数。
		JsonObject config = new JsonObject()
				.put("delay", 2000L)
				.put("fail", false);
		DeploymentOptions options = new DeploymentOptions().setConfig(config);
		Future<String> deploy2Result = vertx.deployVerticle("learning.vertx.c3.Verticle2", options);
		deploy2Result.onSuccess(event -> {
			LOGGER.info("Verticle2 deployed. deploymentId = " + event);
		}).onFailure(exception -> {
			LOGGER.log(Level.SEVERE, "Verticle2 deploy failed!", exception);
		});
	}
}

执行输出:

2024-09-26 11:32:11 [信息] Verticle2 deployed. deploymentId = 1f55131b-53cd-41b6-b7f3-b8b0600e504b
2024-09-26 11:32:12 [信息] Verticle1 Running.
2024-09-26 11:32:13 [信息] Verticle1 Running.
2024-09-26 11:32:13 [信息] Verticle2 Running.
2024-09-26 11:32:14 [信息] Verticle1 Running.
2024-09-26 11:32:15 [信息] Verticle1 Running.
2024-09-26 11:32:15 [信息] undeploy deploymentId: d2848b93-d649-44a5-9e3f-af5bb536f975 ⇒ Verticle1撤销后不再继续执行。
2024-09-26 11:32:15 [信息] Verticle2 Running.
2024-09-26 11:32:17 [信息] Verticle2 Running.
2024-09-26 11:32:19 [信息] Verticle2 Running.
...

Event Bus

A Vert.x event-bus is a light-weight distributed messaging system which allows different parts of your application, or different applications and services to communicate with each in a loosely coupled way.

The Event Bus can also be distributed, meaning Verticles that are not necessarily running on the same machine can communicate with each other. In addition, thanks to the “bridges” it can also communicate with generic messaging protocols such as AMQP and STOMP.

The Event Bus is the communication channel that Verticles used to send messages asynchronously. The type of data exchanged can be of any type, but it is preferable to use the JSON format since it can be understood by all languages.

基本概念

消息的发送目标被称作地址(address) 。Vert.x中的地址就是一个简单的字符串,任何字符串都合法。 不过还是建议使用某种规范来进行地址的命名。如Java风格的命名空间命名:online-store.order-service.new-order。

消息需由处理器(Handler)来接收。您需要将处理器注册在某个地址上。同一个地址可以注册许多不同的处理器。一个处理器也可以注册在多个不同的地址上。

Event Bus支持以下3种消息传递方式:

  • 发布/订阅: Publish / Subscribe messaging (to send messages in broadcast);
    • 消息将被发布到一个地址上,信息会被传递给所有注册在该地址上的处理器。
  • 点对点: Point-to-Point messaging (direct message);
    • 消息将被发送到一个地址上,Vert.x仅会把消息发给注册在该地址上的处理器中的其中一个。
    • 若这个地址上注册有不止一个处理器,那么Vert.x将使用 不严格的轮询算法选择其中一个。
  • 请求-响应: Request / Response messaging;
    • 当接收者收到消息并且处理完成时,它可以选择性地回复该消息。
    • 若回复,则关联的应答处理器将会被调用。

Event Bus API

获取Event Bus

每一个Vert.x实例都有一个单独的Event Bus实例。可以通过以下方式获取引用。

EventBus eventBus = vertx.eventBus();

消息发送

Vert.x会按照发送者发送消息的顺序,将消息以同样的顺序传递给处理器。

在Event Bus上发送的消息可通过DeliveryOptions包含头信息。

DeliveryOptions options = new DeliveryOptions();
options.addHeader("some-header", "some-value");
eventBus.send("news.uk.sport", "Yay! Someone kicked a ball", options);

当发送带有应答处理器的消息时,可以在DeliveryOptions中指定一个超时时间。如果在这个时间之内没有收到应答,则会以“失败的结果”为参数调用应答处理器。默认超时是 30 秒。

Vert.x会尽它最大努力去传递消息,并且不会主动丢弃消息。这种方式称为 尽力传输(Best-effort delivery)。但是,当Event Bus生故障时,消息可能会丢失。

EventBus eventBus = vertx.eventBus();
// 点对点模式发送
eventBus.send("news.uk.sport", "Yay! Someone kicked a ball");

// 发布/订阅模式
eventBus.publish("news.uk.sport", "Yay! Someone kicked a ball");

// 请求-响应模式
eventBus.request("news.uk.sport", "Yay! Someone kicked a ball");

消息接收

消息处理器中接收到的对象的类型是Message。消息的body对应发送或发布(publish)的对象。消息的头信息可以通过 headers方法获取。

可以通过consumer方法获取指定地址的消息, consumer方法直接返回MessageConsumer, 可以通过为MessageConsumer设置处理器方式处理接收到的消息。

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

如果不需要接收指定地址的消息,可以通过通过unregister方法来注销处理器。

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

Event Bus案例

通过案例学习Event Bus使用,假设一个在线商店系统,接收到用户的订单请求后,将请求以以“请求-响应模式”发给订单系统(OrderService)生成订单,订单系统处理完成后结果答复给消息发送者。消息发送者接收订单生成答复后,通过"发布/订阅模式"发布订单生成消息,短信系统(SortMessageService)订阅订单生成信息,发送短信给用户。在线商城发起“十一送积分活动”,通过deploy上线一个积分系统(RewardService)订阅订单生成消息,并为用户积分。

应用设计OrderService, SortMessageService和RewardService三个Verticle。

OrderService为订单系统,接收"store.order.new"地址的消息,获取订单请求,生成订单后,答复给调用者。

import java.util.concurrent.ThreadLocalRandom;
import java.util.logging.Logger;

import io.vertx.core.AbstractVerticle;
import io.vertx.core.eventbus.EventBus;
import io.vertx.core.eventbus.MessageConsumer;
import io.vertx.core.json.JsonObject;

public class OrderService extends AbstractVerticle {
	private static final Logger LOGGER = Logger.getLogger(OrderService.class.getName());
	public void start() {
		EventBus eventBus = vertx.eventBus();
		MessageConsumer<JsonObject> consumer = eventBus.consumer("store.order.new");
		consumer.handler(message -> {
			JsonObject json = (JsonObject) message.body();
			// int customerId = json.getInteger("customerId");
			LOGGER.info("正在生成订单: " + json.toString()); // json.encodePrettily()
			// 这里处理订单逻辑, 例如获取报价系统报价 ...
			// eventBus.send("store.price.discont", json);
			// 组合多个异步结果, 答复最终结果..
			int orderId = ThreadLocalRandom.current().nextInt(0, Integer.MAX_VALUE);
			JsonObject replyJson = new JsonObject().put("orderId", orderId);
			message.reply(replyJson);
		});
	}
}

SortMessageService为短信服务,订阅"store.order.done"地址的消息,根据信息内容发送通知短信。

import java.util.logging.Logger;

import io.vertx.core.AbstractVerticle;
import io.vertx.core.eventbus.EventBus;
import io.vertx.core.eventbus.MessageConsumer;

public class SortMessageService extends AbstractVerticle {
	private static final Logger LOGGER = Logger.getLogger(SortMessageService.class.getName());
	@Override
	public void start() {
		EventBus eventBus = vertx.eventBus();
		MessageConsumer<String> consumer = eventBus.consumer("store.order.done");
		consumer.handler(message -> {
			LOGGER.info("发送短信通知: " + message.body());
		});
	}
}

RewardService为积分服务,订阅"store.order.done"地址的消息,根据消息内容为客户积分。

import java.util.logging.Logger;

import io.vertx.core.AbstractVerticle;
import io.vertx.core.eventbus.EventBus;
import io.vertx.core.eventbus.MessageConsumer;

public class RewardService extends AbstractVerticle {
	private static final Logger LOGGER = Logger.getLogger(RewardService.class.getName());
	@Override
	public void start() {
		EventBus eventBus = vertx.eventBus();
		MessageConsumer<String> rewardConsumer = eventBus.consumer("store.order.done");
		rewardConsumer.handler(message -> {
			LOGGER.info("获取积分奖励: " + message.body());
		});
	}
}

最后模拟模拟用户下单,发送生成订单请求。

import java.util.logging.Logger;

import io.vertx.core.Future;
import io.vertx.core.Vertx;
import io.vertx.core.eventbus.EventBus;
import io.vertx.core.eventbus.Message;
import io.vertx.core.json.JsonObject;

public class Core4 {
	private static final Logger LOGGER = Logger.getLogger(Core4.class.getName());
	public static void main(String[] args) {
		Vertx vertx = Vertx.vertx();
		vertx.deployVerticle(new OrderService());
		vertx.deployVerticle(new SortMessageService());
		vertx.deployVerticle("learning.vertx.c3.RewardService");
		EventBus eventBus = vertx.eventBus();
		JsonObject jsonMessage = new JsonObject()  
                .put("customerId", 10)
                .put("productId", 4321)
                .put("quantity", 2);
		
		Future<Message<JsonObject>> requestResult = eventBus.request("store.order.new", jsonMessage);
		LOGGER.info("新订单处理中... ");
		
		requestResult.compose(message -> {
			JsonObject messageBody = message.body();
			LOGGER.info("新订单已生成: " + messageBody);
			int orderId = messageBody.getInteger("orderId");
			eventBus.publish("store.order.done", "order = " + orderId);
			return Future.succeededFuture();
		});
	}
}

运行结果如下:

2024-09-26 17:04:01 [信息] 新订单处理中... 
2024-09-26 17:04:01 [信息] 正在生成订单: {"customerId":10,"productId":4321,"quantity":2}
2024-09-26 17:04:01 [信息] 新订单已生成: {"orderId":699654689}
2024-09-26 17:04:01 [信息] 获取积分奖励: order = 699654689
2024-09-26 17:04:01 [信息] 发送短信通知: order = 699654689
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值