Zuul2 的 线程模型

article/2025/9/22 7:10:29

Zuul2 的 线程模型

转自:https://www.jianshu.com/p/cb413fec1632

Zuul 2相对zuul 1 由同步改进为异步机制,没有了同步阻塞,全部基于事件驱动模型编程,线程模型也变得简单。

zuul做为一个网关接受客户端的请求–服务端,又要和后端的服务建立链接,把请求转发给后端服务–客户端。下面我们来分析zuul 是怎么通过一个线程池来实现的,即服务端和客户端用同一个线程池,通过netty 编程这很容易,空口无凭,我们来看zuul 是怎么实现的。

线程池初始化
线程池的个数如下

public DefaultEventLoopConfig(){eventLoopCount = WORKER_THREADS.get() > 0 ? WORKER_THREADS.get() : PROCESSOR_COUNT;acceptorCount = ACCEPTOR_THREADS.get();}

boss 线程数是1,
worker 线程数是:PROCESSOR_COUNT 缺省值如下,为cpu的核数。

zuul 异步线程模型,吞吐量很高,所以线程的个数基本按cpu核数来,这样上下文切换的开销很少。

private static final int PROCESSOR_COUNT = Runtime.getRuntime().availableProcessors();

Zuul server 的start 方法入手

public void start(boolean sync){//ServerGroup 是zuul 对netty eventloop的简单封装,serverGroup = new ServerGroup("Salamander", eventLoopConfig.acceptorCount(), eventLoopConfig.eventLoopCount(), eventLoopGroupMetrics);//使用epoll还是机遇jdk的多路复用select来实现事件驱动,epoll 只支持Linux,使用serverGroup.initializeTransport();try {List<ChannelFuture> allBindFutures = new ArrayList<>();// Setup each of the channel initializers on requested ports.for (Map.Entry<Integer, ChannelInitializer> entry : portsToChannelInitializers.entrySet()){allBindFutures.add(setupServerBootstrap(entry.getKey(), entry.getValue()));}// Once all server bootstraps are successfully initialized, then bind to each port.for (ChannelFuture f: allBindFutures) {// Wait until the server socket is closed.ChannelFuture cf = f.channel().closeFuture();if (sync) {cf.sync();}}}catch (InterruptedException e) {Thread.currentThread().interrupt();}}

Epoll 还是Selector
我们知道,操作系统底层的IO方式有select,poll,epoll,最早的是select 机制,但是select机制对文件句柄的个数有限制,而且需要迭代,而epoll 无限制,而且不需要迭代,效率最高,jdk也有epoll 的api,不过默认是采用水平出发的方式,netty 的epoll 是采用边缘触发方式,效率更高,所以写netty的都会做个适配,是用epoll还是用select。

服务端

private ChannelFuture setupServerBootstrap(int port, ChannelInitializer channelInitializer)
throws InterruptedException
{
//设置上面创建的两个线程池
ServerBootstrap serverBootstrap = new ServerBootstrap().group(
serverGroup.clientToProxyBossPool,
serverGroup.clientToProxyWorkerPool);

    // Choose socket options.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);// Choose EPoll or 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);}// Apply socket options.for (Map.Entry<ChannelOption, Object> optionEntry : channelOptions.entrySet()) {serverBootstrap = serverBootstrap.option(optionEntry.getKey(), optionEntry.getValue());}//设置channelInitializer,后面分析的入口就在这里了。serverBootstrap.childHandler(channelInitializer);serverBootstrap.validate();LOG.info("Binding to port: " + port);// Flag status as UP just before binding to the port.serverStatusManager.localStatus(InstanceInfo.InstanceStatus.UP);// Bind and start to accept incoming connections.return serverBootstrap.bind(port).sync();
}

客户端
服务端接收到请求后,请求经过一序列的filter 处理,会交给zuul的ProxyEndpoint 来把请求转发给后端服务,ProxyEndpoint这里 需要做路由和对后端链接的建立,即实现netty的客户端,执行的入口如下:
ProxyEndpoint 的 proxyRequestToOrigin 方法:

attemptNum += 1;requestStat = createRequestStat();origin.preRequestChecks(zuulRequest);concurrentReqCount++; //关键是这里,channelCtx.channel().eventLoop()promise = origin.connectToOrigin(zuulRequest, channelCtx.channel().eventLoop(), attemptNum, passport, chosenServer);logOriginServerIpAddr();currentRequestAttempt = origin.newRequestAttempt(chosenServer.get(), context, attemptNum);requestAttempts.add(currentRequestAttempt);passport.add(PassportState.ORIGIN_CONN_ACQUIRE_START);if (promise.isDone()) {operationComplete(promise);} else {promise.addListener(this);}}

上面的这行代码中的channelCtx.channel().eventLoop(),就是当前netty 接入端的worker event loop。

 promise = origin.connectToOrigin(zuulRequest, channelCtx.channel().eventLoop(), attemptNum, passport, chosenServer);

这里主要是实现如下几点:

负载均衡,选择一台机器。
为对应的机器创建链接池。
从链接池活着链接。
第一次建立链接时,最终会执行如下的代码:

public ChannelFuture connect(final EventLoop eventLoop, String host, final int port, CurrentPassport passport) {Class socketChannelClass;if (Server.USE_EPOLL.get()) {socketChannelClass = EpollSocketChannel.class;} else {socketChannelClass = NioSocketChannel.class;}final Bootstrap bootstrap = new Bootstrap().channel(socketChannelClass).handler(channelInitializer).group(eventLoop).attr(CurrentPassport.CHANNEL_ATTR, passport).option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connPoolConfig.getConnectTimeout()).option(ChannelOption.SO_KEEPALIVE, connPoolConfig.getTcpKeepAlive()).option(ChannelOption.TCP_NODELAY, connPoolConfig.getTcpNoDelay()).option(ChannelOption.SO_SNDBUF, connPoolConfig.getTcpSendBufferSize()).option(ChannelOption.SO_RCVBUF, connPoolConfig.getTcpReceiveBufferSize()).option(ChannelOption.WRITE_BUFFER_HIGH_WATER_MARK, connPoolConfig.getNettyWriteBufferHighWaterMark()).option(ChannelOption.WRITE_BUFFER_LOW_WATER_MARK, connPoolConfig.getNettyWriteBufferLowWaterMark()).option(ChannelOption.AUTO_READ, connPoolConfig.getNettyAutoRead()).remoteAddress(new InetSocketAddress(host, port));return bootstrap.connect();}

上面的代码是不是很熟悉,是netty客户端的实现,绑定的eventloop 就是前面传递进来的即接入端的eventLoop,这样zuul 就是实现了接入端的io 操作和后端服务的读写都是绑定到同一个eventLoop线程上。

总结

zuul2的线程模型好简单,就是netty的一个eventloop 线程池,来处理所有的请求和远程调用。

在这里插入图片描述
从上面的图可以看出,一个请求进来和调用远程服务,以及回写都是由同一个线程来完成的,完全没有上下文切换。zuul2 用一个线程池搞定所有的这些,这都是得益于netty 异步编程的威力。


http://chatgpt.dhexx.cn/article/XDi5pGC9.shtml

相关文章

Spring Cloud Gateway VS Netflix Zuul2

最近公司要引入统一网关&#xff0c;自己也参与调研了几种&#xff0c;当在研究Netflix的Zuul2和SpringCloudGateway时被网络上杂七杂八的材料跟震惊了&#xff0c;不客气地说很多国内博客都是在误人子弟&#xff0c;充斥着那些基于SpringCloud全家桶号称自己使用的是zuul2的“…

Zuul1和Zuul2该如何选择?

介绍 在今年5月中&#xff0c;Netflix终于开源了它的支持异步调用模式的Zuul网关2.0版本&#xff0c;真可谓千呼万唤始出来。从Netflix的官方博文[附录1]中&#xff0c;我们获得的信息也比较令人振奋&#xff1a; The Cloud Gateway team at Netflix runs and operates more t…

Zuul 2: Netflix的异步、无阻塞系统之旅

作者: Netflix Technology Blog 译者: java达人 来源: https://medium.com/netflix-techblog/zuul-2-the-netflix-journey-to-asynchronous-non-blocking-systems-45947377fb5c Zuul 2和它的“前辈”做了同样的事情—充当Netflix服务器基础设施的前门&#xff0c;处理来自全…

微服务架构:Zuul 1.0 和 2.0 我们该如何选择?

作者&#xff1a;架构师杨波 来源&#xff1a;波波微课 在今年5月中&#xff0c;Netflix终于开源了它的支持异步调用模式的Zuul网关2.0版本&#xff0c;真可谓千呼万唤始出来。从Netflix的官方博文[附录1]中&#xff0c;我们获得的信息也比较令人振奋&#xff1a; The Cloud Ga…

Zuul2核心架构

Zuul2的核心架构就是就是两大体系&#xff0c;netty体系和filter体系。 1 Netty体系 Zuul2底层采用Netty的事件响应模式&#xff0c;要掌握zuul2就必须先要掌握Netty。 1.1 Channel、Event、EventLoop、EventLoopGroup、ChannelHandler Channel&#xff1a;每一次通信就会启…

【Zuul2】网关Zuul控制台DashBoard

目录 一、需求背景 二、实现方案 一、源码获取 二、源码分析 三、效果展示 三、相关问题 一、需求背景 用JAVA为开发语言的流控网关主要分为以下三种&#xff1a; Netflix Zuul/Zuul2Spring Cloud GateWayAlibaba Sentinel 从定位上来看&#xff0c;Zuul2与SpringClou…

【Zuul2】Zuul2网关介绍及案例(非spring集成)

目录 一.使用缘由 二.项目介绍 1.核心内容 (1)三种过滤器 Inbound、Endpoint 、Outbound (2)配置文件application.properties (3)动态配置application.properties 2.参考文档 一.使用缘由 公司需要在springcloudgateway和zuul2间做一次较为完整的调研对比&#xff0c;…

time_t c语言 2038,什么是2038问题?

什么是2038问题 不知道你有没有听过2038问题?无论你是否听过,本文将带你认识什么是2038问题。 Unix时间戳 定义为从格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01日08时00分00秒)起至现在的总秒数。 而在C语言中,常用time_t来表示。举个例子: #include #in…

MySQL的时间戳2038年问题还有16年,最好在设计上的时候使用datetime就可以了,不要使用时间戳字段了,即使用了也不要用int类型进行映射,使用long类型映射即可

目录 前言1&#xff0c;关于MySQL时间戳的2038年BUG2&#xff0c;使用Docker创建MySQL 模拟下3&#xff0c;总结 前言 本文的原文连接是: https://blog.csdn.net/freewebsys/article/details/127455169 未经博主允许不得转载。 博主CSDN地址是&#xff1a;https://blog.csdn.n…

计算机为什么不用三十二进制,32位进制导致2038年问题

该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 在计算机应用上,2038年问题可能会导致某些软件在2038年无法正常工作。所有使用UNIX时 间表示时间的程序都将受其影响,因为它们以自1970年1月1日经过的秒数(忽略闰秒)来表示时间。 这种时间表示法在类Unix(Unix-like)操作系统上是…

2038年问题 linux内核5.6,Linux Kernel 5.6 开发者已率先做好准备 应对 2038 年问题

新十年伊始&#xff0c;Linux Kernel 5.6的开发者已经准备好着手解决将在下一个十年到来的2038年问题(又称“Y2038”或“Unix Y2K”问题)。Linux 5.6也成为第一个为32位系统准备运行到2038年之后的主线内核。 2038年问题与千年虫问题类似&#xff0c;它可能会导致某些软件在203…

mysql 2038年问题_时间戳(UnixTimestamp)与 《2038年问题》

时间戳是从格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01日08时00分00秒)起至现在的总秒数。 现在时间戳的长度是十位(1435113975--2015/6/24 10:46:15)。 要到 2286/11/21 01:46:40 才会变成11位(10000000000)&#xff0c;距离现在还有 271年。 不同时区获取的…

2038年问题 linux内核5.6,Linux Kernel 5.6 开发者已准备好应对 2038 年问题

2038 年问题与千年虫问题类似&#xff0c;它可能会导致某些软件在 2038 年 1 月 19 日 3 时 14 分 07 秒之后无法正常工作。届时&#xff0c;在大部分 32 位操作系统上&#xff0c;依据 “time_t” 标准&#xff0c;时间将会“绕回”且在内部被表示为一个负数&#xff0c;并造成…

2038年问题 linux内核5.6,Linux 5.1延续为2038年问题所做的多年准备

Linux 5.1为2038年问题在内核方面继续进行大量的工作。 多年来在Linux内核一直看到“Y2038”的工作&#xff0c;而这一努力远未结束。Thomas Gleixner为Linux 5.1内核提交了最新的Y2038工作&#xff0c;在之前的内核中做了大量基础工作之后&#xff0c;Linux 5.1内核引入了一组…

2038计算机系统,2038年问题

2038年问题是指在使用POSIX时间的32位计算机应用程序上&#xff0c;格林尼治时间2038年1月19日凌晨03:14:07(北京时间&#xff1a;2038年1月19日中午11:14:07)之后无法正常工作。 中文名 2038年问题 外文名 Year 2038 problem概 念 计算机bug(程序错误) 载 体 使用POSIX时…

聊一聊2038年问题

庚子年是中国传统的 60 甲子纪年法。擅长观测的古人很早就发现&#xff0c;每当年份执行到庚子这一年&#xff0c;自然灾害变多&#xff0c;突发事件频频&#xff0c;一些震动世界、影响安定的大事件也容易发生在这一年。而我们现在所处的 2020 年就是新一轮的庚子年&#xff0…

List集合去重 --指定对象属性去重

在针对特定场景下,将获取到的list<T> 集合 按照某一个特定的对象中的属性进行去重操作,以下代码会将传入的集合进行指定去重,会将指定属性重复的对象 只保留第一个,后续的重复则不会保存到去重后的集合中,当然我们也可以通过集合的差异获取出重复的对象以及后续的再将去…

使用Set集合对List集合进行去重

使用Set集合对List集合进行去重 前段时间正好遇到这样一个需求&#xff1a;我们的支付系统从对方系统得到存储明细对象的List集合&#xff0c;存储的明细对象对象的明细类简化为如下TradeDetail类&#xff0c;需求是这样的&#xff0c;我要对称List集合进行去重&#xff0c;这里…

关于两个List集合对象去重

实际项目开发中&#xff0c;很多业务场景下都会遇见集合去重。在说到两个对象去重之前&#xff0c;首先我们回顾下普通类型的list如何去重&#xff0c;这里先说两个list自带的方法&#xff0c;图画的不是太好&#xff0c;勿喷- -&#xff01; 一&#xff1a;retainAll() List&…

java list集合数据去重方式

1.概述 最近又是一轮代码review , 发现了一些实现去重的代码&#xff0c;在使用 list.contain … 我沉思&#xff0c;是不是其实很多初学者也存在这种去重使用问题&#xff1f; 所以我选择把这个事情整出来&#xff0c;分享一下。 2.contain 去重 首先是造出一个 List 模拟…