feat: rpc远程调用统一fallback处理(也可自定义fallback)

远程调用报错时
1 在服务调用方打印错误日志方便排查
2 返回报错信息处理
pull/154/head
tpz 2024-10-31 10:04:12 +08:00
parent f9fdca0d6d
commit d39a9509c0
10 changed files with 387 additions and 4 deletions

View File

@ -31,6 +31,7 @@ public interface GlobalErrorCodeConstants {
ErrorCode INTERNAL_SERVER_ERROR = new ErrorCode(500, "系统异常");
ErrorCode NOT_IMPLEMENTED = new ErrorCode(501, "功能未实现/未开启");
ErrorCode ERROR_CONFIGURATION = new ErrorCode(502, "错误的配置项");
ErrorCode FEIGN_ERROR = new ErrorCode(555, "rpc远程调用异常");
// ========== 自定义错误段 ==========
ErrorCode REPEATED_REQUESTS = new ErrorCode(900, "重复请求,请稍后重试"); // 重复请求

View File

@ -0,0 +1,32 @@
package cn.iocoder.yudao.framework.rpc.config;
import cn.iocoder.yudao.framework.rpc.core.fallback.YudaoFeignBuilder;
import feign.Feign;
import feign.RequestInterceptor;
import jakarta.annotation.Resource;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Scope;
import java.util.List;
/**
* @Description Rpc
* @Author tpz
* @Date 2024/10/30 14:27
*/
@AutoConfiguration
public class YudaoCloudRpcAutoConfiguration {
@Resource
private List<RequestInterceptor> requestInterceptors;
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
public Feign.Builder feignSentinelBuilder() {
return new YudaoFeignBuilder().requestInterceptors(requestInterceptors);
}
}

View File

@ -1,4 +1,6 @@
/**
* TODO
* open-feign
*
* @author tpz
*/
package cn.iocoder.yudao.framework.rpc.config;

View File

@ -0,0 +1,28 @@
package cn.iocoder.yudao.framework.rpc.core.fallback;
import feign.Target;
import lombok.AllArgsConstructor;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cloud.openfeign.FallbackFactory;
/**
* FallbackFactory,FallbackFactoryFallbackFactory
* tips:使CGLIBEnhancer+MethodInterceptor
* @author tpz
*/
@AllArgsConstructor
public class YudaoFallbackFactory<T> implements FallbackFactory<T> {
// 代理对象
private final Target<T> target;
@SuppressWarnings("unchecked")
@Override
public T create(Throwable cause) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.type());
enhancer.setUseCache(true);
enhancer.setCallback(new YudaoFeignFallback<>(target.type(), target.name(), cause));
return (T) enhancer.create();
}
}

View File

@ -0,0 +1,91 @@
package cn.iocoder.yudao.framework.rpc.core.fallback;
import feign.Feign;
import feign.InvocationHandlerFactory;
import feign.Target;
import lombok.SneakyThrows;
import org.springframework.beans.BeansException;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.cloud.openfeign.FeignClientFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.StringUtils;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;
/**
* @Description Feign Builder fallback peignfallback
* @Author tpz
* @Date 2024/10/30 14:37
*/
public class YudaoFeignBuilder extends Feign.Builder implements ApplicationContextAware {
public String CONTEXT_FALL_BACK = "fallback";
public String CONTEXT_FALL_BACK_FACTORY = "fallbackFactory";
private FeignClientFactory feignClientFactoryBean;
@Override
public Feign internalBuild() {
super.invocationHandlerFactory(new InvocationHandlerFactory() {
@SneakyThrows
@Override
public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {
// 获取 FeignClient 注解信息
FeignClient feignClient = AnnotationUtils.findAnnotation(target.type(), FeignClient.class);
assert feignClient != null;
Class<?> fallback = feignClient.fallback();
Class<?> fallbackFactory = feignClient.fallbackFactory();
String contextId = feignClient.contextId();
if (!StringUtils.hasText(contextId)) {
contextId = feignClient.name();
}
Object fallbackInstance;
FallbackFactory<?> fallbackFactoryInstance;
// 如果有自定义fallback使用自定义fallback
if (void.class != fallback) {
fallbackInstance = getFromContext(contextId, CONTEXT_FALL_BACK, fallback, target.type());
return new YudaoInvocationHandler(target, dispatch, new FallbackFactory.Default<>(fallbackInstance));
}
// 如果有自定义fallbackFactory使用自定义fallbackFactory
if (void.class != fallbackFactory) {
fallbackFactoryInstance = (FallbackFactory<?>) getFromContext(contextId, CONTEXT_FALL_BACK_FACTORY, fallbackFactory, FallbackFactory.class);
return new YudaoInvocationHandler(target, dispatch, fallbackFactoryInstance);
}
// 默认的FallbackFactory
YudaoFallbackFactory<?> defaultFallbackFactory = new YudaoFallbackFactory<>(target);
return new YudaoInvocationHandler(target, dispatch, defaultFallbackFactory);
}
private Object getFromContext(String name, String type, Class<?> fallbackType, Class<?> targetType) {
Object fallbackInstance = feignClientFactoryBean.getInstance(name, fallbackType);
if (fallbackInstance == null) {
throw new IllegalStateException(String.format("【rpc 配置错误】类型未找到 name:[{%s}] fallbackType:[{%s}]", name, fallbackType));
}
if (!targetType.isAssignableFrom(fallbackType)) {
throw new IllegalStateException(String.format("【rpc 配置错误】实例不兼容 name:[{%s}]", name));
}
return fallbackInstance;
}
});
super.contract(contract);
return super.internalBuild();
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
feignClientFactoryBean = applicationContext.getBean(FeignClientFactory.class);
}
}

View File

@ -0,0 +1,69 @@
package cn.iocoder.yudao.framework.rpc.core.fallback;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import org.springframework.lang.Nullable;
import java.lang.reflect.Method;
import java.util.Objects;
/**
* fallback
* 1 便
* 2
* @author tpz
*/
@Slf4j
@AllArgsConstructor
public class YudaoFeignFallback<T> implements MethodInterceptor {
// 代理类
private final Class<T> targetType;
private final String targetName;
private final Throwable cause;
/**
* fallback
* @author tpz
* @date 2024/10/31 9:23
**/
@Nullable
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
String errorMessage = cause.getMessage();
log.error("【rpc调用异常】被调服务:[{}] 错误信息:[{}]", targetName, errorMessage);
// 否则是在调用的过程中发生的异常
if (cause instanceof ServiceException) {
Integer code = ((ServiceException) cause).getCode();
// 如果是系统异常直接返回feign错误编码 否则返回业务异常的编码
return CommonResult.error(Objects.equals(code, GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR.getCode()) ?
GlobalErrorCodeConstants.FEIGN_ERROR.getCode() : code, errorMessage);
}
// 其他情况统一返回feign的错误编码
return CommonResult.error(GlobalErrorCodeConstants.FEIGN_ERROR.getCode(), errorMessage);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
YudaoFeignFallback<?> that = (YudaoFeignFallback<?>) o;
return targetType.equals(that.targetType);
}
@Override
public int hashCode() {
return Objects.hash(targetType);
}
}

View File

@ -0,0 +1,152 @@
package cn.iocoder.yudao.framework.rpc.core.fallback;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import feign.InvocationHandlerFactory;
import feign.Target;
import org.springframework.cloud.openfeign.FallbackFactory;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.LinkedHashMap;
import java.util.Map;
import static feign.Util.checkNotNull;
/**
* fallback
* @author tpz
*/
public class YudaoInvocationHandler implements InvocationHandler {
private final Target<?> target;
private final Map<Method, InvocationHandlerFactory.MethodHandler> dispatch;
private FallbackFactory<?> fallbackFactory;
private Map<Method, Method> fallbackMethodMap;
public YudaoInvocationHandler(Target<?> target, Map<Method, InvocationHandlerFactory.MethodHandler> dispatch,
FallbackFactory<?> fallbackFactory) {
this.target = checkNotNull(target, "target");
this.dispatch = checkNotNull(dispatch, "dispatch");
this.fallbackFactory = fallbackFactory;
this.fallbackMethodMap = toFallbackMethod(dispatch);
}
public YudaoInvocationHandler(Target<?> target, Map<Method, InvocationHandlerFactory.MethodHandler> dispatch) {
this.target = checkNotNull(target, "target");
this.dispatch = checkNotNull(dispatch, "dispatch");
}
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args)
throws Throwable {
// 其他方法直接执行
Object result = handleNormalMethod(method, args);
if (result != null) return result;
// 获取当前执行代理的方法
InvocationHandlerFactory.MethodHandler methodHandler = this.dispatch.get(method);
// 只代理feign的HardCodedTarget
if (target instanceof Target.HardCodedTarget) {
try {
// 执行远程调用
result = methodHandler.invoke(args);
// 执行错误的情况
handleErrResult(result);
} catch (Throwable ex) {
// fallback handle
if (fallbackFactory != null) {
try {
// 根据方法名称使用过程创建代理对象
return fallbackMethodMap.get(method)
.invoke(fallbackFactory.create(ex), args);
} catch (IllegalAccessException e) {
throw new AssertionError(e);
} catch (InvocationTargetException e) {
throw new AssertionError(e.getCause());
}
} else {
// 没有fallback就报错
throw ex;
}
}
} else {
// 直接执行
result = methodHandler.invoke(args);
}
return result;
}
/**
*
*
* @author tpz
* @date 2024/10/30 16:41
**/
private Object handleNormalMethod(Method method, Object[] args) {
if ("equals".equals(method.getName())) {
try {
Object otherHandler = args.length > 0 && args[0] != null
? Proxy.getInvocationHandler(args[0]) : null;
return equals(otherHandler);
} catch (IllegalArgumentException e) {
return false;
}
} else if ("hashCode".equals(method.getName())) {
return hashCode();
} else if ("toString".equals(method.getName())) {
return toString();
}
return null;
}
/**
* fallback
*
* @author tpz
* @date 2024/10/30 18:08
**/
private void handleErrResult(Object result) {
if (result instanceof CommonResult) {
// 如果请远程调用报错会抛出遗异常
((CommonResult<?>) result).getCheckedData();
}
}
@Override
public boolean equals(Object obj) {
if (obj instanceof YudaoInvocationHandler) {
YudaoInvocationHandler other = (YudaoInvocationHandler) obj;
return target.equals(other.target);
}
return false;
}
@Override
public int hashCode() {
return target.hashCode();
}
@Override
public String toString() {
return target.toString();
}
/**
* @author tpz
* @date 2024/10/30 16:42
**/
static Map<Method, Method> toFallbackMethod(Map<Method, InvocationHandlerFactory.MethodHandler> dispatch) {
Map<Method, Method> result = new LinkedHashMap<>();
for (Method method : dispatch.keySet()) {
method.setAccessible(true);
result.put(method, method);
}
return result;
}
}

View File

@ -0,0 +1,6 @@
/**
* 使fallbackfallback
*
* @author tpz
*/
package cn.iocoder.yudao.framework.rpc.core.fallback;

View File

@ -1,4 +1,6 @@
/**
* TODO
* feign
*
* @author tpz
*/
package cn.iocoder.yudao.framework.rpc.core;

View File

@ -1,6 +1,6 @@
/**
* OpenFeign RESTful API
* feign
*
* @author
* @author tpz
*/
package cn.iocoder.yudao.framework.rpc;