Skip to content

CubeProxy:OpenResty 里的路由魔法

本文基于 CubeSandbox 开源仓库中 CubeProxy/ 目录下的源码进行分析。

概述

CubeProxy 是 CubeSandbox 的请求路由层,基于 OpenResty(Nginx + Lua)构建。它作为沙箱流量的入口,负责将 SDK 请求路由到正确的沙箱实例。核心功能包括:

  1. Host 路由:从 Host header 解析出沙箱 ID 和端口
  2. Path 路由:支持 /sandbox/<id>/<port>/ 格式的路径路由
  3. Redis 查询:从 Redis 获取沙箱的路由元数据
  4. 智能转发:本地沙箱直连,远程沙箱通过节点代理
  5. 健康检查:故障后端检测与熔断

架构总览

请求处理流程

Host 路由模式

这是主要的路由方式,通过 Host header 中的格式 <port>-<sandbox_id>.<domain> 解析目标:

Path 路由模式

支持通过 URL 路径 /sandbox/<sandbox-id>/<container-port>/<rest> 访问沙箱:

核心模块解析

rewrite_phase.lua — Host 路由入口

lua
-- 解析 Host header: <container_port>-<sandbox_id>.<domain>
local function parse_port_and_instance_from_host(host)
    local hostname = host:match("^([^%:]+)")
    local container_port, ins_id = hostname:match("^(%d+)%-([^%.]+)")
    return container_port, ins_id
end

-- 解析并验证
local container_port, ins_id = parse_port_and_instance_from_host(ngx.var.http_host)
if not container_port or not ins_id then
    ngx.var.cube_retcode = "310400"
    ngx.exit(400)
end

-- 解析后端地址
local host_ip, host_port = sb.resolve_backend(ins_id, container_port)
ngx.var.backend_ip = host_ip
ngx.var.backend_port = host_port

-- 健康检查
sb.assert_backend_healthy(host_ip, ins_id)

sandbox_backend.lua — 后端解析核心

这是路由逻辑的核心模块,负责从 Redis 查询沙箱元数据并决定转发目标:

lua
function _M.resolve_backend(ins_id, container_port)
    -- 1. 查询本地缓存
    local cache = ngx.shared.local_cache
    local host_ip = cache:get(cache_backend_ip_key)
    local host_port = cache:get(cache_backend_port_key)
    
    if host_ip and host_port then
        -- 缓存命中,刷新过期时间
        cache:set(cache_backend_ip_key, host_ip, timeout)
        cache:set(cache_backend_port_key, host_port, timeout)
        return host_ip, host_port
    end
    
    -- 2. 查询 Redis
    local metadata = load_sandbox_proxy_metadata(ins_id)
    -- metadata 格式: {HostIP: "10.0.0.1", SandboxIP: "192.168.1.100", "8080": "49983", ...}
    
    -- 3. 判断路由方式
    local target_host_ip = metadata["HostIP"]
    local target_sandbox_ip = metadata["SandboxIP"]
    
    if caller_host_ip == target_host_ip then
        -- 本地沙箱:直连 SandboxIP
        host_ip = target_sandbox_ip
        host_port = container_port
    else
        -- 远程沙箱:通过节点代理
        host_ip = target_host_ip
        host_port = metadata[container_port]  -- 映射后的 HostPort
    end
    
    -- 4. 写入缓存
    cache:set(cache_backend_ip_key, host_ip, timeout)
    cache:set(cache_backend_port_key, host_port, timeout)
    
    return host_ip, host_port
end

路由决策逻辑

场景条件转发目标
本地沙箱CubeProxy IP == metadata.HostIPSandboxIP:ContainerPort
远程沙箱CubeProxy IP != metadata.HostIPHostIP:HostPort

balancer_phase.lua — Upstream 选择

lua
local balancer = require "ngx.balancer"

-- 设置后端地址(由 rewrite 阶段计算)
local ok, err = balancer.set_current_peer(ngx.var.backend_ip, ngx.var.backend_port)
if not ok then
    ngx.var.cube_retcode = "310509"
    ngx.exit(500)
end

redis_iresty.lua — Redis 客户端

封装了 Redis 操作,支持连接池和重试:

lua
local redis = require "redis_iresty"
local red = redis:new({
    redis_ip = ngx.var.redis_ip,
    redis_port = ngx.var.redis_port,
    redis_pd = ngx.var.redis_pd,
    redis_index = ngx.var.redis_index
})

-- 查询沙箱元数据
local key = "bypass_host_proxy:" .. ins_id
local value, err = red:hgetall(key)

Redis 数据结构

CubeProxy 依赖 Redis 存储沙箱的路由元数据,由 CubeMaster 写入:

bypass_host_proxy:<sandbox_id>

Hash 结构,存储沙箱的网络路由信息:

Key: bypass_host_proxy:abc123
Value:
  HostIP: "10.0.0.1"           # 沙箱所在节点 IP
  SandboxIP: "192.168.1.100"   # 沙箱内部 IP(TAP 设备 IP)
  8080: "49983"                # 容器端口 → HostPort 映射
  3000: "49984"                # 另一个端口映射
  Status: "running"            # 沙箱状态

faulty_backend_set

Set 结构,存储故障后端 IP:

Key: faulty_backend_set
Members: ["10.0.0.2", "10.0.0.3"]

Nginx 配置详解

监听端口

nginx
server {
    listen 8081 reuseport;    # HTTP 入口
    server_name _;
}

server {
    listen 8080 ssl reuseport;  # HTTPS 入口
    server_name _;
    ssl_certificate     /usr/local/openresty/nginx/certs/cube.app+3.pem;
    ssl_certificate_key /usr/local/openresty/nginx/certs/cube.app+3-key.pem;
}

Location 配置

nginx
# Host 路由模式(默认)
location / {
    rewrite_by_lua_file lua/rewrite_phase.lua;
    proxy_pass http://backend;
    header_filter_by_lua_file lua/header_filter_phase.lua;
    log_by_lua_file lua/log_phase.lua;
}

# Path 路由模式
location ^~ /sandbox/ {
    rewrite_by_lua_file lua/path_rewrite_phase.lua;
    proxy_pass http://backend;
    
    # 响应头重写,保持路径前缀
    proxy_redirect    ~^/(.*)$  /sandbox/$ins_id/$container_port/$1;
    proxy_cookie_path /         /sandbox/$ins_id/$container_port/;
}

Upstream 配置

nginx
upstream backend {
    server 0.0.0.1:1234;           # 占位地址
    balancer_by_lua_file lua/balancer_phase.lua;
    keepalive 1500;
    keepalive_timeout 80;
}

缓存机制

CubeProxy 使用多层缓存提高性能:

1. Lua 共享内存缓存

nginx
lua_shared_dict local_cache 500m;
lua_shared_dict faulty_backend 100m;

缓存 Key 格式:

  • <sandbox_id>:<container_port>:backend_ip
  • <sandbox_id>:<container_port>:backend_port
  • <sandbox_id>:HostIP
  • <sandbox_id>:SandboxIP

2. 缓存过期策略

lua
local function get_cache_timeout()
    return math.random(ngx.var.timeout_min, ngx.var.timeout_max)
end

使用随机过期时间避免缓存雪崩。

故障处理

故障后端检测

lua
function _M.is_faulty_backend(self, backend_ip, check_remote)
    -- 1. 检查本地缓存
    local cache = ngx.shared.faulty_backend
    local value = cache:get(backend_ip)
    if value == "true" then
        return true, nil
    end
    
    -- 2. 检查 Redis
    if check_remote then
        local key = "faulty_backend_set"
        local value = red:smembers(key)
        -- 检查 backend_ip 是否在集合中
    end
    
    return false, nil
end

熔断响应

lua
function _M.assert_backend_healthy(host_ip, ins_id)
    local faulty_backend = utils:is_faulty_backend(host_ip, true)
    if faulty_backend == true then
        ngx.var.cube_retcode = "340500"
        ngx.exit(500)  -- 返回 500,触发重试
    end
end

错误码

CubeProxy 定义了详细的错误码用于监控和排查:

错误码含义
310200正常
310400请求格式错误(Host/Path 解析失败)
310500Redis 查询失败
310507元数据缺失(HostIP/SandboxIP/端口映射)
310508后端地址无效
310509连接后端失败
340500后端故障熔断

性能优化

1. 连接池

nginx
upstream backend {
    keepalive 1500;           # 最大空闲连接数
    keepalive_timeout 80;     # 空闲连接超时
}

2. 缓存命中

  • 首次请求查询 Redis(~1ms)
  • 后续请求命中本地缓存(~0.01ms)

3. 本地直连

当 CubeProxy 与沙箱在同一节点时,直接连接 SandboxIP,跳过节点代理,减少一跳延迟。

启动脚本

bash
#!/bin/bash
# start.sh

/usr/sbin/crond                    # 启动定时任务(日志轮转)
/usr/local/openresty/nginx/sbin/nginx  # 启动 OpenResty

延伸阅读