SpringCloud Nacos + Ribbon 调用服务的实现方式(两种)
作者:磊哥 发布时间:2023-11-18 09:30:38
在 Nacos 中,服务调用主要是通过 RestTemplate + Ribbon 实现的,RestTemplate 是 Spring 提供的 Restful 请求实现类,而 Ribbon 是客户端负载均衡器,通过 Ribbon 可以获取服务实例的具体信息(IP 和端口号),之后再通过 RestTemplate 加服务实例的具体信息就可以完成一次服务调用了。
而 RestTemplate + Ribbon 调用服务的实现方式两种:通过代码的方式调用服务和通过注解方式调用服务。但两种实现方式的原理都是一样的:都是通过注册中心,将可用服务列表拉取到本地(客户端),再通过客户端负载均衡器得到某个服务器的具体信息,然后请求此服务器即可,如下图所示:
1.代码方式调用
通过代码的方式调用服务在实际工作中并不常用,主要是写法太麻烦,但了解它对于后面理解注解调用方式有很大的帮助,所以我们这里重点来看一下。服务调用需要有两个角色:一个是服务提供者(Provider),另一个是服务调用者(Consumer),接下来我们来创建一下这两个角色。
1.1 创建服务提供者:Provider
第一步:先创建一个 Spring Boot 项目(Spring Cloud 项目是基于 Spring Boot 创建的),添加 spring-web 和 nacos-discovery 依赖,具体依赖信息如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 添加 Nacos 支持 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
第二步:设置 Nacos 相关配置,在 application.yml 中添加以下配置:
spring:
application:
name: springcloud-nacos-provider # 项目名称(nacos 注册的服务名)
cloud:
nacos:
discovery:
username: nacos # nacos 登录用户名
password: nacos666 # nacos 密码
server-addr: 127.0.0.1:8848 # nacos 服务端地址
server:
port: 8081 # 项目启动端口号
第三步:添加服务方法,如下代码所示:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
@RestController
public class HttpProviderApplication {
public static void main(String[] args) {
SpringApplication.run(HttpProviderApplication.class, args);
}
/**
* 为客户端提供可调用的接口
*/
@RequestMapping("/call/{name}")
public String call(@PathVariable String name) {
return "I'm Provider. Received a message from: " + name;
}
}
然后使用相同的方法再创建 2 个服务提供者,最终对应的端口号分别为:
127.0.0.1:8081 127.0.0.1:8082 127.0.0.1:8083
这 3 个服务提供者分别打印的内容是“I'm Provider...”、“I'm Provider2...”、“I'm Provider3...”,如下图所示:
1.2 创建服务调用者:Consumer
本文的核心是服务调用者的实现代码,它的创建方式和服务提供者的创建方式类似。第一步:创建一个 Spring Boot 项目,添加 spring-web 和 nacos-discovery 依赖,具体依赖内容如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 添加 Nacos 支持 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
可能有人会有疑问,本文标题是 Spring Cloud Alibaba Nacos + Ribbon,那为什么不添加 Ribbon 的依赖呢?这是因为 Spring Cloud Alibaba Nacos 中已经内置了 Ribbon 框架了,打开项目的依赖树就可以清楚的看到了,如下图所示:
第二步:设置 Nacos 相关配置,在 application.yml 中添加以下配置:
spring:
application:
name: springcloud-nacos-consumer # 项目名称(nacos 注册的服务名)
cloud:
nacos:
discovery:
username: nacos # nacos 登录用户名
password: nacos666 # nacos 密码
server-addr: 127.0.0.1:8848 # nacos 服务端地址
server:
port: 8091 # 项目启动端口号
第三步:在项目启动类中,使用 Spring Java Config 的方式声明 RestTemplate 对象,如下代码所示:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
public class RibbonCodeConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(RibbonCodeConsumerApplication.class, args);
}
/**
* 使用 Spring Java Config 方式声明 RestTemplate
*/
@Bean
RestTemplate restTemplate() {
return new RestTemplate();
}
}
第四步:使用 RestTemplate + Ribbon 的代码方式调用服务,首先使用 Ribbon 提供的 LoadBalancerClient 对象的 choose 方法,根据 Nacos 中的服务 id 获取某个健康的服务实例,服务实例中包含服务的 IP 地址和端口号,然后再使用 RestTemplate 根据获取到的 IP 和 端口号访问服务即可,具体实现代码如下:
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
@RestController
public class ConsumerController {
// Ribbon 提供的负载均衡对象
@Resource
private LoadBalancerClient loadBalancerClient;
// Spring 提供进行 Restful 请求对象
@Resource
private RestTemplate restTemplate;
@GetMapping("/consumer")
public String consumer(@RequestParam String name) {
// 根据 Ribbon 提供的对象 + Nacos 的服务 id 获取服务实例
ServiceInstance serviceInstance = loadBalancerClient.choose("springcloud-nacos-provider");
// 获取服务实例中的 ip
String ip = serviceInstance.getHost();
// 获取服务实例中的端口号
int port = serviceInstance.getPort();
// 使用 restTemplate 请求并获取结果
String result = restTemplate.getForObject("http://" + ip + ":" + port + "/call/" + name,String.class);
return result;
}
}
以上程序的执行结果如下图所示:
2.注解方式调用
使用注解方式调用服务就简单多了,服务提供者的创建方法和上面相同,这里就不再赘述了,接下来我们来创建一个注解方式的服务调用者 Consumer。第一步:创建一个 Spring Boot 项目,添加 spring-web 和 nacos-discovery 依赖,具体依赖内容如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 添加 Nacos 支持 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
第二步:设置 Nacos 相关配置,在 application.yml 中添加以下配置:
spring:
application:
name: springcloud-nacos-consumer # 项目名称(nacos 注册的服务名)
cloud:
nacos:
discovery:
username: nacos # nacos 登录用户名
password: nacos666 # nacos 密码
server-addr: 127.0.0.1:8848 # nacos 服务端地址
server:
port: 8092 # 项目启动端口号
第三步:在项目启动类中,使用 Spring Java Config 的方式声明 RestTemplate 对象,此步骤中,需要在 RestTemplate 对象上加上 @LoadBalanced 注解,加上此注解之后就可以让 RestTemplate 对象自动支持负载均衡了,如下代码所示:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
public class RibbonAnnotationConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(RibbonAnnotationConsumerApplication.class, args);
}
@LoadBalanced // 使 RestTemplate 自动支持 Ribbon 负载均衡
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
第四步:创建客户端请求方法,具体实现代码如下:
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
@RestController
public class ConsumerController {
@Resource
private RestTemplate restTemplate;
@GetMapping("/consumer")
public String consumer(@RequestParam String name) {
// 请求并获取结果(springcloud-nacos-provider 为 Nacos 服务id)
String result = restTemplate.getForObject("http://springcloud-nacos-provider/call/" + name, String.class);
return result;
}
}
以上程序的执行结果如下图所示:
注解实现原理分析
通过上述代码我们可以看出,Nacos 实现调用服务的关键是通过 @LoadBalanced,它为 RestTemplate 赋予了负载均衡的能力,从而可以正确的调用到服务,那 @LoadBalanced 是如何实现的呢?要知道这个问题的答案,就得阅读 LoadBalancerAutoConfiguration 的源码。LoadBalancerAutoConfiguration 是实现客户端负载均衡器的自动装配类,随着 Spring 的启动而启动,它的源码内容有很多,我们这里截取部分核心的方法来看一下:
@Bean
public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
return () -> {
restTemplateCustomizers.ifAvailable((customizers) -> {
Iterator var2 = this.restTemplates.iterator();
while(var2.hasNext()) {
RestTemplate restTemplate = (RestTemplate)var2.next();
Iterator var4 = customizers.iterator();
while(var4.hasNext()) {
RestTemplateCustomizer customizer = (RestTemplateCustomizer)var4.next();
customizer.customize(restTemplate);
}
}
});
};
}
这里的 this.restTemplates.iterator() 既所有被 @LoadBalanced 注解修饰的 RestTemplate 对象,所有被 @LoadBalanced 修饰的 RestTemplate 对象会被强转为 RestTemplateCustomizer 对象,而这个对象的实现源码如下:
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(final LoadBalancerInterceptor loadBalancerInterceptor) {
return (restTemplate) -> {
List<ClientHttpRequestInterceptor> list = new ArrayList(restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
};
}
也就是所有被 @LoadBalanced 注解修饰的 RestTemplate 对象,会为其添加一个 loadBalancerInterceptor 的 * , * 的实现源码如下:
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
private LoadBalancerClient loadBalancer;
private LoadBalancerRequestFactory requestFactory;
public LoadBalancerInterceptor(LoadBalancerClient loadBalancer, LoadBalancerRequestFactory requestFactory) {
this.loadBalancer = loadBalancer;
this.requestFactory = requestFactory;
}
public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
this(loadBalancer, new LoadBalancerRequestFactory(loadBalancer));
}
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException {
URI originalUri = request.getURI();
String serviceName = originalUri.getHost();
Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
return (ClientHttpResponse)this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution));
}
}
从上述源码可以看出,@LoadBalanced 的执行流程是,被 @LoadBalanced 注解修饰的 RestTemplate 对象,会被 LoadBalancerInterceptor * 所拦截,拦截之后使用 LoadBalancerClient 对象,按照负载均衡的策略获取一个健康的服务实例,然后再通过服务实例的 IP 和端口,调用实例方法,从而完成服务请求。
总结
Nacos 调用 Restful 服务是通过内置的 Ribbon 框架实现的,它有两种调用方法,通过代码的方式或通过注解的方式完成调用。其中注解的方式使用起来比较简单,只需要在 RestTemplate 对象上添加一个 @LoadBalanced 注解,就可以为请求对象赋予负载均衡的能力了。
来源:https://developer.51cto.com/article/703588.html


猜你喜欢
- 由于 python 本身为脚本语言,且经常存在调用第三方库的情况,有的时候用 java 调用 python 不如用 python 调用 ja
- SpringBoot中的SpringMVC配置功能都是在WebMvcAutoConfiguration类中,xxxxAutoConfigur
- 上周,公司的项目改版要求加上一个右滑返回上一个界面,于是就在网上找了一些开源库打算实现.但是在使用的时候遇见了许多的问题.试了两天用过 ht
- 目录1 HttpClient简介2 代码实现2.1 服务端2.1.1 新建控制器2.1.2 新建启动器2.2 客户端2.2.1 添加依赖2.
- 在Android中通过ListView显示SD卡中的文件列表一共有两种方法,一是:通过继承ListActivity显示;二是:利用BaseA
- 最近接手了一个需求,要求实现,叮咚买菜。秒杀位置的轮播拆解通过观察发现其实还是挺简单,大致分为1、商品图片的上下轮播2、价格布局渐隐渐现在a
- 一、申请你的AppIDhttp://open.weixin.qq.com/ 友情提示:推荐使用eclipse打包软件最后一步的M
- mybatis简单的CURD就不用多说了,网上相关博客文档一大堆。分析一下Mybatis里面的collection聚集查询。 假设一个班级有
- 1.前言对于一个Android开发者来说,每一个页面都继承一个单独的系统Activity,有时候会带来很多不必要的困扰。比如:每一个页面会有
- Result 类型是许多编程语言中处理错误的常用方式,包括 C# 的 dotNext 库。在本文中,我们将通过例子回顾 C# 中 using
- 前言单元测试是编写测试代码,用来检测特定的、明确的、细颗粒的功能。单元测试并不一定保证程序功能是正确的,更不保证整体业务是准备的。单元测试不
- 简单工厂模式(Simple Factory),说他简单是因为我们可以将此模式比作一个简单的民间作坊,他们只有固定的生产线生产固定的产品。也可
- 前言上一篇我们介绍了使用 sqflite 这个数据库工具在 Flutter 的应用中建立本地数据库的实例应用。了解过数据库的同学应该会知道,
- 最近在项目过程中遇到了保存数据的需求,对实体类的部分数据进行保存,打算采用反射+自定义特性来实现数据保存,利于扩展1. 采用反射实现能够灵活
- 目录源码使用源码@Target({ElementType.TYPE, ElementType.METHOD})@Retention(Rete
- SpringBoot线程池和Java线程池的用法和实现原理使用默认的线程池方式一:通过@Async注解调用public class Asyn
- 这几年都在搞前后端分离、RESTful风格,我们项目中也在这样用。前几天有人遇到了解析JSON格式的请求数据的问题,然后说了一下解析的方式,
- Android activity和view判断滑动 实例代码://手
- 前言这似乎是 Reactor 的热门搜索之一,至少当我在谷歌中输入 onErrorContinue 时,onErrorResume 会在它旁
- IntelliJ IDEA一个吸引人的地方在于,他有比较好的反编译工具,这让Eclipse用户牙痒痒。但不要紧,本文介绍如何在Eclipse