SpringCloud笔记

警告
本文最后更新于 2023-05-08,文中内容可能已过时,请谨慎使用。

本文代码仓库地址:https://git.bnblogs.cc/zfp/SpringCloudStudy

官网介绍:

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

版本对应关系:

依赖 版本号
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中添加SpringCloudAlibabaSpring 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>

下载nacos2.0.4,下载地址:https://github.com/alibaba/nacos/releases/tag/2.0.4

单机启动命令

startup.cmd -m standalone
/images/all/image-20230505215652974.png

用户名和密码都是nacos,登录之后是下面的界面

/images/all/image-20230505215759302.png

新增一个moduleprovider

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
Bug
刷新maven,可能会报错xxx\pom.xml: must be “pom” but is “jar”

解决方案:在父工程中添加配置

<packaging>pom</packaging>

启动provider, 查看已注册服务:

/images/all/image-20230505230058026.png

允许IDEA启动一个子项目的多个实例

/images/all/image-20230505231026006.png

这样可以修改application.yml中的服务端口来同时启动多个实例**(便于后面测试负载均衡)**。

新增一个moduleprovider

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会返回当前已注册的所有实例

/images/all/image-20230506142436902.png

现在如何通过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,会发现随机返回一个端口值。

RestTemplate的配置中添加@LoadBalanced注解,负载均衡默认配置为轮询算法

@Configuration
public class ConsumerConfig {
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

修改父项目的pom.xml文件,添加spring-cloud-starter-loadbalancer依赖

信息
SpringCloudAlibaba 2021.0.4.0对应的spring-cloud-starter-loadbalancer版本是3.1.4
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-loadbalancer</artifactId>
    <version>3.1.4</version>
</dependency>

修改consumerindex接口

@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();
    }
}

常见概念:

  • 服务雪崩:在整条链路的服务中,一个服务失败,导致整条链路的服务都失败的情形。

  • 服务熔断(外部出问题了,主动放弃):当下游的服务因为某种原因突然变得不可用响应过慢,上游服务为了保证自己整体服务的可用性,不再继续调用目标服务,直接返回,快速释放资源。如果目标服务情况好转则恢复调用。

  • 服务降级(自身处理不过来了,舍弃一些功能):

    • 当下游的服务因为某种原因响应过慢,下游服务主动停掉一些不太重要的业务,释放出服务器资源,增加响应速度!
    • 当下游的服务因为某种原因不可用,上游主动调用本地的一些降级逻辑,避免卡顿,迅速返回给用户!

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.5https://github.com/alibaba/Sentinel/releases

Sentinel 启动命令

java -jar sentinel-dashboard-1.8.5.jar --server.port=8200

访问localhost:8200用户:sentinel 密码: sentinel

/images/all/image-20230506171139025.png

通过前面的localhost:8100/index访问provider服务,在Sentinel控制台进行监控

/images/all/image-20230506172922503.png
/images/all/image-20230506174527541.png

每秒只能访问index接口一次

provider中的indexController中添加一个list接口

@GetMapping("/list")
public String list() {
    return "list";
}
/images/all/image-20230506174805479.png

这样如果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新增两个接口test1test2用来访问testService方法:

@GetMapping("/test1")
public String test1() {
    return indexService.testService();
}

@GetMapping("/test2")
public String test2() {
    return indexService.testService();
}

调用localhost:8080/test1localhost:8080/test2, 发现sentinel已经监控到链路了!

/images/all/image-20230506181811681.png

设置链路流控:

/images/all/image-20230508170502666.png

这是1s内多次访问localhost:8080/test1会被限流。链路限流比关联限流细粒度更高!

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;
}
/images/all/image-20230506195710989.png

当访问接口时带num1的次数大于每秒1次,会进行限流,将参数索引改为1,则对num2进行限流

/images/all/image-20230506200016230.png

高级规则,将某个变量值的访问阈值增大

配置授权规则,只有经过授权后的接口才能访问

添加配置类

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字段才可以访问

/images/all/image-20230506201312584.png

配置访问接口的白名单和黑名单:

  • 如果配置白名单,那么只有在白名单中的name才能访问该接口
  • 如果配置黑名单,则只有在黑名单中的name不能访问
/images/all/image-20230506201352343.png
/images/all/image-20230506201539399.png

下载地址: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
/images/all/image-20230506203530255.png
/images/all/image-20230506203729131.png

启动broker

nohup ./mqbroker -n localhost:9876 & # 后台启动broker
tail -f ~/logs/rocketmqlogs/broker.log # 查看日志
/images/all/image-20230506204108794.png

测试RockerMQ是否正常

设置nameserver环境变量

export NAMESRV_ADDR=localhost:9876

测试消息发送

./tools.sh org.apache.rocketmq.example.quickstart.Producer
/images/all/2023-05-08_16-53-01.png

测试消息接收

./tools.sh org.apache.rocketmq.example.quickstart.Consumer
/images/all/image-20230506204632893.png

关闭RockerMQ

./mqshutdown broker
./mqshutdown namesrv

再次启动

nohup sh mqnamesrv >> /home/zfp/nameserver.out 2>&1 & # 后台启动nameserver
nohup ./mqbroker -n localhost:9876 & # 后台启动broker

下载地址: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
/images/all/image-20230506211601517.png

启动项目

cd target
java -jar rocketmq-dashboard-1.0.0.jar
/images/all/image-20230506211944690.png

访问localhost:9000打开控制台界面

/images/all/image-20230506212049334.png

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();
    }
}

控制台查看消息

/images/all/image-20230506215606374.png

测试代码:

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();
    }
}

Provider将大量消息写入RocketMQ,Consumer从RocketMQ读取消息进行处理。

修改Providerpom.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;
}

修改consumerpom.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;
}

API 网关是一个搭建在客户端和微服务之间的服务,我们可以在 API 网关中处理一些非业务功能的逻辑,例如权限验证、监控、缓存、请求路由等。

API 网关就像整个微服务系统的门面一样,是系统对外的唯一入口。有了它,客户端会先将请求发送到 API 网关,然后由 API 网关根据请求的标识信息将请求转发到微服务实例。

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依赖移除,并在providerconsumer中添加上该依赖。

新建一个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,现在访问providerlist接口可以通过http://localhost:8080/list直接访问,也可以通过网关地址http://localhost:9999/provider/list间接访问。

修改gatewaypom.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中添加两组接口,分别是api1api2,每组有demo1demo2两个接口

@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读取配置信息,不用手动配置路由信息

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>

修改gatewaypom.xml

server:
  port: 9999
spring:
  application:
    name: gateway
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true
    nacos:
      discovery:
        server-addr: localhost:8848
        service: gateway

重启providergateway,可以实现之前配置的分组限流的所有效果。

如果修改了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获取路由信息还是自定义路由信息,都需要一一对应。


相关文章