需要配置服务提供者和服务消费者
引入Dubbo相关依赖:
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.8</version>
</dependency>
1、在配置文件中配置Dubbo的相关信息:
# Dubbo应用名称
dubbo.application.name=forlan-provider
# Dubbo注册中心地址
dubbo.registry.address=zookeeper://localhost:2181
# Dubbo服务协议
dubbo.protocol.name=dubbo
dubbo.protocol.port=20880
2、在服务提供者的启动类上添加@EnableDubbo注解:
@SpringBootApplication
@EnableDubbo
public class ForlanProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderApplication.class, args);
}
}
3、编写服务提供者的接口和实现类:
public interface HelloService {
String sayHello(String name);
}
@Service
public class HelloServiceImpl implements HelloService {
@Override
public String sayHello(String name) {
return "Hello, " + name + "!";
}
}
1、在配置文件中配置Dubbo的相关信息:
# Dubbo应用名称
dubbo.application.name=forlan-consumer
# Dubbo注册中心地址
dubbo.registry.address=zookeeper://localhost:2181
# Dubbo服务协议
dubbo.protocol.name=dubbo
dubbo.protocol.port=20880
2、在服务消费者的启动类上添加@EnableDubbo注解:
@SpringBootApplication
@EnableDubbo
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
3、编写服务消费者的代码:
@RestController
public class HelloController {
@Reference
private HelloService helloService;
@GetMapping("/hello")
public String sayHello(@RequestParam String name) {
return helloService.sayHello(name);
}
}
需要配置Eureka服务器和客户端
添加Eureka依赖:在您的pom.xml文件中添加以下依赖项:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
添加配置
什么都不配,Eureka服务的默认端口号是8761
配置Eureka服务器:在您的Spring Boot应用程序的主类上添加@EnableEurekaServer注解,以将应用程序标记为Eureka服务器
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);
}
}
添加以下依赖项
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
添加配置
spring.application.name=forlan
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka
启用Eureka客户端:在您的Spring Boot应用程序的主类上添加@EnableDiscoveryClient注解,以将应用程序标记为Eureka客户端。例如:
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服务器进行服务发现和负载均衡。
@SpringBootApplication
@EnableDubbo
public class ForlanProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ForlanProviderApplication.class, args);
}
}
# 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,用于标识和区分不同的服务
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被调用前执行特定逻辑,清空本地线程变量
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;
}
}
@SpringBootApplication
@EnableDiscoveryClient
@EnableDubbo
public class ForlanRestApplication {
public static void main(String[] args) {
SpringApplication.run(ForlanRestApplication.class, args);
}
}
@EnableDiscoveryClient的作用:服务注册到服务注册中心,并且能够发现其他注册的微服务实例
@EnableDubbo的作用:启用Dubbo服务,标识该类作为Dubbo服务的提供者或消费者
# 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
# 省略其它无关配置...
获取公共参数(accessToken、requestUUID),从本地线程取,设置到dubbo接口上下文中
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;
}
}
项目中配置了-consumercontext,表示移除默认的ConsumerContextFilter,用TagRouterFilter替换,之所以要替换,是因为dubbo自带的过滤器会在调用完成之后清理掉Attachments,由于我们需要在Attachments中保留dubbo.tag,否则无法找到对应的服务,这就是TagRouterFilter的作用,其实就是保留了tag路由,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());
//保留tag路由
RpcContext.getContext().setAttachment("dubbo.tag", invocation.getAttachment("dubbo.tag"));
return result;
}
}
在Dubbo中,AbstractRouter是一个抽象类,用于实现路由策略。它的作用是根据一定的规则,决定将请求路由到哪个服务提供者。具体的路由策略可以通过继承AbstractRouter类并实现其中的方法来自定义。Dubbo提供了多种内置的路由策略,如基于标签、基于权重等。通过配置AbstractRouter的实现类,可以灵活地控制服务的路由行为。
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);
}
}
# 客户端是否注册到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
其实已经作为dubbo的消费者,一样的启动类
#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配置使用,其实还是注册到两个注册中心。
网关和对外提供API访问的服务都注册到Eureka,主要是为了方便网关进行路由,网关可以从Eureka获取服务的注册信息,包括服务的主机和端口等信息。这样,网关就可以根据需要将请求路由到相应的服务实例上,实现请求的转发和负载均衡。同时,通过Eureka的服务发现机制,网关可以动态地获取服务实例的变化,以便及时更新路由规则。这种方式可以提高系统的灵活性和可扩展性,使网关能够自动适应服务实例的变化。
相关service服务提供者和对外提供API访问的服务注册到Zookeeper,一方作为Dubbo服务提供者,一方作为Dubbo服务消费者,通过dubbo进行调用
总的来说,就是对外提供API访问的的服务,和网关一起注册到Eureka,方便网关路由,并且这些服务也作为Dubbo消费者,注册到Zookeeper,同时相关服务提供者注册到Zookeeper即可,两者通过Dubbo进行调用。
默认使用的是Zookeeper作为注册中心,还可以使用Eureka、Nacos作为注册中心。
区别在于注册中心的选择和配置方式
可以,但需要在服务A中手动指定服务B的地址信息。如果想通过dubbo调用,是不可以的,因为服务A只注册到Eureka,而Dubbo默认使用的是Zookeeper作为注册中心,所以服务A无法通过Dubbo直接调用到服务B,那么服务A需要将自己注册到Zookeeper作为Dubbo的注册中心,才能拉取到服务注册列表,进行dubbo调用。
Eureka的心跳间隔默认为30秒,项目中设置为3s;而Zookeeper的心跳间隔为两倍的tickTime,默认情况下tickTime为2000毫秒(2秒),所以心跳间隔为4秒。两者的心跳间隔不一致,会导致服务状态出现短暂的不一致,可以调整心跳间隔保持一致,也可以需要使用同步处理机制。
具体方案有: