目录
一、微服务介绍
1.微服务架构
微服务架构(MSA)的基础是将单个应用程序开发为一组小型独立服务,这些独立服务在自己的进程中运行,独立开发和部署。
这些服务使用轻量级 API 通过明确定义的接口进行通信。这些服务是围绕业务功能构建的,每项服务执行一项功能。由于它们是独立运行的,因此可以针对各项服务进行更新、部署和扩展,以满足对应用程序特定功能的需求。
程序中的微服务,就是将各个业务系统的共性再进行抽取,做成独立的服务。
2.SpringCloud Alibaba微服务解决方案
概述
Spring Cloud Alibaba 是Spring Cloud的一个子项目,致力于提供微服务开发的一站式解决方案。此项目包含开发分布式应用微服务的必需组件,方便开发者通过 Spring Cloud 编程模型轻松使用这些组件来开发分布式应用服务。依托 Spring Cloud Alibaba,您只需要添加一些注解和少量配置,就可以将 Spring Cloud 应用接入阿里微服务解决方案,通过阿里中间件来迅速搭建分布式应用系统。
核心组件分析
Spring Cloud Alibaba 默认提供了如下核心功能:
- 服务限流降级:
默认支持 WebServlet、OpenFeign、RestTemplate、Spring Cloud Gateway, RocketMQ 限流降级功能的接入,可以在运行时通过控制台实时修改限流降级规则,还支持查看限流降级 Metrics 监控。 - 服务注册与发现:
基于Spring Cloud 服务注册与发现标准,借助Nacos进行实现,默认还集成了 Ribbon 的支持。 - 分布式配置管理:
基于Nacos支持分布式系统中的外部化配置,配置更改时自动刷新。 - 消息驱动能力:
基于Spring Cloud Stream 为微服务应用构建消息驱动能力。 - 分布式事务:
使用 @GlobalTransactional 注解, 高效并且对业务零侵入地解决分布式事务问题。 - 分布式任务调度:
提供秒级、精准、高可靠、高可用的定时(基于 Cron 表达式)任务调度服务。同时提供分布式的任务执行模型,如网格任务。网格任务支持海量子任务均匀分配到所有 Worker上执行。
解决方案架构设计
二、构建SpringCloud聚合项目并进行环境初始化
聚合工程结构设计:
GitCGB2105IVProjects (工作区/空项目)
├── 01-sca //(微服务父工程)
├── sca-provider //服务提供方法
├── sca-consumer //服务消费方法
├── sca-gateway //网关服务
创建空项目(Empty Project)——>项目初始化配置(1.配置maven 2.配置JDK编译环境compiler
3.配置项目编译方式 File Encodinghs)——>创建聚合父工程(创建父工程module 01-sca)——>删除src
——>pom文件中添加核心依赖及版本管理
<dependencyManagement>
<dependencies>
<!--Spring boot 依赖(定义了微服务规范)-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.3.2.RELEASE</version>
<scope>import</scope>
<type>pom</type>
</dependency>
<!--Spring Cloud 依赖(定义了微服务规范)-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR9</version>
<scope>import</scope>
<type>pom</type>
</dependency>
<!--Spring Cloud Alibaba依赖(基于spring微服务规范做了具体落地实现)-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.6.RELEASE</version>
<scope>import</scope>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>
创建服务提供方模块sca-provider,继承01-sca
创建服务消费方模块sca-consumer,继承01-sca
创建API网关服务模块sca-gateway,继承01-sca
三、Nacos 注册中心简介
1.概述
Nacos 是一个应用于服务注册与发现、配置管理的平台。它孵化于阿里巴巴,成长于十年双十一的洪峰考验,沉淀了简单易用、稳定可靠、性能卓越的核心竞争力。
2.构建Nacos服务
准备工作:
1.确保电脑已配置JAVA_HOME环境变量
2.确保MariaDB版本10.5以上
3.Nacos下载,地址:https://github.com/alibaba/nacos/releases
4.选择对应的版本下载 (课上使用nacos-server-1.4.1.zip)
5.解压Nacos (最好解压到英文目录下)
初始化配置:
1.登陆mariaDB,执行老师发给同学们的sql脚本。
输入指令: source d:/nacos-mysql.sql (执行成功后,会创建一个nacos_config数据库)
2.打开/conf/application.properties里默认配置,按照要求修改
3.服务启动与访问
第一步:启动Nacos服务
linux系统命令: ./startup.sh -m standalone
windows系统: startup.cmd -m standalone
第二步:访问Nacos服务
打开浏览器,输入http://localhost:8848/nacos地址,出现登陆页面
四、服务注册与调用入门
1.业务描述
创建两个项目Module分别为服务提供者和服务消费者,两者都要注册到NacosServer中(这个server本质上就是一个web服务,端口默认为8848),然后服务提供者可以为服务消费者提供远端调用服务(例如支付服务为服务提供方,订单服务为服务消费方),如图所示:
2.生产者服务创建及注册
第一步:创建服务提供者工程(module名为sca-provider),继承parent工程(01-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>
第二步:创建并修改配置文件application.yml,实现服务注册,代码如下:
server:
port: 8081
spring:
application:
name: sca-provider
cloud:
nacos:
discovery:
server-addr: localhost:8848
第三步:创建启动类,并定义处理请求的控制层对象和方法,代码如下:
package com.cy;
@SpringBootApplication
public class ProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderApplication.class, args);
}
* 基于此对象处理客户端的请求*/
@RestController
public class ProviderController{
//@Value读取项目配置文件中配置的内容,8080为没有读到server.port的值时,给定的默认值
@Value("${server.port:8080}")
private String server;
//http://localhost:8081/provider/echo/tedu
@GetMapping("/provider/echo/{msg}")
public String doRestEcho1(@PathVariable String msg){
return server+" say hello "+msg;
}
}
}
第四步:启动启动类,然后刷新nacos服务,检测是否服务注册成功
3.消费者服务发现及调用
第一步:创建服务消费者(module名为sca-consumer),继承parent工程(01-sca),pom文件同上
第二步:修改配置文件application.yml
server:
port: 8090
spring:
application:
name: sca-consumer
cloud:
nacos:
discovery:
server-addr: localhost:8848 #从哪里去查找服务
第三步:创建启动类并实现服务消费
package com.cy;
@SpringBootApplication
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class,args);
}
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
@RestController
public class ConsumerController{
@Value("${spring.application.name}")
private String appName;
@Autowired
private RestTemplate restTemplate;
@GetMapping("/consumer/doRestEcho1")
public String doRestEcho01(){
String url = "http://localhost:8081/provider/echo/"+appName;
System.out.println("request url:"+url);
return restTemplate.getForObject(url, String.class);
}
}
}
第四步:启动消费者服务,输入网址,获取页面结果
五、服务负载均衡设计及实现
1.定义
一个服务实例可以处理请求是有限的,假如服务实例的并发访问比较大,我们会启动多个服务实例,让这些服务实例采用一定策略均衡(轮询,权重,随机,hash等)的处理并发请求,这就是Nacos中服务的负载均衡(Nacos客户端负载均衡)
2.实现
第一步:修改ConsumerController类,注入LoadBalancerClient对象,并添加doRestEcho2方法,然后进行服务访问.
@Autowired
private LoadBalancerClient loadBalancerClient;
@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服务启动配置,如图
第三步:修改并发运行选项
第四步:修改sca-provider的配置文件端口,分别以8081,8082端口方式进行启动(来回修改port)
server:
port: 8081
spring:
application:
name: sca-provider
cloud:
nacos:
server-addr: localhost:8848
第五步:启动成功后访问nacos服务列表,检测服务是否成功注册,如图
第六步:启动sca-consumer项目,打开浏览器,输入http://localhost:8090/consumer/doRestEcho02 反复访问
然后会发现sca-provider的两个服务器都可以处理sca-consumer的请求,如图:
3.总结
这里多个实例并发提供服务的方式为负载均衡,这里的负载均衡实现默认是因为Nacos集成了Ribbon来实现的,Ribbon配RestTemplate,可以非常容易的实现服务之间的访问。Ribbon是Spring Cloud核心组件之一,它提供的最重要的功能就是客户端的负载均衡,这个功能可以让我们轻松地将面向服务的REST模版请求自动转换成客户端负载均衡方式的服务调用。
六、@LoadBalanced
当使用RestTemplate进行远程服务调用时,假如需要负载均衡,可以在RestTemplate对象构建时,使用@LoadBalanced对构建RestTemplate的方法进行修饰
@Bean
@LoadBalanced
public RestTemplate loadBalancedRestTemplate(){
return new RestTemplate();
}
之后,在需要RestTemplate实现负载均衡调用的地方进行依赖注入:
@Autowired
private RestTemplate loadBalancedRestTemplate;
接下来,可以在对应的服务端调用方的方法内,基于RestTemplate借助服务名进行服务调用
@GetMapping("/consumer/doRestEcho3")
public String doRestEcho03(){
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
总结:@LoadBalanced注解是属于Spring,而不是Ribbon的,Spring在初始化容器的时候,如果检测到Bean被@LoadBalanced注解,Spring会为其设置LoadBalancerInterceptor的拦截器。
七、基于Feign的远程服务调用
1.概述
Feign 是一种声明式Web服务客户端,底层封装了对Rest技术的应用,通过Feign可以简化服务消费方对远程服务提供方法的调用实现。 (后期更名为OpenFeign)
2.Feign应用实践
第一步:在服务消费方,添加项目依赖(SpringCloud团队基于OpenFeign研发了starter)
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
第二步:在启动类上添加@EnableFeignClients注解
@EnableFeignClients
@SpringBootApplication
public class ConsumerApplication {…}
第三步:定义Http请求API,基于此API借助OpenFeign访问远端服务
@FeignClient(name="sca-provider")//sca-provider为服务提供者名称
interface RemoteProviderService{
@GetMapping("/provider/echo/{string}")//前提是远端需要有这个服务
public String echoMessage(@PathVariable("string") String string);
}
(注:其中,@FeignClient描述的接口底层会为其创建实现类。)
第四步:创建FeignConsumerController中并添加feign访问
@RestController
@RequestMapping("/consumer/ ")
public class FeignConsumerController {
@Autowired
private RemoteProviderService remoteProviderService;
/**基于feign方式的服务调用*/
@GetMapping("/echo/{msg}")
public String doFeignEcho(@PathVariable String msg){
//基于feign方式进行远端服务调用(前提是服务必须存在)
return remoteProviderService.echoMessage(msg);
}
}
第五步:启动消费者服务,在浏览器中直接通过feign客户端进行访问
3.Feign配置进阶实践
问题1:一个服务提供方通常提供很多资源服务,当服务消费方基于同一个服务提供方提供很多服务调用接口的时候,就会失败。
解决办法:此时我们需要为远程调用服务接口指定一个contextId,作为远程调用服务的唯一标识
@FeignClient(name="sca-provider",contextId="remoteProviderService")
interface RemoteProviderService{
@GetMapping("/provider/echo/{string}")//前提是远端需要有这个服务
public String echoMessage(@PathVariable("string") String string);
}
问题2:当我们在进行远程服务调用时,假如服务突然不可用了或调用过程超时怎么办?
解决办法:服务消费端会给出具体的解决方案
第一步:定义FallbackFactory接口的实现
package com.cy.service.factory;
/**
* 基于此对象处理RemoteProviderService接口调用时出现的服务中断,超时等问题
*/
@Component
public class ProviderFallbackFactory
implements FallbackFactory<RemoteProviderService> {
/**
* 此方法会在RemoteProviderService接口服务调用时,出现了异常后执行.
* @param throwable 用于接收异常
*/
@Override
public RemoteProviderService create(Throwable throwable) {
return (msg)->{
return "服务维护中,稍等片刻再访问";
};
}
}
第二步:在Feign访问接口中应用FallbackFactory对象
@FeignClient(name = "sca-provider", contextId = "remoteProviderService",
fallbackFactory = ProviderFallbackFactory.class)//sca-provider为nacos中的服务名
public interface RemoteProviderService {
@GetMapping("/provider/echo/{msg}")
public String echoMsg(@PathVariable String msg);
}
第三步:在配置文件application.yml中添加如下配置,启动feign方式调用时的服务中断处理机制
feign:
hystrix:
enabled: true #默认值为false
4.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 返回客户端,将信息流解析成为接口返回数据。
八、配置中心
1.概述:
配置中心最基础的功能就是存储一个键值对,进阶的功能就是当某个配置项发生变更时,不停机就可以动态刷新服务内部的配置项,例如,在生产环境上我们可能把我们的日志级别调整为 error 级别,但是,在系统出问题我们希望对它 debug 的时候,我们需要动态的调整系统的行为的能力,把日志级别调整为 debug 级别。还有,当你设计一个电商系统时,设计大促预案一定会考虑,同时涌进来超过一亿人并发访问的时候,假如系统是扛不住的,你会怎么办,在这个过程中我们一般会采用限流,降级。系统的限流和降级本质上来讲就是从日常的运行态切换到大促态的一个行为的动态调整,这个本身天然就是配置起到作用的一个相应的场景。
2.Nacos配置入门案例
基于nacos配置中心,实现日志级别的动态配置,然后基于日志级别动态调整服务中日志信息的输出.
第一步:
@GetMapping("/provider/echo/{msg}")
public String doRestEcho1(
@PathVariable("msg") String msg) throws InterruptedException {
//需求:通过配置中心中动态日志级别的配置,控制日志信息的输出
log.info("doRestEcho1 start {}",//{} 表示占位符
System.currentTimeMillis());
return server+" say hello "+msg;
}
第二步:添加依赖
在已有的sca-provider项目中添加如配置依赖,例如:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
第三步:修改配置文件
将项目中的application.yml的名字修改为bootstrap.yml配置文件(启动优先级最高)
spring:
application:
name: sca-provider
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
config:
server-addr: 127.0.0.1:8848
file-extension: yml
第四步:新建Nacos配置
打开nacos配置中心,新建配置如下:
(注:其中Data ID的值要与bootstrap.yml中定义的spring.application.name的值相同)
第五步:配置中心动态发布测试
启动sca-provider服务,然后打开浏览器,最/provider/echo/{msg}这个路径代表的资源进行访问,检测idea控制台日志输出.
访问成功后,打开idea控制台,检测日志输出.然后再打开nacos控制台动态更新日志级别,再访问资源并检测后台日志输出
九、Nacos配置管理模型
1.概述
Nacos配置管理模型由三部分构成,如图
- Namespace:命名空间,对不同的环境进⾏隔离,⽐如隔离开发环境和⽣产环境。
- Group:分组,将若⼲个服务或者若⼲个配置集归为⼀组。
- Service/DataId:某⼀个服务或配置集,一般对应一个配置文件。
2.命名空间设计
Nacos中的命名空间一般用于配置隔离,这种命名空间的定义一般会按照环境(开发,生产等环境)进行设计和实现.我们默认创建的配置都存储到了public命名空间,如图所示:
3.案例实现
创建新的开发环境并定义其配置,然后从开发环境的配置中读取配置信息,该如何实现呢?
第一步:创建新命名空间,如图
第二步:命名空间成功创建以后,会在如下列表进行呈现
第三步:在指定命名空间下添加配置,也可以直接取配置列表中克隆,如下:
第四步:克隆成功以后,我们会发现中指定的命名空间中有了我们克隆的配置,如图:
第五步:此时我们修改dev命名空间中Data Id的sca-provider配置,如图所示:
第六步:修改项目module中的配置文件bootstrap.yml,添加如下配置:
spring:
cloud:
nacos:
config:
namespace: 6058fd3f-0d4d-44f2-85d6-5fc7d2348046
……
其中namespace后面的字符串为命名空间的id,可直接从命名空间列表中进行拷贝.
第七步:重启服务,继续刷新http://localhost:8081/config/doGetLogLevel地址。检测输出,看看输出的内容是什么,是否为dev命名空间下配置的内容,如图所示:
4.分组设计及实现
第一步:当我们在指定命名空间下,按环境或服务做好了配置以后,有时还需要基于服务做分组配置,例如,一个服务在不同时间节点(节假日,活动等)切换不同的配置,可以在新建配置时指定分组名称,如图所示:
第二步:配置发布以后,修改boostrap.yml配置类,在其内部指定我们刚刚创建的分组,代码如下:
server:
port: 8081
spring:
application:
name: sca-provider
cloud:
nacos:
config:
server-addr: 127.0.0.1:8848
group: DEV_GROUP_51 # Group, default is DEFAULT_GROUP
file-extension: yml # Configure the data format of the content, default to properties
namespace: 5c27fe4a-1141-4836-a14e-cbac77fb2130
第三步:在指定的Controller类中添加属性和方法用于获取和输出DEV_GROUP_51配置中设置的线程数,代码如下:
package com.cy.controller;
@RefreshScope
@RestController
public class ProviderThreadController {
@Value("${server.tomcat.threads.max:200}")
private Integer maxThread;
@RequestMapping("/provider/doGetMaxThread")
public String doGetMaxThread(){
return "server.threads.max is "+maxThread;
}
}
第四步:重启服务,进行测试
十、共享配置设计及读取
当同一个namespace的多个配置文件中都有相同配置时,可以对这些配置进行提取,然后存储到nacos配置中心的一个或多个指定配置文件,哪个微服务需要,就在服务的配置中设置读取即可。例如:
第一步:在nacos中创建一个共享配置文件,例如:
第二步: 在指定的微服务配置文件(bootstrap.yml)中设置对共享配置文件的读取,例如:
spring:
application:
name: sca-provider
cloud:
nacos:
config:
server-addr: localhost:8848
# 命名空间
namespace: 83ed55a5-1dd9-4b84-a5fe-a734e4a6ec6d
# 分组名
# group: DEFAULT_GROUP
# 配置中心文件扩展名
file-extension: yml
# 共享配置
shared-configs[0]:
data-id: app-public-dev.yml
refresh: true #默认false,共享配置更新,引用此配置的地方是否要更新
第三步:在指定的Controller类中读取和应用共享配置即可,例如:
package com.cy.controller;
@RefreshScope
@RestController
public class ProviderPageController {
@Value("${page.pageSize:10}")
private Integer pageSize;
@GetMapping("/provider/doGetPageSize")
public String doGetPageSize(){
//return String.format()
return "page size is "+pageSize;
}
}
第四步:启动服务进行访问测试