1.什么是Spring cloud Alibaba
1.1 简介
Spring Cloud Alibaba 致力于提供微服务开发的一站式解决方案。此项目包含开发分布式应用微服务的必需组件,方便开发者通过 Spring Cloud 编程模型轻松使用这些组件来开发分布式应用服务。
依托 Spring Cloud Alibaba,您只需要添加一些注解和少量配置,就可以将 Spring Cloud 应用接入阿里微服务解决方案,通过阿里中间件来迅速搭建分布式应用系统。
1.2 主要功能
- 服务限流降级:默认支持 WebServlet、WebFlux、OpenFeign、RestTemplate、Spring Cloud Gateway、Zuul、Dubbo 和 RocketMQ 限流降级功能的接入,可以在运行时通过控制台实时修改限流降级规则,还支持查看限流降级 Metrics 监控。
- 服务注册与发现:适配 Spring Cloud 服务注册与发现标准,默认集成了 Ribbon 的支持。
- 分布式配置管理:支持分布式系统中的外部化配置,配置更改时自动刷新。
- 消息驱动能力:基于 Spring Cloud Stream 为微服务应用构建消息驱动能力。
- 分布式事务:使用 @GlobalTransactional 注解, 高效并且对业务零侵入地解决分布式事务问题。
- 阿里云对象存储:阿里云提供的海量、安全、低成本、高可靠的云存储服务。支持在任何应用、任何时间、任何地点存储和访问任意类型的数据。
- 分布式任务调度:提供秒级、精准、高可靠、高可用的定时(基于 Cron 表达式)任务调度服务。同时提供分布式的任务执行模型,如网格任务。网格任务支持海量子任务均匀分配到所有 Worker(schedulerx-client)上执行。
- 阿里云短信服务:覆盖全球的短信服务,友好、高效、智能的互联化通讯能力,帮助企业迅速搭建客户触达通道。
1.3 组件
- Sentinel:把流量作为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
- Nacos:一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。
- RocketMQ:一款开源的分布式消息系统,基于高可用分布式集群技术,提供低延时的、高可靠的消息发布与订阅服务。
- Dubbo:Apache Dubbo™ 是一款高性能 Java RPC 框架。
- Seata:阿里巴巴开源产品,一个易于使用的高性能微服务分布式事务解决方案。
- Alibaba Cloud OSS: 阿里云对象存储服务(Object Storage Service,简称 OSS),是阿里云提供的海量、安全、低成本、高可靠的云存储服务。您可以在任何应用、任何时间、任何地点存储和访问任意类型的数据。
- Alibaba Cloud SchedulerX: 阿里中间件团队开发的一款分布式任务调度产品,提供秒级、精准、高可靠、高可用的定时(基于 Cron 表达式)任务调度服务。
- Alibaba Cloud SMS: 覆盖全球的短信服务,友好、高效、智能的互联化通讯能力,帮助企业迅速搭建客户触达通道。
1.4 为什么选择SpringCloud alibaba
首先, SpringCloud中的技术组件是集众家之长,如注册中心 Eureka,Zuul等都是依赖于Netflix的,这也导致它受制于第三方厂商。如Zuul宣布停止维护,Spring机构便不得不寻找替代品或自研;Eureka2.x 闭源不允许使用;
其次,Springcloud作为国外产品引入到国内后出现了水土不服,如SpringCloud Config默认将文件存在Github上,且没有维护界面,国内软件公司很少会同意这么做。比如我们部门就是使用了Apollo配置中心替代了原生的SpringCloud Config。
Spring Cloud Alibaba是国产的微服务开发一站式解决方案,与原有 Spring Cloud 兼容的同时对微服务生态进行扩展,通过添加少量的配置注解,便可实现更符合国情的微服务架构,当前Spring Cloud Alibaba已经是直接隶属于 Spring Cloud 的子项目。
Spring Cloud Alibaba 对服务注册、配置中心与负载均衡功能都整合进 Nacos,有图形化界面,简化了微服务架构的复杂度,出问题的概率也会降低。原有的服务保护组件也调整为 Sentinel,相较Hystrix功能更强大,使用也更加友好。同时还支持了对Dubbo的调用,而且还有Seata用于支持分布式事务。
2.搭建注册中心nacos
在传统的eureka的基础上改为了nacos作为 服务发现和注册的中心,我们大致看一下两者有什么不同
2.1 nacos的服务管理
服务如何注册
服务注册最重要的就是将服务注册到哪里,在注册中心服务端,肯定有一个用来管理服务的容器,他保存着所有服务的实例。
服务如何发现
服务注册到注册中心后,服务的消费者就可以进行服务发现的流程了,消费者可以直接向注册中心发送获取某个服务实例的请求,这种情况下注册中心将返回所有可用的服务实例给消费者,但是一般不推荐这种情况。
另一种方法就是服务的消费者向注册中心订阅某个服务,并提交一个监听器,当注册中心中服务发生变更时,监听器会收到通知,这时消费者更新本地的服务实例列表,以保证所有的服务均是可用的。
Nacos 服务注册与订阅的完整流程
Nacos 客户端进行服务注册有两个部分组成:
- 一个是将服务信息注册到服务端
- 另一个是向服务端发送心跳包
这两个操作都是通过 NamingProxy 和服务端进行数据交互的。
Nacos 客户端进行服务订阅时也有两部分组成:
- 一个是不断从服务端查询可用服务实例的定时任务
- 另一个是不断从已变服务队列中取出服务并通知 EventListener 持有者的定时任务。
2.2 搭建nacos服务
本文采用docker搭建 ,参考此处
3.客户端服务搭建
新建一个springBoot项目,添加以下依赖
3.1 添加依赖
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<java.version>11</java.version>
<naco.version>2021.1</naco.version>
<spring-cloud-dependencies.version>2021.0.0</spring-cloud-dependencies.version>
</properties>
<dependencies>
<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>
<version>${naco.version}</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-dependencies -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud-dependencies.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
3.2 添加注解
在主启动类上添加注解
@EnableDiscoveryClient
3.3 添加配置
# 远程地址
remote.nacos.ip=192.168.1.2
remote.nacos.port=8848
# 项目内部配置
server.port=3062
spring.application.name=supplier-finance
spring.cloud.nacos.discovery.server-addr=${remote.nacos.ip}:${remote.nacos.port}
spring.cloud.nacos.discovery.username=xxx
spring.cloud.nacos.discovery.password=xxxx
启动之后,在nacos注册中心查看自己是否配置成功
4.在客户端集成声明式服务的调用-fegin
有了客户端配置之后,我们还需要不同服务之间的相互调用,接下来我们利用feign实现。
feign可以以Java接口注解的方式调用Http请求,它使java调用Http请求变的简单Feign集成了Ribbon,实现了客户端的负载均衡。
4.1 feign的工作原理
- 1、首先通过@EnableFeignCleints注解开启FeignCleint
- 2、根据Feign的规则实现接口,并加@FeignCleint注解
- 3、程序启动后,会进行包扫描,扫描所有的@FeignCleint的注解的类,并将这些信息注入到ioc容器中。
- 4、当接口的方法被调用,通过jdk的代理,来生成具体的RequesTemplate
- 5、RequesTemplate再生成Request
- 6、Request交给Client去处理,其中Client默认是HttpUrlConnection(也可以是HttpClient或Okhttp,需要配置)
- 7、最后Client被封装到LoadBalanceClient类,这个类结合类Ribbon做到了负载均衡。
4.2 添加 依赖
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-openfeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-loadbalancer</artifactId>
</dependency>
4.3 声明服务调用的接口
package com.common.indentitycenter.feginClient;
import com.common.api.commonApi.BaseResult;
import com.common.indentitycenter.fallBack.IdentityServiceFallBack;
import io.swagger.annotations.ApiOperation;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
@FeignClient(value = "identity-center", fallback = IdentityServiceFallBack.class)
public interface IdentityService {
@RequestMapping(value = "identityUserInfo", method = RequestMethod.POST)
public BaseResult identityUserInfo(@RequestParam(value = "token") String token);
}
注意:
- @FeignClient后面的value值来自于调用服务的application.name
- fallback 服务降级
@Component @Slf4j public class IdentityServiceFallBack implements IdentityService { private final BaseResult baseResult = new BaseResult(); @Override public BaseResult identityUserInfo(String token) { baseResult.makError("校验用户信息服务关闭,请稍后再试"); return baseResult; } }
- identityUserInfo这个接口就是我们调用的外部接口
原接口内容如下:@Slf4j @RestController public class IdentytyController { @Autowired IdentityService identityService; @RequestMapping(value = "identityUserInfo", method = RequestMethod.POST) @PassToken public BaseResult identityUserInfo(@RequestParam(value = "token") String token, HttpServletResponse response, HttpServletRequest request) { return identityService.identityUserInfo(token); } }
更多具体详细的操作可以参考此处
4.4 在主配置类添加注解
@EnableFeignClients
5.搭建路由gateway
5.1 我们为什么要使用getway?
传统的springCloud组件中,一般都会使用zuul作为我们开发的路由组件,接下来我们就简单对比一下这两个组件的差别
getway | zuul | |
---|---|---|
基本介绍 | Spring Cloud Gateway是Spring官方基于Spring 5.0,Spring Boot 2.0和Project Reactor等技术开发的网关。Spring Cloud Gateway旨在为微服务架构提供一种简单而有效的统一的API路由管理方式。Spring Cloud Gateway作为Spring Cloud生态系中的网关,目标是替代Netflix ZUUL,其不仅提供统一的路由方式,并且基于Filter链的方式提供了网关基本的功能,例如:安全,监控/埋点,和限流等。 | Zuul 是基于 Servlet 框架构建,采用的是阻塞和多线程方式,即一个线程处理一次连接请求,这种方式在内部延迟严重、设备故障较多情况下会引起存活的连接增多和线程增加的情况发生。 |
性能 | WebFlux 模块的名称是 spring-webflux,名称中的 Flux 来源于 Reactor 中的类 Flux。Spring webflux 有一个全新的非堵塞的函数式 Reactive Web 框架,可以用来构建异步的、非堵塞的、事件驱动的服务,在伸缩性方面表现非常好。使用非阻塞API。 Websockets得到支持,并且由于它与Spring紧密集成,所以将会是一个更好的开发体验。 | 本文的Zuul,指的是Zuul 1.x,是一个基于阻塞io的API Gateway。Zuul已经发布了Zuul 2.x,基于Netty,也是非阻塞的,支持长连接,但Spring Cloud暂时还没有整合计划。 |
源码维护组织 | spring-cloud-Gateway是spring旗下spring-cloud的一个子项目。还有一种说法是因为zuul2连续跳票和zuul1的性能表现不是很理想,所以催生了spring孵化Gateway项目。 | zuul则是netflix公司的项目,只是spring将zuul集成在spring-cloud中使用而已。关键目前spring不打算集成zuul2.x。 |
新建一个SpringBoot项目,注意不要有web模块
5.2 添加依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.supplier</groupId>
<artifactId>getway</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>getway</name>
<description>getway</description>
<properties>
<java.version>11</java.version>
<naco.version>2021.1</naco.version>
<spring-cloud-dependencies.version>2021.0.0</spring-cloud-dependencies.version>
</properties>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>${naco.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-loadbalancer</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-dependencies -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud-dependencies.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
5.3 配置
# 远程连接配置
remote:
nacos:
ip: 192.168.1.2
port: 8848
# 服务内部配置
server:
port: 3069
servlet:
encoding: # 解决返回页面中文乱码问题
enabled: true
force: true
charset: UTF-8
spring:
application:
name: supplier-getway
devtools:
restart:
enabled: true
cloud:
nacos:
discovery:
server-addr: ${remote.nacos.ip}:${remote.nacos.port}
username: nacos
password: nacos
gateway:
discovery:
locator:
enabled: true
lower-case-service-id: true
routes:
# 客户模块
- id: supplier-custom # 路由的唯一标识通常是微服务名称
uri: lb://supplier-custom # lib表示从注册中心获取服务
predicates:
- Path=/supplier/custom/** #制定具体的路径匹配规则
filters:
# 限流配置
- StripPrefix=2
# 经销商模块
- id: suppler-provider # 路由的唯一标识通常是微服务名称
uri: lb://suppler-provider # lib表示从注册中心获取服务
predicates:
- Path=/provider/** #制定具体的路径匹配规则
filters:
# 限流配置
- StripPrefix=1
servlet: # 配置文件传输
multipart:
enabled: true
file-size-threshold: 0
max-file-size: 1024MB #单个数据大小
max-request-size: 10240MB #总数据大小
5.4 配置跨域
新增一个config
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.cors.reactive.CorsUtils;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
/**
* 代码配置方式配置跨域
*/
@Configuration
public class GlobalCorsConfig {
@Bean
public WebFilter corsFilter2() {
return (ServerWebExchange ctx, WebFilterChain chain) -> {
ServerHttpRequest request = ctx.getRequest();
if (CorsUtils.isCorsRequest(request)) {
HttpHeaders requestHeaders = request.getHeaders();
ServerHttpResponse response = ctx.getResponse();
HttpMethod requestMethod = requestHeaders.getAccessControlRequestMethod();
HttpHeaders headers = response.getHeaders();
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, requestHeaders.getOrigin());
headers.addAll(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS,
requestHeaders.getAccessControlRequestHeaders());
if (requestMethod != null) {
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, requestMethod.name());
}
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
headers.add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "*");
if (request.getMethod() == HttpMethod.OPTIONS) {
response.setStatusCode(HttpStatus.OK);
return Mono.empty();
}
}
return chain.filter(ctx);
};
}
}
5.5 在主配置类添加注解
@EnableDiscoveryClient