4.0.0
com.fengxuechao.examples
zull-sentinel-integrate-demo
1.0-SNAPSHOT
org.springframework.boot
spring-boot-starter-parent
1.5.13.RELEASE
org.springframework.cloud
spring-cloud-dependencies
Edgware.SR5
pom
import
com.alibaba.cloud
spring-cloud-alibaba-dependencies
1.5.1.RELEASE
pom
import
com.alibaba.cloud
spring-cloud-starter-alibaba-sentinel
com.alibaba.cloud
spring-cloud-alibaba-sentinel-gateway
org.springframework.cloud
spring-cloud-starter-netflix-zuul
org.springframework.cloud
spring-cloud-starter-consul-discovery
org.projectlombok
lombok
compile
org.springframework.boot
spring-boot-configuration-processor
true
redis.clients
jedis
org.apache.commons
commons-lang3
3.9
commons-io
commons-io
2.6
org.springframework.boot
spring-boot-maven-plugin
org.apache.maven.plugins
maven-compiler-plugin
8
仅显示有关 sentinel 的配置,其它与本文目的无关的省略
# sentinel
spring.cloud.sentinel.enabled=true
## sentinel dashboard【可选配置】
spring.cloud.sentinel.transport.dashboard=192.168.200.19:8090
spring.cloud.sentinel.transport.port=8919
spring.cloud.sentinel.transport.client-ip=192.168.120.81
## sentinel 过滤器顺序配置 【必须配置】
spring.cloud.sentinel.zuul.order.pre=10000
spring.cloud.sentinel.zuul.order.post=1000
spring.cloud.sentinel.zuul.order.error=-1
## sentinel 基于文件的动态规则数据源配置 【DEMO】
#spring.cloud.sentinel.datasource.ds.file.file=classpath:sentinel/rules/gw_flow.json
#spring.cloud.sentinel.datasource.ds.file.rule-type=gw_flow
spring.cloud.sentinel.log.dir=D:\\develop\\idea-workspace\\zull-sentinel-integrate-demo\\logs\\
Sentinel 默认的异常提示是这样的:
{
"code": 429,
"message": "Sentinel block exception",
"route": ""
}
com.alibaba.csp.sentinel.adapter.gateway.zuul.fallback.DefaultBlockFallbackProvider
com.alibaba.csp.sentinel.adapter.gateway.zuul.fallback.BlockResponse
com.alibaba.cloud.sentinel.zuul.handler.FallBackProviderHandler
com.alibaba.cloud.sentinel.zuul.SentinelZuulAutoConfiguration
com.alibaba.csp.sentinel.adapter.gateway.zuul.filters.SentinelZuulPreFilter
可以发现其默认格式不符合已有系统的返回格式,所以我们需要自定义。自定义所需的类如下:
import com.alibaba.csp.sentinel.adapter.gateway.zuul.fallback.BlockResponse;
/**
* 自定义返回限流返回对象
*
* @author fengxuechao
* @version 0.1
* @date 2019/11/20
*/
public class CustomBlockResponse extends BlockResponse {
public CustomBlockResponse(int code, String message, String route) {
super(code, message, route);
}
/**
* 必须重写,才能够自定义限流返回消息
*
* @return
*/
@Override
public String toString() {
return "{\"errorCode\":\"" + this.getCode() + "\", \"errorMsg\":\"" + this.getMessage() + "\"}";
}
}
/**
* 自定义限流的 fallback
*
* @author fengxuechao
* @version 0.1
* @date 2019/11/20
*/
public class CustomZuulBlockFallbackProvider implements ZuulBlockFallbackProvider {
/**
* The route this fallback will be used for.
*
* @return The route the fallback will be used for.
*/
@Override
public String getRoute() {
return "*";
}
/**
* Provides a fallback response based on the cause of the failed execution.
*
* @param route The route the fallback is for
* @param cause cause of the main method failure, may be null
* @return the fallback response
*/
@Override
public CustomBlockResponse fallbackResponse(String route, Throwable cause) {
if (cause instanceof BlockException) {
return new CustomBlockResponse(429, "Sentinel block exception", route);
} else {
return new CustomBlockResponse(500, "System Error", route);
}
}
}
package com.fengxuechao.examples.zuul.sentinel;
/**
* 流量控制配置类
*
* @author fengxuechao
* @version 0.1
* @date 2019/11/15
* @see 网关限流
* @see 动态规则扩展
*/
@Slf4j
@Configuration
@EnableConfigurationProperties({CustomSentinelProperties.class})
public class SentinelGatewayFlowConfig implements InitializingBean {
@Autowired
@Qualifier("sentinel-json-gw-flow-converter")
private JsonConverter jsonConverter;
@Autowired
private JedisCluster jedisCluster;
@Autowired
private CustomSentinelProperties customSentinelProperties;
/**
* 自定义限流返回消息
* 与业务的返回消息保持一致
*
* @return CustomZuulBlockFallbackProvider
* @see CustomBlockResponse CustomBlockResponse 自定义 BlockResponse,重写 toString() 自定义返回消息
* @see FallBackProviderHandler FallBackProviderHandler 实现了接口 SmartInitializingSingleton,故此利用 Spring Bean 生命周期原理将默认的 ZuulBlockFallbackProvider 替换为自定义的返回限流处理
* @see SentinelZuulPreFilter SentinelZuulPreFilter 捕获 BlockException, 设置限流返回消息,也就是 CustomBlockResponse
*/
@Bean
public CustomZuulBlockFallbackProvider customZuulBlockFallbackProvider() {
return new CustomZuulBlockFallbackProvider();
}
/**
* bean 创建完成后执行
* 1. 注册sentinel网关流控, 但是网关流控不能应用集群流控
*
* @throws Exception
*/
@Override
public void afterPropertiesSet() throws Exception {
String gwFlowKey = customSentinelProperties.getGwFlowKey();
String gwFlowChanel = customSentinelProperties.getGwFlowChanel();
JedisPullDataSource redisDataSource = new JedisPullDataSource<>(jsonConverter, jedisCluster, gwFlowKey, gwFlowChanel);
// 网关流控,不适配我们现有的业务,应使用集群流控
GatewayRuleManager.register2Property(redisDataSource.getProperty());
}
}
我发现 Sentinel 的动态规则 Redis 扩展使用的客户端是 io.lettuce:lettuce-core
并且没有支持 Redis 集群,而业务使用的是 Redis 集群,同时用的是 redis.clients:jedis
。所以需要自定义动态规则数据源的实现。
我选择继承 com.alibaba.csp.sentinel.datasource.AutoRefreshDataSource
,代码如下:
package com.fengxuechao.examples.zuul.sentinel.datasource;
/**
* @author fengxuechao
* @version 0.1
* @date 2019/11/22
*/
public class JedisPullDataSource extends AutoRefreshDataSource {
private final JedisCluster jedisCluster;
private final String ruleKey;
/**
* Constructor of {@code JedisClusterDataSource}.
*
* @param jedisCluster JedisCluster
* @param ruleKey data key in Redis
* @param channel channel to subscribe in Redis
* @param parser customized data parser, cannot be empty
*/
public JedisPullDataSource(Converter parser, JedisCluster jedisCluster, String ruleKey, String channel) {
super(parser);
AssertUtil.notNull(jedisCluster, "JedisCluster can not be null");
AssertUtil.notEmpty(ruleKey, "Redis ruleKey can not be empty");
AssertUtil.notEmpty(channel, "Redis subscribe channel can not be empty");
this.jedisCluster = jedisCluster;
this.ruleKey = ruleKey;
loadInitialConfig();
}
private void loadInitialConfig() {
try {
T newValue = loadConfig();
if (newValue == null) {
RecordLog.warn("[RedisDataSource] WARN: initial config is null, you may have to check your data source");
}
getProperty().updateValue(newValue);
} catch (Exception ex) {
RecordLog.warn("[RedisDataSource] Error when loading initial config", ex);
}
}
/**
* Read original data from the data source.
*
* @return the original data.
* @throws Exception IO or other error occurs
*/
@Override
public String readSource() throws Exception {
if (this.jedisCluster == null) {
throw new IllegalStateException("JedisCluster has not been initialized or error occurred");
}
return jedisCluster.get(ruleKey);
}
}
应用网关流控的关键代码已有,如下所示为限流规则:
[
{
"resource": "capacity-group-protocol-adaptor-consumer",
"count": 1,
"intervalSec": 20,
"paramItem": {
"parseStrategy": 3,
"matchStrategy": 0,
"fieldName": "accessToken",
"pattern": "325144132CC3BC7C433CD64C0BE98CC8"
}
}
]