Nacos服务发现
什么是服务发现
在微服务架构中,整个系统会按职责能力划分为多个服务,通过服务之间协作来实现业务目标。这样在我们的代码中免不了要进行服务间的远程调用,服务的消费方要调用服务的生产方,为了完成一次请求,消费方需要知道服务生产方的网络位置(IP地址和端口号)。
我们的代码可以通过读取配置文件的方式读取服务生产方网络位置,如下:
远程调用
SpringBoot怎么实现?
服务生产方
Service B是服务的生产方,暴露/service服务地址,实现代码如下:
@SpringBootApplication
@RestController
public class SpringRestProviderBootstrap {
public static void main(String[] args) {
SpringApplication. run(SpringRestProviderBootstrap.class, args);
}
@GetMapping(value = "/service") //暴露服务
public String service(){
return "provider invoke" ;
}
}
配置文件
server.port = 56010
Service A (服务消费者)
@SpringBootApplication
@RestController
public class SpringRestConsumerBootstrap {
public static void main(String[] args) {
SpringApplication. run(SpringRestConsumerBootstrap.class, args);
}
@Value("${provider . address}")
private String providerAddress;
@GetMapping(value = "/service")
public String service(){
RestTemplate restTemplate = new RestTemplate();
//调用服务
String providerResult = restTemplate . getFor0bject("http://" + providerAddress +
"/service" ,String. class);
return "consumer invoke|” + providerResult;
}
}
配置文件
server.port = 56020
#服务生产方地址
provider.address = 127.0.0.1:56010
首先,微服务可能是部署在云环境的,服务实例的网络位置或许是动态分配的。另外,每-个服务一般会有多个实例来做负载均衡,由于宕机或升级,服务实例网络地址会经常动态改变。再者,每一个服务也可能应对临时访问压力增加新的服务节点。正如下图所示:
基于以上的问题,服务之间如何相互感知?服务如何管理?这就是服务发现的问题了。如下图
上图中服务实例本身并不记录服务生产方的网络地址,所有服务实例内部都会包含服务发现客户端。
(1 )在每个服务启动时会向服务发现中心上报自己的网络位置。这样,在服务发现中心内部会形成一个服务注册表,服务注册表是服务发现的核心部分,是包含所有服务实例的网络地址的数据库。
( 2 )服务发现客户端会定期从服务发现中心同步服务注册表,并缓存在客户端。
(3)当需要对某服务进行请求时,服务实例通过该注册表,定位目标服务网络地址。若目标服务存在多个网络地址,则使用负载均衡算法从多个服务实例中选择出一个,然后发出请求。
主流服务发现与配置中心对比
目前市面上用的比较多的服务发现中心有: Nacos. Eureka. Consul和Zookeeper。
服务发现快速入门
Spring Cloud服务协作流程
Spring Cloud 常见的集成方式是使用Feign+ Ribbon技术来完成服务间远程调用及负载均衡的,如下图:
负载均衡的概念
在SpringCloud服务协议流程中, ServiceA通过负载均衡调用ServiceB,'下边来了解一下负载均衡 :
负载均衡就是将用户请求(流量)过-定的策略 .分摊在多个服务实例上执行,它是系统处理高并发、缓解网络压力和进行服务端扩容的重要手段之一。 它分为服务端负载均衡和客户端负载均衡。
服务端负载均衡
在负载均衡器中维护一个可用的服务实例清单 ,当客户端请求来临时,负载均衡服务器按照某种配置好的规则(负载均衡算法)从可用服务实例清单中选取其-去处理客户端的请求。 这就是服务端负载均衡。
例如Nginx ,通过Nginx进行负载均衡,客户端发送请求至Nginx , Nginx通过负载均衡算法,在多个服务器之间选择一个进行访问。 即在服务器端再进行负载均衡算法分配。
客户端服务负载均衡
Ribbon ,就属于客户端负载均衡。在ribbon客户端会有 一个服务实例地址列表,在发送请求前通过负载均衡算法选择一个服务实例 ,然后进行访问,这是客户端负载均衡。即在客户端就进行负载均衡算法分配。
Ribbon是一个客户端负载均衡器,它的责任是从一组实例列表中挑选合适的实例,如何挑选?取决于负载均衡策略。
Ribbon核心组件IRule是负载均衡策略接口,它有如下实现,大家仅做了解:
RoundRobinRule**(默认):轮询,即按一定的顺序轮换获取实例的地址。
RandomRile:随机,即以随机的方式获取实例的地址。
AvailabilityFilteringRule:会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,以及并发的连接数量超过阈值的服务然后对剩余的服务列表按照轮询策略进行访问**;
WeightedResponseTimeRule:根据平均响应时间计算所有服务的权重响应时间越快,服务权重越大,被选中的机率越高;
刚启动时,如果统计信息不足则使用RoundRobinRule策略等统计信息足够时,会切换到
WeightedResponse TimeRule
RetryRule:先按照RoundRobinRule的策略获取服务,如果获取服务失败,则在指定时间内会进行重试,获取可用的服务;
BestAvailableRule:会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个 并发量最小的服务;
Feign介绍
Feiga是Netflix开发的声明式、模板化的HTTP客户端, Feign可以帮助我们更快捷、优雅地调用HTTP API。
Feign的英文表意为“假装,伪装,彩”,可以理解为将HTTP报文请求方式伪装为简单的java接口调用方式。
参考第1章节的ServiceA调用ServiceB的例子,我们使用Feign实现这个过程,代码如下:
@SpringBootApplication
@RestController
public class SpringRestProviderBootstrap {
public static void main(String[] args) {
SpringApplication. run(SpringRestProviderBootstrap.class, args);
}
@GetMapping(value = "/service") //暴露服务
public String service(){
return "provider invoke";
}
}
Feign调用方式
1.生明Feign客户端
生成一个动态代理对象,从服务中心拿服务实例地址,发起远程调用
@FeignClient(value = "serviceB")
public interface ServiceBAgent {
/**
*根据用户名查询账号信息
本
@param username用户名
@return账号信息
*/
@GetMapping(value = "/service")
public String service();
}
2.业务调用
@Autowired
private ServiceBAgent serviceBAgent. ;
//....略
serviceBAgent . service();
//....略
serviceB的具体访问地址, Feign会交由ribbon获取,若该服务有多个实例地址, ribbon会采用指定的负载均衡策略选取实例。
Feign兼容spring的web注解(如: @GetMapping) ,它会分析声明Feign客户端方法中的Spring注解,得出Http请求method、参数信息以及返回信息结构。
当业务调用Feign客户端方法时,会调用代理类,根据以上分析结果,由代理类完成实际的参数封装、远程http请求,返回结果封装等操作。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</ artifactId>
</ dependency>
Feign默认集成了Ribbon,可直接使用
还需要在spring cloud启动类中标注@EnableFeignClients ,表明此项目开启Feign客户端:
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class SpringRestlonsumerBootstrap {
public static void main(String[] args) {
SpringApplication. run(SpringRestConsumerBootstrap.class, args);
创建父工程
pom依赖
<packaging>pom</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8