基于 Nginx 的动态代理

wkxw0750 8年前
   <h2>基于 Nginx 的动态代理</h2>    <p>在实际应用中,遇到了这样一个场景:</p>    <p>已有一个手机 APP 客户端,需要在该 APP 客户端中实现通过 Web 的形式接入其他的应用页面。按照常规的流程,在 APP 中为应用设置入口链接按钮,当用户点击应用入口按钮时,APP 启动 WebView 并打开设置的应用链接即可。</p>    <p>但在该场景中,接入 APP 的应用均部署在内网服务器,外网无法直接访问,因此在 APP 中配置的链接是内网地址,当用户通过外网使用 APP 时,将无法访问接入的 Web 应用。</p>    <p>针对如上场景中遇到的问题,本文中提出了基于 Nginx 实现动态代理的解决方案。</p>    <h2>使用代理</h2>    <p>在前面的场景中,要实现内网应用能够被外网访问,一般有两种方式:</p>    <ul>     <li> <p>将应用部署到可被外网访问的服务器,通常为 DMZ 区服务器</p> </li>     <li> <p>使用反向代理服务器,将外网请求代理转发到内网的应用服务器</p> </li>    </ul>    <p>其中,将内网应用部署到可被外网访问的服务器上的方法,通常受限于可提供的硬件环境、安全控制等方面的问题,并不是解决该类问题的首选方案。因此,通常会在可被外网访问的服务器上部署反向代理服务器,使用代理转发来解决。</p>    <p>目前最常用的软件反向代理服务器有 Apache 和 Nginx 。通过配置文件设置,就可以将特定的链接向应用服务器转发。例如 Nginx 可通过以下简单的配置,即可实现代理转发:</p>    <pre>  <code class="language-groovy">server  {      listen 80;      server_name domain.com;      location /app1 {          proxy_redirect off;          proxy_set_header Host $host;          proxy_set_header X-Real-IP $remote_addr;          proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;          proxy_pass http://192.168.10.38:3000/app1;      }      access_log logs/domain.com.access.log;  }</code></pre>    <p>在以上实例中,Nginx 配置完成后,即可将 <a href="/misc/goto?guid=4959718276399696191" rel="nofollow,noindex">http://domain.com/app</a> 的所有请求代理转发到 <a href="/misc/goto?guid=4959718276499533871" rel="nofollow,noindex">http://192.168.10.38:3000/app1</a> 地址,这样就可以实现针对 app1 的从外网访问内网应用的代理转发功能。此时 app1 对于外网的访问地址就变成了 <a href="/misc/goto?guid=4959718276399696191" rel="nofollow,noindex">http://domain.com/app</a> 。</p>    <p>通过使用代理服务器, <strong>所有的应用将拥有统一域名</strong> ,而通过 <strong>二级目录区分</strong> 不同的应用。</p>    <p>在 Apache 中同样可以采用类似的策略进行代理转发,本文中主要以 Nginx 作为实例。</p>    <p>在多个接入多个应用的情况下,只要按照上述实例中的配置,为不同的应用分配不同的二级目录,即可实现向不同应用进行代理转发。这样就可以解决文章开始提出的问题。</p>    <p>但是这一解决方案也存在问题:</p>    <ul>     <li> <p>当需要新增/修改/删除应用的代理转发配置时,需要人工修改配置文件,并重启代理转发服务器,这将导致服务的暂时不可用。</p> </li>     <li> <p>当接入应用较多时,配置文件将越来越大,对用人工维护造成极大的不便。</p> </li>     <li> <p>当代理转发服务器进行集群化部署时,每次对配置文件的更新,都需要更新所有代理转发服务器,并进行重启,这将增大维护的风险。</p> </li>    </ul>    <p>针对以上的问题,需要对该访问进行进一步改进。</p>    <h2>使用动态代理</h2>    <p>如果能够使反向代理服务器动态的通过集中的配置数据更新针对应用的代理配置,就可以解决上述方案中存在的问题。</p>    <p>经过研究分析,本文中提出 <strong>动态代理</strong> 方案,流程如下:</p>    <p><img src="https://simg.open-open.com/show/668f2933a1c4c55d87e6d4ea6fbf434b.png"></p>    <p>当请求进入反向代理服务器时,反向代理服务器将分析进入的请求 URL ,识别 URL 中的二级目录(用于区分不同的应用),然后使用该二级目录作为应用标识,到代理配置数据数据中进行查询,获得代理地址的返回结果,然后将该请求转发到对应的应用服务器。</p>    <p>同时,管理人员可以通过特定的管理端,对代理配置数据进行 CRUD 操作,方便管理人员对代理应用配置的实时管理。</p>    <p>通过以上流程,即可实现在不中断服务的情况下,动态修改代理配置。同时也可以确保集群化的反向代理服务器同步更新,都可以获得最新的配置数据。</p>    <p>使用动态代理方案,即可以解决在文章开头提出的问题。</p>    <h2>基于 Nginx 实现动态代理</h2>    <p>为了实现动态代理方案,需要在反向代理服务器中增加定制的功能。针对这一目的,研究了目前主流的反向代理服务器 Apache 和 Nginx ,结论如下:</p>    <p>Apache 和 Nginx 均可以增加定制的模块以实现定制的功能。但是 Apache 目前必须使用 C 语言按照 Apache 的要求编写模块,这对于开发者要求相对较高。而 Nginx 同样可以使用 C 语言开发扩展模块,但除此之外,目前已有针对 Nginx 开发的 Lua 语言解释器模块,即可以在 Nginx 的配置文件中直接调用 Lua 语言开发的脚本程序,这种方式极大的降低了定制功能开发的难度。因此,采用 Nginx 作为反向代理服务器,使用 Lua 语言作为定制功能开发语言,进行动态代理功能实现。</p>    <p>同时,由于反向代理服务器需要处理大量的代理请求,因此会频繁的读取反向代理配置数据。基于这一情况,选用 Redis 作为数据库,利用其高性能的数据读写,支撑代理配置数据的频繁访问。</p>    <p>根据以上的技术选型,设计流程图如下:</p>    <p><img src="https://simg.open-open.com/show/37b5ce92c400006f91c75ddf9e9d876b.png"></p>    <p>在 Nginx 的配置文件中通过 Lua 解释器模块,调用 Lua 脚本。请求进入 Nginx 后,通过 Lua 脚本处理请求,并连接 Redis 获取当前 URL 对应的应用的代理地址,处理完成后,将代理地址回写到 Nginx 的配置块,由 Nginx 完成后续的代理转发工作。</p>    <p>在运行期间,管理人员可以使用代理管理端,对 Redis 中的代理配置数据进行操作,操作完成后,Nginx 代理服务器将及时读取到最新的配置数据进行转发。</p>    <p>经过调研,在具体开发过程中,采用了基于 Nginx 进行了模块扩展的 OpenResty。OpenResty 基于 Nginx 扩展了大量的模块,其中非常核心的特点就是在 Nginx 中集成了 Lua 解释器,实现了 Nginx 调用 Lua 脚本。同时 OpenResty 还提供了 Lua 语言实现的访问 Redis 的代码模块。</p>    <h2>Nginx 动态代理优化</h2>    <h3>代理配置数据缓存</h3>    <p>在实际测试过程中,当访问量较大时,由于 Nginx 服务器每次代理都会查询 Redis ,可能是导致 Redis 压力过大而无法响应,导致请求被阻塞。</p>    <p>为了应对这一问题,在 Nginx 中,使用 Lua 脚本设置内存缓存,从 Redis 获取的代理数据将在一定时间内保留在 Nginx 服务器的内存中,在内存缓存数据有效期内,将不会再重复向 Redis 请求数据。</p>    <p>经过测试,在进行数据缓存优化后,极大的提高了 Redis 访问的稳定性。</p>    <h3>Redis 集群化</h3>    <p>由于单点 Redis 一旦无法提供服务,将导致 Nginx 代理服务无法正常使用。针对这一问题,需要对 Redis 进行集群化。</p>    <p>目前比较成熟的解决方案是对 Redis 进行主从备份,一个主节点提供对外服务,多个从节点进行数据备份,并在主节点停止服务后产生新的主节点继续提供服务。使用 Redis 提供的 Redis Sentinel 进行主从节点监控,并向 Nginx 提供最新的主节点信息。</p>    <h3>Nginx 集群化</h3>    <p>随着访问量逐渐增大,单机的 Nginx 将无法再支持过大的访问量,同时单机 Nginx 一旦停止服务,将影响整个系统的正常运行。因此需要将 Nginx 进行集群化,部署多个 Nginx 反向代理服务器,提供同样的服务。</p>    <p>基于 Nginx 的动态代理方案,提供的代理服务为无状态服务,因此可以直接复制 Nginx 以实现集群化。</p>    <p> </p>    <p>来自:https://segmentfault.com/a/1190000007059973</p>    <p> </p>