APISIX + Nacos + Dubbo 整合实践:以订单服务为例

在 Dubbo 微服务体系中,外部客户端通常使用 HTTP/HTTPS,而内部服务使用 Dubbo RPC,Nacos 负责注册与服务发现。传统方案往往需要额外部署 HTTP Adapter,将 HTTP 请求转换为 Dubbo 调用。本文以订单查询与下单业务为例,整理 APISIX、Nacos 与 Dubbo 的整合方式,重点说明服务发现、HTTP→Dubbo 协议转换、Dubbo 3 注册模型、动态扩缩容、版本兼容以及生产环境容易踩到的问题。

为什么需要 APISIX + Nacos + Dubbo

假设一个典型的订单系统已经采用 Dubbo 构建内部微服务:

1
2
3
4
order-service
payment-service
inventory-service
user-service

内部调用可能是:

1
2
3
4
5
order-service
|
+---- Dubbo ----> inventory-service
|
+---- Dubbo ----> payment-service

但浏览器、App、小程序或者第三方系统不可能直接调用 Dubbo,它们通常使用:

1
2
3
4
HTTP
HTTPS
REST API
JSON

于是南北向流量会遇到两个问题:

  1. HTTP 与 Dubbo 的协议不同,需要协议转换。
  2. Dubbo Provider 地址动态变化,网关不能把 Provider IP 写死。

传统做法通常是在 Dubbo 服务前面再增加一个 HTTP Adapter:

flowchart LR
    Client[Web / App / 第三方系统]
    Gateway[API Gateway]
    Adapter[Order HTTP Adapter]
    Nacos[Nacos]
    Order1[Order Dubbo Provider #1]
    Order2[Order Dubbo Provider #2]

    Client -->|HTTP| Gateway
    Gateway -->|HTTP| Adapter
    Adapter -->|Dubbo| Order1
    Adapter -->|Dubbo| Order2

    Adapter -. 服务发现 .-> Nacos
    Order1 -. 注册 .-> Nacos
    Order2 -. 注册 .-> Nacos

这种架构当然能工作,而且当 Adapter 承担 API 聚合、DTO 转换、权限模型转换、领域防腐等职责时,它本身也是有价值的。

但如果 Adapter 只是机械地做:

1
2
3
4
5
HTTP JSON

转换参数

Dubbo RPC

那么它会额外增加一次网络跳转、一套部署单元以及一层故障点。

APISIX 与 Dubbo、Nacos 组合的核心思路,就是让 APISIX 同时承担:

1
2
3
4
5
HTTP API Gateway
+
HTTP → Dubbo Protocol Bridge
+
Nacos Service Discovery

从而形成:

flowchart LR
    Client[Web / App / 第三方系统]
    APISIX[Apache APISIX]
    Nacos[Nacos Registry]
    Order1[order-service #1<br/>Dubbo :20880]
    Order2[order-service #2<br/>Dubbo :20881]

    Client -->|HTTP / HTTPS| APISIX

    Order1 -. 注册 Provider .-> Nacos
    Order2 -. 注册 Provider .-> Nacos

    APISIX -. 查询并刷新实例列表 .-> Nacos

    APISIX -->|Dubbo RPC| Order1
    APISIX -->|Dubbo RPC| Order2

这正是早期 APISIX + Dubbo + Nacos 实践要解决的问题:APISIX 一侧处理 HTTP/Dubbo 协议转换,同时通过 Nacos 动态获取 Dubbo Provider,而不是额外维护一层 HTTP Adapter。

需要特别区分两个概念:

Nacos 服务发现解决的是“调用谁”,HTTP→Dubbo 插件解决的是“怎么调用”。

仅仅让 APISIX 从 Nacos 找到 Dubbo Provider,并不代表 APISIX 自动具备 Dubbo RPC 调用能力。


三个组件分别负责什么

在开始配置之前,可以先把职责边界理清。

组件 核心职责 在订单场景中的作用
APISIX API Gateway、路由、鉴权、限流、负载均衡、协议转换 接收 /api/orders/** HTTP 请求
Nacos 服务注册、服务发现、实例管理 保存 order-service Provider 地址
Dubbo RPC 框架 APISIX 或其他微服务调用订单 RPC

Nacos 本身并不承载订单请求。

真实业务数据流是:

1
2
3
4
5
6
7
Client

APISIX

Dubbo Provider

Database

Nacos 更接近控制面:

1
2
3
4
5
Dubbo Provider ──注册──> Nacos

服务发现

APISIX

Nacos 官方关于 APISIX 服务发现的实践也是这个模型:服务实例注册到 Nacos,APISIX 根据 Nacos 返回的实例列表动态构建 Upstream。


本文的订单业务场景

假设系统存在一个订单查询 RPC:

1
2
3
4
5
6
package com.example.order.api;

public interface OrderQueryService {

OrderView queryOrder(OrderQueryRequest request);
}

请求对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.example.order.api;

import java.io.Serializable;

public class OrderQueryRequest implements Serializable {

private Long orderId;

private Long userId;

public Long getOrderId() {
return orderId;
}

public void setOrderId(Long orderId) {
this.orderId = orderId;
}

public Long getUserId() {
return userId;
}

public void setUserId(Long userId) {
this.userId = userId;
}
}

返回对象:

1
2
3
4
5
6
7
8
9
10
11
12
package com.example.order.api;

import java.io.Serializable;

public class OrderView implements Serializable {

private Long orderId;
private String orderStatus;
private Long amount;

// getter / setter
}

内部 Dubbo RPC 是:

1
OrderQueryService.queryOrder(OrderQueryRequest)

对外则希望暴露:

1
2
POST /api/orders/query
Content-Type: application/json

请求:

1
2
3
4
{
"orderId": 10001,
"userId": 20001
}

因此 APISIX 需要完成:

1
2
3
POST /api/orders/query

OrderQueryService.queryOrder(...)

这里刻意采用单 Request DTO 参数,而不是:

1
queryOrder(Long orderId, Long userId, Integer type, String channel)

后面会看到,这种接口设计对于 HTTP→Dubbo 协议转换非常重要。


整体调用链

一次订单查询可以抽象成下面的流程。

sequenceDiagram
    participant O as order-service
    participant N as Nacos
    participant C as Client
    participant A as APISIX

    O->>N: 注册 OrderQueryService Provider
    A->>N: 周期刷新 Provider 实例
    N-->>A: IP + Dubbo Port

    C->>A: POST /api/orders/query
    A->>A: Route 匹配
    A->>A: 选择 Upstream
    A->>A: HTTP → Dubbo 编码
    A->>O: queryOrder(request)
    O-->>A: OrderView
    A-->>C: HTTP Response

注意请求阶段并不是:

1
Client -> APISIX -> Nacos -> Dubbo

而是:

1
Client -> APISIX -> Dubbo

APISIX 的 Nacos Discovery 模块按照配置周期刷新服务实例,因此不需要每次业务请求都实时查询 Nacos。当前 APISIX Nacos Discovery 配置提供 fetch_interval、连接超时、读取超时等参数。


环境与版本选择

早期 Nacos 官方案例使用的是 APISIX 2.x,2023 年 APISIX + Dubbo + Nacos 示例同样属于较早的 APISIX/Dubbo 组合。今天直接照搬旧配置,最容易遇到的并不是 Nacos,而是 Dubbo 3 注册模型和 Dubbo 序列化方式已经发生变化

当前 APISIX 文档已经进入 3.x 系列,Nacos Service Discovery、dubbo-proxyhttp-dubbo 仍然存在。

本文建议把版本兼容拆成两层考虑:

1
2
3
4
5
第一层
APISIX <-> Nacos

第二层
APISIX <-> Dubbo

APISIX 与 Nacos 的服务发现通常比较直接。

真正需要重点验证的是:

1
2
3
4
5
6
7
APISIX HTTP-Dubbo Plugin

Dubbo Protocol Version

Serialization

Provider

官方 Dubbo 3.3 Nacos 注册中心文档给出的示例组合中,Dubbo 3.3.0 对应推荐 Nacos 2.3.0,并兼容 Nacos 2.x。

生产环境不要简单理解为:

APISIX 3.x + Dubbo 3.x + Nacos 3.x,数字越大就一定越兼容。

协议转换层必须单独做兼容性测试。


第一步:创建 Dubbo Order Provider

Maven 依赖

以 Dubbo 3.3.x 为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<properties>
<java.version>17</java.version>
<dubbo.version>3.3.0</dubbo.version>
</properties>

<dependencies>

<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>${dubbo.version}</version>
</dependency>

<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-nacos-spring-boot-starter</artifactId>
<version>${dubbo.version}</version>
</dependency>

</dependencies>

这是当前 Dubbo Nacos 注册中心文档提供的 Spring Boot 依赖方式。

Provider 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.example.order.provider;

import com.example.order.api.OrderQueryRequest;
import com.example.order.api.OrderQueryService;
import com.example.order.api.OrderView;
import org.apache.dubbo.config.annotation.DubboService;

@DubboService(version = "1.0.0")
public class OrderQueryServiceImpl implements OrderQueryService {

@Override
public OrderView queryOrder(OrderQueryRequest request) {

// 示例中省略 Repository 查询逻辑。

OrderView result = new OrderView();
result.setOrderId(request.getOrderId());
result.setOrderStatus("PAID");
result.setAmount(9900L);

return result;
}
}

一个非常重要的 Dubbo 3 配置:register-mode

配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
spring:
application:
name: order-service

dubbo:
application:
name: order-service

protocol:
name: dubbo
port: 20880

registry:
address: nacos://127.0.0.1:8848
register-mode: all

这里为什么显式写:

1
register-mode: all

而不是使用 Dubbo 3 新项目经常推荐的:

1
register-mode: instance

这是整个 APISIX + Dubbo 3 整合里最容易忽略的问题。


Dubbo 3 的服务注册模型发生了什么变化

Dubbo 2 时代主要采用接口级服务发现。

例如:

1
com.example.order.api.OrderQueryService

在 Nacos 中会形成类似:

1
providers:com.example.order.api.OrderQueryService:1.0.0:

这个名字包含:

1
2
3
4
5
6
7
providers:
+
interface
+
version
+
group

完整规则可以理解为:

1
providers:${interface}:${version}:${group}

如果没有 Dubbo Group:

1
2
3
providers:com.example.order.api.OrderQueryService:1.0.0:

最后的冒号仍存在

Dubbo 官方排障资料也明确给出了这种接口级 Nacos Service Name 格式。

但 Dubbo 3 引入了应用级服务发现:

1
order-service

服务发现的中心开始从:

1
interface

逐渐转向:

1
application

Dubbo 3 提供:

1
2
3
dubbo.registry.register-mode=interface
dubbo.registry.register-mode=instance
dubbo.registry.register-mode=all

其中:

模式 含义
interface 只进行接口级注册
instance 只进行应用级注册
all 两种方式都注册

对于新的 Dubbo 3 Consumer,官方更推荐逐步转向应用级服务发现;但 APISIX 并不是一个完整的 Dubbo 3 Consumer,它通常没有 Dubbo MetadataService、应用映射等完整服务发现过程。

而 APISIX + Nacos 的传统整合方法依赖的恰恰是:

1
providers:interface:version:group

所以本文显式使用:

1
register-mode: all

目的不是说 all 永远是 Dubbo 3 的最佳配置,而是:

为 APISIX 保留接口级 Nacos 注册信息,同时让 Dubbo 3 内部 Consumer 仍然能够使用应用级服务发现。

这是一种兼容策略。


验证订单 Dubbo Provider 是否已经注册

启动:

1
java -jar order-service.jar

然后可以直接查询 Nacos Open API:

1
2
3
4
SERVICE_NAME='providers:com.example.order.api.OrderQueryService:1.0.0:'

curl -G 'http://127.0.0.1:8848/nacos/v1/ns/instance/list' \
--data-urlencode "serviceName=${SERVICE_NAME}"

如果注册正常,应能够看到类似实例信息:

1
2
3
4
5
6
7
8
9
10
{
"name": "providers:com.example.order.api.OrderQueryService:1.0.0:",
"hosts": [
{
"ip": "192.168.10.21",
"port": 20880,
"healthy": true
}
]
}

这里真正重要的不是 JSON 长什么样,而是确认三个字段:

1
2
3
serviceName
IP
Dubbo Port

其中端口必须是:

1
20880

这样的 Dubbo 协议端口,而不是 Spring Boot 的:

1
8080

第二步:让 APISIX 接入 Nacos

APISIX 的 Nacos Service Discovery 需要在 conf/config.yaml 中配置。

当前 APISIX 文档采用类似下面的配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
discovery:
nacos:
host:
- "http://127.0.0.1:8848"

prefix: "/nacos/v1/"

fetch_interval: 30

weight: 100

timeout:
connect: 2000
send: 2000
read: 5000

最简单的配置实际上只需要:

1
2
3
4
discovery:
nacos:
host:
- "http://127.0.0.1:8848"

不过生产环境建议显式配置超时。

fetch_interval

例如:

1
fetch_interval: 30

表示 APISIX 周期刷新 Nacos 服务信息。

开发环境如果希望快速观察实例上下线,可以适当缩短:

1
fetch_interval: 5

但刷新频率越高,对注册中心的查询也越频繁。

没有必要为了追求“秒级感知”直接写:

1
fetch_interval: 1

特别是网关节点很多时:

1
2
3
4
5
10 个 APISIX
×
大量 Service
×
1 秒刷新

会无意义地增加 Nacos 压力。


第三步:让 APISIX 根据 Nacos 创建动态 Upstream

普通 APISIX Upstream 可能这样配置:

1
2
3
4
5
6
7
{
"type": "roundrobin",
"nodes": {
"192.168.10.21:20880": 1,
"192.168.10.22:20880": 1
}
}

问题是 Provider 扩缩容后必须修改 APISIX。

接入 Nacos 后,则变成:

1
2
3
4
5
{
"type": "roundrobin",
"service_name": "providers:com.example.order.api.OrderQueryService:1.0.0:",
"discovery_type": "nacos"
}

这里不再配置:

1
"nodes"

APISIX 根据:

1
2
3
service_name
+
discovery_type=nacos

动态查询 Provider。

这正是 Nacos 官方 APISIX 服务发现示例采用的方式。


到这里还不能调用 Dubbo

现在 APISIX 已经知道:

1
2
192.168.10.21:20880
192.168.10.22:20880

但客户端发送的仍然是:

1
2
POST /api/orders/query HTTP/1.1
Content-Type: application/json

而:

1
20880

监听的是 Dubbo RPC Protocol。

所以:

1
HTTP Request

不能直接扔给:

1
Dubbo Port

还必须加入:

1
HTTP → Dubbo

协议转换。

APISIX 当前仍提供与 Dubbo 相关的 dubbo-proxyhttp-dubbo 插件。


dubbo-proxy 与 http-dubbo 应该怎么选

当前主要可以看到两种路径。

插件 特点 更适合
dubbo-proxy 较早的 APISIX Dubbo 方案 兼容已有 APISIX/Dubbo 部署
http-dubbo APISIX 自己构造 Dubbo 请求 需要显式映射 Dubbo 方法签名

早期 APISIX + Nacos + Dubbo Demo 使用的是:

1
dubbo-proxy

并且组合了:

1
2
"service_name": "providers:...",
"discovery_type": "nacos"

这是原始实践已经验证过的整合结构。

不过当前 dubbo-proxy 官方文档明确提醒:如果基于 OpenResty,需要使用包含 Dubbo 支持的构建。

http-dubbo 则直接在 APISIX Lua 插件中构造 Dubbo Protocol 数据,并连接 APISIX 已经选择好的 Upstream 节点。当前插件源码可以看到,它最终读取:

1
2
ctx.picked_server.host
ctx.picked_server.port

建立 TCP 连接。由此可以把:

1
2
3
4
5
Nacos Discovery

APISIX picked_server

http-dubbo

组合起来。这里属于根据当前插件实现得出的工程性判断,因此部署前必须针对所使用的 APISIX/Dubbo 版本进行真实冒烟验证,而不能只检查配置是否能保存。

下面以 http-dubbo 说明完整调用过程。


第四步:创建订单查询 Route

假设:

1
2
3
4
5
6
7
8
9
10
11
Dubbo Interface:
com.example.order.api.OrderQueryService

Version:
1.0.0

Method:
queryOrder

Argument:
com.example.order.api.OrderQueryRequest

对应 JVM Method Descriptor 中的参数描述为:

1
Lcom/example/order/api/OrderQueryRequest;

创建 APISIX Route:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
export APISIX_ADMIN_KEY='replace-with-your-admin-key'

curl -X PUT \
'http://127.0.0.1:9180/apisix/admin/routes/order-query' \
-H "X-API-KEY: ${APISIX_ADMIN_KEY}" \
-H 'Content-Type: application/json' \
-d '
{
"uri": "/api/orders/query",
"methods": [
"POST"
],
"plugins": {
"http-dubbo": {
"service_name": "com.example.order.api.OrderQueryService",
"service_version": "1.0.0",
"method": "queryOrder",
"params_type_desc": "Lcom/example/order/api/OrderQueryRequest;",
"serialized": true,
"connect_timeout": 2000,
"send_timeout": 3000,
"read_timeout": 3000
}
},
"upstream": {
"type": "roundrobin",
"service_name": "providers:com.example.order.api.OrderQueryService:1.0.0:",
"discovery_type": "nacos"
}
}'

这里实际上同时完成了两件事情。

Upstream 负责找 Provider

1
2
3
4
5
"upstream": {
"type": "roundrobin",
"service_name": "providers:com.example.order.api.OrderQueryService:1.0.0:",
"discovery_type": "nacos"
}

解决:

1
OrderQueryService 在哪?

http-dubbo 负责调用接口

1
2
3
4
5
6
"http-dubbo": {
"service_name": "com.example.order.api.OrderQueryService",
"service_version": "1.0.0",
"method": "queryOrder",
"params_type_desc": "Lcom/example/order/api/OrderQueryRequest;"
}

解决:

1
2
3
4
到底调用哪个 Dubbo 接口?
哪个版本?
哪个方法?
参数类型是什么?

把它们组合起来,就是:

1
2
3
4
5
6
7
8
9
Nacos

找到 192.168.10.21:20880

http-dubbo

OrderQueryService

queryOrder(OrderQueryRequest)

params_type_desc 是什么

http-dubbo 并不是只根据 JSON 猜 Java 类型。

例如:

1
OrderView queryOrder(OrderQueryRequest request);

Dubbo 方法参数是:

1
com.example.order.api.OrderQueryRequest

它的 JVM Descriptor 是:

1
Lcom/example/order/api/OrderQueryRequest;

所以配置:

1
"params_type_desc": "Lcom/example/order/api/OrderQueryRequest;"

官方文档也建议使用 Dubbo 的:

1
ReflectUtils.getDesc(...)

获取准确的类型描述,避免人工拼错。

例如:

1
2
3
4
5
6
7
8
9
10
import org.apache.dubbo.common.utils.ReflectUtils;

public class DubboDescriptorTest {

public static void main(String[] args) {
System.out.println(
ReflectUtils.getDesc(OrderQueryRequest.class)
);
}
}

输出:

1
Lcom/example/order/api/OrderQueryRequest;

对于:

1
Long

则是:

1
Ljava/lang/Long;

这也是为什么直接改 Java 方法签名之后,APISIX Route 也可能需要同步更新。


为什么推荐一个 Request DTO,而不是多个参数

假设 Dubbo 接口设计成:

1
2
3
4
5
6
OrderView queryOrder(
Long orderId,
Long userId,
Integer tenantId,
String channel
);

那么 HTTP→Dubbo 层必须同时正确处理:

1
2
3
4
5
参数顺序
参数数量
Java 类型
JSON 序列化
Dubbo 类型描述

http-dubbo 的预序列化调用方式要求每个参数按照 Dubbo 方法参数顺序组织;当前实现本身也是围绕 Dubbo 参数序列逐个构造请求。

因此更推荐:

1
OrderView queryOrder(OrderQueryRequest request);

把:

1
2
3
4
orderId
userId
tenantId
channel

封装到:

1
OrderQueryRequest

这样协议边界稳定得多:

1
2
3
4
5
HTTP JSON Object

OrderQueryRequest

Dubbo Method

这不仅方便 APISIX,也让后续接口扩展更容易保持兼容。


调用订单 API

发送:

1
2
3
4
5
6
7
curl -X POST \
'http://127.0.0.1:9080/api/orders/query' \
-H 'Content-Type: application/json' \
-d '{
"orderId": 10001,
"userId": 20001
}'

整个链路是:

1
2
3
4
5
6
7
8
9
10
11
12
13
curl
↓ HTTP
APISIX Route

Nacos Upstream Discovery

选择 order-service Provider

HTTP → Dubbo

OrderQueryService.queryOrder(...)

order-service

业务返回内容取决于 Provider 实际实现。


Dubbo 3.x 下必须单独关注序列化兼容

这里是目前最容易出现“配置全部正确,但就是调用失败”的地方。

当前 http-dubbo 文档明确涉及 Dubbo 序列化约束,而插件实现自身也存在特定 Dubbo/Fastjson 编码逻辑。

与此同时,Dubbo 3.3 当前默认支持的主要序列化已经是:

1
2
3
Hessian2
Fastjson2
Protobuf

而不是早期的 legacy Fastjson;Dubbo 3.3 的默认序列化策略本身也发生过调整。

因此不能简单认为:

1
2
3
4
5
APISIX 支持 http-dubbo
+
Dubbo 3.3 正常运行
=
一定能够互相调用

生产环境必须做下面这个最小测试:

1
2
3
4
5
6
7
8
9
10
11
单 Provider

单接口

单 DTO 参数

固定版本

真实 APISIX

真实 HTTP 请求

至少验证:

1
2
3
4
5
6
7
8
9
请求序列化
响应反序列化
异常响应
null
Long 精度
中文字符串
复杂 DTO
List
Map

如果 Provider 使用的 Dubbo 版本、Serialization 与 APISIX 插件不兼容,不建议为了让网关能调用而强行降级到已经不推荐的旧序列化方式。

这种情况下,更合理的选择通常是:

1
2
3
4
5
6
7
8
9
10
11
方案 A
验证 dubbo-proxy 对现有运行时是否兼容

方案 B
服务同时暴露 HTTP / Triple / REST 能力

方案 C
保留轻量 API Adapter / BFF

方案 D
等待或升级到支持目标 Dubbo 编解码方式的协议转换实现

不要把“少一层 Adapter”看得比协议安全和稳定性还重要。


使用 dubbo-proxy 的兼容方案

如果已有 APISIX 环境具备 dubbo-proxy 所要求的 Dubbo Runtime,也可以继续采用早期实践中的方式。

订单 Route 可以写成类似:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"uri": "/api/orders/query",
"methods": [
"POST"
],
"plugins": {
"dubbo-proxy": {
"service_name": "com.example.order.api.OrderQueryService",
"service_version": "1.0.0",
"method": "queryOrder"
}
},
"upstream": {
"type": "roundrobin",
"service_name": "providers:com.example.order.api.OrderQueryService:1.0.0:",
"discovery_type": "nacos"
}
}

这个结构与早期 APISIX + Dubbo + Nacos Demo 基本一致:upstream 通过 Nacos 定位 Provider,dubbo-proxy 负责 Dubbo 调用。

不过当前 dubbo-proxy 官方文档明确注明了 OpenResty 构建要求,因此不能默认任意 APISIX 镜像都具备完整运行条件。


扩容第二个订单 Provider

现在启动两个订单实例:

1
2
order-service-1
192.168.10.21:20880
1
2
order-service-2
192.168.10.22:20880

或者本地测试:

1
2
3
java -jar order-service.jar \
--server.port=18080 \
--dubbo.protocol.port=20880

另一个:

1
2
3
java -jar order-service.jar \
--server.port=18081 \
--dubbo.protocol.port=20881

Nacos 中最终形成:

1
2
3
4
5
providers:com.example.order.api.OrderQueryService:1.0.0:
|
+-- 127.0.0.1:20880
|
+-- 127.0.0.1:20881

APISIX Route 不需要改成:

1
2
3
4
"nodes": {
"127.0.0.1:20880": 1,
"127.0.0.1:20881": 1
}

仍然保持:

1
2
3
4
5
{
"service_name": "providers:com.example.order.api.OrderQueryService:1.0.0:",
"discovery_type": "nacos",
"type": "roundrobin"
}

APISIX 刷新实例列表以后,就可以根据 Upstream 策略选择 Provider。

这才是:

1
APISIX + Nacos

相比静态 Upstream 真正带来的价值。


Provider 下线时发生什么

假设:

1
2
order-service-1
order-service-2

同时在线。

APISIX 当前缓存:

1
2
20880
20881

此时停止:

1
order-service-1

Dubbo Registry 更新 Nacos 实例状态,之后 APISIX 在下一轮服务发现刷新时获取新的 Provider 列表:

1
20881

随后新请求就不会继续依赖已经被移除的静态配置。

因此这里的关键参数:

1
fetch_interval: 30

实际上决定了 APISIX 对注册中心变化的轮询刷新节奏。

它与应用自己的:

1
2
3
Dubbo heartbeat
Nacos health state
Provider unregister

是不同层面的机制。


Nacos Namespace 与 Group 怎么处理

生产环境一般至少存在:

1
2
3
4
dev
test
staging
prod

不应该让 APISIX 查询:

1
所有环境

APISIX Nacos Discovery 支持:

1
2
3
4
"discovery_args": {
"namespace_id": "YOUR_NAMESPACE_ID",
"group_name": "DEFAULT_GROUP"
}

当前 APISIX 文档中:

1
namespace_id

默认对应 public Namespace,

1
group_name

默认是:

1
DEFAULT_GROUP

例如生产订单 Route:

1
2
3
4
5
6
7
8
9
10
11
{
"upstream": {
"type": "roundrobin",
"service_name": "providers:com.example.order.api.OrderQueryService:1.0.0:",
"discovery_type": "nacos",
"discovery_args": {
"namespace_id": "YOUR_PROD_NAMESPACE_ID",
"group_name": "ORDER_RPC"
}
}
}

注册端必须进入同一个:

1
2
3
Namespace
+
Nacos Group

否则现象通常是:

1
2
3
Dubbo Provider 明明在 Nacos 页面里

APISIX 却提示没有 Upstream

因为它们实际上查询的不是同一个命名空间。


不要混淆 Nacos Group 与 Dubbo Service Group

这里还有两个名字非常容易混。

Nacos Group

例如:

1
2
3
DEFAULT_GROUP
ORDER_RPC
PAYMENT_RPC

用于 Nacos 服务分类。

APISIX 对应:

1
"group_name": "ORDER_RPC"

Dubbo Group

Dubbo Interface 本身还支持:

1
2
3
4
@DubboService(
version = "1.0.0",
group = "mall"
)

此时 Service Key 会发生变化。

例如:

1
providers:com.example.order.api.OrderQueryService:1.0.0:mall

它和 Nacos 的:

1
ORDER_RPC

不是一回事。

更麻烦的是,当前 http-dubbo 插件公开的配置 Schema 中没有与 Dubbo Service Group 完全对等的配置字段。

因此如果准备直接通过 APISIX 调 Dubbo,建议网关暴露的 Dubbo Facade 尽量避免再使用复杂的 Dubbo Group 路由。

版本隔离优先使用:

1
version = "1.0.0"

会更容易控制。


订单接口版本升级怎么做

假设当前:

1
OrderQueryService:1.0.0

准备上线:

1
OrderQueryService:2.0.0

Nacos 中可以同时存在:

1
providers:com.example.order.api.OrderQueryService:1.0.0:

以及:

1
providers:com.example.order.api.OrderQueryService:2.0.0:

APISIX 可以分别配置:

1
2
3
/api/v1/orders/query

Dubbo 1.0.0
1
2
3
/api/v2/orders/query

Dubbo 2.0.0

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"uri": "/api/v2/orders/query",
"plugins": {
"http-dubbo": {
"service_name": "com.example.order.api.OrderQueryService",
"service_version": "2.0.0",
"method": "queryOrder",
"params_type_desc": "Lcom/example/order/api/v2/OrderQueryRequest;",
"serialized": true
}
},
"upstream": {
"type": "roundrobin",
"service_name": "providers:com.example.order.api.OrderQueryService:2.0.0:",
"discovery_type": "nacos"
}
}

这里尤其要保证两个地方同时升级:

1
http-dubbo.service_version

以及:

1
upstream.service_name

如果出现:

1
Plugin 调 2.0.0

但:

1
Nacos 找到 1.0.0 Provider

最终通常只能得到非常难看的 RPC 异常。


下单接口不能照搬查询接口的重试策略

订单查询:

1
queryOrder

通常属于只读请求。

而:

1
createOrder

属于写操作。

假设 APISIX 请求 Provider 后发生:

1
2
3
4
5
6
7
Provider 已经创建订单

响应返回途中网络断开

Gateway 认为失败

重新请求另外一个 Provider

就可能出现重复下单。

因此订单写接口至少需要业务级幂等键:

1
2
3
4
5
6
{
"requestId": "01JXXXXXXXXXXXX",
"userId": 20001,
"skuId": 30001,
"quantity": 1
}

服务端:

1
2
3
4
5
requestId

幂等检查

创建订单

而不是寄希望于:

1
Gateway Retry

自动解决业务一致性。

对订单系统来说,应该明确:

1
2
3
4
5
GET / Query
可以根据实际情况有限重试

Create / Pay / Refund
不能在不知道执行结果的情况下盲目重试

API Gateway 的网络可靠性机制不能替代业务幂等。


为什么不建议把所有 Dubbo 接口自动暴露出去

早期 APISIX + Dubbo + Nacos Demo 已经暴露出一个现实问题:

1
2
一个 Dubbo Service
可能有几十个 Method

如果全部手工创建 Route:

1
2
3
4
5
Service A.method1
Service A.method2
Service A.method3
Service B.method1
...

维护成本会快速上升。

很自然会想到:

1
2
3
4
5
扫描 Nacos

获取所有 providers

自动生成 APISIX Route

技术上当然可以做。

但生产环境千万不要变成:

注册到 Nacos 的 Dubbo 接口自动全部开放公网。

因为内部 RPC 与外部 API 的安全边界完全不同。

例如:

1
OrderService.cancelOrder(...)

内部可能默认:

1
2
3
4
调用方已经经过 Dubbo 鉴权
调用方是可信服务
userId 从上下文取得
tenantId 从内部 Attachment 获取

如果机械暴露成:

1
POST /api/order/cancel

安全模型就被绕过了。

更合理的方式是维护白名单式 API Mapping

1
2
3
4
5
6
7
8
9
10
apis:
- path: /api/orders/query
service: com.example.order.api.OrderQueryService
version: 1.0.0
method: queryOrder

- path: /api/orders/create
service: com.example.order.api.OrderCommandService
version: 1.0.0
method: createOrder

然后由 CI/CD:

1
2
3
4
5
6
7
API Mapping

生成 APISIX Route

Review

Deploy

而不是:

1
2
3
Nacos Service

无脑暴露公网

一个更合理的订单服务接口划分

如果确实准备让 APISIX 直接连接 Dubbo,可以专门设计 Gateway Facade。

不要直接暴露内部领域 Service:

1
2
3
OrderDomainService
OrderRepositoryService
OrderWorkflowService

而是提供:

1
2
3
4
5
6
public interface OrderGatewayFacade {

OrderView queryOrder(OrderQueryRequest request);

CreateOrderResult createOrder(CreateOrderRequest request);
}

形成:

1
2
3
4
5
6
7
8
9
Public HTTP API

APISIX

OrderGatewayFacade

Application Service

Domain

这样:

1
HTTP API Contract

与:

1
内部 Domain API

之间仍然保留一层清晰的业务边界。

APISIX 消掉的是:

1
纯协议 Adapter

而不是让:

1
2
3
外部 API
=
内部所有 RPC

Docker 和 Kubernetes 环境最常见的问题:注册 IP 不可达

本地测试时:

1
2
3
APISIX
Nacos
Dubbo

可能都在同一台机器。

到了 Docker 或 Kubernetes 里,经常出现:

1
2
3
Nacos 中 Provider 是 healthy
APISIX 也能查询到
调用却 502

这时第一件事情不是查 APISIX Route,而是看 Nacos 返回的 IP。

例如:

1
2
3
4
{
"ip": "172.18.0.5",
"port": 20880
}

然后从 APISIX 所在网络测试:

1
nc -vz 172.18.0.5 20880

如果不通:

1
Nacos Discovery

实际上已经成功了。

失败的是:

1
2
3
APISIX

Provider 网络

Docker

应确保:

1
2
3
APISIX
Nacos
order-service

位于可以互通的 Docker Network。

Kubernetes

如果 Dubbo Provider 注册的是 Pod IP,则 APISIX 必须能够路由到对应 Pod CIDR。

如果:

1
APISIX 在 K8s 外

而 Provider 注册:

1
10.244.x.x

这样的 Pod IP,外部 APISIX 很可能根本无法访问。

因此整个架构设计必须确保:

1
2
3
Nacos 注册地址
=
APISIX 实际可达地址

而不是只看 Nacos 控制台显示:

1
UP

就认为网络没有问题。


Kubernetes 中推荐的网络关系

如果三个组件都部署在集群中:

flowchart TB
    Ingress[External LoadBalancer]
    APISIX[APISIX Pods]
    Nacos[Nacos Cluster]

    O1[Order Pod 1<br/>Dubbo 20880]
    O2[Order Pod 2<br/>Dubbo 20880]
    O3[Order Pod 3<br/>Dubbo 20880]

    Ingress --> APISIX

    APISIX -. Discovery .-> Nacos

    O1 -. Register .-> Nacos
    O2 -. Register .-> Nacos
    O3 -. Register .-> Nacos

    APISIX --> O1
    APISIX --> O2
    APISIX --> O3

这时候:

1
Nacos

负责 Dubbo Endpoint 的服务发现,

而不是让 APISIX 再通过:

1
Kubernetes Service

做第二层负载均衡。

否则可能形成:

1
2
3
4
5
APISIX roundrobin

K8s Service roundrobin

Dubbo Pod

两层负载均衡叠在一起,反而失去了 APISIX 直接获取 Provider 实例的意义。


APISIX Admin API 不要暴露公网

教程为了方便通常直接:

1
curl http://127.0.0.1:9180/apisix/admin/routes/...

但生产环境的 Admin API 属于控制面。

能够访问它,就可能:

1
2
3
4
5
创建 Route
修改 Upstream
关闭鉴权
修改插件
转发流量

所以至少应该:

1
2
3
4
5
6
7
只允许管理网络访问
+
强 Admin Key
+
IP Allow List
+
Secret / 环境变量管理

不要复制旧教程里的默认 Admin Key。

当前 APISIX 配置中,Admin Key 属于 deployment.admin.admin_key 等管理面配置,应按照当前版本安全配置管理,而不是继续沿用早期 Demo 的默认密钥。

例如:

1
export APISIX_ADMIN_KEY='your-production-secret'

然后:

1
2
3
curl \
-H "X-API-KEY: ${APISIX_ADMIN_KEY}" \
...

而不是把密钥直接提交到:

1
2
3
4
Git
Dockerfile
Helm values
Blog

APISIX 与 Nacos 的账号也不要直接硬编码

开发环境可能:

1
2
3
4
discovery:
nacos:
host:
- "http://127.0.0.1:8848"

当前 APISIX Nacos Discovery 也支持包含认证信息的 Host 配置形式。

生产环境仍然更建议:

1
2
3
4
5
Secret

Environment Variable

APISIX Config

不要:

1
2
host:
- "http://nacos:nacos123@nacos-prod:8848"

然后把整个文件提交到仓库。


可观测性应该怎么看

这条调用链至少涉及:

1
2
3
4
5
Client
APISIX
Nacos
Dubbo Provider
Database

排查问题时,建议把监控拆成三层。

Gateway

关注:

1
2
3
4
5
6
HTTP Status
Latency
Route
Upstream IP
Upstream Port
Request ID

例如:

1
2
3
4
5
request_id=xxx
route=order-query
upstream=10.10.1.23:20880
status=200
cost=23ms

Service Discovery

关注:

1
2
3
4
5
6
7
8
Nacos Service Name
Instance Count
Healthy
Enabled
IP
Port
Namespace
Group

Dubbo

关注:

1
2
3
4
5
6
7
8
Interface
Method
Version
Provider
RT
Exception
Timeout
Concurrent Requests

最终要能把:

1
HTTP Request

定位到:

1
2
3
4
5
哪一个 APISIX

哪一个 Provider

哪个 Dubbo Method

否则一旦 APISIX 返回:

1
502

很容易陷入:

1
2
3
4
5
6
到底是网关?
Nacos?
Dubbo?
Provider?
网络?
序列化?

逐个猜的状态。


常见故障排查

现象 重点检查
APISIX 404 Route URI、Method 是否匹配
No upstream node Nacos Service Name、Namespace、Group
Nacos 有实例但 APISIX 找不到 discovery_typeservice_namediscovery_args
Dubbo 3 中只有 application service register-mode 是否为 instance
Provider 连接失败 Nacos 注册 IP、Dubbo Port、网络、防火墙
502 / Connection refused APISIX 到 Provider 的 TCP 20880 是否可达
Service not found Interface、Version、Group 是否一致
参数反序列化失败 params_type_desc、请求 Body、序列化方式
Dubbo 3.3 调用异常 APISIX 插件与 Dubbo Serialization 兼容性
扩容后迟迟没有流量 Nacos 实例状态与 fetch_interval
Admin API 401/403 Admin Key、管理面访问控制

No upstream node:先检查 Service Name

最典型错误:

1
"service_name": "order-service"

但实际使用的是接口级 Dubbo 注册:

1
providers:com.example.order.api.OrderQueryService:1.0.0:

那么 APISIX 自然查不到。

检查:

1
2
3
4
curl -G \
'http://127.0.0.1:8848/nacos/v1/ns/instance/list' \
--data-urlencode \
'serviceName=providers:com.example.order.api.OrderQueryService:1.0.0:'

如果这里有数据,而:

1
order-service

没有对应 Provider,就说明使用的是:

1
interface-level discovery

Dubbo 3 Provider 根本没有 providers:* 怎么办

如果 Nacos 中只看到:

1
order-service

看不到:

1
providers:com.example.order.api.OrderQueryService:1.0.0:

很可能当前配置是:

1
register-mode: instance

对于本文这种 APISIX 直接通过接口名称查询 Nacos 的方案,应改为:

1
register-mode: all

或者在只需要兼容接口发现的场景:

1
register-mode: interface

但一般 Dubbo 3 系统中更推荐:

1
register-mode: all

作为迁移期间的兼容方案,而不是彻底退回接口级发现。Dubbo 官方目前仍明确区分 interfaceinstanceall 三种模式。


Nacos 有实例,但是 20880 连不上

先获取 Provider:

1
10.244.2.31:20880

在 APISIX 节点:

1
nc -vz 10.244.2.31 20880

如果:

1
Connection timed out

不要再继续研究:

1
params_type_desc

因为请求压根没有进入 Dubbo。

检查:

1
2
3
4
5
6
7
Docker Network
Kubernetes CNI
Security Group
iptables
NetworkPolicy
Dubbo bind IP
Dubbo registry IP

排障一定要从 OSI 更低层开始。


Dubbo 能连上但反序列化失败

如果日志出现类似:

1
2
3
deserialize error
serialization error
argument type mismatch

重点看:

1
2
3
4
5
6
service_name
service_version
method
params_type_desc
serialized
Dubbo serialization

例如 Java:

1
OrderView queryOrder(OrderQueryRequest request);

配置却写:

1
"params_type_desc": "Ljava/lang/Long;"

那协议层一定无法正常调用。

另外对于 Dubbo 3.x,要把:

1
Serialization Compatibility

放到排障优先级非常高的位置,而不是默认认为“都是 Dubbo 协议,所以一定兼容”。当前 APISIX http-dubbo 实现和 Dubbo 3.3 默认序列化并非一个可以忽略版本差异的问题。


旧教程与当前实践的主要差异

项目 早期 APISIX + Dubbo + Nacos 示例 当前实践需要补充的内容
APISIX 2.x 以当前 3.x 文档为准
Nacos Discovery service_name + discovery_type 核心模型仍然适用
Dubbo 注册 接口级 Provider Dubbo 3 还存在应用级注册
Nacos Service providers:interface:version:group 使用 register-mode=all/interface 才能稳定保留
Dubbo 插件 dubbo-proxy 当前还可以评估 http-dubbo
序列化 老版本通常更简单 Dubbo 3.x 必须检查 Serialization
Admin API Demo 默认 Key 强密钥、私网、Secret 管理
Route 手工配置 可以 CI/CD 自动生成,但必须白名单控制

Nacos 服务发现本身的核心配置模型这些年变化并不大:

1
2
3
4
{
"service_name": "...",
"discovery_type": "nacos"
}

真正变化最大的,是:

1
2
3
4
5
Dubbo Registry Model
+
Dubbo Serialization
+
APISIX Dubbo Plugin Runtime

因此迁移旧教程时,重点不应该只是把:

1
APISIX 2.x

机械替换成:

1
APISIX 3.x

而是重新验证整个协议边界。


哪些场景特别适合这种架构

比较适合:

1
2
3
HTTP API

基本 1:1 对应 Dubbo Facade

例如:

1
2
3
4
查询订单
查询商品
查询库存
查询用户基础信息

这些接口:

1
2
3
编排逻辑少
协议转换简单
DTO 稳定

APISIX 直接调用 Dubbo 可以有效减少纯粹的 HTTP Adapter。


哪些场景不建议直接让 APISIX 调 Dubbo

例如首页接口:

1
GET /api/home

背后需要:

1
2
3
4
5
6
7
8
9
User
+
Product
+
Promotion
+
Recommendation
+
Inventory

如果试图全部放到 APISIX:

1
2
3
4
5
6
7
8
9
Gateway

调用 5 个 Dubbo

聚合

降级

业务判断

API Gateway 很快就会变成:

1
Application Service

这不是一个好的边界。

这类场景依然适合:

1
2
3
4
5
6
7
Client

APISIX

BFF / API Aggregation Service

Dubbo

同样,订单提交可能需要:

1
2
3
4
5
6
7
创建订单
锁库存
优惠计算
风控
支付预处理
消息
分布式事务

这些业务编排也应该留在:

1
Order Application Service

而不是塞进 APISIX 插件。


推荐的生产架构

如果确实需要 APISIX + Nacos + Dubbo,比较稳妥的方式是:

flowchart LR
    Client[Client]

    APISIX[APISIX]

    Nacos[Nacos]

    GatewayFacade[OrderGatewayFacade]
    Application[Order Application Service]
    Domain[Order Domain]
    DB[(Order DB)]

    Client -->|HTTP| APISIX

    APISIX -. Nacos Discovery .-> Nacos
    GatewayFacade -. Register .-> Nacos

    APISIX -->|Dubbo| GatewayFacade
    GatewayFacade --> Application
    Application --> Domain
    Domain --> DB

也就是说:

1
APISIX

只解决:

1
2
3
4
5
6
API Gateway
Routing
Security
Traffic Governance
Service Discovery
Protocol Conversion

订单服务继续解决:

1
2
3
4
5
6
Business Logic
Transaction
Idempotency
Domain Rule
Workflow
Persistence

这样不会因为使用 APISIX 直连 Dubbo,就把网关变成业务系统。


推荐的配置管理方式

不要在大量 Route 中手工重复:

1
providers:com.example.order.api.OrderQueryService:1.0.0:

可以抽象成 API Mapping:

1
2
3
4
5
6
7
8
9
10
11
12
13
service:
interface: com.example.order.api.OrderQueryService
version: 1.0.0
nacos:
namespace: ${NACOS_NAMESPACE_ID}
group: DEFAULT_GROUP

apis:
- name: query-order
path: /api/v1/orders/query
method: POST
dubbo-method: queryOrder
params-type-desc: Lcom/example/order/api/OrderQueryRequest;

CI/CD 再生成 APISIX Route。

这样:

1
2
3
4
5
6
7
Git

Review

API Contract

APISIX Admin API

而不是:

1
2
3
4
5
工程师登录服务器

手敲 curl

没人知道现在 Route 是什么

生产环境真正重要的是:

1
Route As Code

而不只是“能不能调通”。


一份实用的上线检查清单

上线前至少确认:

  • Dubbo Provider 已正确注册到目标 Nacos Namespace。
  • Nacos 中存在正确的 providers:interface:version:group Service。
  • Dubbo 3 使用 register-mode=all 或兼容的接口注册模式。
  • APISIX 能访问 Nacos。
  • APISIX 能从自身网络直接连接 Provider Dubbo Port。
  • service_name 与 Nacos 中名称完全一致。
  • Dubbo Interface、Version、Method 完全一致。
  • params_type_desc 已根据真实 Java 类型生成。
  • HTTP→Dubbo 插件与目标 Dubbo 版本的 Serialization 已经过真实测试。
  • 写操作具有业务幂等机制。
  • APISIX Admin API 不暴露公网。
  • Admin Key 与 Nacos 凭证没有硬编码进 Git。
  • Route 通过 Git/CI/CD 管理,而不是人工维护。
  • API 只暴露经过审核的 Dubbo Facade。
  • APISIX、Dubbo、Nacos 都具有可关联的日志和监控。

总结

APISIX + Nacos + Dubbo 的核心并不只是把三个中间件配置到一起,而是组合三种不同能力:

1
2
3
4
5
6
7
8
9
10
11
APISIX
=
流量入口

Nacos
=
动态服务地址

Dubbo
=
RPC 通信

完整链路可以概括为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Dubbo Provider

注册 Nacos

APISIX

根据 Interface Service Name
从 Nacos 获取 Provider

HTTP Request

APISIX

HTTP → Dubbo

Order Dubbo Provider

真正需要记住三个工程细节。

第一,服务发现与协议转换是两回事。Nacos 只能告诉 APISIX Provider 在哪里,不能自动把 HTTP 请求变成 Dubbo RPC。

第二,Dubbo 3 的应用级服务发现改变了注册模型。如果 APISIX 仍然按 providers:interface:version:group 查询 Provider,就应该显式保留接口级注册,例如使用 register-mode: all

第三,当前 Dubbo 3 的序列化体系与早期 APISIX Dubbo Demo 已经存在明显版本差异。Route 能创建成功、Nacos 能看到实例,都不能证明 HTTP→Dubbo 一定能够正确通信。协议插件、Dubbo 版本和 Serialization 必须作为一组进行兼容性测试。

对于订单系统,APISIX 直连 Dubbo 最适合查询类、简单 Command、稳定 Facade 等 1:1 API 映射场景。如果接口承担复杂业务编排、领域转换或跨服务聚合,保留 BFF 或 Adapter 并不是架构退步,反而能够保持 API 层和领域层之间清晰的边界。

参考资料

  • 《APISIX + Dubbo + Nacos 最佳实践》,介绍了通过 APISIX 完成 HTTP/Dubbo 协议转换,并利用 Nacos 动态发现 Dubbo Provider 的整体方案。
  • Nacos 官方《Apache APISIX 基于 Nacos 实现服务发现》,说明了 APISIX 使用 service_namediscovery_type=nacos 动态构建 Upstream 的方式。
  • Apache APISIX Nacos Service Discovery 当前文档,包括 discovery.nacosfetch_interval、Namespace 和 Group 等配置。
  • Apache APISIX http-dubbodubbo-proxy 文档及当前插件实现,用于理解 HTTP→Dubbo 协议转换、方法描述和运行时要求。
  • Apache Dubbo Nacos Registry 与服务发现资料,用于理解 Dubbo 3 的应用级/接口级注册模式及 Nacos Service Name。

APISIX + Nacos + Dubbo 整合实践:以订单服务为例
https://allendericdalexander.github.io/2026/08/13/java/apisix-nacos-dubbo/
作者
AtLuoFu
发布于
2026年8月13日
许可协议