Vert.x 动态切换数据源

217 阅读2分钟

环境:Java8, Vertx 4.4 支持多个数据源动态切换,支持配置文件热更新 一个 ApiVerticle 运行 http server, 一个 DbVerticle 运行 数据库部分,两者使用 EventBus 通信

代码地址 Gitee

目录结构

image.png

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包后,再测试