1.注册中心简介
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/v2/quickstart/quick-start.html
Nacos是Alibaba基于SpringBoot技术,采用Client/Server架构实现的一个注册中心,本质上也是一个web服务。其主要提供服务的注册,发现,配置功能。
2.构建Nacos服务
2.1 准备工作
第一:确保你电脑已配置JAVA_HOME环境变量(Nacos启动时需要),例如:
第二:确保你的MySQL版本为5.7以上(MariaDB10.5以上),例如
2.2 下载与安装
第一步:Nacos下载,可在浏览器直接输入如下地址:
https://github.com/alibaba/nacos/releases
第二步:选择对应版本,直接下载,如图所示:
当前推荐下载的稳定版本是2.1.1版本,可以在nacos官网查看具体推荐下载版本(注意不是上面的下载网址,是上上面的nacos官网):
第三步:解压Nacos(最好不要解压到中文目录下),其目录结构如下:
2.3 初始化配置
第一步:登陆mysql,执行sql脚本。
例如,我们可以使用mysql自带客户端,在命令行首先登录mysql,然后执行source d:/nacos-mysql.sql
指令,执行成功以后,会创建一个nacos_config数据库,打开数据库会看到一些表,如下图:
此时,nacos-mysql.sql脚本已成功执行,相关表已创建,使用此命令执行其他数据库脚本即可,或者也可以在可视化软件端执行脚本;
在执行此文件时,要求mysql的版本大于5.7版本(MariaDB最好10.5.11),否则会出现如下错误:
第二步:配置/conf/application.properties
打开nacos目录下的/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 服务启动与访问
2.4.1 命令行启动
第一步:启动Nacos服务。
如果是Linux/Unix/Mac环境下,执行./startup.sh -m standalone
启动命令(standalone代表着单机模式运行,非集群模式):
如果是Windows环境下执行startup.cmd -m standalone
启动命令(standalone代表着单机模式运行,非集群模式):
说明:
-
执行执行令时要么配置环境变量,要么直接在nacos/bin目录下去执行.
-
nacos启动时需要本地环境变量中配置了JAVA_HOME(对应jdk的安装目录),
-
一定要确保你连接的数据库(nacos_config)是存在的.
-
假如所有的配置都正确,还连不上,检查一下你有几个数据库(mysql,…)
第二步:访问Nacos服务。
打开浏览器,输入http://localhost:8848/nacos
地址,出现如下登陆页面:
其中,默认账号密码为nacos/nacos,想要停止nacos服务只需要使用ctrl+c即可。
2.4.2 IDEA启动Nacos
首先,我们选择Edit Configuration:
我们点击左上角的+
号,选择shell script 选项,添加shell脚本:
我们在其中添加必要的启动信息:
‘’选择右下角的应用,确定后,即可以看到我们创建的nacos启动按钮了:
直接点击就可以启动nacos了:
3.服务注册与调用入门-不使用nacos(重点)
3.1 业务描述
创建两个项目Module分别为服务提供者和服务消费者,两者都要注册到NacosServer中(这个server本质上就是一个web服务,端口默认为8848),然后服务提供者可以为服务消费者提供远端调用服务(例如支付服务为服务提供方,订单服务为服务消费方),如图所示:
3.2 生产者服务创建及注册
第一步:创建服务提供者工程(module名为sca-provider),继承parent工程(sca),在其pom.xml文件中添加nacos依赖:
<!--服务的注册和发现(我们要讲服务注册到nacos)-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
此依赖主要的作用是,WEB启动时会依赖一些API,创建一些对象向nacos发送一些信息,这些信息被称为心跳包,每隔一段发送一些心跳包信息,告知目前服务是否还在运行;
第二步:创建并修改配置文件application.yml(或者application.properties),实现服务注册,关键代码如下:
server:
port: 8081
spring:
application:
name: sca-provider #进行服务注册必须配置服务名
cloud:
nacos:
discovery:
server-addr: localhost:8848
注意:服务名不要使用下划线(“_”),应使用横杠(“-”),这是规则。本地环境,可以省略nacos服务地址,但是在生产环境必须写完整地址,在生产环境,可能各服务不在同一个地址下,需要具体地址来寻找服务。
第三步:创建启动类(假如已有则无需定义),关键代码如下:
package com.jt;
@SpringBootApplication
public class ProviderApplication {
public static void main(String[] args) {ai
SpringApplication.run(ProviderApplication.class, args);
}
}
第四步:启动启动类,然后刷先nacos服务,检测是否服务注册成功,如图所示:
第五步:停掉sca-provider服务,然后不断刷新nacos服务列表,检查服务的健康状态。
刚停止服务时,集群数据,实例数,健康实例数均为0。nacos-provider会每隔一段时间向nacos-server发送一个心跳包,确认当前服务的状态,会发现当前服务已经不健康了;当超过一定时间,还未查询到正常的服务,nacos-server就会认为当前服务已经死掉,不存在了:
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;
/**定义Controller对象(这个对象在spring mvc中给他的定义是handler),
* 基于此对象处理客户端的请求*/
@RestController
public class ProviderController{
//@Value默认读取项目配置文件中配置的内容
//8080为没有读到server.port的值时,给定的默认值
@Value("${server.port:8080}")
private String serverPort;
//http://localhost:8081/provider/echo/tedu
@GetMapping("/provider/echo/{msg}")
public String doRestEcho1(@PathVariable String msg){
return serverPort+" say hello "+msg;
}
}
此时已经可以启动 sca-provider 服务,在浏览器中输入http://localhost:8081/provider/echo/tedu
来访问 sca-provider 服务,测试 sca-provider 服务是否正常;
第二步:创建服务消费者工程(module名为sca-consumer),继承parent工程(sca),为pom.xml文件添加如下内容:
<dependencies>
<!--Web服务-->
<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>
</dependencies>
第三步:创建sca-consumer服务中的配置文件application.yml,关键代码如下:
server:
port: 8090
spring:
application:
name: sca-consumer #服务注册时,服务名必须配置
cloud:
nacos:
discovery:
server-addr: localhost:8848 #从哪里去查找服务
第四步:创建消费端启动类并实现服务消费,关键代码如下:
package com.jt;
@SpringBootApplication
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class,args);
}
}
第五步:在sca-consumer启动类中添加如下方法,用于创建RestTemplate对象.
我们进行服务调用时需要使用一个restTemplate对象,但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.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
/**
* 定义服务消费端Controller,在这个Controller对象
* 的方法中实现对远端服务sca-provider的调用
*/
@RestController
public class ConsumerController {
/**
* 从spring容器获取一个RestTemplate对象,
* 基于此对象实现远端服务调用
*/
@Autowired
private RestTemplate restTemplate;
/**
* 在此方法中通过一个RestTemplate对象调用远端sca-provider中的服务
* @return
* 访问此方法的url: http://localhost:8090/consumer/doRestEcho1
*/
@GetMapping("/consumer/doRestEcho1")
public String doRestEcho01(){
//1.定义要调用的远端服务的url
String url="http://localhost:8081/provider/echo/8090";
//2.基于restTemplate对象中的相关方法进行服务调用
return restTemplate.getForObject(url, String.class);
}
}
第七步:启动消费者服务
在浏览器输入http://localhost:8090/consumer/doRestEcho1
地址进行访问,假如访问成功会出现,如图所示效果:
我们访问8090,但是页面却显示了8081提供的返回信息,这就是不通过nacos的远程服务调用。
3.4 小节面试分析
- 为什么要将服务注册到nacos?(为了更好的查找这些服务)
- 在Nacos中服务提供者是如何向Nacos注册中心(Registry)续约的?(5秒心跳)
- 对于Nacos服务来讲它是如何判定服务实例的状态?(检测心跳包,15,30)
- 服务消费方是如何调用服务提供方的服务的?(RestTemplate)
4.服务负载均衡设计及实现(重点)
4.1 业务描述
一个服务实例可以处理请求是有限的,假如服务实例的并发访问比较大,我们会启动多个服务实例,让这些服务实例采用一定策略均衡(轮询,权重,随机,hash等)的处理并发请求,在Nacos中服务的负载均衡(Nacos客户端负载均衡)是如何应用的?
4.2 LoadBalancerClient应用
LoadBalancerClient对象可以从nacos中基于服务名获取服务实例,然后在工程中基于特点算法实现负载均衡方式的调用,案例实现如下:
第一步:修改ConsumerController类,注入LoadBalancerClient对象,并添加doRestEcho2方法,通过获取不同的访问端口进行访问的分配,然后进行服务访问.
@Autowired
private LoadBalancerClient loadBalancerClient;
@Value("${spring.application.name:8090}")
private String appName;
@GetMapping("/consumer/doRestEcho02")
public String doRestEcho02(){
ServiceInstance serviceInstance = loadBalancerClient.choose("sca-provider");
String url = String.format("http://%s:%s/provider/echo/%s",serviceInstance.getHost(),serviceInstance.getPort(),appName);
System.out.println("request url:"+url);
return restTemplate.getForObject(url, String.class);
}
}
第二步:打开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
对象对远程服务进行访问时,此请求就会被一个拦截器拦截下来,请求被拦截到以后,就会优先基于服务名找到服务实例(负载均衡):
@Bean
@LoadBalanced
public RestTemplate loadBalancedRestTemplate(){
return new RestTemplate();
}
在需要RestTemplate
实现负载均衡调用的地方进行依赖注入.例如在ConsumerController
类中添加loadBalancedRestTemplate
属性
@Autowired
private RestTemplate loadBalancedRestTemplate;
接下来,可以在对应的服务端调用方的方法内,基于RestTemplate
借助服务名进行服务调用, 例如:
@GetMapping("/consumer/doRestEcho3")
public String doRestEcho3(){
String url=String.format("http://%s/provider/echo/%s","sca-provider",appName);
//向服务提供方发起http请求,获取响应数据
return loadBalancedRestTemplate.getForObject(
url,//要请求的服务的地址
String.class);//String.class为请求服务的响应结果类型
}
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.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进行实现)
5.默认负载均衡策略实现
5.1 方式一:通过配置文件实现
我们可以通过在sca-consume
中的application.yml
配置文件中添加下面的语句来指定负载均衡策略:
#客户端负载均衡
sca-provider: #要访问的服务名(这个服务名可能会对应着多个实例)
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #定义负载均衡算法
后期我们可以将其写入配置中心,这样就可以不重启就实现负载均衡策略的变更;
5.2 方式二:通过配置类或启动类实现
在配置类或启动类中,我们只要重新定义一个IRule
对象规定其默认的返回对象并将其交给spring管理,就可以改变其默认的轮询方式:
@Bean
public IRule ribbonRule(){
return new RandomRule();
}
5.3 俩种方式对比
修改配置类或启动类相对而言要比修改配置文件简单很多,但我们在完成代码时还要考虑代码质量,对于之后整个项目的维护,修改配置类肯定是更加方便的;
如果我们上线的项目要进行负载均衡项目策略的变更需要更改代码,那无疑将是恐怖的一件事情;
6.核心知识点归纳
-
负载均衡诞生背景:
- 为了解决大批量数据访问时,单个节点的压力过大可能造成的服务器崩溃问题,通过在网络中设置一组可以提供相同服务的计算机,来共同提供服务,负载均衡就是解决如何分配访问压力到各个提供服务的计算机的;可以将其理解为银行网点,如果所有人都只到一个网点办理业务,那么网点压力过大,无法处理,如果设置多个银行服务网点,就可以有效的分担办理业务的处理压力,而我们要解决的就是如何通过负载均衡将需要办理业务的人员,按照某种规则分散到各个网点;
-
客户端负载均衡与服务端负载均衡的区别?
- 客户端负载均衡:例如我们的sca-consumer去访问sca-provider,负载均衡算法我们写到了sca-consumer中,sca-consumer相对于sca-peovider来说是消费端也是客户端,sca-peovider是提供具体服务的一方,其可能有多个实例存在;那么sca-consumer如何将请求指向不同的实例呢?我们可以在sca-consumer中,将所有的sca-peovider实例从注册中心拿到,假设存储到一个集合中,这时我们就可以通过一定的规则(例如轮询,随机等)从这个集合中取出一个实例并交给sca-consumer,然后sca-consumer再进行调用,此时就是客户端的负载均衡;
- 服务端负载均衡:例如我们通过浏览器访问nginx,在负载均衡中,我们的访问请求到达nginx后,由nginx将请求分发到各个服务上,这个过程就叫服务端的负载均衡,我们发起很多请求到nginx,nginx作为一个反向代理服务器,在这个服务器内部,通过一些负载均衡算法,将我们的请求转发到不同的其他服务;
-
在SpriingCloud中,我们是如何基于负载均衡算法进行服务的调用的?
- 通过
LoadBalancerClient
对象(其实际是一个接口)的一个实现类RibbonLoadBalancerClient
来进行服务的调用,这个对象的功能就是服务的发现及负载均衡;其通过注册中心去发现获取服务列表,拿到具体服务后,通过Ribbon
负载均衡组件进行服务的调用,Ribbon
负载均衡组件提供了一套负载均衡算法;
- 通过
-
@Loadbalanced
注解的应用:@Loadbalanced
注解用来描述RestTemplate对象,为此对象的远程调用进行赋能,当使用此对象进行远程调用时,会默认添加一个拦截器,在拦截器中基于LoadBalancerClient
对象进行服务的发现,基于Ribbon
进行负载均衡的调用;
-
我们如何修改负载均衡策略?
- 可以通过配置文件或配置类进行指定;