Java 项目实战: 交友项目 - Dubbo 启动检查

概述

只启动消费者、不启动提供者,消费者会直接起不来------这不是 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 之后:

  1. 消费者启动成功,正常注册到注册中心;
  2. 如果提供者一直没起来,第一次真正调用时才会失败,典型异常是:
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
  1. 错误从"启动期一次性暴露"变成了"运行期每次调用都炸",排查成本上升。

所以关掉 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 就是"开发期关闭"这一策略的落地。
  • 关掉不等于放弃可用性,而是把启动检查 换成运行时容错 + 就绪探针------这才是云原生里更常见的组合。
  • 关掉之后必须配兜底:没探针、没熔断、没告警,就等于把故障从启动期搬到了用户面前。
相关推荐
零基础1231 小时前
权限管理怎么做?从“资格”与“空间”双维度设计一套可落地的权限体系
java·服务器·数据库
starzy19901 小时前
Flink Task 调度任务深度剖析:从调度策略到故障恢复的完整链路
java·大数据·flink
开开心心_Every1 小时前
文件查重软件批量删除重复文件释放空间
java·开发语言·c#·计算机外设·ocr·excel·perl
Wang's Blog1 小时前
Java 项目实战: 交友项目 - 技术架构与选型剖析
java·架构·交友
我是大猴子1 小时前
一些八股和限流措施
java
认真的小羽❅2 小时前
ThreadLocal 在虚拟线程间要小心
java·开发语言·jvm
美味蛋炒饭.2 小时前
Java八股面试(4)
java·开发语言·面试
SL_staff2 小时前
OKR落地难?技术团队如何用五层联动链打通执行断层
java·开源·全栈
省钱兄--zs2 小时前
无人自助拼豆系统架构设计与功能实现落地部署全流程实战指南
java·数据仓库·spring boot·系统架构·需求分析