SpringCloudAlibaba学习笔记
- 简介
官方网站:Spring Cloud Alibaba
官方文档:Document
版本说明:版本说明
github:Spring Cloud Alibaba github
- 学习环境
环境:
jdk:1.8
maven:3.6.3
spring-boot:2.5.2
spring-cloud:2020.0.3
spring-cloud-alibaba:2021.1
1.搭建父工程及相关服务
搭建父工程
-
新建maven项目
-
配置父工程
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.hikw</groupId>
<artifactId>Spring-Cloud-Alibaba-Parent</artifactId>
<version>1.0-SNAPSHOT</version>
<modules>
<module>provider</module>
<module>consumer</module>
</modules>
<name>spring-cloud-alibaba</name>
<description>This is the parent project of spring cloudalibaba</description>
<!--打包格式配置-->
<packaging>pom</packaging>
<!--版本控制-->
<properties>
<spring-boot.version>2.5.2</spring-boot.version>
<spring-cloud.version>2020.0.3</spring-cloud.version>
<spring-cloud-alibaba.version>2021.1</spring-cloud-alibaba.version>
</properties>
<!--通用依赖-->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<!--全局依赖管理-->
<dependencyManagement>
<dependencies>
<!--spring-boot依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--spring-cloud依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--spring-cloud-alibaba依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.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>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
<!--配置阿里云镜像-->
<repositories>
<repository>
<id>spring</id>
<url>https://maven.aliyun.com/repository/public</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
</project>
搭建相关服务
- provider服务
- 配置
pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>Spring-Cloud-Alibaba-Parent</artifactId> <groupId>com.hikw</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>provider</artifactId> <version>1.0-SNAPSHOT</version> <groupId>com.hikw</groupId> <description>This is a service provider</description> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> </project>
- 配置
application.yml
# 配置端口号 server: port: 8888 spring: # 配置服务名 application: name: providers
- 配置
ProviderController
@RestController @Slf4j public class ProviderController { @Value("${server.port}") private String port; //获取服务端口 @GetMapping("/provider/getPort") public String StringgetPort() { log.info("当前端口号:{}", port); return port; } }
- 添加入口类
@SpringBootApplication public class ProviderApplication { public static void main(String[] args) { SpringApplication.run(ProviderApplication.class, args); } }
- consumer服务
- 配置
pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>Spring-Cloud-Alibaba-Parent</artifactId> <groupId>com.hikw</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>consumer</artifactId> <version>1.0-SNAPSHOT</version> <groupId>com.hikw</groupId> <description>This is a service consumer</description> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> </project>
- 配置
application.yml
# 配置端口号 server: port: 9999 spring: # 配置服务名 application: name: consumers
- 添加入口类
@SpringBootApplication public class ConsumerApplication { public static void main(String[] args) { SpringApplication.run(ConsumerApplication.class, args); } }
2.服务注册与发现
2.1 nacos使用
官方文档:Nacos
Nacos下载:Dowload
如何使用:
1.下载nacos后解压缩
2.找到解压缩后目录中的
bin
目录下的startup.cmd
运行由于nacos默认是以集群模式启动,所以第一次会无法启动。需要修改
startup.cmd
文件中的MOD属性为standalone
rem 单机模式启动 set MODE="standalone" rem 集群模式启动(默认) set MODE="cluster"
3.访问
http://127.0.0.1:8848/nacos/
进入首页4.首次进入需要登录(登录账号和密码均为
nacos
)
2.2 服务注册
这里以
provider服务
为例
- 添加
nacos
依赖
由于SpringCloud Feign在Hoxton.M2 RELEASED版本之后不再使用Ribbon而是使用spring-cloud-loadbalancer,所以不引入spring-cloud-loadbalancer会报错加入spring-cloud-loadbalancer依赖 并且在nacos中排除ribbon依赖,不然loadbalancer无效
<!--引入nacos依赖并排除ribbon-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--引入loadbalancer依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
application.yml
添加配置
# 配置端口号
server:
port: 8888
spring:
# 配置服务名
application:
name: providers
cloud:
nacos:
# 指定nacos地址
server-addr: 127.0.0.1:8848
# 指定注册中心地址
discovery:
server-addr: ${spring.cloud.nacos.server-addr}
- 启动
provider服务
2.3 服务发现
这里以
consumer服务
为例
- 添加
nacos
依赖
由于SpringCloud Feign在Hoxton.M2 RELEASED版本之后不再使用Ribbon而是使用spring-cloud-loadbalancer,所以不引入spring-cloud-loadbalancer会报错加入spring-cloud-loadbalancer依赖 并且在nacos中排除ribbon依赖,不然loadbalancer无效
<!--引入nacos依赖并排除ribbon-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--引入loadbalancer依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>v
- 添加
ConsumerController
@RestController
public class ConsumerController {
@Autowired
private DiscoveryClient discoveryClient;
//获取providers服务的服务列表
@GetMapping("/consumer/getServiceInstance")
public List<ServiceInstance> geterviceInstance() {
return this.discoveryClient.getInstances("providers");
}
}
- 访问
http://127.0.0.1:9999/consumer/getServiceInstance
即可
3.服务通信
- 启动三个
provider服务
3.1 基本服务调用(不推荐)
- 编辑
ConsumerController
(重点)
@RestController
@Slf4j
public class ConsumerController {
@Autowired
//RestTemplate不能自动装载 需要手动配置Bean
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient;
//获取providers服务的服务列表
@GetMapping("/consumer/getServiceInstance")
public List<ServiceInstance> geterviceInstance() {
return this.discoveryClient.getInstances("providers");
}
/*
服务调用
方法1:通过RestTemplate直接调用(不常用)
缺点:1.uri固定写死 2.不能实现负载均衡 3.后期维护困难
*/
// @GetMapping("/consumer/serviceInvoking")
// public String serviceInvoking() {
// String result = restTemplate.getForObject("http://127.0.0.1:8888/provider/getPort", String.class);
// log.info("调用成功,端口为:{}", result);
// return "调用成功,端口为:" + result;
// }
/*
服务调用
方法2:通过DiscoveryClient+RestTemplate调用(不常用)
缺点:1.代码冗余量大 2.后期维护困难
*/
// @GetMapping("/consumer/serviceInvoking")
// public String serviceInvoking() {
// //获取服务列表
// List<ServiceInstance> serviceInstanceList = this.discoveryClient.getInstances("providers");
// //手动实现负载均衡
// int num = ThreadLocalRandom.current().nextInt(serviceInstanceList.size());
// //获取实例
// ServiceInstance serviceInstance = serviceInstanceList.get(num);
// //通过RestTemplate调用
// String result = restTemplate.getForObject(serviceInstance.getUri() + "/provider/getPort", String.class);
// log.info("调用成功,端口为:{}", result);
// return "调用成功,端口为:" + result;
// }
/*
服务调用
方法3:通过Ribbon+RestTemplate调用(不常用)
使用:在RestTemplate Bean上添加@LoadBalanced注解实现自动负载均衡
缺点:1.后期维护困难
*/
@GetMapping("/consumer/serviceInvoking")
public String serviceInvoking() {
// http://服务ID/服务地址
String result = restTemplate.getForObject("http://providers/provider/getPort", String.class);
log.info("调用成功,端口为:{}", result);
return "调用成功,端口为:" + result;
}
}
- 配置
RestTemplate Bean
@Configuration
public class CommonConfiguration {
@Bean
@LoadBalanced//方法3调用时需要加此注解
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
3.2 OpenFeign调用(推荐)
- 引入
openfeign
依赖
<!--引入openfeign依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
- 入口类添加
@EnableFeignClients
注解开启Feifn支持
@SpringBootApplication
@EnableFeignClients
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
- 开发Feign客户端接口
ProviderClient
@FeignClient("providers")
public interface ProviderClient {
@GetMapping("/provider/getPort")
String getPort();
}
- 实现调用
@RestController
@Slf4j
public class ConsumerController {
@Autowired
private ProviderClient providerClient;
@GetMapping("/consumer/serviceInvoking")
public String serviceInvoking() {
String result = providerClient.getPort();
log.info("调用成功,端口为:{}", result);
return "调用成功,端口为:" + result;
}
}
扩展:openfeign常用配置
# 配置随机负载均衡策略 providers: ribbon: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 配置日志输出 feign: client: config: providers: loggerLevel: FULL connectTimeout: 5000 readTimeout: 5000 logging: level: com.hikw.clients.ProviderClient: debug # 启用OkHttp feign: okhttp: enabled: true
4.服务限流降级
Sentinel:Sentinel官网
4.1 Sentinel 使用
- 通过 Dowload 下载好Sentinel,使用下列命令执行jar包
java -jar sentinel-dashboard-1.8.2.jar
- 通过
127.0.0.1:8080
访问Sentinel首页(默认密码账户为:sentinel )
以下步骤全在
provider服务
中进行
- 添加sentinel依赖以及actuator依赖
<!--引入sentinel依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!--引入actuator依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
application.yml
添加配置
# 指定sentinel地址
spring:
cloud:
sentinel:
transport:
dashboard: 127.0.0.1:8080
# 暴露所有端口(用于监控)
management:
endpoints:
web:
exposure:
include: '*'
- 访问
http://127.0.0.1:9999/consumer/serviceInvoking
即可发现访问已被监控
4.2 流量控制
- 选择需要添加流控的地址
- 操作详解
资源名:即访问资源路径(设置资源)
针对来源:一般默认
阈值类型:OPS(常用)/并发线程数
单机阈值:即一秒钟可以允许的请求数
是否集群:集群设置
流控模式:
- 直接:对
设置资源
进行限流- 关联:若对关联资源进行限流,关联资源会影响到设置资源(即当关联资源被限流时,设置资源也会被影响不可访问)
- 链路:对service层进行限流
流控效果:
- 快速失败:访问失败时直接返回异常信息
- Warm Up:预加载(预热时间)
- 排队等待:第一次调用超出阈值时则会在规定的时间内再次进行调用,如果第二次也失败则抛出异常
4.3 熔断(降级)控制
- 选择需要熔断的地址
- 操作详解
资源名:即访问资源路径(设置资源)
熔断策略:
- 慢调用比例
- 异常比例
- 异常数
最大RT:单个请求的响应时间
比利阈值:比例之间阈值
熔断时长:熔断时间长度
最小请求数:1秒内5次请求机会(判断是否有问题)
统计时长:统计时间
4.4 热点控制
对某个参数进行限流
4.5 授权控制
- 添加配置类
public class RequestOriginParserConfiguration implements RequestOriginParser {
@Override
public String parseOrigin(HttpServletRequest httpServletRequest) {
String name = httpServletRequest.getParameter("name");
if (StringUtils.isEmpty(name)) {
throw new RuntimeException("未授权!");
}
return name;
}
}
@Configuration
public class SentinelConfiguration {
@PostConstruct
public void init() {
WebCallbackManager.setRequestOriginParser(new RequestOriginParserConfiguration());
}
}
5.消息队列
下载:Download
这里使用windows启动RocketMQ
1.配置环境变量
变量名:ROCKETMQ_HOME
变量值:你的RocketMQ解压缩后的路径
2.cmd到RocketMQ的bin目录下
- 启动NameServer服务
start mqnamesrv.cmd
- 启动Broker服务
首先修改内存大小:
修改
runserver.cmd
和runbroker.cmd
的下类命令:set "JAVA_OPT=%JAVA_OPT% -server -Xms256m -Xmx256m -Xmn128m"
在启动服务
start mqbroker.cmd -n 127.0.0.1:9876
- 引入RocketMQ相关依赖(provider&consumer)
<!--引入RocketMQ依赖-->
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>4.9.0</version>
</dependency>
6.服务网关
6.1 gateway使用
-
新建网关服务
gateway
(注意不能有web组件依赖) -
手动配置(方式一)
引入
gateway
依赖
<!--引入Gateway依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
配置
application.yml
# 设置端口号
server:
port: 80
spring:
# 配置服务名
application:
name: gateway
cloud:
gateway:
# 开启根据服务名动态获取路由功能
discovery:
locator:
enabled: true
# 配置网关路由
routes:
- id: provider_route
# 单体配置
# uri: http://127.0.0.1:8888
# 集群配置
uri: lb://providers
predicates:
- Path=/provider/**
- id: consumer_route
# uri: http://127.0.0.1:9999
uri: lb://consumers
predicates:
- Path=/consumer/**
通过
127.0.0.1/provider/getPort
即可访问
- 自动配置(方式二)
添加nacos依赖
<!--引入nacos依赖并排除ribbon-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<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>
</dependency>
配置
application.yml
# 设置端口号
server:
port: 80
spring:
# 配置服务名
application:
name: gateway
cloud:
gateway:
# 开启根据服务名动态获取路由功能
discovery:
locator:
enabled: true
此时通过nacos注册后使用
127.0.0.1/providers/provider/getPort
也可以访问(127.0.0.1/服务名/服务URL)
6.2 路由限流
- 删除
nacos
依赖并引入sentinel
适配器依赖
<!--引入sentinel网关限流依赖-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
</dependency>
- 编写配置类
GatewayConfiguration
@Configuration
public class GatewayConfiguration {
private final List<ViewResolver> viewResolvers;
private final ServerCodecConfigurer serverCodecConfigurer;
public GatewayConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider,
ServerCodecConfigurer serverCodecConfigurer) {
this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
this.serverCodecConfigurer = serverCodecConfigurer;
}
//配置限流异常处理
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
return new SentinelGatewayBlockExceptionHandler((viewResolvers), serverCodecConfigurer);
}
//配置初始化限流参数
@PostConstruct
public void initGatewayRules() {
Set<GatewayFlowRule> rules = new HashSet<>();
rules.add(
new GatewayFlowRule("provider_route")
.setCount(1)
.setIntervalSec(1)
);
GatewayRuleManager.loadRules(rules);
}
//初始化限流过滤器
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public GlobalFilter sentinelGatewayFilter() {
return new SentinelGatewayFilter();
}
//自定义限流异常页面
@PostConstruct
public void initBlockHandlers() {
BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {
@Override
public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
Map map = new HashMap();
map.put("code", 0);
map.put("msg", "被限流了");
return ServerResponse.status(HttpStatus.OK)
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromObject(map));
}
};
GatewayCallbackManager.setBlockHandler(blockRequestHandler);
}
}
Sec(1)
);
GatewayRuleManager.loadRules(rules);
}
//初始化限流过滤器
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public GlobalFilter sentinelGatewayFilter() {
return new SentinelGatewayFilter();
}
//自定义限流异常页面
@PostConstruct
public void initBlockHandlers() {
BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {
@Override
public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
Map map = new HashMap();
map.put("code", 0);
map.put("msg", "被限流了");
return ServerResponse.status(HttpStatus.OK)
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromObject(map));
}
};
GatewayCallbackManager.setBlockHandler(blockRequestHandler);
}
}