背景
反复阅读sentinel的官方文档,发现:
1、默认情况下sentinel的限流规则是写在内存里的,一旦重启,规则就会丢失
2、限流规则的持久化,提供了以下几个推荐的方式,主要分为推和拉模式:
公司一般不会直接暴露redis、ZooKeeper等给业务同学使用,看了下redis的demo是需要输入用户名密码等,无法与公司的系统融合
再考虑到使用mysql相对比较简单,容易理解,于是选择使用拉模式定时从数据库中读取数据
具体实现
然而sentinel官网并没有提供拉模式读取mysql数据库的demo,于是只能参照拉模式读取文件的方式修改一下使用:
1、定义MySQLDataSource
需要继承AutoRefreshDataSource,然后这是一个模板类,第一个参数表示从DataSource中读取的数据格式是啥样,第二个参数表示最后需要转换成啥样(在这里分别是从数据库读取出来的List<OpenApiAppIdApiQps>
, 和转换后的List<FlowRule>
)
@Service
public class MysqlRefreshableDataSource extends AutoRefreshDataSource<List<OpenApiAppIdApiQps>, List<FlowRule>> {
private static final long DEFAULT_REFRESH_MS = 3000;
@Autowired
private OpenApiAppIdApiQpsMapper readOpenApiAppIdApiQpsMapper;
public MysqlRefreshableDataSource(Converter<List<OpenApiAppIdApiQps>, List<FlowRule>> configParser) {
super(configParser, DEFAULT_REFRESH_MS);
firstLoad();
}
private void firstLoad() {
try {
List<FlowRule> newValue = loadConfig();
getProperty().updateValue(newValue);
} catch (Throwable e) {
RecordLog.info("loadConfig exception", e);
}
}
@Override
public List<OpenApiAppIdApiQps> readSource() {
return readOpenApiAppIdApiQpsMapper.selectAll();
}
@Override
public void close() throws Exception {
super.close();
}
}
然后readSource()
函数是用来自定义怎么读取规则数据源,在这里是直接读取数据表里的全部数据
附数据表
CREATE TABLE `openapi_appid_api_qps`
(
`id` bigint PRIMARY KEY NOT NULL AUTO_INCREMENT COMMENT '自增主键',
`app_id` varchar(36) NOT NULL DEFAULT '' COMMENT 'appid',
`api` varchar(36) NOT NULL DEFAULT '' COMMENT 'appid有权限访问的api',
`limit_qps` bigint NOT NULL DEFAULT 0 COMMENT 'api对某个appid的限流qps',
`create_at` bigint NOT NULL DEFAULT 0 COMMENT '创建时间戳',
`update_at` bigint NOT NULL DEFAULT 0 COMMENT '更新时间戳',
UNIQUE INDEX uk_appid_api (`app_id`, `api`)
) ENGINE = InnoDB DEFAULT CHARSET utf8mb4 COMMENT 'api对某个appid的限流qps对应表';
2、自定义如何实现数据源到flowRule的转换
在这里是通过stream直接做的对象转换:
@Configuration
public class SentinelConfiguration {
@Autowired
private MysqlRefreshableDataSource mysqlRefreshableDataSource;
@Bean
public Converter converter() {
return (Converter<List<OpenApiAppIdApiQps>, List<FlowRule>>) source ->
source.stream().map(openApiAppIdApiQps -> {
FlowRule flowRule = new FlowRule();
flowRule.setResource(openApiAppIdApiQps.getApi());
flowRule.setCount(openApiAppIdApiQps.getLimitQps());
flowRule.setLimitApp(openApiAppIdApiQps.getAppId());
flowRule.setGrade(1);
flowRule.setStrategy(0);
flowRule.setControlBehavior(0);
return flowRule;
}).collect(Collectors.toList());
}
@PostConstruct
public void doInit() {
// 自定义限流参数
WebCallbackManager.setRequestOriginParser(request -> {
String origin = request.getParameter("appid");
return origin != null ? origin : "";
});
// 自定义限流返回的响应
WebCallbackManager.setUrlBlockHandler((request, response, ex) -> {
Message message = Message.error(IExceptionCode.OPENAPI_TOO_MANY_REQ_EXCEPTION, "请求过于频繁");
response.setContentType("application/json;charset=UTF-8");
Gson gson = new GsonBuilder()
.registerTypeAdapter(LocalDateTime.class, new LocalDateTimeAdapter())
.create();
response.getOutputStream().write(gson.toJson(message).getBytes());
});
// 自定义拉取数据源
FlowRuleManager.register2Property(mysqlRefreshableDataSource.getProperty());
}
}
注意还需要在PostConstruct的时候注册一下数据源(上面的最后一行)
FlowRuleManager.register2Property(mysqlRefreshableDataSource.getProperty());
这样,就会在Bean PostConstruct的时候,注册我们自定义的数据源了。mysqlRefreshableDataSource
通过Autowired自动导入。
如此一来,我们就实现了定时从mysql读取规则数据了。
相关原理
to be continued