简易RPC框架-SPI

案例

咱们所熟悉的jbdc是一种用于执行SQL语句的Java API,能够为多种关系数据库提供统一访问,提供了一种基准,据此能够构建更高级的工具和接口。css

如上图所示,任意的一个数据库厂商只要去实现jdbc的接口,就能够轻松的对接jbdc从而为应用开发人员所服务。html

SPI

上面的jdbc的设计理念叫SPI,它的全名是Service Provider Interface。它的理念是对某类功能进行抽象,确保应用程序依赖抽象而不是具体的某种实现,经过配置服务实现者的方式来达到面向接口编程以及扩展的目的。好比咱们项目中须要用到日志组件,有的项目喜欢logback,有的喜欢log4j,有的喜欢common-log等等,若是在项目中直接依赖这些日志接口,那么后续若是须要对日志组件从新选型,对现有项目的影响会很是大,全部后来就有了slfj,它抽象了日志接口但不包含任何的实现,具体实现所有依赖于不一样的厂商。java

传统的java spi通常作法是在resources/META-INF/services/目录下面建立一个以服务接口命名的文件,该文件内容就是实现该服务接口的具体实现类的彻底限定名。当程序加载的时候,就能经过resources/META-INF/services/里的配置文件找到具体的实现类名,并加载实例化。 经过这个机制就能找到服务接口的实现类,而不须要再代码里写死。git

java.util.ServiceLoader这个就是java spi中用来加载服务实现类的工具,本文不对它的具体用法作过多介绍。github

主题:限流策略如何扩展

本文要讨论的问题是,rpc框架中的限流过滤器扩展问题(可参考以前的文章 :简易RPC框架-客户端限流配置),以前介绍的限流实现是采用了guava提供的RateLimit,当时客户端限流的实现是在框架中写好的不容许修改,不一样项目若是须要不一样的限流策略那么就须要针对原有方案进行扩展,若是扩展呢?web

Spring-boot 中的SPI

我在spring-boot项目中按传统的spi方式配置后,发现ServiceLoader加载指定接口找不到具体的实现类,后来发现spring-boot有本身的spi实现。它是在resources/META-INF/spring.factories中配置相关的接口,并且这个类的配置方式与传统的spi也有所不一样,它采用了key=value方式,这点有点相似dubbo的spi机制。文件目录以下:spring

下面给出我调整以后的方案:数据库

客户端限流接口

定义一个限流的接口,由于限流会有些参数控制,因此就增长RpcInvocation来协助完成。编程

public interface AccessLimitService {

    void acquire(RpcInvocation invocation);
}

客户端限流接口实现

本文只是为了简单实现,因此直接将原有写在rpc框架中的限流方式抽取出来,并无从新采用一种新的限流策略。框架

public class AccessLimitServiceImpl implements AccessLimitService {

    @Override
    public void acquire(RpcInvocation invocation) {
        AccessLimitManager.acquire(invocation);
    }


    static class AccessLimitManager{

        private final static Object lock=new Object();

        private final static Map<String,RateLimiter> rateLimiterMap= Maps.newHashMap();

        public static void acquire(RpcInvocation invocation){
            if(!rateLimiterMap.containsKey(invocation.getClassName())) {
                synchronized (lock) {
                    if(!rateLimiterMap.containsKey(invocation.getClassName())) {
                        final RateLimiter rateLimiter = RateLimiter.create(invocation.getMaxExecutesCount());
                        rateLimiterMap.put(invocation.getClassName(), rateLimiter);
                    }
                }
            }
            else {
                RateLimiter rateLimiter=rateLimiterMap.get(invocation.getClassName());
                rateLimiter.acquire();
            }
        }
    }
}

客户端限流过滤器调整

既然限流的实现抽取成了接口,因此此处的具体实现调整为从服务提供者中找对应的实现。

@Override
public Object invoke(RpcInvoker invoker, RpcInvocation invocation) {
    logger.info("before acquire,"+new Date());
    List<AccessLimitService> accessLimitServiceLoader = SpringFactoriesLoader.loadFactories(AccessLimitService.class, null);
    if(!CollectionUtils.isEmpty(accessLimitServiceLoader)){
        AccessLimitService accessLimitService=accessLimitServiceLoader.get(0);
        accessLimitService.acquire(invocation);
    }

    Object rpcResponse=invoker.invoke(invocation);
    logger.info("after acquire,"+new Date());
    return rpcResponse;
}

目前还不支持同一个项目中多种限流策略,目前版本只容许存在一种,若是配置了多种实现,也只会选择第一个。若是须要支持也是能够的,经过配置一个名称来指定便可,但感受价值并不大。

SpringFactoriesLoader就是spring-boot实现的相似java.util.ServiceLoader的一种服务加载工具,它负责从resources/META-INF/spring.factories中读取相应的配置,并对其加载实例化。总共包含两个核心方法:

  • loadFactoryNames

    这个方法就是加载某个接口的全部指定实现类名,它能够服务于下面的loadFactories方法。

  • loadFactories 首先经过loadFactoryNames方法从配置文件中获取接口与实现类的关系,而后一个一个实例化服务实现类。

通过以上几步的调整,就基本实现了一个简单的基于SPI思想的组件扩展机制。客户端能够扩展任意的限流机制去替换。

本文源码

文中代码是依赖上述项目的,若是有不明白的可下载源码

相关文章
相关标签/搜索