返回

透过Sentinel源码剖析与其他框架的集成实现

后端

前言

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();