微服务框架ServiceComb源码解析之一Vertx使用

ThinkFault

1. Vertx使用

serviceComb基于vertx构建,在consumer向provider发请求的时候,最终是通过vertx的eventloop线程发送出去。

consumer的内置的最后一个Handler:TransportClientHandler,负责发送前预处理,发送请求,收到响应后的预处理,处理响应结果。发送就依赖vertx。
image.png

image.png
由于vertx是异步的,事件驱动,要了解vertx发送过程,就得先了解一些概念:
AsynResult、Future、Promise、Context,下面是它们之间的关系

image.png
image.png

1.1 ServiceComb 服务端Vertx部署

//1. SCBEngine.java 初始化transport
  private void doRun() throws Exception {
...
    transportManager.init(this);
...
}

//2. TransportManager.java 查找transport
  public void init(SCBEngine scbEngine) throws Exception {
    for (Transport transport : transportMap.values()) {
      if (transport.init()) {
...
      }
    }
  }

//3.VertxRestTransport.java 部署vertx

  @Override
  public boolean init() throws Exception {
    restClient = RestTransportClientManager.INSTANCE.getRestClient();
...
//两个重要参数传到vertx框架,最后传到Verticle实例
    SimpleJsonObject json = new SimpleJsonObject();
    json.put(ENDPOINT_KEY, getEndpoint());
    json.put(RestTransportClient.class.getName(), restClient);
    options.setConfig(json);
...
    return VertxUtils.blockDeploy(transportVertx, TransportConfig.getRestServerVerticle(), options);
  }

//TransportConfig.getRestServerVerticle()是RestServerVerticle.java
  @Override
  public void init(Vertx vertx, Context context) {
    super.init(vertx, context);
//取出endpoint信息在start方法启动部署
    this.endpoint = (Endpoint) context.config().getValue(AbstractTransport.ENDPOINT_KEY);
    this.endpointObject = (URIEndpointObject) endpoint.getAddress();
  }

由于是用vertx的eventloop线程,所以每个verticle实例与一个eventloop线程绑定。形成类似Actor-Pattern,这种模式不存在资源竞争线程安全问题

1.2 ServiceComb客户端verticle部署

// 1. CseApplicationListener.java
  @Override
  public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
...
    HttpClients.load();
...
  }

//2.HttpClients.java
  private static ClientPoolManager<HttpClientWithContext> createClientPoolManager(HttpClientOptionsSPI option) {
    Vertx vertx = getOrCreateVertx(option);
    ClientPoolManager<HttpClientWithContext> clientPoolManager = new ClientPoolManager<>(vertx,
        new HttpClientPoolFactory(HttpClientOptionsSPI.createHttpClientOptions(option)));

    DeploymentOptions deployOptions = VertxUtils.createClientDeployOptions(clientPoolManager,
        option.getInstanceCount())
        .setWorker(option.isWorker())
        .setWorkerPoolName(option.getWorkerPoolName())
        .setWorkerPoolSize(option.getWorkerPoolSize());
    try {
      VertxUtils.blockDeploy(vertx, ClientVerticle.class, deployOptions);
      return clientPoolManager;
    } catch (InterruptedException e) {
      throw new IllegalStateException(e);
    }
  }

2.Future 和 Promise

异步编程,那么就涉及到异步结果如何获取的问题。任何编程问题都是计算问题,计算的三要素是,业务逻辑(计算什么)、执行器(谁来计算)、输出结果。异步计算,结果如何回收,Java用Future对象进行包装,那么Future里面就必然有执行器,执行器执行完业务逻辑后通知Futrue对象,是成功了还是失败了,输出结果是什么。因此,我们可以把Future看作是业务逻辑,执行器,结果输出的封装。

对于异步任务,我们经常把用户线程和执行任务的线程混在一起,实际上,要实现异步,必须有两个或两个以上执行器,一个执行器是入口,也就是常说的main线程,另外一个执行器是异步任务的线程,在main线程中创建,异步执行业务逻辑,把业务逻辑执行结果填到Future中,主线程main,从Future中获取异步任务结果。

如下代码摘自文章末尾的参考链接

image.png

这段代码,从开发态去看,就是一段从上到下顺序排列的代码。但是从运行态去看,就不一样了,这段代码有两个参与者,一个是1执行进来的main,还有一个是2,是main中创建的执行器2,执行器2负责执行业务逻辑,并通过Future和main交流

Future实现思路
image.png

那么我们讲完类Future,这个是java jdk的future啊。其实nettey的future略有不同,具体看参考链接的内容。但这细节不重要。nettty的Future支持设置回调,但是为了防止回调地狱,引入类Promise对象,其实Promise类似Futrue,充当main和异步执行器的交互角色,Promise为了更好的处理嵌套回调问题。

进一步,java8 增加的CompletableFuture,更高级的回调方式,以弥补java 自带的Future的不足。

总结:通过上述我们知道,异步任务模式Future,有不同的实现方式,有jdk自带的Future,有netty自己写的Future,并且nettey还加了Promise。java 8后,jdk又新增了CompletableFuture。这些林林总总,都是从不同角度去解决异步编程易于使用的问题。本质没有新增什么东西。也就是说,java从1.0到8.0不断往前发展,并不是增加了什么新的技术,而是对已有的东西进行优化,更好用了。因此才出现,比如线程执行任务写法有几十种,琳琅满目,让人晕头转向。所以,看似知识点多,其实也就是那些旧饭不断炒而已,饭还是饭,并没有增加面或菜。


Future 模式与 Promise 模式
第四章 Future和Promise

3.Verticle

Verticle 有Standard Verticles和Worker Verticles

  • Standard Verticles
    1.使用event loop thread执行
    2.This means we can guarantee that all the code in your verticle instance is always executed on the same event loop (as long as you don’t create your own threads and call it!).
  • Worker Verticles
    1.用the worker pool 执行
    2.Worker verticle instances are never executed concurrently by Vert.x by more than one thread, but can executed by different threads at different times.

3.1 Verticle的instance和work

instance是Verticle的实例数,关键代码如下

// vertx源码DeploymentManager.java

    int nbInstances = options.getInstances();
    Set<Verticle> verticles = Collections.newSetFromMap(new IdentityHashMap<>());
    for (int i = 0; i < nbInstances; i++) {
      Verticle verticle;
      try {
        verticle = verticleSupplier.call();
      } catch (Exception e) {
        return Future.failedFuture(e);
      }
      if (verticle == null) {
        return Future.failedFuture("Supplied verticle is null");
      }
      verticles.add(verticle);
    }

standard Verticle 和work Verticle区别是什么,如下是关键代码可以看出来
前者用eventLoop线程(netty网络线程),后者用work线程(vertx自己创建的线程)

// vertx源码DeploymentManager.java

    for (Verticle verticle: verticles) {
      CloseFuture closeFuture = new CloseFuture(log);
      WorkerPool workerPool = poolName != null ? vertx.createSharedWorkerPool(poolName, options.getWorkerPoolSize(), options.getMaxWorkerExecuteTime(), options.getMaxWorkerExecuteTimeUnit()) : null;
      ContextImpl context = (options.isWorker() ? vertx.createWorkerContext(deployment, closeFuture, workerPool, tccl) :
        vertx.createEventLoopContext(deployment, closeFuture, workerPool, tccl));
      VerticleHolder holder = new VerticleHolder(verticle, context, workerPool, closeFuture);
      deployment.addVerticle(holder);
      context.runOnContext(v -> {
        try {
          verticle.init(vertx, context);
          Promise<Void> startPromise = context.promise();
          Future<Void> startFuture = startPromise.future();
          verticle.start(startPromise);
          startFuture.onComplete(ar -> {
            if (ar.succeeded()) {
              if (parent != null) {
                if (parent.addChild(deployment)) {
                  deployment.child = true;
                } else {
                  // Orphan
                  deployment.doUndeploy(vertx.getOrCreateContext()).onComplete(ar2 -> promise.fail("Verticle deployment failed.Could not be added as child of parent verticle"));
                  return;
                }
              }
              deployments.put(deploymentID, deployment);
              if (deployCount.incrementAndGet() == verticles.length) {
                promise.complete(deployment);
              }
            } else if (failureReported.compareAndSet(false, true)) {
              deployment.rollback(callingContext, promise, context, holder, ar.cause());
            }
          });
        } catch (Throwable t) {
          if (failureReported.compareAndSet(false, true))
            deployment.rollback(callingContext, promise, context, holder, t);
        }
      });
    }

3.2 ServiceComb RestServerVerticle部署

1.Verticle instance实例数配置

TransportConfig.getThreadCount()
优先从配置文件获取servicecomb.rest.server.verticle-count

// 如果没配置,取默认值default value
count = Runtime.getRuntime().availableProcessors() > 8 ? 8 : Runtime.getRuntime().availableProcessors();

2.workerPoolSize配置,默认20
options.setWorkerPoolSize(VertxOptions.DEFAULT_WORKER_POOL_SIZE);

3.eventloop个数配置
优先从配置servicecomb.transport.eventloop.size获取
默认是cpu核两倍
DEFAULT_EVENT_LOOP_POOL_SIZE = 2 * CpuCoreSensor.availableProcessors();
4.internalBlockingPoolSize默认20个

如下是将business-1-1-0的微服务配置verticle-count: 8,并发请求,可以看到8个evenloop线程都被调动。
image.png

3.3 vertx线程创建

eventloop线程是由VertxThread创建,而VertxThread是netty FastThreadLocalThread的子类

//VertxImpl构造函数

    eventLoopThreadFactory = createThreadFactory(maxEventLoopExecTime, maxEventLoopExecTimeUnit, "vert.x-eventloop-thread-", false);
    eventLoopGroup = transport.eventLoopGroup(Transport.IO_EVENT_LOOP_GROUP, options.getEventLoopPoolSize(), eventLoopThreadFactory, NETTY_IO_RATIO);
    ThreadFactory acceptorEventLoopThreadFactory = createThreadFactory(options.getMaxEventLoopExecuteTime(), options.getMaxEventLoopExecuteTimeUnit(), "vert.x-acceptor-thread-", false);
    // The acceptor event loop thread needs to be from a different pool otherwise can get lags in accepted connections
    // under a lot of load
    acceptorEventLoopGroup = transport.eventLoopGroup(Transport.ACCEPTOR_EVENT_LOOP_GROUP, 1, acceptorEventLoopThreadFactory, 100);

问题待续

1.verticle实例数量大于eventloop数;如果业务处理慢,1个eventloop 挂多个verticle instance?高并发,所有instance用完了,会怎样?
2.vertx如何分发请求到不同的verticle实例,acceptorEventLoopGroup循环分发给eventLoopGroup?

阅读 1.3k
10 声望
1 粉丝
0 条评论
10 声望
1 粉丝
文章目录
宣传栏