pigeon熔断降级
当服务调用在短时间内出现大量的失败且失败率超过一定阀值时,可以通过配置手动或自动触发降级,调用端直接返回默认对象或抛出异常,不会将调用请求发到服务提供方,如果服务提供方恢复可用,客户端可以自动或手工解除降级。
pigeon降级开关
pigeon提供三种降级开关,来分别支持不同的降级策略:
- 强制降级开关:在远程服务大量超时或其他不可用情况时,紧急时候进行设置,开启后,调用端会根据上述降级策略直接返回默认值或抛出降级异常,当远程服务恢复后,建议关闭此开关。对应配置
pigeon.invoker.degrade.force=true,默认为false
- 失败降级开关:失败降级开关便于客户端在服务端出现非业务异常(比如网络失败,超时,无可用节点等)时进行降级容错,而在出现业务异常(比如登录用户名密码错误)时不需要降级。对应配置
pigeon.invoker.degrade.failure=true,默认为false
- 自动降级开关:自动降级开关是在调用端设置,开启自动降级后,调用端如果调用某个服务出现连续的超时或不可用,当一段时间内(10秒内)失败率超过一定阀值(默认1%)会触发自动降级,调用端会根据上述降级策略直接返回默认值或抛出降级异常;当服务端恢复后,调用端会自动解除降级模式,再次发起请求到远程服务。对应配置
pigeon.invoker.degrade.auto=true,默认为false
若同时开启了多个开关,会根据下面优先级使用相应降级策略:强制降级 > 自动降级 > 失败降级,其中自动降级包含失败降级策略。
pigeon降级处理策略配置
通过配置pigeon.invoker.degrade.methods为不同的服务方法指定不同的降级策略,如:
http://service.dianping.com/com.dianping.pigeon.demo.EchoService#echo=a,http://service.dianping.com/com.dianping.pigeon.demo.EchoService#getUserDetail=b,http://service.dianping.com/com.dianping.pigeon.demo.EchoService#getUserDetailArray=c
上述配置内容包含多个方法的降级策略a、b、c。如果某此调用需要降级,而降级策略没有配置则不降级,进行正常调用流程。
配置解析定义在DegradationFilter#parseDegradeMethodsConfig方法中,
对于a、b、c这些降级策略,可以通过诸如pigeon.invoker.degrade.method.return.a等配置来定义具体的降级处理策略。
在触发降级后,pigeon支持4种降级处理策略:
- 指定默认返回值,可以为一个复杂对象
- 抛出指定异常
- 执行groovy脚本
- Mock方式
下面对这几种降级处理策略举例分析:
指定默认返回值
如策略a有pigeon.invoker.degrade.method.return.a配置值为:
{
"returnClass": "java.lang.String",
"content": "echo,input"
}
这里意思是降级返回一个字符串"echo,input"。
而对于复杂对象,可参照json格式配置,如:
{
"returnClass": "com.dianping.pigeon.demo.User",
"content": "{\"username\":\"user-1\"}"
}
如果反序列化是Map类型,还可以配置keyClass和valueClass属性来指定键值类型,如果是Collection类型,可以配置getComponentClass来指定元素类型。
抛出指定异常
如果想在降级后抛出指定异常,可以配置如pigeon.invoker.degrade.method.return.b为:
{
"throwException": "true",
"returnClass": "Exception"
}
执行groovy脚本
如配置pigeon.invoker.degrade.method.return.c值为:
{
"useGroovyScript": "true",
"content": "throw new RuntimeException('test groovy degrade');"
}
pigeon可以根据配置的content,动态执行groovy脚本,这里需注意脚本的最后一条语句必须返回方法的返回值类型或抛出异常。
mock方式
除了上述几种使用lion配置降级策略的方式,pigeon还提供了一种使用mock类的降级配置方式。
例如我们想修改pigeon-test.pigeon.invoker.degrade.method.return.a的降级策略方式为mock方式,只需修改配置为:{"useMockClass":"true"}
打开mock开关,然后在spring的xml配置中添加mock类的引用对象:
<bean id="echoService" class="com.dianping.pigeon.remoting.invoker.config.spring.ReferenceBean" init-method="init">
<property name="url" value="com.dianping.pigeon.benchmark.service.EchoService" />
<property name="interfaceName" value="com.dianping.pigeon.benchmark.service.EchoService" />
<property name="mock" ref="echoServiceMock" /><!-- 添加mock类的引用 -->
</bean>
<!-- 必须实现EchoService接口 -->
<bean id="echoServiceMock" class="com.dianping.pigeon.benchmark.service.EchoServiceMock"/
对于上面几种策略,可以通过配置enable=true|false来确定是否启动策略,不填写默认为true,如{“useMockClass”:“true”,“enable”:“false”}。
如果对于同一个服务方法启动了多种降级策略,会根据以下优先级执行策略:
mock方式>groovy脚本>抛出异常>返回默认对象。
分析完以上策略配置,来看看pigeon解析配置的代码实现,定义在DegradationFilter#parseDegradeMethodsConfig方法中:
private static void parseDegradeMethodsConfig(String degradeMethodsConfig) throws Throwable {
if (StringUtils.isNotBlank(degradeMethodsConfig)) {
ConcurrentHashMap<String, DegradeAction> map = new ConcurrentHashMap<String, DegradeAction>();
// 格式如"key1=value1,key2=value2",其中key为url + "#" + methodName
// 可以从配置"pigeon.invoker.degrade.method.return." + value中获取具体方法的DegradeActionConfig JSON字符串
String[] pairArray = degradeMethodsConfig.split(",");
for (String str : pairArray) {
if (StringUtils.isNotBlank(str)) {
String[] pair = str.split("=");
if (pair != null && pair.length == 2) {
String key = pair[1].trim();
DegradeAction degradeAction = new DegradeAction();
if (StringUtils.isNotBlank(key)) {
// 获取指定的degradeActionConfig,并装配DegradeAction对象
String config = configManager.getStringValue(KEY_DEGRADE_METHOD + key);
if (StringUtils.isNotBlank(config)) {
// 反序列化DegradeActionConfig
config = config.trim();
config = "{\"@class\":\"" + DegradeActionConfig.class.getName() + "\","
+ config.substring(1);
DegradeActionConfig degradeActionConfig = (DegradeActionConfig) jacksonSerializer
.toObject(DegradeActionConfig.class, config);
// 解析配置,初始化degradeAction
degradeAction.setUseMockClass(degradeActionConfig.getUseMockClass());
degradeAction.setUseGroovyScript(degradeActionConfig.getUseGroovyScript());
degradeAction.setThrowException(degradeActionConfig.getThrowException());
degradeAction.setEnable(degradeActionConfig.getEnable());
String content = degradeActionConfig.getContent();
Object returnObj = null;
// 解析具体的降级方案
if (degradeAction.isUseMockClass()) {
// use mock class
} else if (degradeAction.isUseGroovyScript()) {
degradeAction.setGroovyScript(GroovyUtils.getScript(content));
} else if (degradeAction.isThrowException()) {
if (StringUtils.isNotBlank(degradeActionConfig.getReturnClass())) {
// 反序列化成指定异常
returnObj = jacksonSerializer
.toObject(Class.forName(degradeActionConfig.getReturnClass()), content);
if (!(returnObj instanceof Exception)) {
throw new IllegalArgumentException(
"Invalid exception class:" + degradeActionConfig.getReturnClass());
}
degradeAction.setReturnObj(returnObj);
}
} else {
if (StringUtils.isNotBlank(degradeActionConfig.getKeyClass())
&& StringUtils.isNotBlank(degradeActionConfig.getValueClass())) {
// 反序列化map对象
returnObj = jacksonSerializer.deserializeMap(content,
Class.forName(degradeActionConfig.getReturnClass()),
Class.forName(degradeActionConfig.getKeyClass()),
Class.forName(degradeActionConfig.getValueClass()));
} else if (StringUtils.isNotBlank(degradeActionConfig.getComponentClass())) {
// 反序列化collection对象
returnObj = jacksonSerializer.deserializeCollection(content,
Class.forName(degradeActionConfig.getReturnClass()),
Class.forName(degradeActionConfig.getComponentClass()));
} else if (StringUtils.isNotBlank(degradeActionConfig.getReturnClass())) {
// 反序列化普通java对象
returnObj = jacksonSerializer
.toObject(Class.forName(degradeActionConfig.getReturnClass()), content);
}
degradeAction.setReturnObj(returnObj);
}
}
}
map.put(pair[0].trim(), degradeAction);
}
}
}
// 重置缓存
degradeMethodActions.clear();
degradeMethodActions = map;
} else {
// 重置缓存
degradeMethodActions.clear();
}
groovyMocks.clear()