簡易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思想的組件擴展機制。客戶端能夠擴展任意的限流機制去替換。

本文源碼

文中代碼是依賴上述項目的,若是有不明白的可下載源碼

相關文章
相關標籤/搜索