Apache Dubbo RPC Framework Overview
Apache Dubbo is a high-performance RPC (Remote Procedure Call) service development framework designed to address service governance and communication challenges in microservice architectures. The framework provides multi-language SDK implementations including Java and Golang. Microservices built with Dubbo inherently possess remote address discovery and communication capabilities between eachother. By leveraging Dubbo's extensive service governance features, users can implement various service governance requirements such as service discovery, load balancing, and traffic scheduling. Dubbo is designed for high extensibility, allowing users to easily implement customized logic for traffic interception and service routing.
Dubbo has been widely adopted across industries, with its stability and comprehensive features thoroughly tested in production environments. The framework has evolved to Dubbo 3.0, which maintains a strong position in scenarios involving usability, large-scale microservice practices, cloud-native infrastructure adaptation, and security. It remains a top choice for RPC service frameworks.
- Distributed System Fundamentals
1.1 Why Do We Need Distribution?
Why do modern large-scale internet applications adopt distributed architectures?
The key consideration is: only when the processing capacity of a single machine cannot meet growing computing and storage demands, and hardware improvements (such as adding memory, using better CPUs) fail to provide better optimization, should we consider a distributed architecture. The fundamental problems that distributed systems aim to solve are the same as those in standalone systems. However, distributed architectures introduce multiple nodes and network communication topologies that create many issues not present in standalone systems. Resolving these issues introduces additional mechanisms and protocols, potentially leading to more unexpected problems.
Simply put, a distributed architecture distributes application pressure by deploying different modules on different servers, with modules communicating through lightweight mechanisms to reduce the processing burden on the application system.
With the growth of internet applications, the scale of website applications continues to expand, and traditional vertical application architectures can no longer meet requirements. Distributed service architectures and flow computing architectures have become necessary. The following illustrates the evolution of website architectures:
1. Single Application Architecture (1-10 users)
When website traffic is minimal, only one application is needed with all functionalities deployed together to reduce deployment nodes and costs. At this stage, data access frameworks (ORM) that simplify CRUD operations are crucial. This architecture suits small websites and management systems due to its simplicity.
Drawbacks:
- Difficult to scale performance
- Collaboration challenges (not conducive to team development)
- Maintenance difficulties (e.g., functional adjustments require repackaging and restarting)
2. Vertical Application Architecture (10-1000 users)
As traffic increases, optimizing a single application becomes less effective. The application is split into independent applications to improve processing efficiency. At this stage, web frameworks (MVC) for accelerating frontend development become essential.
Advantages: Independent module deployment through business division reduces maintenance and deployment difficulties. Teams can focus on specific areas, making performance scaling more targeted.
Drawbacks: Public modules cannot be reused, high module coupling, and increased maintenance costs.
3. Distributed Service Architecture (1000-10000 users)
Improvement over vertical application architecture: as vertical applications grow, interaction between applications becomes inevitable. Core business is extracted as independent services, gradually forming a stable service center that enables frontend applications to respond more quickly to changing market demands. At this stage, distributed service frameworks (RPC) that improve business reuse and manage service collaboration become essential.
4. Flow Computing Architecture (10000+ users)
As services proliferate, reasonable utilization of service resources becomes increasingly important (for example, some services may have very high call frequency while others have very low). In such cases, a scheduling center is needed to dynamically manage cluster capacity based on访问压力 and improve cluster utilization.
SOA (Service Oriented Architecture) serves as the key technology for resource scheduling and governance centers that provide machine utilization.
1.2 Understanding RPC
What is RPC?
RPC (Remote Procedure Call) is a method of inter-process communication. It is a technical approach rather than a specification or protocol. RPC allows a program to call procedures or functions in another address space (typically on another machine connected via a network) without requiring developers to implement the details of the remote call. Whether calling local or remote functions, developers essentially write the same calling code.
In essence, when server A hosts an application that needs to call a function provided by an application on server B, since they don't share memory space, the call must be expressed through the network, conveying both the semantics and data of the call.
Why Use RPC?
RPC is needed for requirements that cannot be met through local calls within a single process or even a single computer, such as communication between different systems or even different organizations. When horizontal scaling of computing capability is required, applications need to be deployed across multiple machines in a cluster. RPC enables calling remote functions as if they were local functions.
Basic RPC working principle involves client stub and server stub components that act as "assistants" for coordinating remote function calls. Their main responsibilities include:
Client Stub:
- Client invocation
- Serialization
- Message sending
- Deserialization of response
- Return call results
Server Stub:
- Deserialization of request
- Local service invocation
- Return processing results
- Serialization of results
- Response message
Two main factors affect RPC call efficiency:
- Communication (lightweight communication is particularly important)
- Serialization and deserialization (whether using XML, JSON, or binary streams for data transmission)
Based on the RPC concept, various RPC frameworks have emerged in the market, including Dubbo, gRPC, Thrift, and HSF (High Speed Service Framework).
- Dubbo Framework
2.1 Dubbo Role Architecture
The following describes the node roles in Dubbo architecture:
| Node | Role Description |
|---|---|
| Provider | Service provider that exposes services |
| Consumer | Service consumer that invokes remote services |
| Registry | Service registry and discovery center |
| Monitor | Monitoring center for call statistics and timing |
| Container | Service running container |
Understanding these roles, let's examine Dubbo's workflow:
- Start: The service container is responsible for starting, loading, and running service providers.
- Register: Service providers register their services with the registry upon startup.
- Subscribe: Service consumers subscribe to required services from the registry upon startup.
- Notify: The registry returns the provider address list to consumers. If changes occur, the registry pushes updated data to consumers through persistent connections.
- Invoke: Service consumers select a provider from the address list using soft load balancing algorithms. If the call fails, another provider is selected.
- Count: Service consumers and providers accumulate call counts and times in memory, sending statistical data to the monitoring center every minute.
2.2 Setting Up the Dubbo Environment
1. Registry Center
Dubbo supports mainstream registry centers including Zookeeper, Nacos, Multicast, and Redis. This guide uses Zookeeper as the registry center, which is a tree-structured directory service supporting change notifications, making it suitable as Dubbo's service registry. This is also the officially recommended registry.
Docker commands to set up Zookeeper:
# Pull latest Zookeeper image
docker pull zookeeper
# Start Zookeeper
docker run -d -p 2181:2181 --name zookeeper --restart always [imageId]
# Check Zookeeper status
./zkServer.sh status
2. Project Integration
Dubbo is essentially a JAR package that helps Java applications connect to Zookeeper for service consumption or provision. It is not a standalone service application.
3. Project Setup
Create two projects: a provider (service producer) and a consumer (service consumer). These projects will be used to test various Dubbo features.
2.3 Hello World Implementation
Consider a scenario with a user service (provider) and an order service (consumer) deployed on different servers. When creating an order, the order service needs to retrieve user information including phone numbers and shipping addresses. This requires the order service to remotely invoke interfaces or methods provided by the user service.
1. Registering the Provider with Zookeeper
Adding Dependencies
Add Dubbo dependencies to the provider project (using SpringBoot for the provider, and native Spring for the consumer):
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.8</version>
</dependency>
Dubbo uses Curator as the programming client for interacting with Zookeeper Server. Add the Zookeeper dependency:
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-dependencies-zookeeper</artifactId>
<version>2.7.8</version>
<type>pom</type>
</dependency>
Note: Official documentation indicates version compatibility issues between Dubbo and dubbo-dependencies-zookeeper. If compatibility issues arise, you can add Curator and Zookeeper Client dependencies directly:
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>4.2.0</version>
</dependency>
Configuration Methods
Method 1: Annotation-based Configuration
Using @DubboService to expose services and @DubboReference to reference services:
dubbo.application.name=provider-server
dubbo.registry.address=zookeeper://180.76.238.29:2181
dubbo.registry.timeout=20000
dubbo.protocol.name=dubbo
dubbo.protocol.port=20880
dubbo.scan.base-packages=com.example.provider.service
Method 2: XML Configuration
spring-dubbo.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://code.alibabatech.com/schema/dubbo
http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<dubbo:application name="user-service"/>
<dubbo:registry protocol="zookeeper" address="127.0.0.1:2181"/>
<dubbo:protocol name="dubbo" port="20880"/>
<dubbo:service interface="com.example.provider.service.UserService" ref="userService"/>
<bean id="userService" class="com.example.provider.service.impl.UserServiceImpl"/>
</beans>
Import in the main application:
@ImportResource(locations = {"classpath:spring/spring-dubbo.xml"})
@SpringBootApplication
public class ApplicationMain {
public static void main(String[] args) {
SpringApplication.run(ApplicationMain.class, args);
}
}
Method 3: Java Configuration Class
@Configuration
public class DubboConfiguration {
@Autowired
private UserService userService;
@Bean
public ApplicationConfig applicationConfig() {
ApplicationConfig config = new ApplicationConfig();
config.setName("user-service");
return config;
}
@Bean
public RegistryConfig registryConfig() {
RegistryConfig config = new RegistryConfig();
config.setProtocol("zookeeper");
config.setAddress("127.0.0.1:2181");
return config;
}
@Bean
public ProtocolConfig protocolConfig() {
ProtocolConfig config = new ProtocolConfig();
config.setName("dubbo");
config.setPort(20882);
return config;
}
@Bean
public ServiceConfig<UserService> serviceConfig() {
ServiceConfig<UserService> serviceConfig = new ServiceConfig<>();
serviceConfig.setInterface(UserService.class);
serviceConfig.setRef(userService);
serviceConfig.setVersion("1.0-SNAPSHOT");
return serviceConfig;
}
}
Verify service registration in Zookeeper:
[zk: localhost:2181(CONNECTED) 12] ls /
[dubbo, zookeeper]
[zk: localhost:2181(CONNECTED) 13] ls /dubbo
[metadata, config, com.example.provider.service.UserService]
The UserService node indicates successful registration.
2. Consumer Subscribing to Services
spring-dubbo.xml for consumer:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://code.alibabatech.com/schema/dubbo
http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<dubbo:application name="consumer-server"/>
<dubbo:registry address="zookeeper://180.76.238.29:2181" timeout="20000"/>
<dubbo:reference interface="com.example.provider.service.UserService" id="userService"/>
</beans>
Important notes: If Java objects are transferred during remote calls, they must be serializable. Additionally, consumers need to maintain the same package structure as providers, or extract shared interfaces into a common module.
3. Testing Remote Procedure Calls
Provider side - create the service interface and implementation:
public interface UserService {
String login(String username, String password);
}
@DubboService
public class UserServiceImpl implements UserService {
@Override
public String login(String username, String password) {
JSONObject response = new JSONObject();
response.put("token", UUID.randomUUID().toString());
response.put("username", username);
return response.toString();
}
}
Consumer side - invoke the remote service:
@SpringBootTest
class ConsumerApplicationTests {
@Autowired
private UserService userService;
@Test
void testLogin() {
String result = userService.login("admin", "123");
System.out.println(result);
}
}
2.4 Dubbo-Admin Setup
After Alibaba donated Dubbo to Apache, Apache重构了 dubbo-admin with Vue for the frontend and SpringBoot for the backend, offering improved UI and enhanced functionality.
1. Download and Setup
Download the source code from GitHub: https://github.com/apache/dubbo-admin. Find Dubbo Admin 0.3.0 from releases (compatible with Dubbo 2.7+).
2. Backend Configuration
Modify application.properties to configure Zookeeper connection:
admin.registry.address=zookeeper://180.76.238.29:2181?timeout=20000
admin.config-center=zookeeper://180.76.238.29:2181
admin.metadata-report.address=zookeeper://180.76.238.29:2181
Start the backend and access http://127.0.0.1:8080/swagger-ui.html
3. Frontend Configuration
Install dependencies: npm install (requires Node.js version >10.x). Modify vue.config.js to set the proxy target:
module.exports = {
outputDir: "target/dist",
lintOnSave: false,
devServer: {
port: 8082,
proxy: {
'/': {
target: 'http://127.0.0.1:8080/',
changeOrigin: true
}
}
}
}
Start the frontend with npm run serve.
2.5 Dubbo Configuration Options
Dubbo supports three configuration approaches: XML, properties, and API configuration.
1. Startup Check
Dubbo checks service availability on startup. Disable with check="false":
<!-- Disable specific service check -->
<dubbo:reference interface="com.example.service.BarService" check="false"/>
<!-- Disable all service checks -->
<dubbo:consumer check="false"/>
<!-- Disable registry check -->
<dubbo:registry check="false" />
2. Timeout Configuration
<!-- Interface-level timeout -->
<dubbo:reference interface="com.example.service.UserService" timeout="3000"/>
<!-- Global timeout -->
<dubbo:consumer application="consumer-server" timeout="5000"/>
<!-- Method-level timeout -->
<dubbo:reference interface="com.example.service.UserService">
<dubbo:method name="login" timeout="4000"/>
</dubbo:reference>
Priority: method-level > interface-level > global configuration
3. Retry Configuration
<dubbo:reference interface="com.example.service.UserService">
<dubbo:method name="login" timeout="4000" retries="3"/>
</dubbo:reference>
Note: Retry is suitable for idempotent methods.
4. Multiple Versions
Support multiple versions for the same service:
@DubboService(version = "v1.0")
public class UserServiceImplV1 implements UserService { }
@DubboService(version = "v2.0")
public class UserServiceImplV2 implements UserService { }
Consumer configuration:
<!-- Call v1.0 -->
<dubbo:reference interface="com.example.service.UserService" version="v1.0"/>
<!-- Call v2.0 -->
<dubbo:reference interface="com.example.service.UserService" version="v2.0"/>
<!-- Random version -->
<dubbo:reference interface="com.example.service.UserService" version="*"/>
5. Local Stub
Execute client-side logic before remote calls:
<dubbo:reference interface="com.example.service.UserService" stub="true"/>
Stub implementation:
public class UserServiceStub implements UserService {
private final UserService userService;
public UserServiceStub(UserService userService) {
this.userService = userService;
}
@Override
public String login(String username, String password) {
if ("admin".equals(username)) {
return "{\"message\":\"Admin user doesn't need login\",\"success\":true}";
}
try {
return userService.login(username, password);
} catch (Exception e) {
return "{\"message\":\"Login failed: \" + e.getMessage(),\"success\":false}";
}
}
}
- Dubbo High Availability
3.1 Service Downtime and Direct Connection
When the registry (Zookeeper) experiences downtime, Dubbo can still consume exposed services using local cache information that records provider details.
Additional high-availability features:
- Monitoring center downtime doesn't affect usage (only loses some sampled data)
- Database failure allows the registry to serve cached service lists but prevents new registrations
- Registry peer clusters automatically switch when one fails
- Stateless providers allow any node to go down without affecting usage
- When all providers fail, consumers will retry indefinitely
Direct Connection Configuration
Bypass the registry and connect directly:
<dubbo:reference interface="com.example.service.UserService" url="127.0.0.1:20880"/>
Or using annotations:
@DubboReference(url = "127.0.0.1:20880")
private UserService userService;
Summary: Dubbo can call services via local cache when the registry fails, and can also communicate directly without a registry.
3.2 Load Balancing in Cluster Mode
Setting Up the Cluster Environment
Provider implementation:
@DubboService(
interfaceClass = ServerPortService.class,
version = "v0.1",
group = "server"
)
public class ServerPortServiceImpl implements ServerPortService {
@Value("${server.port}")
private String port;
@Override
public String getServerPort() {
return port;
}
}
Start multiple provider instances with different ports:
java -Dserver.port=8081 -Ddubbo.protocol.port=20881 -jar provider.jar
java -Dserver.port=8082 -Ddubbo.protocol.port=20882 -jar provider.jar
java -Dserver.port=8083 -Ddubbo.protocol.port=20883 -jar provider.jar
Load Balancing Strategies
Dubbo provides four load balancing strategies:
1. Random LoadBalance: Weighted random selection. Default strategy where each service has equal weight.
2. RoundRobin LoadBalance: Weighted round-robin. Configure with loadbalance attribute:
@DubboReference(loadbalance = "roundrobin")
private ServerPortService serverPortService;
3. LeastActive LoadBalance: Least active calls - tracks request completion time and selects the fastest service.
4. ConsistentHash LoadBalance: Consistent hashing - selects services based on hash values for stable request distribution.
3.3 Service Degradation
What is Service Degradation?
In complex distributed or microservice systems with numerous dependencies (where service A depends on B, which depends on C), if service C times out, the entire call chain fails, leading to cascading failures and potentially a service avalanche effect.
Service degradation provides a mechanism to prevent cascading failures when issues occur in specific nodes or services.
1. Mock/Force Return
Temporarily mock a service to return null, ensuring core business continues正常运行. Configure in dubbo-admin dynamic configuration:
configVersion: v0.1
scope: service
enabled: true
key: server:com.example.service.ServerPortService:v0.1
configs:
- addresses: [0.0.0.0]
side: consumer
parameters:
force: return null
2. Cluster Fault Tolerance
When cluster calls fail, Dubbo provides multiple fault tolerance strategies, with failover as the default. Other options include failfast, failback, and fork cluster modes.
Example configuring failfast:
<dubbo:reference cluster="failfast" />
- Dubbo Architecture Principles
Dubbo's architecture is built on several key principles that enable its high performance and reliability in distributed systems. The framework handles service discovery, communication, and governance through a well-designed architecture that separates concerns and provides flexibility for various deployment scenarios.
The core components work together to provide seamless remote service invocation while maintaining service governance capabilities essential for production microservices environments.