开始
文档目的
原来想通过整合Sentinel,对spring cloud gateway请求进行流控;在Sentinel界面中修改和增加流控规则,同步到nacos。
百度有很多文章,但是实践下来没有一个能够实现我想要的结果,于是决定在前人的基础上研究,终于初步达成了目的。由于本人水平有限,有些概念没有深入了解,请见谅!
版本信息
name | version | desc |
---|---|---|
spring boot | 2.7.3 | |
spring cloud | 2021.0.3 | |
io.springfox | 3.0.0 | |
knife4j | 3.0.2 | |
com.alibaba.cloud | 2021.1 | |
nacos | 2.1.0 | cluster mode |
sentinel | 1.8.4 | |
mysql | 5.7.22 |
整合
nacos 集群模式配置(一台机器)
-
配置3个nacos服务,目录如下:

-
配置端口和数据库连接
### nacos1
### Default web server port:
server.port=8840
### Connect URL of DB:
db.url.0=jdbc:mysql://127.0.0.1:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
db.user.0=XXXX
db.password.0=XXXXXX
同样,将nacos2的server.port
改为8850;nacos3的server.port
改为8860
-
nacos1、nacos2、nacos3三个实例的 cluster.conf
修改
#2022-09-19T08:54:21.232
172.20.11.5:8840
172.20.11.5:8850
172.20.11.5:8860
注意:集群模式下一定要配置为真实的ip加端口,不能用127.0.0.1或者localhost代替;后面为spring cloud gateway
配置nacos时候,一定要写真实的ip,否则会报以下的错误:
com.alibaba.nacos.api.exception.NacosException: failed to req API:/nacos/v1/ns/instance
after all servers([http://127.0.0.1:8848])
由于机器资源限制,实际我只启动了nacos1一个实例。
spring cloud gateway网关搭建
server:
port: 9000
spring:
application:
name: gateway-server
main:
allow-bean-definition-overriding: true
config:
activate:
on-profile: ${SPRING_PROFILES_ACTIVE:dev}
cloud:
nacos:
config:
server-addr: ${NACOS_SERVER_ADDR:172.20.11.5}:8840 # 配置中心真实ip
file-extension: yaml
namespace: middle-${SPRING_PROFILES_ACTIVE:dev}
group: ${SPRING_PROFILES_ACTIVE:dev}
refresh-enabled: true
discovery:
server-addr: ${NACOS_SERVER_ADDR:172.20.11.5}:8840 #服务注册和发现中心,和配置中心使用同一个变量
#集群模式:localhost:8848,localhost:8849,localhost:8850
namespace: middle-${SPRING_PROFILES_ACTIVE:dev}
group: ${SPRING_PROFILES_ACTIVE:dev}
gateway:
globalcors:
cors-configurations: #允许跨域请求
'[**]':
allowedOrigins: '*'
allowedMethods: '*'
discovery:
locator:
enabled: true
sentinel: # sentinel 整合
transport:
dashboard: ${SENTINEL_DASHBOARD_ADDR:172.20.11.5}:8800 #sentinel控制台访问路径
filter:
enabled: false #心跳启动
datasource:
ds:
nacos: # 整合nacos,把流控规则保存到nacos, sentinel控制台启动时候就能够读取这个配置
server-addr: ${NACOS_SERVER_ADDR:172.20.11.5}:8840
dataId: ${spring.application.name}-sentinel-flow # 为什么会这样设置,后面会讲到
namespace: middle-${SPRING_PROFILES_ACTIVE:dev}
groupId: ${SPRING_PROFILES_ACTIVE:dev}
rule-type: gw_flow # 流控配置
eager: true
management:
endpoints:
web:
exposure:
include: '*' #shutdown,health,info,loggers,gateway,sentinel
endpoint:
shutdown:
enabled: true
health: # 适用于k8s LivenessProbe(健康检查) 和 ReadinessProbe(是否已经准备好接受请求)
show-details: always #不显示详细信息
上述配置要注意几点:
-
nacos只启动了一个点,所以只配置一个节点 -
nacos既作为注册中心又作为配置中心 -
在 sentinel
下面配置datasource
为nacos时候,就是把sentinel下流控配置存储到nacos, 这里采用服务名加-sentinel-flow
的后缀,对应于后面sentinel-dashboard
源码的修改 -
在网关启动时候,加入参数 -Dcsp.sentinel.app.type=1
,告诉sentinel这是一个网关类型,sentinel-bashboard
控制台如下显示就对了:

两个微服务开发,分别为demo和demo1, 这里就不用多讲了,见源码。
sentinel-dashboard改造
主要就是新建、修改和删除流控规则时候,调用nacos提供的接口,将相关流控信息推送给nacos, 保存到mysql.我参考了下面的这个文章,在他提供的源码上面进行了修改,实现了我的目的,非常感谢!
https://blog.csdn.net/q669239799/article/details/125777745
-
参考上面的文章,下载文章中提供的 sentinel-bashboard
源码。在application.properties
中修改端口和增加nacos配置
server.port=8800
nacos.address=172.20.11.5:8840
nacos.namespace=middle-dev
nacos.group=dev
-
通过debug发现,最后其实是调用 GatewayFlowRuleController
类的addFlowRule
、updateFlowRule
和deleteFlowRule
对sentinel内存中的流控规则进行新增、修改和删除的。所以在对内存中的流控规则操作后,再调用nacos的接口,更改nacos里面的流控规则,达到保存到nacos的目的。这里以修改流控规则为例子: -
修改 NacosConfig.java
, 增加对于GatewayFlowRuleEntity
的转换Bean
package com.alibaba.csp.sentinel.dashboard.config.nacos;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.GatewayFlowRuleEntity;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.DegradeRuleEntity;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity;
import com.alibaba.csp.sentinel.datasource.Converter;
import com.alibaba.fastjson.JSON;
import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.api.config.ConfigFactory;
import com.alibaba.nacos.api.config.ConfigService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.List;
import java.util.Properties;
/**
* @author Eric Zhao
* @since 1.4.0
*/
@Configuration
public class NacosConfig {
@Value("${nacos.address}")
private String addr; //Nacos地址,对应于上面的设置
@Value("${nacos.namespace}")
private String namespace; //Nacos命名空间,对应于上面的设置
@Bean
public Converter<List<DegradeRuleEntity>, String> degradeRuleEntityEncoder() {
return JSON::toJSONString;
}
@Bean
public Converter<String, List<DegradeRuleEntity>> degradeRuleEntityDecoder() {
return s -> JSON.parseArray(s, DegradeRuleEntity.class);
}
// 添加对于GatewayFlowRuleEntity的转换
@Bean
public Converter<List<GatewayFlowRuleEntity>, String> gatewayFlowRuleEntityEncoder() {
return JSON::toJSONString;
}
@Bean
public Converter<String, List<GatewayFlowRuleEntity>> gatewayFlowRuleEntityDecoder() {
return s -> JSON.parseArray(s, GatewayFlowRuleEntity.class);
}
// end
@Bean
public Converter<List<FlowRuleEntity>, String> flowRuleEntityEncoder() {
return JSON::toJSONString;
}
@Bean
public Converter<String, List<FlowRuleEntity>> flowRuleEntityDecoder() {
return s -> JSON.parseArray(s, FlowRuleEntity.class);
}
@Bean
public ConfigService nacosConfigService() throws Exception {
Properties properties = new Properties();
//nacos集群地址
properties.put(PropertyKeyConst.SERVER_ADDR,addr);
//namespace为空即为public
properties.put(PropertyKeyConst.NAMESPACE,namespace);
return ConfigFactory.createConfigService(properties);
}
}
-
新增 GatewayFlowRuleNacosPublisher.java
package com.alibaba.csp.sentinel.dashboard.config.flow;
import com.alibaba.csp.sentinel.dashboard.config.nacos.NacosConfigUtil;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.GatewayFlowRuleEntity;
import com.alibaba.csp.sentinel.dashboard.rule.DynamicRulePublisher;
import com.alibaba.csp.sentinel.datasource.Converter;
import com.alibaba.csp.sentinel.util.AssertUtil;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.ConfigType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.List;
// 注意,不一样的名称
@Component("gatewayFlowRuleNacosPublisher")
public class GatewayFlowRuleNacosPublisher implements DynamicRulePublisher<List<GatewayFlowRuleEntity>> {
@Autowired
private ConfigService configService;
@Value("${nacos.group}") // 对应于application.properties里面的配置
private String group;
// 使用到上面定义的Converter
@Autowired
private Converter<List<GatewayFlowRuleEntity>, String> converter;
@Override
public void publish(String app, List<GatewayFlowRuleEntity> rules) throws Exception {
AssertUtil.notEmpty(app, "app name cannot be empty");
if (rules == null) {
return;
}
// 通过app服务名+后缀: 还记得我们前面说的,后缀是:-sentinel-flow, 这样就能找到nacos里面的配置信息
// 如果没有设置group ,缺省就是DEFAULT_GROUP
// 这儿,实际上是调用NacosConfigService的功能对nacos进行操作
configService.publishConfig(app + NacosConfigUtil.FLOW_DATA_ID_POSTFIX,
group == null ? NacosConfigUtil.GROUP_ID : group, converter.convert(rules), ConfigType.JSON.getType());
}
}
-
为 GatewayFlowRuleController
添加注入刚才创建的类的实例
@Autowired
@Qualifier("gatewayFlowRuleNacosPublisher")
private DynamicRulePublisher<List<GatewayFlowRuleEntity>> rulePublisher;
-
为 GatewayFlowRuleController
添加函数
private void publishRules(String appName) {
List<GatewayFlowRuleEntity> rules = repository.findAllByApp(appName);
try {
rulePublisher.publish(appName, rules);
} catch (Exception e) {
logger.warn("public gateway flow rules to nacos fail");
}
}
-
在 GatewayFlowRuleController
的updateFlowRule
函数最后,添加publishRules
调用
// 原有代码,发送流控规则更新客户端,比如这里是我的网关服务
if (!publishRules(app, entity.getIp(), entity.getPort())) {
logger.warn("publish gateway flow rules fail after update");
} else {
// 客户端成功,更新保存到nacos持久化
publishRules(entity.getApp());
}
-
到这儿修改流控规则持久化到nacos就完了,新增和删除按照上面的思路改一下就行。 -
对sentinel-bashboard进行打包,然后运行进行测试。
事情还没有完…
实际在使用的时候,在sentinel更改流控规则进行更新时候,我的网关服务会出现NullPointerException
的错误,导致sentinel-bashboard
更新流控不成功,通过网关程序日志发现,问题出在阿里提供的GatewayRuleManager
这个类的applyToConvertedParamMap
函数里面:(GatewayRuleManager
在阿里提供的sentinel适配于gateway的包中:sentinel-api-gateway-adapter-common-1.8.0.jar
)
ParameterMetricStorage.getParamMetricForResource(resource).clearForRule(rule);
问题出在这一行上面,ParameterMetricStorage.getParamMetricForResource(resource)
获取为null, 所以报NullPointerExceptin
; 大致看了一下,是获取网关相关指标的函数,这一块也没有精力研究,直接改为:
ParameterMetric parameterMetric = ParameterMetricStorage.getParamMetricForResource(resource);
if (parameterMetric != null) {
parameterMetric.clearForRule(rule);
}
这儿没有完全深入研究这样写的影响,反正从代码上看是没有错的,以后有时间再深入评估。到此为止,代码已经能够顺利运行了。
最后…
实际上,我的操作顺序是这样的
1、在nacos写一个流控的json格式配置,主要他的data-id为:gateway-server-sentinel-flow
(网关服务名+后缀)
[{
"resourceMode": 0,
"resource": "demo1",
"grade": 1,
"count": 3,
"intervalSec": 1,
"controlBehavior": 0,
"burst": 0
},{
"resourceMode": 0,
"resource": "demo",
"grade": 1,
"count": 2,
"intervalSec": 1,
"controlBehavior": 0,
"burst": 0
}]
2、启动sentinel-bashboard
(自己打的jar)
3、启动网关程序,可以在sentinel-bashboard
控制台上面能够看见流控规则:注意,我涂掉的黑色部分一开始是没有的。

所以无论我怎么设置,从网关调用后台服务从来不会阻塞;后来才发现是resource
不对,应该在原来的resource前面加上ReactiveCompositeDiscoveryClient_
:
[{
"resourceMode": 0,
"resource": "ReactiveCompositeDiscoveryClient_demo1",
"grade": 1,
"count": 3,
"intervalSec": 1,
"controlBehavior": 0,
"burst": 0
},{
"resourceMode": 0,
"resource": "ReactiveCompositeDiscoveryClient_demo",
"grade": 1,
"count": 2,
"intervalSec": 1,
"controlBehavior": 0,
"burst": 0
}]
这样,网关调用时候就能准确的进行流控了。
注:我利用http://localhost:9000/actuator/gateway/routes查看网关的路由信息,得到:

这个带前缀的route_id
实际就是在sentinelroute
控制台看见的请求链路上的route_id
(见下图), 所以上面的资源应该加上ReactiveCompositeDiscoveryClient_
前缀。再查,这个前缀实际为DiscoveryLocatorProperties
里面的routeIdPrefix
属性,缺省为ReactiveCompositeDiscoveryClient_
, 在网关服务的配置文件里面可以配置(最后一行):
spring:
cloud:
gateway:
globalcors:
cors-configurations: #允许跨域请求
'[**]':
allowedOrigins: '*'
allowedMethods: '*'
discovery:
locator:
enabled: true
route-id-prefix: sentinel # 前缀
4、实际上,应该先从网关调用,在sentinel控制面板的请求链路上面显示:

应该从这儿添加流控规则最好,然后同步到网关服务和持久化到nacos,这才是正确的步骤。
推荐
PS:因为公众号平台更改了推送规则,如果不想错过内容,记得读完点一下“在看”,加个“星标”,这样每次新文章推送才会第一时间出现在你的订阅列表里。点“在看”支持我们吧!
原文始发于微信公众号(Java知音):如何实现 SpringCloud Gateway 整合 Sentinel 流控规则,并持久化到 Nacos
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/51595.html