深入解析Nginx的location路由匹配规则与负载均衡算法 - 反向代理实战指南

按照我的推理,我来到了 443 号城门口,进门时,嗖的一声,被一个叫 Nginx 的大叔抓住了......话音刚

深入解析Nginx的location路由匹配规则与负载均衡算法 - 反向代理实战指南

落,Nginx 就用三咔嚓五除以二的成绩拆解了我的身体,然后得到了 HTTP 包,然后从 HTTP 包的请求行中找到了我行程的具体目的地:/user/862486453028888/posts,然后 Nginx 叔叔又把我组装了一遍,然后按照里面配置的规则, 然后说:

这里的规则是什么?其实就是 Nginx 的位置路由匹配规则、上游代理集群列表、负载均衡算法,具体请参考:《Nginx:反向代理与负载均衡》、《负载均衡算法原理》。

顺着 Nginx 叔叔给出的地址,来到了另一台服务器,这个服务器也是 Linux 大陆,然后根据地址在上面找到了一个叫 Gateway 的咚咚,听了它自己的介绍,仿佛属于系统网关一样。但是当我让他做点什么的时候,他对我说:“具体业务处理我不负责,按照你的目标 /user/....,你应该去 Nacos 注册中心问问他们 USER-SERVICE 的具体地址,所以你得一直跑来跑去,小家伙”!

好好,多亏了网关大叔的指路,那我现在就走!

哒哒哒~,我迈着高兴的步伐来到了纳科斯注册处,然后把网关叔叔给我的名字:USER-SERVICE交给了他们的工作人员,他们的工作人员经过一番询问告诉我,这个“品牌”还有一个分店,你可以去任何一家分店处理你的任务, 你可以去:xxx.xxx.xxx.xxx:8080 这个地址!

这里的 “品牌” 指的是后端的具体服务,分支是指服务集群中的每个节点。

好,好,那我去你说的那个 xxx.xxx.xxx.xxx:8080 地址!

我一边走在路上,一边思考刚刚发生的事情,然后把这次经历画了一个逻辑图,如下:

当我回去时,我必须把这个有趣的经历分享给我的朋友们,耶!

3.2. 我遇到了一只名叫 Tomcat 的大猫

根据 Nacos 给我的地址,我又来到了一个新的服务器,我记得 Nacos 给了我一个端口号,让我找到带有 8080 号的位置 来到这里之后,我慢慢地顺着这个号走,突然在我面前出现了一只大老虎,哦不, 应该是一只大猫,看起来像这样:

看起来看起来有点不错,但是头上写着我想找到 8080 地址,所以应该是我要找的吧!终于来了!我慢慢靠近那只大猫,然后告诉它找它做点什么,Tomcat 说要看我的数据,然后按照之前的封装方法把我的身体反向拆解,从而恢复了我原来的身体——HTTP 请求消息,最后 Tomcat 说:“我确实是你这次要找的最终目标, 但我想对我的肚子做这件事“!

就在我以为说完的时候,发现 Tomcat 里面有个东西,而且似乎有一小块.....漂浮在上面的陆地,当我走近时,我看到那是 JVM!

我二话不说,踏上了这片土地,正当我看着上面密密麻麻的“房间(Java方法)”时,我一头雾水,一个人走到我面前,向我介绍了自己

尊敬的各位嘉宾,远道而来的,您好,欢迎来到 JVM 神舟,我叫 Thread-xxx,我是 Thread 家族的一员,您的下一次旅程,我最终会陪伴您,您需要做的一切都由我完成,这里的客席官员请(45 度鞠躬)~

然后我走路和 Thread-xxx 聊天:

PS:接下来,我们将讨论 Java-SpringMVC 框架的实现过程,对于非 Java 开发,您可以忽略细节。

跟着 Thread-xxx 的步伐,我们找到了线程提到的 DispatcherServlet office,办公室的工作人员先看了我这次的具体目的地(资源地址),然后说:你需要先问 HandlerMapping Authority,让它给你找到负责这个业务的具体工作室。

然后线程 Thread-xxx 带我去 HandlerMapping Authority 找它的管理,经理让我给它我要找的资源的位置,然后我看到它以我的目标地址为条件,然后输入到查询器中,瞬间我就找到了这次的最终目的地: UserController 工作室!

Thread-xxx:这是你任务的最后一个工作室!我带你去那里。

这本质上是 SpringMVC 请求定位具体 Java 方法的逻辑,但由于之前一直没有“SpringMVC 原则”,下面从专业的角度对 SpringMVC 的核心原则进行简要说明。

让我们从 SpringMVC 原理图开始:

观察上面的流程图,其实难免显得有些生涩,所以此时此刻,我们还是换个更简单的方式来描述它,不再通过这个源码流程来理解它。

不知道大家还记得刚学 SpringMVC 时的配置过程吗,不过我们简单回顾一下:

(1) 配置 springmvc-servlet.xml 文件:


<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-4.3.xsd
        http://www.springframework.org/schema/mvc 
        http://www.springframework.org/schema/mvc/spring-mvc.xsd">
    
    
    <context:component-scan base-package="com.xxx.xxx"/>
    
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        
        <property name="viewClass"
                  value="org.springframework.web.servlet.view.JstlView">property>
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <property name="suffix" value=".jsp"/>
    bean>
beans>

在这第一步中,最重要的是配置扫描的数据包的位置并配置视图解析器。

(2) 配置 web.xml:


<web-app>
  <display-name>Archetype Created Web Applicationdisplay-name>
  
  <servlet>
    <servlet-name>SpringMVCservlet-name>
    
    <servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
    
    <init-param>
      <param-name>contextConfigLocationparam-name>
      <param-value>/WEB-INF/springmvc-servlet.xmlparam-value>
    init-param>
    <load-on-startup>1load-on-startup>
    
    
  servlet>
  
  
  <servlet-mapping>
    <servlet-name>SpringMVCservlet-name>
    <url-pattern>/url-pattern>
  servlet-mapping>
web-app>

在第二步中,您将配置请求路径的映射位置,并将所有请求传输到 DispatcherServlet doGet 和 doPost 方法中。

同时,因为web.xml中配置了 servlet:DispatcherServlet,所以当程序启动时,会先加载 DispatcherServlet,加载时会进行初始化操作,并调用 initStrategies() 方法

protected void initStrategies(ApplicationContext context) {
   
   
    initMultipartResolver(context);
    initLocaleResolver(context);
    initThemeResolver(context);
    initHandlerMappings(context);
    initHandlerAdapters(context);
    initHandlerExceptionResolvers(context);
    initRequestToViewNameTranslator(context);
    initViewResolvers(context);
    initFlashMapManager(context);
}

关注第四个初始化操作:调用 initHandlerMappings() 方法,由于初始化文件的位置在web.xml中指定: /WEB-INF/springmvc-servlet.xml,那么 SpringMVC 会读取配置文件中的 base-package 来扫描 package 路径,然后开始扫描 package 路径下的所有类

上面的过程一般都会有点头晕目眩,所以举个简单的例子:

@Controller("/user")
public class UserController {
   
   
    @RequestMapping("/get")
    public String get(User user) {
   
   
        ......
    }
    @RequestMapping("/add")
    public String add(User user) {
   
   
        ......
    }
}

在上述情况下,初始化后,Map容器将按以下形式添加:

// 这里是伪代码,主要是为了阐述逻辑
Map<String,InvocationHandler> map = new HashMap<>();
// 后面的UserController#get()就是以反射获取到的Method方法实例
map.put("/user/get",InvocationHandler(UserController#get()));
map.put("/user/add",InvocationHandler(UserController#add()));

最后,当请求到达时,由于前面的web.xml配置了匹配规则,所有请求都会被转移到 DispatcherServlet 的 doGet 和 doPost 中,其中 HTTP 请求 packet - 请求行中的资源路径将作为 key,然后在这个 Map 容器中进行匹配,从而定位到具体的 Java 方法并执行它。

好,最后简单描述一下整个过程:

好~,继续上一篇文章,经过 HandlerMapping Authority 的管理,我们找到了这个任务的具体工作室......

随着线程工作的开始,我们一路走过服务层,道/映射器层,当我们在服务层做事时,遇到了强大的 Redis 兄弟,Redis 兄弟看到我们后,他清楚地询问了我们来的目的,然后它说:“远道而来的尊贵客人, 请稍等片刻,让我看看我这里有没有你需要的东西!”

这个场景似曾相识,记得路上还有个CDN爷爷跟我说了同样的话~

在 Redis 的要求下,thread-xxx 将我带到了 MyBatis,它看起来像这样:

原来,雷迪斯哥哥口中的 MyBatis 竟然是一只鸟叔 [伸出舌头~]。

MyBatis 简要地回顾了我这次的探索:

慢慢地,这条线索带我去找了“鸟叔”说的那个 JDBC 爷爷,JDBC 爷爷看到了我的到来,眼中没有任何惊讶,似乎已经习以为常了,只见 JDBC 爷爷举起瘦弱的右手,指向一个地址:

jdbc:mysql://xxx.xxx.xxx.xxx:3306/db_xxxxx

然后他说:“小家伙,你又要跑很长一段路,你只能走了,Thread-xxx 只能在这里等你。

我:好,好,我走!

又是一次孤独的旅程,难免会有一些孤独感袭来,但幸运的是,我已经习惯了!一路走来,我来到了JDBC爷爷给的地址,也位于另一台服务器的Linux大陆上,我通过3306这个数字找到了一个叫MySQL的城市,当我踏入时,发现和之前踩上JVM神舟一样,当我第一次踏入MySQL这个大城市时, 有个弟子自称是DB连接家来接待我。

这里就不详细介绍 SQL 执行了,因为 MySQL 也是一个比较大的内容,在“MySQL 数据库”栏的开头,会有一篇文章:“SQL 是怎么执行的?

就在我吃完一块西瓜,喝完一瓶冰可乐的时候,DB 连接家族的一位弟子回来了,他怀里抱着很多东西(数据),然后扔给我,说:“这就是你这次需要的数据,你所有的任务我已经按照列表上的记录(SQL)处理完了。

我:好的,非常感谢你,那我走了!

顺着来的路,我赶紧赶回了JVM神舟所在的位置,然后首先映入眼帘的是:Thread-xxx哪个家伙站在原地,老老实在等我回来,我悄悄地绕到Thread-xxx后面,然后打了我一巴掌:

以后

追随 Thread-xxx 的脚步,我们终于回到了 DispatcherServlet 办公室,在他们内部人士进行了几次会议后,我准备回家了!一路上停,来到了 JVM 中国的边缘。

我告别了 Thread-xxx,离开了 JVM,终于飞出了大猫 Tomcat 的嘴巴,正式踏上了回家的路。

第四,我该回家了 - 服务器响应

经过大量的体验,现在我带着这次任务的结果在回家的路上,先是路过了网关叔叔,然后又回到了 Nginx 叔叔所在的城市,但是 Nginx 叔叔把我的身体改成了回复消息的结构,还写了一些东西进去,听说我被要求回去交给浏览器 Boss。

不过,在我回来之前,似乎还有加密层、传输层、网络层、链路层、物理层,和我当时出发的流程一样,我被一层一层地粘贴了很多东西,最后变成了 0 和 1 组成的体结构, 这个过程多么熟悉啊!

我踏上了一段不知道走了多远的旅程,同样来的路上,也遇到了很多中转站,走了各种各样的路,当然,为了防止我迷路,Nginx 叔叔还给我头上戴了一个 TCP 头,上面清楚地写着我从哪里来,应该去哪里.....

愣地往前走,终于看到了自己的出生地,看到了网路进程和浏览器老板~,哦不!我回来了!

在进屋之前,我会依次进行物理层、链路层、网络层、传输层、TLS 层的解封过程,主要是为了解析出我从后端带回来的数据。网络进程解析数据后,我的任务就完成了!紧接着,网络进程就会把数据交给浏览器 Boss,然后 Boss 会派一个小弟(渲染过程)来处理数据,我看了几眼,一般的流程是这样的:

最后,因为我已经正常返船了,为了节省资源开销,我会关闭我出发前搭建的安全通道(TCP、TLS 连接),这个过程会经过四波后由 TCP Brother 完成,如下:

详情请参考:《网络计算基础与 TCP/IP-TCP 四波》。

5. 互联网之旅总结

综上所述,用户在浏览器地址栏输入内容后,我们站在“网络请求”的角度,感受一段精彩的网络之旅,从客户端向服务器发送请求到服务器返回响应,整个过程我们“亲身”体验过一次,最后写出一个过程总结:

至此,整个流程就结束了,当然,这个过程并没有涉及太多的技术栈,也没有详细阐述整个 front/back 系统的内部执行细节,这是因为整个系统的全部细节都比较大,描述之后很难结束, 所以在本文中,我们将抓住核心点进行叙述。

最后,将请求执行的完整体验也画进了流程图中,但因为文件太失真了,所以可以通过点击链接在线访问:“浏览器进入 URL 后会发生什么?

我正在参与 Nugget Technology Community Creator Signing Program 的招募,点击链接注册提交。

你可能想看:
« 上一篇
下一篇 »

相关推荐

java中什么是冒泡,冒泡算法

2026年09月06日

28阅读

java中冒号,java双冒号

2026年09月06日

26阅读

java中的->,java中的是什么意思

2026年09月06日

24阅读

java中的super用法,Java中super的说明

2026年09月06日

28阅读

java中反射的应用,java中反射的应用场景

2026年09月06日

26阅读

java中开关的定义,Java关键字有哪些

2026年09月06日

26阅读