開始
文檔目的
原來想通過整合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服務,目錄如下:
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5yM1ImZ2ATZ3YTY0gDOhZGMycjM1UGZ1EzM4YGNzADOw8CX0JXZ252bj91Ztl2Lc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
- 配置端口和資料庫連接配接
### 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
為nacos時候,就是把sentinel下流控配置存儲到nacos, 這裡采用服務名加datasource
的字尾,對應于後面-sentinel-flow
源碼的修改sentinel-dashboard
- 在網關啟動時候,加入參數
,告訴sentinel這是一個網關類型,-Dcsp.sentinel.app.type=1
控制台如下顯示就對了:sentinel-bashboard
兩個微服務開發,分别為demo和demo1, 這裡就不用多講了,見源碼。
sentinel-dashboard改造
主要就是建立、修改和删除流控規則時候,調用nacos提供的接口,将相關流控資訊推送給nacos, 儲存到mysql.我參考了下面的這個文章,在他提供的源碼上面進行了修改,實作了我的目的,非常感謝!
https://blog.csdn.net/q669239799/article/details/125777745
- 參考上面的文章,下載下傳文章中提供的
源碼。在sentinel-bashboard
中修改端口和增加nacos配置application.properties
server.port=8800
nacos.address=172.20.11.5:8840
nacos.namespace=middle-dev
nacos.group=dev
- 通過debug發現,最後其實是調用
類的GatewayFlowRuleController
、addFlowRule
和updateFlowRule
對sentinel記憶體中的流控規則進行新增、修改和删除的。是以在對記憶體中的流控規則操作後,再調用nacos的接口,更改nacos裡面的流控規則,達到儲存到nacos的目的。這裡以修改流控規則為例子:deleteFlowRule
- 修改
, 增加對于NacosConfig.java
的轉換BeanGatewayFlowRuleEntity
- 修改
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,這才是正确的步驟。