一个入口通全球 | 如何使用同一个域名访问多地部署的同一个服务

0 | 前言

最近,大模型很火,国内国外都在争相推出自己的大语言模型(LLM),有些可以公开调用 API,有些只能面向 B 端用户,各家 API 调用范式还不一致,如果单个去接的话,会花费大量的时间和经历去阅读API文档和配置 token。

由于业务需要,我们需要提供一个统一的大模型 API 聚合入口服务,对使用者屏蔽底层接入细节,统一接口规范,以及配置对应大模型的 token。

然而,大模型的提供方既有国内的(minimax,spark,文心一言等等),也有国外的(openai,claude,google-palm等等),如果将我们的聚合服务放到一个地方部署,就会有一个问题:如果部署在国内,那么很多大模型API调用的连通性就有障碍;如果部署在国外,那访问国内大模型API接口就会很慢。

本文,针对这个痛点,提出了统一域名下的跨地域联合部署方案。

1 | 方案

在最初的版本,是一套代码分别在A地和B地同时部署,通过在接口文档里,显式规定用不同的域名来区分调用A地的模型还是B地的模型API。能解决问题,但是往往使用者有概率会忽略这部分内容,导致沟通成本的提升;同时,这种方式也不利于后续的扩展(如果有C地部署,那就需要新增一个新的域名访问)。

因此,在升级方案里,通过 nginx配置转发 + header自定义字段 的形式,来实现统一域名下的跨地域服务调用。 基本原理是:统一域名指向特定的 nginx 服务器,通过在请求的 header 里的自定义字段,将请求转发到不同地域。

2 | 实操

从上面的方案原理,可以看到,关键点就在 nginx 的配置:如何配置 nginx.conf 文件,来实现根据 header 里的特定 kv,来实现请求的转发。 这里直接上 nginx.conf 上的相关配置(提供了开箱即用的全量配置文件,但是重点在最后十几行,可以直接看最后的说明):

dart 复制代码
worker_processes  4;
error_log  /error.log info;
events {
    accept_mutex on;
    multi_accept on;
    use epoll;
    worker_connections  4096;
}

http {
    include       mime.types;
    default_type  application/octet-stream;
    log_format  main  '$time_local $remote_user $remote_addr $host $request_uri $request_method $http_cookie '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for" '
                      '$request_time $upstream_response_time "$upstream_cache_status"';
    log_format  browser '$time_iso8601 $cookie_km_uid $remote_addr $host $request_uri $request_method '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for" '
                      '$request_time $upstream_response_time "$upstream_cache_status" $http_x_requested_with $http_x_real_ip $upstream_addr $request_body';
    log_format client '{"@timestamp":"$time_iso8601",'
                      '"time_local":"$time_local",'
                      '"remote_user":"$remote_user",'
                      '"http_x_forwarded_for":"$http_x_forwarded_for",'
                      '"host":"$server_addr",'
                      '"remote_addr":"$remote_addr",'
                      '"http_x_real_ip":"$http_x_real_ip",'
                      '"body_bytes_sent":$body_bytes_sent,'
                      '"request_time":$request_time,'
                      '"status":$status,'
                      '"upstream_response_time":"$upstream_response_time",'
                      '"upstream_response_status":"$upstream_status",'
                      '"request":"$request",'
                      '"http_referer":"$http_referer",'
                      '"http_user_agent":"$http_user_agent"}';

    access_log  /access.log  main;
    sendfile        on;
    
    keepalive_timeout 120s 100s;
    keepalive_requests 500;
    send_timeout 60000s;
    client_header_buffer_size 4k;
    proxy_ignore_client_abort on;
    proxy_buffers 16 32k;
    proxy_buffer_size 64k;
    proxy_busy_buffers_size 64k;
    proxy_send_timeout 60000;
    proxy_read_timeout 60000;
    proxy_connect_timeout 60000;
    proxy_cache_valid 200 304 2h;
    proxy_cache_valid 500 404 2s;
    proxy_cache_key $host$request_uri$cookie_user;
    proxy_cache_methods GET HEAD POST;

    proxy_redirect off;
    proxy_http_version 1.1;
    proxy_set_header Host                $http_host;
    proxy_set_header X-Real-IP           $remote_addr;
    proxy_set_header X-Forwarded-For     $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto   $scheme;
    proxy_set_header X-Frame-Options     SAMEORIGIN;
    proxy_set_header Upgrade             $http_upgrade;
    proxy_set_header Connection          "upgrade";  

    server_tokens off;
    client_max_body_size 50G;
    add_header X-Cache $upstream_cache_status;
    autoindex off;

    resolver      127.0.0.1:53 ipv6=off;

    map $http_region $proxy_upstream {
        cn1 server_cn_1;
        us1 server_us_1;
        default server_us_1;
    }

    upstream server_us_1 {
        server server_us_1.com;
    }

    upstream server_cn_1 {
        server server_cn_1.com;
    }

    server {
      listen 80;

      location / {
          proxy_pass http://$proxy_upstream;
      }
    }
}

说明:

  1. proxy_set_header Connection "upgrade"; 如果需要支持 websocket 链接,需要配置此项;
  2. http_region 表示 http 请求的 header 里的 key=region 的值;
  3. map $http_region $proxy_upstream 表示是一个映射(可以理解成if else),即当 region=cn1 的时候, 转向 upstream=server_cn_1 所对应的 server=server_cn_1.com;当region=us1 的时候,转向 upstream=server_us_1 所对应的 server=server_us_1.com。

因此,以上 nginx 配置表示:监听80端口,通过判断 header 里的 region 值,将请求转发给不同的后端服务。

3 | 一些感触

  1. nginx 真的很强大,越了解它,越觉得强大
  2. 所有做工程的都应该要了解/熟悉/掌握/精通 nginx(前端/devops/后端/架构)
  3. 老毛子是真厉害
  4. 晚饭不吃真的会饿
相关推荐
A尘埃4 分钟前
SpringBoot的数据访问
java·spring boot·后端
yang-23075 分钟前
端口冲突的解决方案以及SpringBoot自动检测可用端口demo
java·spring boot·后端
Marst Code10 分钟前
(Django)初步使用
后端·python·django
代码之光_198017 分钟前
SpringBoot校园资料分享平台:设计与实现
java·spring boot·后端
编程老船长30 分钟前
第26章 Java操作Mongodb实现数据持久化
数据库·后端·mongodb
IT果果日记1 小时前
DataX+Crontab实现多任务顺序定时同步
后端
苹果醋32 小时前
快速玩转 Mixtral 8x7B MOE大模型!阿里云机器学习 PAI 推出最佳实践
spring boot·nginx·毕业设计·layui·课程设计
姜学迁2 小时前
Rust-枚举
开发语言·后端·rust
爱学习的小健3 小时前
MQTT--Java整合EMQX
后端
北极小狐3 小时前
Java vs JavaScript:类型系统的艺术 - 从 Object 到 any,从静态到动态
后端