赞
踩
参考下列博客:
手把手教你使用Springboot整合dubbo,搭建一个微服务
负载均衡:负载均衡是指在集群中,将多个数据请求分散在不同单元上进行执行,主要为了提高系统容错能力和加强系统对数据的处理能力。
dubbo负载均衡机制是决定一次服务调用哪个提供者的服务
在dubbo运行的过程中,有两个位置会用到负载均衡:

我们修改dubbo-provider的代码
UserServiceImpl.java
@DubboService
@Service
@Slf4j
public class UserServiceImpl implements IUserService {
@Autowired
private UserMapper userMapper;
@Value("${server.port}")
private Integer port;
@Override
public User selectUserById(Long id){
log.info("当前访问的提供者的port是:{}",port);
User user=userMapper.selectUserById(id);
return user;
}
}
修改dubbo-consumer的代码
ConsumerUserController.java
@RestController
@RequestMapping("/consumer")
@Slf4j
public class ConsumerUserController {
@DubboReference(protocol = "dubbo",loadbalance = "random")
private IUserService userService;
@RequestMapping("/selectUserById/{id}")
public User getUser(@PathVariable("id")Long id){
User user=userService.selectUserById(id);
log.info("response from provider:{}",user);
return user;
}
}
修改provider的application.yml
server: port: 8091 spring: application: name: dubbo-samples-provider-springCloud datasource: username: root password: 3fa4d180 url: jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=UTC driver-class-name: com.mysql.cj.jdbc.Driver mybatis: mapper-locations: /mapper/*.xml dubbo: registry: address: zookeeper://127.0.0.1:2181 protocol: name: dubbo port: 20811
修改配置,设置多实例

运行一份ProviderApp后,修改application.yml的server.port和protocol.port为8182和20812,8183和20813,总共创建三份实例,然后运行ConsumerApp,多次访问consumer提供的接口
控制台结果如下:



说明确实为随机访问。
接下来我们修改dubbo-consumer,修改负载均衡的方式
@RestController
@RequestMapping("/consumer")
@Slf4j
public class ConsumerUserController {
@DubboReference(protocol = "dubbo",loadbalance = "roundrobin")
private IUserService userService;
@RequestMapping("/selectUserById/{id}")
public User getUser(@PathVariable("id")Long id){
User user=userService.selectUserById(id);
log.info("response from provider:{}",user);
return user;
}
}
重启consumer,多次访问selectuserById,一共访问9次,每个ProviderApp实例出现此时都为3次



Dubbo 的配置,如果存在多个的话,是会有覆盖关系的:
所以,上面4中配置的优先级是:
我们修改ProviderApp的配置,在UserServiceImpl修改负载均衡策略为轮询
@DubboService(loadbalance = "roundrobin") @Service @Slf4j public class UserServiceImpl implements IUserService { @Autowired private UserMapper userMapper; @Value("${server.port}") private Integer port; @Override public User selectUserById(Long id){ log.info("当前访问的提供者的port是:{}",port); User user=userMapper.selectUserById(id); return user; } }
修改Consumer,将负载均衡策略改为random
@RestController
@RequestMapping("/consumer")
@Slf4j
public class ConsumerUserController {
@DubboReference(protocol = "dubbo",loadbalance = "random")
private IUserService userService;
@RequestMapping("/selectUserById/{id}")
public User getUser(@PathVariable("id")Long id){
User user=userService.selectUserById(id);
log.info("response from provider:{}",user);
return user;
}
}
重启实例,多次访问selectUserById,虽然服务提供方的策略是轮询,但消费方的策略是随机,结果如下:



参考连接:Dubbo的负载均衡
远程调用是dubbo框架的核心,基本过程是,向服务端发送参数,并等待获取结果。如果调⽤过程出错则需要对异常进⾏处理。Dubbo调用类别有四种,分别是同步,异步,并行以及广播调用。默认情况下Dubbo是采⽤同步⽅式进⾏调⽤,即发起调⽤后线程将会阻塞,直到结果返回。同时为了提⾼性能,也可采用异⽅式调⽤。另外⼀些特殊的业务场景下,则可以使⽤⼴播⽅式调⽤、以及并⾏调⽤。
#### 3.1 同步调用:向远程服务器发送请求,该请求是阻塞的,直到服务器返回结果。
客户端线程发送给服务端时,其实有专门的io线程完成,它是异步发送的,但dubbo会构建一个CompletableFuture,通过它阻塞当前线程去等待结果返回,但服务端结果返回之后completableFuture填充结果,并释放阻塞的调用线程
在特殊场景下,异步调用可提高性能,比如要远程调用A、B、C,分别用时1s,2s,3s,如果是同步的话,用时6秒,如果异步调用,同时去执行这三个接口,理想情况下用时3秒,不过前提是ABC这三个方法没有参数依赖并且没有顺序依赖。
修改dubbo-api模块,修改接口
public interface IUserService {
User selectUserById(Long id);
User asyncSelectUserById(Long id);
User syncSelectUserById(long id);
}
修改dubbo-provider,修改UserServiceImpl
@DubboService(loadbalance = "roundrobin") @Service @Slf4j public class UserServiceImpl implements IUserService { @Autowired private UserMapper userMapper; @Value("${server.port}") private Integer port; @Override public User selectUserById(Long id){ log.info("当前访问的提供者的port是:{}",port); User user=userMapper.selectUserById(id); return user; } @Override public User asyncSelectUserById(Long id) { log.info("异步访问获取用户,port:{}",port); try{ Thread.sleep(1000); }catch (InterruptedException e){ e.printStackTrace(); } return userMapper.selectUserById(id); } @Override public User syncSelectUserById(Long id) { log.info("同步访问获取用户,port:{}",port); try{ Thread.sleep(1000); }catch (InterruptedException e){ e.printStackTrace(); } return userMapper.selectUserById(id); } }
修改dubbo-consumer,修改consumerUserController
@RestController @RequestMapping("/consumer") @Slf4j public class ConsumerUserController { //设置asyncSelectUserById为异步调用 @DubboReference(protocol = "dubbo",loadbalance = "random", methods = {@Method(name = "asyncSelectUserById",async = true)}) private IUserService userService; @RequestMapping("/selectUserById/{id}") public User getUser(@PathVariable("id")Long id){ User user=userService.selectUserById(id); log.info("response from provider:{}",user); return user; } @RequestMapping("/syncSelectUserById/{id}") public User asyncGetUser(@PathVariable("id")Long id){ long start = System.currentTimeMillis(); User user=null; user=userService.syncSelectUserById(id); user=userService.syncSelectUserById(id); user=userService.syncSelectUserById(id); long end=System.currentTimeMillis(); log.info("用时:{}",end-start); return user; } @RequestMapping("/asyncSelectUserById/{id}") public User asyncGetUser2(@PathVariable("id")Long id){ long start = System.currentTimeMillis(); User user=null; userService.asyncSelectUserById(id); Future<User> future1 = RpcContext.getServletContext().getFuture(); userService.asyncSelectUserById(id); Future<User> future2 = RpcContext.getServletContext().getFuture(); userService.asyncSelectUserById(id); Future<User> future3 = RpcContext.getServletContext().getFuture(); try{ user=future1.get(); user=future2.get(); user=future3.get(); }catch (InterruptedException e){ e.printStackTrace(); }catch (ExecutionException e){ e.printStackTrace(); } long end=System.currentTimeMillis(); log.info("用时:{}",end-start); return user; } }
重启服务,分别访问syncSelectUserById和asyncSelectUserById
syncSelectUserById

asyncSelectUserById

实现原理:事实上Dubbo的调用本身就是异步的,其常规的调用是通过AsyncToSyncInvoker组件,由异步转成了同步,所以异步的实现就是让该组件不去执行阻塞逻辑即可,此外,为了顺利拿到结果回执(Future),在调用发起之后其回执会被填充到RpcContext当中

为了尽可能获得更⾼的性能,以及最⾼级别的保证服务的可⽤性。⾯对多个服务,并不知道哪个处理更快。这时客户端可并⾏发起多个调⽤,只要其中⼀个成功即返回,某个服务异常直接勿略,只有所有服务多出现异常情况下才会判定调⽤出错。
由于并行调用特殊性,生产环境不建议使用,如果一定要用,请配置在method级别,这里不做案例演示
并⾏调⽤原理是通过线程池异步发送远程请求。其流程如下:
在分布式环境,⼀个服务通常有多个提供⽅。原则上调⽤任意⼀个服务,结果都应该是⼀样的。但⼀些特殊场景除外,⽐如:“通知缓存更新”,需要通知到每个服务器都要更新各⾃节点缓存。要确保每个服务都被调⽤到。⼴播调⽤⼀次调⽤,会遍历所有提供者并发起调⽤,确保所有节点都被调⽤到。任意⼀台报错就算失败
参考文章:Dubbo调用及容错机制详解
Dubbo主要内置了如下几种策略:
Failover(失败自动切换):高可用系统中的一个常用概念,服务器通常拥有主备两套机器配置,如果主服务器出现故障,则自动切换到备服务器中,从而保证了整体的高可用性。这是Dubbo对的默认容错策略,当调用出现失败时,根据配置的重试次数,会自动从其他可用地址中重新选择一个可用的地址进行调用,直到调用成功,或者达到重试的上限位置。
Failsafe(失败安全):失败安全策略的核心是即使失败了也不会影响整个调用流程。通常情况下用于旁路系统或流程中,它的失败不影响核心业务的正确性。在实现上,当出现调用失败时,会忽略此错误,并记录一条日志,同时返回一个空结果,在上游看来调用是成功的。应用场景,可以用于写入审计日志等操作。
Failfast(快速失败):某些业务场景中,某些操作可能是非幂等的,如果重复发起调用,可能会导致出现脏数据等。例如调用某个服务,其中包含一个数据库的写操作,如果写操作完成,但是在发送结果给调用方的过程中出错了,那么在调用发看来这次调用失败了,但其实数据写入已经完成。这种情况下,重试可能并不是一个好策略,这时候就需要使用到Failfast策略,调用失败立即报错。让调用方来决定下一步的操作并保证业务的幂等性。
Failback(失败自动恢复):Failback通常和Failover两个概念联系在一起。在高可用系统中,当主机发生故障,通过Failover进行主备切换后,待故障恢复后,系统应该具备自动恢复原始配置的能力。
Dubbo中的Failback策略中,如果调用失败,则此次失败相当于Failsafe,将返回一个空结果。而与Failsafe不同的是,Failback策略会将这次调用加入内存中的失败列表中,对于这个列表中的失败调用,会在另一个线程中进行异步重试,重试如果再发生失败,则会忽略,即使重试调用成功,原来的调用方也感知不到了。因此它通常适合于,对于实时性要求不高,且不需要返回值的一些异步操作。

我们以failover为例,编写案例
修改Dubbo-api的接口
在IUserService接口中添加retrySelectUserById方法
public interface IUserService {
User selectUserById(Long id);
User asyncSelectUserById(Long id);
User syncSelectUserById(Long id);
User retrySelectUserById(Long id);
}
在dubbo-provider模块中,修改UserServiceImpl,添加retrySelectUserById方法
@Override
public User retrySelectUserById(Long id) {
log.info("超时重传获取用户id,port:{}",port);
try{
Thread.sleep(4000);
}catch (InterruptedException e){
e.printStackTrace();
}
User user = userMapper.selectUserById(id);
log.info("user:{}",user);
return user;
}
在dubbo-consumer中,修改ConsumerUserController.java
@DubboReference(protocol = "dubbo",loadbalance = "random",
methods = {@Method(name = "asyncSelectUserById",async = true),
@Method(name = "retrySelectUserById",retries = 3,timeout = 2000)})
private IUserService userService;
@RequestMapping("/retrySelectUserById/{id}")
public User retrySelectUserById(@PathVariable("id")Long id){
User user = userService.retrySelectUserById(id);
log.info("retryUser:{}",user);
return user;
}
重启,访问retrySelectUserById一次,因为我们设置超时时间为2000ms,而服务端Thread.sleep(4000),所以会超时,同时因为在消费端设置retry为3,所以会重试3次,也就是说一共会访问4次方法


超时重试存在一些问题,如果我们执行的是一个插入操作,假设插入业务用时3秒,而我们超时时间设置为2秒,那么因为超时,可能会导致重复插入数据,这时就需要考虑到分布式事务以及幂等性方面的问题了,这里我们不做详细探讨。
远程服务后,客户端通常只剩下接口,而实现全在服务端,但提供方有时想在客户端也执行部分逻辑

使用场景:做ThreadLocal缓存,提前验证参数,调用失败后伪造容错数据等等,此时就需要在API中带上Stub,客户端生成Proxy实例,会把Proxy通过构造函数传给Stub,然后把Stub暴露给用户,Stub可以决定要不要去调Proxy
在dubbo-api模块中,添加IUserService2接口
public interface IUserService2 {
User getUserById(Long id);
}
在dubbo-provider模块中,添加IUserService2的实现
@Service @DubboService @Slf4j public class UserService2Impl implements IUserService2 { @Autowired private UserMapper userMapper; @Value("${server.port}") private Integer port; @Override public User getUserById(Long id) { log.info("超时重传获取用户id,port:{}",port); try{ Thread.sleep(4000); }catch (InterruptedException e){ e.printStackTrace(); } User user = userMapper.selectUserById(id); log.info("user:{}",user); return user; } }
修改dubbo-consumer模块,添加上IUserService2的本地存根
public class UserServiceStub implements IUserService2 { private IUserService2 userService2; public UserServiceStub(IUserService2 userService2){ this.userService2=userService2; } @Override public User getUserById(Long id) { try{ //检查参数是否有误 if (id<=0){ System.out.println("参数有误"); return null; } return userService2.getUserById(id); }catch (Exception e){ // e.printStackTrace(); System.out.println("启动容错==========="); User user=new User(); user.setId(0L); user.setUsername("容错"); user.setPassword("本地存根"); return user; } } }
修改ConsumerUserController,添加下述方法
@RestController
@RequestMapping("/consumer")
@Slf4j
public class ConsumerUserController {
@DubboReference(stub = "com.young.customer.stub.UserServiceStub")
private IUserService2 userService2;
@RequestMapping("/stubSelectUserById/{id}")
public User stubSelectUserById(@PathVariable("id")Long id){
return userService2.getUserById(id);
}
}
重启,访问




参考文章:Dubbo(十二)dubbo的服务版本配置以及本地存根使用介绍
灰度发布(又名金丝雀发布)是指在黑与白之间,能够平滑过渡的一种发布方式。在其上可以进行A/B testing,即让一部分用户继续用产品特性A,一部分用户开始用产品特性B,如果用户对B没有什么反对意见,那么逐步扩大范围,把所有用户都迁移到B上面来。灰度发布可以保证整体系统的稳定,在初始灰度的时候就可以发现、调整问题,以保证其影响度。灰度发布开始到结束期间的这一段时间,称为灰度期。
下面简单演示一下dubbo通过version版本号进行灰度发布
创建一个项目,假设叫dubbo02,引入下列依赖:
pom.xml:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.young</groupId> <artifactId>dubbo02</artifactId> <packaging>pom</packaging> <version>1.0-SNAPSHOT</version> <modules> <module>dubbo-api</module> <module>dubbo-provider</module> <module>dubbo-consumer</module> </modules> <properties> <maven.compiler.source>11</maven.compiler.source> <maven.compiler.target>11</maven.compiler.target> <spring-boot-dependencies.version>2.7.0</spring-boot-dependencies.version> <spring-cloud-dependencies.version>2021.0.1</spring-cloud-dependencies.version> <spring-dubbo.version>2.0.0</spring-dubbo.version> <lombok.version>1.18.22</lombok.version> <dubbo.version>3.0.2.1</dubbo.version> <apache.commons-fileupload.version>1.4</apache.commons-fileupload.version> <apache.commons-text.version>1.9</apache.commons-text.version> <apache.commons-configuration.version>1.10</apache.commons-configuration.version> <httpclient.version>4.5.12</httpclient.version> <hutool.version>5.7.7</hutool.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>${spring-boot-dependencies.version}</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-bom</artifactId> <version>${dubbo.version}</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-spring-boot-starter</artifactId> <version>${dubbo.version}</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${lombok.version}</version> <scope>compile</scope> </dependency> </dependencies> </dependencyManagement> </project>
创建dubbo-api模块,引入依赖:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>dubbo02</artifactId> <groupId>com.young</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>dubbo-api</artifactId> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.83</version> </dependency> </dependencies> <properties> <maven.compiler.source>11</maven.compiler.source> <maven.compiler.target>11</maven.compiler.target> </properties> </project>
User.java实体类
package com.young.entity;
import lombok.Data;
import java.io.Serializable;
@Data
public class User implements Serializable {
private Integer id;
private String username;
private String password;
private String school;
}
UserService.java接口
package service;
import com.young.entity.User;
public interface UserService {
User getUserById();
}
创建dubbo-provider模块,提供服务
引入依赖:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>dubbo02</artifactId> <groupId>com.young</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>dubbo-consumer</artifactId> <properties> <maven.compiler.source>11</maven.compiler.source> <maven.compiler.target>11</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-registry-zookeeper</artifactId> </dependency> <dependency> <groupId>com.young</groupId> <artifactId>dubbo-api</artifactId> <version>1.0-SNAPSHOT</version> </dependency> </dependencies> </project>
UserServiceImpl.java实现类
package com.young.service.impl; import com.young.entity.User; import org.apache.dubbo.config.annotation.DubboService; import org.springframework.stereotype.Service; import service.UserService; @Service @DubboService(version = "1.0.0") public class UserServiceImpl implements UserService { @Override public User getUserById() { User user=new User(); user.setUsername("cxy"); user.setPassword("123456"); user.setId(1); user.setSchool("华南理工大学"); return user; } }
UserServiceImpl2.java实现类
package com.young.service.impl; import com.young.entity.User; import org.apache.dubbo.config.annotation.DubboService; import org.springframework.stereotype.Service; import service.UserService; @Service @DubboService(version = "2.0.0") public class UserServiceImpl2 implements UserService { @Override public User getUserById() { User user=new User(); user.setUsername("dhi"); user.setPassword("123456"); user.setId(1); user.setSchool("潮阳一中"); return user; } }
ProviderApp.java
package com.young;
import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@EnableDubbo
public class ProviderApp {
public static void main(String[] args) {
SpringApplication.run(ProviderApp.class,args);
}
}
application.yml
server:
port: 8089
spring:
application:
name: provider-service
dubbo:
registry:
address: zookeeper://127.0.0.1:2181
protocol:
name: dubbo
port: 29089
创建dubbo-consumer模块,依赖和刚才provider模块一样
DemoController.java
package com.young.controller; import com.young.entity.User; import org.apache.dubbo.config.annotation.DubboReference; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import service.UserService; @RestController @RequestMapping("/demo") public class DemoController { @DubboReference(version = "1.0.0") private UserService userService1; @DubboReference(version = "2.0.0") private UserService userService2; @GetMapping("/v1") public User getUser1(){ return userService1.getUserById(); } @GetMapping("/v2") public User getUser2(){ return userService2.getUserById(); } }
application.yml
server:
port: 8099
spring:
application:
name: consumer-service
dubbo:
protocol:
name: dubbo
port: 29099
registry:
address: zookeeper://localhost:2181
分别访问/demo/v1 和/demo/v2


刚才的项目,只是演示了如何区分不同版本的服务,我们一般使用version或group来对新旧服务进行区分,至于灰度发布的实现,除了刚才提到的新旧版本区分之外,还需要通过使用nginx方向代理、dubbo路由规则等,来实现灰度发布。
除此之外,在灰度发布的实践过程中,我们还会遇到项目的问题:
dubbo灰度发布带来的好处:
在dubbo中,我们有时候要对调用者进行鉴权,保证我们的服务只提供给某些服务调用。因此这里就设计到Dubbo服务鉴权,关于dubbo服务鉴权,有两种方式:
我们分别对上述两种方式进行案例实现
首先是token,我们要将token从消费者服务传递到提供者服务,这里便涉及到一个隐式传参的问题,而隐式传参,可以使用上下文信息,即RpcContext实现,下面是Dubbo3中RpcContext的四大模块
ServiceContext:在 Dubbo 内部使用,用于传递调用链路上的参数信息,如 invoker 对象等
ClientAttachment:在 Client 端使用,往 ClientAttachment 中写入的参数将被传递到 Server 端
ServerAttachment:在 Server 端使用,从 ServerAttachment 中读取的参数是从 Client 中传递过来的
ServerContext:在 Client 端和 Server 端使用,用于从 Server 端回传 Client 端使用,Server 端写入到 ServerContext 的参数在调用结束后可以在 Client 端的 ServerContext 获取到
我们修改刚才灰度发布相关的代码,在dubbo-consumer模块和dubbo-provider模块加上spring-boot-starter-data-redis的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
先修改消费者模块的DemoController.java,这里的hasToken中,我们将token的值隐式传递给消费者
package com.young.controller; import com.young.entity.User; import org.apache.dubbo.config.annotation.DubboReference; import org.apache.dubbo.rpc.RpcContext; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import service.UserService; import java.util.UUID; @RestController @RequestMapping("/demo") public class DemoController { @DubboReference(version = "1.0.0") private UserService userService1; @DubboReference(version = "2.0.0") private UserService userService2; @Autowired private StringRedisTemplate stringRedisTemplate; @GetMapping("/v1") public User getUser1(){ return userService1.getUserById(); } @GetMapping("/v2") public User getUser2(){ return userService2.getUserById(); } @GetMapping("/token/yes") public User hasToken(){ String token= UUID.randomUUID().toString(); stringRedisTemplate.opsForValue().set("authorization",token); System.out.println("token:"+token); RpcContext.getClientAttachment().setAttachment("authorization",token); RpcContext.getClientAttachment().setAttachment("name","cxy"); User user = userService1.getUserById(); return user; } @GetMapping("/token/no") public User notToken(){ User user = userService1.getUserById(); return user; } @GetMapping("/name") public String name(){ String interfaceName = RpcContext.getServerContext().getInterfaceName(); String methodName = RpcContext.getServerContext().getMethodName(); String localHostName = RpcContext.getServerContext().getLocalHostName(); String remoteApplicationName = RpcContext.getServerContext().getRemoteApplicationName(); String remoteHostName = RpcContext.getServerContext().getRemoteHostName(); System.out.println("interface:"+interfaceName); System.out.println("method:"+methodName); System.out.println("localHost"+localHostName); System.out.println("remoteHost:"+remoteHostName); System.out.println("remoteApplication:"+remoteApplicationName); return remoteApplicationName; } }
修改application.yml
server: port: 8099 spring: application: name: consumer-service redis: jedis: pool: max-wait: 1000 max-idle: 8 min-idle: 0 timeout: 5000 host: 127.0.0.1 port: 6379 dubbo: protocol: name: dubbo port: 29099 registry: address: zookeeper://localhost:2181
修改dubbo-provider模块,首先,先添加TokenAuthFilter类
package com.young.filter; import org.apache.dubbo.common.extension.Activate; import org.apache.dubbo.rpc.*; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Component; import javax.annotation.Resource; @Activate @Component public class TokenAuthFilter implements Filter { @Resource private StringRedisTemplate stringRedisTemplate; private static final String TOKEN_KEY="authorization"; @Override public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException { String token = RpcContext.getServerAttachment().getAttachment(TOKEN_KEY); String name = RpcContext.getServerAttachment().getAttachment("name"); System.out.println("token:"+token+",name:"+name); if (token == null || !validateToken(token)) { throw new RpcException("UnValid Token"); } return invoker.invoke(invocation); } private boolean validateToken(String token) { String resToken = stringRedisTemplate.opsForValue().get(TOKEN_KEY); if (token.equals(resToken)) { return true; } return false; } }
接着,在resource目录下,新建META-INF目录,在META-INF目录下创建dubbo目录,然后在dubbo目录下创建org.apache.dubbo.rpc.Filter文件,文件内容如下:
tokenAuthFilter=com.young.filter.TokenAuthFilter
# 格式为: 过滤器名称=过滤器对应的包路径
然后最重要的一点,就是修改application.yml
server: port: 8089 spring: application: name: provider-service redis: jedis: pool: max-wait: 1000 max-idle: 8 min-idle: 0 timeout: 5000 host: 127.0.0.1 port: 6379 dubbo: provider: filter: tokenAuthFilter dubbo: provider: filter: tokenAuthFilter #这个必须配置,不然filter不生效 registry: address: zookeeper://127.0.0.1:2181 protocol: name: dubbo port: 29089
项目的完整结构如下:

启动项目,进行测试
访问/demo/token/yes


访问其他接口


第二种方式,是使用IP白名单来进行权限校验,我这里修改一下,改为使用服务名进行权限校验,我们先创建一个表,表中包含服务名称,以及调用者的服务名称,用来表示有哪些调用者可以使用当前服务,结构如下:

数据内容如下:

在dubbo-provider模块中,引入mysql和mybatis-plus的依赖
修改application.yml
server: port: 8089 spring: application: name: provider-service datasource: username: root password: 3fa4d180 driver-class-name: com.mysql.cj.jdbc.Driver url: mysql:jdbc://localhost:3306/young?useSSL=false&serverTimezone redis: jedis: pool: max-wait: 1000 max-idle: 8 min-idle: 0 timeout: 5000 host: 127.0.0.1 port: 6379 dubbo: provider: filter: tokenAuthFilter dubbo: provider: filter: tokenAuthFilter registry: address: zookeeper://127.0.0.1:2181 protocol: name: dubbo port: 29089 mybatis-plus: global-config: db-config: logic-not-delete-value: 0 logic-delete-value: 1
添加ServiceAuth实体类
package com.young.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableLogic; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; import java.io.Serializable; import java.time.LocalDateTime; @Data @TableName(value = "m_service_auth") public class ServiceAuth implements Serializable { @TableId(type = IdType.AUTO) private Integer id; private String providerService; private String consumerService; private LocalDateTime createTime; @TableLogic private Integer isDelete; }
ServiceAuthMapper.java
package com.young.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.young.entity.ServiceAuth;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface ServiceAuthMapper extends BaseMapper<ServiceAuth> {
}
ProviderConfigProperty.java
package com.young.config;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration
@Data
public class ProviderConfigProperty {
@Value("${spring.application.name}")
private String name;
}
ServiceAuthServiceImpl.java
package com.young.service; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.young.entity.ServiceAuth; import com.young.mapper.ServiceAuthMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; @Service public class ServiceAuthServiceImpl { @Autowired private ServiceAuthMapper serviceAuthMapper; public Boolean canVisit(String providerName,String consumerName){ LambdaQueryWrapper<ServiceAuth>queryWrapper=new LambdaQueryWrapper<>(); queryWrapper.eq(ServiceAuth::getProviderService,providerName) .eq(ServiceAuth::getConsumerService,consumerName); List<ServiceAuth> serviceAuths = serviceAuthMapper.selectList(queryWrapper); if (serviceAuths!=null&&serviceAuths.size()>0){ return true; } return false; } }
修改TokenAuthFilter.java,这里有个坑,在dubbo的拦截器中,采用@Autowired自动注入是无效的,可以采取setter方式来注入其他的bean,且不用标注注解,dubbo自己会对这些bean进行注入
package com.young.filter; import com.young.config.ProviderConfigProperty; import com.young.service.ServiceAuthService; import org.apache.dubbo.common.extension.Activate; import org.apache.dubbo.config.ProviderConfig; import org.apache.dubbo.rpc.*; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Component; import javax.annotation.Resource; @Activate @Component public class TokenAuthFilter implements Filter { @Resource private StringRedisTemplate stringRedisTemplate; private ServiceAuthService serviceAuthService; private ProviderConfigProperty providerConfigProperty; public void setProviderConfigProperty(ProviderConfigProperty providerConfigProperty){ this.providerConfigProperty=providerConfigProperty; } public void setServiceAuthService(ServiceAuthService serviceAuthService){ this.serviceAuthService=serviceAuthService; } private static final String TOKEN_KEY="authorization"; @Override public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException { // String token = RpcContext.getServerAttachment().getAttachment(TOKEN_KEY); // String name = RpcContext.getServerAttachment().getAttachment("name"); // System.out.println("token:"+token+",name:"+name); // if (token == null || !validateToken(token)) { // throw new RpcException("UnValid Token"); // } String consumerName = RpcContext.getServerContext().getRemoteApplicationName(); if (consumerName!=null){ if (!serviceAuthService.canVisit(providerConfigProperty.getName(),consumerName)){ throw new RpcException("UnValid Token"); } } return invoker.invoke(invocation); } private boolean validateToken(String token) { String resToken = stringRedisTemplate.opsForValue().get(TOKEN_KEY); if (token.equals(resToken)) { return true; } return false; } }
dubbo-provider的完整结构如下:

dubbo-consumer不需要修改,然后我们运行项目

修改dubbo-consumer的服务名,然后重新运行项目,访问


参考文章:
pringboot之dubbo实现过滤器-yellowcong
调用链路传递隐式参数
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。