SpringCloud笔记
本文代码仓库地址:https://git.bnblogs.cc/zfp/SpringCloudStudy
Spring Cloud Alibaba
官网介绍:
Spring Cloud Alibaba 致力于提供微服务开发的一站式解决方案。此项目包含开发分布式应用微服务的必需组件,方便开发者通过 Spring Cloud 编程模型轻松使用这些组件来开发分布式应用服务。
依托 Spring Cloud Alibaba,您只需要添加一些注解和少量配置,就可以将 Spring Cloud 应用接入阿里微服务解决方案,通过阿里中间件来迅速搭建分布式应用系统。
Github
地址: https://github.com/alibaba/spring-cloud-alibaba
Spring Boot
–> Spring Cloud
–> Spring Cloud Alibaba
创建Spring Cloud Alibaba项目
版本对应关系:
依赖 | 版本号 |
---|---|
SpringBoot |
2.6.11 |
Spring Cloud |
2021.0.4 |
Spring Cloud Alibaba |
2021.0.4.0 |
Sentinel jar |
1.8.5 |
Nacos jar |
2.0.4 |
RocketMQ |
4.9.4 |
Seata |
1.5.2 |
spring-cloud-starter-loadbalancer |
3.1.4 |
spring-cloud-starter-gateway |
3.1.4 |
spring-cloud-starter-alibaba-sentinel |
2021.0.4.0 |
创建父工程springcloudalibabainit
在pom.xml
中添加SpringCloudAlibaba
和Spring Cloud
对应版本的依赖,完整依赖如下:
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.11</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>cc.bnblogs</groupId>
<artifactId>springcloudalibabainit</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springcloudalibabainit</name>
<description>springcloudalibabainit</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2021.0.4</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2021.0.4.0</version>
</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>
</project>
Nacos服务治理
Nacos实现服务注册+服务发现
下载nacos2.0.4
,下载地址:https://github.com/alibaba/nacos/releases/tag/2.0.4
单机启动命令
startup.cmd -m standalone
用户名和密码都是nacos
,登录之后是下面的界面
创建子工程来测试服务注册
新增一个module
: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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<!--修改这里的配置就可继承父工程配置-->
<groupId>cc.bnblogs</groupId>
<artifactId>springcloudalibabainit</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>provider</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>provider</name>
<description>provider</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!--添加nacos-discovery-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2021.0.4.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
配置provider
模块的application.yml
spring:
cloud:
nacos:
discovery:
server-addr: localhost:8848
application:
name: provider
maven
,可能会报错xxx\pom.xml: must be “pom” but is “jar”解决方案:在父工程中添加配置
<packaging>pom</packaging>
启动provider
, 查看已注册服务:
允许IDEA启动一个子项目的多个实例
这样可以修改application.yml
中的服务端口来同时启动多个实例**(便于后面测试负载均衡)**。
创建子工程来测试服务发现
新增一个module
: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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<!--继承父工程配置-->
<groupId>cc.bnblogs</groupId>
<artifactId>springcloudalibabainit</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>consumer</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>consumer</name>
<description>consumer</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!--添加nacos-discovery-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2021.0.4.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
配置application.yml
:
这里需要配置consumer的服务名称,否则会出现下面的报错
java.lang.IllegalArgumentException: Param ‘serviceName’ is illegal, serviceName is blank
server:
port: 8100
spring:
cloud:
nacos:
discovery:
service: consumer
前面已经注册了多个服务,现在我们需要读取它们。
在consumer
组件中新建一个ConsumerController
@RestController
public class ConsumerController {
@Autowired
private DiscoveryClient discoveryClient;
@GetMapping("/instances")
public List<ServiceInstance> AllInstances() {
return discoveryClient.getInstances("provider");
}
}
访问localhost:8100/instances
会返回当前已注册的所有实例
现在如何通过consumer
访问provider
的接口呢?
首先,在provider
新建一个IndexController
写一个简单的接口,返回当前服务的端口
@RestController
public class IndexController {
@Value("${server.port}")
private String port;
@GetMapping("/index")
public String getPort() {
return this.port;
}
}
回到consumer
, 修改之前的ConsumerController
@RestController
public class ConsumerController {
@Autowired
private DiscoveryClient discoveryClient;
@Autowired
private RestTemplate restTemplate;
@GetMapping("/instances")
public List<ServiceInstance> AllInstances() {
return discoveryClient.getInstances("provider");
}
@GetMapping("/index")
public String index() {
// 返回当前注册的所有实例
List<ServiceInstance> list = AllInstances();
// 随机选择一个实例
int index = ThreadLocalRandom.current().nextInt(list.size());
ServiceInstance serviceInstance = list.get(index);
String url = serviceInstance.getUri() + "/index";
return restTemplate.getForObject(url,String.class);
}
}
这里需要手动将restTemplate
注入IOC
容器,新建一个配置文件ConsumerConfig
@Configuration
public class ConsumerConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
访问localhost:8100/index
,会发现随机返回一个端口值。
基于LoadBalancer实现负载均衡
默认配置
在RestTemplate
的配置中添加@LoadBalanced
注解,负载均衡默认配置为轮询算法
@Configuration
public class ConsumerConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
修改父项目的pom.xml
文件,添加spring-cloud-starter-loadbalancer
依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
<version>3.1.4</version>
</dependency>
修改consumer
的index
接口
@GetMapping("/index")
public String index() {
return restTemplate.getForObject("http://provider/index",String.class);
}
注意这时候采用的是轮询算法,重复返回8080-8082
随机算法配置
首先我们需要创建一个类用来构建RandomLoadBalancer
,但是需要注意的是,这个类不能被加载到spring的上下文中,也就是说这个类要么不添加@Configuration之类的注解,要么不在springscan的扫描包之内。
新建一个配置类RandomLoadBalancerRuleConfig
public class RandomLoadBalancerRuleConfig {
@Bean
public ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment,
LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new RandomLoadBalancer(loadBalancerClientFactory
.getLazyProvider(name, ServiceInstanceListSupplier.class),
name);
}
}
在ConsumerConfig
中指定LoadBalancer
的策略为随机算法
@Configuration
@LoadBalancerClients(defaultConfiguration = {RandomLoadBalancerRuleConfig.class})
public class ConsumerConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
基于权重的算法
和随机算法配置类似,新建一个WeightLoadBalancerRuleConfig
配置类
public class WeightLoadBalancerRuleConfig {
@Resource
private NacosDiscoveryProperties nacosDiscoveryProperties;
@Bean
public ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment,
LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new NacosLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name,nacosDiscoveryProperties);
}
}
在ConsumerConfig
中指定LoadBalancer
的策略为基于权重的算法
@Configuration
@LoadBalancerClients(defaultConfiguration = {WeightLoadBalancerRuleConfig.class})
public class ConsumerConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
Sentinel 服务限流
常见概念:
-
服务雪崩:在整条链路的服务中,一个服务失败,导致整条链路的服务都失败的情形。
-
服务熔断(外部出问题了,主动放弃):当下游的服务因为某种原因突然变得不可用或响应过慢,上游服务为了保证自己整体服务的可用性,不再继续调用目标服务,直接返回,快速释放资源。如果目标服务情况好转则恢复调用。
-
服务降级(自身处理不过来了,舍弃一些功能):
- 当下游的服务因为某种原因响应过慢,下游服务主动停掉一些不太重要的业务,释放出服务器资源,增加响应速度!
- 当下游的服务因为某种原因不可用,上游主动调用本地的一些降级逻辑,避免卡顿,迅速返回给用户!
在provider
中添加依赖:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<version>2021.0.4.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
配置application.yml
:
spring:
cloud:
sentinel:
transport:
# 配置控制台的访问地址
dashboard: localhost:8200
# 监控所有请求
management:
endpoints:
web:
exposure:
include: '*'
下载Sentinel v1.8.5:https://github.com/alibaba/Sentinel/releases
Sentinel 启动命令
java -jar sentinel-dashboard-1.8.5.jar --server.port=8200
访问localhost:8200
,用户:sentinel 密码: sentinel
通过前面的localhost:8100/index
访问provider
服务,在Sentinel控制台进行监控
直接限流
每秒只能访问index
接口一次
关联限流
在provider
中的indexController
中添加一个list
接口
@GetMapping("/list")
public String list() {
return "list";
}
这样如果list
接口每秒次数超过1次,会导致index接口被限流。
链路限流
之前是对Controller
中的某个接口进行限流,而链路限流可以做到对Service
进行限流
首先需要在provider
中引入两个依赖
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
<version>1.8.5</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-web-servlet</artifactId>
<version>1.8.5</version>
</dependency>
添加下面的配置到application.yml
中
spring:
cloud:
nacos:
filter:
enabled: false
添加配置类FilterConfig
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean registrationBean() {
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(new CommonFilter());
registrationBean.addUrlPatterns("/*");
registrationBean.addInitParameter(CommonFilter.WEB_CONTEXT_UNIFY,"false");
registrationBean.setName("sentinelFilter");
return registrationBean;
}
}
新增一个IndexService
@Service
public class IndexService {
@SentinelResource("test")
public String testService() {
return "just test!";
}
}
IndexController
新增两个接口test1
和test2
用来访问testService
方法:
@GetMapping("/test1")
public String test1() {
return indexService.testService();
}
@GetMapping("/test2")
public String test2() {
return indexService.testService();
}
调用localhost:8080/test1
和localhost:8080/test2
, 发现sentinel
已经监控到链路了!
设置链路流控:
这是1s内多次访问localhost:8080/test1
会被限流。链路限流比关联限流细粒度更高!
Sentinel 热点规则
在IndexController
中添加新增一个hot
接口,可以做到对某个变量限流
@GetMapping("/hot")
@SentinelResource("hot")
public String getHot(@RequestParam(value = "num1",required = false)Integer num1,
@RequestParam(value = "num2",required = false) Integer num2){
return num1 + "-" + num2;
}
当访问接口时带num1
的次数大于每秒1次,会进行限流,将参数索引改为1,则对num2
进行限流
高级规则,将某个变量值的访问阈值增大
Sentinel 授权规则
配置授权规则,只有经过授权后的接口才能访问
添加配置类
public class RequestOriginParserDefinition implements RequestOriginParser {
@Override
public String parseOrigin(HttpServletRequest httpServletRequest) {
String name = httpServletRequest.getParameter("name");
if (StringUtils.isEmpty(name)) {
throw new RuntimeException("name is null");
}
return name;
}
}
注入IOC容器
@Configuration
public class SentinelConfiguration {
// 配置授权规则
@PostConstruct
public void init() {
WebCallbackManager.setRequestOriginParser(new RequestOriginParserDefinition());
}
}
这样访问所有接口必须带上name
字段才可以访问
配置访问接口的白名单和黑名单:
- 如果配置白名单,那么只有在白名单中的name才能访问该接口
- 如果配置黑名单,则只有在黑名单中的name不能访问
RocketMQ
安装RocketMQ 4.9.4
下载地址:https://www.apache.org/dyn/closer.cgi?path=rocketmq/4.9.4/rocketmq-all-4.9.4-bin-release.zip
解压:
unzip rocketmq-all-4.9.4-bin-release.zip
启动nameserver
cd rocketmq-all-4.9.4-bin-release/bin
nohup sh mqnamesrv >> /home/zfp/nameserver.out 2>&1 &
修改内存限制
vim runbroker.sh
vim runServer.sh
启动broker
nohup ./mqbroker -n localhost:9876 & # 后台启动broker
tail -f ~/logs/rocketmqlogs/broker.log # 查看日志
测试RockerMQ是否正常
设置nameserver
环境变量
export NAMESRV_ADDR=localhost:9876
测试消息发送
./tools.sh org.apache.rocketmq.example.quickstart.Producer
测试消息接收
./tools.sh org.apache.rocketmq.example.quickstart.Consumer
关闭RockerMQ
./mqshutdown broker
./mqshutdown namesrv
再次启动
nohup sh mqnamesrv >> /home/zfp/nameserver.out 2>&1 & # 后台启动nameserver
nohup ./mqbroker -n localhost:9876 & # 后台启动broker
安装RockerMQ-console
下载地址:https://github.com/apache/rocketmq-dashboard/releases/tag/rocketmq-dashboard-1.0.0
解压后,修改application.properties
,修改下面两个配置
server.port=9000
rocketmq.config.namesrvAddr=192.168.153.131:9876
回到项目根目录,使用maven进行打包,执行需要一段时间,耐心等待
mvn clean package -Dmaven.test.skip=true
启动项目
cd target
java -jar rocketmq-dashboard-1.0.0.jar
访问localhost:9000
打开控制台界面
SpringBoot实现消息发送
在provider
中的pom.xml
添加依赖
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.2.3</version>
</dependency>
测试代码:
package cc.bnblogs.provider.test;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;
public class Test {
public static void main(String[] args) throws Exception {
// 创建消息生产者
DefaultMQProducer producer = new DefaultMQProducer("myProducer-Group");
// 设置Nameserver
producer.setNamesrvAddr("192.168.153.131:9876");
// 启动生产者
producer.start();
// 构建消息对象
Message message = new Message("myTopic","myTag",("test mq").getBytes());
// 发送消息
SendResult sendResult = producer.send(message,1000);
System.out.println(sendResult);
// 关闭生产者
producer.shutdown();
}
}
控制台查看消息
SpringBoot实现消息接收
测试代码:
package cc.bnblogs.provider.test;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
@Slf4j
public class ConsumerTest {
public static void main(String[] args) throws Exception {
// 创建消息消费者
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("myConsumer-Group");
// 设置NameServer
consumer.setNamesrvAddr("192.168.153.131:9876");
// 指定订阅的主题和标签
consumer.subscribe("myTopic","*");
// 回调函数
consumer.registerMessageListener((MessageListenerConcurrently)
(list, consumeConcurrentlyContext) -> {
log.info("Message=>{}",list);
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
});
// 启动消费者
consumer.start();
}
}
SpringBoot整合RocketMQ
Provider
将大量消息写入RocketMQ,Consumer
从RocketMQ读取消息进行处理。
Provider
修改Provider
的pom.xml
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>5.0.0</version>
</dependency>
修改application.yml
rocketmq:
name-server: 192.168.153.131:9876
producer:
group: producer-group
添加实体类Order
:
/**
* 订单实体类
*/
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Order {
private Integer id;
private String buyerName;
private String buyerTel;
private String address;
private Date createDate;
}
修改IndexController
:
@GetMapping("/create")
public Order createOrder() {
Order order = new Order(
1,
"tom",
"123123",
"shanghai",
new Date()
);
rocketMQTemplate.convertAndSend("orderTopic",order);
return order;
}
consumer
修改consumer
的pom.xml
,加入下面的依赖
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>5.0.0</version>
</dependency>
修改application.yml
rocketmq:
name-server: 192.168.153.131:9876
添加之前相同的Order
实体类:
/**
* 订单实体类
*/
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Order {
private Integer id;
private String buyerName;
private String buyerTel;
private String address;
private Date createDate;
}
Gateway路由映射
API网关
API 网关是一个搭建在客户端和微服务之间的服务,我们可以在 API 网关中处理一些非业务功能的逻辑,例如权限验证、监控、缓存、请求路由等。
API 网关就像整个微服务系统的门面一样,是系统对外的唯一入口。有了它,客户端会先将请求发送到 API 网关,然后由 API 网关根据请求的标识信息将请求转发到微服务实例。
Spring Cloud Gateway
Spring Cloud Gateway 是 Spring Cloud 团队基于 Spring 5.0、Spring Boot 2.0 和 Project Reactor 等技术开发的高性能 API 网关组件。
Spring Cloud Gateway 旨在提供一种简单而有效的途径来发送 API,并为它们提供横切关注点,例如:安全性,监控/指标和弹性。
Spring Cloud Gateway 是基于 WebFlux 框架实现的,而 WebFlux 框架底层则使用了高性能的 Reactor 模式通信框架 Netty。
Spring Cloud Gateway和Servelt不能同时使用,所以需要移除Spring web
依赖
将父工程的spring-boot-starter-web
依赖移除,并在provider
和consumer
中添加上该依赖。
新建一个gateway
模块,并添加Spring Cloud Gateway
的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
<version>3.1.4</version>
</dependency>
配置application.yml
server:
port: 9999
spring:
application:
name: gateway
cloud:
gateway:
discovery:
locator:
enabled: true
routes:
- id: provider_router
uri: http://localhost:8080
predicates:
- Path=/provider/** # 将http://localhost:9999/provider/list映射到http://localhost:8080/provider/list
filters:
- StripPrefix=1 # 将映射后的第一个前缀provider去掉,得到最后的映射地址http://localhost:8080/list
网关端口为9999
,现在访问provider
的list
接口可以通过http://localhost:8080/list
直接访问,也可以通过网关地址http://localhost:9999/provider/list
间接访问。
基于路由的限流
修改gateway
的pom.xml
,添加一些依赖
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
<version>1.8.6</version>
</dependency>
application.yml
如下:
server:
port: 9999
spring:
application:
name: gateway
cloud:
gateway:
discovery:
locator:
enabled: true
routes:
- id: provider_router
uri: http://localhost:8080
predicates:
- Path=/provider/**
filters:
- StripPrefix=1
新建路由限流的配置类: SentinelRouteConfiguration
@Configuration // 标记为配置类
public class SentinelRouteConfiguration { // 路由维度限流配置类
private final List<ViewResolver> viewResolvers;
private final ServerCodecConfigurer serverCodecConfigurer;
public SentinelRouteConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider, // 构造函数
ServerCodecConfigurer serverCodecConfigurer) {
this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
this.serverCodecConfigurer = serverCodecConfigurer;
}
@PostConstruct
public void initGatewayRules() { //初始化限流规则
Set<GatewayFlowRule> rules = new HashSet<>();
GatewayFlowRule gatewayFlowRule = new GatewayFlowRule("provider_router");// 资源名称,对应routeId的值 此处限流用户服务
gatewayFlowRule.setCount(1); // 限流阀值
gatewayFlowRule.setIntervalSec(10); // 统计时间窗口(单位:秒),默认是1秒
rules.add(gatewayFlowRule);
GatewayRuleManager.loadRules(rules); // 载入规则
}
@PostConstruct
public void initBlockHandlers() { // 自定义限流后的界面
BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {
@Override
public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
Map<String, String> result = new HashMap<>(); // 限流提示
result.put("code", "0");
result.put("message", "您已被限流");
return ServerResponse.status(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON_UTF8).
body(BodyInserters.fromObject(result));
}
};
GatewayCallbackManager.setBlockHandler(blockRequestHandler);
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() { // 配置限流异常处理器
return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public GlobalFilter sentinelGatewayFilter() { //初始化一个限流的过滤器
return new SentinelGatewayFilter();
}
}
如果通过路由访问list
接口:http://localhost:9999/provider/list
,如果每秒访问次数超过1,会被限流
使用http://localhost:8080/list
则不受影响
基于分组的限流
当接口太多时,可以对接口进行分组,对一个分组进行限流
首先在provider
中的IndexController
中添加两组接口,分别是api1
和api2
,每组有demo1
和demo2
两个接口
@GetMapping("/api1/demo1")
public String demo1(){
return "api1/demo1";
}
@GetMapping("/api1/demo2")
public String demo2(){
return "api1/demo2";
}
@GetMapping("/api2/demo1")
public String demo3(){
return "api2/demo1";
}
@GetMapping("/api2/demo2")
public String demo4(){
return "api2/demo2";
}
修改限流的配置类SentinelRouteConfiguration
如下:
package cc.bnblogs.gateway.config;
import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPathPredicateItem;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPredicateItem;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.GatewayApiDefinitionManager;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandler;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.reactive.result.view.ViewResolver;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import javax.annotation.PostConstruct;
import java.util.*;
@Configuration // 标记为配置类
public class SentinelRouteConfiguration { // 添加分组api限流
private final List<ViewResolver> viewResolvers;
private final ServerCodecConfigurer serverCodecConfigurer;
public SentinelRouteConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider, // 构造函数
ServerCodecConfigurer serverCodecConfigurer) {
this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
this.serverCodecConfigurer = serverCodecConfigurer;
}
@PostConstruct
public void initGatewayRules() {
//初始化限流规则
Set<GatewayFlowRule> rules = new HashSet<>();
// 定义两个api分组
// 加入分组限流
rules.add(new GatewayFlowRule("provider_api1").setCount(1).setIntervalSec(1));
rules.add(new GatewayFlowRule("provider_api2").setCount(1).setIntervalSec(1));
GatewayRuleManager.loadRules(rules); // 载入规则
}
//自定义API分组
@PostConstruct
private void initCustomizedApis() {
Set<ApiDefinition> definitions = new HashSet<>();
ApiDefinition api1 = new ApiDefinition("provider_api1")
.setPredicateItems(new HashSet<ApiPredicateItem>() {{
add(new ApiPathPredicateItem().setPattern("/provider/api1/**") // api1下所有接口均被限流
.setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
}});
ApiDefinition api2 = new ApiDefinition("provider_api2")
.setPredicateItems(new HashSet<ApiPredicateItem>() {{
add(new ApiPathPredicateItem().setPattern("/provider/api2/demo2")); // api2只对demo2接口限流
}});
definitions.add(api1);
definitions.add(api2);
GatewayApiDefinitionManager.loadApiDefinitions(definitions);
}
@PostConstruct
public void initBlockHandlers() { // 自定义限流后的界面
BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {
@Override
public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
Map<String, String> result = new HashMap<>(); // 限流提示
result.put("code", "0");
result.put("message", "您已被限流");
return ServerResponse.status(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON_UTF8).
body(BodyInserters.fromObject(result));
}
};
GatewayCallbackManager.setBlockHandler(blockRequestHandler);
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() { // 配置限流异常处理器
return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public GlobalFilter sentinelGatewayFilter() { //初始化一个限流的过滤器
return new SentinelGatewayFilter();
}
}
可实现对api1
分组的所有接口限流,也可以对api2
分组的某个接口如demo2
进行限流
基于Nacos获取其他服务
直接通过Nacos
读取配置信息,不用手动配置路由信息
在gateway
中的pom.xml
中添加Nacos
依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2021.0.4.0</version>
</dependency>
修改gateway
的pom.xml
server:
port: 9999
spring:
application:
name: gateway
cloud:
gateway:
discovery:
locator:
enabled: true
nacos:
discovery:
server-addr: localhost:8848
service: gateway
重启provider
和gateway
,可以实现之前配置的分组限流的所有效果。
如果修改了provider
的服务名称比如修改为service
, 那么nacos注册的服务名称也会该为service
此时通过网关访问分组api1
下的demo1
的地址应该为http://localhost:9999/service/api1/demo1
,其他接口类似。
**此时你还会发现之前配置的限流规则也失效了,**因为配置文件中配置的限流的路径还是provider/**
,所以也要进行修改:
@Configuration // 标记为配置类
public class SentinelRouteConfiguration { // 添加分组api限流
private final List<ViewResolver> viewResolvers;
private final ServerCodecConfigurer serverCodecConfigurer;
public SentinelRouteConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider, // 构造函数
ServerCodecConfigurer serverCodecConfigurer) {
this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
this.serverCodecConfigurer = serverCodecConfigurer;
}
@PostConstruct
public void initGatewayRules() {
//初始化限流规则
Set<GatewayFlowRule> rules = new HashSet<>();
// 路由限流
GatewayFlowRule gatewayFlowRule = new GatewayFlowRule("provider_router");// 资源名称,对应routeId的值 此处限流用户服务
gatewayFlowRule.setCount(1); // 限流阀值
gatewayFlowRule.setIntervalSec(10); // 统计时间窗口(单位:秒),默认是1秒
// rules.add(gatewayFlowRule); //为了测试分组限流,先把这个注释
// 定义两个api分组
// 加入分组限流
rules.add(new GatewayFlowRule("provider_api1").setCount(1).setIntervalSec(1));
rules.add(new GatewayFlowRule("provider_api2").setCount(1).setIntervalSec(1));
GatewayRuleManager.loadRules(rules); // 载入规则
}
//自定义API分组
@PostConstruct
private void initCustomizedApis() {
Set<ApiDefinition> definitions = new HashSet<>();
ApiDefinition api1 = new ApiDefinition("provider_api1")
.setPredicateItems(new HashSet<ApiPredicateItem>() {{
add(new ApiPathPredicateItem().setPattern("/service/api1/**") // api1下所有接口均被限流
.setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
}});
ApiDefinition api2 = new ApiDefinition("provider_api2")
.setPredicateItems(new HashSet<ApiPredicateItem>() {{
add(new ApiPathPredicateItem().setPattern("/service/api2/demo2")); // api2只对demo2接口限流
}});
definitions.add(api1);
definitions.add(api2);
GatewayApiDefinitionManager.loadApiDefinitions(definitions);
}
@PostConstruct
public void initBlockHandlers() { // 自定义限流后的界面
BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {
@Override
public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
Map<String, String> result = new HashMap<>(); // 限流提示
result.put("code", "0");
result.put("message", "您已被限流");
return ServerResponse.status(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON_UTF8).
body(BodyInserters.fromObject(result));
}
};
GatewayCallbackManager.setBlockHandler(blockRequestHandler);
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() { // 配置限流异常处理器
return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public GlobalFilter sentinelGatewayFilter() { //初始化一个限流的过滤器
return new SentinelGatewayFilter();
}
}
总而言之,无论使用Nacos
获取路由信息还是自定义路由信息,都需要一一对应。