标准 专业
多元 极客

Zuul 2研究院(1)——启动流程源码分析

首先我们来看Zuul 2的启动代码:

try {
    // 读取properties配置
    ConfigurationManager.loadCascadedPropertiesFromResources("application");
    // IOC容器依赖注入
    Injector injector = InjectorBuilder.fromModule(new ZuulSampleModule()).createInjector();
    BaseServerStartup serverStartup = injector.getInstance(BaseServerStartup.class);
    // 获取已构建的服务
    server = serverStartup.server();¡¡

    long startupDuration = System.currentTimeMillis() - startTime;
    System.out.println("Zuul Sample: finished startup. Duration = " + startupDuration + " ms");
    // 启动服务
    server.start(true);
} catch (Throwable t) {
    ...
} finally {
    if (server != null) server.stop();

    System.exit(exitCode);
}

是不是有一些似曾相识?

1 加载配置文件

ConfigurationManager#loadCascadedPropertiesFromResources方法是Netflix处理配置的公共方法,所以比较固执,该方法的入参是configName
首先,它会加载configName.properties,其次,它会加载configName-deploymentEnvironment.propertiesconfigName-deploymentEnvironment.properties会覆盖configName.properties中出现的属性。

// ① 加载配置属性
Properties props = loadCascadedProperties(configName);
// ② 如果是聚合配置类型
if (instance instanceof AggregatedConfiguration) {
    // 使用可并发控制的配置类加载配置,然后放在配置实例中
    ConcurrentMapConfiguration config = new ConcurrentMapConfiguration();
    config.loadProperties(props);
    ((AggregatedConfiguration) instance).addConfiguration(config, configName);
} else {
    // ③ 随意加载配置
    ConfigurationUtils.loadProperties(props, instance);
}
  1. 就是很简单的拼装两个文件名称,然后用Thread的ClassLoader加载Properties,然后利用HashTable#putAll合并两个属性对象,是用configName.properties合并configName-deploymentEnvironment.properties
  2. ConcurrentMapConfiguration是用ConcurrentHashMap的高吞吐和线程安全版本,Zuul 2以此来读写配置属性,在loadProperties时,也是非常小心的使用前置监听后置监听线程安全的容器进行加载。这里的AggregatedConfiguration的意思是,它拥有一个configList来存储各种配置。如果配置发生变更,会将配置放在list的尾部;如果配置没有发生变更,那么就将配置放在原处。
  3. 如果不是AggregatedConfiguration,那么我只需要用AbstractConfiguration的实现类来接受配置即可。

2 容器注入

Sample中,服务有以下注册需求:

@Override
protected void configure() {
	// 启动器
	bind(BaseServerStartup.class).to(SampleServerStartup.class);
	// 一段自定义Eureka实例配置的代码
	bind(EurekaInstanceConfig.class).to(MyDataCenterInstanceConfig.class);
	// 基础NettyOriginManager,用于管理后端服务
	bind(OriginManager.class).to(BasicNettyOriginManager.class);
	// 添加ZuulFilterModule绑定类型
	install(new ZuulFiltersModule());
	// 恶汉单例模式的FilterFileManager
	bind(FilterFileManager.class).asEagerSingleton();
	// 服务器健康状态、发现状态管理
	bind(ServerStatusManager.class);
	// 请求接入时的Session装饰器
	bind(SessionContextDecorator.class).to(ZuulSessionContextDecorator.class);
	// 计数器注册
	bind(Registry.class).to(DefaultRegistry.class);
	// Request请求完成的处理器,也是用于计数
	bind(RequestCompleteHandler.class).to(BasicRequestCompleteHandler.class);
	// 服务发现客户端,实现的是Eureka
	bind(AbstractDiscoveryClientOptionalArgs.class).to(DiscoveryClient.DiscoveryClientOptionalArgs.class);
	// Request请求时间点计算与发布
	bind(RequestMetricsPublisher.class).to(BasicRequestMetricsPublisher.class);
	// Request日志生成,以及打一些Request基础日志
	bind(AccessLogPublisher.class).toInstance(new AccessLogPublisher("ACCESS",
			(channel, httpRequest) -> ClientRequestReceiver.getRequestFromChannel(channel).getContext().getUUID()));
}

Zuul整体使用的是Guice管理对象的依赖注入问题。

接下来我们进行逐个分析。

2.1 SampleServerStartup(基础服务启动类)

依赖注入的地方代码虽然庞大且杂乱,但是简单分析一下,就是我们之前看到的一些需要注入进IOC容器的对象。

@Inject
public SampleServerStartup(ServerStatusManager serverStatusManager, FilterLoader filterLoader,
						   SessionContextDecorator sessionCtxDecorator, FilterUsageNotifier usageNotifier,
						   RequestCompleteHandler reqCompleteHandler, Registry registry,
						   DirectMemoryMonitor directMemoryMonitor, EventLoopGroupMetrics eventLoopGroupMetrics,
						   EurekaClient discoveryClient, ApplicationInfoManager applicationInfoManager,
						   AccessLogPublisher accessLogPublisher, PushConnectionRegistry pushConnectionRegistry,
						   SamplePushMessageSenderInitializer pushSenderInitializer) {
	super(serverStatusManager, filterLoader, sessionCtxDecorator, usageNotifier, reqCompleteHandler, registry,
			directMemoryMonitor, eventLoopGroupMetrics, discoveryClient, applicationInfoManager,
			accessLogPublisher);
	// 维持Websocket和SSE渠道的客户端验证映射关系
	this.pushConnectionRegistry = pushConnectionRegistry;
	// 加入到ChannelPipeline中
	this.pushSenderInitializer = pushSenderInitializer;
}
  1. 维持Websocket和SSE渠道的客户端验证映射关系,实现方式是ConcurrentHashMap,key是clientId,主要是为了为Websocket和SSE的消息push做一下客户端映射关系。
  2. 一个加入到ChannelPipeline中的简单实现,用于发送客户端请求的一些信息。

接下来实现来自超类BaseServerStartup#choosePortsAndChannels方法:

// 启动服务@Override
protected Map<Integer, ChannelInitializer> choosePortsAndChannels(
        ChannelGroup clientChannels,
        ChannelConfig channelDependencies) {
    // 放入到ChannelPipeline中的渠道信息,key:端口,value:ChannelInitializer
    Map<Integer, ChannelInitializer> portsToChannels = new HashMap<>();
    // 获取Zuul服务的启动端口,默认为70001
    int port = new DynamicIntProperty("zuul.server.port.main", 7001).get();
    // 首先加载默认的Channel配置
    ChannelConfig channelConfig = BaseServerStartup.defaultChannelConfig();
    // 获取推送端口,默认的推送端口是7008
    int pushPort = new DynamicIntProperty("zuul.server.port.http.push", 7008).get();
    ServerSslConfig sslConfig;
    // 下面这些配置的调整依赖于你是否在ELB Http监听器、TCP监听器或者直接放在互联网上
    switch (SERVER_TYPE) {

        case HTTP:
            ...
            break;

        case HTTP2:
            ...
            break;

        case HTTP_MUTUAL_TLS:
            ...
            break;

        case WEBSOCKET:
            ...
            break;

        case SSE:
            ...
            break;
    }
    return portsToChannels;
}

也就是说SampleServerStartup是根据不同的使用场景,指定不同的ServerStartup

那我们继续看下它的超类BaseServerStartup,它的核心方法是init()

@PostConstruct
public void init() throws Exception {
    // Channel的一些配置管理
    ChannelConfig channelDeps = new ChannelConfig();
    // 添加一些Channel的依赖
    addChannelDependencies(channelDeps);
    // Channel组
    ChannelGroup clientChannels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
    // 客户端关闭钩子
    clientConnectionsShutdown = new ClientConnectionsShutdown(clientChannels,
            GlobalEventExecutor.INSTANCE, discoveryClient);
    // 构建端口和指定的ChannelInitializer
    portsToChannelInitializers = choosePortsAndChannels(clientChannels, channelDeps);
    // 根据上面的信息构建Server
    server = new Server(portsToChannelInitializers, serverStatusManager, clientConnectionsShutdown, eventLoopGroupMetrics);
}
  1. 是把我们之前声明的一些组件放入到ChannelConfig中,这些组件每个Channel都会用到。
  2. DefaultChannelGroup用于提供通知ChannelGroupFuture任务的服务组,而ChannelGroupFuture用于接受会影响ChannelGroup中Channel的I/O操作,是一个异步操作的结果集。
  3. 暂且理解它负责客户端关闭。
  4. choosePortsAndChannels()即为上面提到根据不同的协议,构建不一样的ChannelInitializer
  5. 构建Server服务。

在默认配置中,我们会注入最大连接数最大连接请求空闲时间读取时间等属性。

2.2 BasicNettyOriginManager(Origin管理者)

一个基础的基于Netty的Origin管理器,虽说是带Basic,但是也可以做一个复杂的Origin管理者。

一般在Zuul中,Origin代表边界之后的服务。

BasicNettyOriginManager使用ConcurrentHashMap来管理Origin,Key是Origin的名称,value是BasicNettyOrigin对象,BasicNettyOriginOrigin的具体实现。

BasicNettyOrigin中会有一些请求执行、请求记录、响应记录的方法。再次暂时先不详细展开讲述。

2.3 ZuulFilterModule(过滤器)

ZuulFilterModule注入了一些我们可能需要的组件。

@Override
protected void configure() {
	// Groovy动态脚本编译
	bind(DynamicCodeCompiler.class).to(GroovyCompiler.class);
	// 基于ZuulFilter的注入容器,获取出来的对象均是ZuulFilter类型
	bind(FilterFactory.class).to(GuiceFilterFactory.class);
	// ZuulFilter的一个使用次数的通知器
	bind(FilterUsageNotifier.class).to(BasicFilterUsageNotifier.class);
}
  1. Groovy动态编译脚本,用于动态加载ZuulFilter
  2. 注入一个ZuulFilter类型的IOC容器。
  3. 一个用于通知ZuulFilter使用次数的通知器。

同时它还会去加载一批已存储的过滤器,provideFilterFileManagerConfig()

@Provides
FilterFileManagerConfig provideFilterFileManagerConfig() {
	// 获取filter的综合信息
	final AbstractConfiguration config = ConfigurationManager.getConfigInstance();

	String[] filterLocations = findFilterLocations(config);
	String[] filterClassNames = findClassNames(config);

	// 实例化已知的过滤器
	FilterFileManagerConfig filterConfig = new FilterFileManagerConfig(filterLocations, filterClassNames, 5);
	return filterConfig;
}

Zuul通过FilterFileManagerConfig来加载filters到Zuul中,FilterFileManagerConfigFilterFileManger的内部类。FilterFileManagerConfigFilterFileManager的管理类,会提供一些filter的位置、类名称的集合,以及轮询时间和关于文件名的过滤器。

FilterFileManager是用于管理轮询ZuulFilter变化和添加新Groovy过滤器的管理类,内部轮询和目录轮询一般都是轮询已指定的实现的类,然后还会一个轮询者来检查一些变化信息和新添加的东西。

2.4 ServerStatusManager(Server的状态管理者)

Server的状态管理者,通过status()方法来获取实例状态,这里会有一个trick。
它分为Server的本地状态服务发现管理的远端状态,获取Server状态时,会同时对本地状态和远端状态进行校验:

public InstanceInfo.InstanceStatus status() {
    InstanceInfo.InstanceStatus local = localStatus();
    InstanceInfo.InstanceStatus remote = remoteStatus();
    // 如果本地状态为ready,且远端状态并非未知,那么返回远端状态
    // 否则返回本地状态
    if (local == UP && remote != UNKNOWN) {
        return remote;
    } else {
        return local;
    }
}
  1. 如果同时存在本地状态和远端状态,并且本地状态为ready,则返回远端状态。
  2. 如果无法获取远端状态,直接返回本地状态。

2.5 ZuulSessionContextDecorator(Zuul的会话管理上下文包装)

ZuulSessionContextDecorator是对SessionContext的进行一些其他属性注入操作,核心方法是decorate()方法:

@Override
public SessionContext decorate(SessionContext ctx) {
    ChannelHandlerContext nettyCtx = (ChannelHandlerContext) ctx.get(CommonContextKeys.NETTY_SERVER_CHANNEL_HANDLER_CONTEXT);
    if (nettyCtx == null) {
        return null;
    }

    Channel channel = nettyCtx.channel();

	// 添加已经注入的OriginManager
    ctx.put(CommonContextKeys.ORIGIN_MANAGER, originManager);

    // 声明一个用于存储Request尝试信息的容器,默认是一个ArrayList
    ctx.put(CommonContextKeys.REQUEST_ATTEMPTS, new RequestAttempts());

    // 从当前Channel中获取记录RequestBody大小和Response大小的记录
    ctx.set(CommonContextKeys.REQ_BODY_SIZE_PROVIDER, HttpBodySizeRecordingChannelHandler.getCurrentRequestBodySize(channel));
    ctx.set(CommonContextKeys.RESP_BODY_SIZE_PROVIDER, HttpBodySizeRecordingChannelHandler.getCurrentResponseBodySize(channel));

    // 获取请求通行证,它是基于纳秒时间记录的关于请求瞬时状态
    CurrentPassport passport = CurrentPassport.fromChannel(channel);
    ctx.set(CommonContextKeys.PASSPORT, passport);
    // 生成对应的UUID
    ctx.setUUID(UUID_FACTORY.generateRandomUuid().toString());

    return ctx;
}
  1. 注入之前声明的BasicNettyOriginManager实例。
  2. 声明一个Request尝试信息的容器。它用于记录每次请求尝试的详情信息,它将放在Response的内部头中使用,对于跟踪和调试有这个很大的帮助。
  3. 根据Channel设置Request、Response的Body记录大小。
  4. 获取请求通行证,该对象存储了一个基于纳秒事件记录的请求瞬时状态,什么时间点进行了什么操作,它会记录一个起始的纳秒时间,而后的瞬时状态,都是基于它的增量纳秒。
  5. 生成唯一的UUID用作标识。

2.6 DefaultRegistry

DefaultRegistry在我看来就是一个简单的计数器统一管理者。

2.7 BasicRequestCompleteHandler

BasicRequestCompleteHandler是一个关于Request请求完成的计数器,实现了RequestCompleteHandler接口,核心方法是handle()

@Override
public void handle(HttpRequestInfo inboundRequest, HttpResponseMessage response) {
	SessionContext context = inboundRequest.getContext();

    // 请求计数
	if (requestMetricsPublisher != null) {
		requestMetricsPublisher.collectAndPublish(context);
	}
}

collectAndPublish()中则实现了具体的计数实现:

@Override
public void collectAndPublish(SessionContext context) {
	// 请求的时间点
	long totalRequestTime = context.getTimings().getRequest().getDuration();
	long requestProxyTime = context.getTimings().getRequestProxy().getDuration();
	// Origin的处理时间
	int originReportedDuration = context.getOriginReportedDuration();

	// Zuul 2处理Request的时间
	long totalInternalTime = totalRequestTime - requestProxyTime;

	// 计算一下路由到Origin的时间
	// 如果值是-1,证明我们没有这个计数
	long totalTimeAddedToOrigin = -1;
	if (originReportedDuration > -1) {
		totalTimeAddedToOrigin = totalRequestTime - originReportedDuration;
	}

	// 发布记录的时间
	final String METRIC_TIMINGS_REQ_PREFIX = "zuul.timings.request.";
	recordRequestTiming(METRIC_TIMINGS_REQ_PREFIX + "total", totalRequestTime);
	recordRequestTiming(METRIC_TIMINGS_REQ_PREFIX + "proxy", requestProxyTime);
	recordRequestTiming(METRIC_TIMINGS_REQ_PREFIX + "internal", totalInternalTime);
	recordRequestTiming(METRIC_TIMINGS_REQ_PREFIX + "added", totalTimeAddedToOrigin);
}
  1. 计算当前请求的时间点。
  2. 计算origin后端节点处理请求的时间间隔。
  3. 计算Zuul 2处理整个请求的时间消耗。
  4. 计算请求路由到指定origin的时间。
  5. 最后记录下所有计算出的时间。

recordRequestTiming()则是以纳秒维度将数据记录到BasicTimer中,BasicTimer是Netflix在其开源项目中广泛应用的计数器。

2.8 AbstractDiscoveryClientOptionalArgs

AbstractDiscoveryClientOptionalArgs用于存储服务发现客户端的一些参数,参数组装来自EurekaInstanceConfig,在Zuul 2中,如果没有特殊指定EurekaInstanceConfig,默认走CloudEurekaInstanceConfig配置。

2.9 BasicRequestMetricsPublisher

BasicRequestMetricsPublisher依然是Request的计数器,上述BasicRequestCompleteHandler调用的collectAndPublish()方法即使BasicRequestMetricsPublisher的方法,在此不再赘述。

2.10 AccessLogPublisher

AccessLogPublisher的目的是是打一些关于Request的日志。

ZuulSampleModule的源码解析完毕,主要是为大家展示下Zuul启动时,依赖的组件。

3 获取已构建的服务

当我们构建完控制反转容器后,我们会从容器中获取BaseServerStartup实例,就是我们在讲容器注入时,第一个注入的部分。
获取BootServerStartup后,我们会构建Server服务,调用BaseServerStartup#server()方法,而Server的信息已经在实例初始化时构建完成(详情见SampleServerStartup讲解)。

4 启动服务

启动服务调用的是Netflix根据Netty重写的一个Server,我们看下start()方法:

public void start(boolean sync) {
	// 设置服务组的接收线程数量和工作线程数量,以及计数器
	serverGroup = new ServerGroup("Salamander", eventLoopConfig.acceptorCount(), eventLoopConfig.eventLoopCount(), eventLoopGroupMetrics);
	// 创建模型线程池
	serverGroup.initializeTransport();
	try {
		List<ChannelFuture> allBindFutures = new ArrayList<>();

		for (Map.Entry<Integer, ChannelInitializer> entry : portsToChannelInitializers.entrySet()) {
			// 根据端口和ChannelInitializer,添加对应的ChannelFuture任务
			allBindFutures.add(setupServerBootstrap(entry.getKey(), entry.getValue()));
		}

		for (ChannelFuture f : allBindFutures) {
			// 等待服务关闭
			ChannelFuture cf = f.channel().closeFuture();
			if (sync) {
				cf.sync();
			}
		}
	} catch (InterruptedException e) {
		Thread.currentThread().interrupt();
	}
}
  1. 根据EventLoopConfig和自定义计数器配置serverGroup
  2. 创建对应的Boss线程池Worer线程池
  3. 然后将之前构建好的端口—>ChannelInitializer服务,一一赋给ChannelFuture进行处理。
  4. 服务启动,等待服务关闭命令。

接下来我们介绍下上面涉及到的核心概念

4.1 ServerGroup

ServerGroupServer下面的子集,用于管理线程池和停止服务。

它使用initializeTransport()来构建线程池:

private void initializeTransport() {

	// EventExecutor的选择器,用于轮询下一个EventExecutor
	EventExecutorChooserFactory chooserFactory;
	if (USE_LEASTCONNS_FOR_EVENTLOOPS.get()) {
		chooserFactory = new LeastConnsEventLoopChooserFactory(eventLoopGroupMetrics);
	} else {
		chooserFactory = DefaultEventExecutorChooserFactory.INSTANCE;
	}

	ThreadFactory workerThreadFactory = new CategorizedThreadFactory(name + "-ClientToZuulWorker");
	Executor workerExecutor = new ThreadPerTaskExecutor(workerThreadFactory);

	// 如果使用epoll模式
	if (USE_EPOLL.get()) {
		clientToProxyBossPool = new EpollEventLoopGroup(
				acceptorThreads,
				new CategorizedThreadFactory(name + "-ClientToZuulAcceptor"));
		// 客户端代理工作线程池使用的是epoll模型线程池
		clientToProxyWorkerPool = new EpollEventLoopGroup(
				workerThreads,
				workerExecutor,
				chooserFactory,
				DefaultSelectStrategyFactory.INSTANCE
		);
	} else {
		clientToProxyBossPool = new NioEventLoopGroup(
				acceptorThreads,
				new CategorizedThreadFactory(name + "-ClientToZuulAcceptor"));
		// 否则,会使用NIO模式的线程池
		clientToProxyWorkerPool = new NioEventLoopGroup(
				workerThreads,
				workerExecutor,
				chooserFactory,
				SelectorProvider.provider(),
				DefaultSelectStrategyFactory.INSTANCE
		);
		// ③ 设置执行IO操作的比值,默认是50
		((NioEventLoopGroup) clientToProxyWorkerPool).setIoRatio(90);
	}

	// ④ 创建完客户端的管理线程池和工作线程池的的后置处理
	postEventLoopCreationHook(clientToProxyBossPool, clientToProxyWorkerPool);
}
  1. EventExecutorChooserFactoryEventExecutorChooser的实例工厂,而EventExecutorChooser用于选择下一个EventExecutor,而EventExecutorEventExecutorGroup的子集。
  2. Netty提供了epollselect两种模式,默认select模式。
  3. 设置的是执行I/O操作占CPU调度的百分比,这里会发现Zuul用的比较狠。
  4. 一个后置处理,默认没有实现,用于添加一些我们自定义的实现。

它的stop()方法用来停止服务组:

synchronized private void stop() {
    LOG.warn("Shutting down");
    if (stopped) {
        LOG.warn("Already stopped");
        return;
    }

    // 设置当前Server状态为DOWN
    serverStatusManager.localStatus(InstanceInfo.InstanceStatus.DOWN);

    // 优雅的停止客户端连接
    // 核心思想方法是关闭所有的Channel,将Channel的close()事件放入到ChannelFuture中,然后等待所有ChannelFuture完成
    clientConnectionsShutdown.gracefullyShutdownClientChannels();

    LOG.warn("Shutting down event loops");
    List<EventLoopGroup> allEventLoopGroups = new ArrayList<>();
    allEventLoopGroups.add(clientToProxyBossPool);
    allEventLoopGroups.add(clientToProxyWorkerPool);
    for (EventLoopGroup group : allEventLoopGroups) {
        // 优雅的停止EventLoopGroup
        group.shutdownGracefully();
    }

    for (EventLoopGroup group : allEventLoopGroups) {
        try {
            // 等待20秒确认EventLoopGroup停止
            group.awaitTermination(20, TimeUnit.SECONDS);
        } catch (InterruptedException ie) {
            LOG.warn("Interrupted while shutting down event loop");
        }
    }

    stopped = true;
    LOG.warn("Done shutting down");
}
  1. 优雅的停止线程池,就是将Channel关闭放入到异步事件中,然后一直阻塞直到关闭Channel完成。
  2. 等待一定时间,确认全部关闭,并将ServerGroup的状态置为已关闭。

4.2 setupServerBootstrap

setupServerBootstrap()是将每个Channel分别组装成ChannelFuture,然后返回给Server

private ChannelFuture setupServerBootstrap(int port, ChannelInitializer channelInitializer)
        throws InterruptedException {
    // 使用Netty的ServerBootstrap构建主线程池和工作线程池
    ServerBootstrap serverBootstrap = new ServerBootstrap().group(
            serverGroup.clientToProxyBossPool,
            serverGroup.clientToProxyWorkerPool);¡

    // 处理socket参数
    Map<ChannelOption, Object> channelOptions = new HashMap<>();
    channelOptions.put(ChannelOption.SO_BACKLOG, 128);
    //channelOptions.put(ChannelOption.SO_TIMEOUT, SERVER_SOCKET_TIMEOUT.get());
    channelOptions.put(ChannelOption.SO_LINGER, -1);
    channelOptions.put(ChannelOption.TCP_NODELAY, true);
    channelOptions.put(ChannelOption.SO_KEEPALIVE, true);

    // 判断是使用epoll还是NIO
    if (USE_EPOLL.get()) {
        LOG.warn("Proxy listening with TCP transport using EPOLL");
        serverBootstrap = serverBootstrap.channel(EpollServerSocketChannel.class);
        channelOptions.put(EpollChannelOption.TCP_DEFER_ACCEPT, Integer.valueOf(-1));
    } else {
        LOG.warn("Proxy listening with TCP transport using NIO");
        serverBootstrap = serverBootstrap.channel(NioServerSocketChannel.class);
    }

    // 向serverBootstrap中注入socket参数
    for (Map.Entry<ChannelOption, Object> optionEntry : channelOptions.entrySet()) {
        serverBootstrap = serverBootstrap.option(optionEntry.getKey(), optionEntry.getValue());
    }

    // 注入通道初始化器
    serverBootstrap.childHandler(channelInitializer);
    // 校验Netty的ChannelHandler和工作线程是否存在
    serverBootstrap.validate();

    LOG.info("Binding to port: " + port);

    // 绑定端口前先更改Server状态
    serverStatusManager.localStatus(InstanceInfo.InstanceStatus.UP);

    // 阻塞直到绑定端口成功
    return serverBootstrap.bind(port).sync();
}
  1. 每个Channel都由统一的一个ServerGroup统一管理,所以构建的线程池也是同一个线程池。
  2. 注入当前Channel的socket参数和类型。
  3. 注入Channel初始化器。
  4. 做一些启动前的必要检查,主要是检查ChannelHandler工作线程是否存在。
  5. 先将Server置为开启状态。
  6. 阻塞直到绑定端口成功。

经过Server#start()方法的循环处理,所有端口->Channel初始化器均已启动完毕。

5 源码环境搭建

我在研究Zuul 2的源码过程中,发现文档并非全部,进行了一些配置之后才搭建起源码调试环境,特此分享给大家。

Zuul源码分析地址

运行zuul-sample项目下,SunshineBootstrap类即可运行Zuul 2。

Eureka服务注册位于zuul2-eureka项目下,运行EurekaBootstrap即可运行一个Eureka服务注册

6 总结

Zuul 2由Zuul版本的阻塞式Servlet网关转换为现在的同步非阻塞,基于Netty构造的网关,核心思想是使用InboundChannelHandler和OutboundChannelHandler对请求进行处理及分发。

在Zuul 2的启动流程中,核心组件交由Guice的进行控制反转。

核心组件包括:启动器,Origin管理器,请求计数器,服务发现管理,与后端业务节点的长连接会话管理等组件。

赞(2) 投币

评论 抢沙发

慕勋的实验室慕勋的研究院

码字不容易,路过请投币

支付宝扫一扫

微信扫一扫