Nacos服务注册中心应用实践
文章目录
一 . 注册中心简介
1.1 背景分析
在微服务中,首先需要面对的问题就是如何查找服务(软件即服务),其次,就是如何在不同的服务之间进行通信?如何更好更方便的管理应用中的每一个服务,如何建立各个服务之间联系的纽带,由此注册中心诞生(例如淘宝网卖家提供服务,买家调用服务)。
市面上常用注册中心有Zookeeper(雅虎Apache),Eureka(Netfix),Nacos(Alibaba),Consul(Google),那他们分别都有什么特点,我们如何进行选型呢?我们主要从社区活跃度,稳定性,功能,性能等方面进行考虑即可.本次微服务的学习,我们选择Nacos,它很好的支持了阿里的双11活动,不仅可以做注册中心,还可以作为配置中心,稳定性和性能都很好。
1.2 Nacos概述
Nacos(DynamicNaming and Configuration Service)是一个应用于服务注册与发现、配置管理的平台。它孵化于阿里巴巴,成长于十年双十一的洪峰考验,沉淀了简单易用、稳定可靠、性能卓越的核心竞争力。其官网地址如下:
https://nacos.io/zh-cn/docs/quick-start.html
二. 构建Nacos服务
2.1 准备工作
第一:确保你电脑已配置JAVA_HOME环境变量(Nacos启动时需要),例如:
第二:确保你的MySQL版本为5.7以上(MariaDB10.5以上),例如
2.2 下载与安装
第一步:Nacos下载,可在浏览器直接输入如下地址:
https://github.com/alibaba/nacos/releases
第二步:选择对应版本,直接下载,如图所示:
第三步:解压Nacos(最好不要解压到中文目录下),其目录结构如下:
2.3 初始化配置
第一步:登陆mysql,执行老师发给同学们的sql脚本。例如,我们可以使用mysql自带客户端,在命令行首先登录mysql,然后执行如下指令:
source d:/nacos-mysql.sql
执行成功以后,会创建一个nacos_config数据库,打开数据库会看到一些表,例如;
说明:在执行此文件时,要求mysql的版本大于5.7版本(MariaDB最好10.5.11),否则会出现如下错误:
第二步:打开/conf/application.properties里打开默认配置,并基于你当前环境配置要连接的数据库,连接数据库时使用的用户名和密码(假如前面有"#"要将其去掉):
### If use MySQL as datasource:
spring.datasource.platform=mysql
### Count of DB:
db.num=1
### Connect URL of DB:
db.url.0=jdbc:mysql://127.0.0.1:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
db.user.0=root
db.password.0=root
2.4 服务启动与访问
第一步:启动Nacos服务。
Linux/Unix/Mac启动命令(standalone代表着单机模式运行,非集群模式):
./startup.sh -m standalone
Windows启动命令(standalone代表着单机模式运行,非集群模式):
startup.cmd -m standalone
说明:
1)执行执行令时要么配置环境变量,要么直接在nacos/bin目录下去执行.
2)nacos启动时需要本地环境变量中配置了JAVA_HOME(对应jdk的安装目录),
3)一定要确保你连接的数据库(nacos_config)是存在的.
4)假如所有的配置都正确,还连不上,检查一下你有几个数据库(mysql,…)
第二步:访问Nacos服务。
打开浏览器,输入http://localhost:8848/nacos地址,出现如下登陆页面:其中,默认账号密码为nacos/nacos.
三. 服务注册与调用入门(重点)
3.1 业务描述
创建两个项目Module分别为服务提供者和服务消费者(假如已有则无需创建),两者都要注册到NacosServer中(这个server本质上就是一个web服务,端口默认为8848),然后服务提供者可以为服务消费者提供远端调用服务(例如支付服务为服务提供方,订单服务为服务消费方),如图所示:
3.2 生产者服务创建及注册
第一步:创建服务提供者工程(module名为sca-provider,假如已有则无需创建),继承parent工程(01-sca),其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">
<parent>
<artifactId>01-sca</artifactId>
<groupId>com.jt</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>sca-provider</artifactId>
<dependencies>
<!--基于springboot 工程的web服务依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--基于alibaba的nacos实现服务注册管理,当添加了此依赖后,系统会
在启动时候会向nacos服务发送一些心跳包,进行服务的注册
当我们的项目中添加了一个这样的依赖,就表示此项目是nacos服务的一个客户端对象-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
</project>
第二步:创建并修改配置文件application.yml(或者application.properties),实现服务注册,关键代码如下:
#服务的端口
server:
port: 8081
#服务名
spring:
application:
name: sca-provider
#服务注册地址(服务启动时向这个位置发送心跳包,一般是每隔5秒发送一次)
#服务端在15秒内每隔5秒检测一次心跳,如果15秒内没有心跳,将当前服务健康实例数变为0;
#再过15秒还没有检测到,那么nacos会将当前服务移除
cloud:
nacos:
discovery:
server-addr: localhost:8848 #将来nacos会在一台独立的机器上进行部署
注意:服务名不要使用下划线(“_”),应使用横杠(“-”),这是规则。
第三步:创建启动类(假如已有则无需定义),关键代码如下:
package com.jt;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
//启动类本身就是一个配置类
public class ProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderApplication.class, args);
}
}
第四步:启动启动类,然后刷先nacos服务,检测是否服务注册成功,如图所示:
3.3 消费者服务发现及调用
第一步: 在sca-provider项目中创建服务提供方对象,基于此对象对外提供服务,例如:
package com.jt.provider.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ProviderController {
//8080为没有读到server.port的值时,给定的默认值
@Value("${server.port:8080}")
private String server;
//访问方式
//1.浏览器中直接输入这个地址访问:http://localhost:8081/provider/echo/tedu
//2.在sca-consumer中对这个服务进行访问
@GetMapping("/provider/echo/{msg}")//@PathVariable("msg")括号中的变量名要和路径中的变量一致(针对JDK1.5)
public String doRestEcho1(@PathVariable("msg") String msg){
return server+"say hello"+msg;
}
}
第二步:创建服务消费者工程(module名为sca-consumer,假如已有则无需创建),继承parent工程(01-sca),其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">
<parent>
<artifactId>01-sca</artifactId>
<groupId>com.jt</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>sca-consumer</artifactId>
<dependencies>
<!--添加web依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--添加服务注册依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
</project>
第三步:创建sca-consumer服务中的配置文件application.yml,关键代码如下:
server:
port: 8090
spring:
application:
name: sca-consumer
cloud:
nacos:
discovery:
server-addr: localhost:8848
第四步:创建消费端启动类并实现服务消费,关键代码如下:
第五步:在sca-consumer启动类中添加如下方法,用于创建RestTemplate对象.
package com.jt;
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 ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
/*
* 构建由spring创建RestTemplate对象
* 可以基于此对象进行远程服务的调用
* 例如 sca-consumer调用sca-provider
* */
//将对象交给spring容器管理
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
第六步:定义sca-consumer服务的消费端Controller,在此对象方法内部实现远端服务调用
package com.jt.consumer.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
public class ConsumerController {
/*
* 由spring帮我们为RestTemplate属性注入值
* 然后我们通过此对象进行远程服务端调用
* */
@Autowired
private RestTemplate restTemplate;
/*
* 当浏览器访问这个http://localhost:8090/consumer/doRestEcho1
* 在此方法内部基于RestTemplate对象中的相关方法进行远端服务调用
* */
@GetMapping("/consumer/doRestEcho1")
public String doRestEcho1(){
//你要调用的远端服务url
String url="http://localhost:8081/provider/echo/8090";
//执行远端服务调用,String.class为远端url对应的方法的返回值类型(响应数据类型)
//第三个参数可写可不写,如果url地址中有值则不写,如果处于取值状态的话,则需要传值
return restTemplate.getForObject(url,String.class);
}
}
第七步:启动消费者服务,并在浏览器输入http://localhost:8090/consumer/doRestEcho1地址进行访问,假如访问成功会出现,如图所示效果:
3.4 小节面试分析
- 为什么要将服务注册到nacos?(为了更好的查找这些服务)
- 在Nacos中服务提供者是如何向Nacos注册中心(Registry)续约的?(5秒心跳)
- 对于Nacos服务来讲它是如何判定服务实例的状态?(检测心跳包,15,30)
- 服务消费方是如何调用服务提供方的服务的?(RestTemplate)
四. 服务负载均衡设计及实现(重点)
4.1 业务描述
一个服务实例可以处理请求是有限的,假如服务实例的并发访问比较大,我们会启动多个服务实例,让这些服务实例采用一定策略均衡(轮询,权重,随机,hash等)的处理并发请求,在Nacos中服务的负载均衡(Nacos客户端负载均衡)是如何应用的?
4.2 LoadBalancerClient应用
LoadBalancerClient对象可以从nacos中基于服务名获取服务实例,然后在工程中基于特点算法实现负载均衡方式的调用,案例实现如下:
第一步:修改ConsumerController类,注入LoadBalancerClient对象,并添加doRestEcho2方法,然后进行服务访问.
/*
* 负载均衡客户端对象,基于此对象可以获取nacos中的服务实例
* 并且通过负载均衡算法从所有实例中获取一个实例
* */
@Autowired
private LoadBalancerClient loadBalancerClient;
@Value("${spring.application.name:sca-consumer}")
private String appName;
@GetMapping("/consumer/doRestEcho2")
public String doRestEcho2(){
System.out.println(loadBalancerClient.getClass());
/*
* class org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient
* 此类封装了负载均衡算法
* */
//从nacos获取服务实例(这里的choose方法内置了负载均衡算法)
//sca-provider为nacos服务列表中的一个服务名
ServiceInstance serviceInstance = loadBalancerClient.choose("sca-provider");
String ip = serviceInstance.getHost();
int port = serviceInstance.getPort();
String url="http://"+ip+":"+port+"/provider/echo/{msg}";
//格式化字符换 %s表示字符串占位符,注意后面参数的传入顺序要对应 %d为数字占位符
String.format("http://%s:%s/provider/echo/%s", ip,port,appName);
//执行远端服务调用
return restTemplate.getForObject(url,String.class,appName);
}
第二步:打开Idea服务启动配置,如图所示:
第三步:修改并发运行选项(假如没有找到这个选项我们需要通过搜索引擎基于组合查询的方法,去找到对应的解决方案,例如搜索 idea allow parallel run),如图所示:
第四步:修改sca-provider的配置文件端口,分别以8081,8082端口方式进行启动。
server:
port: 8082
spring:
application:
name: sca-provider
cloud:
nacos:
server-addr: localhost:8848
第五步:启动成功以后,访问nacos的服务列表,检测服务是否成功注册,如图所示:
第六步:启动sca-consumer项目模块,打开浏览器对consumer服务进行访问,访问时不断刷新,检测页面数据变化,例如:
说明,这里多个实例并发提供服务的方式为负载均衡,这里的负载均衡实现默认是因为Nacos集成了Ribbon来实现的,Ribbon配合RestTemplate,可以非常容易的实现服务之间的访问。Ribbon是Spring Cloud核心组件之一,它提供的最重要的功能就是客户端的负载均衡(客户端可以采用一定算法,例如轮询访问,访问服务端实例信息),这个功能可以让我们轻松地将面向服务的REST模版请求自动转换成客户端负载均衡方式的服务调用。
4.3 @LoadBalanced
当使用RestTemplate进行远程服务调用时,假如需要负载均衡,还可以在RestTemplate对象构建时,使用@LoadBalanced对构建RestTemplate的方法进行修饰,例如在ConsumerApplication中构建名字为loadBalancedRestTemplate的RestTemplate对象:
/*
* 当我们使用@LoadBalanced注解描述RestTemplate对象时,
* 系统底层再基于RestTemplate进行远程服务调用时
* 会被一个拦截器(LoadBalancerInterceptor)拦截到,然后进行功能增强,
* 增强指的是,基于LoadBalancerClient对象进行服务实例获取
* 而这个服务实例获取的过程,底层会采用负载均衡
* */
@Bean
@LoadBalanced
public RestTemplate loadBalancedRestTemplate(){
return new RestTemplate();
}
在需要RestTemplate实现负载均衡调用的地方进行依赖注入.例如在ConsumerController类中添加loadBalancedRestTemplate属性
@Autowired
private RestTemplate loadBalancedRestTemplate;
接下来,可以在对应的服务端调用方的方法内,基于RestTemplate借助服务名进行服务调用, 例如:
@GetMapping("/consumer/doRestEcho3")
public String doRestEcho3(){
//定义远程调用的url
// String url = String.format("http://sca-provider/provider/echo/%s", appName);
String url=String.format("http://%s/provider/echo/%s","sca-provider",appName);
//执行远端服务调用
return loadBalancedRestTemplate.getForObject(url,String.class,appName);
}
RestTemplate在发送请求的时候会被LoadBalancerInterceptor拦截,它的作用就是用于RestTemplate的负载均衡,LoadBalancerInterceptor将负载均衡的核心逻辑交给了loadBalancer,核心代码如下所示(了解):
public ClientHttpResponse intercept(final HttpRequest request,
final byte[] body, final ClientHttpRequestExecution execution) throws IOException {
final URI originalUri = request.getURI();
String serviceName = originalUri.getHost();
return this.loadBalancer.execute(serviceName,
requestFactory.createRequest(request, body, execution));
}
@LoadBalanced注解是属于Spring,而不是Ribbon的,Spring在初始化容器的时候,如果检测到Bean被@LoadBalanced注解,Spring会为其设置LoadBalancerInterceptor的拦截器。
4.4 Ribbon负载均衡策略(了解)
基于Ribbon方式的负载均衡,Netflix默认提供了七种负载均衡策略,对于SpringCloud Alibaba解决方案中又提供了NacosRule策略,默认的负载均衡策略是轮训策略。如图所示:
当系统提供的负载均衡策略不能满足我们需求时,我们还可以基于IRule接口自己定义策略.
4.4.1 Ribbon替换默认的负载均衡策略
底层默认逻辑实现
/*
* 底层默认逻辑实现,由@ConditionalOnMissingBean配合 @Bean一起使用时候
* 会在没有指定Bean时采取创建这个Bean对象,一旦自己创建了,则不创建;
* */
@Bean
@ConditionalOnMissingBean(IRule.class)
public IRule rule(){
return new BestAvailableRule();//系统默认创建的时BestAvailableRule对象
}
方式一:修改sca-consumer的启动类,在启动类中添加如下方法,例如:
对于方案1的写法,编写相对简单,编写过程都有提示,但是将来的可运维性比较差,项目打包以后,我们无法再修改负载均衡算法。
/*
* 指定默认的负载均衡策略,但是这种配置方式不够灵活
* 将来项目上线后,所有的类都会打包成一个jar包,
* 假如项目在运行过程中,我们想调整负载均衡算法,就不太方便了
* 我们可以将这些配置写道配置文件,最后更新到配置中心(config center)
* 可以实现配置的动态发布,不需要重启服务,即可实现配置的动态更新
* */
@Bean
public IRule rule(){
return new RandomRule();
}
方式二:修改sca-consumer配置文件(application.yml),添加如下语句,例如:
对于方案2写法,在写的过程没有提示,编写困难,但是将来的可运维性会比较好,我们这部分配置写到配置中心,不需要重启服务即可实现配置的动态发布,动态更新。
server:
port: 8090
spring:
application:
name: sca-consumer
cloud:
nacos:
discovery:
server-addr: localhost:8848
#sca-provider服务的负载均衡配置
sca-provider:
ribbon: #负载均衡组件,是Ribbon组件提供了IRule接口及相关实现
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
4.5 小节面试分析
- @Bean注解的作用?(一般用于配置类内部,描述相关方法,用于告诉spring此方法的返回值要交给spring管理,bean的名字默认为方法名,假如需要指定名字可以@Bean(“bean的名字”),最多的应用场景是整合第三方的资源-对象)
- @Autowired注解的作用?(此注解用于描述属性,构造方法,set方法等,用于告诉spring框架,按找一定的规则为属性进行DI操作,默认按属性,方法参数类型查找对应的对象,假如只找到一个,则直接注入,类型多个时还会按照属性名或方法参数名进行值的注入,假如名字也不同,就出报错.)
- Nacos中的负责均衡底层是如何实现的?(通过Ribbon实现,Ribbon中定义了一些负载均衡算法,然后基于这些算法从服务实例中获取一个实例为消费方法提供服务)
- Ribbon 是什么?(Netflix公司提供的负载均衡客户端,一般应用于服务的消费方法)
- Ribbon 可以解决什么问题? (基于负载均衡策略进行服务调用, 所有策略都会实现IRule接口)
- Ribbon 内置的负载策略都有哪些?(8种,可以通过查看IRule接口的实现类进行分析)
- @LoadBalanced的作用是什么?(描述RestTemplate对象,用于告诉Spring框架,在使用RestTempalte进行服务调用时,这个调用过程会被一个拦截器进行拦截,然后在拦截器内部,启动负载均衡策略。)
- 我们可以自己定义负载均衡策略吗?(可以,基于IRule接口进行策略定义,也可以参考NacosRule进行实现)
五. 基于Feign的远程服务调用(重点)
5.1 背景分析
服务消费方基于rest方式请求服务提供方的服务时,一种直接的方式就是自己拼接url,拼接参数然后实现服务调用,但每次服务调用都需要这样拼接,代码量复杂且不易维护,此时Feign诞生。
5.2 Feign是什么
Feign 是一种声明式Web服务客户端,底层封装了对Rest技术的应用,通过Feign可以简化服务消费方对远程服务提供方法的调用实现。如图所示:
Feign 最早是由 Netflix 公司进行维护的,后来 Netflix 不再对其进行维护,最终 Feign 由一些社区进行维护,更名为 OpenFeign。
5.3 Feign应用实践(掌握)
第一步:在服务消费方,添加项目依赖(SpringCloud团队基于OpenFeign研发了starter),代码如下:
<!--添加feign组件,我们要基于这里提供的api进行远程服务调用-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
第二步:在启动类上添加@EnableFeignClients注解,代码如下:
/*
* @EnableFeignClients 用于描述一些配置类,告诉系统底层启动时
* 为@FeignClient注解描述的接口创建实现类及d对象,然后交给Spring容器管理
* */
@SpringBootApplication
@EnableFeignClients
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
第三步:定义Http请求API,基于此API借助OpenFeign访问远端服务,其中,@FeignClient描述的接口底层会为其创建实现类。
代码如下:
package com.jt.consumer.service;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
/*
* @FeignClient注解用于描述远程服务接口 其value属性的值有两个层面的含义
* 第一个时你要调用的远程服务名,第二个含义就是当前bean的名字
* */
@FeignClient("sca-provider")
public interface RemoteProviderService {
@GetMapping("/provider/echo/{string}")
/*
* 在@FeignClient修饰的接口方法中,
* @PathVariable注解中的参数必须要写,注解中的名字要和url中的名字相同,可以不和参数列表的变量名相同
* */
String echoMessage(@PathVariable("string") String string);
}
第四步:创建FeignConsumerController中并添加feign访问,代码如下:
package com.jt.consumer.controller;
import com.jt.consumer.service.RemoteProviderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/consumer/") //url拼接过程中如果两个斜杠之间没有任何内容就会合成一个斜杠
public class FeignConsumerController {
@Autowired
private RemoteProviderService remoteProviderService;
@GetMapping("/echo/{msg}")
/*
* controller层这里的@PathVariable注解里面的参数可写可不写,
* 如果写,那么注解里的参数名必须和url中的参数名相同,方法参数列表的参数名可以随意
* 如果不写,那么方法的参数列表的参数名必须与url中的参数同名
* */
public String doFeignEcho(@PathVariable("msg") String msg){
return remoteProviderService.echoMessage(msg);
}
}
第五步:启动消费者服务,在浏览器中直接通过feign客户端进行访问,如图所示(反复刷新检测其响应结果):
说明,feign方式的远程服务调用,底层会自动基于ribbon组件实现负载均衡。
5.4 Feign配置进阶实践
一个服务提供方通常会提供很多资源服务,服务消费方基于同一个服务提供方写了很多服务调用接口,此时假如没有指定contextId,服务
启动就会失败,例如,假如在服务消费方再添加一个如下接口,消费方启动时就会启动失败,例如:
package com.jt.consumer.service;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
//contextId指定bean的名字,防止bean名字冲突
@FeignClient(value = "sca-provider",contextId = "remoteOtherService")
public interface RemoteOtherService {
@GetMapping("/doSomeThing")
public String doSomeThing();
}
如果不加contextId,其启动异常如下:
The bean 'optimization-user.FeignClientSpecification', defined in null, could not be registered. A bean with that name has already been defined in null and overriding is disabled.
此时我们需要为远程调用服务接口指定一个contextId,作为远程调用服务的唯一标识(这个标识是Bean对象的名字)即可,例如:
package com.jt.consumer.service;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
/*
* @FeignClient注解用于描述远程服务接口 其value属性的值有两个层面的含义
* 第一个时你要调用的远程服务名,第二个含义就是当前bean的名字
* 假如不使用这个名字,还可以使用contextId指定bean的名字
* */
@FeignClient(value = "sca-provider",contextId = "remoteProviderService")
public interface RemoteProviderService {
@GetMapping("/provider/echo/{string}")//前提是远端需要有这个服务
/*
* 在@FeignClient修饰的接口方法中,
* @PathVariable注解中的参数必须要写,注解中的名字要和url中的名字相同,可以不和参数列表的变量名相同
* */
String echoMessage(@PathVariable("string") String string);
}
还有,当我们在进行远程服务调用时,假如调用的服务突然不可用了或者调用过程超时了,怎么办呢?一般服务消费端会给出具体的容错方案,例如,在Feign应用中通过FallbackFactory接口的实现类进行默认的相关处理,例如:
第一步:定义FallbackFactory接口的实现,代码如下:
package com.jt.consumer.service;
import feign.hystrix.FallbackFactory;
import org.springframework.stereotype.Component;
/*
* 基于此对象处理RemoteProviderService接口调用时出现的服务中断,超时等问题
*/
@Component
public class ProviderFallbackFactory implements FallbackFactory<RemoteProviderService> {
/*
* 此方法会在RemoteProviderService接口服务调用时,出现了异常后执行.
* throwable 用于接收异常
*/
@Override
public RemoteProviderService create(Throwable throwable) {
return new RemoteProviderService() {
@Override
public String echoMessage(String string) {
return "服务超时,稍等片刻";
}
};
}
}
第二步:在Feign访问接口中应用FallbackFactory对象,例如:
package com.jt.consumer.service;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
/*
* @FeignClient注解用于描述远程服务接口 其value属性的值有两个层面的含义
* 第一个时你要调用的远程服务名,第二个含义就是当前bean的名字
* 假如不使用这个名字,还可以使用contextId指定bean的名字
* */
@FeignClient(value = "sca-provider",contextId = "remoteProviderService",fallbackFactory = ProviderFallbackFactory.class)
public interface RemoteProviderService {
@GetMapping("/provider/echo/{string}")//前提是远端需要有这个服务
/*
* 在@FeignClient修饰的接口方法中,
* @PathVariable注解中的参数必须要写,注解中的名字要和url中的名字相同,可以不和参数列表的变量名相同
* */
String echoMessage(@PathVariable("string") String string);
}
第三步:在sca-consumer配置文件application.yml中添加如下配置,启动feign方式调用时的服务中断处理机制.
server:
port: 8090
spring:
application:
name: sca-consumer
cloud:
nacos:
discovery:
server-addr: localhost:8848
#sca-provider服务的负载均衡配置
sca-provider:
ribbon: #负载均衡组件,是Ribbon组件提供了IRule接口及相关实现
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
feign:
hystrix:
enabled: true #打开熔断机制
第四步:在服务提供方sca-provider对应的调用方法中添加Thread.sleep(5000)模拟耗时操作,然后启动服务进行访问测试.
package com.jt.provider.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ProviderController {
//8080为没有读到server.port的值时,给定的默认值
@Value("${server.port:8080}")
private String server;
//访问方式
//1.浏览器中直接输入这个地址访问:http://localhost:8081/provider/echo/tedu
//2.在sca-consumer中对这个服务进行访问
@GetMapping("/provider/echo/{msg}")//@PathVariable("msg")括号中的变量名要和路径中的变量一致(针对JDK1.5)
public String doRestEcho1(@PathVariable("msg") String msg){
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return server+"say hello "+msg;
}
}
5.5 Feign 调用过程分析(了解)
Feign应用过程分析(底层逻辑先了解):
- 通过 @EnableFeignCleints 注解告诉springcloud,启动 Feign Starter 组件。
- Feign Starter 会在项目启动过程中注册全局配置,扫描包下所由@FeignClient注解描述的接口,然后由系统底层创建接口实现类(JDK代理类),并构建类的对象,然后交给spring管理(注册 IOC 容器)。
- Feign接口被调用时,底层代理对象会将接口中的请求信息通过编码器创建 Request对象,基于此对象进行远程过程调用。
- Feign客户端请求对象会经Ribbon进行负载均衡,(choose方法从服务注册中心)挑选出一个健康的 Server 实例(instance)。
- Feign客户端会携带 Request 调用远端服务并返回一个响应。
- Feign客户端对象对Response信息进行解析然后返回客户端。
5.6 小节面试分析
- 为什么使用feign?(基于Feign可以更加友好的实现服务调用,简化服务消费方对服务提供方方法的调用)。
- @FeignClient注解的作用是什么?(告诉Feign Starter,在项目启动时,为此注解描述的接口创建实现类-代理类)
- Feign方式的调用,底层负载均衡是如何实现的?(Ribbon)
- @EnableFeignCleints 注解的作用是什么?(描述配置类,例如启动类)
六. 总结(Summary)
6.1 核心知识点
- 服务注册中心诞生背景? (服务多了,需要统一管理,例如所有公司需要在工商局进行备案)
- 服务注册中心的选型?(社区活跃度,稳定性,功能,性能,学习成本)
- Nacos下载,安装,配置,启动,访问(http://ip:port/nacos)
- 基于Nacos实现服务的注册?(添加依赖,服务配置,启动服务并检查)
- 基于RestTemplate实现服务的简易调用?(服务消费方调用服务提供方)
- @Loadbalanced注解的应用
- Feign 诞生的背景及应用方式
- Feign 接口对应的Bean对象命名方式
- Feign 方式远程服务调用时熔断处理方案
- Feign 方式调用原理分析。
6.2 常见问题分析
- Nacos是什么,提供了什么特性(服务的注册、发现、配置)?
- 你为什么会选择Nacos?(活跃度、稳定、性能、学习成本)
- Nacos的官网?(nacos.io)
- Nacos在github的源码?(github.com/alibaba/nacos)
- Nacos在windows环境下安装?(解压即可使用)
- Nacos在windows中的的初步配置?(application.properties访问数据库的数据源)
- Nacos服务注册的基本过程?(服务启动时发送web请求)
- Nacos服务消费的基本过程?(服务启动时获取服务实例,然后调用服务)
- Nacos服务负载均衡逻辑及设计实现?(Ribbon)
- 注册中心的核心数据是什么?(服务的名字和它对应的网络地址)
- 注册中心中心核心数据的存取为什么会采用读写锁?(底层安全和性能)
- Nacos健康检查的方式?(基于心跳包机制进行实现)
- Nacos是如何保证高可用的?(重试,本地缓存、集群)
- RestTemplate的基本作用是什么?
- Feign是什么,它的应用是怎样的,feign应用过程中的代理对象是如何创建的(JDK)?
- Feign方式的调用过程,其负载均衡是如何实现?(Ribbon)
- @Loadbalanced注解作用?(描述RestTemplate对象,让系统底层为RestTemplate对象赋能,对请求过程进行拦截)
- Ribbon是什么,基于Ribbon可以解决什么问题?(一个负载均衡组件,这个组件中提供一套负载均衡算法)
- 为什么要负载均衡?(网络中有一组可以提供相同服务的计算机~类似银行的各个营业点)
- 你了解哪些负载均衡策略?(基于IRule接口去查看,快捷键ctrl+h)
- 我们如何修改负载均衡策略?(配置文件->例如application.yml,配置类->例如启功类)
- 为什么使用Feign方式的服务调用?(优化结构,简化服务调用过程代码的编写、)
- 如何基于feign方式实现远程服务调用?(依赖,配置,feign接口定义)
- @EnableFeignCleints 注解的作用是什么?(描述配置类,例如启动类)
- @FeignClient注解的作用是什么?(告诉Feign Starter,在项目启动时,为此注解描述的接口创建实现类-代理类)
- 如何理解基于Feign方式的远程服务调用?声明式远程服务调用,底层封装了服务调用过程。
- Feign方式如何实现负载均衡?(底层基于ribbon组件实现)
- Feign接口指向的对象是谁?
- 常用服务发现、服务调用方式有哪些?
6.3 常见Bug分析
- JAVA_HOME环境变量定义错误,例如:
说明,这里一定要注意JAVA_HOME单词的拼写,JAVA_HOME中定义的JDK是存在的,还有后面的路径不能有分号“;”.
- MySQL版本比较低(建议mysql5.7或mariadb10.5及以上版本),例如:
当执行nacos-mysql.sql文件时,出现如下错误:
- sql文件不存在,例如
- SQL文件应用错误,例如:
- Nacos的application.properties配置文件中,连接数据库的配置错误.
- nacos配置文件application.properties配置错误,例如:
- 服务启动时,端口被占用了。例如:
- 服务注册时,服务名不正确,格式不正确,配置文件名字不正确,或者没有配置文件,例如:
- 磁盘写权限问题,例如:
- 基于Nacos实现服务注册失败,例如
- 客户端500异常,例如
- 服务调用时,连接异常,例如:
- 客户端404异常,例如:
- 服务调用时底层404问题,例如:
- 服务访问问题,例如:
- 依赖注入异常,例如:
- 客户端请求方式与服务端不匹配,例如:
- 依赖版本问题,例如:
- 服务配置读取问题,例如:
- NullPointerException,例如
- 端口被占用,例如
- UnknownHostException,例如
- Fien接口对象依赖注入异常,例如
- 服务访问时的404异常,例如:
- Feign接口方法中@PathVariable注解参数定义问题,例如:
- 读数据超时,例如:
- 依赖注入异常,例如: