微服务框架间服务的调用建议用dubbo进行实现,因为dubbo性能比feign高。
环境要求:
1、centos7
2、nacos(注册中心)
3、springboot微服务
一、centos7 搭建nacos
Docker启动nacos
shell命令:
docker run --name nacos-standalone -e MODE=standalone -d -p 8848:8848 -p 9848:9848 -p 9849:9849 nacos/nacos-server:2.0.0
地址访问:http://虚拟机ip:8848/nacos
默认账号、密码都是:nacos
二、springboot集成dubbo过程
自己搭的项目可自行下载:
【百度网盘】https://pan.baidu.com/s/1qXvNakjWH1nrjsib3uzlUA 提取码:61hh
项目结构:
创建三个springboot微服务,分别如下:
- springboot-dubbo-provider-api(服务生产者api,提供消费者调用)
- springboot-dubbo-provider(服务生产者,服务提供方)
- springboot-dubbo-customer(服务消费者,服务调用方)
新建一个maven工程,添加如下依赖
<!-- registry dependency -->
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>${nacos.version}</version>
</dependency>
<!-- dubbo dependency-->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>${dubbo.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
发布服务
1、定义服务接口
public interface DevelopService {
String invoke(String param);
}
2、服务接口实现
@DubboService(group = "group1",version = "0.0.1")
public class DevelopProviderServiceV1 implements DevelopService{
@Override
public String invoke(String param) {
StringBuilder s = new StringBuilder();
s.append("ServiceV1 param:").append(param);
return s.toString();
}
}
使用@DubboService 注解,Dubbo会将对应的服务注册到spring, 在spring启动后调用对应的服务导出方法,将服务注册到注册中心, 这样Consumer端才能发现我们发布的服务并调用
3、添加Dubbo配置
添加application.yml相关配置,内容如下:
dubbo:
application:
# qosEnable: false
logger: slf4j
name: SpringbootProviderApplication
registry:
address: nacos://虚拟机ip:8848
protocol:
name: dubbo
port: 20880
server:
port: 8080
4、启动服务
创建Springboot启动类,需添加@EnableDubbo注解,开启Dubbo自动配置功能
@EnableDubbo
@SpringBootApplication
public class SpringbootProviderApplication{
public static void main(String[] args) {
SpringApplication.run(SpringbootProviderApplication.class, args);
}
}
启动成功后,在注册中心可以看到对应的服务列表,如图:
调用服务
创建DemoCustomer类,通过@DubboReference注解对需要调用的服务进行引入。即可像调用本地方法一样调用远程服务了。
@RestController
@RequestMapping("dubbo/rpc/")
public class DemoCustomer {
@DubboReference(group = "group1",version = "0.0.1")
private DevelopService developService;
@RequestMapping("invoke")
public String invoke() {
//调用DevelopService的group1分组实现
return developService.invoke("1");
}
}
先后启动springboot-dubbo-prodiver、spring-dubbo-customer服务,调用invoke方法,返回 ServiceV1 param: 1
说明调用成功
三、异步调用
Dubbo异步调用分为Provider端异步调用和Consumer端异步调用。 Provider端异步执行将阻塞的业务从Dubbo内部线程池切换到业务自定义线程, 避免Dubbo线程池的过度占用,有助于避免不同服务间的互相影响。异步执行无异于节省资源或提升RPC响应性能。
Provider 端异步执行和 Consumer 端异步调用是相互独立的,你可以任意正交组合两端配置
- Consumer同步 - Provider同步
- Consumer异步 - Provider同步
- Consumer同步 - Provider异步
- Consumer异步 - Provider异步
使用场景
provider 异步
1、使用CompletableFuture实现异步
接口定义:
public interface AsyncService {
/**
* 同步调用方法
*/
String invoke(String param);
/**
* 异步调用方法
*/
CompletableFuture<String> asyncInvoke(String param);
}
服务实现:
@DubboService(version = "0.0.1")
public class AsyncServiceImpl implements AsyncService {
@Override
public String invoke(String param) {
//int cnt = 100 / 0;//降级容错测试
try {
long time = ThreadLocalRandom.current().nextLong(1000);
Thread.sleep(time);
StringBuilder s = new StringBuilder();
s.append("AsyncService invoke param:").append(param).append(",sleep:").append(time);
return s.toString();
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return null;
}
@Override
public CompletableFuture<String> asyncInvoke(String param) {
// 建议为supplyAsync提供自定义线程池
return CompletableFuture.supplyAsync(() -> {
//int cnt = 100 / 0;//降级容错测试
try {
// Do something
long time = ThreadLocalRandom.current().nextLong(1000);
Thread.sleep(time);
StringBuilder s = new StringBuilder();
s.append("AsyncService asyncInvoke param:").append(param).append(",sleep:").append(time);
return s.toString();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return null;
});
}
}
通过 return CompletableFuture.supplyAsync() ,业务执行已从 Dubbo 线程切换到业务线程,避免了对 Dubbo 线程池的阻塞。
2、使用AsyncContext实现异步
Dubbo 提供了一个类似 Servlet 3.0 的异步接口AsyncContext,在没有 CompletableFuture 签名接口的情况下,也可以实现 Provider 端的异步执行。
接口定义:
public interface AsyncService {
String sayHello(String name);
}
服务实现:
public class AsyncServiceImpl implements AsyncService {
public String sayHello(String name) {
final AsyncContext asyncContext = RpcContext.startAsync();
new Thread(() -> {
//int cnt = 100 / 0;//降级容错测试
// 如果要使用上下文,则必须要放在第一句执行
asyncContext.signalContextSwitch();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 写回响应
asyncContext.write("Hello " + name + ", response from provider.");
}).start();
return null;
}
}
降级容错:
public class AsyncMock implements AsyncService {
@Override
public String invoke(String param) {
System.out.println("invoke同步降级容错");
return "invoke同步降级容错";
}
@Override
public CompletableFuture<String> asyncInvoke(String param) {
System.out.println("asyncInvoke异步降级容错");
return null;
}
@Override
public String sayHello(String name) {
System.out.println("sayHello异步降级容错");
return "sayHello异步降级容错";
}
}
消费者端 消费测试:
@RestController
@RequestMapping("async/prc/")
public class AsyncCustomer {
@DubboReference(
version = "0.0.1",
mock = "com.provider.module.dubbo.async.AsyncMock",
check = false
)
private AsyncService asyncService;
@RequestMapping("asyncInvoke")
public void asyncInvoke(String param) {
//调用异步接口
CompletableFuture<String> future1 = asyncService.asyncInvoke("async call request1");
future1.whenComplete((v, t) -> {
if (t != null) {
t.printStackTrace();
} else {
System.out.println("AsyncTask Response-1: " + v);
}
});
//两次调用并非顺序返回
CompletableFuture<String> future2 = asyncService.asyncInvoke("async call request2");
future2.whenComplete((v, t) -> {
if (t != null) {
t.printStackTrace();
} else {
System.out.println("AsyncTask Response-2: " + v);
}
});
}
@RequestMapping("sayHello")
public String sayHello(String param) {
return asyncService.sayHello(param);
}
}
注意:
1、使用CompletableFuture实现异步时,如果接口报错,不会走降级容错
2、业务代码写在try代码块内,报错会被捕获,也不会走降级容错
Consumer异步
@RestController
@RequestMapping("async/prc/")
public class AsyncCustomer {
@DubboReference(
version = "0.0.1",
mock = "com.provider.module.dubbo.async.AsyncMock",
check = false
)
private AsyncService asyncService;
//consumer异步调用
@RequestMapping("invoke")
public String invoke(String param) {
CompletableFuture<String> future3 = CompletableFuture.supplyAsync(() -> {
return asyncService.invoke(param);
});
future3.whenComplete((v, t) -> {
if (t != null) {
t.printStackTrace();
} else {
System.out.println("AsyncTask Response-3: " + v);
}
});
return "consumer异步调用成功";
}
}
四、泛化调用
dubbo版本要3以上
泛化调用
泛化调用(客户端泛化调用)是指在调用方没有服务方提供的 API(SDK)的情况下,对服务方进行调用,并且可以正常拿到调用结果。
使用场景
调用方没有接口及模型类元,知道服务的接口的全限定类名和方法名的情况下,可以通过泛化调用调用对应接口。 比如:实现一个通用的服务测试框架
使用方式
接口定义:
public interface DemoService {
String sayHello(String name);
}
接口实现1:
@DubboService(version = "0.0.1", group = "group1")
@Slf4j
public class DemoServiceImpl implements DemoService {
@Override
public String sayHello(String name) {
return "Hello " + name;
}
}
客户端调用:
/**
* @description: 泛化调用
* @author: zxh
* @create: 2024-01-11 11:25
**/
@RestController
@RequestMapping("generic/")
public class GenericCustomer {
@RequestMapping("getGeneric")
public String getGeneric(String param) {
GenericService genericService = buildGenericService("com.provider.module.dubbo.rpc.DemoService","group1","0.0.1");
//传入需要调用的方法,参数类型列表,参数列表
Object result = genericService.$invoke("sayHello", new String[]{"java.lang.String"}, new Object[]{param});
System.out.println("GenericTask Response: " + JSON.toJSONString(result));
return JSON.toJSONString(result);
}
private GenericService buildGenericService(String interfaceClass, String group, String version) {
ReferenceConfig<GenericService> reference = new ReferenceConfig<>();
reference.setInterface(interfaceClass);
reference.setVersion(version);
//开启泛化调用
reference.setGeneric("true");
reference.setTimeout(30000);
reference.setGroup(group);
ReferenceCache cache = SimpleReferenceCache.getCache();
try {
return cache.get(reference);
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
}
}
}
调用结果: