Dubbo 的同步服务调用

在Dubbo中,同步调用的底层是通过DefaultFuture实现的,它使用了CompletableFuture(在较新版本中)或者自己实现的Future(旧版本)来等待结果。

当我们调用一个远程方法时,会生成一个Request对象,并分配一个请求ID,然后通过NettyChannel发送请求,同时创建一个DefaultFuture对象(与请求ID关联)来等待结果。

在NettyChannel中,发送消息是异步的,但是DefaultFuture会通过get方法同步等待结果。

大致代码流程(简化):

arduino 复制代码
public class DefaultFuture implements ResponseFuture {
    private final long id; // 请求ID
    private final Channel channel;
    private final Request request;
    private final int timeout;
    private volatile Response response;

    // 创建DefaultFuture,并存储到全局Map中
    public static DefaultFuture newFuture(Channel channel, Request request, int timeout) {
        DefaultFuture future = new DefaultFuture(channel, request, timeout);
        // 存储到Map,key为请求ID
        FUTURES.put(request.getId(), future);
        return future;
    }

    // 获取结果,会阻塞等待
    public Object get(int timeout) throws RemotingException {
        // 等待响应,直到超时
        // 底层使用锁或CompletableFuture的get方法
    }
}

当消费者收到响应时,会根据响应中的请求ID,从Map中找到对应的DefaultFuture,然后设置响应结果,并唤醒等待的线程。

所以,虽然网络通信是异步的,但通过DefaultFuture的同步等待,实现了同步调用的效果。

总结:Dubbo的同步服务是通过在消费者端使用Future模式(DefaultFuture)来阻塞等待服务提供者的响应结果来实现的。

Dubbo 的同步服务调用是其最核心和常用的调用模式。以下是 Dubbo 实现同步服务的详细机制和配置:

1. 同步调用基础原理

默认的同步调用

java 复制代码
public class SyncServiceDemo {
    
    @Reference
    private UserService userService;
    
    public void demo() {
        // 默认就是同步调用 - 线程会阻塞直到返回结果
        User user = userService.getUserById(123L);
        System.out.println("用户信息: " + user);
        
        // 等同于显式声明
        User user2 = userService.getUserById(456L);
    }
}

2. 底层同步实现机制

客户端同步调用流程

scala 复制代码
public class DubboInvoker<T> extends AbstractInvoker<T> {
    
    @Override
    protected Result doInvoke(final Invocation invocation) throws Throwable {
        // 1. 构建请求
        Request request = new Request();
        request.setVersion(version);
        request.setTwoWay(true); // 设置为双向通信
        request.setData(invocation);
        
        // 2. 发送请求并等待响应
        DefaultFuture future = currentClient.request(request, timeout);
        
        // 3. 同步等待结果
        try {
            Response response = future.get(timeout);
            return new RpcResult(response.getResult());
        } catch (InterruptedException e) {
            throw new RpcException("Interrupted while waiting for response", e);
        } catch (TimeoutException e) {
            throw new RpcException("Timeout while waiting for response", e);
        }
    }
}

DefaultFuture 同步等待实现

java 复制代码
public class DefaultFuture implements ResponseFuture {
    private final long id;
    private final Channel channel;
    private final Request request;
    private final int timeout;
    private volatile Response response;
    private final CountDownLatch latch = new CountDownLatch(1);
    
    public static DefaultFuture newFuture(Channel channel, Request request, int timeout) {
        DefaultFuture future = new DefaultFuture(channel, request, timeout);
        // 存储到全局映射中
        FUTURES.put(request.getId(), future);
        TIMEOUT_CHECKER.addTimeoutTask(future);
        return future;
    }
    
    // 同步获取结果
    public Response get(int timeout) throws RemotingException {
        if (timeout <= 0) {
            timeout = Constants.DEFAULT_TIMEOUT;
        }
        
        if (!isDone()) {
            long start = System.currentTimeMillis();
            lock.lock();
            try {
                // 使用 Condition 进行等待
                while (!isDone()) {
                    done.await(timeout, TimeUnit.MILLISECONDS);
                    if (isDone() || (System.currentTimeMillis() - start) > timeout) {
                        break;
                    }
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } finally {
                lock.unlock();
            }
            
            if (!isDone()) {
                throw new TimeoutException(...);
            }
        }
        return returnFromResponse();
    }
    
    // 收到响应时唤醒等待线程
    public static void received(Response response) {
        DefaultFuture future = FUTURES.remove(response.getId());
        if (future != null) {
            future.doReceived(response);
        }
    }
    
    private void doReceived(Response res) {
        lock.lock();
        try {
            response = res;
            if (done != null) {
                // 唤醒所有等待的线程
                done.signalAll();
            }
        } finally {
            lock.unlock();
        }
    }
}

3. 配置同步服务

XML 配置方式

xml 复制代码
<!-- 服务提供者配置 -->
<dubbo:service interface="com.example.UserService" 
               ref="userService" 
               version="1.0.0"
               timeout="3000" 
               retries="2"
               actives="100" />

<!-- 服务消费者配置 -->
<dubbo:reference id="userService" 
                 interface="com.example.UserService"
                 version="1.0.0"
                 timeout="5000"
                 retries="0"
                 check="false"
                 async="false" />  <!-- 明确设置为同步 -->

注解配置方式

kotlin 复制代码
// 服务提供者
@Service(version = "1.0.0", timeout = 3000, retries = 2)
public class UserServiceImpl implements UserService {
    @Override
    public User getUserById(Long id) {
        // 模拟业务处理
        return userRepository.findById(id);
    }
}

// 服务消费者
@RestController
public class UserController {
    
    @Reference(
        version = "1.0.0",
        timeout = 5000,
        retries = 0,
        async = false,  // 同步调用
        loadbalance = "roundrobin"
    )
    private UserService userService;
    
    @GetMapping("/user/{id}")
    public User getUser(@PathVariable Long id) {
        // 同步调用 - 阻塞直到返回结果
        return userService.getUserById(id);
    }
}

Spring Boot 配置

yaml 复制代码
# application.yml
dubbo:
  application:
    name: user-service-consumer
  registry:
    address: zookeeper://127.0.0.1:2181
  protocol:
    name: dubbo
    port: 20880
  consumer:
    timeout: 5000
    check: false
    retries: 0
    async: false  # 全局设置为同步

4. 同步调用的超时控制

多级别超时配置

less 复制代码
public class TimeoutConfiguration {
    
    // 1. 方法级别超时配置
    @Reference(timeout = 3000, methods = {
        @Method(name = "getUserById", timeout = 1000),
        @Method(name = "updateUser", timeout = 5000)
    })
    private UserService userService;
    
    // 2. 服务级别超时配置
    @Reference(timeout = 5000)
    private OrderService orderService;
}

// XML 配置方式
<dubbo:reference interface="com.example.UserService" timeout="3000">
    <dubbo:method name="getUserById" timeout="1000" />
    <dubbo:method name="updateUser" timeout="5000" />
</dubbo:reference>

超时异常处理

kotlin 复制代码
public class SyncServiceWithTimeout {
    
    @Reference(timeout = 2000)
    private UserService userService;
    
    public User getUserWithFallback(Long id) {
        try {
            return userService.getUserById(id);
        } catch (RpcException e) {
            if (e.isTimeout()) {
                // 超时异常处理
                logger.warn("获取用户信息超时,返回默认用户");
                return getDefaultUser();
            } else if (e.isNetwork()) {
                // 网络异常处理
                logger.error("网络异常", e);
                throw new ServiceUnavailableException("服务暂时不可用");
            } else {
                // 其他异常
相关推荐
技术不打烊1 小时前
10 分钟搞懂 Go 并发:Goroutine vs Thread,一看就会用
后端
r***11331 小时前
SpringBoot教程(三十二) SpringBoot集成Skywalking链路跟踪
spring boot·后端·skywalking
u***45751 小时前
SpringBoot Maven 项目 pom 中的 plugin 插件用法整理
spring boot·后端·maven
x***13391 小时前
MySQL 篇 - Java 连接 MySQL 数据库并实现数据交互
java·数据库·mysql
武子康1 小时前
大数据-169 Elasticsearch 入门到可用:索引/文档 CRUD 与搜索最小示例
大数据·后端·elasticsearch
xuanzdhc2 小时前
Gitgit
java·linux·运维·服务器·c++·git
q***33372 小时前
Spring boot启动原理及相关组件
数据库·spring boot·后端
无心水2 小时前
【Python实战进阶】7、Python条件与循环实战详解:从基础语法到高级技巧
android·java·python·python列表推导式·python条件语句·python循环语句·python实战案例
一点★2 小时前
“equals”与“==”、“hashCode”的区别和使用场景
java·开发语言