在项目中同时使用SpringCloud和Dubbo,注册中心选用Eureka?

文章目录

一、前置知识

1、在Spring Boot中使用Dubbo?

需要配置服务提供者和服务消费者

引入Dubbo相关依赖:

java 复制代码
<dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo-spring-boot-starter</artifactId>
    <version>2.7.8</version>
</dependency>

1)配置服务提供者

1、在配置文件中配置Dubbo的相关信息:

java 复制代码
# Dubbo应用名称
dubbo.application.name=forlan-provider
# Dubbo注册中心地址
dubbo.registry.address=zookeeper://localhost:2181
# Dubbo服务协议
dubbo.protocol.name=dubbo
dubbo.protocol.port=20880

2、在服务提供者的启动类上添加@EnableDubbo注解:

java 复制代码
@SpringBootApplication
@EnableDubbo
public class ForlanProviderApplication {
    public static void main(String[] args) {
        SpringApplication.run(ProviderApplication.class, args);
    }
}

3、编写服务提供者的接口和实现类:

java 复制代码
public interface HelloService {
    String sayHello(String name);
}

@Service
public class HelloServiceImpl implements HelloService {
    @Override
    public String sayHello(String name) {
        return "Hello, " + name + "!";
    }
}

2)配置服务消费者

1、在配置文件中配置Dubbo的相关信息:

java 复制代码
# Dubbo应用名称
dubbo.application.name=forlan-consumer
# Dubbo注册中心地址
dubbo.registry.address=zookeeper://localhost:2181
# Dubbo服务协议
dubbo.protocol.name=dubbo
dubbo.protocol.port=20880

2、在服务消费者的启动类上添加@EnableDubbo注解:

java 复制代码
@SpringBootApplication
@EnableDubbo
public class ConsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication.class, args);
    }
}

3、编写服务消费者的代码:

java 复制代码
@RestController
public class HelloController {
    @Reference
    private HelloService helloService;

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

2、在Spring Boot中使用Eureka?

需要配置Eureka服务器和客户端

1)Eureka服务

添加Eureka依赖:在您的pom.xml文件中添加以下依赖项:

java 复制代码
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>

添加配置

什么都不配,Eureka服务的默认端口号是8761

配置Eureka服务器:在您的Spring Boot应用程序的主类上添加@EnableEurekaServer注解,以将应用程序标记为Eureka服务器

java 复制代码
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客户端

添加以下依赖项

java 复制代码
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

添加配置

java 复制代码
spring.application.name=forlan
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka

启用Eureka客户端:在您的Spring Boot应用程序的主类上添加@EnableDiscoveryClient注解,以将应用程序标记为Eureka客户端。例如:

java 复制代码
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);
    }
}

完成上述配置后,您的Spring Boot应用程序将作为Eureka客户端注册到Eureka服务器,并可以通过Eureka服务器进行服务发现和负载均衡。

二、项目代码分析

1、dubbo服务提供者

1)启动类

java 复制代码
@SpringBootApplication
@EnableDubbo
public class ForlanProviderApplication {
    public static void main(String[] args) {
        SpringApplication.run(ForlanProviderApplication.class, args);
    }
}

2)配置类

yml 复制代码
# Dubbo应用名
dubbo.application.name=forlan-provider
# 设置应用日志记录器,如Log4j、Logback和Java自带的JUL(Java Util Logging),来记录Dubbo框架的日志信息
dubbo.application.logger=slf4j
# 扫描注册com.forlan包下的所有服务接口
dubbo.scan.base-packages=com.msedu.study
# Dubbo协议名称
dubbo.protocol.name=dubbo
# Dubbo协议端口号
dubbo.protocol.port=20883
# Dubbo协议使用的线程池类型为fixed(固定大小线程池)
dubbo.protocol.threadpool=fixed
# Dubbo协议使用的线程数量为1000,表示同时处理的请求数量
dubbo.protocol.threads=1000
# Dubbo协议使用的线队列长度为2000,当线程数达到最大值时,新请求会被放入任务队列中等待处理
dubbo.protocol.queues=2000
# 服务提供者的标签,可以设置不同环境为不同标签
dubbo.provider.tag=v2.8
# apiVersionFilter 版本过滤器,保证tag延续
dubbo.provider.filter=apiVersionFilter,providerFilter
# Dubbo使用的注册中心为zookeeper,地址为zk-cs:2181
dubbo.registry.address=zookeeper://zk-cs:2181
# 指定额外的注册中心键和值
dubbo.registry.extra-keys=default.dubbo.tag
# 使用简化的注册中心实现来减少注册中心的复杂性和性能开销
dubbo.registry.simplified=true
# Dubbo不在启动时检查注册中心的可用性
dubbo.registry.check=false
# 在Dubbo中,注册中心可以按照不同的分组来进行管理和区分,这里指定注册中心的分组为 `formal` 
dubbo.registry.group=formal

# 省略其它无关配置...
标签过滤器

添加过滤器传递dubbo.tag,用于标识和区分不同的服务

java 复制代码
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;
	}

}
提供者过滤器

在每个rpc被调用前执行特定逻辑,清空本地线程变量

java 复制代码
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 {
		// 清空本地线程变量
    	ThreadLocalUtil.clearThreadVarible();
    	Result result = invoker.invoke(invocation);
        return result;
	}
}

2、dubbo服务消费者/Controller

1)启动类

java 复制代码
@SpringBootApplication
@EnableDiscoveryClient
@EnableDubbo
public class ForlanRestApplication {
    public static void main(String[] args) {
        SpringApplication.run(ForlanRestApplication.class, args);
    }
}

@EnableDiscoveryClient的作用:服务注册到服务注册中心,并且能够发现其他注册的微服务实例

@EnableDubbo的作用:启用Dubbo服务,标识该类作为Dubbo服务的提供者或消费者

2)配置类

java 复制代码
# Dubbo应用名
dubbo.application.name=forlan-consumer
# 设置应用日志记录器,如Log4j、Logback和Java自带的JUL(Java Util Logging),来记录Dubbo框架的日志信息
dubbo.application.logger=slf4j
# 扫描注册com.forlan包下的所有服务接口
dubbo.scan.base-packages=com.forlan
# Dubbo协议名称
dubbo.protocol.name=dubbo
# Dubbo协议端口号
dubbo.protocol.port=20880
# 消费者在启动时是否检查服务提供者的可用性,false表示在调用服务时才检查
dubbo.consumer.check=false
# 消费者调用服务的超时时间,这里设置为50秒
dubbo.consumer.timeout=50000
# 消费者在服务调用失败时的重试次数,默认值为0,表示不进行重试
dubbo.consumer.retries=0
# 消费者使用的过滤器链
dubbo.consumer.filter=consumerFilter,-consumercontext,tagRouterFilter
# Dubbo使用的注册中心为zookeeper,地址为zk-cs:2181
dubbo.registry.address=zookeeper://zk-cs:2181
# 指定额外的注册中心键和值
dubbo.registry.extra-keys=default.dubbo.tag
# 使用简化的注册中心实现来减少注册中心的复杂性和性能开销
dubbo.registry.simplified=true
# Dubbo不在启动时检查注册中心的可用性
dubbo.registry.check=false
# 在Dubbo中,注册中心可以按照不同的分组来进行管理和区分,这里指定注册中心的分组为 `formal` 
dubbo.registry.group=formal
# 省略其它无关配置...
ConsumerFilter

获取公共参数(accessToken、requestUUID),从本地线程取,设置到dubbo接口上下文中

java 复制代码
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 {
	    
        Result result = null;
        
        try {
        	// 调用dubbo接口前存入上下文参数
        	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,表示移除默认的ConsumerContextFilter,用TagRouterFilter替换,之所以要替换,是因为dubbo自带的过滤器会在调用完成之后清理掉Attachments,由于我们需要在Attachments中保留dubbo.tag,否则无法找到对应的服务,这就是TagRouterFilter的作用,其实就是保留了tag路由,RpcContext.getContext().setAttachment("dubbo.tag", invocation.getAttachment("dubbo.tag"))

java 复制代码
@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());
		//保留tag路由
		RpcContext.getContext().setAttachment("dubbo.tag", invocation.getAttachment("dubbo.tag"));
		return result;
	}

}

在Dubbo中,AbstractRouter是一个抽象类,用于实现路由策略。它的作用是根据一定的规则,决定将请求路由到哪个服务提供者。具体的路由策略可以通过继承AbstractRouter类并实现其中的方法来自定义。Dubbo提供了多种内置的路由策略,如基于标签、基于权重等。通过配置AbstractRouter的实现类,可以灵活地控制服务的路由行为。

3、Eureka服务端

1)启动类

java 复制代码
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)配置类

java 复制代码
# 客户端是否注册到eureka,默认true,这里为false
eureka.client.register-with-eureka=false
# 是否从Eureka服务器获取注册信息,如果单节点,不需要同步其他节点数据,用false
eureka.client.fetch-registry=false
# true:实例以IP的形式注册;false:实例以主机名的形式注册
eureka.instance.preferIpAddress=true
# 客户端注册到的Eureka服务器地址
eureka.client.serviceUrl.defaultZone=http://localhost:8300/eureka/
#Eureka服务器中用于清理过期实例的驱逐间隔时间,默认5000ms
eureka.server.evictionIntervalTimerInMs=5000
#  Eureka服务器更新响应缓存的时间间隔,服务器本地会缓存一份,确保响应性能,但又需要确保是最新的,需要定期拉取
eureka.server.responseCacheUpdateIntervalMs=5000 
# Eureka服务器是否使用只读响应缓存,false表示可以对缓存修改,一般建议false
eureka.server.use-read-only-response-cache=false
# Eureka服务器是否启用自我保护机制,默认true
eureka.server.enable-self-preservation=false

4、Eureka客户端

1)启动类

其实已经作为dubbo的消费者,一样的启动类

2)配置类

java 复制代码
#eureka地址
eureka.client.serviceUrl.defaultZone=http://localhost:8300/eureka/
# 服务示例的版本号
eureka.instance.metadata-map.version=v2.8
# true:实例以IP的形式注册;false:实例以主机名的形式注册
eureka.instance.preferIpAddress=true
# Eureka客户端发送心跳保持租约的时间间隔,默认30s,这里设置为3s,即每3秒发送一次续约
eureka.instance.leaseRenewalIntervalInSeconds=3
# Eureka客户端租约到期时间,默认90s,这里设置为10s,即服务器10s内没收到心跳,则认为该实例不可用
eureka.instance.leaseExpirationDurationInSeconds=10
# true:客户端会从服务注册中心获取并缓存服务注册表的信息;false:仅使用本地缓存的信息,客户端默认为true
eureka.client.fetchRegistry=true
# 客户端获取服务注册表的间隔时间,即刷新本地缓存时间间隔,默认30s
eureka.client.registryFetchIntervalSeconds=5

一般的话,eureka.instance.leaseExpirationDurationInSeconds设置为eureka.instance.leaseRenewalIntervalInSeconds的两倍或更多,以确保在网络故障或其他问题时仍能保持可用

三、总结

在项目中,SpringCloud和Dubbo配置使用,其实还是注册到两个注册中心。

  • Eureka

网关和对外提供API访问的服务都注册到Eureka,主要是为了方便网关进行路由,网关可以从Eureka获取服务的注册信息,包括服务的主机和端口等信息。这样,网关就可以根据需要将请求路由到相应的服务实例上,实现请求的转发和负载均衡。同时,通过Eureka的服务发现机制,网关可以动态地获取服务实例的变化,以便及时更新路由规则。这种方式可以提高系统的灵活性和可扩展性,使网关能够自动适应服务实例的变化。

  • Zookeeper

相关service服务提供者和对外提供API访问的服务注册到Zookeeper,一方作为Dubbo服务提供者,一方作为Dubbo服务消费者,通过dubbo进行调用

总的来说,就是对外提供API访问的的服务,和网关一起注册到Eureka,方便网关路由,并且这些服务也作为Dubbo消费者,注册到Zookeeper,同时相关服务提供者注册到Zookeeper即可,两者通过Dubbo进行调用。

思考题

1、Dubbo可以使用的注册中心?

默认使用的是Zookeeper作为注册中心,还可以使用Eureka、Nacos作为注册中心。

2、Dubbo集成使用Eureka和Zookeeper有什么区别?

区别在于注册中心的选择和配置方式

  • 当集成Eureka时,注册中心使用的是Eureka,而集成Zookeeper时,注册中心使用的是Zookeeper
  • Eureka是基于RESTful的服务注册与发现组件,适用于微服务架构,而Zookeeper是一个分布式协调服务,适用于分布式系统

3、服务A只注册到Eureka,Dubbo服务提供者B注册到Zookeeper,此时,服务A可以调用到B?

可以,但需要在服务A中手动指定服务B的地址信息。如果想通过dubbo调用,是不可以的,因为服务A只注册到Eureka,而Dubbo默认使用的是Zookeeper作为注册中心,所以服务A无法通过Dubbo直接调用到服务B,那么服务A需要将自己注册到Zookeeper作为Dubbo的注册中心,才能拉取到服务注册列表,进行dubbo调用。

4、对外提供API访问的服务同时注册到了Eureka和Zookeeper中,两个独立注册中心,需要同步机制来保证一致性?

Eureka的心跳间隔默认为30秒,项目中设置为3s;而Zookeeper的心跳间隔为两倍的tickTime,默认情况下tickTime为2000毫秒(2秒),所以心跳间隔为4秒。两者的心跳间隔不一致,会导致服务状态出现短暂的不一致,可以调整心跳间隔保持一致,也可以需要使用同步处理机制。

具体方案有:

  1. 通过监听器机制:在服务注册时,可以注册监听器用于监听Eureka和Zookeeper中的服务变化。当服务状态发生变化时,监听器可以接收到通知,并更新另一个注册中心中的服务状态。
  2. 通过定时任务:定时任务可以周期性地检查Eureka和Zookeeper中的服务状态,如果发现状态不一致,则进行相应的同步操作。
  3. 通过事件驱动:当服务状态变化时,Eureka和Zookeeper可以发布事件通知其他服务节点,其他节点通过接收事件来进行状态同步。
  4. 双向注册中心同步:在服务注册或状态变更时,同时将变更信息同步到两个注册中心中,保证双方的服务状态一致性。

5、对外提供API访问的服务为什么要同时注册到了Eureka和Zookeeper?

  • 兼容性问题:为了保持兼容性,同时注册到两个注册中心,以便能够被不同的组件发现和调用
  • 弹性和高可用性要求:如果其中一个注册中心出现故障或不可用,另一个注册中心仍然可以正常工作,确保系统的稳定性
  • 跨平台支持:支持跨不同平台或技术栈的服务发现和调用
相关推荐
微学AI2 天前
百度、115、夸克太分散?用 LitePan 把多网盘和影音入口收拢到一起
dubbo
行百里er2 天前
Spring Insight 里上报 Span 为什么用 JDK HttpClient 而不是 RestTemplate
spring boot·spring cloud·监控
陈皮波比茶2 天前
SpringCloudAlibaba
java·spring cloud·微服务
能源革命2 天前
AI 日报 2026-09-10
人工智能·dubbo
欢迎来到祖安!2 天前
企业微信 API 接口能力介绍:支持消息收发、图片发送、表情互动、联系人管理等多场景应用
java·前端·数据仓库·spring cloud·企业微信
Meta393 天前
PostgreSQL报SELECT rule‘s target entry X has different type from column “XXX“
数据库·postgresql·dubbo
行百里er3 天前
BlockingQueue:异步批量上报
spring boot·spring cloud·监控
vHelios3 天前
【电商项目】生成二维码测试报错Dubbo超时以及data无返回问题
dubbo
wno7044 天前
Spring Cloud Hystrix Dashboard仪表盘
spring·spring cloud·hystrix
BUG指挥官4 天前
Redis已接入AI
数据库·人工智能·spring boot·redis·spring cloud