Sentinel 之 自定义持久化规则
谈谈我为甚要自行编码实现,因为我使用的早期的版本,并没有找到官方提供 依赖引入配置持久化方式,所以索性编码实现了,现在感觉没有编码实现的必要了,有更简便的方式了
既然已经做了这是,我还是愿意分享一下,权当抛砖引玉,供新用户参考,大佬请批评指点
********** 已推送到 GitHub 不想动手的同学,可直接抄作业 **********
配置实现方案:
当然,如果没有兴趣修改官方源码实现的朋友,可以直接引入官方整合方案,直接配置使用即可
引入依赖(这里仅仅提供了 Nacos 和 Redis 的方案 )
<!-- 流控规则持久化到 Nacos 按需选择使用 <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> </dependency> 流控规则持久化到 redis 按需选择使用 <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-redis</artifactId> </dependency> -->
配置(注意,万不可直接复制使用,需要根据自己选择方案进行修改):
spring: cloud: sentinel: transport: # 配置 Sentinel dashboard 地址信息 dashboard: ${SENTINEL_HOST:kwanyon-sentinel}:${SENTINEL_PORT:8858} # Sentinel 默认端口号,加入被占用了,会自动从 8719 自动往后依次 +1 扫描,直到找到未占用的 port: 8719 scg: # 限流后的相应配置 fallback: content-type: application/json # 模式: response / redirect mode: response # 相应状态码 response-status: 200 # 相应消息体 response-body: '{"code":0,"message":"服务忙请稍后再试...","data":null}' # 请求重定向到某个地址 redirect: https://www.kwanyon-cloud.com # Sentinel 流控规则持久化 datasource: # 自定义命名 kwanyon-flow-name: # 支持多种持久化数据源:file、nacos、zookeeper、apollo、redis、consul # 多选一方案 之 Redis 方案 nacos: # 配置类com.alibaba.cloud.sentinel.datasource.config.NacosDataSourceProperties # 连接地址 server-addr: ${NACOS_HOST:kwanyon-nacos}:${NACOS_PORT:8848} namespace: '名称空间' # 组名 默认 DEFAULT_GROUP group-id: 'SENTINEL_GROUP' # Nacos 中配置的Id data-id: ${spring.application.name}-flow-fules # RuleType rule-type: flow # Nacos 配置访问用户名 username: nacos # Nacos 访问密码 password: nacos_password # 默认就是Json data-type: 'json' # 多选一方案 之 Redis 方案 # com.alibaba.cloud.sentinel.datasource.config.RedisDataSourceProperties redis: # 连接地址 host: ${REDIS_HOST:kwanyon-redis} # 密码 password: ${REDIS_PWD:kwanyon_redis_password} # 存那个库 database: 15 # RuleType rule-type: flow rule-key: ${spring.application.name}-flow-fules # 暴露用于监控等 management: endpoint: web: exposure: include: '*' # Feign 开启 Sentinel 对的支持(对于网关模块没有引入相关依赖,忽略此处配置) feign: sentinel: enabled: true
编码实现方案
准备事项:
本案例需要下载 阿里巴巴 Sentinel 源码进行运行,请前往官方代码库克隆: https://github.com/alibaba/Sentinel
Sentinel 官网: https://sentinelguard.io/zh-cn/
克隆下来的代码库只需要 sentinel-dashboard 模块,

注意:以下整合说明中,需要 nacos 服务的支持 Nacos 的整合请参考: https://www.cnblogs.com/Alay/p/15116662.html
启动 Sentinel 服务:
1、修改 Sentinel 配置文件
server: port: ${SENTINEL_PORT:8858} servlet: encoding: force: true spring: application: name: ${@artifactId@:kwanyon-sentinel} cloud: nacos: discovery: server-addr: ${NACOS_HOST:kwanyon-nacos}:${NACOS_PORT:8848} management: endpoints: web: exposure: include: '*' endpoint: health: show-details: ALWAYS logging: level: org: springframework: web: info file: name: ${user.home}/logs/csp/sentinel-dashboard.log pattern: file: '%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n' auth: username: kwanyon password: sentinel filter: exclude-urls: /,/auth/login,/auth/logout,/registry/machine,/version,/actuator/**,/details exclude-url-suffixes: htm,html,js,css,map,ico,ttf,woff,png sentinel: dashboard: version: ${@project.version@:1.8.5} # Jedis 自定义相关配置 redis: host: ${REDIS_HOST:kwanyon-redis} # 端口,默认6379 port: 6379 # 超时,默认2000 timeout: 2000 # 连接超时,默认timeout connectionTimeout: 2000 # 读取超时,默认timeout soTimeout: 2000 # 密码 password: your_password # 数据库序号,默认0 database: 15 # 客户端名称 clientName: kwanyon # SSL连接,默认false ssl: false
2、启动 Sentinel 服务
sentinel-dashboard 本身是一个 springboot 项目,直接启动主启动了即可启动

成功启动后客户端页面,输入账号密码(配置文件中配置的,默认是 账号:sentinel 密码:sentinel)

思路分析
1、切入点 Controller 查找
通过 sentinel 客户端页面进行 规则设置查询等操作,最终 F12 ---->>> 浏览器控制台追踪 --->> network 可以得到 Sentinel 中 规则管理接口 Controller 的 Java 类
FlowControllerV1 和 FlowControllerV2 是不同的版本(二选一即可)GatewayFlowRuleController 是 Sentinel 整合网关,管理规则的 Controller Java 类

2、核心三接口:RuleRepository 、DynamicRuleProvider、DynamicRulePublisher
a、规则核心存储类 RuleRepository
顺着源码梳理,得到 规则存储的顶级 接口 RuleRepository 类
public interface RuleRepository<T, ID> { /** * Save one. */ T save(T entity); /** * Save all. */ List<T> saveAll(List<T> rules); /** * Delete by id */ T delete(ID id); /** * Find by id. */ T findById(ID id); /** * Find all by machine. */ List<T> findAllByMachine(MachineInfo machineInfo); /** * Find all by application. */ List<T> findAllByApp(String appName); ///** // * Find all by app and enable switch. // */ //List<T> findAllByAppAndEnable(String app, boolean enable); }
其依赖关系树如下图,可看出,官方的实现均为 基于 程序内存的实现

b、流程规则查询 核心接口 DynamicRuleProvider
public interface DynamicRuleProvider<T> { T getRules(String appName) throws Exception; }
官方已经提供了四个默认实现:FlowRuleApolloProvider、FlowRuleApiProvider(这个是默认的方案)、FlowRuleNacosProvider、FlowRuleZookeeperProvider

c、接口:DynamicRulePublisher
public interface DynamicRulePublisher<T> {
void publish(String app, T rules) throws Exception; }
官方提供了四种实现方案

FlowRuleApiProvider 通过 API 接口,调用官方提供的存储实现
实施方案确定
那么 ,以这个思路我们可以自行编写 基于 Redis (或其他存储方案)的实现类,即可达到 规则持久化方案,一下以 Redis 为例,抛砖引玉
a、自行编写的 流程规则存储方案 RuleRepository 接口的实现:

b、自行编写实现基于Redis 的流程规则查询提供方案 DynamicRuleProvider 接口的实现

c、自行实现 基于Redis 的 DynamicRulePublisher 实现方案

代码编写实现:
1、配置 Redis 客户端信息
这是我早前编写的实现方案,使用自定义配置 Redis 客户端 Jedis 的实现方案,新的版本已经变更为 RedisTemplate 方案,可以省去很多代码
a、属性配置实体
@ConfigurationProperties(prefix = "redis") public class RedisProperties implements Serializable { private static final long serialVersionUID = 1L; private String host = "localhost"; /** * 端口,默认6379 */ private int port = 6379; /** * 超时,默认2000 */ private int timeout = 2000; /** * 连接超时,默认timeout */ private int connectionTimeout = 2000; /** * 读取超时,默认timeout */ private int soTimeout = 2000; /** * 密码 */ private String password; /** * 数据库序号,默认0 */ private int database = 15; /** * 客户端名称 */ private String clientName; /** * SSL连接,默认false */ private Boolean ssl = false; }
b、自动配置类
@Configuration @EnableConfigurationProperties(value = {RedisProperties.class}) public class RedisClient { private final Logger logger = LoggerFactory.getLogger(this.getClass()); @Autowired private RedisProperties properties; private JedisPool jedisPool; @Bean public JedisPool jedisPool() { JedisPoolConfig config = new JedisPoolConfig(); jedisPool = new JedisPool(config, // 地址 properties.getHost(), // 端口 properties.getPort(), // 连接超时 properties.getConnectionTimeout(), // 读取数据超时 properties.getSoTimeout(), // 密码 properties.getPassword(), // 默认数据库序号 properties.getDatabase(), // 客户端名 properties.getclientName(), // 是否使用SSL properties.getSsl(), // SSL相关,使用默认 null, null, null); logger.info("Sentinel 中整合的 Redis 客户端工具 Jedis 配置完成"); return jedisPool; } public Jedis getJedis() { Jedis jedis = jedisPool.getResource(); jedis.select(properties.getDatabase()); return jedis; } public void close(Jedis jedis) { if (null != jedis) { jedis.close(); } } }
c、流控规则Id 生成器
@Component public class RedisIdGenerator { @Autowired private RedisClient client; public long nextId(String key) { Jedis jedis = null; try { jedis = client.getJedis(); return jedis.incrBy(key, 1); }finally { client.close(jedis); } } }
2、RuleRepository 接口的实现
a、公共代码抽象基类(RuleRepository 接口聚合了 DynamicRulePublisher 和 DynamicRuleProvider 接口)
public abstract class InRedisRuleRepositoryAdapter<T extends RuleEntity> implements RuleRepository<T, Long> { protected final Logger logger = LoggerFactory.getLogger(FlowControllerV1.class); protected final AbstractRedisRulePublisher rulePublisher; protected final AbstractRedisRuleProvider ruleProvider;
@Autowired protected RedisIdGenerator redisIdGenerator; public InRedisRuleRepositoryAdapter(AbstractRedisRulePublisher rulePublisher, AbstractRedisRuleProvider ruleProvider) { this.rulePublisher = rulePublisher; this.ruleProvider = ruleProvider; } /** * 保存 规则 * @param entity * @return */ @Override public T save(T entity) { if (entity.getId() == null) { entity.setId(nextId()); } entity = this.preProcess(entity); //List<T> entities = this.findAllByApp(entity.getApp()); // entities.add(entity); // 保存到 Redis rulePublisher.publish(entity.getApp(), List.of(entity)); return entity; } /** * 批量保存(从持久化存储中加载来的数据存储到 内存中) * @param rules * @return */ @Override public List<T> saveAll(List<T> rules) { // 从 Redis 中处理 List<T> entities = new ArrayList<>(); for (T rule : rules) { T entity = this.save(rule); entities.add(entity); } // 从 程序内存中处理 // List<FlowRuleEntity> flowRuleEntities = repository.saveAll(rules); return entities; } /** * 删除规则 * @param id * @return */ @Override public T delete(Long id) { // 先查询出 规则实体对象 T entity = (T) ruleProvider.findById(id); logger.warn("##### 删除规则: {}", JSON.toJSONString(entity)); rulePublisher.delete(entity); // 内存中删除 // entity = repository.delete(id); return entity; } /** * 指定Id 查询 * * @param id * @return */ @Override public T findById(Long id) { // 或者从程序内存中获取 // FlowRuleEntity entity = repository.findById(id); // 从 Redis 中获取 T entity = (T) ruleProvider.findById(id); return entity; } /** * 按条件查询 * * @param machineInfo * @return */ @Override public List<T> findAllByMachine(MachineInfo machineInfo) { // 从内存中获取 // List<FlowRuleEntity> allByMachine = repository.findAllByMachine(machineInfo); // 从Redis 中获取 List<T> allByMachine = ruleProvider.findAllByMachine(machineInfo); return allByMachine; } /** * 通过 appName 查询 * * @param appName valid app name * @return */ @Override public List<T> findAllByApp(String appName) { List<T> rules = ruleProvider.getRules(appName); // 或者村内存中获取 // List<FlowRuleEntity> rules = repository.findAllByApp(appName); return rules; } protected T preProcess(T entity) { return entity; } /** * Get next unused id. * * @return next unused id */ abstract protected long nextId(); }
b、网关流控规则存储实现
@Component public class InRedisGatewayFlowRuleStore extends InRedisRuleRepositoryAdapter<GatewayFlowRuleEntity> { public InRedisGatewayFlowRuleStore(GatewayFlowRulePublisher rulePublisher, GatewayFlowRuleProvider ruleProvider) { super(rulePublisher, ruleProvider); } @Override protected long nextId() { return redisIdGenerator.nextId(Constants.RULE_FLOW_GATEWAY_ID_KEY); } }
c、流控规则存储实现
@Component public class InRedisFlowRuleStore extends InRedisRuleRepositoryAdapter<FlowRuleEntity> { //@Autowired //private InMemoryRuleRepositoryAdapter<FlowRuleEntity> repository; public InRedisFlowRuleStore(RedisFlowRulePublisher rulePublisher, RedisFlowRuleProvider ruleProvider) { super(rulePublisher, ruleProvider); } public long nextId() { return redisIdGenerator.nextId(Constants.RULE_FLOW_RULE_ID_KEY); } protected FlowRuleEntity preProcess(FlowRuleEntity entity) { if (entity != null && entity.isClusterMode()) { ClusterFlowConfig config = entity.getClusterConfig(); if (config == null) { config = new ClusterFlowConfig(); entity.setClusterConfig(config); } // Set cluster rule id. config.setFlowId(entity.getId()); } return entity; } }
3、DynamicRuleProvider 接口的实现
a、公共代码抽象基类
public abstract class AbstractRedisRuleProvider<T extends RuleEntity> implements DynamicRuleProvider<List<T>> { protected final Logger logger = LoggerFactory.getLogger(this.getClass()); @Autowired private RedisClient client; @Override public List<T> getRules(String appName) { Assert.notNull(appName, "应用名称不能为空"); logger.info("拉取redis流控规则开始: {}", appName); String key = this.appRuleKey() + appName; Jedis jedis = null; List<T> rules = new ArrayList<>(); try { jedis = client.getJedis(); Map<String, String> rulesMap = jedis.hgetAll(key); if (null != rulesMap && !rulesMap.isEmpty()) { for (Map.Entry<String, String> entry : rulesMap.entrySet()) { rules.add(JSON.parseObject(entry.getValue(), this.genericsClazz())); } } } finally { client.close(jedis); } logger.info("拉取redis流控规则成功, 规则数量: {}", CollectionUtils.size(rules)); return rules; } /** * 指定Id 查询应用 * * @param id * @return */ public T findById(Long id) { Jedis jedis = null; try { jedis = client.getJedis(); // 从所有规则组中查找 String ruleStr = jedis.hget(this.allRuleKey(), id + ""); if (null == ruleStr) return null; return JSON.parseObject(ruleStr, this.genericsClazz()); } finally { client.close(jedis); } } public List<T> findAllByMachine(MachineInfo machineInfo) { if (null == machineInfo) return null; Jedis jedis = null; try { jedis = client.getJedis(); // 从所有规则组中查找 List<T> rules = this.getRules(machineInfo.getApp()); // 路由为空 if (rules.isEmpty()) return rules; List<T> entities = new ArrayList<>(rules.size()); for (T rule : rules) { MachineInfo compare = MachineInfo.of(rule.getApp(), rule.getIp(), rule.getPort()); if (Objects.equals(compare, machineInfo)) { entities.add(rule); } } return entities; } finally { client.close(jedis); } } /** * 泛型clazz * * @return */ protected abstract Class<T> genericsClazz(); /** * 基于 App 存储的 key * * @return */ protected abstract String appRuleKey(); /** * 全量规则存储的 key * * @return */ protected abstract String allRuleKey(); }
b、网关流控规则实现
@Component public class GatewayFlowRuleProvider extends AbstractRedisRuleProvider<GatewayFlowRuleEntity> { @Override protected Class<GatewayFlowRuleEntity> genericsClazz() { return GatewayFlowRuleEntity.class; } @Override protected String appRuleKey() { return Constants.APP_RULE_FLOW_KEY; } @Override protected String allRuleKey() { return Constants.ALL_RULE_FLOW_GATEWAY_KEY; } }
c、流控规则实现
@Component("redisFlowRuleProvider")
public class RedisFlowRuleProvider extends AbstractRedisRuleProvider<FlowRuleEntity> {
@Override
protected Class<FlowRuleEntity> genericsClazz() {
return FlowRuleEntity.class;
}
@Override
protected String appRuleKey() {
return Constants.APP_RULE_FLOW_KEY;
}
@Override
protected String allRuleKey() {
return Constants.ALL_RULE_FLOW_KEY;
}
}
3、DynamicRulePublisher 接口的实现逻辑
a、公共代码抽象基类
public abstract class AbstractRedisRulePublisher<T extends RuleEntity> implements DynamicRulePublisher<List<T>> { private final Logger logger = LoggerFactory.getLogger(this.getClass()); @Autowired private RedisClient client; @Override public void publish(String appName, List<T> rules) { Assert.notNull(appName, "应用名称不能为空"); Assert.notEmpty(rules, "策略规则不为空"); logger.info("推送流控规则开始, 应用名: {}, 规则数量: {}", appName, rules.size()); String ruleKey = this.appRuleKey() + appName; Jedis jedis = null; try { jedis = client.getJedis(); for (T rule : rules) { // 以 Hash 形式存储到 应用规则中 jedis.hset(ruleKey, rule.getId() + "", JSON.toJSONString(rule)); // 以 Hash 形式存储 到所有规则中 jedis.hset(this.allRuleKey(), rule.getId() + "", JSON.toJSONString(rule)); } } finally { client.close(jedis); } } public void delete(T entity) { Jedis jedis = null; try { jedis = client.getJedis(); // 从所有规则组中删除 jedis.hdel(this.allRuleKey(), entity.getId() + ""); // 从规则运用组中删除 jedis.hdel(this.appRuleKey() + entity.getApp(), entity.getId() + ""); } finally { client.close(jedis); } } /** * 基于 App 存储的 key * * @return */ protected abstract String appRuleKey(); /** * 全量规则存储的 key * * @return */ protected abstract String allRuleKey(); }
b、整合网关的实现
@Component public class GatewayFlowRulePublisher extends AbstractRedisRulePublisher<GatewayFlowRuleEntity> { @Override protected String appRuleKey() { return Constants.APP_RULE_FLOW_KEY; } @Override protected String allRuleKey() { return Constants.ALL_RULE_FLOW_GATEWAY_KEY; } }
c、流控规则实现
@Component("redisFlowRulePublisher")
public class RedisFlowRulePublisher extends AbstractRedisRulePublisher<FlowRuleEntity> {
@Override
protected String appRuleKey() {
return Constants.APP_RULE_FLOW_KEY;
}
@Override
protected String allRuleKey() {
return Constants.ALL_RULE_FLOW_KEY;
}
}
4、将自行实现的实现类注入到 Controller 替换默认的
a、FlowControllerV1

b、GatewayFlowRuleController

常量:
public interface Constants { /** * 流控规则 存储的key,基于 appName 的 key */ String APP_RULE_FLOW_KEY = "sentinel:flow:rule:"; /** * 所有规则存储的 key */ String ALL_RULE_FLOW_KEY = "sentinel:flow:rule:all"; String ALL_RULE_FLOW_GATEWAY_KEY = "sentinel:flow:rule:all"; /** * 流控规则数量 */ String RULE_FLOW_CHANNEL_PREFIX = "sentinel:flow:channel:"; /** * 流控规则自增Id 的记录 */ String RULE_FLOW_RULE_ID_KEY = "sentinel:flow:id:rule"; /** * 网关流控规则的自增Id */ String RULE_FLOW_GATEWAY_ID_KEY = "sentinel:flow:id:gateway"; }
...
本文来自博客园,作者:Vermeer,转载请注明原文链接:https://www.cnblogs.com/chxlay/p/16839285.html

浙公网安备 33010602011771号