OpenResty+NtripCaster实现挂载点负载均衡使用初探

文章目录

  • [1 OpenResty 介绍及与 Nginx 的区别](#1 OpenResty 介绍及与 Nginx 的区别)
    • [1.1 OpenResty 介绍](#1.1 OpenResty 介绍)
    • [1.2 OpenResty 与 Nginx 的区别](#1.2 OpenResty 与 Nginx 的区别)
  • 2.安装OpenResty
  • [3 OpenResty+NtripCaster负载均衡](#3 OpenResty+NtripCaster负载均衡)
    • [3.1 编写 Lua 辅助函数(缓存 + 解析 sourcetable)](#3.1 编写 Lua 辅助函数(缓存 + 解析 sourcetable))
    • [3.2 配置 nginx.conf](#3.2 配置 nginx.conf)
  • [4 启动 OpenResty](#4 启动 OpenResty)
    • [4.1 启动 OpenResty命令](#4.1 启动 OpenResty命令)
    • [4.2 可能会遇到的错误](#4.2 可能会遇到的错误)
  • [5 挂载点负载均衡验证](#5 挂载点负载均衡验证)
    • [5.1 挂载点自动选择测试](#5.1 挂载点自动选择测试)
    • [5.2 挂载点自动切换测试](#5.2 挂载点自动切换测试)
  • [6. 后记](#6. 后记)
    • [6.1 本文相关资源](#6.1 本文相关资源)
    • [6.2 待解决的问题](#6.2 待解决的问题)

1 OpenResty 介绍及与 Nginx 的区别

1.1 OpenResty 介绍

OpenResty(也称为 OpenResty/nginx)是一个基于Nginx核心的高性能 Web 平台,由章亦春(agentzh)主导开发。它将 Nginx 与众多 Lua 库、模块和工具链深度集成,实现了在 Nginx 核心中通过 Lua 脚本进行灵活的业务逻辑开发,本质上是"Nginx + Lua 生态"的扩展套件。

核心特点:

  1. 高性能:继承 Nginx 的事件驱动、非阻塞 I/O 模型,同时 Lua 脚本的执行开销极低。
  2. 扩展性:通过 Lua 可以轻松实现复杂的业务逻辑(如 API 网关、限流、缓存、鉴权等),无需修改 Nginx 核心代码。
  3. 丰富的生态 :内置大量 Lua 库(如 lua-nginx-modulelua-resty-redislua-resty-mysql),支持与主流数据库、缓存中间件交互。
  4. 一站式解决方案:可作为 Web 服务器、反向代理、API 网关、WAF(Web 应用防火墙)等使用。

1.2 OpenResty 与 Nginx 的区别

维度 Nginx OpenResty
本质 轻量级高性能 Web 服务器/反向代理 基于 Nginx 核心的 Lua 扩展平台
功能扩展 主要通过 C 模块扩展,开发成本高 支持 Lua 脚本快速扩展,开发灵活高效
生态支持 原生模块较少,以基础网络功能为主 内置丰富的 Lua 库,适配各类中间件
使用场景 纯静态资源服务、简单反向代理 复杂 API 网关、动态业务逻辑处理、微服务网关等
性能 原生性能极致,无额外开销 继承 Nginx 性能,Lua 执行开销可忽略
开发门槛 扩展需掌握 C 语言 扩展可使用 Lua,门槛更低

简单总结:Nginx 是基础的高性能网络引擎,而 OpenResty 是在其之上构建的、面向业务开发的"超级引擎",通过 Lua 赋予了 Nginx 处理复杂业务逻辑的能力,同时保留了 Nginx 原有的高性能特性。

2.安装OpenResty

以Centos为例

bash 复制代码
cd /etc/yum.repos.d
sudo wget -nc  https://openresty.org/package/centos/openresty.repo
sudo yum clean all
sudo yum list
sudo yum makecache
yum install openresty

以Ubuntu为例

bash 复制代码
add-apt-repository -y ppa:openresty/ppa
apt-get update
apt-get install openresty

3 OpenResty+NtripCaster负载均衡

3.1 编写 Lua 辅助函数(缓存 + 解析 sourcetable)

例如现在有两台NtripCaster服务器,分别是:

bash 复制代码
 { name = "A", host = "172.0.0.1", 数据端口 = 7001, 信息端口= 7070 },
 { name = "B", host = "172.0.0.1", 数据端口 = 8001, 信息端口 = 8080 }

创建文件:/usr/local/openresty/nginx/conf/mountpoint_router.lua

文件中编辑如下信息:

lua 复制代码
-- mountpoint_router.lua
local _M = {}

local cache = ngx.shared.mountpoint_cache

-- 解析 sourcetable 内容(支持纯文本或 <pre> 包裹的 HTML)
local function parse_sourcetable(content)
    if not content or type(content) ~= "string" then
        return {}
    end

    local mounts = {}

    -- 如果是 HTML,尝试提取 <pre> 内容
    local pre_match = string.match(content, "<pre[^>]*>(.-)</pre>")
    if pre_match then
        content = pre_match
    end

    -- 按行解析
    for line in string.gmatch(content, "[^\r\n]+") do
        line = string.gsub(line, "^%s+", "")  -- 去除行首空格
        if string.sub(line, 1, 3) == "STR" then
            local mount = string.match(line, "STR;([^;]+);")
            if mount and mount ~= "" then
                mounts[mount] = true
                -- 可选:记录日志用于调试
                -- ngx.log(ngx.DEBUG, "Found mountpoint: ", mount)
            end
        end
    end

    return mounts
end

-- 获取某主机的挂载点列表(带缓存)
local function get_mounts(host, port)
    local key = host .. ":" .. port
    local cached, err = cache:get(key)
    if cached then
        return cached
    end

    local http = require "resty.http"
    local httpc = http.new()
    local uri = "http://" .. host .. ":" .. port .. "/sourcetable.html"

    local res, err = httpc:request_uri(uri, {
        timeout = 1000,  -- 1秒超时
    })

    local mounts = {}
    if res and res.status == 200 and res.body then
        mounts = parse_sourcetable(res.body)
    else
        ngx.log(ngx.WARN, "Failed to fetch sourcetable from ", uri, ": ", err or ("status=" .. (res and res.status or "nil")))
    end

    -- 缓存 5 秒(可根据 sourcetable 更新频率调整)
    cache:set(key, mounts, 5)

    httpc:close()
    return mounts
end

-- 主路由逻辑
function _M.select_backend(mountpoint)
    if not mountpoint or mountpoint == "" then
        return nil
    end

    -- 定义后端(A 和 B)
    local BACKENDS = {
        { name = "A", host = "172.35.20.115", data_port = 7001, status_port = 7070 },
        { name = "B", host = "172.35.20.115", data_port = 8001, status_port = 8080 }
    }

    -- 先查 A
    local mounts_A = get_mounts(BACKENDS[1].host, BACKENDS[1].status_port)
    if mounts_A[mountpoint] then
        ngx.log(ngx.INFO, "Mountpoint '", mountpoint, "' found on backend A")
        return BACKENDS[1]
    end

    -- 再查 B
    local mounts_B = get_mounts(BACKENDS[2].host, BACKENDS[2].status_port)
    if mounts_B[mountpoint] then
        ngx.log(ngx.INFO, "Mountpoint '", mountpoint, "' found on backend B")
        return BACKENDS[2]
    end

    -- 都没有,返回 nil(调用方应处理 404)
    ngx.log(ngx.WARN, "Mountpoint '", mountpoint, "' not found on any backend")
    return nil
end

return _M

3.2 配置 nginx.conf

配置文件默认在:/usr/local/openresty/nginx/conf/nginx.conf

bash 复制代码
# nginx.conf
worker_processes 1;
error_log logs/error.log warn;
pid logs/nginx.pid;

events {
    worker_connections 1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    log_format main '$remote_addr - $remote_user [$time_local] '
                    '"$request" $status $body_bytes_sent '
                    '"$http_referer" "$http_user_agent"';

    access_log logs/access.log main;

    # Lua 模块路径(确保能加载 .lua 文件)
    lua_package_path "/usr/local/openresty/nginx/conf/?.lua;;";

    # 共享内存缓存挂载点状态
    lua_shared_dict mountpoint_cache 10m;

    server {
        listen 2101;
        server_name localhost;

        # 必须预先声明变量!
        set $backend_host "";
        set $backend_port "";

        location ~ ^/(?<mount>[^/\s]+)$ {
            access_by_lua_block {
                local router = require "mountpoint_router"
                local backend = router.select_backend(ngx.var.mount)

                if not backend then
                    ngx.log(ngx.ERR, "Mountpoint not available: ", ngx.var.mount)
                    ngx.status = 404
                    ngx.say("Mountpoint not found: ", ngx.var.mount)
                    ngx.exit(404)
                end

                ngx.var.backend_host = backend.host
                ngx.var.backend_port = backend.data_port
            }

            # 转发到选中的后端
            proxy_pass http://$backend_host:$backend_port/$mount;

            # Ntrip 关键配置
            proxy_http_version 1.0;
            proxy_set_header Host $host;
            proxy_set_header Connection "";
            proxy_set_header User-Agent $http_user_agent;
            proxy_buffering off;
            proxy_read_timeout 3600s;
            proxy_send_timeout 3600s;
        }

        # 可选:根路径返回 sourcetable(兼容部分客户端)
        location = / {
            return 200 "Ntrip Caster Proxy\n";
        }
    }
}

4 启动 OpenResty

4.1 启动 OpenResty命令

bash 复制代码
openresty

或使用如下命令查看是否无错误

bash 复制代码
openresty -t

重新加载配置

bash 复制代码
openresty -t && sudo openresty -s reload

停止服务

bash 复制代码
openresty -s stop 

您可以通过访问 http://localhost:8080 来验证 OpenResty 是否正常运行

错误信息可以从如下命令查看

bash 复制代码
tail -f /usr/local/openresty/nginx/logs/error.log

4.2 可能会遇到的错误

遇到 'resty.http' not found的错误:

2025/12/24 12:21:02 [error] 16031#16031: *7 lua entry thread aborted: runtime error: /usr/local/openresty/nginx/conf/mountpoint_router.lua:44: module 'resty.http' not found:

这是因为你的 OpenResty 没有安装 lua-resty-http 库,而你的 Lua 脚本中使用了:

bash 复制代码
local http = require "resty.http"

可以到https://github.com/lewis6991/lua-resty-http获取/lib/resty/目录下获取http.luahttp_connect.lua以及http_headers.lua这三个脚本

放置在如下目录并重启服务即可:/usr/local/openresty/lualib

(备用下载地址1:https://github.com/lewis6991/lua-resty-http

(备用下载地址2:lua-resty-http-master库及OpenResty 配置

5 挂载点负载均衡验证

5.1 挂载点自动选择测试

A Caster上(7001端口)只配置了AHCZ0挂载点

B Caster上(8001端口)只配置了BJCP0挂载点

使用STRSVR工具连接 OpenResty(2101端口),即可自动根据挂载点情况,选择后台Caster服务。

5.2 挂载点自动切换测试

步骤1:A Caster上(7001端口)只配置了AHCZ0挂载点,利用STR工具上传BJCP0挂载点

步骤2:B Caster上(8001端口)只配置了BJCP0挂载点

步骤3:使用STRSVR工具连接 OpenResty(2101端口),BJCP0挂载点,可以看到先连接了A Caster 7001的上传的BJCP0挂载点;

步骤4:停止A Caster上的挂载点BJCP0,用户端短暂重连后,可发现自动成功定向至B Caster的挂载点。

可验证本脚本的有效性。

6. 后记

6.1 本文相关资源

本文用到的相关文件如下
lua-resty-http-master库及OpenResty 配置

6.2 待解决的问题

(1)目前后台连接数据流的时候会提示2025/12/25 15:57:26 [error] 16636#16636: *20 upstream sent no valid HTTP/1.0 header while reading response header from upstream, client: 10.35.0.3, server: localhost, request: "GET /BJCP0 HTTP/1.0", upstream: "http://172.XXX.XXX.XXX:8001/BJCP0",但不影响使用。

(2)通过OpenResty的2101端口,使用RTKLIB STRSVR等工具不能实现Get mountP按钮获取挂载点列表sourcetable信息。

相关推荐
秃了也弱了。3 小时前
OpenResty+redis实现基于ip的代理层灰度发布
redis·tcp/ip·openresty
汽车仪器仪表相关领域5 小时前
ZRT-I 精密减速器测试系统
大数据·运维·功能测试·安全·单元测试·负载均衡·压力测试
要开心吖ZSH1 天前
Spring Boot + JUnit 5 + Mockito + JaCoCo 单元测试实战指南
java·spring boot·junit·单元测试
secondyoung1 天前
Pandoc转换Word文档:使用Lua过滤器统一调整Pandoc文档中的图片和表格格式
经验分享·junit·word·lua·markdown·pandoc·mermaid
Knight_AL1 天前
Redis Lua 脚本核心语法详解:KEYS[1]、ARGV[1]、tonumber 是什么意思?
redis·junit·lua
木风小助理2 天前
分布式系统统一限流:基于Redis与Lua的跨实例流量管控方案
junit
云老大TG:@yunlaoda3602 天前
华为云国际站代理商NAT的高可用与弹性具体是如何实现的?
服务器·数据库·华为云·负载均衡
徒手千行代码无bug2 天前
Nginx upstream 负载均衡 404,单节点转发正常的根因与解决
运维·nginx·负载均衡
没有bug.的程序员2 天前
负载均衡的真正含义:从算法到架构的深度解析
java·jvm·算法·微服务·架构·负载均衡