透过Sentinel源码剖析与其他框架的集成实现
2024-02-03 22:29:21
前言
Sentinel作为一款功能强大的分布式系统保护框架,在现代微服务架构中扮演着至关重要的角色。它能够对分布式系统的各个节点进行流量控制、熔断降级、系统负载保护等操作,保障系统的稳定性和可用性。Sentinel与其他框架的集成是其功能的重要组成部分,本文将深入源码,剖析Sentinel如何与AspectJ、SpringMVC、Dubbo和SpringCloud进行集成,帮助开发者了解Sentinel如何与这些框架无缝协作,实现资源管控和限流保护。
AspectJ集成
Sentinel通过AspectJ对方法进行增强,实现方法级别的限流、熔断等功能。AspectJ是一种面向切面的编程技术,允许开发者在不修改原有代码的情况下,为代码添加额外的行为。Sentinel通过在编译时织入AspectJ切面,在方法执行前和执行后分别执行相应的操作,实现对方法的增强。
@Around("@annotation(com.alibaba.csp.sentinel.annotation.SentinelResource)")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
String resourceName = getResourceName(pjp);
Entry entry = null;
try {
entry = acquireEntry(resourceName, pjp.getArgs());
return pjp.proceed();
} catch (Throwable throwable) {
if (throwable instanceof Error) {
throw (Error)throwable;
}
if (throwable instanceof RuntimeException) {
throw (RuntimeException)throwable;
}
throw new RuntimeException(throwable);
} finally {
if (entry != null) {
entry.exit();
}
}
}
在上面的代码中,@Around
注解表示该方法是一个环绕通知,它将在目标方法执行前后都被执行。ProceedingJoinPoint
对象提供了对目标方法的访问,resourceName
是方法的资源名称,它将被用作Sentinel资源的标识符。acquireEntry
方法尝试获取Sentinel资源的令牌,如果成功获取令牌,则表示方法可以执行;否则,方法将被限流或熔断。entry.exit()
方法表示方法执行完成,并释放Sentinel资源的令牌。
SpringMVC集成
Sentinel提供了对SpringMVC框架的集成支持,允许开发者轻松地将Sentinel与SpringMVC应用程序集成。Sentinel通过拦截SpringMVC的请求和响应,实现对请求的限流和熔断等功能。
@Override
public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
Entry entry = null;
try {
// 获取资源名称
String resourceName = getResourceName(request);
// 获取流量控制参数
TrafficController trafficController = getTrafficController(resourceName);
// 尝试获取令牌
entry = trafficController.tryAcquire(resourceName, 1, request.getRemoteAddr());
// 如果获取令牌失败,则限流
if (entry == null) {
handleBlockRequest(request, response);
return;
}
// 继续执行请求
chain.doFilter(request, response);
} catch (Throwable throwable) {
// 如果请求执行过程中发生异常,则熔断
handleExceptionRequest(request, response, throwable);
} finally {
// 释放令牌
if (entry != null) {
entry.exit();
}
}
}
在上面的代码中,doFilterInternal
方法是SpringMVC的过滤器方法,它将在每个请求被处理之前被调用。getResourceName
方法获取请求的资源名称,getTrafficController
方法获取Sentinel资源的流量控制器,tryAcquire
方法尝试获取Sentinel资源的令牌。如果获取令牌失败,则请求将被限流;否则,请求将被继续执行。handleBlockRequest
方法处理被限流的请求,handleExceptionRequest
方法处理在请求执行过程中发生的异常。
Dubbo集成
Sentinel提供了对Dubbo框架的集成支持,允许开发者轻松地将Sentinel与Dubbo应用程序集成。Sentinel通过拦截Dubbo的RPC调用,实现对RPC调用的限流和熔断等功能。
@Override
public Result handle(Invoker<?> invoker, Object[] args) throws RpcException {
Entry entry = null;
try {
// 获取资源名称
String resourceName = getResourceName(invoker);
// 获取流量控制参数
TrafficController trafficController = getTrafficController(resourceName);
// 尝试获取令牌
entry = trafficController.tryAcquire(resourceName, 1, RpcContext.getContext().getAttachment("consumer"));
// 如果获取令牌失败,则限流
if (entry == null) {
throw new TooManyRequestsException();
}
// 继续执行RPC调用
Result result = invoker.invoke(args);
// 如果RPC调用成功,则记录成功调用次数
trafficController.success();
return result;
} catch (Throwable throwable) {
// 如果RPC调用失败,则记录失败调用次数
trafficController.failed();
throw throwable;
} finally {
// 释放令牌
if (entry != null) {
entry.exit();
}
}
}
在上面的代码中,handle
方法是Dubbo的拦截器方法,它将在每个RPC调用被执行之前被调用。getResourceName
方法获取RPC调用的资源名称,getTrafficController
方法获取Sentinel资源的流量控制器,tryAcquire
方法尝试获取Sentinel资源的令牌。如果获取令牌失败,则RPC调用将被限流;否则,RPC调用将被继续执行。success
方法记录RPC调用的成功调用次数,failed
方法记录RPC调用的失败调用次数。
SpringCloud集成
Sentinel提供了对SpringCloud框架的集成支持,允许开发者轻松地将Sentinel与SpringCloud应用程序集成。Sentinel通过拦截SpringCloud的Feign客户端调用,实现对Feign客户端调用的限流和熔断等功能。
@Override
public Response execute(RequestTemplate request) {
Entry entry = null;
try {
// 获取资源名称
String resourceName = getResourceName(request);
// 获取流量控制参数
TrafficController trafficController = getTrafficController(resourceName);
// 尝试获取令牌
entry = trafficController.tryAcquire(resourceName, 1);
// 如果获取令牌失败,则限流
if (entry == null) {
throw new TooManyRequestsException();
}
// 继续执行Feign客户端调用
Response response = super.execute(request);
// 如果Feign客户端调用成功,则记录成功调用次数
trafficController.success();
return response;
} catch (Throwable throwable) {
// 如果Feign客户端调用失败,则记录失败调用次数
trafficController.failed();
throw throwable;
} finally {
// 释放令牌
if (entry != null) {
entry.exit();