环境:Java8, Vertx 4.4 支持多个数据源动态切换,支持配置文件热更新 一个 ApiVerticle 运行 http server, 一个 DbVerticle 运行 数据库部分,两者使用 EventBus 通信
代码地址 Gitee
目录结构
MainVerticle.java
public class MainVerticle extends AbstractVerticle {
private static final String CONFIG_FILE = "config.json";
private final Logger logger = LoggerFactory.getLogger(MainVerticle.class);
public static void main(String[] args) {
Vertx.vertx().deployVerticle(new MainVerticle());
}
private String apiServiceDeployId = null;
private Map<String, String> dbServiceDeployIdMap = new HashMap<>();
@Override
public void start(Promise<Void> startPromise) throws Exception {
ConfigRetriever retriever = getConfigRetriever();
retriever.getConfig().onSuccess(res -> {
JsonObject apiConfig = res.getJsonObject("api");
JsonObject dbConfig = res.getJsonObject("db");
deployApiService(apiConfig);
deployDbService(dbConfig);
}).onFailure(fail -> logger.error("configErr", fail));
retriever.listen(config -> {
JsonObject oldConfig = config.getPreviousConfiguration();
JsonObject newConfig = config.getNewConfiguration();
logger.debug("oldConfig: " + oldConfig);
logger.debug("newConfig: " + newConfig);
restartApiService(oldConfig, newConfig);
restartDbService(oldConfig, newConfig);
});
startPromise.complete();
}
private void deployDbService(JsonObject dbConfig) {
for (Map.Entry<String, Object> entry : dbConfig) {
String dbName = entry.getKey();
JsonObject configJson = (JsonObject) entry.getValue();
vertx.deployVerticle(new DbVerticle(), new DeploymentOptions().setWorker(true)
.setConfig(configJson.copy().put("name", dbName)))
.onFailure(fail -> {
logger.error("[" + dbName + "]DbServiceDeployErr", fail);
}).onSuccess(dbRes -> {
logger.debug("[" + dbName + "]DbServiceDeploySuccess: " + dbRes);
dbServiceDeployIdMap.put(dbName, dbRes);
});
}
}
private void deployApiService(JsonObject apiConfig) {
vertx.deployVerticle(new ApiVerticle(), new DeploymentOptions().setConfig(apiConfig))
.onFailure(fail -> {
logger.error("ApiServiceDeployErr", fail);
}).onSuccess(apiRes -> {
logger.debug("ApiServiceDeploySuccess: " + apiRes);
apiServiceDeployId = apiRes;
});
}
private void restartApiService(JsonObject oldConfig, JsonObject newConfig) {
JsonObject oldApiConfig = oldConfig.getJsonObject("api");
JsonObject newApiConfig = newConfig.getJsonObject("api");
if (!oldApiConfig.equals(newApiConfig)) {
logger.debug("apiConfigChange");
vertx.undeploy(apiServiceDeployId)
.compose(a -> vertx.deployVerticle(new ApiVerticle(), new DeploymentOptions()
.setConfig(newApiConfig))).onSuccess(res -> {
logger.debug("restartApiServiceSuccess: " + res);
apiServiceDeployId = res;
}).onFailure(err -> {
logger.error("restartApiServiceErr", err);
});
}
}
private void restartDbService(JsonObject oldConfig, JsonObject newConfig) {
JsonObject oldDbConfig = oldConfig.getJsonObject("db");
JsonObject newDbConfig = newConfig.getJsonObject("db");
if (!oldDbConfig.equals(newDbConfig)) {
logger.debug("dbConfigChange");
logger.debug("oldConfig:" + oldDbConfig);
logger.debug("newConfig:" + newDbConfig);
for (Map.Entry<String, String> entry : new HashMap<>(dbServiceDeployIdMap).entrySet()) {
String dbName = entry.getKey();
String dbServiceDeployId = entry.getValue();
JsonObject dbConfigJson = newDbConfig.getJsonObject(dbName);
vertx.undeploy(dbServiceDeployId)
.compose(a -> vertx.deployVerticle(new DbVerticle(), new DeploymentOptions()
.setConfig(dbConfigJson.copy().put("name", dbName)))).onSuccess(res -> {
logger.debug("[" + dbName + "]restartDbServiceSuccess: " + res);
dbServiceDeployIdMap.put(dbName, res);
}).onFailure(err -> {
logger.error("[" + dbName + "]restartDbServiceErr", err);
});
}
}
}
private ConfigRetriever getConfigRetriever() {
ConfigStoreOptions fileStore = new ConfigStoreOptions()
.setType("file")
.setConfig(new JsonObject().put("path", CONFIG_FILE));
ConfigRetrieverOptions options = new ConfigRetrieverOptions()
.addStore(fileStore);
return ConfigRetriever.create(vertx, options);
}
}
ApiVerticle.java
public class ApiVerticle extends AbstractVerticle {
private final Logger logger = LoggerFactory.getLogger(MainVerticle.class);
@Override
public void start(Promise<Void> startPromise) throws Exception {
JsonObject config = config();
Integer port = config.getInteger("port");
Router router = Router.router(vertx);
router.route().handler(BodyHandler.create());
router.post("/app/query")
.handler(ctx -> {
JsonObject reqBody = ctx.body().asJsonObject();
logger.debug("/app/query Body:" + reqBody);
String name = reqBody.getString("name");
vertx.eventBus().<JsonObject>request(name, reqBody).onFailure(fail -> {
if (fail instanceof ReplyException) {
ReplyException replyException = (ReplyException) fail;
fail(ctx, replyException.failureCode(), replyException.getMessage());
return;
}
logger.error("eventBusFail", fail);
fail(ctx, fail.getMessage());
}).onSuccess(res -> {
JsonObject body = res.body();
ok(ctx, body);
});
});
vertx.createHttpServer().requestHandler(router).listen(port, http -> {
if (http.succeeded()) {
logger.debug("http Started:" + port);
startPromise.complete();
} else {
startPromise.fail(http.cause());
}
});
}
private void ok(RoutingContext ctx, JsonObject obj) {
ctx.response().putHeader(HttpHeaders.CONTENT_TYPE, "application/json;charset=UTF-8")
.end(new JsonObject().put("code", 0).put("data", obj).toString());
}
private void fail(RoutingContext ctx, String msg) {
fail(ctx, 1, msg);
}
private void fail(RoutingContext ctx, int code, String msg) {
ctx.response().putHeader(HttpHeaders.CONTENT_TYPE, "application/json;charset=UTF-8")
.end(new JsonObject().put("code", code).put("msg", msg).toString());
}
@Override
public void stop() throws Exception {
logger.debug("apiServiceStop");
super.stop();
}
}
DbVerticle.java
public class DbVerticle extends AbstractVerticle {
private final Logger logger = LoggerFactory.getLogger(DbVerticle.class);
public static void main(String[] args) {
Vertx.vertx().deployVerticle(new DbVerticle());
}
@Override
public void start() throws Exception {
logger.debug("configDb: " + config().toString());
JDBCPool pool = initPool(config());
String dbName = config().getString("name");
vertx.eventBus().<JsonObject>localConsumer(dbName, msg -> {
JsonObject body = msg.body();
logger.debug("[" + dbName + "]EventBusMsg: " + body);
JsonArray param = body.getJsonArray("param");
String sql = body.getString("sql");
pool.preparedQuery(sql).execute(Tuple.from(param.getList()))
.onFailure(fail -> {
logger.error("[" + dbName + "]queryErr:" + sql + ", params: " + param);
msg.fail(-1, dbName + ":" + fail.getMessage());
}).onSuccess(res -> {
JsonArray array = rowSetToJsonArray(res);
msg.reply(new JsonObject().put("dbName", dbName).put("list", array));
});
});
}
private JsonArray rowSetToJsonArray(RowSet<Row> res) {
JsonArray array = new JsonArray();
for (Row re : res) {
array.add(re.toJson());
}
return array;
}
@Override
public void stop() throws Exception {
logger.debug("dbServiceStop");
super.stop();
}
private JDBCPool initPool(JsonObject object) {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setDriverClassName(object.getString("driverClassName", "com.mysql.cj.jdbc.Driver"));
dataSource.setJdbcUrl(object.getString("jdbcUrl"));
dataSource.setUsername(object.getString("userName"));
dataSource.setPassword(object.getString("password"));
dataSource.setMaximumPoolSize(object.getInteger("maxPoolSize", 10));
dataSource.setMaxLifetime(object.getLong("maxLifetime", 1765000L));
return JDBCPool.pool(vertx, dataSource);
}
}
IDEA 里面直接使用 MainVerticle 中的 main 方法运行即可
Maven 打包命令 mvn clean package -Dmaven.test.skip=true
jar 运行命令 java -jar multipledatasources-1.0.0-SNAPSHOT-fat.jar
动态修改配置文件,在IDEA环境里,效果不太明显,IDEA有文件缓存,可以打成jar包后,再测试