SpringCloudGateway集成Sentinel并持久化到Redis
Sentinel 是什么?
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
Sentinel 具有以下特征:
丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。
完备的实时监控:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。
广泛的开源生态:Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。
完善的 SPI 扩展点:Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。
规则配置
一、从 Sentinel 1.4.0 开始,sentinel抽取出了接口用于向远程配置中心推送规则以及拉取规则:
DynamicRuleProvider<T>: 拉取规则
DynamicRulePublisher<T>: 推送规则
用户只需实现 DynamicRuleProvider 和 DynamicRulePublisher 接口,即可实现应用维度推送
改造前原理分析:
客户端利用sentinel-transport-simple-http模块暴露一个特定的端口,sentinel-dashboard项目通过http的形式进行数据推送,客户端接收后将规则保存在本地内存中,如下图
改造后原理解析:
sentinel-dashboard项目控制台将配置信息推送到配置中心,如nacos、zookeeper、apollo中,由配置中心去进行配置推送,更新配置到客户端本地内存中
二、官方提供了 Nacos、Apollo 、zookeeper的推送和拉取规则实现示例(位于 test 目录下)
三、官方没有给出redis持久化规则demo,下面我们自己改造,本文demo采用Sentinel2.0.0版本
注意:由于redis没有控制台,所以当客户端挂掉重启后,内存中的限流、降级等配置信息丢失,sentinel-dashboard项目需要通过按钮,定时任务等重新推送配置信息给客户端,或者客户端自己拉取规则。
1、sentinel-dashboard项目引入redis依赖
<!--redis持久化 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <version>2.5.12</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> <version>2.6.2</version> </dependency> 2、改造controller
核心代码:
@Autowired @Qualifier("gatewayApiRedisProvider") private DynamicRuleProvider<List<ApiDefinitionEntity>> ruleProvider; @Autowired @Qualifier("gatewayApiRedisPublisher") private DynamicRulePublisher<List<ApiDefinitionEntity>> rulePublisher;
@Autowired @Qualifier("gatewayFlowRuleRedisProvider") private DynamicRuleProvider<List<GatewayFlowRuleEntity>> ruleProvider; @Autowired @Qualifier("gatewayFlowRuleRedisPublisher") private DynamicRulePublisher<List<GatewayFlowRuleEntity>> rulePublisher;
完整代码如下:
GatewayApiController_Makr代码
package com.alibaba.csp.sentinel.dashboard.controller.gateway; import com.alibaba.csp.sentinel.dashboard.auth.AuthAction; import com.alibaba.csp.sentinel.dashboard.auth.AuthService; import com.alibaba.csp.sentinel.dashboard.client.SentinelApiClient; import com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.ApiDefinitionEntity; import com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.ApiPredicateItemEntity; import com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.GatewayFlowRuleEntity; import com.alibaba.csp.sentinel.dashboard.discovery.MachineInfo; import com.alibaba.csp.sentinel.dashboard.domain.Result; import com.alibaba.csp.sentinel.dashboard.domain.vo.gateway.api.AddApiReqVo; import com.alibaba.csp.sentinel.dashboard.domain.vo.gateway.api.ApiPredicateItemVo; import com.alibaba.csp.sentinel.dashboard.domain.vo.gateway.api.UpdateApiReqVo; import com.alibaba.csp.sentinel.dashboard.repository.gateway.InMemApiDefinitionStore; import com.alibaba.csp.sentinel.dashboard.rule.DynamicRuleProvider; import com.alibaba.csp.sentinel.dashboard.rule.DynamicRulePublisher; import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.fastjson.JSON; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.util.CollectionUtils; import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletRequest; import java.util.*; import static com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants.*; /** * @className: GatewayApiController_Makr * @description: Gateway api管理 * @author: wuhk * @date: 2023/9/18 20:41 * @version: 1.0 * @company 自主研发 **/ @RestController @RequestMapping(value = "/gateway/api") public class GatewayApiController_Makr { private final Logger logger = LoggerFactory.getLogger(GatewayApiController_Makr.class); @Autowired private InMemApiDefinitionStore repository; @Autowired @Qualifier("gatewayApiRedisProvider") private DynamicRuleProvider<List<ApiDefinitionEntity>> ruleProvider; @Autowired @Qualifier("gatewayApiRedisPublisher") private DynamicRulePublisher<List<ApiDefinitionEntity>> rulePublisher; @Autowired private SentinelApiClient sentinelApiClient; @GetMapping("/list.json") @AuthAction(AuthService.PrivilegeType.READ_RULE) public Result<List<ApiDefinitionEntity>> queryApis(String app, String ip, Integer port) { if (StringUtil.isEmpty(app)) { return Result.ofFail(-1, "app can't be null or empty"); } if (StringUtil.isEmpty(ip)) { return Result.ofFail(-1, "ip can't be null or empty"); } if (port == null) { return Result.ofFail(-1, "port can't be null"); } try { /*List<ApiDefinitionEntity> apis = sentinelApiClient.fetchApis(app, ip, port).get(); repository.saveAll(apis); return Result.ofSuccess(apis);*/ List<ApiDefinitionEntity> apis = ruleProvider.getRules(app); apis = repository.saveAll(apis); return Result.ofSuccess(apis); } catch (Throwable throwable) { logger.error("queryApis error:", throwable); return Result.ofThrowable(-1, throwable); } } @PostMapping("/new.json") @AuthAction(AuthService.PrivilegeType.WRITE_RULE) public Result<ApiDefinitionEntity> addApi(HttpServletRequest request, @RequestBody AddApiReqVo reqVo) { String app = reqVo.getApp(); if (StringUtil.isBlank(app)) { return Result.ofFail(-1, "app can't be null or empty"); } ApiDefinitionEntity entity = new ApiDefinitionEntity(); entity.setApp(app.trim()); String ip = reqVo.getIp(); if (StringUtil.isBlank(ip)) { return Result.ofFail(-1, "ip can't be null or empty"); } entity.setIp(ip.trim()); Integer port = reqVo.getPort(); if (port == null) { return Result.ofFail(-1, "port can't be null"); } entity.setPort(port); // API名称 String apiName = reqVo.getApiName(); if (StringUtil.isBlank(apiName)) { return Result.ofFail(-1, "apiName can't be null or empty"); } entity.setApiName(apiName.trim()); // 匹配规则列表 List<ApiPredicateItemVo> predicateItems = reqVo.getPredicateItems(); if (CollectionUtils.isEmpty(predicateItems)) { return Result.ofFail(-1, "predicateItems can't empty"); } List<ApiPredicateItemEntity> predicateItemEntities = new ArrayList<>(); for (ApiPredicateItemVo predicateItem : predicateItems) { ApiPredicateItemEntity predicateItemEntity = new ApiPredicateItemEntity(); // 匹配模式 Integer matchStrategy = predicateItem.getMatchStrategy(); if (!Arrays.asList(URL_MATCH_STRATEGY_EXACT, URL_MATCH_STRATEGY_PREFIX, URL_MATCH_STRATEGY_REGEX).contains(matchStrategy)) { return Result.ofFail(-1, "invalid matchStrategy: " + matchStrategy); } predicateItemEntity.setMatchStrategy(matchStrategy); // 匹配串 String pattern = predicateItem.getPattern(); if (StringUtil.isBlank(pattern)) { return Result.ofFail(-1, "pattern can't be null or empty"); } predicateItemEntity.setPattern(pattern); predicateItemEntities.add(predicateItemEntity); } entity.setPredicateItems(new LinkedHashSet<>(predicateItemEntities)); // 检查API名称不能重复 List<ApiDefinitionEntity> allApis = repository.findAllByMachine(MachineInfo.of(app.trim(), ip.trim(), port)); if (allApis.stream().map(o -> o.getApiName()).anyMatch(o -> o.equals(apiName.trim()))) { return Result.ofFail(-1, "apiName exists: " + apiName); } Date date = new Date(); entity.setGmtCreate(date); entity.setGmtModified(date); try { entity = repository.save(entity); } catch (Throwable throwable) { logger.error("add gateway api error:", throwable); return Result.ofThrowable(-1, throwable); } if (!publishApis(app, ip, port)) { logger.warn("publish gateway apis fail after add"); } return Result.ofSuccess(entity); } @PostMapping("/save.json") @AuthAction(AuthService.PrivilegeType.WRITE_RULE) public Result<ApiDefinitionEntity> updateApi(@RequestBody UpdateApiReqVo reqVo) { String app = reqVo.getApp(); if (StringUtil.isBlank(app)) { return Result.ofFail(-1, "app can't be null or empty"); } Long id = reqVo.getId(); if (id == null) { return Result.ofFail(-1, "id can't be null"); } ApiDefinitionEntity entity = repository.findById(id); if (entity == null) { return Result.ofFail(-1, "api does not exist, id=" + id); } // 匹配规则列表 List<ApiPredicateItemVo> predicateItems = reqVo.getPredicateItems(); if (CollectionUtils.isEmpty(predicateItems)) { return Result.ofFail(-1, "predicateItems can't empty"); } List<ApiPredicateItemEntity> predicateItemEntities = new ArrayList<>(); for (ApiPredicateItemVo predicateItem : predicateItems) { ApiPredicateItemEntity predicateItemEntity = new ApiPredicateItemEntity(); // 匹配模式 int matchStrategy = predicateItem.getMatchStrategy(); if (!Arrays.asList(URL_MATCH_STRATEGY_EXACT, URL_MATCH_STRATEGY_PREFIX, URL_MATCH_STRATEGY_REGEX).contains(matchStrategy)) { return Result.ofFail(-1, "Invalid matchStrategy: " + matchStrategy); } predicateItemEntity.setMatchStrategy(matchStrategy); // 匹配串 String pattern = predicateItem.getPattern(); if (StringUtil.isBlank(pattern)) { return Result.ofFail(-1, "pattern can't be null or empty"); } predicateItemEntity.setPattern(pattern); predicateItemEntities.add(predicateItemEntity); } entity.setPredicateItems(new LinkedHashSet<>(predicateItemEntities)); Date date = new Date(); entity.setGmtModified(date); try { entity = repository.save(entity); } catch (Throwable throwable) { logger.error("update gateway api error:", throwable); return Result.ofThrowable(-1, throwable); } if (!publishApis(app, entity.getIp(), entity.getPort())) { logger.warn("publish gateway apis fail after update"); } return Result.ofSuccess(entity); } @PostMapping("/delete.json") @AuthAction(AuthService.PrivilegeType.DELETE_RULE) public Result<Long> deleteApi(Long id) { if (id == null) { return Result.ofFail(-1, "id can't be null"); } ApiDefinitionEntity oldEntity = repository.findById(id); if (oldEntity == null) { return Result.ofSuccess(null); } try { repository.delete(id); } catch (Throwable throwable) { logger.error("delete gateway api error:", throwable); return Result.ofThrowable(-1, throwable); } if (!publishApis(oldEntity.getApp(), oldEntity.getIp(), oldEntity.getPort())) { logger.warn("publish gateway apis fail after delete"); } return Result.ofSuccess(id); } private boolean publishApis(String app, String ip, Integer port) { //List<ApiDefinitionEntity> apis = repository.findAllByMachine(MachineInfo.of(app, ip, port)); List<ApiDefinitionEntity> apis = repository.findAllByApp(app); try { rulePublisher.publish(app, apis); logger.info("添加Gateway API管理规则成功{}", JSON.toJSONString(apis)); } catch (Exception e) { e.printStackTrace(); logger.warn("publishRules failed", e); return false; } //单节点:核心代码,sentinel-dashboard通过http的形式进行数据推送,客户端接收后将规则保存在本地内存中 //return sentinelApiClient.modifyApis(app, ip, port, apis); //针对多节点集群不同ip时,需要和客户端进行发布订阅通道一致,否则会导致某些节点规则失效 return true; } }
3、改造接口实现
完整代码如下:
GatewayApiRedisProvider代码
package com.alibaba.csp.sentinel.dashboard.rule.redis.gateway; import com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.ApiDefinitionEntity; import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.AuthorityRuleEntity; import com.alibaba.csp.sentinel.dashboard.rule.DynamicRuleProvider; import com.alibaba.csp.sentinel.dashboard.rule.redis.RedisUtil; import com.alibaba.csp.sentinel.dashboard.rule.redis.RuleConsts; import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.fastjson.JSONObject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.List; /** * @className: GatewayApiRedisProvider * @description: 网关api管理规则订阅 * @author: wuhk * @date: 2023/9/18 20:33 * @version: 1.0 * @company 自主研发 **/ @Component("gatewayApiRedisProvider") public class GatewayApiRedisProvider implements DynamicRuleProvider<List<ApiDefinitionEntity>> { @Autowired private RedisUtil redisConfigUtil; @Override public List<ApiDefinitionEntity> getRules(String appName) throws Exception { String rules = redisConfigUtil.getString(RuleConsts.GATEWAY_RULE_API + appName); if (StringUtil.isEmpty(rules)) { return new ArrayList<>(); } return JSONObject.parseArray(rules, ApiDefinitionEntity.class); } }
GatewayApiRedisPublisher代码
package com.alibaba.csp.sentinel.dashboard.rule.redis.gateway; import com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.ApiDefinitionEntity; import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.AuthorityRuleEntity; import com.alibaba.csp.sentinel.dashboard.rule.DynamicRulePublisher; import com.alibaba.csp.sentinel.dashboard.rule.redis.RedisUtil; import com.alibaba.csp.sentinel.dashboard.rule.redis.RuleConsts; import com.alibaba.csp.sentinel.util.AssertUtil; import com.alibaba.fastjson.JSON; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.List; /** * @className: GatewayApiRedisPublisher * @description: 网关api管理规则发布 * @author: wuhk * @date: 2023/9/18 20:34 * @version: 1.0 * @company 自主研发 **/ @Component("gatewayApiRedisPublisher") public class GatewayApiRedisPublisher implements DynamicRulePublisher<List<ApiDefinitionEntity>> { @Autowired private RedisUtil redisConfigUtil; @Override public void publish(String app, List<ApiDefinitionEntity> rules) throws Exception { AssertUtil.notEmpty(app, "app name cannot be empty"); if (rules == null) { return; } String strs = JSON.toJSONString(rules); redisConfigUtil.setString(RuleConsts.GATEWAY_RULE_API + app, strs); //针对多节点集群不同ip时,需要和客户端进行发布订阅通道一致,否则会导致某些节点规则失效 redisConfigUtil.convertAndSend(RuleConsts.GATEWAY_RULE_API_CHANNEL,strs); } }
GatewayFlowRuleController_Makr代码
/* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed 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.csp.sentinel.dashboard.controller.gateway; import com.alibaba.csp.sentinel.dashboard.auth.AuthAction; import com.alibaba.csp.sentinel.dashboard.auth.AuthService; import com.alibaba.csp.sentinel.dashboard.client.SentinelApiClient; import com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.GatewayFlowRuleEntity; import com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.GatewayParamFlowItemEntity; import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity; import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.ParamFlowRuleEntity; import com.alibaba.csp.sentinel.dashboard.discovery.MachineInfo; import com.alibaba.csp.sentinel.dashboard.domain.Result; import com.alibaba.csp.sentinel.dashboard.domain.vo.gateway.rule.AddFlowRuleReqVo; import com.alibaba.csp.sentinel.dashboard.domain.vo.gateway.rule.GatewayParamFlowItemVo; import com.alibaba.csp.sentinel.dashboard.domain.vo.gateway.rule.UpdateFlowRuleReqVo; import com.alibaba.csp.sentinel.dashboard.repository.gateway.InMemGatewayFlowRuleStore; import com.alibaba.csp.sentinel.dashboard.rule.DynamicRuleProvider; import com.alibaba.csp.sentinel.dashboard.rule.DynamicRulePublisher; import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.fastjson.JSON; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.web.bind.annotation.*; import java.util.Arrays; import java.util.Date; import java.util.List; import static com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants.*; import static com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.GatewayFlowRuleEntity.*; import static com.alibaba.csp.sentinel.slots.block.RuleConstant.*; /** * Gateway * @className: GatewayFlowRuleController_Makr * @description: Gateway流控规则 * @author: wuhk * @date: 2023/9/18 20:41 * @version: 1.0 * @company 自主研发 **/ @RestController @RequestMapping(value = "/gateway/flow") public class GatewayFlowRuleController_Makr { private final Logger logger = LoggerFactory.getLogger(GatewayFlowRuleController_Makr.class); @Autowired private InMemGatewayFlowRuleStore repository; @Autowired @Qualifier("gatewayFlowRuleRedisProvider") private DynamicRuleProvider<List<GatewayFlowRuleEntity>> ruleProvider; @Autowired @Qualifier("gatewayFlowRuleRedisPublisher") private DynamicRulePublisher<List<GatewayFlowRuleEntity>> rulePublisher; @Autowired private SentinelApiClient sentinelApiClient; @GetMapping("/list.json") @AuthAction(AuthService.PrivilegeType.READ_RULE) public Result<List<GatewayFlowRuleEntity>> queryFlowRules(String app, String ip, Integer port) { if (StringUtil.isEmpty(app)) { return Result.ofFail(-1, "app can't be null or empty"); } if (StringUtil.isEmpty(ip)) { return Result.ofFail(-1, "ip can't be null or empty"); } if (port == null) { return Result.ofFail(-1, "port can't be null"); } try { /*List<GatewayFlowRuleEntity> rules = sentinelApiClient.fetchGatewayFlowRules(app, ip, port).get(); repository.saveAll(rules); return Result.ofSuccess(rules);*/ List<GatewayFlowRuleEntity> rules = ruleProvider.getRules(app); rules = repository.saveAll(rules); return Result.ofSuccess(rules); } catch (Throwable throwable) { logger.error("query gateway flow rules error:", throwable); return Result.ofThrowable(-1, throwable); } } @PostMapping("/new.json") @AuthAction(AuthService.PrivilegeType.WRITE_RULE) public Result<GatewayFlowRuleEntity> addFlowRule(@RequestBody AddFlowRuleReqVo reqVo) { String app = reqVo.getApp(); if (StringUtil.isBlank(app)) { return Result.ofFail(-1, "app can't be null or empty"); } GatewayFlowRuleEntity entity = new GatewayFlowRuleEntity(); entity.setApp(app.trim()); String ip = reqVo.getIp(); if (StringUtil.isBlank(ip)) { return Result.ofFail(-1, "ip can't be null or empty"); } entity.setIp(ip.trim()); Integer port = reqVo.getPort(); if (port == null) { return Result.ofFail(-1, "port can't be null"); } entity.setPort(port); // API类型, Route ID或API分组 Integer resourceMode = reqVo.getResourceMode(); if (resourceMode == null) { return Result.ofFail(-1, "resourceMode can't be null"); } if (!Arrays.asList(RESOURCE_MODE_ROUTE_ID, RESOURCE_MODE_CUSTOM_API_NAME).contains(resourceMode)) { return Result.ofFail(-1, "invalid resourceMode: " + resourceMode); } entity.setResourceMode(resourceMode); // API名称 String resource = reqVo.getResource(); if (StringUtil.isBlank(resource)) { return Result.ofFail(-1, "resource can't be null or empty"); } entity.setResource(resource.trim()); // 针对请求属性 GatewayParamFlowItemVo paramItem = reqVo.getParamItem(); if (paramItem != null) { GatewayParamFlowItemEntity itemEntity = new GatewayParamFlowItemEntity(); entity.setParamItem(itemEntity); // 参数属性 0-ClientIP 1-Remote Host 2-Header 3-URL参数 4-Cookie Integer parseStrategy = paramItem.getParseStrategy(); if (!Arrays.asList(PARAM_PARSE_STRATEGY_CLIENT_IP, PARAM_PARSE_STRATEGY_HOST, PARAM_PARSE_STRATEGY_HEADER , PARAM_PARSE_STRATEGY_URL_PARAM, PARAM_PARSE_STRATEGY_COOKIE).contains(parseStrategy)) { return Result.ofFail(-1, "invalid parseStrategy: " + parseStrategy); } itemEntity.setParseStrategy(paramItem.getParseStrategy()); // 当参数属性为2-Header 3-URL参数 4-Cookie时,参数名称必填 if (Arrays.asList(PARAM_PARSE_STRATEGY_HEADER, PARAM_PARSE_STRATEGY_URL_PARAM, PARAM_PARSE_STRATEGY_COOKIE).contains(parseStrategy)) { // 参数名称 String fieldName = paramItem.getFieldName(); if (StringUtil.isBlank(fieldName)) { return Result.ofFail(-1, "fieldName can't be null or empty"); } itemEntity.setFieldName(paramItem.getFieldName()); } String pattern = paramItem.getPattern(); // 如果匹配串不为空,验证匹配模式 if (StringUtil.isNotEmpty(pattern)) { itemEntity.setPattern(pattern); Integer matchStrategy = paramItem.getMatchStrategy(); if (!Arrays.asList(PARAM_MATCH_STRATEGY_EXACT, PARAM_MATCH_STRATEGY_CONTAINS, PARAM_MATCH_STRATEGY_REGEX).contains(matchStrategy)) { return Result.ofFail(-1, "invalid matchStrategy: " + matchStrategy); } itemEntity.setMatchStrategy(matchStrategy); } } // 阈值类型 0-线程数 1-QPS Integer grade = reqVo.getGrade(); if (grade == null) { return Result.ofFail(-1, "grade can't be null"); } if (!Arrays.asList(FLOW_GRADE_THREAD, FLOW_GRADE_QPS).contains(grade)) { return Result.ofFail(-1, "invalid grade: " + grade); } entity.setGrade(grade); // QPS阈值 Double count = reqVo.getCount(); if (count == null) { return Result.ofFail(-1, "count can't be null"); } if (count < 0) { return Result.ofFail(-1, "count should be at lease zero"); } entity.setCount(count); // 间隔 Long interval = reqVo.getInterval(); if (interval == null) { return Result.ofFail(-1, "interval can't be null"); } if (interval <= 0) { return Result.ofFail(-1, "interval should be greater than zero"); } entity.setInterval(interval); // 间隔单位 Integer intervalUnit = reqVo.getIntervalUnit(); if (intervalUnit == null) { return Result.ofFail(-1, "intervalUnit can't be null"); } if (!Arrays.asList(INTERVAL_UNIT_SECOND, INTERVAL_UNIT_MINUTE, INTERVAL_UNIT_HOUR, INTERVAL_UNIT_DAY).contains(intervalUnit)) { return Result.ofFail(-1, "Invalid intervalUnit: " + intervalUnit); } entity.setIntervalUnit(intervalUnit); // 流控方式 0-快速失败 2-匀速排队 Integer controlBehavior = reqVo.getControlBehavior(); if (controlBehavior == null) { return Result.ofFail(-1, "controlBehavior can't be null"); } if (!Arrays.asList(CONTROL_BEHAVIOR_DEFAULT, CONTROL_BEHAVIOR_RATE_LIMITER).contains(controlBehavior)) { return Result.ofFail(-1, "invalid controlBehavior: " + controlBehavior); } entity.setControlBehavior(controlBehavior); if (CONTROL_BEHAVIOR_DEFAULT == controlBehavior) { // 0-快速失败, 则Burst size必填 Integer burst = reqVo.getBurst(); if (burst == null) { return Result.ofFail(-1, "burst can't be null"); } if (burst < 0) { return Result.ofFail(-1, "invalid burst: " + burst); } entity.setBurst(burst); } else if (CONTROL_BEHAVIOR_RATE_LIMITER == controlBehavior) { // 1-匀速排队, 则超时时间必填 Integer maxQueueingTimeoutMs = reqVo.getMaxQueueingTimeoutMs(); if (maxQueueingTimeoutMs == null) { return Result.ofFail(-1, "maxQueueingTimeoutMs can't be null"); } if (maxQueueingTimeoutMs < 0) { return Result.ofFail(-1, "invalid maxQueueingTimeoutMs: " + maxQueueingTimeoutMs); } entity.setMaxQueueingTimeoutMs(maxQueueingTimeoutMs); } Date date = new Date(); entity.setGmtCreate(date); entity.setGmtModified(date); try { entity = repository.save(entity); } catch (Throwable throwable) { logger.error("add gateway flow rule error:", throwable); return Result.ofThrowable(-1, throwable); } if (!publishRules(app, ip, port)) { logger.warn("publish gateway flow rules fail after add"); } return Result.ofSuccess(entity); } @PostMapping("/save.json") @AuthAction(AuthService.PrivilegeType.WRITE_RULE) public Result<GatewayFlowRuleEntity> updateFlowRule(@RequestBody UpdateFlowRuleReqVo reqVo) { String app = reqVo.getApp(); if (StringUtil.isBlank(app)) { return Result.ofFail(-1, "app can't be null or empty"); } Long id = reqVo.getId(); if (id == null) { return Result.ofFail(-1, "id can't be null"); } GatewayFlowRuleEntity entity = repository.findById(id); if (entity == null) { return Result.ofFail(-1, "gateway flow rule does not exist, id=" + id); } // 针对请求属性 GatewayParamFlowItemVo paramItem = reqVo.getParamItem(); if (paramItem != null) { GatewayParamFlowItemEntity itemEntity = new GatewayParamFlowItemEntity(); entity.setParamItem(itemEntity); // 参数属性 0-ClientIP 1-Remote Host 2-Header 3-URL参数 4-Cookie Integer parseStrategy = paramItem.getParseStrategy(); if (!Arrays.asList(PARAM_PARSE_STRATEGY_CLIENT_IP, PARAM_PARSE_STRATEGY_HOST, PARAM_PARSE_STRATEGY_HEADER , PARAM_PARSE_STRATEGY_URL_PARAM, PARAM_PARSE_STRATEGY_COOKIE).contains(parseStrategy)) { return Result.ofFail(-1, "invalid parseStrategy: " + parseStrategy); } itemEntity.setParseStrategy(paramItem.getParseStrategy()); // 当参数属性为2-Header 3-URL参数 4-Cookie时,参数名称必填 if (Arrays.asList(PARAM_PARSE_STRATEGY_HEADER, PARAM_PARSE_STRATEGY_URL_PARAM, PARAM_PARSE_STRATEGY_COOKIE).contains(parseStrategy)) { // 参数名称 String fieldName = paramItem.getFieldName(); if (StringUtil.isBlank(fieldName)) { return Result.ofFail(-1, "fieldName can't be null or empty"); } itemEntity.setFieldName(paramItem.getFieldName()); } String pattern = paramItem.getPattern(); // 如果匹配串不为空,验证匹配模式 if (StringUtil.isNotEmpty(pattern)) { itemEntity.setPattern(pattern); Integer matchStrategy = paramItem.getMatchStrategy(); if (!Arrays.asList(PARAM_MATCH_STRATEGY_EXACT, PARAM_MATCH_STRATEGY_CONTAINS, PARAM_MATCH_STRATEGY_REGEX).contains(matchStrategy)) { return Result.ofFail(-1, "invalid matchStrategy: " + matchStrategy); } itemEntity.setMatchStrategy(matchStrategy); } } else { entity.setParamItem(null); } // 阈值类型 0-线程数 1-QPS Integer grade = reqVo.getGrade(); if (grade == null) { return Result.ofFail(-1, "grade can't be null"); } if (!Arrays.asList(FLOW_GRADE_THREAD, FLOW_GRADE_QPS).contains(grade)) { return Result.ofFail(-1, "invalid grade: " + grade); } entity.setGrade(grade); // QPS阈值 Double count = reqVo.getCount(); if (count == null) { return Result.ofFail(-1, "count can't be null"); } if (count < 0) { return Result.ofFail(-1, "count should be at lease zero"); } entity.setCount(count); // 间隔 Long interval = reqVo.getInterval(); if (interval == null) { return Result.ofFail(-1, "interval can't be null"); } if (interval <= 0) { return Result.ofFail(-1, "interval should be greater than zero"); } entity.setInterval(interval); // 间隔单位 Integer intervalUnit = reqVo.getIntervalUnit(); if (intervalUnit == null) { return Result.ofFail(-1, "intervalUnit can't be null"); } if (!Arrays.asList(INTERVAL_UNIT_SECOND, INTERVAL_UNIT_MINUTE, INTERVAL_UNIT_HOUR, INTERVAL_UNIT_DAY).contains(intervalUnit)) { return Result.ofFail(-1, "Invalid intervalUnit: " + intervalUnit); } entity.setIntervalUnit(intervalUnit); // 流控方式 0-快速失败 2-匀速排队 Integer controlBehavior = reqVo.getControlBehavior(); if (controlBehavior == null) { return Result.ofFail(-1, "controlBehavior can't be null"); } if (!Arrays.asList(CONTROL_BEHAVIOR_DEFAULT, CONTROL_BEHAVIOR_RATE_LIMITER).contains(controlBehavior)) { return Result.ofFail(-1, "invalid controlBehavior: " + controlBehavior); } entity.setControlBehavior(controlBehavior); if (CONTROL_BEHAVIOR_DEFAULT == controlBehavior) { // 0-快速失败, 则Burst size必填 Integer burst = reqVo.getBurst(); if (burst == null) { return Result.ofFail(-1, "burst can't be null"); } if (burst < 0) { return Result.ofFail(-1, "invalid burst: " + burst); } entity.setBurst(burst); } else if (CONTROL_BEHAVIOR_RATE_LIMITER == controlBehavior) { // 2-匀速排队, 则超时时间必填 Integer maxQueueingTimeoutMs = reqVo.getMaxQueueingTimeoutMs(); if (maxQueueingTimeoutMs == null) { return Result.ofFail(-1, "maxQueueingTimeoutMs can't be null"); } if (maxQueueingTimeoutMs < 0) { return Result.ofFail(-1, "invalid maxQueueingTimeoutMs: " + maxQueueingTimeoutMs); } entity.setMaxQueueingTimeoutMs(maxQueueingTimeoutMs); } Date date = new Date(); entity.setGmtModified(date); try { entity = repository.save(entity); } catch (Throwable throwable) { logger.error("update gateway flow rule error:", throwable); return Result.ofThrowable(-1, throwable); } if (!publishRules(app, entity.getIp(), entity.getPort())) { logger.warn("publish gateway flow rules fail after update"); } return Result.ofSuccess(entity); } @PostMapping("/delete.json") @AuthAction(AuthService.PrivilegeType.DELETE_RULE) public Result<Long> deleteFlowRule(Long id) { if (id == null) { return Result.ofFail(-1, "id can't be null"); } GatewayFlowRuleEntity oldEntity = repository.findById(id); if (oldEntity == null) { return Result.ofSuccess(null); } try { repository.delete(id); } catch (Throwable throwable) { logger.error("delete gateway flow rule error:", throwable); return Result.ofThrowable(-1, throwable); } if (!publishRules(oldEntity.getApp(), oldEntity.getIp(), oldEntity.getPort())) { logger.warn("publish gateway flow rules fail after delete"); } return Result.ofSuccess(id); } private boolean publishRules(String app, String ip, Integer port) { //List<GatewayFlowRuleEntity> rules = repository.findAllByMachine(MachineInfo.of(app, ip, port)); List<GatewayFlowRuleEntity> rules = repository.findAllByApp(app); try { rulePublisher.publish(app, rules); logger.info("添加Gateway限流规则成功{}", JSON.toJSONString(rules)); } catch (Exception e) { e.printStackTrace(); logger.warn("publishRules failed", e); return false; } //单节点:核心代码,sentinel-dashboard通过http的形式进行数据推送,客户端接收后将规则保存在本地内存中 //return sentinelApiClient.modifyGatewayFlowRules(app, ip, port, rules); //针对多节点集群不同ip时,需要和客户端进行发布订阅通道一致,否则会导致某些节点规则失效 return true; } }
3、改造接口实现
完整代码如下:
GatewayFlowRuleRedisProvider代码
package com.alibaba.csp.sentinel.dashboard.rule.redis.gateway; import com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.GatewayFlowRuleEntity; import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity; import com.alibaba.csp.sentinel.dashboard.rule.DynamicRuleProvider; import com.alibaba.csp.sentinel.dashboard.rule.redis.RedisUtil; import com.alibaba.csp.sentinel.dashboard.rule.redis.RuleConsts; import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.fastjson.JSONObject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.List; /** * @className: GatewayFlowRuleRedisProvider * @description: 网关gateway限流规则订阅 * @author: wuhk * @date: 2023/9/18 10:20 * @version: 1.0 * @company 自主研发 **/ @Component("gatewayFlowRuleRedisProvider") public class GatewayFlowRuleRedisProvider implements DynamicRuleProvider<List<GatewayFlowRuleEntity>> { @Autowired private RedisUtil redisConfigUtil; @Override public List<GatewayFlowRuleEntity> getRules(String appName) throws Exception { String rules = redisConfigUtil.getString(RuleConsts.GATEWAY_RULE_FLOW + appName); if (StringUtil.isEmpty(rules)) { return new ArrayList<>(); } return JSONObject.parseArray(rules, GatewayFlowRuleEntity.class); } }
GatewayFlowRuleRedisPublisher代码
package com.alibaba.csp.sentinel.dashboard.rule.redis.gateway; import com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.GatewayFlowRuleEntity; import com.alibaba.csp.sentinel.dashboard.rule.DynamicRulePublisher; import com.alibaba.csp.sentinel.dashboard.rule.redis.RedisUtil; import com.alibaba.csp.sentinel.dashboard.rule.redis.RuleConsts; import com.alibaba.csp.sentinel.util.AssertUtil; import com.alibaba.fastjson.JSON; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.List; /** * @className: GatewayFlowRuleRedisPublisher * @description: 网关gateway限流规则发布 * @author: wuhk * @date: 2023/9/18 10:21 * @version: 1.0 * @company 自主研发 **/ @Component("gatewayFlowRuleRedisPublisher") public class GatewayFlowRuleRedisPublisher implements DynamicRulePublisher<List<GatewayFlowRuleEntity>> { @Autowired private RedisUtil redisConfigUtil; @Override public void publish(String app, List<GatewayFlowRuleEntity> rules) throws Exception { AssertUtil.notEmpty(app, "app name cannot be empty"); if (rules == null) { return; } String strs = JSON.toJSONString(rules); redisConfigUtil.setString(RuleConsts.GATEWAY_RULE_FLOW + app, strs); //针对多节点集群不同ip时,需要和客户端进行发布订阅通道一致,否则会导致某些节点规则失效 redisConfigUtil.convertAndSend(RuleConsts.GATEWAY_RULE_FLOW_CHANNEL,strs); } }
RedisUtil代码
package com.alibaba.csp.sentinel.dashboard.rule.redis; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Component; import java.util.concurrent.TimeUnit; /** * @className: RedisUtil * @description: redis工具类 * @author: wuhk * @date: 2023/9/18 10:21 * @version: 1.0 * @company 自主研发 **/ @Component public class RedisUtil { @Autowired private StringRedisTemplate stringRedisTemplate; /** * 存放string类型 * @param key * @param data * @param timeout */ public void setString(String key, String data, Long timeout) { try { stringRedisTemplate.opsForValue().set(key, data); if (timeout != null) { stringRedisTemplate.expire(key, timeout, TimeUnit.SECONDS); } } catch (Exception e) { } } /** * 存放string类型 * @param key * @param data */ public void setString(String key, String data) { setString(key, data, null); } /** * 根据key查询string类型 * @param key * @return */ public String getString(String key) { String value = stringRedisTemplate.opsForValue().get(key); return value; } /** * 根据对应的key删除key * @param key */ public Boolean delKey(String key) { return stringRedisTemplate.delete(key); } /** * 根据对应的key发布通道 * @param channel,message */ public void convertAndSend(String channel, Object message) { stringRedisTemplate.convertAndSend(channel, message); } }
RuleConsts代码
package com.alibaba.csp.sentinel.dashboard.rule.redis; /** * @className: RuleConsts * @description: 规则key前缀 * @author: wuhk * @date: 2023/9/18 10:22 * @version: 1.0 * @company 自主研发 **/ public class RuleConsts { //key前缀 public static final String RULE = "sentinel:rule:"; //流控规则key前缀 public static final String RULE_FLOW = RULE + "sentinel_rule_flow_"; public static final String RULE_FLOW_CHANNEL = RULE + "sentinel_rule_flow_channel"; //限流规则key前缀 public static final String RULE_DEGRADE = RULE + "sentinel_rule_degrade_"; public static final String RULE_DEGRADE_CHANNEL = RULE + "sentinel_rule_degrade_channel"; //系统规则key前缀 public static final String RULE_SYSTEM = RULE + "sentinel_rule_system_"; public static final String RULE_SYSTEM_CHANNEL = RULE + "sentinel_rule_system_channel"; //授权规则key前缀 public static final String RULE_AUTHORITY = RULE + "sentinel_rule_authority_"; public static final String RULE_AUTHORITY_CHANNEL = RULE + "sentinel_rule_authority_channel"; //热点参数规则key前缀 public static final String RULE_PARAM = RULE + "sentinel_rule_param_"; public static final String RULE_PARAM_CHANNEL = RULE + "sentinel_rule_param_channel"; //网关Gateway流控规则key前缀 public static final String GATEWAY_RULE_FLOW = RULE + "sentinel_gateway_rule_flow_"; public static final String GATEWAY_RULE_FLOW_CHANNEL = RULE + "sentinel_gateway_rule_flow_channel"; //网关GatewayAPI管理规则key前缀 public static final String GATEWAY_RULE_API = RULE + "sentinel_gateway_rule_api_"; public static final String GATEWAY_RULE_API_CHANNEL = RULE + "sentinel_gateway_rule_api_channel"; }
RedisCachePoolConfig代码(此类为了解决连接redis时,密码中含有特殊字符)
package com.alibaba.csp.sentinel.dashboard.config; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; import io.lettuce.core.RedisURI; import io.lettuce.core.cluster.RedisClusterClient; import org.apache.commons.pool2.impl.GenericObjectPoolConfig; import org.springframework.beans.factory.annotation.Value; import org.springframework.cache.CacheManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.cache.RedisCacheConfiguration; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory; import org.springframework.data.redis.connection.RedisPassword; import org.springframework.data.redis.connection.RedisStandaloneConfiguration; import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration; import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration; import org.springframework.data.redis.core.ReactiveRedisTemplate; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.RedisSerializationContext; import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; import java.time.Duration; import java.util.*; /** * @className: RedisCachePoolConfig * @description: Redis连接 * @author: wuhk * @date: 2023/12/14 20:58 * @version: 1.0 * @company 自主研发 **/ @Configuration public class RedisCachePoolConfig { @Value(value = "${spring.redis.host:127.0.0.1}") private String host; @Value(value = "${spring.redis.port:6379}") private String port; @Value(value = "${spring.redis.password:1234}") private String password; @Bean public RedisClusterClient redisClient(){ ArrayList<RedisURI> list = new ArrayList<>(); RedisURI redisURI = new RedisURI(); redisURI.setHost(host); redisURI.setPassword(password); redisURI.setPort(Integer.parseInt(port)); list.add(redisURI); return RedisClusterClient.create(list); } @Bean public LettuceConnectionFactory lettuceConnectionFactory() { GenericObjectPoolConfig genericObjectPoolConfig = new GenericObjectPoolConfig(); RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(); redisStandaloneConfiguration.setHostName(host); redisStandaloneConfiguration.setPort(Integer.parseInt(port)); redisStandaloneConfiguration.setPassword(RedisPassword.of(password)); LettuceClientConfiguration clientConfig = LettucePoolingClientConfiguration.builder() .commandTimeout(Duration.ofMillis(5000)) .shutdownTimeout(Duration.ofMillis(10000)) .poolConfig(genericObjectPoolConfig) .build(); LettuceConnectionFactory factory = new LettuceConnectionFactory(redisStandaloneConfiguration, clientConfig); // factory.setShareNativeConnection(true); factory.setValidateConnection(true); return factory; } Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class); private final ObjectMapper objectMapper = new ObjectMapper(); @Bean("redisCacheManager") //@Primary //必须指定一个会话管理器主键,否则会报:No CacheResolver specified, and no unique bean of type CacheManager found. Mark one as primary or declare a specific CacheManager to use. public CacheManager cacheManager(LettuceConnectionFactory lettuceConnectionFactory) { RedisSerializer<String> redisSerializer = new StringRedisSerializer(); //解决查询缓存转换异常的问题 ObjectMapper mapper = new ObjectMapper(); mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); //mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY); mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(mapper); // 配置1 , RedisCacheConfiguration config1 = RedisCacheConfiguration.defaultCacheConfig() //缓存失效时间 .entryTtl(Duration.ofSeconds(30)) //key序列化方式 .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer)) //value序列化方式 .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)) //不允许缓存null值 .disableCachingNullValues(); //配置2 , RedisCacheConfiguration config2 = RedisCacheConfiguration.defaultCacheConfig() .entryTtl(Duration.ofMinutes(1000)) .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer)) .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)) .disableCachingNullValues(); //设置一个初始化的缓存空间set集合 Set<String> cacheNames = new HashSet<>(); cacheNames.add("my-redis-cache1"); cacheNames.add("my-redis-cache2"); //对每个缓存空间应用不同的配置 Map<String, RedisCacheConfiguration> configurationMap = new HashMap<>(3); configurationMap.put("my-redis-cache1", config1); configurationMap.put("my-redis-cache2", config2); return RedisCacheManager.builder(lettuceConnectionFactory) //默认缓存配置 .cacheDefaults(config1) //初始化缓存空间 .initialCacheNames(cacheNames) //初始化缓存配置 .withInitialCacheConfigurations(configurationMap).build(); } // config one model @Bean public ReactiveRedisTemplate<String, Object> productModelReactiveRedisTemplate(ReactiveRedisConnectionFactory factory) { StringRedisSerializer stringSerializer = new StringRedisSerializer(); Jackson2JsonRedisSerializer<Object> valueSerializer = new Jackson2JsonRedisSerializer<>(Object.class); valueSerializer.setObjectMapper(objectMapper); RedisSerializationContext<String, Object> context = RedisSerializationContext.<String, Object>newSerializationContext() .key(stringSerializer) .value(valueSerializer) .hashKey(stringSerializer) .hashValue(valueSerializer) .build(); return new ReactiveRedisTemplate<>(factory, context); } // config model list @Bean public ReactiveRedisTemplate<String, Object> getProductWhitelistModelReactiveRedisTemplate(ReactiveRedisConnectionFactory factory) { StringRedisSerializer stringSerializer = new StringRedisSerializer(); Jackson2JsonRedisSerializer<Object> valueSerializer = new Jackson2JsonRedisSerializer(List.class); valueSerializer.setObjectMapper(objectMapper); RedisSerializationContext<String, Object> context = RedisSerializationContext.<String, Object>newSerializationContext() .key(stringSerializer) .value(valueSerializer) .hashKey(stringSerializer) .hashValue(valueSerializer) .build(); return new ReactiveRedisTemplate<>(factory, context); } @Bean("reactiveRedisTemplate") public ReactiveRedisTemplate<String, Object> objectReactiveRedisTemplate(ReactiveRedisConnectionFactory factory) { StringRedisSerializer stringSerializer = new StringRedisSerializer(); Jackson2JsonRedisSerializer<Object> valueSerializer = new Jackson2JsonRedisSerializer<>(Object.class); valueSerializer.setObjectMapper(objectMapper); RedisSerializationContext<String, Object> context = RedisSerializationContext.<String, Object>newSerializationContext() .key(stringSerializer) .value(valueSerializer) .hashKey(stringSerializer) .hashValue(valueSerializer) .build(); return new ReactiveRedisTemplate<>(factory, context); } }
4、以上步骤完成后,只是sentinel-dashboard监控服务端改造完成,下面改造sentinel-client被监控的客户端;由于客户端重启项目之后内存中的限流、降级等规则丢失,所以项目启动的时候需要重新加载配置规则到内存中,Gateway服务增加配置类
SpringCloudGateway项目集成Sentinel并持久化redis依赖:
RedisDataSourceConfig 代码:
package com.it.sentinel.demo.config;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition; import com.alibaba.csp.sentinel.adapter.gateway.common.api.GatewayApiDefinitionManager; import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule; import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager; import com.alibaba.csp.sentinel.config.SentinelConfig; import com.alibaba.csp.sentinel.datasource.Converter; import com.alibaba.csp.sentinel.datasource.ReadableDataSource; import com.alibaba.csp.sentinel.datasource.redis.RedisDataSource; import com.alibaba.csp.sentinel.datasource.redis.config.RedisConnectionConfig; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; import com.alibaba.csp.sentinel.slots.system.SystemRule; import com.alibaba.csp.sentinel.slots.system.SystemRuleManager; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.TypeReference; import gov.chinatax.tpass.gateway.bean.ApiPathDefinition; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; import org.springframework.stereotype.Component; import java.util.*; /** * @className: RedisDataSourceConfig * @description: SpringCloudGateway网关集成sentinel并双向持久化redis **/ @Component @Slf4j public class RedisDataSourceConfig implements ApplicationRunner { @Value("${spring.redis.host}") public String redisHost; @Value("${spring.redis.port}") public int redisPort; @Value("${spring.redis.password}") public String password; //key前缀 public static final String RULE = "sentinel:rule:"; //网关Gateway流控规则key前缀 public static final String GATEWAY_RULE_FLOW = RULE + "sentinel_gateway_rule_flow_"; public final String GATEWAY_RULE_FLOW_CHANNEL = RULE + "sentinel_gateway_rule_flow_channel"; //网关GatewayAPI管理规则key前缀 public static final String GATEWAY_RULE_API = RULE + "sentinel_gateway_rule_api_"; public final String GATEWAY_RULE_API_CHANNEL = RULE + "sentinel_gateway_rule_api_channel"; //降级规则key前缀 public final String RULE_DEGRADE = RULE + "sentinel_rule_degrade_"; public final String RULE_DEGRADE_CHANNEL = RULE + "sentinel_rule_degrade_channel"; //系统规则key前缀 public final String RULE_SYSTEM = RULE + "sentinel_rule_system_"; public final String RULE_SYSTEM_CHANNEL = RULE + "sentinel_rule_system_channel"; /** * <p>ApplicationRunner 该接口的方法会在服务启动之后被立即执行 主要用来做一些初始化的工作 但是该方法的运行是在SpringApplication.run(…) 执行完毕之前执行</p> * @method RedisDataSourceConfig.run() **/ @Override public void run(ApplicationArguments args) { log.info(">>>>>>>>>执行sentinel规则初始化"); try{ RedisConnectionConfig config = RedisConnectionConfig.builder().withHost(redisHost).withPort(redisPort).withPassword(password).build(); ReadableDataSource<String, Set<ApiDefinition>> redisDataSource3 = new RedisDataSource<>(config, GATEWAY_RULE_API + SentinelConfig.getAppName(), GATEWAY_RULE_API_CHANNEL, s -> { List<ApiPathDefinition> lists = JSONObject.parseArray(s, ApiPathDefinition.class); Set<ApiDefinition> resultSet = new HashSet<>(); for (ApiPathDefinition apiPathDefinition : lists) { resultSet.add(apiPathDefinition.toApiDefinition()); } return resultSet; }); GatewayApiDefinitionManager.register2Property(redisDataSource3.getProperty()); Converter<String, Set<GatewayFlowRule>> parser = source -> JSON.parseObject(source,new TypeReference<Set<GatewayFlowRule>>() {}); ReadableDataSource<String, Set<GatewayFlowRule>> redisDataSource = new RedisDataSource<>(config, GATEWAY_RULE_FLOW + SentinelConfig.getAppName(), GATEWAY_RULE_FLOW_CHANNEL, parser); GatewayRuleManager.register2Property(redisDataSource.getProperty()); Converter<String, List<DegradeRule>> parser1 = source -> JSON.parseObject(source,new TypeReference<List<DegradeRule>>() {}); ReadableDataSource<String, List<DegradeRule>> redisDataSource1 = new RedisDataSource<>(config, RULE_DEGRADE + SentinelConfig.getAppName(), RULE_DEGRADE_CHANNEL, parser1); DegradeRuleManager.register2Property(redisDataSource1.getProperty()); Converter<String, List<SystemRule>> parser2 = source -> JSON.parseObject(source,new TypeReference<List<SystemRule>>() {}); ReadableDataSource<String, List<SystemRule>> redisDataSource2 = new RedisDataSource<>(config, RULE_SYSTEM + SentinelConfig.getAppName(), RULE_SYSTEM_CHANNEL, parser2); SystemRuleManager.register2Property(redisDataSource2.getProperty()); log.info(">>>>>>>>>执行sentinel规则初始化完成"); }catch (Exception e){ log.info(">>>>>>>>>执行sentinel规则初始化失败"); } } }
5、使用redis持久化sentinel配置规则,改造完成。
更多推荐
所有评论(0)