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";
}

...

 
posted @ 2022-10-29 17:55  Vermeer  阅读(205)  评论(0)    收藏  举报