Evolution of the Dubbo Framework
Origin: Alibaba open-sourced the Dubbo framework on October 27, 2011, introducing comprehensive service governance to the Java ecosystem. It quickly became a standard for many organizations outside the Alibaba ecosystem.
Stagnation: Following the release of version 2.5.3 in October 2012, major development halted. Maintenance was minimal between 2013 and 2014, leaving Spring support stuck at version 2.5.6.
Community Intervention: During the lull, organizations like Dangdang forked the project (creating Dubbox) to support newer Spring versions and REST protocols. Netease Koala also maintained a private fork (Dubbok).
Revival: On September 7, 2017, Alibaba resumed active development with version 2.5.4. At the 2017 Yunqi Conference, Dubbo was declared a key strategic project.
Consolidation: Version 2.6.0 (January 2018) merged changes from the Dubbox fork, unifying the ecosystem.
Top-Level Status: Donated to the Apache Foundation in February 2018, Dubbo graduated as a top-level Apache project in May 2019 after releasing version 2.7.x, which introduced significant features like Nacos/Consul support and an overhauled async model.
Dubbo Invocation Patterns
Dubbo supports four distinct communication patterns:
- Oneway (Fire-and-Forget): The client sends a request but expects no response. There is no
respobject returned. Note that a method returningvoidis not necessarily Oneway; Dubbo still constructs a response behind the scenes unless explicitly configured. - Sync (Blocking): The default behavior. The client sends a request and blocks the thread until a response is received or a timeout occurs.
- Future (Client-Polling): The client sends a request, receives a
Futureobject immediately, and decides when to block (callingget()) to retrieve the result. - Calback (Event-Driven): The client passes a callback logic. The thread does not block; the framwork executes the callback logic automatically when the response arrives.
Source Code Analysis: Async Implementation
Legacy Approach (Dubbo 2.6.x)
In DubboInvoker.doInvoke(), the logic relies on checking URL parameters to determine the invocation mode:
boolean isAsync = RpcUtils.isAsync(getUrl(), invocation);
boolean isOneway = RpcUtils.isOneway(getUrl(), invocation);
if (isOneway) {
// Send request without expecting response
} else if (isAsync) {
// Return future via RpcContext
ResponseFuture future = currentClient.request(inv, timeout);
RpcContext.getContext().setFuture(new FutureAdapter<>(future));
return new RpcResult();
} else {
// Sync: Block immediately
ResponseFuture future = currentClient.request(inv, timeout);
return (Result) future.get();
}
The core difference between Sync and Async in 2.6.x is who calls future.get(). In Sync, the framework calls it internally. In Async, the framework places the future in RpcContext, and the consumer must manually retrieve and call get().
Modern Approach (Dubbo 2.7.x)
The 2.7.x branch refactored this significantly.
- InvokeMode Enum:
RpcInvocationnow contains aInvokeModeattribute (SYNC, ASYNC, FUTURE, CALLBACK) determined earlier in the filter chain, rather than raw boolean checks in the invoker. - AsyncToSyncInvoker: A wrapper invoker handles the transition. If the mode is SYNC, it wraps the async execution and calls
future.get()automatically.
// Simplified logic in AsyncToSyncInvoker
if (InvokeMode.SYNC == invocation.getInvokeMode()) {
// Block here if sync
AsyncRpcResult asyncResult = (AsyncRpcResult) invoker.invoke(invocation);
return asyncResult.get();
}
// Otherwise, proceed async
return invoker.invoke(invocation);
Practical Implementation
Legacy Style (2.6.x)
Configuration required XML or annotation attributes:
<dubbo:reference id="legacyService" interface="com.example.LegacyService" async="true"/>
Usage involved the RpcContext, which was prone to pollution if not handled carefully:
legacyService.invokeMethod("test");
Future<String> responseFuture = RpcContext.getContext().getFuture();
String result = responseFuture.get(); // Manual blocking
Modern Style (2.7.x)
The framework leverages CompletableFuture directly in the interface definition, removing the need for RpcContext hacks.
Interface Definition:
public interface ModernService {
String greet(String name);
default CompletableFuture<String> greetAsync(String name) {
return CompletableFuture.completedFuture(greet(name));
}
}
Asynchronous Usage:
CompletableFuture<String> future = modernService.greetAsync("World");
future.whenComplete((response, error) -> {
if (error != null) {
System.err.println("Request failed: " + error.getMessage());
} else {
System.out.println("Response: " + response);
}
});
// Thread continues immediately without blocking
This approach aligns with standard Java 8 asynchronous programming patterns, supports callbacks natively via whenComplete or thenAccept, and eliminates context pollution issues.