适配器模式在京东的应用

最近在为系统做一个限流的小组件,采用漏桶算法,基于AOP+Semaphore实现,限流不是本文的重点,有兴趣的读者可以去查阅相关资料。我们首先来简单描述一下实现过程:

  1. 用AOP切面的方式对需要进行限流的服务进行统一切面
  2. 在切面层用Semaphore信号量实现漏桶限流
  3. 限流的维度为每个类的每个方法
  4. 限流的配置如许可数量、降级服务列表等通过Zookeeper实现统一的配置下发

所以,这里的关键点之一就是根据切面的切入点拿到类和方法的信息,而我们工程中现有的AOP切面是用两种方式实现的,一种是采用@Aspect注解,切入点为ProceedingJoinPoint实例,另一种是通过实现MethodInterceptor接口实现,切入点为MethodInvocation实例,两者获取类和方法信息的方式是不同的,逻辑无法统一,这里使用适配器模式来解决这个问题,我们来看适配器模式的类图:

这里写图片描述
首先定义统一的用于限流器的切入点接口:

/**
 * 限流器切入点
 */
public interface PointCut {

    /**
     * 执行切入点方法
     */
    Object proceed() throws Throwable;

    /**
     * 获取类名
     */
    String getClassName();

    /**
     * 获取方法名
     */
    String getMethodName();

}

接下来为ProceedingJoinPoint和MethodInvocation创建适配器:

/**
 * MethodInvocation适配器
 */
public class MethodInvocationAdapter implements PointCut {

    private MethodInvocation methodInvocation;

    public MethodInvocationAdapter(MethodInvocation methodInvocation) {
        this.methodInvocation = methodInvocation;
    }

    public Object proceed() throws Throwable {
        return methodInvocation.proceed();
    }

    public String getClassName() {
        return methodInvocation.getMethod().getDeclaringClass().getName();
    }

    public String getMethodName() {
        return methodInvocation.getMethod().getName();
    }

}
/**
 * ProceedingJoinPoint适配器
 */
public class ProceedingJoinPointAdapter implements PointCut {

    private ProceedingJoinPoint proceedingJoinPoint;

    public ProceedingJoinPointAdapter(ProceedingJoinPoint proceedingJoinPoint) {
        this.proceedingJoinPoint = proceedingJoinPoint;
    }

    public Object proceed() throws Throwable {
        return proceedingJoinPoint.proceed();
    }

    public String getClassName() {
        return proceedingJoinPoint.getTarget().getClass().getName();
    }

    public String getMethodName() {
        return proceedingJoinPoint.getSignature().getName();
    }
}

下面我们来看限流器的实现:

import com.xxx.PointCut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Semaphore;

/**
 * 限流工具
 */
@Service
public class Limiter implements DisposableBean {

    private static final Logger logger = LoggerFactory.getLogger(Limiter.class);

    /**
     * 默认许可数量
     */
    private static final int DEFAULT_PERMITS_COUNT = 100;

    /**
     * 配置许可数量后缀
     */
    private static final String CONFIG_PERMITS_SUFFIX = "_permits";

    private final ConcurrentMap<String, Semaphore> semaphoreMap = new ConcurrentHashMap<String, Semaphore>();

    @Autowired
    private ConfigCenterUtils configCenterUtils;

    /**
     * 尝试获取许可,该方法会立即尝试获取一个许可,不会阻塞,获取失败将抛出异常
     */
    public Object invoke(PointCut pointCut) throws Throwable {
        String key = getKey(pointCut);
        Semaphore semaphore = getSemaphore(key);
        if (!semaphore.tryAcquire()) {
            throw new RuntimeException("Limiter --> tryAcquire fail! no remaining permits! key: " + key);
        }
        try {
            return pointCut.proceed();
        } finally {
            logger.debug("Limiter --> invoke release permit! key: {}, permits: {}", key, semaphore.availablePermits());
            semaphore.release();
        }
    }

    /**
     * 获取信号量
     */
    private Semaphore getSemaphore(String key) {
        Semaphore semaphore = semaphoreMap.get(key);
        if (semaphore == null) {
            semaphore = new Semaphore(getPermitsCount(key));
            semaphoreMap.putIfAbsent(key, semaphore);
        }
        return semaphore;
    }

    /**
     * 获取key,格式:[类名_方法名],作用:
     * 1、信号量map的key
     * 2、后面拼接常量CONFIG_PERMITS_SUFFIX的值用作配置许可数量的key
     */
    private String getKey(PointCut pointCut) {
        return pointCut.getClassName() + "_" + pointCut.getMethodName();
    }

    /**
     * 获取许可数量,默认为常量DEFAULT_PERMITS_COUNT的值
     */
    private int getPermitsCount(String key) {
        try {
            return Integer.parseInt(configCenterUtils.getContentFromConfigCenter(key + CONFIG_PERMITS_SUFFIX));
        } catch (Exception e) {
            // 如果获取配置中心许可数量失败(最可能的原因是没有配置)则返回默认许可数量
            return DEFAULT_PERMITS_COUNT;
        }
    }

    public void destroy() throws Exception {
        semaphoreMap.clear();
    }
}

这里的ConfigCenterUtils是使用ZkClient实现的统一配置中心工具类,用于获取zookeeper中的配置。限流器的使用只需要在拦截器中注入限流器,并在执行切入点方法时加入限流器即可:

@Autowired
protected Limiter limiter;

public Object process(ProceedingJoinPoint point) throws Throwable {
    // ...
    return limiter.invoke(new ProceedingJoinPointAdapter(point));
    // ...
}
@Autowired
protected Limiter limiter;

public Object invoke(MethodInvocation invocation) throws Throwable {
    // ...
    return limiter.invoke(new MethodInvocationAdapter(invocation));
    // ...
}

本文旨在凸现适配器模式的应用,示例只是一个简版,它有很多缺陷,并不适合应用在生产环境中,利用zookeeper的watcher机制更新配置的代码也没有给出(监听到节点变更之后移除map中对应的key即可,这样就可以读取新的配置生成新的Semaphore对象)。

示例使用适配器的好处是:

  1. 便于复用,限流器可以应用到不同的场景,不仅仅是AOP层面,只需要实现新的适配器即可复用限流器
  2. 便于扩展,我们在扩展限流器的应用时只需要增加适配器即可
  3. 便于维护,我们将获取方法和类的信息、执行目标方法的逻辑封装在各个适配器中,这样这部分逻辑需要变更时就可以直接修改适配器即可,不需要修改限流器,这也符合面向对象设计的开闭原则

猜你喜欢

转载自blog.csdn.net/heroqiang/article/details/80704709