教学目标
1)理解什么是微服务
2)能够说出服务发现的概念以及使用场景
3)了解主流的服务发现中心
4)掌握Nacos作为服务发现中心的快速入门方法
5)理解Nacos服务发现的核心概念及数据模型
6)掌握使用Nacos控制台管理服务的操作方法
7)了解Nacos服务发现API的使用方式
8)掌握Spring cloud alibaba实际项目架构案例
9)掌握Nacos与dubbo集成方式
1 概览
1.1 从单体架构到微服务
1.1.1 单体架构
Web应用程序发展的早期,大部分web工程师将所有的功能模块打包到一起并放在一个web容器中运行,所有功能模块使用同一个数据库,同时,它还提供API或者UI访问的web模块等。
尽管也是模块化逻辑,但是最终它还是会打包并部署为单体式应用,这种将所有功能都部署在一个web容器中运行的系统就叫做单体架构(也叫:巨石型应用)。
单体架构有很多好处:
开发效率高
:模块之间交互采用本地方法调用,并节省微服务之间的交互讨论时间与开发成本。
容易测试
:IDE都是为开发单个应用设计的、容易测试——在本地就可以启动完整的系统。
容易部署
:运维成本小,直接打包为一个完整的包,拷贝到web容器的某个目录下即可运行。
但是,上述的好处是有条件的,它适用于小型简单应用,对于大规模的复杂应用,就会展现出来以下的不足:
复杂性逐渐变高,可维护性逐渐变差
:所有业务模块部署在一起,复杂度越来越高,修改时牵一发动全身。
版本迭代速度逐渐变慢
:修改一个地方就要将整个应用全部编译、部署、启动时间过长、回归测试周期过长。
阻碍技术创新
:若更新技术框架,除非你愿意将系统全部重写,无法实现部分技术更新。
无法按需伸缩
:通过冗余部署完整应用的方式来实现水平扩展,无法针对某业务按需伸缩。
1.1.2 微服务
许多大型公司,通过采用微服务架构解决了上述问题。其思路不是开发一个巨大的单体式的应用,而是将应用分解为小的、互相连接的微服务。
一个微服务一般完成某个特定的功能,比如订单服务、用户服务等等。每一个微服务都是完整应用,都有自己的业务逻辑和数据库。一些微服务还会发布API给其它微服务和应用客户端使用。
比如,一个前面描述系统可能的分解如下:
每一个业务模块都使用独立的服务完成,这种微服务架构模式也影响了应用和数据库之间的关系,不像传统多个业务模块共享一个数据库,微服务架构每个服务都有自己的数据库。
微服务架构的好处:
- 分而治之,职责单一;易于开发、理解和维护、方便团队的拆分和管理
- 可伸缩;能够单独的对指定的服务进行伸缩
- 局部容易修改,容易替换,容易部署,有利于持续集成和快速迭代
- 不会受限于任何技术栈
1.2 什么是服务发现
在微服务架构中,整个系统会按职责能力划分为多个服务,通过服务之间协作来实现业务目标。这样在我们的代码中免不了要进行服务间的远程调用,服务的消费方要调用服务的生产方,为了完成一次请求,消费方需要知道服务生产方的网络位置(IP地址和端口号)。
我们的代码可以通过读取配置文件的方式读取服务生产方网络位置,如下:
我们通过Spring boot技术很容易实现:
Service B(服务生产者)
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.getForObject("http://" + providerAddress +
"/service",String.class);
return "consumer invoke | " + providerResult;
}
}
配置文件:
server.port = 56020
# 服务生产方地址
provider.address = 127.0.0.1:56010
访问http://127.0.0.1:56020/service,输出以下内容:
consumer invoke | provider invoke
看上去很完美,但是,仔细考虑以下,此方案对于微服务应用而言行不通。
首先,微服务可能是部署在云环境的,服务实例的网络位置或许是动态分配的。另外,每一个服务一般会有多个实例来做负载均衡,由于宕机或升级,服务实例网络地址会经常动态改变。再者,每一个服务也可能应对临时访问压力增加新的服务节点。正如下图所示:
基于以上的问题,服务之间如何相互感知?服务如何管理?这就是服务发现的问题了。如下图:
上图中服务实例本身并不记录服务生产方的网络地址,所有服务实例内部都会包含
服务发现客户端
。(1)在每个服务启动时会向
服务发现中心
上报自己的网络位置。这样,在服务发现中心内部会形成一个服务注册表,服务注册表
是服务发现的核心部分,是包含所有服务实例的网络地址的数据库。(2)
服务发现客户端
会定期从服务发现中心
同步服务注册表
,并缓存在客户端。(3)当需要对某服务进行请求时,服务实例通过该注册表,定位目标服务网络地址。若目标服务存在多个网络地址,则使用负载均衡算法从多个服务实例中选择出一个,然后发出请求。
总结一下,在微服务环境中,由于服务运行实例的网络地址是不断动态变化的,服务实例数量的动态变化 ,因此无法使用固定的配置文件来记录服务提供方的网络地址,必须使用动态的服务发现机制用于实现微服务间的相互感知
。各服务实例会上报自己的网络地址,这样服务中心就形成了一个完整的服务注册表,各服务实例会通过服务发现中心
来获取访问目标服务的网络地址,从而实现服务发现
的机制。
1.3 主流服务发现与配置中心对比
目前市面上用的比较多的服务发现中心有:Nacos、Eureka、Consul和Zookeeper。
从上面对比可以了解到,Nacos作为服务发现中心,具备更多的功能支持项,且从长远来看Nacos在以后的版本会支持SpringCLoud+Kubernetes的组合,填补 2 者的鸿沟,在两套体系下可以采用同一套服务发现和配置管理的解决方案,这将大大的简化使用和维护的成本。另外,Nacos 计划实现 Service Mesh,也是未来微服务发展的趋势。
1.4 Nacos简介
2 Nacos 服务发现快速入门
本小节,我们将演示如何使用Spring Cloud Alibaba Nacos Discovery为Spring cloud 应用程序与 Nacos 的无缝集成。 通过一些原生的spring cloud注解,我们可以快速来实现Spring cloud微服务的服务发现机制,并使用Nacos Server作为服务发现中心,统一管理所有微服务。
2.1 Spring Cloud服务协作流程
现在,我们对Spring cloud内的一些组件还不了解,为了能够完全理解快速入门程序,我们需要学习以下内容。
Spring Cloud 常见的集成方式是使用Feign+Ribbon技术来完成服务间远程调用及负载均衡的,如下图:
(1)在微服务启动时,会向服务发现中心上报自身实例信息,这里ServiceB 包含多个实例。
每个实例包括:IP地址、端口号信息。
(2)微服务会定期从Nacos Server(服务发现中心)获取服务实例列表。
(3)当ServiceA调用ServiceB时,ribbon组件从本地服务实例列表中查找ServiceB的实例,如获取了多个实例如Instance1、Instance2。这时ribbon会通过用户所配置的负载均衡策略从中选择一个实例。
(4)最终,Feign组件会通过ribbon选取的实例发送http请求。采用Feign+Ribbon的整合方式,是由Feign完成远程调用的整个流程。而Feign集成了Ribbon,Feign使用Ribbon完成调用实例的负载均衡。
2.1.1 负载均衡的概念
在SpringCloud服务协议流程中,ServiceA通过负载均衡调用ServiceB,下边来了解一下负载均衡
:
负载均衡就是将用户请求(流量)通过一定的策略,分摊在多个服务实例上执行,它是系统处理高并发、缓解网络压力和进行服务端扩容的重要手段之一。它分为服务端负载均衡
和客户端负载均衡
。
服务器端负载均衡:
在负载均衡器中维护一个可用的服务实例清单,当客户端请求来临时,负载均衡服务器按照某种配置好的规则(负载均衡算法)从可用服务实例清单中选取其一去处理客户端的请求。这就是服务端负载均衡。
例如Nginx,通过Nginx进行负载均衡,客户端发送请求至Nginx,Nginx通过负载均衡算法,在多个服务器之间选择一个进行访问。即在服务器端再进行负载均衡算法分配。
客户端服务负载均衡:
我们接下来要讲的Ribbon,就属于客户端负载均衡。在ribbon客户端会有一个服务实例地址列表,在发送请求前通过负载均衡算法选择一个服务实例,然后进行访问,这是客户端负载均衡。即在客户端就进行负载均衡算法分配。
Ribbon是一个客户端负载均衡器,它的责任是从一组实例列表中挑选合适的实例,如何挑选?取决于负载均衡策略 。
Ribbon核心组件IRule是负载均衡策略接口,它有如下实现,大家仅做了解:
- RoundRobinRule(默认):轮询,即按一定的顺序轮换获取实例的地址。
- RandomRule:随机,即以随机的方式获取实例的地址。
- AvailabilityFilteringRule: 会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,以及并发的连接数量超过阈值的服务,然后对剩余的服务列表按照轮询策略进行访问;
- WeightedResponseTimeRule: 根据平均响应时间计算所有服务的权重,响应时间越快,服务权重越大,被选中的机率越高;
刚启动时,如果统计信息不足,则使用RoundRobinRule策略,等统计信息足够时,会切换到WeightedResponseTimeRule - RetryRule: 先按照RoundRobinRule的策略获取服务,如果获取服务失败,则在指定时间内会进行重试,获取可用的服务;
- BestAvailableRule: 会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务;
- ZoneAvoidanceRule: 默认规则,复合判断server所在区域的性能和server的可用性选择服务器;
可通过下面方式在spring boot 配置文件中修改默认的负载均衡策略:
account‐service.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule
account-service 是调用的服务的名称,后面的组成部分是固定的。
2.1.2 Feign 介绍
Feign是Netflix开发的声明式、模板化的HTTP客户端, Feign可以帮助我们更快捷、优雅地调用HTTP API。Feign的英文表意为“假装,伪装,变形”, 可以理解为将HTTP报文请求方式伪装为简单的java接口调用方式。
参考第1章节的ServiceA调用ServiceB的例子,我们使用Feign实现这个过程,代码如下:
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";
}
}
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();
//....略
是不是非常简单?并且在业务调用时,减少了与业务无关的http请求相关代码的编写,使业务逻辑清晰。咱们分析一下Feign帮我们做了哪些事儿:
- 在 声明Feign客户端 之后,Feign会根据@FeignClient注解使用java的动态代理技术生成代理类,在这里我们指定@FeignClient value为serviceB,则说明这个类的远程目标为spring cloud的服务名称为serviceB的微服务。
- serviceB的具体访问地址,Feign会交由ribbon获取,若该服务有多个实例地址,ribbon会采用指定的负载均衡策略选取实例。
- Feign兼容spring的web注解(如:@GetMapping),它会分析声明Feign客户端方法中的Spring注解,得出Http请求method、参数信息以及返回信息结构。
- 当业务调用Feign客户端方法时,会调用代理类,根据以上分析结果,由代理类完成实际的参数封装、远程http请求,返回结果封装等操作。
另外,别忘了,若在在Spring cloud中使用Feign,需要引入以下依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring‐cloud‐starter‐openfeign</artifactId>
</dependency>
Feign默认集成了Ribbon,可以直接使用。
还需要在spring cloud 启动类中标注@EnableFeignClients,表明此项目开启Feign客户端:
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class SpringRestConsumerBootstrap {
public static void main(String[] args) {
SpringApplication.run(SpringRestConsumerBootstrap.class, args);
}
...
总结:通过上面的学习,我们已经了解Spring cloud的微服务是如何协作的,通过哪些组件的配合能够完成服务间协作?我们了解了什么是负载均衡,Feign用于服务间Http调用,Ribbon用于执行负载均衡算法选取访问实例,而Ribbon的实例列表来源是由Spring cloud的服务发现中心提供(当前实现为Nacos),更详细的内容请学习Spring Cloud的相关课程 。