Dubbo高级特性与应用
Dubbo异步调用
Consumer端异步调用
Consumer端异步调用是指消费者在调用远程服务时不同步等待结果,可以先去处理其他业务逻辑,在需要结果时再获取。
sequenceDiagram
participant App as 业务代码
participant Async as 异步任务
participant Service as 远程服务
participant Other as 其他业务
App->>Async: 提交异步调用任务
App->>Other: 继续处理其他业务
Async->>Service: 发起远程调用
Service->>Service: 执行业务逻辑
Other->>App: 其他业务完成
Service->>Async: 返回调用结果
App->>Async: 获取调用结果
Async->>App: 返回执行结果实现方式 - CompletableFuture:
服务接口保持同步定义:
public interface InventoryService {
boolean deductStock(String productId, int quantity);
}Consumer端使用CompletableFuture实现异步:
@RestController
public class OrderController {
@DubboReference
private InventoryService inventoryService;
@PostMapping("/createOrder")
public String createOrder(OrderRequest request) {
// 异步调用库存服务
CompletableFuture<Boolean> stockFuture =
CompletableFuture.supplyAsync(() -> {
return inventoryService.deductStock(
request.getProductId(),
request.getQuantity()
);
});
// 可以继续处理其他业务
System.out.println("继续处理其他业务逻辑...");
// 需要结果时再获取
stockFuture.whenComplete((result, ex) -> {
if (ex != null) {
System.err.println("库存扣减失败: " + ex.getMessage());
} else {
System.out.println("库存扣减结果: " + result);
}
});
return "订单创建中";
}
}优势:
- 提高系统响应速度
- 充分利用CPU资源
- 支持并发调用多个服务
- 业务逻辑不被阻塞
Provider端异步调用
Provider端异步调用是指服务提供方本身就提供异步接口,接口返回CompletableFuture类型。
graph TB
A[客户端调用] --> B[接收CompletableFuture]
B --> C[继续其他处理]
D[Provider后台线程] --> E[执行业务逻辑]
E --> F[完成Future]
F --> G[通知客户端]
G --> H[获取最终结果]
style A fill:#4A90E2,color:#fff
style D fill:#50C878,color:#fff
style F fill:#E85D75,color:#fff
style H fill:#4A90E2,color:#fff服务提供方定义异步接口:
@DubboService
public class NotificationServiceImpl implements NotificationService {
@Override
public CompletableFuture<String> sendNotification(String userId, String message) {
// 建议使用自定义线程池
return CompletableFuture.supplyAsync(() -> {
try {
// 模拟发送通知的耗时操作
Thread.sleep(ThreadLocalRandom.current().nextInt(1000));
// 发送短信、邮件等
String result = "通知发送成功: 用户" + userId
+ ", 消息: " + message;
return result;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("通知发送失败", e);
}
});
}
}服务消费方调用:
@RestController
public class UserController {
@DubboReference
private NotificationService notificationService;
@PostMapping("/notify")
public String notifyUser(String userId, String message) {
// 调用异步接口,立即返回Future
CompletableFuture<String> future =
notificationService.sendNotification(userId, message);
// 注册回调处理结果
future.whenComplete((result, throwable) -> {
if (throwable != null) {
System.err.println("通知失败: " + throwable.getMessage());
} else {
System.out.println("通知结果: " + result);
}
});
return "通知发送中";
}
}AsyncContext异步上下文
除了CompletableFuture,Dubbo还提供了类似Servlet 3.0的AsyncContext机制。
使用AsyncContext实现异步:
@DubboService
public class ReportServiceImpl implements ReportService {
@Override
public String generateReport(String reportType) {
// 开启异步上下文
final AsyncContext asyncContext = RpcContext.startAsync();
new Thread(() -> {
// 必须在第一句执行,切换上下文
asyncContext.signalContextSwitch();
try {
// 模拟耗时的报表生成
Thread.sleep(3000);
String report = "报表生成完成: " + reportType;
// 写回响应
asyncContext.write(report);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
asyncContext.write("报表生成失败");
}
}).start();
// 立即返回null,实际结果通过AsyncContext异步返回
return null;
}
}版本差异:
- Dubbo 2.6.x及之前: 使用Future
- Dubbo 2.7及之后: 推荐使用CompletableFuture
Dubbo泛化调用
泛化调用的应用场景
泛化调用允许在没有服务提供方API的情况下调用远程服务。这在以下场景特别有用:
graph TB
subgraph 典型应用场景
A[测试平台<br/>无需依赖所有API]
B[API网关<br/>统一服务入口]
C[服务治理平台<br/>动态调用服务]
D[跨语言调用<br/>无Java SDK]
end
G[泛化调用GenericService]
A --> G
B --> G
C --> G
D --> G
style G fill:#50C878,color:#fff
style A fill:#4A90E2,color:#fff
style B fill:#9B59B6,color:#fff核心价值:
- 无需依赖服务提供方的API jar包
- 只需要知道服务接口的全限定名
- 通过泛化接口调用任意服务
- 降低系统间的耦合度
泛化调用实现
配置泛化引用:
<dubbo:reference id="userService"
interface="com.example.UserService"
generic="true" />或使用注解:
@Configuration
public class GenericConfig {
@Bean
public ReferenceConfig<GenericService> userService() {
ReferenceConfig<GenericService> reference =
new ReferenceConfig<>();
reference.setInterface("com.example.UserService");
reference.setGeneric("true");
return reference;
}
}执行泛化调用:
@Service
public class GenericInvokeService {
@Autowired
private ApplicationContext applicationContext;
public Object invokeService() {
// 获取泛化服务
GenericService userService =
(GenericService) applicationContext.getBean("userService");
// 方法名
String methodName = "getUserInfo";
// 参数类型数组
String[] parameterTypes = new String[] {
"java.lang.Long"
};
// 参数值数组
Object[] args = new Object[] {
12345L
};
// 执行泛化调用
Object result = userService.$invoke(
methodName,
parameterTypes,
args
);
return result;
}
}复杂对象调用:
// 调用带对象参数的方法
String methodName = "createOrder";
String[] parameterTypes = new String[] {
"com.example.dto.OrderDTO"
};
// 使用Map表示复杂对象
Map<String, Object> orderMap = new HashMap<>();
orderMap.put("userId", 12345L);
orderMap.put("productId", 67890L);
orderMap.put("quantity", 2);
orderMap.put("totalAmount", 199.99);
Object[] args = new Object[] { orderMap };
Object result = orderService.$invoke(
methodName,
parameterTypes,
args
);泛化调用的返回值
泛化调用的返回值会被反序列化为Map、List等基本类型:
Object result = userService.$invoke("getUserInfo",
new String[]{"java.lang.Long"},
new Object[]{12345L});
if (result instanceof Map) {
Map<String, Object> userMap = (Map<String, Object>) result;
System.out.println("用户ID: " + userMap.get("userId"));
System.out.println("用户名: " + userMap.get("username"));
System.out.println("邮箱: " + userMap.get("email"));
}Dubbo缓存机制
缓存的作用
Dubbo提供了结果缓存机制,主要用于:
- 减少重复调用服务的次数
- 提高接口响应速度
- 降低Provider压力
- 提升系统整体吞吐量
graph LR
subgraph 无缓存
C1[请求1] --> S1[远程服务]
C2[请求2] --> S1
C3[请求3] --> S1
end
subgraph 有缓存
R1[请求1] --> S2[远程服务]
S2 --> CA[缓存]
R2[请求2] --> CA
R3[请求3] --> CA
end
style S1 fill:#E85D75,color:#fff
style S2 fill:#50C878,color:#fff
style CA fill:#9B59B6,color:#fff服务端缓存
服务端缓存将方法返回结果缓存到Provider端的内存中。
LRU Cache(最近最少使用):
@DubboService(cache = "lru")
public class ProductServiceImpl implements ProductService {
@Override
public Product getProductById(Long productId) {
// 查询数据库
return productRepository.findById(productId);
}
}工作原理:
- 缓存满时,淘汰最久未使用的数据
- 适合热点数据访问场景
- 缓存命中率高
Thread Local Cache(线程本地缓存):
@DubboService(cache = "threadlocal")
public class ConfigServiceImpl implements ConfigService {
@Override
public Config getConfig(String key) {
// 每个线程独立缓存
return configRepository.get(key);
}
}特点:
- 每个线程拥有独立缓存
- 线程隔离,避免并发问题
- 适合线程内重复调用场景
Concurrent Map Cache(并发Map缓存):
@DubboService(cache = "jcache")
public class UserServiceImpl implements UserService {
@Override
public User getUserInfo(Long userId) {
return userRepository.findById(userId);
}
}特点:
- 基于ConcurrentHashMap实现
- 支持高并发读写
- 缓存效率最高
客户端缓存
客户端缓存将远程调用结果缓存到Consumer端。
接口级别缓存:
@DubboReference(cache = "lru")
private ProductService productService;方法级别缓存:
<dubbo:reference interface="com.example.ProductService">
<dubbo:method name="getProductById" cache="lru" />
<dubbo:method name="searchProducts" cache="threadlocal" />
</dubbo:reference>或使用注解:
@DubboReference(methods = {
@Method(name = "getProductById", cache = "lru"),
@Method(name = "searchProducts", cache = "threadlocal")
})
private ProductService productService;缓存使用注意事项
适用场景:
- 数据更新频率低
- 读多写少的场景
- 对实时性要求不高
- 查询操作占比大
不适用场景:
- 实时性要求高的数据
- 频繁更新的数据
- 需要强一致性的场景
- 写操作为主的场景
数据一致性问题:
graph TB
A[数据库更新] -.不会通知.-> B[Dubbo缓存]
B --> C[可能返回旧数据]
D[解决方案1<br/>设置缓存过期时间]
E[解决方案2<br/>主动清理缓存]
F[解决方案3<br/>使用分布式缓存]
C -.-> D
C -.-> E
C -.-> F
style A fill:#4A90E2,color:#fff
style C fill:#E85D75,color:#fff
style D fill:#50C878,color:#fff
style E fill:#50C878,color:#fff
style F fill:#50C878,color:#fff最佳实践:
- 为缓存设置合理的过期时间
- 数据更新时主动清理缓存
- 对于关键数据不使用缓存
- 监控缓存命中率和内存使用
Dubbo优雅停机
优雅停机的必要性
应用停机时如果处理不当,会导致:
- 正在处理的请求被中断
- 客户端调用失败
- 数据不一致
- 监控告警
优雅停机确保:
- 完成正在处理的请求
- 拒绝新的请求
- 通知客户端服务下线
- 平滑释放资源
操作系统级别的支持
graph LR
A[kill -15<br/>SIGTERM] -->|通知应用| B[应用收到信号]
B --> C[执行清理逻辑]
C --> D[正常退出]
E[kill -9<br/>SIGKILL] -->|强制杀死| F[立即终止]
F --> G[数据可能丢失]
style A fill:#50C878,color:#fff
style D fill:#50C878,color:#fff
style E fill:#E85D75,color:#fff
style G fill:#E85D75,color:#fff优雅停机命令:
kill -15 <PID>: 发送SIGTERM信号,应用可以处理清理逻辑kill -9 <PID>: 发送SIGKILL信号,强制终止(不推荐)
Docker容器:
docker stop: 等同于kill -15,等待10秒后强制杀死docker kill: 等同于kill -9,立即杀死
JVM的Shutdown Hook
Java应用通过Shutdown Hook实现优雅停机:
public class ShutdownHookDemo {
public static void main(String[] args) {
// 注册JVM关闭钩子
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
System.out.println("收到停机信号,开始清理资源...");
// 停止接收新请求
// 等待现有请求处理完成
// 关闭数据库连接
// 释放其他资源
System.out.println("资源清理完成,准备退出");
}));
// 应用运行
while (true) {
// 业务逻辑
}
}
}Spring的优雅停机
Spring通过事件机制支持优雅停机:
@Component
public class GracefulShutdownListener
implements ApplicationListener<ContextClosedEvent> {
@Override
public void onApplicationEvent(ContextClosedEvent event) {
System.out.println("Spring容器正在关闭...");
// 执行清理逻辑
// 关闭线程池
// 释放资源
System.out.println("清理完成");
}
}Dubbo的优雅停机实现
Dubbo基于Spring的事件机制实现优雅停机:
sequenceDiagram
participant OS as 操作系统
participant JVM as JVM
participant Spring as Spring容器
participant Dubbo as Dubbo框架
participant App as 应用服务
OS->>JVM: kill -15 发送SIGTERM
JVM->>JVM: 触发Shutdown Hook
JVM->>Spring: 通知容器关闭
Spring->>Dubbo: 触发ContextClosedEvent
Dubbo->>Dubbo: 标记为不接受新请求
Dubbo->>App: 拒绝新调用
Dubbo->>Dubbo: 检查线程池状态
Dubbo->>Dubbo: 等待现有请求完成(最多10秒)
Dubbo->>Dubbo: 注销服务注册
Dubbo->>Spring: 关闭完成
Spring->>JVM: 容器关闭完成
JVM->>OS: 进程退出Provider端停机流程:
- 收到停机信号
- 标记为不接受新请求
- 新请求直接返回错误,让Consumer重试其他Provider
- 检查线程池中是否有正在执行的请求
- 等待正在执行的请求完成(默认10秒)
- 从注册中心注销服务
- 关闭网络连接
- 释放资源
Consumer端停机流程:
- 收到停机信号
- 不再发起新的远程调用
- 新调用在客户端直接报错
- 检查是否有未返回的远程请求
- 等待远程请求返回结果
- 关闭网络连接
- 释放资源
核心源码:
public class SpringExtensionFactory implements ExtensionFactory {
public static void addApplicationContext(ApplicationContext context) {
CONTEXTS.add(context);
if (context instanceof ConfigurableApplicationContext) {
// 注册Spring的Shutdown Hook
((ConfigurableApplicationContext) context).registerShutdownHook();
// 注销Dubbo自己的Shutdown Hook,使用Spring的
DubboShutdownHook.getDubboShutdownHook().unregister();
}
// 添加容器关闭监听器
BeanFactoryUtils.addApplicationListener(
context,
SHUTDOWN_HOOK_LISTENER
);
}
}优雅停机配置
配置等待时间:
# 配置优雅停机等待时间(毫秒)
dubbo.service.shutdown.wait=15000超时后强制关闭:
- 如果等待超时,Dubbo会强制关闭
- 避免长时间卡住无法停机
- 需要合理设置超时时间
通过完善的优雅停机机制,Dubbo确保应用在发布、升级、下线等场景下平滑过渡,避免服务中断和数据丢失。
总结
Dubbo作为成熟的RPC框架,提供了丰富的高级特性:
- 异步调用: 提升系统响应速度和资源利用率
- 泛化调用: 降低系统耦合,支持动态调用
- 结果缓存: 优化性能,减少重复调用
- 优雅停机: 保证服务平滑下线,避免请求中断
合理使用这些特性,可以构建高性能、高可用、易维护的微服务系统。
更新: 2025-12-04 17:40:55
原文: https://www.yuque.com/u22210564/zoxfmt/doc-27-dubbo-06-dubbo