音视频开发在数字化转型中扮演着愈加重要的角色,如何在合规与高效的情况下实现跨平台兼容,将是企业关注的焦点。
781
2023-01-18
基于SpringCloud手写一个简易版Sentinel
Sentinel 是什么?
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
不可否认的是,Sentinel功能丰富,并且在提供好用的dashboard提供配置,但是Sentinel在集成到项目中时需要引入多个依赖,并且需要阅读相关文档,以及dashboard中的相关配置才可以接入到项目中,这个过程还是较为复杂的。
如果我们的项目并不需要这么多的功能,只是需要当某个方法或者某个功能发生异常的时候可以实现降级,并不是直接中断程序,该业务功能不是主流程,那么我们为了实现这样一个小功能的时候,将Sentinel集成到项目中的过程显然是较为复杂的,那么这个时候,就需要我们实现一个简答的功能降级的通用方式,下面就一起看看一个简易版的Sentinel的实现
当然,实现这个功能,只需要一个try-catch就可以搞定个,但是我们需要的是try-catch吗?No! 我们需要的是优雅~ 我想你也不想看到满屏的try-catch吧,如果哪天这个方法无需降级的时候,再去一行一行删代码吗?
示例代码已收录到github: github.com/chenliang15…
定义注解
第一步,定义一个通用注解,这个注解可以帮助我们无侵入性的实现功能降级,并且提供丰富的属性,让注解的通用性和灵活性更强
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
@Inherited
public @interface DegradeResource {
// 降级的方法名称
String fallback();
// 降级的类名称,可选
Class>[] fallbackClass() default {};
// 指定降级异常,可选
Class extends Throwable>[] exceptionHandle() default {};
}
fallback:降价方法的名称,需要指定降级方法的名称,才可以在发生异常时调用降级方法,必选参数。
降级方法须知:
必须为public
方法返回类型、方法参数必须和原始方法保持一致,最后一个参数允许多一个Throwable,用来接收发生的异常
fallbackClass:指定降级方法所在的class,可选参数,如果不指定则默认降级方法在当前class中
exceptionHandle:指定异常处理,当发生指定的异常时才选择进行降级,可选参数,数组类型,可以接收多个异常类型
定义切面处理器
当资源降级注解定义之后,我们就需要一个切面处理器,对定义的降级注解做切面处理,当调用的方法上有@DegradeResource注解时,会通过切面处理器进行处理
@Aspect
public class DegradeResourceAspect {
@Around("@annotation(degradeResource)")
public Object doAround(ProceedingJoinPoint pjp, DegradeResource degradeResource) throws Throwable {
try {
return pjp.proceed();
} catch(Throwable e){
// need to trace exception list
Class extends Throwable>[] exceptions = degradeResource.exceptionHandle();
if(exceptions.length > 0) {
List
// 判断是否为同一个个异常
if (exceptionBelongTo(e, exceptionList)) {
return handleFallbackMethod(pjp, degradeResource, e);
} else {
throw e;
}
}
return handleFallbackMethod(pjp, degradeResource, e);
}
}
/**
* if the throw exception is belong to exception trace list
*
* @param e
* @param exceptionList
* @return
*/
private boolean exceptionBelongTo(Throwable e, List
for (Class extends Throwable> aClass : exceptionList) {
if(aClass.isAssignableFrom(e.getClass())) {
return true;
}
}
return false;
}
/**
* invoke fallback method
*
*/
private Object handleFallbackMethod(ProceedingJoinPoint pjp, DegradeResource degradeResource, Throwable e) throws Throwable {
// fallback method
String fallback = degradeResource.fallback();
if(StringUtils.isEmpty(fallback)) {
throw e;
}
// fallback class
Class> clazz = degradeResource.fallbackClass().length > 0 ? degradeResource.fallbackClass()[0] : pjp.getTarget().getClass();
// 获取当前执行的方法名称
Method fallbackMeXRsgonthod = findFallbackMethod(pjp, clazz, fallback);
if(Objects.isNull(fallbackMethod)) {
throw e;
}
// fallback method args
Object[] args;
Object[] originArgs = pjp.getArgs();
int paramCount = fallbackMethod.getParameterTypes().length;
if(originArgs.length == paramCount) {
args = originArgs;
} else {
// fill throwable to fallback method args
args = Arrays.copyOf(originArgs, originArgs.length + 1);
args[args.length - 1] = e;
}
// if static
if(Modifier.isStatic(fallbackMethod.getModifiers())) {
return fallbackMethod.invoke(null, args);
}
return fallbackMethod.invoke(clazz.newInstance(), args);
}
private Method findFallbXRsgonackMethod(ProceedingJoinPoint pjp, Class> clazz, String fallbackName) {
MethodSignature signers = (MethodSignature) pjp.getSignature();
Class>[] originParams = signers.getParameterTypes();
Class>[] paramsWithException = Arrays.copyOf(originParams, originParams.length + 1);
paramsWithException[paramsWithException.length - 1] = Throwable.class;
// find fallback method with origin params
Method method = findMethod(clazz, originParams, fallbackName, signers.getReturnType());
if(method == null) {
// find fallback method with exception params
method = findMethod(clazz, paramsWithException, fallbackName, signers.getReturnType());
}
return method;
}
private Method findMethod(Class> clazz, Class>[] paramsType, String fallbackName, Class> returnType) {
Method[] declaredMethods = clazz.getDeclaredMethods();
for (Method method : declaredMethods) {
if(method.getName().equals(fallbackName)
&& returnType.isAssignableFrom(method.getReturnType())
&& Arrays.equals(paramsType, method.getParameterTypes())) {
return method;
}
}
return null;
}
}
总体的流程为:当扫描到切面时,第一步先正常执行方法,当方法发生异常时,判断当前是否制定异常,如果没有指定异常处理类型,那么就默认走降级方法,如果当前指定了降级的异常处理类型,那么就判断当前方法抛出的异常是否为需要处理的异常,如果是则调用降级方法,如果不是需要处理的异常,那么就抛出异常。
符合当前场景的需要,简单化的异常降级
测试降级
总共测试了3中方式的异常降级,分别为默认所有异常降级、指定异常降级、指定降级方法的处理类
@Service
public class DegradeResourceTestService {
@DegradeResource(fallback = "findByIdFromCacheFallback1")
public String findById(String id) {
int i = Integer.parseInt(id);
System.out.println("id=" + id);
return "ok = " + id;
}
@DegradeResource(fallback = "findByIdFromCacheFallback2", exceptionHandle = {NumberFormatException.class})
public String findByIdWithException(String id) {
int i = Integer.parseInt(id);
System.out.println("id=" + id);
return "ok = " + id;
}
/**
* 支持指定fallback method的class,将降级方法统一放置指定的class中
*
*/
@DegradeResource(fallback = "findByIdFromCacheFallback3", exceptionHandle = {NumberFormatException.class},
fallbackClass =XRsgon {FallbackClassService.class})
public String findByIdWithFallbackClass(String id) {
int i = Integer.parseInt(id);
System.out.println("id=" + id);
return "ok = " + id;
}
/**
* fallback method可以只接受原始函数的参数
*/
public String findByIdFromCacheFallback1(String id) {
return "fallback1 = " + id;
}
/**
* fallback method 不仅可以接收原始方法的参数,还可以接收具体的Exception
*
*/
public String findByIdFromCacheFallback2(String id, Throwable e) {
System.out.println("fallback method exception:" + e);
return "fallback2 = " + id;
}
}
结果:
可以看到,当发生异常时,可以通过降级保证主流程的通常,对于非主流程的功能,我们可以通过@DegradeResource注解保证流程的完善和降级方案,保证用户的体验和程序的正常。
以上就是基于SpringCloud手写一个简易版Sentinel的详细内容,更多关于SpringCloud手写Sentinel的资料请关注我们其它相关文章!
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。
发表评论
暂时没有评论,来抢沙发吧~