概述
只启动消费者、不启动提供者,消费者会直接起不来------这不是 bug,是 Dubbo 默认开启的启动检查在拦你。这篇讲清它拦什么、默认值是多少、生产上为什么反而要关掉它,以及关掉以后该拿什么兜底。
纲要
- 启动检查(
check)到底检查什么:检查的是消费者启动时依赖的提供者是否可用,不是检查自己 - 默认值
true,以及check的配置位置 / 作用对象 / 生效时机对照表 - 为什么 Dubbo 要把它默认设成
true:暴露问题要趁早,避免"起来了但不可用"的假健康 - 生产上却常常关掉它的四个真实原因:循环启动依赖、滚动发布、弹性扩缩容、本地开发
- 怎么关:
@DubboReference(check = false)/dubbo.consumer.check: false/ Provider 侧的同名配置 - 关掉之后会发生什么:启动成功,但第一次调用直接
No provider available - 兜底手段清单:就绪探针、熔断降级、监控告警,以及"用就绪探针替代启动检查"这个取舍
- 对照实验:不启动 Provider 只启动 Consumer,先看
check=true的报错,再看check=false的差异 - 工程实读:
user-consumer里那句dubbo.consumer.check: false是怎么来的 - 实战坑清单
启动检查是什么
一句话:消费者启动的那一刻,Dubbo 会去注册中心查一遍------它 @DubboReference 引用的服务,现在有没有可用的提供者。没有就抛异常,消费者直接启动失败。
注意主语是消费者,检查的对象是提供者。素材里那句"启动检查和提供者没什么太大关系"要读得再精确一点,准确说法是:启动检查这个动作发生在消费者(或"同时也是消费者的提供者")的启动过程里,被检查的是下游。
#mermaid-svg-UDYASSem56kc4Ge4{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-UDYASSem56kc4Ge4 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-UDYASSem56kc4Ge4 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-UDYASSem56kc4Ge4 .error-icon{fill:#552222;}#mermaid-svg-UDYASSem56kc4Ge4 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-UDYASSem56kc4Ge4 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-UDYASSem56kc4Ge4 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-UDYASSem56kc4Ge4 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-UDYASSem56kc4Ge4 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-UDYASSem56kc4Ge4 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-UDYASSem56kc4Ge4 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-UDYASSem56kc4Ge4 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-UDYASSem56kc4Ge4 .marker.cross{stroke:#333333;}#mermaid-svg-UDYASSem56kc4Ge4 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-UDYASSem56kc4Ge4 p{margin:0;}#mermaid-svg-UDYASSem56kc4Ge4 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-UDYASSem56kc4Ge4 .cluster-label text{fill:#333;}#mermaid-svg-UDYASSem56kc4Ge4 .cluster-label span{color:#333;}#mermaid-svg-UDYASSem56kc4Ge4 .cluster-label span p{background-color:transparent;}#mermaid-svg-UDYASSem56kc4Ge4 .label text,#mermaid-svg-UDYASSem56kc4Ge4 span{fill:#333;color:#333;}#mermaid-svg-UDYASSem56kc4Ge4 .node rect,#mermaid-svg-UDYASSem56kc4Ge4 .node circle,#mermaid-svg-UDYASSem56kc4Ge4 .node ellipse,#mermaid-svg-UDYASSem56kc4Ge4 .node polygon,#mermaid-svg-UDYASSem56kc4Ge4 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-UDYASSem56kc4Ge4 .rough-node .label text,#mermaid-svg-UDYASSem56kc4Ge4 .node .label text,#mermaid-svg-UDYASSem56kc4Ge4 .image-shape .label,#mermaid-svg-UDYASSem56kc4Ge4 .icon-shape .label{text-anchor:middle;}#mermaid-svg-UDYASSem56kc4Ge4 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-UDYASSem56kc4Ge4 .rough-node .label,#mermaid-svg-UDYASSem56kc4Ge4 .node .label,#mermaid-svg-UDYASSem56kc4Ge4 .image-shape .label,#mermaid-svg-UDYASSem56kc4Ge4 .icon-shape .label{text-align:center;}#mermaid-svg-UDYASSem56kc4Ge4 .node.clickable{cursor:pointer;}#mermaid-svg-UDYASSem56kc4Ge4 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-UDYASSem56kc4Ge4 .arrowheadPath{fill:#333333;}#mermaid-svg-UDYASSem56kc4Ge4 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-UDYASSem56kc4Ge4 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-UDYASSem56kc4Ge4 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-UDYASSem56kc4Ge4 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-UDYASSem56kc4Ge4 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-UDYASSem56kc4Ge4 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-UDYASSem56kc4Ge4 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-UDYASSem56kc4Ge4 .cluster text{fill:#333;}#mermaid-svg-UDYASSem56kc4Ge4 .cluster span{color:#333;}#mermaid-svg-UDYASSem56kc4Ge4 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-UDYASSem56kc4Ge4 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-UDYASSem56kc4Ge4 rect.text{fill:none;stroke-width:0;}#mermaid-svg-UDYASSem56kc4Ge4 .icon-shape,#mermaid-svg-UDYASSem56kc4Ge4 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-UDYASSem56kc4Ge4 .icon-shape p,#mermaid-svg-UDYASSem56kc4Ge4 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-UDYASSem56kc4Ge4 .icon-shape .label rect,#mermaid-svg-UDYASSem56kc4Ge4 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-UDYASSem56kc4Ge4 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-UDYASSem56kc4Ge4 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-UDYASSem56kc4Ge4 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} user-consumer 启动流程
是
有
无
否
首次调用时才校验
Spring 容器启动
解析 @DubboReference
check = true ?
去注册中心查询
UserService 有无可用提供者
注入代理,启动成功
抛 IllegalStateException
启动中断
跳过检查,直接注入代理
启动成功
无提供者 → 调用失败
配置位置、默认值与生效时机
| 配置写法 | 作用对象 | 默认值 | 生效时机 | 说明 |
|---|---|---|---|---|
dubbo.consumer.check |
全部消费者引用 | true |
消费者启动 | 全局开关,最常用 |
@DubboReference(check = false) |
单个引用字段 | true |
消费者启动 | 注解级,优先级高于全局 |
dubbo.provider.check |
Provider 依赖的下游服务 | true |
Provider 启动 | Provider 同时是消费者时才有意义 |
@DubboService(check = false) |
单个 Provider | true |
Provider 启动 | 同上,注解级 |
表格里"Provider 也有 check"这一点容易被忽略:Dubbo 的 Provider 如果自己还通过 @DubboReference 调别人,那它启动时同样会去检查下游。一个服务既提供又消费是常态,所以启动顺序问题会在整条链路上放大。
为什么默认是 true
很多人只记住"默认 true",却没想过它为什么默认 true。答案在"故障暴露时机"上。
假设 check 默认是 false。消费者启动时下游还没就绪,它照样启动成功、注册到注册中心、开始接收流量。上游一看注册列表里有它,就把请求打进来------然后第一批请求全部失败。这个服务在监控上表现为"进程活着、端口通、健康检查全绿,但业务全错",也就是假健康。这种状态排查起来最痛苦:你得先怀疑业务代码、再怀疑数据库、最后才想到下游没起来。
check = true 把这个问题前置到了启动阶段:要么全链路就绪后一起起来,要么在启动就报错、一眼看出是谁缺了。素材里"消费者启动失败说明它关联的地方有错,两端都要排查,有利于平稳上线"讲的就是这个意思------它把运行期的隐性故障,换成了启动期的显性失败。
代价也很明确:灵活性没了。你被强制"必须按依赖顺序启动"。这个代价在单体或少量服务时无所谓,在微服务集群里就变成了麻烦。
生产上为什么常常关掉
循环启动依赖:谁也别想先起来
服务一多,依赖关系图上出现环是迟早的事。A 依赖 B、B 依赖 C、C 又依赖 A(哪怕其中一个是"弱依赖",代码里只是顺手引用了对方一个接口),全开 check 的结局是:
#mermaid-svg-r7tM2eZgrr9XD3L9{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-r7tM2eZgrr9XD3L9 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-r7tM2eZgrr9XD3L9 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-r7tM2eZgrr9XD3L9 .error-icon{fill:#552222;}#mermaid-svg-r7tM2eZgrr9XD3L9 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-r7tM2eZgrr9XD3L9 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-r7tM2eZgrr9XD3L9 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-r7tM2eZgrr9XD3L9 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-r7tM2eZgrr9XD3L9 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-r7tM2eZgrr9XD3L9 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-r7tM2eZgrr9XD3L9 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-r7tM2eZgrr9XD3L9 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-r7tM2eZgrr9XD3L9 .marker.cross{stroke:#333333;}#mermaid-svg-r7tM2eZgrr9XD3L9 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-r7tM2eZgrr9XD3L9 p{margin:0;}#mermaid-svg-r7tM2eZgrr9XD3L9 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-r7tM2eZgrr9XD3L9 .cluster-label text{fill:#333;}#mermaid-svg-r7tM2eZgrr9XD3L9 .cluster-label span{color:#333;}#mermaid-svg-r7tM2eZgrr9XD3L9 .cluster-label span p{background-color:transparent;}#mermaid-svg-r7tM2eZgrr9XD3L9 .label text,#mermaid-svg-r7tM2eZgrr9XD3L9 span{fill:#333;color:#333;}#mermaid-svg-r7tM2eZgrr9XD3L9 .node rect,#mermaid-svg-r7tM2eZgrr9XD3L9 .node circle,#mermaid-svg-r7tM2eZgrr9XD3L9 .node ellipse,#mermaid-svg-r7tM2eZgrr9XD3L9 .node polygon,#mermaid-svg-r7tM2eZgrr9XD3L9 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-r7tM2eZgrr9XD3L9 .rough-node .label text,#mermaid-svg-r7tM2eZgrr9XD3L9 .node .label text,#mermaid-svg-r7tM2eZgrr9XD3L9 .image-shape .label,#mermaid-svg-r7tM2eZgrr9XD3L9 .icon-shape .label{text-anchor:middle;}#mermaid-svg-r7tM2eZgrr9XD3L9 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-r7tM2eZgrr9XD3L9 .rough-node .label,#mermaid-svg-r7tM2eZgrr9XD3L9 .node .label,#mermaid-svg-r7tM2eZgrr9XD3L9 .image-shape .label,#mermaid-svg-r7tM2eZgrr9XD3L9 .icon-shape .label{text-align:center;}#mermaid-svg-r7tM2eZgrr9XD3L9 .node.clickable{cursor:pointer;}#mermaid-svg-r7tM2eZgrr9XD3L9 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-r7tM2eZgrr9XD3L9 .arrowheadPath{fill:#333333;}#mermaid-svg-r7tM2eZgrr9XD3L9 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-r7tM2eZgrr9XD3L9 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-r7tM2eZgrr9XD3L9 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-r7tM2eZgrr9XD3L9 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-r7tM2eZgrr9XD3L9 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-r7tM2eZgrr9XD3L9 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-r7tM2eZgrr9XD3L9 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-r7tM2eZgrr9XD3L9 .cluster text{fill:#333;}#mermaid-svg-r7tM2eZgrr9XD3L9 .cluster span{color:#333;}#mermaid-svg-r7tM2eZgrr9XD3L9 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-r7tM2eZgrr9XD3L9 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-r7tM2eZgrr9XD3L9 rect.text{fill:none;stroke-width:0;}#mermaid-svg-r7tM2eZgrr9XD3L9 .icon-shape,#mermaid-svg-r7tM2eZgrr9XD3L9 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-r7tM2eZgrr9XD3L9 .icon-shape p,#mermaid-svg-r7tM2eZgrr9XD3L9 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-r7tM2eZgrr9XD3L9 .icon-shape .label rect,#mermaid-svg-r7tM2eZgrr9XD3L9 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-r7tM2eZgrr9XD3L9 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-r7tM2eZgrr9XD3L9 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-r7tM2eZgrr9XD3L9 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} @DubboReference
@DubboReference
@DubboReference
启动失败
启动失败
启动失败
order-service
user-service
message-service
No provider available
三个服务互相等着对方先起来,结果是一个都起不来。这就是微服务里经典的启动死锁。只要有一个环,check = true 下整个依赖环上的服务全部锁死,重启集群都救不回来。
滚动发布与弹性扩缩容
发布期间是分批重启的:先重启 A 的新实例,此时 B 的老实例可能刚好在下线窗口里,A 因为找不到 B 而启动失败,发布流程被卡住。部署过程本来就不追求"任意时刻全链路都在线",而是靠多发几个实例滚动替换来做到最终一致,check = true 恰恰和这个节奏冲突。
扩缩容更直观:半夜流量高峰,下游 B 因为一次网络抖动短暂不可用,你想把上游 A 扩容 3 个实例顶一顶。结果 A 的新实例全部因为 check 失败起不来------本来只是想加机器,最后变成了"一个都起不来",小故障被放大成大故障。
测试与本地开发
一个人只想调自己负责的消费者服务,下游是别人写的、本地根本没部署。有引用关系在,check = true 就让他永远起不来。素材里说的"开发时由于有这种引用关系,导致你的消费者端程序永远运行不起来"就是这种情况。
四种原因归纳一下:
| 场景 | check = true 的后果 | 期望行为 |
|---|---|---|
| 依赖成环 | 整个环全部启动失败,集群起不来 | 先后启动,靠运行时容错 |
| 滚动发布 | 单次重启卡住,发布中断 | 允许短暂"下游不在",发布继续 |
| 弹性扩容 | 故障时扩容实例起不来,故障被放大 | 新实例照常起来,等下游恢复 |
| 本地开发 | 没有下游就完全无法启动 | 只启动自己调试 |
怎么关
三处配置,按需选。
消费者注解级------只影响这一个引用:
java
package cn.mf.user.controller;
import cn.mf.user.domain.User;
import cn.mf.user.service.UserService;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/user")
public class UserController {
//引用远程服务:check = false 表示启动时不检查该提供者是否可用
@DubboReference(version = "2.0.0", check = false, retries = 0, loadbalance = "Random")
private UserService userService;
@GetMapping("/{id}")
public User queryById(@PathVariable("id") Long id) {
return userService.queryById(id);
}
}
消费者全局级------工程里实际采用的就是这个写法:
yaml
dubbo:
registry:
address: nacos://127.0.0.1:8848
consumer:
check: false # 关闭启动检查:消费者启动时不再校验提供者是否存在
retries: 0
提供者级------只有当它自己也引用了别人的服务时才需要:
java
package cn.mf.user.service.impl;
import cn.mf.user.mapper.UserMapper;
import cn.mf.user.domain.User;
import cn.mf.user.service.UserService;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.beans.factory.annotation.Autowired;
// check = false:本服务启动时不检查它依赖的下游是否就绪
@DubboService(version = "1.0.0", check = false)
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public User queryById(Long id) {
return userMapper.findById(id);
}
}
对应的 yml 是 dubbo.provider.check: false。
推荐做法 :开发环境直接全局关(dubbo.consumer.check: false),省得被别人的服务拖住;生产环境也建议关掉,但前提是用别的机制替代它。
这里要讲清楚一个取舍------关掉 check 不等于放弃可用性。它换的是保障方式:
- 启动时检查 → 保证"启动那一刻依赖就绪",代价是启动强耦合;
- 运行时容错 + 就绪探针 → 保证"对外提供服务时依赖就绪",服务可以随时起来,但没就绪之前不允许接流量。
云原生体系更认第二种。K8s 的 readinessProbe 就是在做这件事:容器起来了但下游还没通,探针失败,Service 就不把流量转发给它,等依赖恢复、探针通过再放流量进来。这比"启动失败、靠人工重启"要平滑得多。
关掉之后会发生什么
必须把这句话说透,否则很容易误解成"关了就没问题"。
关掉 check 之后:
- 消费者启动成功,正常注册到注册中心;
- 如果提供者一直没起来,第一次真正调用时才会失败,典型异常是:
text
org.apache.dubbo.rpc.RpcException: No provider available for the service
cn.mf.user.service.UserService:2.0.0 from the url
consumer://10.0.0.12/cn.mf.user.service.UserService?...
to the consumer 10.0.0.12 use dubbo version 3.0.4
- 错误从"启动期一次性暴露"变成了"运行期每次调用都炸",排查成本上升。
所以关掉 check 只是把问题推迟了,必须有人接住:
| 兜底手段 | 解决什么 | 缺了它会怎样 |
|---|---|---|
| 就绪探针 readinessProbe | 下游未就绪时不让流量进来 | 流量打进未就绪实例,首批请求全失败 |
| 熔断降级(Sentinel / 自定义) | 下游宕机时快速失败、返回兜底值 | 每次调用都阻塞等待,线程堆积 |
| 监控告警 | 及时知道"某提供者长时间无实例" | 故障静默,等用户投诉才发现 |
| 定时重试 / 自动恢复 | 下游恢复后自动重新调用 | 需要人工重启消费者 |
| 依赖治理 / 依赖分级 | 区分强依赖和弱依赖 | 弱依赖缺失也拖着主流程 |
一句话:check = false 是"允许降级启动",不是"允许不健康运行"。这两件事必须配套,缺了配套就变成了纯粹的裸奔。
对照实验
复现一遍就能彻底理解。前提:Nacos 已在 127.0.0.1:8848,MySQL 里 dubbo-demo 库已建好。
第一步,制造失败。不启动 user-provider,只启动 user-consumer ,保持默认 check = true:
text
启动过程中报错(节选):
Injection of @DubboReference ... failed;
nested exception is java.lang.IllegalStateException:
Failed to check the status of the service cn.mf.user.service.UserService.
No provider available for the service cn.mf.user.service.UserService:2.0.0
from the url consumer://.../cn.mf.user.service.UserService?check=true...
报错信息里两个关键片段要会读:Failed to check the status of the service 说明是启动检查 阶段失败;check=true 直接告诉你当前开关状态。
第二步,改配置。在 application.yml 里加 dubbo.consumer.check: false 后重启消费者:
text
user-consumer 正常启动,端口 18081 监听成功。
第三步,验证差异。此时再访问 GET http://localhost:18081/user/1:
text
HTTP 500
java.lang.RuntimeException / RpcException:
No provider available for the service cn.mf.user.service.UserService
结论很清晰:启动不报错了,但调用照样失败。check 改变的是"失败发生在什么时候",不是"会不会失败"。
第四步,把 user-provider 起起来,再访问同一个 URL,返回正常数据。整个链路打通。
工程实读
dubbo-demo 工程里,消费者侧的 application.yml 真实内容是这样的:
yaml
server:
port: 18081
spring:
application:
name: user-consumer
dubbo:
registry:
address: nacos://127.0.0.1:8848
consumer:
check: false #关闭了启动检查
retries: 0
提供者 user-provider 的 application.yml 里没有 任何 check 配置,它只配了协议端口 20881、注册中心和包扫描------因为它没有引用别人的服务,启动检查对它不构成问题:
yaml
server:
port: 18080
dubbo:
protocol:
name: dubbo
port: 20881
registry:
address: nacos://127.0.0.1:8848
scan:
base-packages: cn.mf.user.service
再看对照工程 cloud-dubbo-demo(Spring Cloud 整合版):order-service 作为消费者同样写了 dubbo.consumer.check: false,而 user-service 作为纯提供者没有这项配置。两个工程的策略是一致的:消费者关、提供者不管。
tree
dubbo-demo
├── user-api # 公共接口模块
│ └── src/main/java/cn/mf/user
│ ├── domain/User.java
│ └── service/UserService.java
├── user-provider # 提供者 端口 18080 / Dubbo 协议 20881
│ └── src/main
│ ├── java/cn/mf/user
│ │ ├── UserProviderApplication.java
│ │ ├── mapper/UserMapper.java
│ │ └── service/impl
│ │ ├── UserServiceImpl.java # @DubboService(version = "1.0.0")
│ │ └── UserServiceImpl2.java # @DubboService(version = "2.0.0")
│ └── resources/application.yml # 无 check 配置
└── user-consumer # 消费者 端口 18081
└── src/main
├── java/cn/mf/user
│ ├── UserConsumerApplication.java
│ └── controller/UserController.java # @DubboReference(version="2.0.0", retries=0, ...)
└── resources/application.yml # dubbo.consumer.check: false
需要说明的是:本工程把 check 关掉,是教学阶段的工程选择,不是唯一正确答案。 前置课里各个服务由不同的练习轮流启动,学习者经常只想跑自己那一端,全开 check 会让人寸步难行;同时这也能顺带演示"关掉 check 后调用失败"这一课的重点。放到真实生产,这个开关要根据依赖关系、发布方式、是否已有就绪探针来定,而不是照抄。
实战坑
- 把
check = false当成懒加载 。它不是"用到才连接"的意思,只是跳过启动时那一次可用性校验,代理对象该建还是照建。Dubbo 本身也没有"彻底懒连接"这种语义。 - 关了 check 却没配就绪探针。最常见的线上事故形态:服务起来了,注册中心里也有了,但下游没通,网关把流量打进来,用户在报错。关 check 必须和探针一起上。
- Provider 侧也开了 check 导致启动顺序死锁。很多人只记得关消费者的 check,忘了 Provider 自己也可能是消费者。依赖一旦成环,全集群起不来。
- 注解和全局配置同时存在时的优先级困扰 。
@DubboReference(check = false)与dubbo.consumer.check: false一个在注解、一个在 yml,谁覆盖谁要想一下。建议只保留一处配置------要么统一走全局 yml,要么逐个注解写明,避免歧义。真需要混用,也要清楚注解级的优先级更高。 - 报错信息看漏
check=true。异常里这个参数是判断"是启动检查拦的"还是"调用时没提供者"的最快依据,比翻代码快得多。
API 速览
| 配置项 | 位置 | 默认 | 作用 |
|---|---|---|---|
check |
dubbo.consumer.check |
true |
消费者启动时是否校验引用的服务 |
check |
@DubboReference(check = false) |
true |
单个引用关闭启动检查 |
check |
dubbo.provider.check |
true |
Provider 启动时是否校验其依赖 |
check |
@DubboService(check = false) |
true |
单个 Provider 关闭启动检查 |
官方文档
下一篇按讲义顺序进入 Dubbo 的另一个高级特性------多版本支持 (3.4),讲怎么用 version 做灰度发布,让一部分用户先用上新功能。
总结
check是消费者启动时对下游可用性的校验 ,默认true,检查对象是提供者不是自己。- 它默认开着是为了把"服务依赖没就绪"从运行期问题提前到启动期暴露,代价是启动强耦合。
- 生产常关它的四个理由:循环启动依赖、滚动发布、弹性扩缩容、本地开发。工程里
dubbo.consumer.check: false就是"开发期关闭"这一策略的落地。 - 关掉不等于放弃可用性,而是把启动检查 换成运行时容错 + 就绪探针------这才是云原生里更常见的组合。
- 关掉之后必须配兜底:没探针、没熔断、没告警,就等于把故障从启动期搬到了用户面前。