When using SpringCloud and Dubbo at the same time in the project, should Eureka be used as the registration center?

Article directory

  • 1. Prerequisite knowledge
    • 1. Using Dubbo with Spring Boot?
      • 1) Configure service provider
      • 2) Configure service consumers
    • 2. Using Eureka with Spring Boot?
      • 1) Eureka server
      • 2) Eureka client
  • 2. Project code analysis
    • 1. Eureka server
      • 1) Startup class
      • 2) Configuration class
    • 2. Dubbo service provider
      • 1) Startup class
      • 2) Configuration class
        • tag filter
        • Provider filter
    • 3. Dubbo service consumer/Eureka client
      • 1) Startup class
      • 2) Configuration class
        • Eureka related
        • Dubbo related
          • ConsumerFilter
          • TagRouterFilter
  • 3. Summary
    • Thinking questions
      • 1. Which registration center can Dubbo use?
      • 2. Why can’t Dubbo register with Eureka?
      • 3. Service A is only registered with Eureka, and Dubbo service provider B is only registered with Zookeeper. At this time, service A can call B?
      • 4. The service where the web application is located is registered in Eureka and Zookeeper at the same time. Two independent registration centers require a synchronization mechanism to ensure consistency?
      • 5. Why are two different registration centers used at the same time in the project?
      • 6. Why is the service where the web application is located registered with Eureka and Zookeeper at the same time?

1. Prerequisite knowledge

1. Use Dubbo in Spring Boot?

Add Dubbo related dependencies in the pom.xml file: required by both service providers and service consumers

<dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo-spring-boot-starter</artifactId>
    <version>2.7.8</version>
</dependency>

1) Configure service provider

1. Configure Dubbo related information in the configuration file:

# Dubbo application name
dubbo.application.name=forlan-provider
# Dubbo registration center address
dubbo.registry.address=zookeeper://localhost:2181
#DubboService Agreement
dubbo.protocol.name=dubbo
dubbo.protocol.port=20880

2. Add the @EnableDubbo annotation to the service provider’s startup class:

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

3. Write the interface and implementation class of the service provider:
Add @Service and specify the interfaceClass attribute to specify the interface to be exposed

public interface HelloService {<!-- -->
    String sayHello(String name);
}

@Service(interfaceClass = HelloService.class)
public class HelloServiceImpl implements HelloService {<!-- -->
    @Override
    public String sayHello(String name) {<!-- -->
        return "Hello, " + name + "!";
    }
}

2) Configure service consumers

1. Configure Dubbo related information in the configuration file:

# Dubbo application name
dubbo.application.name=forlan-consumer
# Dubbo registration center address
dubbo.registry.address=zookeeper://localhost:2181
#DubboService Agreement
dubbo.protocol.name=dubbo
dubbo.protocol.port=20880

2. Add the @EnableDubbo annotation to the startup class of the service consumer:
Add the @Reference annotation to declare a reference to the Dubbo service

@SpringBootApplication
@EnableDubbo
public class ConsumerApplication {<!-- -->
    public static void main(String[] args) {<!-- -->
        SpringApplication.run(ConsumerApplication.class, args);
    }
}

3. Write code to serve consumers:

@RestController
public class HelloController {<!-- -->
    @Reference
    private HelloService helloService;

    @GetMapping("/hello")
    public String sayHello(@RequestParam String name) {<!-- -->
        return helloService.sayHello(name);
    }
}

2. Use Eureka in Spring Boot?

1) Eureka server

Add Eureka server dependencies in the pom.xml file

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>

Add configuration:

# Nothing matches. The default port number of the Eureka service is 8761.

Configure the Eureka server: Add the @EnableEurekaServer annotation on the main class of the Spring Boot application to mark the application as an Eureka server.

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {<!-- -->
    public static void main(String[] args) {<!-- -->
        SpringApplication.run(EurekaServerApplication.class, args);
    }
}

2) Eureka client

Add Eureka client dependency in the pom.xml file:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

Add configuration:

spring.application.name=forlan
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka

Configure the Eureka client: Add the @EnableDiscoveryClient annotation on the main class of the Spring Boot application to mark the application as an Eureka client.

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
public class ForlanApplication {<!-- -->
    public static void main(String[] args) {<!-- -->
        SpringApplication.run(ForlanApplication .class, args);
    }
}

After completing the above configuration, the Spring Boot application will be registered to the Eureka server as an Eureka client, and service discovery and load balancing can be performed through the Eureka server.

2. Project code analysis

Eureka related dependencies:


<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

Dubbo related dependencies:

<dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo-spring-boot-starter</artifactId>
    <version>2.7.1</version>
</dependency>
<dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo-dependencies-bom</artifactId>
    <version>2.7.1</version>
    <type>pom</type>
    <scope>import</scope>
</dependency>
<dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo</artifactId>
    <version>2.7.1</version>
    <exclusions>
        <exclusion>
            <groupId>org.springframework</groupId>
            <artifactId>spring</artifactId>
        </exclusion>
        <exclusion>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
        </exclusion>
        <exclusion>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
        </exclusion>
    </exclusions>
</dependency>

1. Eureka server

1) Startup class

With @EnableEurekaServer, the Spring Boot application is configured as an Eureka Server, enabling it to receive registration requests from other service instances and maintain status information of the service instance.

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {<!-- -->
    public static void main(String[] args) {<!-- -->
        SpringApplication.run(EurekaServerApplication.class, args);
    }
}

2) Configuration class

# Whether the client is registered to eureka, the default is true, here it is false
eureka.client.register-with-eureka=false
# Whether to obtain registration information from the Eureka server. If there is a single node and there is no need to synchronize other node data, use false.
eureka.client.fetch-registry=false
# true: The instance is registered in the form of IP; false: The instance is registered in the form of host name
eureka.instance.preferIpAddress=true
#The Eureka server address registered by the client
eureka.client.serviceUrl.defaultZone=http://localhost:8300/eureka/
#The eviction interval used to clean up expired instances in the Eureka server, the default is 5000ms
eureka.server.evictionIntervalTimerInMs=5000
# The time interval for the Eureka server to update the response cache. The server will cache a copy locally to ensure response performance, but it also needs to ensure that it is the latest and needs to be pulled regularly.
eureka.server.responseCacheUpdateIntervalMs=5000
# Whether the Eureka server uses read-only response cache, false means the cache can be modified, false is generally recommended
eureka.server.use-read-only-response-cache=false
# Whether the Eureka server enables the self-protection mechanism, the default is true
eureka.server.enable-self-preservation=false

2. Dubbo service provider

1) Startup class

Add @EnableDubbo to configure the Spring Boot application as a Dubbo service provider or consumer

@SpringBootApplication
@EnableDubbo
public class ForlanProviderApplication {<!-- -->
    public static void main(String[] args) {<!-- -->
        SpringApplication.run(ForlanProviderApplication.class, args);
    }
}

2) Configuration class

# Dubbo application name
dubbo.application.name=forlan-provider
# Set up application loggers, such as Log4j, Logback and JUL (Java Util Logging) that comes with Java, to record the log information of the Dubbo framework
dubbo.application.logger=slf4j
# Scan and register all service interfaces under the com.forlan package
dubbo.scan.base-packages=com.msedu.study
# Dubbo protocol name
dubbo.protocol.name=dubbo
# Dubbo protocol port number
dubbo.protocol.port=20883
# The thread pool type used by Dubbo protocol is fixed (fixed size thread pool)
dubbo.protocol.threadpool=fixed
#The number of threads used by the Dubbo protocol is 1000, indicating the number of requests processed simultaneously
dubbo.protocol.threads=1000
# The thread queue length used by Dubbo protocol is 2000. When the number of threads reaches the maximum value, new requests will be put into the task queue to be processed.
dubbo.protocol.queues=2000
# The label of the service provider, you can set different labels for different environments
dubbo.provider.tag=v2.8
# apiVersionFilter version filter to ensure tag continuation
dubbo.provider.filter=apiVersionFilter,providerFilter
# The registration center used by Dubbo is zookeeper, and the address is zk-cs:2181
dubbo.registry.address=zookeeper://zk-cs:2181
#Specify additional registry keys and values
dubbo.registry.extra-keys=default.dubbo.tag
# Use a simplified registry implementation to reduce the complexity and performance overhead of the registry
dubbo.registry.simplified=true
# Dubbo does not check the availability of the registration center at startup
dubbo.registry.check=false
# In Dubbo, the registration center can be managed and distinguished according to different groups. Here, the grouping of the registration center is specified as `formal`
dubbo.registry.group=formal

# Omit other irrelevant configuration...
Tag filter

Add a filter to pass dubbo.tag to identify and distinguish different services

import org.apache.dubbo.rpc.Filter;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.Result;
import org.apache.dubbo.rpc.RpcContext;
import org.apache.dubbo.rpc.RpcException;
import org.apache.dubbo.common.Constants;

public class ApiVersionFilter implements Filter {<!-- -->
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {<!-- -->
String version = invocation.getAttachment("dubbo.tag");
if (StringUtil.isNotEmpty(version)) {<!-- -->
RpcContext.getContext().setAttachment("dubbo.tag", version);
}
Result result = invoker.invoke(invocation);
return result;
}

}
Provider filter

Execute specific logic and clear local thread variables before each rpc is called.

import org.apache.dubbo.common.Constants;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.rpc.Filter;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.Result;
import org.apache.dubbo.rpc.RpcException;

@Activate(group = "provider",value = "providerFilter")
public class ProviderFilter implements Filter{<!-- -->
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {<!-- -->
// Clear local thread variables
    ThreadLocalUtil.clearThreadVarible();
    Result result = invoker.invoke(invocation);
        return result;
}
}

3. Dubbo service consumer/Eureka client

1) Startup class

@SpringBootApplication
@EnableDiscoveryClient
@EnableDubbo
public class ForlanRestApplication {<!-- -->
    public static void main(String[] args) {<!-- -->
        SpringApplication.run(ForlanRestApplication.class, args);
    }
}

The role of @EnableDiscoveryClient: the service is registered in the service registration center and can discover other registered microservice instances
The role of @EnableDubbo: enable Dubbo service and identify this class as a provider or consumer of Dubbo service

2) Configuration class

Eureka related
#eurekaAddress
eureka.client.serviceUrl.defaultZone=http://localhost:8300/eureka/
# Version number of service example
eureka.instance.metadata-map.version=v2.8
# true: The instance is registered in the form of IP; false: The instance is registered in the form of host name
eureka.instance.preferIpAddress=true
# The time interval for the Eureka client to send heartbeats to maintain the lease. The default is 30s. Here it is set to 3s, that is, a renewal is sent every 3 seconds.
eureka.instance.leaseRenewalIntervalInSeconds=3
# Eureka client lease expiration time, the default is 90s, here it is set to 10s, that is, if the server does not receive a heartbeat within 10s, the instance is considered unavailable
eureka.instance.leaseExpirationDurationInSeconds=10
# true: the client will obtain and cache the service registry information from the service registration center; false: only use locally cached information, the client defaults to true
eureka.client.fetchRegistry=true
# The interval for the client to obtain the service registry, that is, the interval for refreshing the local cache, the default is 30s
eureka.client.registryFetchIntervalSeconds=5

Generally speaking, eureka.instance.leaseExpirationDurationInSeconds is set to twice or more than eureka.instance.leaseRenewalIntervalInSeconds to ensure that it remains available in the event of network failure or other problems.

Dubbo related
# Dubbo application name
dubbo.application.name=forlan-consumer
# Set up application loggers, such as Log4j, Logback and JUL (Java Util Logging) that comes with Java, to record the log information of the Dubbo framework
dubbo.application.logger=slf4j
# Scan and register all service interfaces under the com.forlan package
dubbo.scan.base-packages=com.forlan
# Dubbo protocol name
dubbo.protocol.name=dubbo
# Dubbo protocol port number
dubbo.protocol.port=20880
# Whether the consumer checks the availability of the service provider when it starts, false means it checks when the service is called.
dubbo.consumer.check=false
# The timeout for consumers to call the service, here is set to 50 seconds
dubbo.consumer.timeout=50000
# The number of times the consumer retries when the service call fails. The default value is 0, which means no retries will be made.
dubbo.consumer.retries=0
# The filter chain used by the consumer
dubbo.consumer.filter=consumerFilter,-consumercontext,tagRouterFilter
# The registration center used by Dubbo is zookeeper, and the address is zk-cs:2181
dubbo.registry.address=zookeeper://zk-cs:2181
#Specify additional registry keys and values
dubbo.registry.extra-keys=default.dubbo.tag
# Use a simplified registry implementation to reduce the complexity and performance overhead of the registry
dubbo.registry.simplified=true
# Dubbo does not check the availability of the registration center at startup
dubbo.registry.check=false
# In Dubbo, the registration center can be managed and distinguished according to different groups. Here, the grouping of the registration center is specified as `formal`
dubbo.registry.group=formal
# Omit other irrelevant configuration...
ConsumerFilter

Get the public parameters (accessToken, requestUUID) from the local thread and set them to the dubbo interface context

import com.msedu.common.utils.ThreadUtil;
import org.apache.dubbo.common.Constants;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.rpc.*;

@Activate(group = "consumer",value = "consumerFilter")
public class ConsumerFilter implements Filter{<!-- -->

@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {<!-- -->
\t    
        Result result = null;
        
        try {<!-- -->
        //Save context parameters before calling dubbo interface
        String accessToken = (String) ThreadUtil.get("accessToken");
        String requestUUID = (String) ThreadUtil.get("requestUUID");
      RpcContext.getContext().setAttachment("accessToken", accessToken);
        RpcContext.getContext().setAttachment("requestUUID", requestUUID);

        result = invoker.invoke(invocation);
        }catch(Exception e) {<!-- -->
        throw e;
        }
        return result;
}

}
TagRouterFilter

-consumercontext is configured in the project, which means removing the default ConsumerContextFilter and replacing it with TagRouterFilter. The reason for replacement is because dubbo’s own filter will clean up Attachments after the call is completed, because we need to retain dubbo.tag in Attachments. , otherwise the corresponding service cannot be found. This is the role of TagRouterFilter. In fact, it retains the tag route, RpcContext.getContext().setAttachment(“dubbo.tag”, invocation.getAttachment(“dubbo.tag”))

@Activate(group = "consumer", order = -10000)
public class TagRouterFilter implements Filter {<!-- -->

@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {<!-- -->
RpcContext.getContext().setInvoker(invoker).setInvocation(invocation)
.setLocalAddress(NetUtils.getLocalHost(), 0)
.setRemoteAddress(invoker.getUrl().getHost(), invoker.getUrl().getPort());
if (invocation instanceof RpcInvocation) {<!-- -->
((RpcInvocation) invocation).setInvoker(invoker);
}
try {<!-- -->
RpcContext.removeServerContext();
return invoker.invoke(invocation);
} finally {<!-- -->
RpcContext.getContext().clearAttachments();
}
}

@Override
public Result onResponse(Result result, Invoker<?> invoker, Invocation invocation) {<!-- -->
RpcContext.getServerContext().setAttachments(result.getAttachments());
//Keep tag routing
RpcContext.getContext().setAttachment("dubbo.tag", invocation.getAttachment("dubbo.tag"));
return result;
}

}

In Dubbo, AbstractRouter is an abstract class used to implement routing strategies. Its function is to decide which service provider to route the request to based on certain rules. Specific routing strategies can be customized by inheriting the AbstractRouter class and implementing its methods. Dubbo provides a variety of built-in routing strategies, such as label-based, weight-based, etc. By configuring the implementation class of AbstractRouter, you can flexibly control the routing behavior of the service.

3. Summary

In the project, if SpringCloud and Dubbo are to be configured and used, two registration centers are required.

  • Eureka

Both the gateway and Tomcat (Web application) are registered to Eureka, mainly to facilitate routing by the gateway. The gateway can obtain the registration information of the service from Eureka, including the host and port of the service. In this way, the gateway can route the request to the corresponding service instance as needed to achieve request forwarding and load balancing. At the same time, through Eureka’s service discovery mechanism, the gateway can dynamically obtain changes in service instances in order to update routing rules in a timely manner. This approach can improve the flexibility and scalability of the system and enable the gateway to automatically adapt to changes in service instances.

  • Zookeeper

Server (providing services or resources to clients) is registered with Zookeeper. One party acts as a Dubbo service provider, and the other party acts as a Dubbo service consumer, making calls through Dubbo.

In general, the gateway is registered to Eureka, and the web application is registered to both Eureka and Zookeeper. On the one hand, it can be forwarded to the web application through gateway routing, and on the other hand, it serves as a Dubbo consumer; the server registers with Zookeeper as a service provider. , can also be used as a consumer to call other services through Dubbo.

Thinking questions

1. What registration center can Dubbo use?

Zookeeper is used as the registration center by default. Nacos, Consul, etc. can also be used as the registration center, but Eureka cannot be used!

2. Why can’t Dubbo register to Eureka?

Dubbo and Eureka are two different service registration and discovery frameworks, and the integration between them is not directly supported.

Dubbo is a Java-based distributed service framework that uses its own registration center to manage service registration and discovery. Dubbo’s registration center can be Zookeeper, Etcd, Consul, etc. Dubbo’s design goal is to provide high-performance and low-latency RPC communication, so it uses some specific protocols and mechanisms to achieve these goals.

Eureka is Netflix’s open source service registration and discovery framework. It is based on RESTful style and is mainly used to build cloud native applications and microservice architecture. Eureka provides functions such as service registration, discovery and load balancing, and is tightly integrated with microservice frameworks such as Spring Cloud.

Due to the different designs and implementations of Dubbo and Eureka, integration between them is not directly supported. If you want to use Eureka as the service registration and discovery framework while using Dubbo, you can consider using Nacos in the Spring Cloud Alibaba project as the registration center. Nacos supports service registration and discovery of both Dubbo and Spring Cloud. This enables the integration of Dubbo and Eureka.

3. Service A is only registered with Eureka, and Dubbo service provider B is only registered with Zookeeper. At this time, service A can call B?

No, because Eureka and ZooKeeper are two different registration centers, and the service registration and discovery between them are independent of each other. Service A can only obtain the address information of other services registered through Eureka through the Eureka registration center, but cannot obtain the address information of service B registered through ZooKeeper.

If service A needs to call service B through Dubbo, service A needs to register itself with Zookeeper before it can pull the service registration list and make Dubbo calls.

4. The service where the web application is located is registered in Eureka and Zookeeper at the same time. Two independent registration centers require a synchronization mechanism to ensure consistency?

The default heartbeat interval of Eureka is 30 seconds, which is set to 3s in the project; while the heartbeat interval of Zookeeper is twice the tickTime. By default, the tickTime is 2000 milliseconds (2 seconds), so the heartbeat interval is 4 seconds. The inconsistent heartbeat intervals between the two will lead to temporary inconsistencies in service status. You can adjust the heartbeat intervals to keep them consistent, or you may need to use a synchronization processing mechanism.
Specific plans include:

  1. Through the listener mechanism: When registering the service, you can register a listener to monitor service changes in Eureka and Zookeeper. When the service status changes, the listener can receive notifications and update the service status in another registry.
  2. Through scheduled tasks: Scheduled tasks can periodically check the service status in Eureka and Zookeeper. If the status is found to be inconsistent, corresponding synchronization operations will be performed.
  3. Driven by events: When the service status changes, Eureka and Zookeeper can publish events to notify other service nodes, and other nodes perform status synchronization by receiving events.
  4. Bidirectional registration center synchronization: When service registration or status changes, the change information is synchronized to the two registration centers at the same time to ensure the consistency of the service status of both parties.

5. Why are two different registration centers used in the project at the same time?

At the beginning, the project was a distributed system, using Dubbo + Zookeeper. Later it was transformed into a microservice and SpringCloud was introduced. It uses Eureka by default, which is highly available and has better integration. Although Zookeeper can also be selected, in the old version , it seems that Zookeeper does not support the integration of Spring Cloud, so in the end there are two registration centers at the same time.

6. Why is the service where the web application is located registered with Eureka and Zookeeper at the same time?

  • Registering to Eureka is to facilitate routing and forwarding
  • Registering to Zookeeper is to call services through Dubbo