SpringCloud

SpringCloud

SpringCloudAlibaba

创建项目

创建Springboot父项目,只需要一个pom就可以了(AugJungle:生产项目单独部署)

POM:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<?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.3.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.jungle.springclouddemo</groupId>
<artifactId>springclouddemo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springclouddemo</name>
<description>Demo project for Spring Boot</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>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>

pom添加SpringCloudAlibaba依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.1.1.BUILD-SNAPSHOT</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
#出现问题再引入SpringCloud的包吧,个人感觉不用引入,but写这个的项目时引入了。。。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR6</version>
<type>pom</type>
<scope>import</scope>
</dependency>

创建子模块后修改子模块中的pom里面的parent标签,修改为父项目中的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?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>com.jungle.springclouddemo</groupId>
<artifactId>springclouddemo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.jungle</groupId>
<artifactId>news</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>news</name>
<description>Demo project for Spring Boot</description>

<properties>
<java.version>1.8</java.version>
</properties>

<dependencies>

</dependencies>

<build>

</build>

</project>

开启Nacos Server

文档地址:https://nacos.io/zh-cn/docs/quick-start.html

Windows版Nacos Server直接下载地址:https://download.csdn.net/download/qq_35742333/12697057

下载后进入bin文件夹下运行 .\startup.cmd -m standalone

-m:以单机模式运行

注册Nacos Discovery

子项目导入依赖

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

maven出现问题:

Could not find artifact com.alibaba.cloud:spring-cloud-starter-alibaba-nacos-discovery:pom:unknown in aliyun (https://maven.aliyun.com/repository/public)

Non-resolvable parent POM for com.jungle:news:0.0.1-SNAPSHOT: Could not find artifact com.jungle:springclouddemo:pom:0.0.1-SNAPSHOT and 'parent.relativePath' points at no local POM @ line 5, column 13

问题解决:

父项目添加

不是必须

1
2
3
<modules>
<module>news</module>
</modules>

​ 必须

父项目记得配一个pom

子项目最好配一个jar

修改配置文件

在application.properties文件添加spring.profiles.active=dev

新建application-dev.yml文件,添加配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
server:
port: 18081
spring:
cloud:
nacos:
discovery:
#命令空间ID
namespace: d2f4bb90-17ac-4bc1-9e32-8734e722eb0c
#集群
cluster-name: newMachine
#分组
group: 1
#数据源,可以用来自定义一些数据,供服务进行调用,例如两个服务版本不一致,用来让不同版本的调用不同的服务
metadata:
testKey1: testValue1
testKey2: testValue2
#Nacos注册服务的IP及端口号
server-addr: 127.0.0.1:8848
application:
name: service-news

此时运行子项目,已经可以在Nacos Server上看见注册的子项目运行服务,访问网址:http://192.168.20.29:8848/nacos/index.html可以看见已经注册的服务

DiscoveryClient

服务发现对象,可以用来进行手动服务注册

1
2
3
4
5
6
7
8
9
10
11
12
@Resource
private DiscoveryClient discoveryClient;

List<ServiceInstance> instances = discoveryClient.getInstances("service-news");

System.out.println("instances.size():"+instances.size());

ServiceInstance serviceInstance = instances.get(new Random().nextInt(instances.size()));

URI uri = serviceInstance.getUri();

String str = this.restTemplate.getForObject(uri +"/user/list",String.class);

使用Ribbon

参考文档:https://cloud.spring.io/spring-cloud-netflix/reference/html/

参考文档-ribbon细节:https://cloud.spring.io/spring-cloud-netflix/multi/multi_spring-cloud-ribbon.html

概念

Ribbon是Netflix发布的云中间层服务开源项目,主要功能是提供客户端(消费者方)负载均衡算法。Ribbon客户端组件提供一系列完善的配置项,如,连接超时,重试等。简单的说,Ribbon是一个客户端负载均衡器,我们可以在配置文件中列出load Balancer后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器,我们也很容易使用Ribbon实现自定义的负载均衡算法。

Bean实例

The following table shows the beans that Spring Cloud Netflix provides by default for Ribbon:

Bean Type Bean Name Class Name 作用
IClientConfig ribbonClientConfig DefaultClientConfigImpl 读取配置
IRule ribbonRule ZoneAvoidanceRule 负载均衡规则,选择实例
IPing ribbonPing DummyPing 筛选掉ping不通的实例
ServerList<Server> ribbonServerList ConfigurationBasedServerList 交给Ribbon的实例列表
ServerListFilter<Server> ribbonServerListFilter ZonePreferenceServerListFilter 过滤掉不符合条件的实例
ILoadBalancer ribbonLoadBalancer ZoneAwareLoadBalancer Ribbon的入口
ServerListUpdater ribbonServerListUpdater PollingServerListUpdater 更新交给Ribbon的List的策略

开启RestTemplate的负载均衡

  • 启动类上添加@EnableDiscoveryClient
  • RestTemplate 实例添加 @LoadBalanced
1
2
3
4
5
6
7
8
9
10
@SpringBootApplication
@EnableDiscoveryClient
public class NacosConsumerApplication {

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

代码实现自定义配置

局部配置
1
2
3
4
@Configuration
@RibbonClient(name = "custom", configuration = CustomConfiguration.class)
public class TestConfiguration {
}

上面意思是,针对custom服务调用,选择CustomConfiguration类的算法合适

全局配置
1
@RibbonClients(defaultConfiguration = DefaultRibbonConfig.class)

上面意思是,针对所有服务调用,选择DefaultRibbonConfig类的算法合适

注意

The CustomConfiguration class must be a @Configuration class, but take care that it is not in a @ComponentScan for the main application context. Otherwise, it is shared by all the @RibbonClients. If you use @ComponentScan (or @SpringBootApplication), you need to take steps to avoid it being included (for instance, you can put it in a separate, non-overlapping package or specify the packages to scan explicitly in the @ComponentScan).

需要将配置类放到@SpringBootApplication注解扫描不到的上级菜单中,避免冲突

实例
  • @SpringBootApplication不可以扫描到的地方,定义一个Ribbon的配置类

    1
    2
    3
    4
    5
    6
    7
    @Configuration
    public class RibbonConfig {
    @Bean
    public IRule getIRule(){
    return new BestAvailableRule();
    }
    }
  • @SpringBootApplication可以扫描到,定义一个Ribbon配置类,用来指定配置的位置

    1
    2
    3
    4
    @Configuration
    @RibbonClient(name = "custom", configuration = CustomConfiguration.class)
    public class TestConfiguration {
    }

properteis属性配置自定义配置

Starting with version 1.2.0, Spring Cloud Netflix now supports customizing Ribbon clients by setting properties to be compatible with the Ribbon documentation.

This lets you change behavior at start up time in different environments.

The following list shows the supported properties>:

  • <clientName>.ribbon.NFLoadBalancerClassName: Should implement ILoadBalancer
  • <clientName>.ribbon.NFLoadBalancerRuleClassName: Should implement IRule
  • <clientName>.ribbon.NFLoadBalancerPingClassName: Should implement IPing
  • <clientName>.ribbon.NIWSServerListClassName: Should implement ServerList
  • <clientName>.ribbon.NIWSServerListFilterClassName: Should implement ServerListFilter
Classes defined in these properties have precedence over beans defined by using @RibbonClient(configuration=MyRibbonConfig.class) and the defaults provided by Spring Cloud Netflix.

To set the IRule for a service name called users, you could set the following properties:

application.yml

1
2
3
4
users:
ribbon:
NIWSServerListClassName: com.netflix.loadbalancer.ConfigurationBasedServerList
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule

See the Ribbon documentation for implementations provided by Ribbon.

与NacosServer联动

可以通过在nacos server网页上设置值,来动态调整Ribbon策略

  • 修改Ribbon的配置类,调用自定义的配置类

    1
    2
    3
    4
    5
    6
    7
    8
    @Configuration
    public class RibbonConfig {

    @Bean
    public IRule getIRule(){
    return new WeightRule;
    }
    }
  • 增加配置类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    public class WeightRule extends AbstractLoadBalancerRule {

    /**
    * 服务发现参数对象,Nacos自带
    *
    * @date 2020/08/11
    * @see NacosDiscoveryProperties
    */
    @Resource
    private NacosDiscoveryProperties nacosDiscoveryProperties;
    /**
    * 配置初始化
    *
    * @param iClientConfig 我的客户端配置
    * @return
    * @author Jiangmanman
    * @date 2020/08/11
    */
    @Override
    public void initWithNiwsConfig(IClientConfig iClientConfig) {
    }

    @SneakyThrows
    @Override
    public Server choose(Object key) {
    //1.拿到要访问的服务名
    //1.1调用父类对象
    ILoadBalancer loadBalancer = this.getLoadBalancer();
    //1.2转换成真正的实现类
    BaseLoadBalancer baseLoadBalancer = (BaseLoadBalancer)loadBalancer;
    //1.3得到加载的服务名称
    String serviceName = baseLoadBalancer.getName();
    //2.通过服务名获得在网页端往 Nacos Service 设置的权重值的实例
    //2.1通过Nacos参数对象获得服务名
    NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
    //2.1得到所有的健康的实例
    Instance instance = namingService.selectOneHealthyInstance(serviceName);
    return new NacosServer(instance);
    }
    }

  • 优化,可以限定上,仅调用相同集群下的instance(或者更加精确)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
           //优化,通过服务名在Nacos上面获得相同集群下面的instance
    //获得服务名下所有健康的实例
    List<Instance> allInstances = namingService.getAllInstances(serviceName, true);
    //获得当前集群名
    String clusterName = nacosDiscoveryProperties.getClusterName();
    //进行比对
    List<Instance> clusterInstance = allInstances.stream().filter(instance1 -> {
    return Objects.equals(clusterName, instance1);
    }).collect(Collectors.toList());
    //判断是否有相邻的集群节点
    if (CollectionUtils.isEmpty(clusterInstance)) {
    //维持原样
    clusterInstance = allInstances;
    }
    //通过权重算法从集合中获取一个instance,分析前面的 selectOneHealthyInstance 方法,里面就有实现的过程
    Instance weightInstance = Mybalancer.getWeightInstance(clusterInstance);

    /**
    *这个类是分析 selectOneHealthyInstance 方法的实现得来的
    */
    public static class Mybalancer extends Balancer{

    public static Instance getWeightInstance(List<Instance> hosts){
    return getHostByRandomWeight(hosts);
    }
    }

    注意:上述的优化只是手动实现,其实NamingService里面的selectOneHealthyInstance方法的重载里面有对这个的实现,直接调用即可

现在就可以通过Nacos Server 在网页中手动设置权重值,动态改变调用者,但是遇到No instances available,原因是配置里面进行了分组造成的,将分组去掉后可以正常调用,原因不明,有时间在回头看

缓存的设置

Ribbon第一次调用比较慢,就是因为默认实现是懒加载的方式,调用后才会取拉取信息,可以通过配置来修改这种默认行为

参考文档7.9

Each Ribbon named client has a corresponding child application Context that Spring Cloud maintains. This application context is lazily loaded on the first request to the named client. This lazy loading behavior can be changed to instead eagerly load these child application contexts at startup, by specifying the names of the Ribbon clients, as shown in the following example:

application.yml

1
2
3
4
ribbon:
eager-load:
enabled: true
clients: client1, client2, client3

Feign

Spring官方参考文档

概念

Feign is a declarative web service client. It makes writing web service clients easier. To use Feign create an interface and annotate it. It has pluggable annotation support including Feign annotations and JAX-RS annotations. Feign also supports pluggable encoders and decoders. Spring Cloud adds support for Spring MVC annotations and for using the same HttpMessageConverters used by default in Spring Web. Spring Cloud integrates Ribbon and Eureka, as well as Spring Cloud LoadBalancer to provide a load-balanced http client when using Feign.

主要解决客户端调用问题

使用方式

  • 导入依赖

    To include Feign in your project use the starter with group org.springframework.cloud and artifact id spring-cloud-starter-openfeign. See the Spring Cloud Project page for details on setting up your build system with the current Spring Cloud Release Train.

    1
    2
    3
    4
    5
    <!--Spring Feign-->
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
  • 配置注解@EnableFeignClients,调用方必须配置,被调用方可以不配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @SpringBootApplication
    @EnableFeignClients
    public class Application {

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

    }
  • 编写接口,接口的配置和要调用的controller保持一直

    1
    2
    3
    4
    5
    6
    7
    @FeignClient("service-user")
    //调用的方法有,这里也必须有,保证一致性
    @RequestMapping("/news")
    public interface UserClient {
    @GetMapping("/all")
    CommonResult all();
    }
  • 使用方式,此接口会放入容器中,所以@Resource注入即可

  • 注意:

    • 使用注解@FeignClient的接口去调用其他服务一定要标准化,例如多个参数必须使用@RequestParam来注明
    • 在调用表单参数的方法时,例如CommonResult all(User user);,需要将对象转变成表单参数,加上@SpringQueryMap注解

开启日志(默认关闭)

代码实现
  • 编写配置类

    1
    2
    3
    4
    5
    6
    7
    @Configuration
    public class FooConfiguration {
    @Bean
    Logger.Level feignLoggerLevel() {
    return Logger.Level.FULL;
    }
    }
  • 全局配置需要在@EnableFeignClients注解上指定defaultConfiguration 属性,例如:@EnableFeignClients(defaultConfiguration = UserClient.class)

  • 具备配置则是将@FeignClient("service-user")变成@FeignClient(name = "service-user",configuration = FeignConfig.class)

配置实现
  • 开启整体的默认日志环境,例如上诉UserClient类所在的的包

    1
    2
    3
    logging:
    level:
    com.jungle.servicenews.client: debug
  • 开启日志,还可以配置其他feign的属性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    feign:
    client:
    config:
    #也可以针对某个包进行配置
    default:
    connectTimeout: 5000
    readTimeout: 5000
    #日志级别:NONE(默认),BASIC,HEADERS,FULL
    loggerLevel: FULL

连接优化

因为feign默认的是来一次就建立一次连接,可以配置类似连接池的东东

配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
feign:
# client:
# config:
# #也可以针对某个包进行配置
# default:
# connectTimeout: 5000
# readTimeout: 5000
# #日志级别:NONE(默认),BASIC,HEADERS,FULL
# loggerLevel: FULL
httpclient:
enabled: true
max-connections: 200
max-connections-per-route: 50
connection-timeout: 200000

Sentinel

Github上的参考资料-简要

Github上的参考资料-详细

在github上搜索spring-cloud-alibaba

  • 然后点击上面Wiki,进入的是每个模块下面大致的内容
  • 点击下面的read.me的内容,可以进入到具体的某个模块,再点击上面的wiki便可以进入这个模块的具体内容

在spring官网上的参考资料说明如下所示

简介

随着微服务的流行,服务和服务之间的稳定性变得越来越重要。 Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。

使用方式

  • 前置,通过actuator暴露端点信息,先导入依赖

    1
    2
    3
    4
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
  • 然后修改配置文件,加入如下

    1
    2
    3
    4
    5
    6
    management:
    endpoints:
    web:
    exposure:
    #下面是暴漏所有端点
    include: "*"
  • 测试暴漏端点是否成功,打开网页访问:IP+端口+/actuator查看所有暴漏的端点信息,引入sentinel依赖后,可以直接通过IP:端口/actuator/sentinel访问查看sentinel的信息

  • 引入依赖,如果要在您的项目中引入 Sentinel,使用 group ID 为 com.alibaba.cloud 和 artifact ID 为 spring-cloud-starter-alibaba-sentinel 的 starter。

    1
    2
    3
    4
    <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
    </dependency>
  • 下载centinel控制台,下载地址:https://download.csdn.net/download/qq_35742333/12705492,下载完成直接可以运行`java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar`,运行完成直接ip+端口访问,默认java -jar 启动后的端口为8080,默认账户密码一致,都为sentinel

  • 服务与控制台建立通信

    1
    2
    3
    4
    5
    6
    7
    cloud:
    sentinel:
    transport:
    #控制台与服务之间通信的端口,不写默认也会给个8719
    port: 8719
    #控制台位置
    dashboard: localhost:8080
  • 测试:服务至少有一次访问,控制台的网页才会有显示

  • 对接口的调用如果出现返回数据为XML格式,因为sentinel中集成了com.fastxml.jackson.dataformat的jackson-dataformat-xml.xml优先级比JSON高,所以先返回XML

    • 解决方式一,去除sentinel依赖包的com.fastxml.jackson-dataformat

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
      <exclusions>
      <exclusion>
      <groupId>com.fasterxml.jackson.dataformat</groupId>
      <artifactId>jackson-dataformat-xml</artifactId>
      </exclusion>
      </exclusions>
      </dependency>
    • 解决方式二,Controller层增加返回格式指定@GetMapping(value = "/all",produces = MediaType.APPLICATION_JSON_VALUE)

流量控制

控制模式
  • 直接
    • 针对资源自己直接进行流量控制
  • 关联
    • 监控关联资源的流量情况来对自身资源进行限制
  • 链路
    • 对资源内部调用的资源进行入口来源的限制
控制效果
  • 快速失败
    • 达到阈值之后直接抛出异常
  • Warm Up
    • 当浏览在一瞬间很大的时候,不会让流量立即到达阈值,而是经过一段时间慢慢的预热增长
  • 排队通过
    • 请求达到阈值进行

熔断降级

  • 平均响应时间 (DEGRADE_GRADE_RT):当 1s 内持续进入 N 个请求,对应时刻的平均响应时间(秒级)均超过阈值(count,以 ms 为单位),那么在接下的时间窗口(DegradeRule 中的 timeWindow,以 s 为单位)之内,对这个方法的调用都会自动地熔断(抛出 DegradeException)。注意 Sentinel 默认统计的 RT 上限是 4900 ms,超出此阈值的都会算作 4900 ms,若需要变更此上限可以通过启动配置项 -Dcsp.sentinel.statistic.max.rt=xxx 来配置。
  • 异常比例 (DEGRADE_GRADE_EXCEPTION_RATIO):当资源的每秒请求量 >= N(可配置),并且每秒异常总数占通过量的比值超过阈值(DegradeRule 中的 count)之后,资源进入降级状态,即在接下的时间窗口(DegradeRule 中的 timeWindow,以 s 为单位)之内,对这个方法的调用都会自动地返回。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。
  • 异常数 (DEGRADE_GRADE_EXCEPTION_COUNT):当资源近 1 分钟的异常数目超过阈值之后会进行熔断。注意由于统计时间窗口是分钟级别的,若 timeWindow 小于 60s,则结束熔断状态后仍可能再进入熔断状态。
  • 注意:设置异常比例时,时间窗口的时间应该大于或者等于60S,因为假如设置30比例,时间窗口设置为10S,那么因为默认的比例计算时间是60S统计一次,熔断降级后10S内又调用,则还是会读取到30S比例(写的什么毛线!)

热点参数限流

Github上WIKI参考文档

在需要进行限流的方法上加上注解@SentinelResource("myMethod")

注意:若需要配置例外项或者使用集群维度流控,则传入的参数只支持基本类型。

如果还是测试不好

配置文件将filter干掉

1
2
3
4
5
6
7
8
9
cloud:
#sentinel:
#transport:
#控制台与服务之间通信的端口,不写默认也会给个8719
# port: 8719
#控制台位置
#dashboard: localhost:8080
filter:
enabled: false

PS:感觉热点参数限流这里测试没成功。。。QAQ

ps:没测试成功是必然的,因为你的参数没加@RequestParam

扩展接口

Github参考文档

下表显示当应用的 ApplicationContext 中存在对应的Bean的类型时,会进行自动化设置:

存在Bean的类型 操作 作用
UrlCleaner WebCallbackManager.setUrlCleaner(urlCleaner) 资源清理(资源(比如将满足 /foo/:id 的 URL 都归到 /foo/* 资源下))
UrlBlockHandler WebCallbackManager.setUrlBlockHandler(urlBlockHandler) 自定义限流处理逻辑
RequestOriginParser WebCallbackManager.setRequestOriginParser(requestOriginParser) 设置来源信息

Spring Cloud Alibaba Sentinel 提供了这些配置选项:

配置项 含义 默认值
spring.application.name or project.name Sentinel项目名
spring.cloud.sentinel.enabled Sentinel自动化配置是否生效 true
spring.cloud.sentinel.eager 是否提前触发 Sentinel 初始化 false
spring.cloud.sentinel.transport.port 应用与Sentinel控制台交互的端口,应用本地会起一个该端口占用的HttpServer 8719
spring.cloud.sentinel.transport.dashboard Sentinel 控制台地址
spring.cloud.sentinel.transport.heartbeat-interval-ms 应用与Sentinel控制台的心跳间隔时间
spring.cloud.sentinel.transport.client-ip 此配置的客户端IP将被注册到 Sentinel Server 端
spring.cloud.sentinel.filter.order Servlet Filter的加载顺序。Starter内部会构造这个filter Integer.MIN_VALUE
spring.cloud.sentinel.filter.url-patterns 数据类型是数组。表示Servlet Filter的url pattern集合 /*
spring.cloud.sentinel.filter.enabled Enable to instance CommonFilter true
spring.cloud.sentinel.metric.charset metric文件字符集 UTF-8
spring.cloud.sentinel.metric.file-single-size Sentinel metric 单个文件的大小
spring.cloud.sentinel.metric.file-total-count Sentinel metric 总文件数量
spring.cloud.sentinel.log.dir Sentinel 日志文件所在的目录
spring.cloud.sentinel.log.switch-pid Sentinel 日志文件名是否需要带上 pid false
spring.cloud.sentinel.servlet.block-page 自定义的跳转 URL,当请求被限流时会自动跳转至设定好的 URL
spring.cloud.sentinel.flow.cold-factor WarmUp 模式中的 冷启动因子 3
spring.cloud.sentinel.zuul.order.pre SentinelZuulPreFilter 的 order 10000
spring.cloud.sentinel.zuul.order.post SentinelZuulPostFilter 的 order 1000
spring.cloud.sentinel.zuul.order.error SentinelZuulErrorFilter 的 order -1
spring.cloud.sentinel.scg.fallback.mode Spring Cloud Gateway 流控处理逻辑 (选择 redirect or response)
spring.cloud.sentinel.scg.fallback.redirect Spring Cloud Gateway 响应模式为 ‘redirect’ 模式对应的重定向 URL
spring.cloud.sentinel.scg.fallback.response-body Spring Cloud Gateway 响应模式为 ‘response’ 模式对应的响应内容
spring.cloud.sentinel.scg.fallback.response-status Spring Cloud Gateway 响应模式为 ‘response’ 模式对应的响应码 429
spring.cloud.sentinel.scg.fallback.content-type Spring Cloud Gateway 响应模式为 ‘response’ 模式对应的 content-type application/json
Note 请注意。这些配置只有在 Servlet 环境下才会生效,RestTemplate 和 Feign 针对这些配置都无法生效

实例:

修改访问URL
1
2
3
4
5
6
7
8
9
10
11
12
//可以修改访问的URL
@Component
public class MyUrlCleaner implements UrlCleaner {

@Override
public String clean(String s) {

System.out.println(s);
//修改来源的URL
return s+":admin";
}
}
自定义流控返回(加了@SentinelResource注解后,这个就不会生效)
1
2
3
4
5
6
7
8
9
10
11
@Component
public class MyUrlBlockHandler implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {

response.setContentType("application/json;charset=utf8");
response.setCharacterEncoding("utf-8");
response.getWriter().write("{\"msg\":\"流控\"}");
response.getWriter().flush();
}
}
设置来源信息

和上一样,只改变实现接口RequestOriginParser,可以通过request.getParameter得到请求携带的参数

注解支持

Github上详细参考

@SentinelResource 注解

注意:注解方式埋点不支持 private 方法。

@SentinelResource 用于定义资源,并提供可选的异常处理和 fallback 配置项。 @SentinelResource 注解包含以下属性:

  • value:资源名称,必需项(不能为空)
  • entryType:entry 类型,可选项(默认为 EntryType.OUT
  • blockHandler / blockHandlerClass: blockHandler 对应处理 BlockException 的函数名称,可选项。blockHandler 函数访问范围需要是 public,返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为 BlockException。blockHandler 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 blockHandlerClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
  • fallback / fallbackClass:fallback 函数名称,可选项,用于在抛出异常的时候提供 fallback 处理逻辑。fallback 函数可以针对所有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。fallback 函数签名和位置要求:
    • 返回值类型必须与原函数返回值类型一致;
    • 方法参数列表需要和原函数一致,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常。
    • fallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
  • defaultFallback(since 1.6.0):默认的 fallback 函数名称,可选项,通常用于通用的 fallback 逻辑(即可以用于很多服务或方法)。默认 fallback 函数可以针对所有类型的异常(除了 exceptionsToIgnore里面排除掉的异常类型)进行处理。若同时配置了 fallback 和 defaultFallback,则只有 fallback 会生效。defaultFallback 函数签名要求:
    • 返回值类型必须与原函数返回值类型一致;
    • 方法参数列表需要为空,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常。
    • defaultFallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
  • exceptionsToIgnore(since 1.6.0):用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出。

注:1.6.0 之前的版本 fallback 函数只针对降级异常(DegradeException)进行处理,不能针对业务异常进行处理

特别地,若 blockHandler 和 fallback 都进行了配置,则被限流降级而抛出 BlockException 时只会进入 blockHandler 处理逻辑。若未配置 blockHandlerfallbackdefaultFallback,则被限流降级时会将 BlockException 直接抛出(若方法本身未定义 throws BlockException 则会被 JVM 包装一层 UndeclaredThrowableException)。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class TestService {

// 对应的 `handleException` 函数需要位于 `ExceptionUtil` 类中,并且必须为 static 函数.
@SentinelResource(value = "test", blockHandler = "handleException", blockHandlerClass = {ExceptionUtil.class})
public void test() {
System.out.println("Test");
}

// 原函数
@SentinelResource(value = "hello", blockHandler = "exceptionHandler", fallback = "helloFallback")
public String hello(long s) {
return String.format("Hello at %d", s);
}

// Fallback 函数,函数签名与原函数一致或加一个 Throwable 类型的参数.
public String helloFallback(long s) {
return String.format("Halooooo %d", s);
}

// Block 异常处理函数,参数最后多一个 BlockException,其余与原函数一致.
public String exceptionHandler(long s, BlockException ex) {
// Do some log here.
ex.printStackTrace();
return "Oops, error occurred at " + s;
}
}

从 1.4.0 版本开始,注解方式定义资源支持自动统计业务异常,无需手动调用 Tracer.trace(ex) 来记录业务异常。Sentinel 1.4.0 以前的版本需要自行调用 Tracer.trace(ex) 来记录业务异常。

配置
Spring Cloud Alibaba

若您是通过 Spring Cloud Alibaba 接入的 Sentinel,则无需额外进行配置即可使用 @SentinelResource 注解。

Spring AOP

若您的应用使用了 Spring AOP(无论是 Spring Boot 还是传统 Spring 应用),您需要通过配置的方式将 SentinelResourceAspect 注册为一个 Spring Bean:

1
2
3
4
5
6
7
8
@Configuration
public class SentinelAspectConfiguration {

@Bean
public SentinelResourceAspect sentinelResourceAspect() {
return new SentinelResourceAspect();
}
}

我们提供了 Spring AOP 的示例,可以参见 sentinel-demo-annotation-spring-aop

AspectJ

若您的应用直接使用了 AspectJ,那么您需要在 aop.xml 文件中引入对应的 Aspect:

1
2
3
<aspects>
<aspect name="com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect"/>
</aspects>
Feign的支持

Sentinel 适配了 Feign 组件。如果想使用,除了引入 spring-cloud-starter-alibaba-sentinel 的依赖外还需要 2 个步骤:

  • 配置文件打开 Sentinel 对 Feign 的支持:feign.sentinel.enabled=true
  • 加入 spring-cloud-starter-openfeign 依赖使 Sentinel starter 中的自动化配置类生效:
1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

这是一个 FeignClient 的简单使用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@FeignClient(name = "service-provider", fallback = EchoServiceFallback.class, configuration = FeignConfiguration.class)
public interface EchoService {
@RequestMapping(value = "/echo/{str}", method = RequestMethod.GET)
String echo(@PathVariable("str") String str);
}

class FeignConfiguration {
@Bean
public EchoServiceFallback echoServiceFallback() {
return new EchoServiceFallback();
}
}

class EchoServiceFallback implements EchoService {
@Override
public String echo(@PathVariable("str") String str) {
return "echo fallback";
}
}
RestTemplate的支持

Spring Cloud Alibaba Sentinel 支持对 RestTemplate 的服务调用使用 Sentinel 进行保护,在构造 RestTemplate bean的时候需要加上 @SentinelRestTemplate 注解。

1
2
3
4
5
@Bean
@SentinelRestTemplate(blockHandler = "handleException", blockHandlerClass = ExceptionUtil.class)
public RestTemplate restTemplate() {
return new RestTemplate();
}

@SentinelRestTemplate 注解的属性支持限流(blockHandler, blockHandlerClass)和降级(fallback, fallbackClass)的处理。

其中 blockHandlerfallback 属性对应的方法必须是对应 blockHandlerClassfallbackClass 属性中的静态方法。

该方法的参数跟返回值跟 org.springframework.http.client.ClientHttpRequestInterceptor#interceptor 方法一致,其中参数多出了一个 BlockException 参数用于获取 Sentinel 捕获的异常。

比如上述 @SentinelRestTemplate 注解中 ExceptionUtilhandleException 属性对应的方法声明如下:

1
2
3
4
5
public class ExceptionUtil {
public static ClientHttpResponse handleException(HttpRequest request, byte[] body, ClientHttpRequestExecution execution, BlockException exception) {
...
}
}
Note 应用启动的时候会检查 @SentinelRestTemplate 注解对应的限流或降级方法是否存在,如不存在会抛出异常

@SentinelRestTemplate 注解的限流(blockHandler, blockHandlerClass)和降级(fallback, fallbackClass)属性不强制填写。

当使用 RestTemplate 调用被 Sentinel 熔断后,会返回 RestTemplate request block by sentinel 信息,或者也可以编写对应的方法自行处理返回信息。这里提供了 SentinelClientHttpResponse 用于构造返回信息。

Sentinel RestTemplate 限流的资源规则提供两种粒度:

  • httpmethod:schema://host:port/path:协议、主机、端口和路径
  • httpmethod:schema://host:port:协议、主机和端口
Note https://www.taobao.com/test 这个 url 并使用 GET 方法为例。对应的资源名有两种粒度,分别是 GET:https://www.taobao.com 以及 GET:https://www.taobao.com/test

Spring Cloud Gateway

Spring官网参考资料

概念

To include Spring Cloud Gateway in your project, use the starter with a group ID of org.springframework.cloud and an artifact ID of spring-cloud-starter-gateway. See the Spring Cloud Project page for details on setting up your build system with the current Spring Cloud Release Train.

If you include the starter, but you do not want the gateway to be enabled, set spring.cloud.gateway.enabled=false.

Spring Cloud Gateway is built on Spring Boot 2.x, Spring WebFlux, and Project Reactor. As a consequence, many of the familiar synchronous libraries (Spring Data and Spring Security, for example) and patterns you know may not apply when you use Spring Cloud Gateway. If you are unfamiliar with these projects, we suggest you begin by reading their documentation to familiarize yourself with some of the new concepts before working with Spring Cloud Gateway.
Spring Cloud Gateway requires the Netty runtime provided by Spring Boot and Spring Webflux. It does not work in a traditional Servlet Container or when built as a WAR.

大致就是如何使用,以及注意gateeay不是传统的springweb项目,所以不要导入乱七八糟的web包

使用方式

首先导包

  • 因为网关也需要注册到Nacos里面去,所以需要导入Nacos的包

    1
    2
    3
    4
    <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
  • Nacos为啥不要写版本号,因为交给dependencyManagement标签来管理了,所以需要导入

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <dependencyManagement>
    <dependencies>
    <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-alibaba-dependencies</artifactId>
    <version>2.2.1.RELEASE</version>
    <type>pom</type>
    <scope>import</scope>
    </dependency>
    </dependencies>
    </dependencyManagement>
  • 导入网关自身的包spring.cloud.gateway,然后依赖于springboot以及自身受dependencyManagement里面的rg.springframework.cloud来管理自身的版本,所以这里直接给出完整的pom配置,Jiang慢慢应该懂得

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    <?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.2.1.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.jungle.gateway</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo</name>
    <description>Demo project for Spring Boot</description>

    <properties>
    <java.version>1.8</java.version>
    </properties>

    <dependencies>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
    <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>



    </dependencies>
    <dependencyManagement>
    <dependencies>
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-dependencies</artifactId>
    <version>Hoxton.SR6</version>
    <type>pom</type>
    <scope>import</scope>
    </dependency>
    <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-alibaba-dependencies</artifactId>
    <version>2.2.1.RELEASE</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>

    • 将网关注册到Nacos Server里面,以及开启Gateway,配置如下

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22

      spring:
      cloud:
      gateway:
      #开启网关的自动路由方式,也就是通过url的服务名称去找到对应的服务
      discovery:
      locator:
      enabled: true
      nacos:
      discovery:
      # #命令空间ID
      namespace: d2f4bb90-17ac-4bc1-9e32-8734e722eb0c
      # #集群
      cluster-name: newMachine
      # group: 1
      # metadata:
      # testKey1: testValue1
      # testKey2: testValue2
      server-addr: 127.0.0.1:8848
      server:
      port: 9001

    • 启动报错 org.springframework.cloud.gateway.config.GatewayAutoConfiguration$NettyConfiguration.gatewayHttpClient(GatewayAutoConfiguration.java:622),反正是依赖有问题,直接ctrl+左键点击gateway的依赖,查看这个吊毛到底要毛线依赖,发现依赖的是2.3.0的springboot版本,改了下,ok,然后通过IP:9001/服务名/news/testTemplate测试成功

自定义配置

最有用的是谓词工厂,可以通过这个来指定什么请求什么时间段访问什么服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
server:
port: 9001
spring:
application:
name: spring-cloud-gateway
cloud:
gateway:
enabled: true
routes:
# 自定义唯一标识
- id: damkdamk54da1
# 调用自己服务可以使用:lb://服务名
# uri: http://baidu.com
uri: lb://service-news
predicates:
#谓词工厂,例如这里针对COOKIE名为mycookie,值为mycookieValue的到上面的uri中
#使用最多的是 - Path
- Path=/news/**
- id: httpbin_route
uri: https://httpbin.org
predicates:
- Path=/httpbin/**
nacos:
discovery:
# #命令空间ID
namespace: d2f4bb90-17ac-4bc1-9e32-8734e722eb0c
# #集群
cluster-name: newMachine
# group: 1
# metadata:
# testKey1: testValue1
# testKey2: testValue2
server-addr: 127.0.0.1:8848


Filter的使用

局部配置可以在配置文件中使用,也就是在routes下面增加

1
2
filters:
- RewritePath=/httpbin/(?<segment>.*), /$\{segment}

全局的写出来丢到容器中,或者在配置文件中gateway的下面增加default-filter也可以

自定义Filter

!!!注意:拦截器弄好了千万记得加@Component注解放到容器中,这是我弄完Filter后得到的经验教训

  • 可以模仿SpringCloudGateway的实现自己来写一个,例如在idea中使用ctrl+N搜索类AddRequestHeaderGatewayFilterFactory

  • 实现过程如下

  • 可以看出AddRequestHeaderGatewayFilterFactory类继承了 extends AbstractAuthenticationGatewayFilterFactory抽象类,并且重写了一个apply方法.那么可以自建一个类,保持名字的一致性,就是自定义名+GatewayFilterFactory这种格式,这种格式可以直接在配置中给每个服务局部配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    package com.jungle.gateway.demo.filter;

    import org.apache.commons.lang.StringUtils;
    import org.springframework.cloud.gateway.filter.GatewayFilter;
    import org.springframework.cloud.gateway.filter.GatewayFilterChain;
    import org.springframework.cloud.gateway.filter.factory.AddRequestHeaderGatewayFilterFactory;
    import org.springframework.cloud.gateway.support.GatewayToStringStyler;
    import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
    import org.springframework.http.server.reactive.ServerHttpRequest;
    import org.springframework.stereotype.Component;
    import org.springframework.web.server.ServerWebExchange;
    import reactor.core.publisher.Mono;

    /**
    * @author Jiangmanman
    */
    @Component
    public class AuthenticationGatewayFilterFactory extends AbstractAuthenticationGatewayFilterFactory{



    @Override
    public GatewayFilter apply(Config config) {

    return new GatewayFilter() {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    ServerHttpRequest request = exchange.getRequest();
    System.out.println("/******************进入认证过滤器**********************/");
    System.out.println("/******************取出参数"+config+"**********************/");
    String token = request.getHeaders().getFirst("token");
    System.out.println(request.getHeaders());
    if(StringUtils.isBlank(token)){
    throw new RuntimeException("没有Token");
    }


    return chain.filter(exchange);
    }

    @Override
    public String toString() {
    return GatewayToStringStyler.filterToStringCreator(AuthenticationGatewayFilterFactory.this).append(config.getName()).toString();
    }


    };
    }
    }

  • 抽象类AbstractAuthenticationGatewayFilterFactory继承了AbstractGatewayFilterFactory这个接口,并且指定了由哪个类去读取参数,shortcutFieldOrder方法主要用来控制参数的排序

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    package com.jungle.gateway.demo.filter;

    import org.springframework.cloud.gateway.filter.GatewayFilter;
    import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
    import org.springframework.cloud.gateway.filter.factory.AbstractNameValueGatewayFilterFactory;
    import org.springframework.core.style.ToStringCreator;
    import org.springframework.validation.annotation.Validated;

    import javax.validation.constraints.NotEmpty;
    import java.util.Arrays;
    import java.util.Collections;
    import java.util.List;

    /**
    * @author Jiangmanman
    */
    public abstract class AbstractAuthenticationGatewayFilterFactory extends AbstractGatewayFilterFactory<AbstractAuthenticationGatewayFilterFactory.Config> {


    public AbstractAuthenticationGatewayFilterFactory() {
    super(AbstractAuthenticationGatewayFilterFactory.Config.class);
    }

    @Override
    public List<String> shortcutFieldOrder() {
    return Collections.singletonList("name");
    }

    @Validated
    public static class Config {
    @NotEmpty
    protected String name;


    public String getName() {
    return name;
    }

    public void setName(String name) {
    this.name = name;
    }

    @Override
    public String toString() {
    return "NameValueConfig{" +
    "name='" + name + '\'' +
    '}';
    }
    public String getValue() {
    return this.name;
    }


    }

    }

最后在在配置上写上服务对应的过滤器名即可,如下所示,增加的就是最后一行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
spring:
application:
name: spring-cloud-gateway
cloud:
gateway:
enabled: true
routes:
# 自定义唯一标识
- id: damkdamk54da1
# 调用自己服务可以使用:lb://服务名
# uri: http://baidu.com
uri: lb://service-news
predicates:
#谓词工厂,例如这里针对COOKIE名为mycookie,值为mycookieValue的到上面的uri中
#使用最多的是 - Path
- Path=/news/**
filters:
- Authentication=test

上面的自定义Filter主要用来做服务的局部配置,对于服务的全局配置(拦截所有服务)代码实现如下

1
2
3
4
5
6
7
8
9
10
@Component
public class MyglobalFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

System.out.println("=========全局拦截===========");

return chain.filter(exchange);
}
}
过滤器顺序

spring官方网站7.1里就有实现方法,就是通过实现Ordered接口再重写getOrder方法,给一个值,越小的值,优先级越高,越大的值优先级越低

hen a request matches a route, the filtering web handler adds all instances of GlobalFilter and all route-specific instances of GatewayFilter to a filter chain. This combined filter chain is sorted by the org.springframework.core.Ordered interface, which you can set by implementing the getOrder() method.

As Spring Cloud Gateway distinguishes between “pre” and “post” phases for filter logic execution (see How it Works), the filter with the highest precedence is the first in the “pre”-phase and the last in the “post”-phase.

The following listing configures a filter chain:

Example 59. ExampleConfiguration.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Bean
public GlobalFilter customFilter() {
return new CustomGlobalFilter();
}

public class CustomGlobalFilter implements GlobalFilter, Ordered {

@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("custom global filter");
return chain.filter(exchange);
}

@Override
public int getOrder() {
return -1;
}
}

为了测试这个改变Filter拦截的顺序,我又把Filter提取出去了,不然怎么实现Ordered接口呢…,如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class MyServiceFilter  implements GatewayFilter, Ordered {

private AbstractAuthenticationGatewayFilterFactory.Config config;

public MyServiceFilter() {
}

public MyServiceFilter(AbstractAuthenticationGatewayFilterFactory.Config config) {
this.config = config;
}

@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
System.out.println("/******************进入认证过滤器**********************/");
System.out.println("/******************取出参数"+config+"**********************/");
String token = request.getHeaders().getFirst("token");
System.out.println(request.getHeaders());
if(StringUtils.isBlank(token)){
throw new RuntimeException("没有Token");
}


return chain.filter(exchange);
}

@Override
public String toString() {
return GatewayToStringStyler.filterToStringCreator(MyServiceFilter.this).append(config.getName()).toString();
}

@Override
public int getOrder() {
return 1;
}
}
1
2
3
4
5
6
7
8
9
@Component
public class AuthenticationGatewayFilterFactory extends AbstractAuthenticationGatewayFilterFactory{

@Override
public GatewayFilter apply(Config config) {

return new MyServiceFilter();
}
}
filter在请求从服务返回后进行处理

参考资料17.2

在上面MyServiceFilter类里的filter方法

1
2
3
4
return chain.filter(exchange).then(Mono.fromRunnable(()->{
// 请求-》过滤器-》服务-》过滤器(现在在这里)-》返回请求
//例如可以计算这次服务的响应时间
}));
Http超时配置

参考资料12

Http timeouts (response and connect) can be configured for all routes and overridden for each specific route.

Global timeouts

To configure Global http timeouts:
connect-timeout must be specified in milliseconds.
response-timeout must be specified as a java.time.Duration

global http timeouts example

1
2
3
4
5
6
spring:
cloud:
gateway:
httpclient:
connect-timeout: 1000
response-timeout: 5s
Per-route timeouts

To configure per-route timeouts:
connect-timeout must be specified in milliseconds.
response-timeout must be specified in milliseconds.

per-route http timeouts configuration via configuration

1
2
3
4
5
6
7
8
9
- id: per_route_timeouts
uri: https://example.org
predicates:
- name: Path
args:
pattern: /delay/{timeout}
metadata:
response-timeout: 200
connect-timeout: 200

跨域问题

在实际项目中,一般通过Nginx做反向代理,不做概述

spring官网资料

参考资料

You can configure the gateway to control CORS behavior. The “global” CORS configuration is a map of URL patterns to Spring Framework CorsConfiguration. The following example configures CORS:

Example 73. application.yml

1
2
3
4
5
6
7
8
9
spring:
cloud:
gateway:
globalcors:
cors-configurations:
'[/**]':
allowedOrigins: "https://docs.spring.io"
allowedMethods:
- GET

In the preceding example, CORS requests are allowed from requests that originate from docs.spring.io for all GET requested paths.

To provide the same CORS configuration to requests that are not handled by some gateway route predicate, set the spring.cloud.gateway.globalcors.add-to-simple-url-handler-mapping property to true. This is useful when you try to support CORS preflight requests and your route predicate does not evalute to true because the HTTP method is options.

我是这样配置的

1
2
3
4
5
6
7
8
9
spring:
cloud:
gateway:
globalcors:
cors-configurations:
'[/**]':
allowedOrigins: "http://127.0.0.1:8020,localhost:8020"
allowedMethods: "*"
allowedHeaders: "*"

异常处理

因为网关不是传统的Web项目,所以不应该引入web的包

代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
package com.jungle.gateway.demo.handler;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import dto.JungleExceptionCode;
import dto.RusultError;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
import org.springframework.core.annotation.Order;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import type.AuthenticationJungleException;
import type.JungleException;

/**
* 全局错误处理程序
*
* 在网关层的处理(写在网关层)
*
* @author Jiangmanman
* @date 2020/08/15
*/
@Component
@Order(-1)
@Slf4j
public class GlobalErrorHandler implements ErrorWebExceptionHandler {

private ObjectMapper objectMapper = new ObjectMapper();

@Override
public Mono<Void> handle(ServerWebExchange serverWebExchange, Throwable throwable) {

RusultError rusultError = null;

//自定义异常
if(throwable instanceof AuthenticationJungleException){

AuthenticationJungleException e = (AuthenticationJungleException)throwable;

log.error("/**************出现异常************\n" +
"code:\n{},message:\n{},class:\n{}" +
"**********************************/",
JungleExceptionCode.SYS_ERROR.getCode(),
e.getMessage(),
e.getClass().getName()
);
rusultError = RusultError.errorAJE(e);
}else{
//全局异常
log.error("/**************出现异常************\n" +
"code:\n{},message:\n{},class:\n{}" +
"**********************************/",
JungleExceptionCode.SYS_ERROR.getCode(),
throwable.getMessage(),
throwable.getClass().getName()
);
rusultError = RusultError.builder().code(JungleExceptionCode.SYS_ERROR.getCode()).message(throwable.getMessage()).build();
}


RusultError re = rusultError;

//设置响应格式
serverWebExchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_PROBLEM_JSON);

//响应
return serverWebExchange.getResponse().writeWith(Mono.fromSupplier(()->{

//将对象转未Byte数组
byte[] myByte=null;

try {
myByte = objectMapper.writeValueAsBytes(re);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return serverWebExchange.getResponse().bufferFactory().wrap(myByte);
}));

}
}

服务间调用传参问题

Feign方式解决

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* @author Jiangmanman
* @date 2020/08/13
*/
public class SecuringRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder
.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
Enumeration<String> headerNames = request.getHeaderNames();
if (headerNames != null) {
while (headerNames.hasMoreElements()) {
String name = headerNames.nextElement();
String values = request.getHeader(name);
requestTemplate.header(name, values);
}
}
}
}

上面的可以加 @Component注解,直接放入容器中,也可以在配置文件中指定,就不要加注解了,如下所示

1
2
3
4
5
6
7
8
9
10
11
12
feign:
client:
config:
#也可以针对某个包进行配置
default:
connectTimeout: 5000
readTimeout: 5000
#日志级别:NONE(默认),BASIC,HEADERS,FULL
loggerLevel: FULL
#interceptor
request-interceptors:
- com.jungle.servicenews.Interceptor.SecuringRequestInterceptor

RestTemplate方式解决

  • 定义Intercepter

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public class SecuringRequestInterceptor implements RequestInterceptor {
    @Override
    public void apply(RequestTemplate requestTemplate) {
    ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder
    .getRequestAttributes();
    HttpServletRequest request = attributes.getRequest();
    Enumeration<String> headerNames = request.getHeaderNames();
    if (headerNames != null) {
    while (headerNames.hasMoreElements()) {
    String name = headerNames.nextElement();
    String values = request.getHeader(name);
    requestTemplate.header(name, values);
    }
    }
    }
    }
  • 在resttemplate的配置上加上interceptor

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    @Configuration
    @RibbonClients(defaultConfiguration = RibbonConfig.class)
    public class BaseConfig {

    @Bean
    @LoadBalanced
    public RestTemplate getRestTemplate(){
    RestTemplate restTemplate = new RestTemplate();
    //这里就是将interceptor放入resttemplate中
    restTemplate.setInterceptors(Arrays.asList(new TokenRestTemplateInterceptor()));
    return restTemplate;
    }

    }
  • 注意要使用resttemplate方式去访问接口,如下所示

    1
    CommonResult forObject = restTemplate.getForObject("http://(服务名||IP+端口)/user/all", CommonResult.class);

Zipkin

官网地址

数据一般存在ElasticSearch

返回优化

在全局异常处理处返回ResponseEntity<主题类名,例如我喜欢用CommonResult>

new ResponseEntiry<ResultError(返回内容,HttpsStatus.NOT_FUND)>

项目文件

项目文件:链接: https://pan.baidu.com/s/1N4eMWDfjzQZwoWwusYwoZA 提取码: at59

将配置文件中的数据库连接去掉即可运行,加上Nacos Server可正常注册,加上Sentinel可正常进行服务流控和降级

参考链接

参考链接: