Vertx入门和源码浅析

ThinkFault

1.Vertx重要概念

vertx demo项目 方便了解vertx框架使用

  • 概念1:Vertx
    实现类VertxImpl,它的重要线程
    eventLoopGroup
    acceptorEventLoopGroup
  • 概念2: Verticle
    1) Verticle起作用的方式,主要是通过Vertx进行部署,也就是deployVerticle
    2) Verticle部署,主要是Vertx框架把vertx 和context 传给Verticle,并且通过context启动
  • 概念3: Context上下文
    1) 子类:ContextInternal EventLoopContext
    2) 上下文是通过Vertx创建,如ContextInternal callingContext = vertx.getOrCreateContext();
    3) context 带有线程,可以跑task

2.Vertx创建

以io.vertx.example.core.http.sharing.HttpServerVerticle为例子,该例子来源于上述的demo工程

Vertx创建很简单,传入VertxOptions 通过VertxBuilder进行创建VertxImpl实例,两类重要的网络I/O线程就是在此时创建的,见VertxImpl的构造函数

eventLoopGroup = transport.eventLoopGroup(Transport.IO_EVENT_LOOP_GROUP, options.getEventLoopPoolSize(), eventLoopThreadFactory, NETTY_IO_RATIO);

acceptorEventLoopGroup = transport.eventLoopGroup(Transport.ACCEPTOR_EVENT_LOOP_GROUP, 1, acceptorEventLoopThreadFactory, 100);



线程和线程上下文:Vertx线程VertxThread,vertx框架创建出来的线程都是VertxThread。

VertxBuilder 初始化线程工厂

  private void initThreadFactory() {
    if (threadFactory != null) {
      return;
    }
    threadFactory = VertxThreadFactory.INSTANCE;
  }

VertxThreadFactory 匿名内部类创建INSTANCE,类似的用法还有ExecutorServiceFactory

public interface VertxThreadFactory extends VertxServiceProvider {

  VertxThreadFactory INSTANCE = new VertxThreadFactory() {
  };

  @Override
  default void init(VertxBuilder builder) {
    if (builder.threadFactory() == null) {
      builder.threadFactory(this);
    }
  }

  default VertxThread newVertxThread(Runnable target, String name, boolean worker, long maxExecTime, TimeUnit maxExecTimeUnit) {
    return new VertxThread(target, name, worker, maxExecTime, maxExecTimeUnit);
  }
}

vertx常用获取上下文方法getOrCreateContext

  public ContextInternal getOrCreateContext() {
    AbstractContext ctx = getContext();
    if (ctx == null) {
      // We are running embedded - Create a context
      ctx = createEventLoopContext();
      stickyContext.set(new WeakReference<>(ctx));
    }
    return ctx;
  }


// 判断当前线程是不是VertxThread,是的话取线程上下文
  static ContextInternal current() {
    Thread current = Thread.currentThread();
    if (current instanceof VertxThread,是的话取线程上下文) {
      return ((VertxThread) current).context();
    }
    return null;
  }

3.Vertx部署Verticle

vertx实例调用deployVerticle进行并部署,deployVerticle等待参数有Verticle,如例子中的HttpServerVerticle,另外可以定制部署参数,通过DeploymentOptions配置。

如下是部署过程,关键的流程

// 开始部署
verticleManager.deployVerticle(name, options).map(Deployment::deploymentID);

// 创建context
ContextInternal callingContext = vertx.getOrCreateContext();

//最后进入DeploymentManager.doDeploy,循环部署多个verticle实例
    for (Verticle verticle: verticles) {
      ...
      // 创建新的context
      ContextImpl context = (options.isWorker() ? vertx.createWorkerContext(deployment, closeFuture, workerPool, tccl) :
        vertx.createEventLoopContext(deployment, closeFuture, workerPool, tccl));
      ...
      // 通过context的线程驱动verticle运行
      context.runOnContext(v -> {
        try {
          // 把vertx和context传给verticle
          verticle.init(vertx, context);
          Promise<Void> startPromise = context.promise();
          Future<Void> startFuture = startPromise.future();
          // 运行start方法,用户的业务逻辑是在start方法实现,驱动用户的业务逻辑
          verticle.start(startPromise);
         ...
        } catch (Throwable t) {
          if (failureReported.compareAndSet(false, true))
            deployment.rollback(callingContext, promise, context, holder, t);
        }
      });
    }

3.1 Server sharing 分析

vertx官方文档 里提到Server sharing,比如,部署一个HttpServerVerticle,启动多个实例,多verticle实例如何共享一个listen port呢?


3.1.1 demo演示

HttpServerVerticle启动httpServer,监听8080端口,

public class HttpServerVerticle extends AbstractVerticle {
  @Override
  public void start() throws Exception {
    vertx.createHttpServer().requestHandler(req -> {
      req.response()
          .putHeader("content-type", "text/html")
          .end("<html><body><h1>Hello from " +  this + "</h1></body></html>");
    }).listen(8080);
  }
}

Server verticle部署HttpServerVerticle,配置启动2个HttpServerVerticle instance

  @Override
  public void start() throws Exception {
    vertx.deployVerticle(
        "io.vertx.example.core.http.sharing.HttpServerVerticle",
        new DeploymentOptions().setInstances(2));
  }

启动Client运行结果如下,说明两个HttpServerVerticle实例成功部署,并且负载均衡对client服务
image.png


3.1.2 分析

分析1:多个verticle instance共享端口和轮询处理

入口是listen(),HttpServerVerticle的第一个instance负责启动Netty 监听端口,后面的instance,全部复用这个监听端口,只不过把自己的eventloop线程加入到网络I/O线程池。

// HttpServerImpld的listen,开始获取上下文
    ContextInternal listenContext = vertx.getOrCreateContext();

// TCPServerBase,所有密码藏在这里
  public synchronized io.netty.util.concurrent.Future<Channel> listen(SocketAddress localAddress, ContextInternal context, Handler<Channel> worker) {
    ...

    // 获取共享服务
    Map<ServerID, TCPServerBase> sharedNetServers = vertx.sharedTCPServers((Class<TCPServerBase>) getClass());
    synchronized (sharedNetServers) {
      ...
      if (actualPort > 0 || localAddress.isDomainSocket()) {
        id = new ServerID(actualPort, hostOrPath);
        // 第一个instance进来 main是null,剩下的instance尽量main都不为null
        main = sharedNetServers.get(id);
        shared = true;
        bindAddress = localAddress;
      } else {
        ...
      }

      // 第一个instance进来 main是null
      if (main == null) {
        servers = new HashSet<>();
        servers.add(this);
        // 所谓的负载均衡器,就是这里
        channelBalancer = new ServerChannelLoadBalancer(vertx.getAcceptorEventLoopGroup().next());
        channelBalancer.addWorker(eventLoop, worker);
        
        // netty的ServerBootstrap终于出现了!!!!!
        ServerBootstrap bootstrap = new ServerBootstrap();
        bootstrap.group(vertx.getAcceptorEventLoopGroup(), channelBalancer.workers());
        ....
        
        // netty的childHandler,请求处理一切秘密都在里面
        bootstrap.childHandler(channelBalancer);

        ...
        if (shared) {
          sharedNetServers.put(id, this);
        }
        actualServer = this;
      } else { // 从第2个instance开始,都走这里,共享listen port,只不过,把自己的eventloop加入到netty的I/O线程池罢了。

        // Server already exists with that host/port - we will use that
        actualServer = main;
        actualServer.servers.add(this);
        actualServer.channelBalancer.addWorker(eventLoop, worker);
        metrics = main.metrics;
        listenContext.addCloseHook(this);
      }
    }

    return actualServer.bindFuture;
  }

轮询: 从bootstrap启动可知,I/O线程轮询,I/O线程是channelBalancer.workers(),也就是VertxEventLoopGroup。Netty网络框架的acceptor线程接收连接,把网络连接I/O处理,分配给I/O线程池,如何获取线程池中的线程,应该是通过next方法获取,因此是轮询。

// VertxEventLoopGroup 取线程方法就是轮询
  @Override
  public synchronized EventLoop next() {
    if (workers.isEmpty()) {
      throw new IllegalStateException();
    } else {
      EventLoop worker = workers.get(pos).worker;
      pos++;
      checkPos();
      return worker;
    }
  }

分配verticle实例,每部署一个HttpServerVerticle,就创建一个HttpServerImpl实例,每个HttpServerImpl创建HttpServerWorker作为Hanlder。因此可以把verticle实例看作是ServerChannelLoadBalancer 某个I/O线程的一个Hanlder,

ServerChannelLoadBalancer类:
1) worker集中在workers,worker的Hanlder放在workerMap中;
2) 由于每个worker可能承载多个Handler,所以workerMap的value是个WorkerList,是个Handler列表。
3) Hanlder就是承载一个verticle实例,worker就是vertx的一个eventloop线程

// HttpServerImpl的listen 创建HttpServerWorker
    Handler<Channel> channelHandler = childHandler(connContext, streamContextSupplier, hello, exceptionHandler, address, serverOrigin);

    io.netty.util.concurrent.Future<Channel> bindFuture = listen(address, listenContext, channelHandler);

// TCPServerBase中的listen,worker就是上述的channelHandler
        actualServer.channelBalancer.addWorker(eventLoop, worker);

以某个新建的连接为例,从workers I/O线程池取一个woker处理,在channel初始化的时候,在workerMap 轮询一个Hanler(也就是verticle某个instance)处理,分配给这个channel,以worker做为驱动处理这个channel请求。

//ServerChannelLoadBalancer 中
  @Override
  protected void initChannel(Channel ch) {
    // 取某个Hanlder,Handler也就是上诉述的某个verticle实例
    Handler<Channel> handler = chooseInitializer(ch.eventLoop());
    if (handler == null) {
      ch.close();
    } else {
      channelGroup.add(ch);
      handler.handle(ch);
    }
  }



分析2:当vertx eventloop个数小于verticle instance个数的情况

我们知道HttpServerVerticle部署的时候可以设置实例数量,demo例子是2个,我们可以设置大一点,比如20个。另外,我们考察一下vertx默认启动的eventloop线程个数,我们发现默认是DEFAULT_EVENT_LOOP_POOL_SIZE,即机器核数乘以2。

当instance个数大于eventloop个数,必将会出现1个eventloop绑定到多个instance的情况。最终体现到上述ServerChannelLoadBalancer中,一个worker对应多个Handler。
如下图,2个eventloop线程,HttpServerVerticle部署5个 instance,可以看出每个eventloop管理多个instance。客户端请求,先按eventloop轮询,找到eventloop后,轮询instance

image.png
TODO:于是,自然而然引出一个问题,一个eventloop对应多个verticle instance,
如何保证线程安全?
每个instance对应的业务有一个response,如何利用eventloop发送响应,而不会导致错乱?

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