为什么使用SpringCloudAlibaba?

SpringCloud部分组件停止维护和更新,给开发带来不便

SpringCloud部分环境搭建复杂,没有完善的可视化界面,我们需要大量的二次开发和定制

SpringCloud配置复杂,难以上手,部分配置差别难以区分和合理应用

SpringCloudAlibaba的优势?

阿里使用过的组件经历了考验,性能强悍,设计合理,给开发带来了极大的便利

搭建简单,学习成本低,可视化界面好

结合SpringCloudAlibaba的最终技术搭配方案

SpringCloudAlibaba-Nacos:注册中心(服务发现/注册)

SpringCloudAlibaba-Nacos:配置中心(动态配置管理)

SpringCloud-Ribbon:负载均衡

SpringCloud-Feign:声明式HTTP客户端(调用远程服务)

SpringCloudAlibaba-Sentinel:服务容错(限流、降级、熔断)

SpringCloud-Gateway:API网关(webflux编程模式)

SpringCloud-Sleuth:调用链监控

SpringCloudAlibaba-Seata:原Fescar,即分布式事务解决方案

SpringCloudAlibaba源码地址

https://github.com/alibaba/spring-cloud-alibaba

如何整合Nacos?

学习最快的方式就是看官方文档啦😊

首先我们需要统一下SpringBoot&SpringCloud&SpringCloudAlibaba版本号,防止出现一些不合常规的问题,以下配置是建立在SpringBoot 2.3.10.RELEASE版本基础上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<properties>
<spring-cloud.version>Hoxton.SR10</spring-cloud.version>
<spring-cloud-alibaba.version>2.2.5.RELEASE</spring-cloud-alibaba.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<scope>import</scope>
<type>pom</type>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<scope>import</scope>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>

Nacos官方文档地址:

https://github.com/alibaba/spring-cloud-alibaba/blob/master/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/readme.md

1、将spring-cloud-starter-alibaba-nacos-discovery添加到pom.xml中

1
2
3
4
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

需要注意的是,如果你使用的是SpringBoot2.4.x版本,因为负载均衡不再是使用ribbon而是使用spring-cloud-loadbalancer,所以我们需要排除ribbon并引入loadbalancer,如果使用的跟我一样是SpringBoot2.3.x版本,则无需这一步

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2021.1</version>
<exclusions>
<exclusion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
<version>3.0.2</version>
</dependency>

2、在application.yml中配置Nacos的服务地址,也就是Nacos注册中心的地址,这里我是在windows中直接通过客户端启动,所以地址为127.0.0.1:8848

1
2
3
4
5
spring:
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848

这里我们需要先获取到一个Nacos Server注册中心的服务地址

  • github中下载Nacos Server,建议选择Latest release稳定版
  • 下载后解压,双击Nacos\bin\startup.bat,就开启了一个Nacos Server,默认端口是在我们本机的8848端口

这里需要注意:

如果你使用的是1.4.1或更高版本,则默认模式为集群模式

我们需要打开nacos\bin\startup.bat文件,将如下内容

1
set MODE="cluster"

修改为单机模式

1
set MODE="standalone"

3、在启动类上使用@EnableDiscoveryClient注解,开启服务注册与发现功能,开启后才能将服务注册到Nacos中,其他服务才能通过接口调用到你提供的服务

1
2
3
4
5
6
7
8
@EnableDiscoveryClient
@SpringBootApplication
public class CouponApplication {

public static void main(String[] args) {
SpringApplication.run(CouponApplication.class, args);
}
}

4、在application.yml中添加spring.application.name名字,该名字会作为服务名在nacos中显示,例如:

1
2
3
spring:
application:
name: coupon-provider

运行SpringBoot启动类,我们可以在http://localhost:8848/nacos查看服务是否注册成功,默认登录的账号密码均为`nacos`,登录成功后,在左侧`服务管理-服务列表`中可以查看到注册成功的服务

OpenFeign远程调用服务

如果我们想要远程调用服务,我们需要如下几步:

1、在pom.xml中添加OpenFeign的依赖,这里我统一放到common公共模块中统一管理

1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

2、编写一个接口,告诉SpringCloud这个接口需要调用远程服务,并声明接口的每一个方法都是调用哪个远程服务的请求

建议在controller包同级下创建一个feign目录,在feign包路径下创建Interface接口

例如:我想要在会员服务中调用上优惠券服务,那么我们就在会员服务中刚刚创建的fegin目录中,创建一个OrderFeginService.java接口(接口名字随意起 没有要求)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 给CouponController中添加一个测试接口 用于给会员服务调用获取优惠券信息
@RequestMapping("/memberCoupons")
public R memberCoupons() {
CouponEntity entity = new CouponEntity();
entity.setCouponName("满100减50");
return R.ok().put("data", entity);
}
--------------------------------------------------------------------------------
// 表明该接口是一个远程客户端 调用哪个服务就写哪个服务的服务名
@FeignClient("coupon-provider")
public interface CouponFeginService {

@RequestMapping("coupon/coupon/memberCoupons")
R memberCoupons();
}

需要注意的是:这里通过@FeignClient标注你调用的是哪个服务,其中的coupon-provider是对应服务在yml中配置的spring.application.name的名称

同时需要注意,接口需要与服务中的接口保持一致,且调用路径需要写全路径,即如果你在Controller中标注了统一的路径,也需要一起加到地址中,否则会提示无法找到

3、在启动类上开启远程调用功能注解支持@EnableFeignClients,默认扫描的是当前所在工程目录下标注了@FeginClient的接口,也可以basePackages标注feign所在的包

1
2
3
4
5
6
7
8
9
@EnableFeignClients(basePackages = "com.atguigu.gulimall.member.feign")
@EnableDiscoveryClient
@SpringBootApplication
public class GulimallMemberApplication {

public static void main(String[] args) {
SpringApplication.run(GulimallMemberApplication.class, args);
}
}

4、在MemberController中注入刚刚编写的接口CouponFeginService,并编写测试方法调用CouponController中的测试方法

1
2
3
4
5
6
7
@Autowired
private CouponFeginService feginService;

@RequestMapping("/test")
public R test() {
return feginService.memberCoupons();
}

5、依次启动优惠券服务会员服务,浏览器调用会员服务接口地址,请换成你们自己的端口

http://localhost:8000/member/member/test

浏览器响应结果如下:

1
{"msg":"success","code":0,"data":{"couponName":"满100减50"}}

到此为止,我们就成功从会员服务调用到了订单服务

为什么要接入Nacos Config

如果我们的代码中有一部分是从配置文件中读取的,每次修改后都需要重新打包项目在进行调用接口才能看到效果,如果我们部署了很多台机器,每次这样重新修改打包后在发布调用是很繁琐的

如何接入Nacos Config作为配置中心

1、在pom.xml中引入Nacos Config Starter

1
2
3
4
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

2、在resources目录下,创建一个bootstrap.properties配置文件,配置如下信息,需要注意的是,如果已经在application.yml中配置了spring.application.name,那么可以不配或要保持一致

1
2
spring.application.name=nacos-config-example
spring.cloud.nacos.config.server-addr=127.0.0.1:8848

3、在调用了配置文件的Controller类上,使用@RefreshScope注解来动态刷新配置文件,配合@Value来获取配置文件中的值,配置中心有的,优先使用配置中心

1
2
3
4
5
6
7
8
9
10
11
12
13
@RefreshScope
@RestController
@RequestMapping("coupon/coupon")
public class CouponController {
@Value("${coupon.name}")
private String couponName;

@RequestMapping("/memberCoupons")
public R memberCoupons() {
CouponEntity entity = new CouponEntity();
entity.setCouponName(couponName);
return R.ok().put("data", entity);
}

4、之后打开Nacos Server,依次点开左侧配置管理节点下的配置列表,点击右上方➕添加一条配置

Data ID:为spring.application.name加上properties,即xxxx.properties

如按照上方配置,则为coupon-provider.properties

Group:默认即可DEFAULT_GROUP,也可以自行输入其他名字分组,例如:1111、618等节日

配置格式:YAML配置后没获取到,可能需要手动指定YAML格式,这里我选择properties

配置内容:根据选择的配置格式进行编写,例如:coupon.name=满100-80

5、编写完成后,点击发布即可,以后想要修改值的时候,只需要在nacos配置中心中修改配置内容即可,而无需重新启动项目即可看到修改后的值

Nacos Config相关细节

1、命名空间:配置隔离

默认新增的所有配置都在public命名空间中

如果将来存在多个配置环境,例如:开发、测试、生产环境

那么我们就需要新建多个命名空间,推荐每一个微服务都创建一个命名空间

  • 首先我们在Nacos左侧命名空间菜单中新增一个dev环境

  • 然后在配置列表上方能看到新增的dev分组,之后选择该分组进行创建配置文件,规则同public

  • 最后在bootstrap.properties中配置namespace,值为该命名空间的id

1
spring.cloud.nacos.config.namespace=e6ad418d-65b6-4a46-9d73-1ccde42240af

2、配置集

一组相关或不相关配置项的集合就叫配置集

3、配置集ID

类似于配置文件名,在Nacos中体现的是Data ID,即应用名+properties

4、配置分组

默认所有的配置集都属于:DEFAULT_GROUP分组

如果需要修改分组,我们可以在bootstrap.properties中配置如下信息,值为组名

1
spring.cloud.nacos.config.group=1111

Nacos配置中心加载多配置集

以前我们将所有的配置文件都放到了一个配置文件中,时间久了这个配置文件就会很大且不够清晰,所以我们可能将跟数据源相关的放到datasource.yml中,跟mybatis相关的放到mybatis.yml中,将一个大的配置文件拆分成多个小的配置文件

那么我们如何才能实现上述效果呢?

我们只需要在Nacos中配置多个对应的配置文件后,在bootstrap.properties中配置如下信息

1
2
3
4
5
# 配置ext-config为List集合 所以依此类推[1]、[2]……
spring.cloud.nacos.config.ext-config[0].data-id=datasource.yml
spring.cloud.nacos.config.ext-config[0].group=dev
# 配置是否自动刷新 默认为false 即在Nacos中修改后不会立即生效
spring.cloud.nacos.config.ext-config[0].refresh=true

Spring Cloud GateWay网关

网关作为流量的入口,常用功能包括路由转发、权限校验、限流控制等

SpringCloudGateWay提供一种简单有效的方式对API进行路由,并为他们提供切面

我们无需去关心后台的情况,哪些服务是否能正常提供服务,网关可以帮我们动态的路由到每一个服务中;同时网关可以为我们做鉴权、限流、日志等服务,我们就无需在每一个微服务中都写一部分相同的代码来进行处理了

网关由ID目标URI断言规则过滤器来决定一个请求是否能路由到某个地方,只有最后断言的结果为true时,才能将请求成功的路由到指定位置

断言为Java8中提供的断言型函数式接口,可以根据任何请求头或者请求参数中的信息,来判断是否满足路由规则,判断成功那么就能路由到指定位置

过滤器与之前Spring中的过滤器功能基本一致,在请求或响应之前或之后都可以进行修改

综上我们可以明白一个完整的流程如下:

客户端想要发送请求到目标服务时,中间会经过API网关,客户端先将请求发给API网关,会先来到Gateway Handler Mapping查看映射信息能否被路由或被处理,如果能被处理则会将请求转发给每一个Handler处理器进行处理,在处理器处理前会经过大量的Filter过滤器进行筛选,之后才能成功请求到目标服务,当目标服务处理完成后,在按照相同流程返回给客户端

我们可以在官方手册中找到大量的predicate或者filter规则,可以直接根据示例进行配置

https://docs.spring.io/spring-cloud-gateway/docs/2.2.7.RELEASE/reference/html/#gateway-request-predicates-factories

https://docs.spring.io/spring-cloud-gateway/docs/2.2.7.RELEASE/reference/html/#gatewayfilter-factories

整合Spring Cloud GateWay网关

1、新建SpringBoot项目或Maven项目,导入GateWay相关starter依赖

1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

2、在启动类上使用注解@EnableDiscoveryClient开启服务注册与发现

1
2
3
4
5
6
7
8
@EnableDiscoveryClient
@SpringBootApplication
public class GulimallGatewayApplication {

public static void main(String[] args) {
SpringApplication.run(GulimallGatewayApplication.class, args);
}
}

3、在application.yml配置nacos的服务注册地址、应用名及服务端口

1
2
3
4
5
6
7
8
9
server:
port: 88
spring:
application:
name: gateway-provider
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848

4、在bootstrap.properties中配置nacos的配置中心地址

1
spring.cloud.nacos.config.server-addr=localhost:8848

5、因为我们还依赖了公共模块common,其中有对MyBatis的引用,启动的时候会提示我们去yml中添加数据源相关的配置,所以我们在启动类中排除对DataSource的配置操作

1
2
3
4
5
6
7
8
9
@EnableDiscoveryClient
// 排除对DataSource的相关自动配置操作
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class GulimallGatewayApplication {

public static void main(String[] args) {
SpringApplication.run(GulimallGatewayApplication.class, args);
}
}

6、现在我们的网关已经在88端口正常启动了

Spring Cloud GateWay网关配置示例

现在我们有这样一个需求,当我发送请求的时候,会跟一个url地址,如果是baidu则跳转到www.baidu.com,如果是qq则跳转到www.qq.com

那么我们需要在yml文件中增加如下配置

详情可参考官方手册query-route-predicate-factory部分讲解

https://docs.spring.io/spring-cloud-gateway/docs/2.2.7.RELEASE/reference/html/#the-query-route-predicate-factory

1
2
3
4
5
6
7
8
9
10
11
12
spring:
cloud:
gateway:
routes:
- id: baidu_route
uri: https://www.baidu.com
predicates:
- Query=url,baidu
- id: qq_route
uri: https://www.qq.com
predicates:
- Query=url,qq

Query后第一个值表示查询参数名,第二个值可以写字符串,且支持正则表达式匹配规则,表示对应参数的值,如果匹配到参数未url且值为指定值的时候,就会跳转到配置的uri地址

所以如上配置后,我们在浏览器发送不同的请求就会来到不同的页面

例如: