前言
我的电商 AI 客服基于 Spring Cloud Alibaba + Dubbo + Nacos + Spring AI 。在接入阿里云百炼(DashScope)和向量数据库时,从启动到接口调用,出现了很多问题。这里按我的排错顺序,记录一下报错信息、可能的原因、最终查出的真相,以及背后涉及的问题。
一:服务启动失败,找不到 Provider
报错信息:
Failed to check the status of the service ... No provider available应用直接启动中止,退出代码为 1。
产生原因:
- 提供者(Provider)服务没启动。
- 注册到 Nacos 的 IP 或端口不对。
真实原因及解决:
Dubbo 默认开启了启动时检查(check=true)。消费者在启动时,会去 Nacos 里找这个接口的提供者,如果没找到或者提供者还没起来,它就直接报错,导致整个应用起不来。
解决:启动提供者或在配置里关掉启动检查。
dubbo: consumer: check: false问题总结:
- Q:Dubbo 的启动检查机制是什么?
- A:Dubbo 默认
check=true,在启动时会检查依赖的服务是否可用,如果不可用会抛出异常阻止 Spring 初始化。开发环境为了防止循环依赖或启动顺序问题,通常会将其设置为false。
二:改了 Nacos 配置后,项目找不到数据库了
报错信息:
Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.可能的原因:
- 本地没配数据库。
- Nacos 配置没拉取下来(配置文件内容有误)。
真实原因及解决:
我在 Nacos 里加 AI 的配置时,不小心破坏了 YAML 文件的缩进格式。YAML 对空格非常严格,缩进错了一点,spring.datasource就变成了别人的子节点,导致 Spring Boot 读不到。
解决:去 Nacos 把 YAML 缩进对齐,重新发布配置并重启。
三:接口报 500,发现是账号欠费(Arrearage)
报错信息:
NonTransientAiException: 400 - {"code":"Arrearage","message":"Access denied..."}可能的原因:
- 阿里云账号没钱了。
- API Key 填错了。
这个我当时看Error没看报错日志,再加上对Arrearage这个词不太敏感,找了半天才弄明白
经验总结:
同一个云平台的对话模型和向量模型,通常是共用同一个 API Key 的。配置多个 Key 时一定要仔细核对。
四:史诗级大坑——Dubbo 报错,异常变成了 HashMap
报错信息:
java.io.IOException: Response data error, expect Throwable, but get class java.util.HashMap Caused by: java.lang.ClassNotFoundException: org.springframework.ai.retry.NonTransientAiException可能的原因:
- 消费端(Consumer)和提供端(Provider)的接口版本不一致。
- 缺少依赖。
- Dubbo 序列化机制问题。
真实原因:
这是最大的一个坑。
- 提供者(Provider)在调用 AI 服务时,报错了,抛出了一个属于 Spring AI 的自定义异常
NonTransientAiException【表示不可重试的模型访问错误,当调用 AI 模型发生永久性或客户端错误(例如请求参数错误、模型不存在、API 密钥无效或收到 HTTP 4xx 错误代码)时抛出】。 - 但是消费者(Consumer)的依赖里没有包含 Spring AI 的包,所以它不认识这个异常类。
- Dubbo 底层在传输异常时,发现消费者不认识,为了不直接断开连接,就把这个异常转换成了一个普通的
HashMap结构传回去。消费者本来准备接一个Throwable异常(编程语言中所有错误和异常的超类),结果接住个HashMap,直接解析失败,报 500。
解决:在 Provider 层(比如AICustCareServiceImpl)用try-catch捕获所有大模型调用异常,转成普通的字符串返回给前端,不要让第三方异常直接穿透 Dubbo。
问题总结:
- Q:Dubbo 中如果 Provider 抛出了自定义异常,Consumer 没引入对应的类会怎样?
- A:如果抛出的异常不是 RuntimeException 或者未在接口中声明,Dubbo 会尝试降级。如果 Consumer 缺少这个异常类,反序列化会失败,抛出的异常会被转成 HashMap 导致解析错误(
expect Throwable, but get class java.util.HashMap)。最佳实践是在 Provider 层捕获异常,转成统一的 Result 对象或抛出原生的 RuntimeException。- Q:Dubbo 遇到异常怎么处理
- A:要在 Provider 层 try-catch 掉,不然反序列化会出大问题
五:刚修好 500,又遇到 429 限流
报错信息:
NonTransientAiException: 429 - {"code":"Throttling.RateQuota"...}可能的原因:
- 请求频率太高。
- 免费额度用尽。
真实原因:
通过try-catch拦截后,前端返回了友好的提示语。但看日志发现是 429。因为 Spring AI 默认有重试机制,我本地一请求,它瞬间并发发了多次,把阿里云的 QPS(每秒请求数)额度打满了。
解决:在catch块里判断异常信息,如果是 429,就返回“当前咨询人数较多,请稍后再试”。然后等个一两分钟,限流窗口重置再测。
六:性能刺客——每次搜索都在动态获取向量维度
报错日志/现象:
通过看堆栈,发现每次请求,都会走
QdrantVectorStore.createObservationContextBuilder->AbstractEmbeddingModel.dimensions。
(Qdrant 向量检索每次搜索都去调 Embedding API 算维度,浪费额度且容易触发限流)
原因:
没有显式指定向量维度。
真实原因:
Qdrant 向量数据库在搜索时,需要知道向量的维度。如果没有配置,它就会在每次搜索时,去远程调用一次 Embedding 模型的接口来获取维度。这不仅慢,还浪费 Token。
解决:在配置里写死维度,比如:
spring: ai: vectorstore: qdrant: dimensions: 1536最后
今天心态挺崩溃的,总结的时候可能感觉也没多难,但当时是真的很难,这些问题堆在一起,解决一个又来一个没问没了。复盘下来发现,排查问题的核心其实就是:看日志的第一行 -> 找出核心英文关键词(如 Arrearage、429、HashMap)-> 猜测原因 -> 验证。
很多时候,问题不是我代码逻辑写错了,而是环境配置、第三方平台状态、或者框架底层的默认机制导致的。希望本文经验能帮到遇到同样问题的同学。感谢阅读。