Spring Cloud Gateway是Spring Cloud官方推出的第二代网关框架,取代Zuul网关。网关做为流量的,在微服务系统中有着很是做用,网关常见的功能有路由转发、权限校验、限流控制等做用。 Sentinel是阿里开源的项目,提供了流量控制、熔断降级、系统负载保护等多个维度来保障服务之间的稳定性。(https://github.com/alibaba/Sentinel)
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> <version>2.1.2.RELEASE</version> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> <version>2.1.7.RELEASE</version> </dependency>
Route(路由) | 路由是网关的基本单元,由ID、URI、一组Predicate、一组Filter组成,根据Predicate进行匹配转发。 | |
Predicate(谓语、断言) | 路由转发的判断条件,目前SpringCloud Gateway支持多种方式,常见如:Path、Query、Method、Header等,写法必须遵循 key=vlue的形式 | |
Filter(过滤器) | 过滤器是路由转发请求时所通过的过滤逻辑,可用于修改请求、响应内容 |
//经过配置文件配置 spring: cloud: gateway: routes: - id: gate_route uri: http://localhost:9023 predicates: ## 当请求的路径为gate、rule开头的时,转发到http://localhost:9023服务器上 - Path=/gate/**,/rule/** ### 请求路径前加上/app filters: - PrefixPath=/app
规则 | 实例 | 说明 |
---|---|---|
Path | - Path=/gate/,/rule/ | ## 当请求的路径为gate、rule开头的时,转发到http://localhost:9023服务器上 |
Before | - Before=2017-01-20T17:42:47.789-07:00[America/Denver] | 在某个时间以前的请求才会被转发到 http://localhost:9023服务器上 |
After | - After=2017-01-20T17:42:47.789-07:00[America/Denver] | 在某个时间以后的请求才会被转发 |
Between | - Between=2017-01-20T17:42:47.789-07:00[America/Denver],2017-01-21T17:42:47.789-07:00[America/Denver] | 在某个时间段之间的才会被转发 |
Cookie | - Cookie=chocolate, ch.p | 名为chocolate的表单或者知足正则ch.p的表单才会被匹配到进行请求转发 |
Header | - Header=X-Request-Id, \d+ | 携带参数X-Request-Id或者知足\d+的请求头才会匹配 |
Host | - Host=www.hd123.com | 当主机名为www.hd123.com的时候直接转发到http://localhost:9023服务器上 |
Method | - Method=GET | 只有GET方法才会匹配转发请求,还能够限定POST、PUT等请求方式 |
过滤规则 | 实例 | 说明 |
---|---|---|
PrefixPath | - PrefixPath=/app | 在请求路径前加上app |
RewritePath | - RewritePath=/test, /app/test | 访问localhost:9022/test,请求会转发到localhost:8001/app/test |
SetPath | SetPath=/app/{path} | 经过模板设置路径,转发的规则时会在路径前增长app,{path}表示原请求路径 |
注:当配置多个filter时,优先定义的会被调用,剩余的filter将不会生效java
@Bean public RouteLocator customRouteLocator(RouteLocatorBuilder builder) { return builder.routes() .route("path_route", r -> r.path("/get") .uri("http://httpbin.org")) .route("host_route", r -> r.host("*.myhost.org") .uri("http://httpbin.org")) .route("rewrite_route", r -> r.host("*.rewrite.org") .filters(f -> f.rewritePath("/foo/(?<segment>.*)", "/${segment}")) .uri("http://httpbin.org")) .route("hystrix_route", r -> r.host("*.hystrix.org") .filters(f -> f.hystrix(c -> c.setName("slowcmd"))) .uri("http://httpbin.org")) .route("hystrix_fallback_route", r -> r.host("*.hystrixfallback.org") .filters(f -> f.hystrix(c -> c.setName("slowcmd").setFallbackUri("forward:/hystrixfallback"))) .uri("http://httpbin.org")) .route("limit_route", r -> r .host("*.limited.org").and().path("/anything/**") .filters(f -> f.requestRateLimiter(c -> c.setRateLimiter(redisRateLimiter()))) .uri("http://httpbin.org")) .build(); }
@Component @Slf4j public class NacosDynamicRouteService implements ApplicationEventPublisherAware { private String dataId = "gateway-router"; private String group = "DEFAULT_GROUP"; @Value("${spring.cloud.nacos.config.server-addr}") private String serverAddr; @Autowired private RouteDefinitionWriter routeDefinitionWriter; private ApplicationEventPublisher applicationEventPublisher; private static final List<String> ROUTE_LIST = new ArrayList<>(); @PostConstruct public void dynamicRouteByNacosListener() { try { ConfigService configService = NacosFactory.createConfigService(serverAddr); configService.getConfig(dataId, group, 5000); configService.addListener(dataId, group, new Listener() { @Override public void receiveConfigInfo(String configInfo) { clearRoute(); try { if (StringUtil.isNullOrEmpty(configInfo)) {//配置被删除 return; } List<RouteDefinition> gatewayRouteDefinitions = JSONObject.parseArray(configInfo, RouteDefinition.class); for (RouteDefinition routeDefinition : gatewayRouteDefinitions) { addRoute(routeDefinition); } publish(); } catch (Exception e) { log.error("receiveConfigInfo error" + e); } } @Override public Executor getExecutor() { return null; } }); } catch (NacosException e) { log.error("dynamicRouteByNacosListener error" + e); } } private void clearRoute() { for (String id : ROUTE_LIST) { this.routeDefinitionWriter.delete(Mono.just(id)).subscribe(); } ROUTE_LIST.clear(); } private void addRoute(RouteDefinition definition) { try { routeDefinitionWriter.save(Mono.just(definition)).subscribe(); ROUTE_LIST.add(definition.getId()); } catch (Exception e) { log.error("addRoute error" + e); } } private void publish() { this.applicationEventPublisher.publishEvent(new RefreshRoutesEvent(this.routeDefinitionWriter)); } @Override public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { this.applicationEventPublisher = applicationEventPublisher; }
[{ "filters": [], "id": "baidu_route", "order": 0, "predicates": [{ "args": { "pattern": "/baidu" }, "name": "Path" }], "uri": "https://www.baidu.com" }]
spring: cloud: gateway: globalcors: cors-configurations: '[/**]': allowed-origins: "*" allowed-headers: "*" allow-credentials: true allowed-methods: - GET - POST - DELETE - PUT - OPTION
## maven依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>de.codecentric</groupId> <artifactId>spring-boot-admin-starter-client</artifactId> <version>2.1.0</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> ##配置项 spring: application: name: mas-cloud-gateway boot: admin: client: ### 本地搭建的admin-server url: http://localhost:8011 eureka: client: registerWithEureka: true fetchRegistry: true healthcheck: enabled: true serviceUrl: defaultZone: http://localhost:6887/eureka/ enabled: true feign: sentinel: enabled: true management: endpoints: web: exposure: include: '*' endpoint: health: show-details: ALWAYS
使用Sentinel做为gateWay的限流、降级、系统保护工具
资源是 Sentinel 的关键概念。它能够是 Java 应用程序中的任何内容,例如,由应用程序提供的服务,或由应用程序调用的其它应用提供的服务,甚至能够是一段代码。在接下来的文档中都会用资源来描述代码块。只要经过 Sentinel API 定义的代码,就是资源,可以被 Sentinel 保护起来。大部分状况下,可使用方法签名,URL,甚至服务名称做为资源名来标示资源。
围绕资源的实时状态设定的规则,能够包括流量控制规则、熔断降级规则以及系统保护规则。全部规则能够动态实时调整。
<!--alibaba 流量卫士--> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-core</artifactId> <version>${sentinel.version}</version> </dependency>
同时也将SpringCloud、GateWay整合,加入以下配置git
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-spring-cloud-gateway-adapter</artifactId> <version>1.7.1</version> </dependency>
@Bean//拦截请求 @Order(Ordered.HIGHEST_PRECEDENCE) public GlobalFilter sentinelGatewayFilter() { return new SentinelGatewayFilter(); }
@Bean public SentinelResourceAspect sentinelResourceAspect() { return new SentinelResourceAspect(); }
public SentinelConfig(ObjectProvider<List<ViewResolver>> viewResolversProvider, ServerCodecConfigurer serverCodecConfigurer) { this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList); this.serverCodecConfigurer = serverCodecConfigurer; } @Bean//自定义异常 @Order(Ordered.HIGHEST_PRECEDENCE) public ExceptionHandler sentinelGatewayBlockExceptionHandler() { // Register the block exception handler for Spring Cloud Gateway. return new ExceptionHandler(viewResolvers, serverCodecConfigurer); }
private Mono<Void> writeResponse(ServerResponse response, ServerWebExchange exchange) { ServerHttpResponse serverHttpResponse = exchange.getResponse(); serverHttpResponse.getHeaders().add("Content-Type", "application/json;charset=UTF-8"); MasResponse<String> stringMasResponse = MasResponse.fail( "限流了" ); byte[] datas = JSON.toJSONString(stringMasResponse).getBytes(StandardCharsets.UTF_8); DataBuffer buffer = serverHttpResponse.bufferFactory().wrap(datas); return serverHttpResponse.writeWith(Mono.just(buffer)); }
不须要额外的配置,sentinel就已经能够正常工做了
当经过 GatewayRuleManager 加载网关流控规则(GatewayFlowRule)时,不管是否针对请求属性进行限流,Sentinel 底层都会将网关流控规则转化为热点参数规则(ParamFlowRule),存储在GatewayRuleManager 中,与正常的热点参数规则相隔离。转换时 Sentinel 会根据请求属性配置,为网关流控规则设置参数索引(idx),并同步到生成的热点参数规则中。 外部请求进入 API Gateway 时会通过 Sentinel 实现的 filter,其中会依次进行 路由/API 分组匹配、请求属性解析和参数组装。Sentinel 会根据配置的网关流控规则来解析请求属性,并依照参数索引顺序组装参数数组,最终传入SphU.entry(res, args) 中。Sentinel API Gateway Adapter Common 模块向 Slot Chain 中添加了一个 GatewayFlowSlot,专门用来作网关规则的检查。GatewayFlowSlot 会从GatewayRuleManager中提取生成的热点参数规则,根据传入的参数依次进行规则检查。若某条规则不针对请求属性,则会在参数最后一个位置置入预设的常量,达到普通流控的效果。
##对应代码中定义的顺序 ProcessorSlotChain chain = new DefaultProcessorSlotChain(); // Prepare slot chain.addLast(new NodeSelectorSlot()); chain.addLast(new ClusterBuilderSlot()); // Stat slot chain.addLast(new LogSlot()); chain.addLast(new StatisticSlot()); // Rule checking slot chain.addLast(new AuthoritySlot()); chain.addLast(new SystemSlot()); chain.addLast(new GatewayFlowSlot()); chain.addLast(new ParamFlowSlot()); chain.addLast(new FlowSlot()); chain.addLast(new DegradeSlot()); return chain;
NodeSelectorSlot 负责收集资源的路径,并将这些资源的调用路径,以树状结构存储起来,用于根据调用路径来限流降级;github
ClusterBuilderSlot 则用于存储资源的统计信息以及调用者信息,例如该资源的 RT, QPS, thread count 等等,这些信息将用做为多维度限流,降级的依据;web
LogSlot:用来记录系统日志,当前经过数量、拒绝数量等正则表达式
StatistcSlot 则用于记录,统计不一样纬度的 runtime 信息;redis
AuthorizationSlot 则根据黑白名单,来作黑白名单控制;spring
SystemSlot 则经过系统的状态,当前的运行状态、CPU占用率等,来控制总的入口流量;json
GatewayFlowSlot::网关限流规则api
ParamFlowSlot:参数值限流规则定义跨域
FlowSlot 则用于根据预设的限流规则,以及前面 slot 统计的状态,来进行限流
DegradeSlot 则经过统计信息,以及预设的规则,来作熔断降级;
注:Sentinel 1.6.0以后的版本引入了 Sentinel API Gateway Adapter Common 模块,此模块中包含网关限流的规则和自定义 API的实体和管理逻辑
resource | 指定访问的资源名称 |
limitApp | 限制来源,可经过英文逗号(,)指定多个来源限定,一般为请求者IP或者消费者名称 |
strategy | 限制模式,白名单仍是黑名单,默认为白名单,启用后只有在白名单的来源才能经过 |
### 只容许来源为127.0.0.1或者localhost的请求才能经过 AuthorityRule rule = new AuthorityRule(); rule.setResource("info"); rule.setStrategy(RuleConstant.AUTHORITY_WHITE); rule.setLimitApp("127.0.0.1,localhost"); AuthorityRuleManager.loadRules(Collections.singletonList(rule)); ###黑名单相似,来源为指定的值时将以前拒绝请求 rule.setStrategy(RuleConstant.AUTHORITY_BLACK); rule.setLimitApp("127.0.0.1,localhost");
注:当其余的来源的应用访问时,该请求将没法经过,若是未查找到该请求来源将直接经过。
highestSystemLoad | 最大的Load,目前只针对Unix/Linux生效,默认为-1不生效 |
highestCpuUsage | 最高CPU使用率,范围[0-1],高于该值时将拒绝全部请求,作降级处理 |
qps | 全部入口资源的QPS,默认为-1不生效 |
avgRt | 全部入口流量的平均响应时间,ms为单位 |
maxThread | 入口流量的最大并发数,默认为-1不生效 |
##限制QPS为3,平均返回时间为200ms List<SystemRule> rules = new ArrayList<>(); SystemRule rule = new SystemRule(); // rule.setHighestSystemLoad(3.0);//系统负载 只针对Linux/unix rule.setHighestCpuUsage(-1);//当前系统的 CPU 使用率 rule.setAvgRt(200);//全部入口流量的平均响应时间 ms rule.setQps(3); rule.setMaxThread(500); rules.add(rule); SystemRuleManager.loadRules(rules);
访问本地API服务器,请求第一次时能够获得正确的返回信息,可是第二次访问时,因为上一次的请求耗时544/2ms,第二次请求将不会被经过
请求次数:2 请求路径:/api/query 访问成功了!! 我是API服务器 {"echoCode":500,"echoMessage":"限流了","success":false} 544ms
更改系统规则中的QPS参数,不限制平均响应时间
List<SystemRule> rules = new ArrayList<>(); SystemRule rule = new SystemRule(); // rule.setHighestSystemLoad(3.0);//系统负载 只针对Linux/unix rule.setHighestCpuUsage(-1);//当前系统的 CPU 使用率 //rule.setAvgRt(200);//全部入口流量的平均响应时间 ms rule.setQps(3); rule.setMaxThread(500); rules.add(rule); SystemRuleManager.loadRules(rules);
因为咱们设置的QPS为3,那么当咱们请求10次时,前三次能够成功请求,但第四次开始,请求将不会被处理。
请求次数:10 请求路径:/api/query 访问成功了!! 我是API服务器 访问成功了!! 我是API服务器 访问成功了!! 我是API服务器 访问成功了!! 我是API服务器 {"echoCode":500,"echoMessage":"限流了","success":false} {"echoCode":500,"echoMessage":"限流了","success":false} {"echoCode":500,"echoMessage":"限流了","success":false} {"echoCode":500,"echoMessage":"限流了","success":false} {"echoCode":500,"echoMessage":"限流了","success":false} {"echoCode":500,"echoMessage":"限流了","success":false} 530ms
resource | 资源名称,能够是网关中的 route 名称或者用户自定义的API 分组名称。 |
resourceMode | 规则是针对 API Gateway 的route(RESOURCE_MODE_ROUTE_ID)仍是用户在 Sentinel 中定义的API 分组(RESOURCE_MODE_CUSTOM_API_NAME),默认是route。 |
grade: | 限流指标维度,同限流规则的grade 字段。 |
count: | 限流阈值 |
intervalSec: | 统计时间窗口,单位是秒,默认是1 秒(目前仅对参数限流生效)。 |
controlBehavior | 流量整形的控制效果,同限流规则的 controlBehavior 字段,目前支持快速失败和匀速排队两种模式,默认是快速失败。 |
burst: | 应对突发请求时额外容许的请求数目(目前仅对参数限流生效)。 |
maxQueueingTimeoutMs: | 匀速排队模式下的最长排队时间,单位是毫秒,仅在匀速排队模式下生效。 |
paramItem: | 参数限流配置。若不提供,则表明不针对参数进行限流,该网关规则将会被转换成普通流控规则;不然会转换成热点规则。其中的字段: |
parseStrategy: | 从请求中提取参数的策略,目前支持提取来源 |
fieldName: | 若提取策略选择 Header 模式或 URL 参数模式,则须要指定对应的 header 名称或 URL 参数名称。 |
pattern 和 matchStrategy: | 为后续参数匹配特性预留,目前未实现。 |
## 将包含content请求路径都转发到CMS服务器上,其余求都转发到product服务器上 - id: cms_route uri: lb://mas-cms-service predicates: - Path=/{tenant}/service/content/** - id: product_route uri: lb://mas-product-service predicates: - Path=/**
路由转发正常
Set<GatewayFlowRule> rules = new HashSet<>(); rules.add(new GatewayFlowRule("cms_route") .setResourceMode(SentinelGatewayConstants.RESOURCE_MODE_ROUTE_ID)//路由规则 .setCount(3) .setGrade(RuleConstant.FLOW_GRADE_QPS)//限制规则为QPS .setIntervalSec(1) ); GatewayRuleManager.loadRules(rules);
针对同一个ROUTE_ID的请求,后面的将会被拦截,而不一样ROUTE_ID的请求将不会受到影响。
Set<ApiDefinition> definitions = new HashSet<>(); //显式申明API ApiDefinition placeApi = new ApiDefinition("place_flow") .setPredicateItems(new HashSet<ApiPredicateItem>() {{ add(new ApiPathPredicateItem().setPattern("/lh9999/service/content/place/**") .setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX)); }}); definitions.add(placeApi); GatewayApiDefinitionManager.loadApiDefinitions(definitions); //设置规则 Set<GatewayFlowRule> rules = new HashSet<>(); rules.add(new GatewayFlowRule("place_flow") .setResourceMode(SentinelGatewayConstants.RESOURCE_MODE_CUSTOM_API_NAME) .setCount(3) .setGrade(RuleConstant.FLOW_GRADE_QPS) .setIntervalSec(1) ); GatewayRuleManager.loadRules(rules);
当同时请求服务器时,只要路径符合**/lh9999/service/content/place/****形式的请求都适用上面的规则,这里咱们使用线程池模拟并发请求,请求第四次时将直接失败,限流效果仍是很明显的。
热点参数规则(ParamFlowRule)相似于流量控制规则(FlowRule)
属性 | 说明 | 默认值 |
---|---|---|
resource | 资源名,必填 | |
count 限流阈值,必填 | ||
grade | 限流模式 | QPS 模式 |
paramIdx | 热点参数的索引,必填,对应 SphU.entry(xxx, args) 中的参数索引位置 | |
paramFlowItemList | 参数例外项,能够针对指定的参数值单独设置限流阈值,不受前面 count 阈值的限制。仅支持基本类型 | |
clusterMode | 是不是集群参数流控规则 | false |
clusterConfig | 集群流控相关配置 |
热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流能够看作是一种特殊的流量控制,仅对包含热点参数的资源调用生效。
public static Entry entry(String name, EntryType type, int count, Object... args) throws BlockException public static Entry entry(Method method, EntryType type, int count, Object... args) throws BlockException
@PostMapping("{account}/checkLogin") @SentinelResource("checkLogin") public BaasResponse<String> checkLogin(@PathVariable(value = "account") String account, @RequestBody String password) { try { SphU.entry("checkLogin", EntryType.IN, 1, account); ...chekcLogin } catch (BlockException e) { BaasResponse<String> response = new BaasResponse<>(); response.setSuccess(false); response.setEchoMessage(account + "被限制登陆"); return response; } return BaasResponse.success(account + "登陆成功"); }
制定限制规则,时间窗口限制为1s,在这1s中全部用户最多容许登陆5次,经过ParamFlowRuleManager.loadRuls咱们能够很容易制定限流规则
List<ParamFlowRule> rules = new ArrayList<>(); ParamFlowRule rule = new ParamFlowRule(); //阈值类型:只支持QPS rule.setGrade(RuleConstant.FLOW_GRADE_QPS); //阈值 rule.setCount(5); //资源名 rule.setResource("checkLogin"); rule.setParamIdx(0);//指配热点参数的下标 //统计窗口时间长度 rule.setDurationInSec(1); List<ParamFlowItem> items = new ArrayList<>(); ParamFlowItem item = new ParamFlowItem(); item.setClassType(String.class.getTypeName()); item.setCount(5); items.add(item); rule.setParamFlowItemList(items); rules.add(rule); ParamFlowRuleManager.loadRules(rules);
同一时刻tom、jack同时登陆,tom登陆两次,jack登陆了10次,那么根据咱们制定的规则,jack的行为将会被限制,其余用户将不会受到影响
若是上面的代码配置不够灵活能够经过Sentinel提供的用户界面sentinel-dashboard实时进行规则的制定,可同时管理多个客户端
spring: cloud: sentinel: transport: ## VM ##-Djava.net.preferIPv4Stack=true -Dcsp.sentinel.dashboard.server=localhost:8080 -Dcsp.sentinel.api.port=8666 -Dproject.name=gateway -Dcsp.sentinel.app.type=1 dashboard: localhost:8880 port: 8880
下载完成可直接启动,java -jar sentinel-dashboard.jar,登陆dashboard,就能够很清楚的看到众多限流、熔断等功能
同时对于刚刚创建的热点参数,还有其余特殊的限制功能,如图所示,能够限制某些特殊值不处理:
规则持久化:在dashboard中配置的规则都是存储在内存中的,dashboard 是经过 transport 模块来获取每一个 Sentinel 客户端中的规则的,获取到的规则经过 RuleRepository 接口保存在 Dashboard 的内存中,若是在 Dashboard 页面中更改了某个规则,也会调用 transport 模块提供的接口将规则更新到客户端中去。
试想这样一种状况,客户端链接上 Dashboard 以后,咱们在 Dashboard 上为客户端配置好了规则,并推送给了客户端。这时因为一些因素客户端出现异常,服务不可用了,当客户端恢复正常再次链接上 Dashboard 后,这时全部的规则都丢失了,咱们还须要从新配置一遍规则,这确定不是咱们想要的。
把本来保存在 内存中的规则,持久化一份副本出去。这样下次客户端重启后,能够从持久化的副本中把数据 load 进内存中,这样就不会丢失规则了,以下图所示:
Sentinel 为咱们提供了两个接口来实现规则的持久化,他们分别是:ReadableDataSource 和 WritableDataSource。由于一般各类持久化的数据源已经提供了具体的将数据持久化的方法了,咱们只须要把数据从持久化的数据源中获取出来,转成咱们须要的格式就能够了。
下面咱们来看一下 ReadableDataSource 接口的具体的定义:
public interface ReadableDataSource<S, T> { // 从数据源中读取原始的数据 S readSource() throws Exception; // 将原始数据转换成咱们所需的格式 T loadConfig() throws Exception; // 获取该种数据源的SentinelProperty对象 SentinelProperty<T> getProperty(); }
接口很简单,最重要的就是这三个方法,另外 Sentinel 还为咱们提供了一个抽象类:AbstractDataSource,该抽象类中实现了两个方法,具体的数据源实现类只须要实现一个 readSource 方法便可,具体的代码以下:
public abstract class AbstractDataSource<S, T> implements ReadableDataSource<S, T> { // Converter接口负责转换数据 protected final Converter<S, T> parser; // SentinelProperty接口负责触发PropertyListener // 的configUpdate方法的回调 protected final SentinelProperty<T> property; public AbstractDataSource(Converter<S, T> parser) { if (parser == null) { throw new IllegalArgumentException("parser can't be null"); } this.parser = parser; this.property = new DynamicSentinelProperty<T>(); } @Override public T loadConfig() throws Exception { return loadConfig(readSource()); } public T loadConfig(S conf) throws Exception { return parser.convert(conf); } @Override public SentinelProperty<T> getProperty() { return property; } }
每一个具体的 DataSource 实现类须要作三件事:
DataSource 扩展常见的实现方式有:
拉模式:客户端主动向某个规则管理中心按期轮询拉取规则,这个规则中心能够是 RDBMS、文件,甚至是 VCS 等。这样作的方式是简单,缺点是没法及时获取变动; 推模式:规则中心统一推送,客户端经过注册监听器的方式时刻监听变化,好比使用 Nacos、Zookeeper 等配置中心。这种方式有更好的实时性和一致性保证。
Pull-based: 文件、Consul Push-based: ZooKeeper, Redis, Nacos, Apollo, etcd
<dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-extension</artifactId> </dependency>
接入的方法以下:
private void init() throws Exception { // 保存了限流规则的文件的地址 String flowRuleName = yourFlowRuleFileName(); Converter<String, List<FlowRule>> parser = source -> JSON.parseObject(source,new TypeReference<List<FlowRule>>() {}); // 建立文件规则数据源 FileRefreshableDataSource<List<FlowRule>> flowRuleDataSource = new FileRefreshableDataSource<>(flowRuleName, parser); // 将Property注册到 RuleManager 中去 FlowRuleManager.register2Property(flowRuleDataSource.getProperty()); }
在系统启动的时候调用该数据源注册的方法,不然不会生效的。具体的方式有不少,能够借助 Spring 来初始化该方法,也能够自定义一个类来实现 Sentinel 中的 InitFunc 接口来完成初始化。
Sentinel 会在系统启动的时候经过 spi 来扫描 InitFunc 的实现类,并执行 InitFunc 的 init 方法
Redis 数据源的实现类是 RedisDataSource。
首先引入依赖:
<dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-redis</artifactId> </dependency>
接入方法以下:
private void init() throws Exception { Converter<String, List<FlowRule>> parser = source -> JSON.parseObject(source,new TypeReference<List<FlowRule>>() {}); RedisConnectionConfig config = RedisConnectionConfig.builder() .withHost(redisHost) .withPort(redisPort) .build(); ReadableDataSource<String, List<FlowRule>> redisDataSource = new RedisDataSource<>(config, ruleKey, channel, parser); FlowRuleManager.register2Property(redisDataSource.getProperty()); }
<dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> </dependency>
接入方法以下:
private void init() throws Exception { Converter<String, List<FlowRule>> parser = source -> JSON.parseObject(source,new TypeReference<List<FlowRule>>() {}); ReadableDataSource<String, List<FlowRule>> nacosDataSource = new NacosDataSource<>(remoteAddress, groupId, dataId, parser); FlowRuleManager.register2Property(nacosDataSource.getProperty()); }
<dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-zookeeper</artifactId> </dependency>
接入方法以下:
private void init() throws Exception { String remoteAddress = yourRemoteAddress(); String path = yourPath(); Converter<String, List<FlowRule>> parser = source -> JSON.parseObject(source,new TypeReference<List<FlowRule>>() {}); ReadableDataSource<String, List<FlowRule>> zookeeperDataSource = new ZookeeperDataSource<>(remoteAddress, path, parser); FlowRuleManager.register2Property(zookeeperDataSource.getProperty()); }
<dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-apollo</artifactId> </dependency>
接入方法以下:
private void init() throws Exception { Converter<String, List<FlowRule>> parser = source -> JSON.parseObject(source,new TypeReference<List<FlowRule>>() {}); ReadableDataSource<String, List<FlowRule>> apolloDataSource = new ApolloDataSource<>(namespaceName, ruleKey, path, defaultRules); FlowRuleManager.register2Property(apolloDataSource.getProperty()); }
能够看到5种持久化的方式基本上大同小异,主要仍是对接每种配置中心,实现数据的转换,而且监听配置中心的数据变化,当接收到数据变化后可以及时的将最新的规则更新到 RuleManager 中去就能够了。其余规则相似,可经过解析type进行区分。
注:https://github.com/alibaba/Sentinel/tree/master/sentinel-dashboard