1. Nacos注册中心简介
1.1 背景分析
在微服务中,首先需要面对的问题就是如何查找服务(软件即服务),其次,就是如何在不同的服务之间进行通信。如何更好更方便的管理应用中的每一个服务,如何建立各个服务之间联系的纽带,由此注册中心诞生(例如淘宝网卖家提供服务,买家调用服务)。
市面上常用的注册中心有Zookeeper(雅虎Apache),Eureka(Netfix),Nacos(Alibaba),Consul(Google)。下面我们选择Nacos进行学习,Nacos它很好的支持了阿里的双11活动,不仅可以做注册中心,还可以作为配置中心,稳定性和性能都很好。
1.2 Nacos概述
Nacos (DynamicNaming and Configuration Service)是一个应用于服务注册与发现、配置管理的平台。它孵化与阿里巴巴,成长于十年双十一的洪峰考研,沉淀了简单易用,稳定可靠,性能卓越的核心竞争力。其官网地址如下:
https://nacos.io/zh-cn/docs/quick-start.html
2. 构建Nacos服务
2.1 下载与安装
第一步:Nacos下载,可在浏览器直接输入如下地址:
https://github.com/alibaba/nacos/releases
第二步:选择对应的版本,直接下载,如图所示:
第三步:解压Nacos(不要解压到中文目录下),其目录结构如下:
2.2 初始化配置
第一步:找到conf/nacos-mysql.sql文件里的sql脚本,然后登录mysql,然后基于脚本文件中的描述,创建数据库(名字nacos_config,编码方式utf8mb4),并执行脚本文件,脚本执行成功之后会创建一些表,如图所示。
说明:在执行此文件时,要求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&serverTimezone=UTC&connectTimeout=1000&socketTimeout=3000&autoReconnect=true
db.user.0=root
db.password.0=root
2.3 服务启动与访问
第一步:启动Nacos服务。
Windows启动命令(standalone代表着单机模式运行,非集群模式):
startup.cmd -m standalone
说明:
1)执行命令时,要么配置环境变量,要么直接在nacos/bin目录下通过cmd命令窗口执行。
2)nacos启动时需要本地环境变量中配置JAVA_HOME(对应jdk的安装目录)。
第二步:访问Nacos服务。
打开浏览器,输入http://localhost:8848/nacos地址,出现如下登录页面:
其中,默认账号:nacos,默认密码:nacos。
3. 服务注册与调用入门
3.1 业务描述
创建两个项目Module分别为服务提供者和服务消费者,两者都要注册到NacosServer中(这个本质上就是一个web服务,默认端口为8848),然后服务提供者可以为服务消费者提供远端调用服务(例如,支付服务是服务提供方,订单服务为服务消费方),如图所示。
3.2 创建Maven父工程
第一步:创建maven module工程05-jt-sca-nacos,此工程为我们后续项目模块的父工程,这个工程的作用就是来管理子模块中所需要的共性依赖。
第二步:导入需要的核心依赖,其pom文件的内容如下:
<?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>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.2.RELEASE</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.cy</groupId>
<artifactId>06-jt-sca-nacos</artifactId>
<version>1.0-SNAPSHOT</version>
<modules>
<module>sca-providers</module>
<module>sca-consumers</module>
</modules>
<packaging>pom</packaging>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8
</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8
</project.reporting.outputEncoding>
<spring.cloud-version>Hoxton.SR8</spring.cloud-version>
<spring.cloud.alibaba-version>
2.2.5.RELEASE
</spring.cloud.alibaba-version>
</properties>
<!--依赖管理-->
<dependencyManagement>
<dependencies>
<dependency>
<!--spring cloud依赖,此依赖需要springboot的支持,依赖中定义了spring cloud微服务规范-->
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud-version}</version>
<!--import表示此工程下的子工程可以直接引用这个依赖中的版本-->
<scope>import</scope>
<!--当scope为import时,这里的类型必须为pom类型-->
<type>pom</type>
</dependency>
<!--spring cloud alibaba依赖,此依赖基于spring cloud,当前依赖中使基于微服务规范做了具体的实现-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring.cloud.alibaba-version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
其中的版本可以参考如下网址(涉及到一个兼容问题,不能随意指定其版本)。
https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E
最后,父工程资源初始化完成之后,将src目录删除,因为父工程只负责依赖管理。
3.3 服务提供者创建及注册
第一步:创建服务提供者继承父工程,其module名为sca-providers。
第二步:添加项目依赖。
<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>
第三步:创建并修改配置文件application.yml,实现服务注册。
server:
port: 8081
spring:
application:
name: sca-providers
cloud:
nacos:
server-addr: localhost:8848
第四步:创建启动类,并定义处理请求的控制层对象和方法。
@SpringBootApplication
public class ProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderApplication.class, args);
}
@Value("${server.port}")
private String server;
@RestController
public class ProviderController {
@GetMapping(value = "/provider/echo/{msg}")
public String doEcho(@PathVariable String msg) throws IllegalAccessException {
//通过如下语句默认服务调用过程中的异常
if(msg==null||msg.length()<3){
throw new IllegalAccessException("参数不合法");
}
return server+"say:Hello Nacos Discovery " + msg;
}
}
}
第五步:启动启动类,然后刷新nacos服务,检测服务是否注册成功。
第六步:打开浏览器,输入http://localhost:8081/provider/echo/msg,进行访问。
3.4 服务消费者的发现及调用
第一步:创建服务消费者继承父工程,module名为sca-consumers。
第二步:添加项目依赖。
<dependencies>
<!--Web服务-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.3.2.RELEASE</version>
</dependency>
<!--服务的注册和发现()-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2.2.5.RELEASE</version>
</dependency>
<!--添加OpenFeign依赖,基于此依赖进行远程服务调用-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
第三步:修改配置文件application.yml。
server:
port: 8091
spring:
application:
name: sca-consumer
cloud:
nacos:
disconvery:
server-addr: localhost:8848 #从哪里去查服务
feign: #启用feign方式服务调用时的异常处理机制
hystrix:
enabled: true #默认值为false
第四步:创建启动类并实现服务消费。
@SpringBootApplication
@EnableFeignClients //启动feign方式的服务调用
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
@Value("${server.port}")
private String server;
//构建远程过程调用对象,基于此对象访问远程对象
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
//构建负载均衡远程过程调用对象
@Bean
@LoadBalanced
public RestTemplate loadBalancedRestTemplate(){
return new RestTemplate();
}
@RestController
public class ConsumerController{
@Autowired
private RestTemplate restTemplate;
/**
负载均衡客户端对象:
基于此对象可以从注册中心获取服务实例
*/
@Autowired
private LoadBalancerClient loadBalancerClient;
/**
* 应用了负载均衡功能的RestTemplate对象
*/
@Autowired
private RestTemplate loadBalancedRestTemplate;
@GetMapping("/consumer/doRestEcho1")
public String doRestEcho1(){
/**
* 说明:
基于RestTemplate进行远程服务调用,但是不具备负载均衡特性,这种方式不需要nacos,
是直接通过固定的ip和端口进行服务调用的,此方式不适合多服务实例调用。
*/
String url ="http://localhost:8081/provider/echo/" +server;
//远程调用
return restTemplate.getForObject(url, String.class);//String.class是调用服务响应数据类型
}
@GetMapping("/consumer/doRestEcho2")
public String doRestEcho2(){
/**
* 说明:
基于RestTemplate进行远程服务调用,但是在调用之前基于loadBalancedClient对象去从nacos中心基于服务名查找
服务实例(可能有多个),此时会在本地按照一定算法去选择服务实例,然后进行服务调用。
*/
//基于服务名(nacos中服务表中的名字)查找服务实例
ServiceInstance choose = loadBalancerClient.choose("sca-providers");
String ip = choose.getHost();//获取ip地址
int port = choose.getPort();//获取端口号
//构建远程调用的url
/*方法1:
String url = "http://"+ip+":"+port+"provider/echo/"+server;*/
//方法2:
String url = String.format("http://%s:%s/provider/echo/%s", ip,port,server);
//远程调用-RPC
return restTemplate.getForObject(url, String.class);
}
@GetMapping("/consumer/doRestEcho3")
public String doRestEcho3(){
/**
* 说明:
基于负载均衡特性的RestTemplate进行远程服务调用,实现的功能与方式2相同,只是代码上做了简化。
从效率上会稍微逊色与方式2,因为底层会对请求进行拦截,拦截到以后再基于loadBalancedClient获取
服务实例进行调用。
*/
String url = String.format("http://%s/provider/echo/%s", "sca-provider",server);
return loadBalancedRestTemplate.getForObject(url , String.class);
}
}
}
第五步:启动消费者服务,并在浏览器输入:http://localhost:8091/consumer/doRestEcho1,进行访问,假如访问成功,如图所示效果。
3.5 小结面试分析
- 为什么要将服务注册到nacos?(为了更好的查找这些服务)
- 在Nacos中服务提供者是如何向Nacos注册中心(Registry)续约的?(5秒心跳)
- 对于Nacos服务来讲它是如何判定服务实例的状态?(检测心跳包,15,30)
- 服务启动时如何找到服务启动注册配置类?(NacosNamingService)
- 服务消费方是如何调用服务提供方的服务的?(RestTemplate)
4. 服务负载均衡设计及实现
4.1 入门案例
一个服务实例可以处理的请求是有限的,假如服务实例的并发访问比较大,我们会启动多个服务实例,让这些服务实例采用一定的策略均衡(轮询,权重,随机,hash等)的处理并发请求。下面将学习Nacos服务中的负载均衡的应用(Nacos客户端负载均衡)。
4.2 实现过程
第一步:修改ConsumerController类,注入LoadBalancerClient对象,添加doRestLoadBalancerClientEcho方法,然后进行访问。
/**
负载均衡客户端对象:
基于此对象可以从注册中心获取服务实例
*/
@Autowired
private LoadBalancerClient loadBalancerClient;
@GetMapping("/consumer/doRestEcho2")
public String doRestEcho2(){
/**
* 说明:
基于RestTemplate进行远程服务调用,但是在调用之前基于loadBalancedClient对象去从nacos中心基于服务名查找
服务实例(可能有多个),此时会在本地按照一定算法去选择服务实例,然后进行服务调用。
*/
//基于服务名(nacos中服务表中的名字)查找服务实例
ServiceInstance choose = loadBalancerClient.choose("sca-providers");
String ip = choose.getHost();//获取ip地址
int port = choose.getPort();//获取端口号
//构建远程调用的url
/*方法1:
String url = "http://"+ip+":"+port+"provider/echo/"+server;*/
//方法2:
String url = String.format("http://%s:%s/provider/echo/%s", ip,port,server);
//远程调用-RPC
return restTemplate.getForObject(url, String.class);
}
第二步:打开IDEA服务启动配置。
第三步:修改并发运行选项:勾选上Allow parallel run。
第四步:修改sca-provider的配置文件端口,分别以8081,8082的端口方式进行启动。
第五步:启动成功以后,访问nacos的服务列表,检测服务是否注册成功。
第六步:启动sca-consunmer项目模块,打开浏览器,输入下列网址进行反复服务访问。
http://localhost:8091/consumer/doRestEcho2
然后会发现sca-providers的两个服务都可以处理sca-consumers的请求。
4.3 分析
这里多个实例并发提供服务的方式为负载均衡。并且负载均衡实现默认Nacos集成的Ribbon来实现的,Ribbon配合RestTemplate,可以非常容易的实现服务之间的访问。Ribbon是Spring Cloud核心组件之一,它提供的最重要的功能就是客户端的负载均衡(客户端可以采用一定的算法,例如,轮询访问服务端实例信息),这个功能可以让我们轻松的将面向服务的REST模板请求自动地转换成客户端负载均衡方式的服务调用。
4.4 @LoadBalanced
当使用RestTemplate进行远程服务调用时,假如需要负载均衡,可以在RestTemplate对象构建时,使用@LoadBalanced对构建RestTemplate的方法进行修饰,例如:
//构建负载均衡远程过程调用对象
@Bean
@LoadBalanced
public RestTemplate loadBalancedRestTemplate(){
return new RestTemplate();
}
在需要RestTemplate实现负载均衡调用的地方进行依赖注入。
/**
* 应用了负载均衡功能的RestTemplate对象
*/
@Autowired
private RestTemplate loadBalancedRestTemplate;
接下来,可以在对应的服务消费端调用方法,基于RestTemplate借助服务名进行服务调用。
@GetMapping("/consumer/doRestEcho3")
public String doRestEcho3(){
/**
* 说明:
基于负载均衡特性的RestTemplate进行远程服务调用,实现的功能与方式2相同,只是代码上做了简化。
从效率上会稍微逊色与方式2,因为底层会对请求进行拦截,拦截到以后再基于loadBalancedClient获取
服务实例进行调用。
*/
String url = String.format("http://%s/provider/echo/%s", "sca-provider",server);
return loadBalancedRestTemplate.getForObject(url , String.class);
}
RestTemplate在发送请求的时候会被LoadBalancerInterceptor拦截,它的作用就是用于RestTemplate的负载均衡,LoadBalancerInterceptor将负载均衡的核心逻辑交给了loadBalancer。
@LoadBalanced注解是属于Spring,而不是Ribbon的,Spring在初始化容器的时候,如果检测到Bean被@LoadBalanced注解,Spring会为其设置LoadBalancerInterceptor的拦截器。
4.5 Ribbon负载均衡策略
基于Ribbon方式的负载均衡,Netflix默认提供了七种负载均衡策略,对于SpringCloud Alibaba解决方案中又提供了NacosRule策略,默认的负载均衡策略是轮训策略。如图所示:
当系统提供的负载均衡策略不能满足我们需求时,我们还可以基于IRule接口自己定义策略。
4.6 小结面试分析
- @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进行实现)
5. 基于Feign的远程服务调用
5.1 背景分析
服务消费方基于rest的请求服务提供方的服务时,直接的方式就是自己拼接url,拼接参数然后实现服务调用,但每次服务调用都需要这么拼接,代码量复杂且不易维护,由此,Feign诞生了。
5.2 什么是Feign
Feign是一种声明式的Web服务客户端,底层封装了对Rest技术的应用,通过Feign可以简化服务消费方对远程服务提供方法的的调用实现。
如图所示:
Feign最早是由Netflix公司进行维护的,后来Netflix不再对其进行维护,最终Feign由社区进行维护,更名为OpenFeign。
5.3 Feign应用实践
第一步:在服务消费方,添加项目依赖(SpringCloud团队基于OpenFeign研发了starter)。
<!--添加OpenFeign依赖,基于此依赖进行远程服务调用-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
第二步:在启动类上添加@EnableFeignClient注解。
@SpringBootApplication
@EnableFeignClients //启动feign方式的服务调用
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
第三步:定义Http请求API,基于此API借助OpenFeign访问远端服务。
/* 基于@FeignClient注解,定义远程调用接口,这个接口不需要我们自己写实现类,底层会在服务启动时帮我们创建
实现代理对象,在实现类的内部进行远程服务调用*/
@FeignClient(name = "sca-providers", /*name属性的值为nacos中的服务名 */
contextId="RemoteProviderService") /* contextId的作用是唯一标识服务的调用,避免多个FeignClient调用带来的冲突 */
public interface RemoteProviderService {
@GetMapping("/provider/echo/{msg}")
public String echoMsg(@PathVariable("msg") String msg);
}
其中,@FeignClient描述的接口底层会为其创建实现类。
第四步:创建FeignConsumerController中并添加feign访问。
RestController
public class FeignConsumerController {
@Autowired
private RemoteProviderService remoteProviderService;
//基于feign方式的服务调用
//http://localhost:8091/fegin/echo/helloKuGou
@GetMapping("/feign/echo/{msg}")
public String doFeignEcho(@PathVariable String msg){
//基于feign方式进行远端服务调用(前提是服务必须存在)
return remoteProviderService.echoMsg(msg);
}
}
第五步:启动消费者服务,在浏览器中直接通过feign客户端进行访问。
说明:一个服务提供方通常会提供很多资源服务,服务消费方基于同一个服务提供方写了很多服务调用接口。为了能对远程调用服务进行唯一识别,特地为服务接口指定一个contextId。
5.4 Feign配置进阶实践
当我们进行远程服务调用时,假如调用的服务突然不可用了,或者调用超时,一般服务提供方会给吃具体的容错方案。
第一步:定义FallbackFactory接口的实现。
**
* 基于此对象处理RemoteProviderService接口调用时出现的异常问题
*/
@Component
public class ProviderFallbackFactory implements FallbackFactory<RemoteProviderService> {
/**
*此方法会在RemoteProviderService接口服务调用时,出现异常后执行。
* @param throwable 用于接收异常
* @return
*/
@Override
public RemoteProviderService create(Throwable throwable) {
return (msg)-> {
return "出现异常";
};
}
}
第二步:在Feign访问接口中应用FallbackFactory对象。
@FeignClient(name = "sca-providers", /*name属性的值为nacos中的服务名 */
contextId="RemoteProviderService", /* contextId的作用是唯一标识服务的调用,避免多个FeignClient调用带来的冲突 */
fallbackFactory = ProviderFallbackFactory.class)
public interface RemoteProviderService {
@GetMapping("/provider/echo/{msg}")
public String echoMsg(@PathVariable String msg);
}
第三步:在配置文件application.yml中添加如下配置,启动feign方式调用时的服务中断处理机制。
feign:
hystrix:
enabled: true #默认值为false
第四步:在服务提供方对应的方法中添加Thread.sleep(50000)模拟耗时操作,然后启动服务进行访问测试。
5.5 Feign调用过程分析
Feign应用过程分析(底层逻辑):
1)通过 @EnableFeignCleints 注解告诉springcloud,启动 Feign Starter 组件。
2) Feign Starter 在项目启动过程中注册全局配置,扫描包下所由@FeignClient注解描述的接口,然后由系统底层创建接口实现类(JDK代理类),并构建类的对象,然后交给spring管理(注册 IOC 容器)。
3) 接口被调用时被动态代理类逻辑拦截,将 @FeignClient 请求信息通过编码器生成 Request对象,基于此对象进行远程过程调用。
4) 请求对象经Ribbon进行负载均衡,挑选出一个健康的 Server 实例(instance)。
5) 通过 Client 携带 Request 调用远端服务返回请求响应。
6) 通过解码器生成 Response 返回客户端,将信息流解析成为接口返回数据。
6. 小结面试分析
- 为什么使用feign?(基于Feign可以更加友好的实现服务调用,简化服务消费方对服务提供方方法的调用)。
- @FeignClient注解的作用是什么?(告诉Feign Starter,在项目启动时,为此注解描述的接口创建实现类-代理类)
- Feign方式的调用,底层负载均衡是如何实现的?(Ribbon)
- @EnableFeignCleints 注解的作用是什么?(描述配置类,例如启动类)
7.总结分析
7.1 重点分析
- 何为注册中心?(用于记录服务信息的一个web服务,例如淘宝平台,滴滴平台,美团外卖平台,……)
- 注册中心的核心对象?(服务提供方,服务消费方,注册中心-Registry)
- 市面上常用注册中心?(Google-Consul,Alibaba-Nacos,…)
- 微服务架构下项目的构建过程?(聚合工程)
7.2 FAQ分析
- Nacos是什么,提供了什么特性(服务的注册、发现、配置)?
- 你为什么会选择Nacos?(活跃度、稳定、性能、学习成本)
- Nacos的官网?(nacos.io)
- Nacos在github的源码?(github.com/alibaba/nacos)
- Nacos在windows环境下安装?(解压即可使用)
- Nacos在windows中的的初步配置?(application.properties访问数据库的数据源)
- Nacos服务注册的基本过程?(服务启动时发送web请求)
- Nacos服务消费的基本过程?(服务启动时获取服务实例,然后调用服务)
- Nacos服务负载均衡逻辑及设计实现?(Ribbon)
- 注册中心的核心数据是什么?(服务的名字和它对应的网络地址)
- 注册中心中心核心数据的存取为什么会采用读写锁?(底层安全和性能)
- Nacos健康检查的方式?(基于心跳包机制进行实现)
- Nacos是如何保证高可用的?(重试,本地缓存、集群)
- Feign是什么,它的应用是怎样的,feign应用过程中的代理对象是如何创建的(JDK)?
- Feign方式的调用过程,其负载均衡是如何实现?(Ribbon)