源码在最后
一、安装Nacos注册中心
1.1查看Nacos官网,安装Nacos服务,下载源码或者安装包
1.2启动服务,默认端口为8848,
二、创建服务注册&发现
2.1使用脚手架,创建注册服务和发现服务项目,我用的版本是2.6.13,
2.2增加启动类配置,用于支持注册服务和发现服务和Feign请求等
2.2.1注册服务增加配置类注解
2.2.2发现服务配置,激活Feign
2.2.3创建包nacosdiscoveryprovider,包下创建接口EchoServiceController作为注册服务接口, 用于后续Feign请求测试,启动服务
2.2.4创建发现服务普通RestTemplate接口,测试通过RestTemplate请求是否可以转发到注册服务端
2.2.5创建Feign接口
三、创建网关服务
3.1 脚手架创建网关服务
3.2启动类增加注解
3.3创建Controller接口测试服务是否可以正常访问
4.网关服务作为统一入口,需要配置断言Predicate机制拦截,一个请求在抵达网关层后,首先就要进行断言匹配,在满足所有断言之后才会进入Filter阶段转发至发现服务nacos-discovery-provider-sample,断言配置采用Nacos中心动态配置。
4.1在Nacos中心创建配置
4.2 网关服务application.properties增加配置获取Nacos中心的配置
4.3动态刷新中心配置
4.4增加配置类事件推送类,动态更新配置DynamicRouteServiceImpl
4.5项目创建DynamicRouteServiceImplNacos类,用于接收动态刷新。
4.5启动网关服务,Postman调用网关
5.网关配置JTWtoken鉴权
5.1自定义过滤器鉴权规则
5.2修改Nacos中心nacos-gateway-sample.json断言配置,增加过滤器
5.3增加模拟登录接口获取token
四、为服务增加sentinel限流熔断配置
4.1下载按照运行sentinel
4.2网关服务配置sentinel
4.3配置限流规则
4.3.1选择API管理,新增API规则
4.3.2新增网关流控规则
4.3.3配置熔断规则
4.3.4配置限流和熔断统一返回值,此例是返回值,
五、限流熔断配置Nacos持久化
5.1Nacos控制台新建一个限流和熔断配置
5.1.1限流配置:
5.1.1熔断配置
5.2项目配置文件增加nacos的数据源配置
5.3创建config类,初始化加载配置参数属性
5.4创建限流熔断工厂类,加载sentinel相关内容。加载限流、熔断规则,自定义限流熔断错误返回处理,
5.5 Postman测试
5.5.1限流生效返回
5.5.2熔断生效返回
六 结束语
一、安装Nacos注册中心
1.1查看Nacos官网,安装Nacos服务,下载源码或者安装包
Nacos 快速开始
1.2启动服务,默认端口为8848,
进入文件夹,cd nacos/bin
以单机模式启动 服务
sh startup.sh -m standalone
登录注册中心查看服务是否正常
二、创建
服务注册&发现
2.1使用脚手架,创建注册服务和发现服务项目,我用的版本是2.6.13,
项目名称nacos-discovery-provider-sample
脚手架创建的服务包含注册和发现
Cloud Native App Initializer (aliyun.com)
选择组件
项目pom完整内容
<?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>
<groupId>com.alibaba.cloud</groupId>
<artifactId>nacos-discovery-provider-sample</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>nacos-discovery-provider-sample</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.6.13</spring-boot.version>
<spring-cloud-alibaba.version>2021.0.5.0</spring-cloud-alibaba.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
<version>3.1.5</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>3.1.5</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<configuration>
<mainClass>com.alibaba.cloud.nacosdiscoveryprovidersample.NacosDiscoveryProviderSampleApplication</mainClass>
<skip>true</skip>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
application.properties内容
默认服务名
spring.application.name=nacos-discovery-provider-sample
# Nacos帮助文档: https://nacos.io/zh-cn/docs/concepts.html
spring.application.name=nacos-discovery-provider-sample
# Nacos认证信息
spring.cloud.nacos.discovery.username=nacos
spring.cloud.nacos.discovery.password=nacos
# Nacos 服务发现与注册配置,其中子属性 server-addr 指定 Nacos 服务器主机和端口
spring.cloud.nacos.discovery.server-addr=192.168.100.115:8848
# 注册到 nacos 的指定 namespace,默认为 public
spring.cloud.nacos.discovery.namespace=23963657-2b3b-49db-be69-f2517dc3695c
#ribbon.nacos.enabled=true
server.port=8082
2.2增加启动类配置,用于支持注册服务和发现服务和Feign请求等
2.2.1注册服务增加配置类注解
@EnableDiscoveryClient 开启服务提供者或消费者,客户端的支持,用来注册服务
@EnableFeignClients 开启Feign功能
配置类注解@Configuration
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.nacosdiscoveryprovidersample.nacosdiscovery;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Configuration;
/**
* @author <a href="mailto:chenxilzx1@gmail.com">theonefx</a>
*/
@EnableDiscoveryClient
@Configuration
@EnableFeignClients
public class NacosDiscoveryConfiguration {
}
2.2.2发现服务配置,激活Feign
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.nacosdiscoveryprovidersample.demos.nacosdiscoveryconsumer;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Configuration;
@EnableFeignClients // 激活 @FeignClient
@Configuration
public class NacosDiscoveryConsumerConfiguration {
}
2.2.3创建包nacosdiscoveryprovider,包下创建接口EchoServiceController作为注册服务接口, 用于后续Feign请求测试,启动服务
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.nacosdiscoveryprovidersample.demos.nacosdiscoveryprovider;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class EchoServiceController {
@GetMapping("/echo/{message}")
public String echo(@PathVariable String message) throws InterruptedException {
System.out.println(message);
return "[ECHO] : " + message;
}
@GetMapping("/test")
public String test() {
return "[ECHO] : " +"test";
}
}
2.2.4创建发现服务普通RestTemplate接口,测试通过RestTemplate请求是否可以转发到注册服务端
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.nacosdiscoveryprovidersample.demos.nacosdiscoveryconsumer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Lazy;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.concurrent.Delayed;
@RestController
public class RestTemplateController {
@LoadBalanced
@Lazy
@Autowired
public RestTemplate restTemplate;
@LoadBalanced
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
@GetMapping("/call/echo/{message}")
public String callEcho(@PathVariable String message) throws InterruptedException {
// 访问应用 nacos-discovery-provider-sample 的 EchoServiceController 中的/echo/{message}
return restTemplate.getForObject("http://nacos-discovery-provider-sample/echo/" + message, String.class);
}
}
Postman 调用本服务接口 /call/echo/{message},正常访问到接口之后,RestTemplate通过路由
http://nacos-discovery-provider-sample/echo/ 路由前缀为注册服务名称 转发的上述
nacosdiscoveryprovider包下接口EchoServiceController。
2.2.5创建Feign接口
创建发现服务Feign 接口,用于接收Feign请求转发只服务端,接口增加注解
@FeignClient("nacos-discovery-provider-sample") 指向服务提供者应用
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.nacosdiscoveryprovidersample.demos.nacosdiscoveryconsumer;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@FeignClient("nacos-discovery-provider-sample") // 指向服务提供者应用
public interface EchoService {
@GetMapping("/echo/{message}")
String echo(@PathVariable("message") String message);
}
创建FeignController 测试Feign请求
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.nacosdiscoveryprovidersample.demos.nacosdiscoveryconsumer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class OpenFeignController {
@Autowired
private EchoService echoService;
@GetMapping("/feign/echo/{message}")
public String feignEcho(@PathVariable String message) throws InterruptedException {
return echoService.echo(message);
}
}
通过Postman 请求接口/feign/echo/{message}
服务根据EchoService 中的FeignClient注解,请求会转发至nacos-discovery-provider-sample服务的/echo/{message}接口
至此,Nacos中心搭建完成,注册服务和发现服务注册成功,并且服务支持Fdign请求。
三、创建网关服务
3.1 脚手架创建网关服务
Cloud Native App Initializer (aliyun.com)
项目pom完整内容
<?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>
<groupId>com.alibaba.cloud</groupId>
<artifactId>nacos-gateway-sample</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>nacos-gateway-sample</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.6.13</spring-boot.version>
<spring-cloud-alibaba.version>2021.0.5.0</spring-cloud-alibaba.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>
<version>3.1.5</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gateway-server</artifactId>
<version>3.1.5</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
<version>3.1.5</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.4</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.76</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.3.10</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gateway-server</artifactId>
<version>3.1.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-crypto</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<configuration>
<mainClass>com.alibaba.cloud.nacosgatewaysample.NacosGatewaySampleApplication</mainClass>
<skip>true</skip>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
application.properties
# Nacos帮助文档: https://nacos.io/zh-cn/docs/concepts.html
spring.application.name=nacos-gateway-sample
server.port=9080
# Nacos认证信息
spring.cloud.nacos.discovery.username=nacos
spring.cloud.nacos.discovery.password=nacos
## 整合sentinel,配置sentinel控制台的地址## 指定控制台的地址,默认端口8080
spring.cloud.sentinel.enabled=true
spring.main.allow-bean-definition-overriding: true
# Nacos 服务发现与注册配置,其中子属性 server-addr 指定 Nacos 服务器主机和端口
spring.cloud.nacos.discovery.server-addr=192.168.100.115:8848
# 注册到 nacos 的指定 namespace,默认为 public
spring.cloud.nacos.discovery.namespace=23963657-2b3b-49db-be69-f2517dc3695c
#sentinel控制台
spring.cloud.sentinel.transport.dashboard=192.168.100.115:9090
##限流或者熔断返回
## 一种是redirect,重定向跳转,需要同时配置redirect(跳转的uri)
spring.cloud.sentinel.scg.fallback.mode=response
## 响应的状态
spring.cloud.sentinel.scg.fallback.response-status=200
## 响应体
spring.cloud.sentinel.scg.fallback.response-body='{"code": 200,"message": "限流/熔断,稍后重试!"}'
nacos.gateway.route.config.data-id=nacos-gateway-sample.json
nacos.gateway.route.config.group=DEFAULT_GROUP
murg.gateway.jwt.enabled=true
murg.gateway.jwt.secret=fjkfaf;afa
murg.gateway.jwt.header=Authorization
murg.gateway.jwt.expiration=3000000000000000
murg.gateway.jwt.userParamName=username
murg.gateway.jwt.pwdParamName=password
3.2启动类增加注解@EnableDiscoveryClient
启动服务,登录Nacos注册中心,查看网关服务是否正常注册。
3.3创建Controller接口测试服务是否可以正常访问
JwtAuthController
/**
* 测试
*/
@RequestMapping("/aaa/test")
public String testaaa(@RequestBody Map<String,String> map) throws InterruptedException {
Thread.sleep(3000);
return "aaaaaa";
}
Postman测试请求/aaa/test
正常返回数据。
4.网关服务作为统一入口,需要配置断言Predicate机制拦截,一个请求在抵达网关层后,首先就要进行断言匹配,在满足所有断言之后才会进入Filter阶段转发至发现服务nacos-discovery-provider-sample,断言配置采用Nacos中心动态配置。
4.1在Nacos中心创建配置
本例采用Json格式,配置详情如下
[
{
"id":"nacos-gateway-sample",
"order":0,
"predicates":[
{
"args":{
"pattern":"/nacos-discovery-provider-sample/**" //路由匹配内容
},
"name":"Path" //通过路由方式拦截
}
],
"uri":"lb://nacos-discovery-provider-sample",//匹配成功转发到的服务地址
}
]
注:中心配置内不能有注释,在中心把注释删除
4.2 网关服务application.properties增加配置获取Nacos中心的配置
nacos.gateway.route.config.data-id=nacos-gateway-sample.json nacos.gateway.route.config.group=DEFAULT_GROUP
启动网关服务,会自动加载Nacos中心中的网关断言配置
4.3动态刷新中心配置
Nacos中心的断言配置如有变化,为了不重新服务,可以创建动态监听配置类,动态刷新
首先需要创建一个网报配置类GatewayConfig,读取Nacos相关访问配置,为配置监听做准备
package com.alibaba.cloud.nacosgatewaysample.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/**
* 配置类:读取Nacos相关的配置项,用于配置监听器
*/
@SuppressWarnings("all")
@Configuration
public class GatewayConfig {
/**
* 读取配置的超时时间
*/
public static final long DEFAULT_TIMEOUT = 30000;
/**
* Nacos 服务器地址
*/
public static String NACOS_SERVER_ADDR;
/**
* Nacos 命名空间
*/
public static String NACOS_NAMESPACE;
/**
* nacos 配置列表中的dataid
*/
public static String NACOS_ROUTE_DATE_ID;
/**
* nacos分组id
*/
public static String NACOS_GROUP_ID;
@Value("${spring.cloud.nacos.discovery.server-addr}")
public void setNacosServerAddr(String nacosServerAddr) {
NACOS_SERVER_ADDR = nacosServerAddr;
}
@Value("${spring.cloud.nacos.discovery.namespace}")
public void setNacosNamespace(String nacosNamespace) {
NACOS_NAMESPACE = nacosNamespace;
}
@Value("${nacos.gateway.route.config.data-id}")
public void setNacosRouteDateId(String nacosRouteDateId) {
NACOS_ROUTE_DATE_ID = nacosRouteDateId;
}
@Value("${nacos.gateway.route.config.group}")
public void setNacosGroupId(String nacosGroupId) {
NACOS_GROUP_ID = nacosGroupId;
}
}
4.4增加配置类事件推送类,动态更新配置DynamicRouteServiceImpl
package com.alibaba.cloud.nacosgatewaysample.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionLocator;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import reactor.core.publisher.Mono;
import java.util.List;
/**
* 事件推送 Aware: 动态更新路由网关 Service
* */
@Slf4j
@Service
@SuppressWarnings("all")
public class DynamicRouteServiceImpl implements ApplicationEventPublisherAware {
/** 写路由定义 */
private final RouteDefinitionWriter routeDefinitionWriter;
/** 获取路由定义 */
private final RouteDefinitionLocator routeDefinitionLocator;
/** 事件发布 */
private ApplicationEventPublisher publisher;
public DynamicRouteServiceImpl(RouteDefinitionWriter routeDefinitionWriter,
RouteDefinitionLocator routeDefinitionLocator) {
this.routeDefinitionWriter = routeDefinitionWriter;
this.routeDefinitionLocator = routeDefinitionLocator;
}
@Override
public void setApplicationEventPublisher(
ApplicationEventPublisher applicationEventPublisher) {
// 完成事件推送句柄的初始化
this.publisher = applicationEventPublisher;
}
/**
* 从nacos读取路由配hi,写道gateway中
* <h2>增加路由定义</h2>
* */
public String addRouteDefinition(RouteDefinition definition) {
log.info("gateway add route: [{}]", definition);
// 保存路由配置并发布
routeDefinitionWriter.save(Mono.just(definition)).subscribe();
// 发布事件通知给 Gateway, 同步新增的路由定义
this.publisher.publishEvent(new RefreshRoutesEvent(this));
return "success";
}
/**
* <h2>更新路由</h2>
* */
public String updateList(List<RouteDefinition> definitions) {
log.info("更新网关路由: [{}]", definitions);
// 先拿到当前 Gateway 中存储的路由定义
List<RouteDefinition> routeDefinitionsExits =
routeDefinitionLocator.getRouteDefinitions().buffer().blockFirst();
if (!CollectionUtils.isEmpty(routeDefinitionsExits)) {
// 清除掉之前所有的 "旧的" 路由定义
routeDefinitionsExits.forEach(rd -> {
deleteById(rd.getId());
log.info("清除掉之前所有的 旧的 路由定义: [{}]", rd);
});
}
// 把更新的路由定义同步到 gateway 中
definitions.forEach(definition -> updateByRouteDefinition(definition));
return "success";
}
/**
* <h2>根据路由 id 删除路由配置</h2>
* */
private String deleteById(String id) {
try {
log.info("要删除的路由id: [{}]", id);
this.routeDefinitionWriter.delete(Mono.just(id)).subscribe();
// 发布事件通知给 gateway 更新路由定义
this.publisher.publishEvent(new RefreshRoutesEvent(this));
return "删除成功";
} catch (Exception ex) {
log.error("删除网关路由失败: [{}]", ex.getMessage(), ex);
return "删除失败";
}
}
/**
* <h2>更新路由</h2>
* 更新的实现策略比较简单: 删除 + 新增 = 更新
* */
private String updateByRouteDefinition(RouteDefinition definition) {
try {
log.info("更新网关路由: [{}]", definition);
this.routeDefinitionWriter.delete(Mono.just(definition.getId()));
} catch (Exception ex) {
return "更新失败,没有查到更新的网关路由id: " + definition.getId();
}
try {
this.routeDefinitionWriter.save(Mono.just(definition)).subscribe();
this.publisher.publishEvent(new RefreshRoutesEvent(this));
return "成功";
} catch (Exception ex) {
return "更新路由失败!";
}
}
}
4.5项目创建DynamicRouteServiceImplNacos类,用于接收动态刷新。
package com.alibaba.cloud.nacosgatewaysample.config;
import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.Listener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.Executor;
/**
* 通过Nacos下发的动态配置,监听nacos中路由配置
*/
@Slf4j
@Component
//再另外一个Bean初始化之后再去初始化当前类
@DependsOn({"gatewayConfig"})
@SuppressWarnings("all")
public class DynamicRouteServiceImplNacos {
private ConfigService configService;
private final DynamicRouteServiceImpl dynamicRouteService;
public DynamicRouteServiceImplNacos(DynamicRouteServiceImpl dynamicRouteService){
this.dynamicRouteService = dynamicRouteService;
}
/**
* 初始化ConfigService
*
* @return
*/
private ConfigService initConfigService() {
try {
Properties properties = new Properties();
properties.setProperty("serverAddr", GatewayConfig.NACOS_SERVER_ADDR);
properties.setProperty("namespace", GatewayConfig.NACOS_NAMESPACE);
return configService = NacosFactory.createConfigService(properties);
} catch (Exception ex) {
log.error("初始化配置服务:[{}]", ex.getMessage());
return null;
}
}
/**
* bean在容器中构造完成之后会立即执行当前的init方法
* 加载路由信息,注册监听器
*/
@PostConstruct
public void init(){
log.info("网关路由初始化...");
try {
//初始化nacos配置客户端
configService = initConfigService();
if(configService==null){
log.error("初始化配置服务异常,配置服务是null!");
return;
}
//通过 nacos config 并指定路由配置路径去获取路由配置
String configInfo = configService.getConfig(GatewayConfig.NACOS_ROUTE_DATE_ID,
GatewayConfig.NACOS_GROUP_ID,
GatewayConfig.DEFAULT_TIMEOUT);
log.info("当前网关配置信息:[{}]:",configInfo);
//反序列化
List<RouteDefinition> routeDefinitions = JSON.parseArray(configInfo, RouteDefinition.class);
if(CollectionUtil.isNotEmpty(routeDefinitions)){
//新增
for (RouteDefinition routeDefinition : routeDefinitions) {
log.info("初始化 路由定义对象 信息:[{}]:",routeDefinition);
dynamicRouteService.addRouteDefinition(routeDefinition);
}
}
} catch (Exception e) {
log.error("网关路由初始化失败:[{}]",e.getMessage());
}
//设置监听器
dynamicRouteServiceImplNacosByListener(GatewayConfig.NACOS_ROUTE_DATE_ID, GatewayConfig.NACOS_GROUP_ID);
}
/**
* 监听 Nacos下发的动态路由配置
*
* @param dataId
* @param group
*/
private void dynamicRouteServiceImplNacosByListener(String dataId, String group) {
try {
//给Nacos config 客户端增加一个监听器
configService.addListener(dataId, group, new Listener() {
/**
* 自己提供线程池执行操作
* @return
*/
@Override
public Executor getExecutor() {
return null;
}
/**
* 监听器收到接收到配置变更信息
* @param configInfo nacos 中最新配置信息
*/
@Override
public void receiveConfigInfo(String configInfo) {
log.info("接收的配置信息:[{}]", configInfo);
List<RouteDefinition> routeDefinitions = JSON.parseArray(configInfo, RouteDefinition.class);
dynamicRouteService.updateList(routeDefinitions);
log.info("更新后的路由配置信息:[{}]", routeDefinitions.toString());
}
});
} catch (Exception e) {
log.error("监听 Nacos下发的动态路由配置异常:[{}]", e.getMessage());
}
}
}
Nacos中心配置变更发布之后,监听更新,调用DynamicRouteServiceImpl updateList 实现更新配置
4.5启动网关服务,Postman调用网关
路由/nacos-discovery-provider-sample/call/echo/nnn
根据断言配置的 "pattern":"/nacos-discovery-provider-sample/**" 匹配到前缀,网关自动转发至
发现服务nacos-discovery-provider-sample的/call/echo/nnn接口,控制台输出nnn
5.网关配置JTWtoken鉴权
为网关配置鉴权过滤器
pom引入以下配置
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-crypto</artifactId>
</dependency>
5.1自定义过滤器鉴权规则
创建JwtProperties类
package com.alibaba.cloud.nacosgatewaysample.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Data
@ConfigurationProperties(prefix = "murg.gateway.jwt")
@Component
public class JwtProperties {
//是否开启JWT,即注入相关的类对象
private Boolean enabled;
//JWT密钥
private String secret;
//JWT有效时间
private Long expiration;
//前端向后端传递JWT时使用HTTP的header名称,前后端要统一
private String header;
//用户登录-用户名参数名称
private String userParamName = "username";
//用户登录-密码参数名称
private String pwdParamName = "password";
}
application.properties增加 JWT配置
murg.gateway.jwt.enabled=true murg.gateway.jwt.secret=fjkfaf;afa murg.gateway.jwt.header=Authorization murg.gateway.jwt.expiration=3000000000000000 murg.gateway.jwt.userParamName=username murg.gateway.jwt.pwdParamName=password 创建JTWUtile,鉴权逻辑
package com.alibaba.cloud.nacosgatewaysample.config;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@Component
public class JwtTokenUtil {
@Resource
private JwtProperties jwtProperties;
/**
* 生成token令牌
*
* @param userId 用户Id或用户名
* @param payloads 令牌中携带的附加信息
* @return 令token牌
*/
public String generateToken(String userId,
Map<String,String> payloads) {
int payloadSizes = payloads == null? 0 : payloads.size();
Map<String, Object> claims = new HashMap<>(payloadSizes + 2);
claims.put("sub", userId);
claims.put("created", new Date());
if(payloadSizes > 0){
for(Map.Entry<String,String> entry:payloads.entrySet()){
claims.put(entry.getKey(),entry.getValue());
}
}
return generateToken(claims);
}
/**
* 从令牌中获取用户名
*
* @param token 令牌
* @return 用户名
*/
public String getUsernameFromToken(String token) {
String username;
try {
Claims claims = getClaimsFromToken(token);
username = claims.getSubject();
} catch (Exception e) {
username = null;
}
return username;
}
/**
* 判断令牌是否过期
*
* @param token 令牌
* @return 是否过期
*/
public Boolean isTokenExpired(String token) {
try {
Claims claims = getClaimsFromToken(token);
Date expiration = claims.getExpiration();
return expiration.before(new Date());
} catch (Exception e) {
//验证JWT签名失败等同于令牌过期
return true;
}
}
/**
* 刷新令牌
*
* @param token 原令牌
* @return 新令牌
*/
public String refreshToken(String token) {
String refreshedToken;
try {
Claims claims = getClaimsFromToken(token);
claims.put("created", new Date());
refreshedToken = generateToken(claims);
} catch (Exception e) {
refreshedToken = null;
}
return refreshedToken;
}
/**
* 验证令牌
*
* @param token 令牌
* @param userId 用户Id用户名
* @return 是否有效
*/
public Boolean validateToken(String token, String userId) {
String username = getUsernameFromToken(token);
return (username.equals(userId) && !isTokenExpired(token));
}
/**
* 从claims生成令牌,如果看不懂就看谁调用它
*
* @param claims 数据声明
* @return 令牌
*/
private String generateToken(Map<String, Object> claims) {
Date expirationDate = new Date(System.currentTimeMillis() + jwtProperties.getExpiration());
return Jwts.builder().setClaims(claims)
.setExpiration(expirationDate)
.signWith(SignatureAlgorithm.HS512, jwtProperties.getSecret())
.compact();
}
/**
* 从令牌中获取数据声明,验证JWT签名
*
* @param token 令牌
* @return 数据声明
*/
private Claims getClaimsFromToken(String token) {
Claims claims;
try {
claims = Jwts.parser().setSigningKey(jwtProperties.getSecret()).parseClaimsJws(token).getBody();
} catch (Exception e) {
claims = null;
}
return claims;
}
}
创建JWTAuthCheckFilter自定义过滤规则实现JTW鉴权,@Order注解配置自动注入顺序后注入,防止先加载。
/authentication模拟登录注册接口,此接口放行。
package com.alibaba.cloud.nacosgatewaysample.Filter;
import com.alibaba.cloud.nacosgatewaysample.config.JwtProperties;
import com.alibaba.cloud.nacosgatewaysample.config.JwtTokenUtil;
import com.alibaba.fastjson.JSON;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
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.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
@Configuration
@Order(-101)
public class JWTAuthCheckFilter implements GatewayFilter, Ordered {
@Resource
private JwtProperties jwtProperties;
@Resource
private JwtTokenUtil jwtTokenUtil;
//将JWT鉴权失败的消息响应给客户端
private Mono<Void> writeUnAuthorizedMessageAsJson(ServerHttpResponse serverHttpResponse, String message) {
serverHttpResponse.setStatusCode(HttpStatus.UNAUTHORIZED);
Map reuslt=new HashMap();
reuslt. put("code","401");
reuslt. put("msg","鉴权失败");
serverHttpResponse.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
DataBuffer dataBuffer = serverHttpResponse.bufferFactory()
.wrap(JSON.toJSONStringWithDateFormat(reuslt,JSON.DEFFAULT_DATE_FORMAT)
.getBytes(StandardCharsets.UTF_8));
return serverHttpResponse.writeWith(Flux.just(dataBuffer));
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest serverHttpRequest = exchange.getRequest();
ServerHttpResponse serverHttpResponse = exchange.getResponse();
String requestUrl = serverHttpRequest.getURI().getPath();
if(!requestUrl.equals("/authentication")){
//从HTTP请求头中获取JWT令牌
String jwtToken = serverHttpRequest
.getHeaders()
.getFirst(jwtProperties.getHeader());
//对Token解签名,并验证Token是否过期
boolean isJwtNotValid = jwtTokenUtil.isTokenExpired(jwtToken);
if(isJwtNotValid){ //如果JWT令牌不合法
return writeUnAuthorizedMessageAsJson(serverHttpResponse,"请先去登录,再访问服务!");
}
//从JWT中解析出当前用户的身份(userId),并继续执行过滤器链,转发请求
ServerHttpRequest mutableReq = serverHttpRequest
.mutate()
.header("userId", jwtTokenUtil.getUsernameFromToken(jwtToken))
.build();
ServerWebExchange mutableExchange = exchange.mutate().request(mutableReq).build();
return chain.filter(mutableExchange);
}else{ //如果是登录认证请求,直接执行不需要进行JWT权限验证
return chain.filter(exchange);
}
}
@Override
public int getOrder() {
return 101;
}
}
自定义过滤器工厂,为网关加载过滤器使用
package com.alibaba.cloud.nacosgatewaysample.Filter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.stereotype.Component;
@Component
public class JWTAuthCheckFilterFactory extends AbstractGatewayFilterFactory<Object> {
@Autowired
JWTAuthCheckFilter jWTAuthCheckFilte;
@Override
public GatewayFilter apply(Object config) {
return jWTAuthCheckFilte;
}
}
5.2修改Nacos中心nacos-gateway-sample.json断言配置,增加过滤器
[
{
"id":"nacos-gateway-sample",
"order":0,
"predicates":[
{
"args":{
"pattern":"/nacos-discovery-provider-sample/**"
},
"name":"Path"
}
],
"uri":"lb://nacos-discovery-provider-sample",
"filters":[
{
"name":"JWTAuthCheckFilterFactory"
},
{
"name":"StripPrefix",
"args":{
"parts":"1"
}
}
]
}
]
filters的name指定为自定义工厂JWTAuthCheckFilterFactory。
至此鉴权流程完成。
5.3增加模拟登录接口获取token
/**
* 使用用户名密码换JWT令牌
*/
@RequestMapping("/authentication")
public Mono<Map> authentication(@RequestBody Map<String,String> map){
//从请求体中获取用户名密码
String username = map.get(jwtProperties.getUserParamName());
String password = map.get(jwtProperties.getPwdParamName());
if(StringUtils.isEmpty(username)
|| StringUtils.isEmpty(password)){
return buildErrorResponse("用户名或者密码不能为空");
}
//根据用户名(用户Id)去数据库查找该用户
SysUser sysUser =new SysUser();
sysUser.setPassword("123456");
if(sysUser != null){
//将数据库的加密密码与用户明文密码match
// boolean isAuthenticated = passwordEncoder.matches("123456",sysUser.getPassword());
boolean isAuthenticated="123456".equals(sysUser.getPassword());
if(isAuthenticated){ //如果匹配成功
//通过jwtTokenUtil生成JWT令牌并return
return buildSuccessResponse(jwtTokenUtil.generateToken(username,null));
} else{ //如果密码匹配失败
return buildErrorResponse("请确定您输入的用户名或密码是否正确!");
}
}else{
return buildErrorResponse("请确定您输入的用户名或密码是否正确!");
}
}
返回TOKEN之后,测试请求网关/nacos-discovery-provider-sample/call/echo/nnn
请求Header中带入参数
Authorization:eyJhbGciOiJIUzUxMiJ9.eyJleHAiOjMwMDE3MDk3Nzc5NjIsInN1YiI6ImFkbWluIiwiY3JlYXRlZCI6MTcwOTc3Nzk2MjQ1Nn0.3pw5NVIhiMggFvPEDvUHGX_Iy8EyLT0EQGQS7Xf3bAiu5GhAFHvtSd2_3nsOMzjtAbYT2sk2ffx122cLBXQzPQ
接口正常。去掉Authorization,接口返回
{
"msg": "鉴权失败",
"code": "401"
}
四、为服务增加sentinel限流熔断配置
4.1下载按照运行sentinel
下载Sentinel: 官网阿里 Sentinelsentinel
Sentinel: 官网阿里 Sentinel
启动sentinel指定管理界面端口为9092
nohup java -Dserver.port=9090 -Dcsp.sentinel.dashboard.server=localhost:9092 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.7.jar &
登录管理端 用户名密码默认 sentinel/sentinel
4.2网关服务配置sentinel
application.properties增加 ## 整合sentinel,配置sentinel控制台的地址## 指定控制台的地址,默认端口8080 spring.cloud.sentinel.enabled=true
#sentinel控制台 spring.cloud.sentinel.transport.dashboard=192.168.100.115:9090
重启服务,登录sentinel管理端 右侧不显示网关服务
idea增加启动参数
Dcsp.sentinel.dashboard.server=192.168.100.115:9090 -Dproject.name=nacos-natewayn-ample -Dcsp.sentinel.api.port=9090 -Dcsp.sentinel.app.type=1
或者启动类增加
重新启动显示正常
正常压测
4.3配置限流规则
本例以路由前缀限流
4.3.1选择API管理,新增API规则
新增API分组
匹配串 /nacos-discovery-provider-sample/**
4.3.2新增网关流控规则
限流配置完成
压测截图
4.3.3配置熔断规则
修改限流规则qps1000,停掉发现服务nacos-discovery-provider-sample测试
返回429 熔断生效
4.3.4配置限流和熔断统一返回值,此例是返回值,
一种是redirect,重定向跳转,需要同时配置redirect(跳转的uri)
application.properties
spring.cloud.sentinel.scg.fallback.mode=response ## 响应的状态 spring.cloud.sentinel.scg.fallback.response-status=200 ## 响应体 spring.cloud.sentinel.scg.fallback.response-body="{\"code\":\"429\",\"msg\":\"请求太多了\"}" 重新启动服务nacos-discovery-provider-sample测试限流返回
返回正常200数据。
五、限流熔断配置Nacos持久化
限流和熔断配置在网关服务重启之后会失效,在sentinel中会消失,改为Nacos持久化配置
引入依赖
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
5.1Nacos控制台新建一个限流和熔断配置
5.1.1限流配置:
nacos-discovery-provider-sample-flow的json配置,内容如下:
[
{
"resource": "nacos-gateway-sample",
"controlBehavior": 0,
"count": 100,
"grade": 1,
"limitApp": "default",
"strategy": 0
}
]
resource:资源名称;和网关的配置nacos-gateway-sample.json id保持一致,指明来源
nacos-gateway-sample
limitApp:来源应用;
grade:阈值类型,0表示线程数,1表示QPS
count:单机阈值;
strategy:流控模式,0表示直接,1表示关联,2表示链路;
controlBehavior:流控效果,0表示快速失败,1表示Warm Up,2表示排队等待;
clusterMode:是否集群。
5.1.1熔断配置
nacos-discovery-provider-sample-degrade
5.2项目配置文件增加nacos的数据源配置
## 整合sentinel,配置sentinel控制台的地址## 指定控制台的地址,默认端口8080 spring.cloud.sentinel.enabled=true #sentinel控制台 spring.cloud.sentinel.transport.dashboard=192.168.100.115:9090 #指定限流的配置名字 sentinel.config.nacos.data-id-flow=nacos-discovery-provider-sample-flow #指定熔断的配置名字 sentinel.config.nacos.data-id-degrade=nacos-discovery-provider-sample-degrade #指定默认分组 sentinel.config.nacos.group=DEFAULT_GROUP
5.3创建config类,初始化加载配置参数属性
package com.alibaba.cloud.nacosgatewaysample.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
@SuppressWarnings("all")
@Configuration
public class SentinelConfig {
/**
* Nacos 服务器地址
*/
public static String NACOS_SERVER_ADDR;
/**
* Nacos 命名空间
*/
public static String NACOS_SENTINEL_NAMESPACE;
/**
* Nacos 分组id
*/
public static String NACOS_SENTINEL_GROUP;
/**
* nacos 限流配置列表中的dataid
*/
public static String NACOS_SENTINEL_DATAID_FLOW;
/**
* nacos 熔断配置列表中的dataid
*/
public static String NACOS_SENTINEL_DATAID_DEGRADE;
@Value("${spring.cloud.nacos.discovery.server-addr}")
public void setNacosServerAddr(String nacosServerAddr) {
NACOS_SERVER_ADDR = nacosServerAddr;
}
@Value("${spring.cloud.nacos.discovery.namespace}")
public void setNacosSentinelNamespace(String nacosSentinelNamespace) {
NACOS_SENTINEL_NAMESPACE = nacosSentinelNamespace;
}
@Value("${sentinel.config.nacos.group}")
public void setNacosSentinelGroup(String nacosSentinelGroup) {
NACOS_SENTINEL_GROUP = nacosSentinelGroup;
}
@Value("${sentinel.config.nacos.data-id-flow}")
public void setNacosSentinelDataidFlow(String nacosSentinelDataidFlow) {
NACOS_SENTINEL_DATAID_FLOW = nacosSentinelDataidFlow;
}
@Value("${sentinel.config.nacos.data-id-degrade}")
public void setNacosSentinelDataidDegrade(String nacosSentinelDataidDegrade) {
NACOS_SENTINEL_DATAID_DEGRADE = nacosSentinelDataidDegrade;
}
}
5.4创建限流熔断工厂类,加载sentinel相关内容。加载限流、熔断规则,自定义限流熔断错误返回处理,
限流自定义返回状态码429
熔断自定义返回状态码504
package com.alibaba.cloud.nacosgatewaysample.Filter;
import com.alibaba.cloud.nacosgatewaysample.config.SentinelConfig;
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.exception.SentinelGatewayBlockExceptionHandler;
import com.alibaba.csp.sentinel.datasource.ReadableDataSource;
import com.alibaba.csp.sentinel.datasource.nacos.NacosDataSource;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.alibaba.nacos.api.PropertyKeyConst;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.DependsOn;
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.stereotype.Component;
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.*;
@Component
//再另外一个Bean初始化之后再去初始化当前类
@DependsOn({"sentinelConfig"})
//再另外一个Bean初始化之后再去初始化当前类
public class SentinelFactory implements BlockRequestHandler {
private List<ViewResolver> viewResolvers;
private ServerCodecConfigurer serverCodecConfigurer;
public SentinelFactory(ObjectProvider<List<ViewResolver>> viewResolversProvider,
ServerCodecConfigurer serverCodecConfigurer) {
//视图解析
this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
this.serverCodecConfigurer = serverCodecConfigurer;
Properties properties = new Properties();
properties.put(PropertyKeyConst.SERVER_ADDR, SentinelConfig.NACOS_SERVER_ADDR);
properties.put(PropertyKeyConst.NAMESPACE, SentinelConfig.NACOS_SENTINEL_NAMESPACE);
//加载nacos配置中心的限流规则
ReadableDataSource<String, List<FlowRule>> flowRuleDataSource = new NacosDataSource<>
(properties, SentinelConfig.NACOS_SENTINEL_GROUP, SentinelConfig.NACOS_SENTINEL_DATAID_FLOW,
source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() {
}));
FlowRuleManager.register2Property(flowRuleDataSource.getProperty());
//加载nacos配置中心的熔断规则
ReadableDataSource<String, List<DegradeRule>> degradeRuleDataSource = new NacosDataSource<>
(properties, SentinelConfig.NACOS_SENTINEL_GROUP, SentinelConfig.NACOS_SENTINEL_DATAID_DEGRADE,
source -> JSON.parseObject(source, new TypeReference<List<DegradeRule>>() {
}));
DegradeRuleManager.register2Property(degradeRuleDataSource.getProperty());
}
//异常处理接收Handler
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
}
//这是执行sentinel规则的拦截器,Gateway路由之前会先执行这个拦截器
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public GlobalFilter sentinelGatewayFilter() {
return new SentinelGatewayFilter();
}
@Override
public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
String path = serverWebExchange.getRequest().getPath().pathWithinApplication().value();
Map map = new HashMap();
map.put("error","Unauthorized");
map.put("path",path);
map.put("status",HttpStatus.TOO_MANY_REQUESTS.value());
map.put("imestamp",new Date());
if (throwable instanceof FlowException){
map.put("message","服务被限流");
return ServerResponse.status(HttpStatus.TOO_MANY_REQUESTS.value()).contentType(MediaType.APPLICATION_JSON).body(Mono.just(map), Map.class);
} else if (throwable instanceof DegradeException) {
map.put("message","服务被熔断降级");
return ServerResponse.status(HttpStatus.GATEWAY_TIMEOUT.value()).contentType(MediaType.APPLICATION_JSON).body(Mono.just(map), Map.class);
}
return ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR.value()).contentType(MediaType.APPLICATION_JSON).body(Mono.just(map), Map.class);
}
}
5.5 Postman测试
重启服务调用接口/nacos-discovery-provider-sample/call/echo/nnn测试
5.5.1限流生效返回
5.5.2熔断生效返回
PS:按照4.3动态刷新配置 创建监听实现动态加载,由于太懒没弄,感兴趣的同学可以自行加一下。
六 结束语
以上就是基于Nacos中心部署spring cloud的基础内容,包含JTW鉴权,网关、feign、sentinel限流熔断等基本内容,自学一周大概了解一下内容。
Nacos 文档地址
Nacos官方文档地址:Nacos 快速开始
github下载地址:Releases · alibaba/nacos · GitHub
gitee下载地址: Nacos 发行版 - Gitee.com
sentinel 文档地址:
Read Me - 《Sentinel v1.8 文档手册》 - 书栈网 · BookStackRead Me - 《Sentinel v1.8 文档手册》 - 书栈网 · BookStackRead Me - 《Sentinel v1.8 文档手册》 - 书栈网 · BookStack
脚手架地址
Cloud Native App Initializer (aliyun.com)
源码下载连接
https://download.csdn.net/download/weixin_41647530/88969539
祝福语:
同窗共学,亦为知己。让我们携手共进,相互勉励,成为更好的自己。