Dubbo入门实战:从零搭建分布式服务调用

85 阅读10分钟

相关文章:深入理解服务暴露机制

Dubbo 是一款源于阿里巴巴的高性能 RPC 框架,在国内互联网企业中普及率极高。本文从零介绍 Dubbo 的核心概念和使用方法。

一、Dubbo是什么?为什么要用它?

简单来说,Dubbo就是一个RPC框架。在微服务架构中,系统被拆分成多个独立的服务,服务之间需要相互调用,Dubbo就是来解决这个问题的。

传统的单体应用里,调用一个方法直接就完了。但在分布式系统中,订单服务要调用用户服务,如果不用Dubbo,就得自己写HTTP请求、维护服务地址、处理网络异常。这些Dubbo都帮你做了,你只需要像调用本地方法一样调用远程服务。

相比Spring Cloud,Dubbo的优势在于性能更好(基于TCP协议)、成熟度更高、对Java更友好。当然这不是说Spring Cloud不好,只是各有侧重点。

二、核心概念

理解Dubbo的核心概念是上手的关键,主要包含四个角色:

Provider(服务提供者):提供服务的那一方。启动时向注册中心注册自己的服务信息,接收Consumer的调用请求并返回结果。

Consumer(服务消费者):调用服务的那一方。启动时从注册中心订阅所需的服务,根据负载均衡策略选择一个Provider发起调用。

Registry(注册中心):服务注册和发现的核心组件。存储服务提供者的地址信息,在服务提供者上下线时通知消费者。常用的有Zookeeper、Nacos、Redis等。

Monitor(监控中心):可选组件,用来统计服务调用次数、响应时间等数据。生产环境强烈建议配置。

Dubbo架构图

graph TB
    Provider[Provider<br/>服务提供者]
    Consumer[Consumer<br/>服务消费者]
    Registry[Registry<br/>注册中心]
    Monitor[Monitor<br/>监控中心]
    
    Provider -->|1.注册服务| Registry
    Consumer -->|2.订阅服务| Registry
    Registry -.->|3.返回服务列表| Consumer
    Registry -.->|4.变更通知| Consumer
    Consumer -->|5.直接调用| Provider
    Consumer -.->|6.上报统计| Monitor
    Provider -.->|6.上报统计| Monitor
    
    style Provider fill:#e1f5ff
    style Consumer fill:#fff4e1
    style Registry fill:#f0e1ff
    style Monitor fill:#e1ffe1

调用流程

  1. Provider启动时向Registry注册服务
  2. Consumer启动时向Registry订阅服务
  3. Registry返回Provider列表给Consumer
  4. Consumer直接调用Provider(不经过Registry)
  5. Consumer和Provider定期向Monitor上报统计数据

重要细节:Consumer调用Provider是点对点直连的,不经过Registry。所以即使Registry挂了,也不影响已经建立的调用关系,只是无法感知Provider的上下线变化。这种设计保证了高可用性。

三、快速开始

1. 环境准备

使用Docker快速启动Zookeeper作为注册中心:

docker run -d --name zookeeper -p 2181:2181 zookeeper:3.7

2. 项目结构设计

推荐分三个模块:

  • api模块:定义服务接口,给Provider和Consumer共用
  • provider模块:实现服务接口
  • consumer模块:调用服务

这样做的好处是Consumer只需要依赖api模块,不会把Provider的实现细节暴露出去。

3. 定义服务接口

在api模块中定义接口:

public interface UserService {
    UserDTO getUserById(Long userId);
    boolean updateUser(UserDTO user);
}

@Data
public class UserDTO implements Serializable {
    private Long id;
    private String username;
    private String email;
}

注意:所有在网络上传输的对象都必须实现Serializable,否则会报序列化异常。

4. Provider端实现

在provider模块的pom.xml中添加依赖:

<dependencies>
    <dependency>
        <groupId>org.apache.dubbo</groupId>
        <artifactId>dubbo-spring-boot-starter</artifactId>
        <version>3.2.0</version>
    </dependency>
    
    <dependency>
        <groupId>org.apache.dubbo</groupId>
        <artifactId>dubbo-zookeeper-spring-boot-starter</artifactId>
        <version>3.2.0</version>
    </dependency>
    
    <dependency>
        <groupId>com.example</groupId>
        <artifactId>dubbo-api</artifactId>
        <version>1.0.0</version>
    </dependency>
</dependencies>

实现服务:

@DubboService(version = "1.0.0",timeout = 3000)
public class UserServiceImpl implements UserService {
    
    @Autowired
    private UserMapper userMapper;
    
    @Override
    public UserDTO getUserById(Long userId) {
        User user = userMapper.selectById(userId);
        // 转换逻辑...
        return userDTO;
    }
    
    @Override
    public boolean updateUser(UserDTO user) {
        // 更新逻辑...
        return true;
    }
}

@DubboService注解的常用参数:

  • version:服务版本号,强烈建议加上,方便以后接口升级时多版本共存
  • timeout:调用超时时间(毫秒),根据业务设置合理值
  • retries:失败重试次数,默认2次,注意幂等性问题
  • loadbalance:负载均衡策略,默认random(随机)

在application.yml中配置:

dubbo:
  application:
    name: user-service
  protocol:
    name: dubbo
    port: 20880
  registry:
    address: zookeeper://127.0.0.1:2181
  scan:
    base-packages: com.example.provider.service

启动类加上@EnableDubbo注解:

@SpringBootApplication
@EnableDubbo
public class ProviderApplication {
    public static void main(String[] args) {
        SpringApplication.run(ProviderApplication.class,args);
    }
}

5. Consumer端实现

pom.xml依赖跟Provider类似,也要引入dubbo-spring-boot-starter和api模块。

在application.yml中配置:

dubbo:
  application:
    name: order-service
  protocol:
    name: dubbo
  registry:
    address: zookeeper://127.0.0.1:2181
  consumer:
    timeout: 3000
    check: false  # 启动时不检查服务是否可用

check参数说明:开发环境建议设为false,避免因Provider未启动导致Consumer启动失败;生产环境建议设为true,确保依赖的服务可用。

调用服务:

@RestController
@RequestMapping("/order")
public class OrderController {
    
    @DubboReference(version = "1.0.0",timeout = 5000)
    private UserService userService;
    
    @GetMapping("/create")
    public String createOrder(Long userId) {
        // 直接调用,就像本地方法一样
        UserDTO user = userService.getUserById(userId);
        if (user == null) {
            return "用户不存在";
        }
        
        // 创建订单逻辑...
        return "订单创建成功";
    }
}

@DubboReference注解的注意点:

  • version要和Provider保持一致,否则会找不到服务
  • timeout可以针对某个调用单独设置,会覆盖全局配置
  • check=false表示启动时不检查服务可用性,开发时很有用

四、高级特性

1. 负载均衡策略

Dubbo提供了四种负载均衡策略:

策略说明适用场景
RandomLoadBalance(随机)随机选择Provider,默认策略大部分场景
WeightedRandomLoadBalance(权重随机)按权重设置随机概率服务器性能有差异
RoundRobinLoadBalance(轮询)依次调用Provider服务器性能相近
WeightedRoundRobinLoadBalance(权重轮询)按权重比例轮询服务器性能有差异
LeastActiveLoadBalance(最少活跃)优先调用活跃请求数少的Provider处理时间差异大
ConsistentHashLoadBalance(一致性哈希)相同参数总是发到同一个Provider有状态服务
@DubboReference(version = "1.0.0",loadbalance = "leastactive")
private UserService userService;

大部分情况下用默认的Random就够了,除非有特殊需求。

2. 容错机制

服务调用失败时,Dubbo提供了多种容错策略:

策略说明适用场景
Failover(失败自动切换)失败后重试其他服务器,默认策略读操作、幂等操作
Failfast(快速失败)调用失败立即报错写操作、非幂等操作
Failsafe(失败安全)出现异常直接忽略日志记录等不重要操作
Failback(失败自动恢复)失败后记录请求,定时重发消息通知
Forking(并行调用)同时调用多个服务器,一个成功即返回实时性要求高的场景
@DubboReference(version = "1.0.0",cluster = "failfast",retries = 0)
private UserService userService;

重要提醒:选择容错策略时一定要考虑业务特点。比如创建订单这种操作,必须用failfast避免重复创建。

3. 异步调用

Dubbo默认是同步调用,如果想提升性能,可以使用异步调用:

@DubboReference(version = "1.0.0",async = true)
private UserService userService;

public void asyncCall() {
    // 发起调用,立即返回null
    userService.getUserById(1L);
    
    // 获取Future对象
    CompletableFuture<UserDTO> future = RpcContext.getContext().getCompletableFuture();
    
    future.whenComplete((user,exception) -> {
        if (exception != null) {
            // 处理异常
        } else {
            // 处理结果
        }
    });
}

适合需要调用多个服务且可以并行处理的场景,能显著提升性能。

4. 服务分组

当同一个接口有多个实现时,可以通过分组区分:

// Provider端
@DubboService(version = "1.0.0",group = "alipay")
public class AlipayServiceImpl implements PayService {}

@DubboService(version = "1.0.0",group = "wechat")
public class WechatPayServiceImpl implements PayService {}

// Consumer端
@DubboReference(version = "1.0.0",group = "alipay")
private PayService alipayService;

@DubboReference(version = "1.0.0",group = "wechat")
private PayService wechatPayService;

5. 结果缓存

对于查询类接口,可以开启结果缓存减少网络调用:

@DubboReference(version = "1.0.0",cache = "lru",cacheSize = 1000)
private UserService userService;

支持lru、threadlocal、jcache等策略。注意要考虑数据一致性问题,适合查询频繁但变化不大的数据。

6. 方法级精细化配置

在实际生产中,同一个接口下的不同方法可能有着完全不同的特性。例如:getUser() 是读操作,响应快、容忍重试;而 updateUser() 是写操作,不能重试、需要快速失败。

单纯的接口级别配置(一刀切)往往无法满足需求。Dubbo 提供了强大的方法级配置能力,允许你针对每个方法独立设置规则。

6.1 支持方法级配置的核心参数

Dubbo 几乎所有的核心治理参数都支持方法级别配置,主要包括:

分类参数名说明典型场景
容错策略cluster容错机制读用 failover,写用 failfast
retries重试次数读重试 2 次,写重试 0 次
负载均衡loadbalance负载策略普通列表用 random,详情查询用 consistenthash
性能控制timeout超时时间复杂报表允许 5s,核心查询限制 500ms
并发控制actives客户端并发限制限制某个慢方法的并发数,防止拖垮服务
executes服务端并发限制保护服务端特定方法的稳定性
其它mock服务降级某个非核心方法失败时返回 Mock 数据
validation参数校验仅对新增方法开启 JSR303 校验

6.2 配置示例

利用 @Method 注解,我们可以在 Consumer 端实现读写分离的精细化控制。

@DubboReference(
    version = "1.0.0",
    timeout = 3000, // 接口级默认配置
    methods = {
        // 方法1:查询操作
        // 特性:允许重试,超时时间短,使用一致性哈希负载均衡(利于缓存)
        @Method(name = "getUserById", timeout = 1000, retries = 2, loadbalance = "consistenthash"),
        
        // 方法2:更新操作
        // 特性:禁止重试(防止数据重复),快速失败,超时时间适当放宽
        @Method(name = "updateUser", timeout = 5000, retries = 0, cluster = "failfast")
    }
)
private UserService userService;

6.3 配置优先级(覆盖规则)

当出现多层级配置冲突时,Dubbo 遵循以下优先级原则(由高到低):

方法级 > 接口级 > 全局配置,Consumer配置 > Provider配置

建议:timeoutretriesloadbalance 这类控制“调用行为”的参数,优先在 Consumer 端配置,因为调用方最清楚自己需要什么服务质量。

五、生产实践

1. 常见问题排查

问题1:服务注册成功但调用报错

可能原因:防火墙未开放dubbo端口(默认20880)

解决方法:检查并开放对应端口

问题2:启动报错No provider available

可能原因:

  • Provider还没启动
  • version或group不匹配
  • 注册中心连接失败
  • check=true但Provider确实不可用

解决方法:开发环境设置check=false,排查配置是否一致

问题3:调用超时

排查步骤:

  1. 检查超时时间设置是否合理
  2. 在Provider端打日志查看实际执行时间
  3. 记住超时时间优先级:方法级 > 接口级 > 全局配置,Consumer配置 > Provider配置

问题4:序列化异常

检查要点:

  • 传输对象必须实现Serializable
  • Consumer和Provider的类定义要一致(包名、字段名、类型)

2. 监控运维

生产环境强烈建议部署Dubbo Admin,可以:

  • 可视化查看所有服务状态
  • 实时监控调用统计数据
  • 动态修改服务配置
  • 服务测试和调试

使用Docker快速部署:

docker run -d \
  --name dubbo-admin \
  -p 8080:8080 \
  -e admin.registry.address=zookeeper://127.0.0.1:2181 \
  apache/dubbo-admin:latest

访问 http://localhost:8080 即可使用。

3. 最佳实践建议

配置规范

  • 生产环境必须配置version,方便版本管理
  • 合理设置timeout,避免过长或过短
  • 写操作使用failfast,读操作使用failover
  • 非幂等操作设置retries=0

性能优化

  • 合理使用异步调用减少等待时间
  • 对热点数据启用结果缓存
  • 根据业务场景选择合适的负载均衡策略
  • 使用连接池复用TCP连接

运维监控

  • 部署Dubbo Admin进行可视化管理
  • 配置Monitor收集调用统计
  • 关注关键指标:QPS、响应时间、错误率
  • 设置合理的告警阈值

总结

Dubbo上手不难,理解核心概念、配好注册中心、掌握Provider和Consumer的配置就能跑起来了。实际使用中要根据业务场景选择合适的负载均衡和容错策略,生产环境务必做好监控。