1. 程式人生 > >關於GeneratorService泛化引用Dubbo的快取問題

關於GeneratorService泛化引用Dubbo的快取問題

在泛化引用dubbo時,因為referencrConfig是一個很重的例項,所以需要使用到快取

簡單呼叫時

1.dubbo自帶的XX快取,快取自帶的cacheKey

完整程式碼:

public static void main(String[] args) {
        // 應用設定
        ApplicationConfig application = new ApplicationConfig();
        application.setName("dubbo-user");

        // 註冊中心
        RegistryConfig registry = new RegistryConfig();
        registry.setProtocol("zookeeper");
        registry.setAddress("192.168.0.1");
        registry.setPort(2181);

        // 服務引用
        ReferenceConfig<GenericService> reference = new ReferenceConfig<GenericService>();
        reference.setApplication(application);
        reference.setRegistry(registry);
        reference.setInterface(UserService.class.getName());//完整類名
        reference.setVersion("0.0.2");
        reference.setGroup("group001");
        reference.setGeneric(true);  // 泛化
        reference.setAsync(true);    // 非同步
        reference.setCache("lru");

        // 直接引用,每次都要建立連線,比較重
        //GenericService genericService = reference.get();   //泛化處理
        //UserService xxxService = reference.get();   //非泛化處理

        // 通過使用cache進行快取
        ReferenceConfigCache cache = ReferenceConfigCache.getCache();
        GenericService genericService = cache.get(reference);

        // 泛化呼叫
        Object resultUser = genericService.$invoke("queryUser", new String[]{"java.lang.Integer"}, new Object[]{1});
        // 非同步獲取泛化呼叫結果
        Future<Object> fooFuture = RpcContext.getContext().getFuture();
        try {
            resultUser = fooFuture.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

        Map<String, Object> userMap=(Map<String, Object>) resultUser;
        String jsonObject = JSONObject.valueToString(userMap);
        System.out.println(jsonObject);

核心程式碼:


        ReferenceConfigCache cache = ReferenceConfigCache.getCache();
        GenericService genericService = cache.get(reference);

2.使用預設的dubbo快取,有時候會產生一些問題或者隱患:比如:dubbo提供的快取,預設的cacheKey只有dubbo的三個屬性:group/interfaceName:version;下面這種情況:兩個介面A1和A2,同名同分組同版本號,釋出在不同的註冊中心,然後用泛化引用去呼叫:

      假設首先呼叫介面A1,此時cache將對應的genericService以預設cacheKey存在其中;接著呼叫介面A2時,會直接獲取到快取中呼叫A1時的service來使用,因為兩個服務的cache是相同的;這就容易產生錯誤了;(同樣的同一介面釋出在不同的註冊中心時,若第一次呼叫服務的註冊中心虛機停掉了,即使我們另一個註冊中心的虛機正常執行,也只會呼叫前者而呼叫失敗),基於這種情況,我們選擇 指定快取的KeyGenerator,即cacheKey,程式碼如下:

 public ReferenceConfigCache.KeyGenerator CACHE_KEY_GENERATOR = new ReferenceConfigCache.KeyGenerator() {
        @Override
        public String generateKey(ReferenceConfig<?> referenceConfig) {
            String iName = referenceConfig.getInterface();
            if(com.alibaba.dubbo.common.utils.StringUtils.isBlank(iName)) {
                Class<?> clazz = referenceConfig.getInterfaceClass();
                iName = clazz.getName();
            }
            if(com.alibaba.dubbo.common.utils.StringUtils.isBlank(iName)) {
                throw new IllegalArgumentException("No interface info in ReferenceConfig" + referenceConfig);
            }

            StringBuilder ret = new StringBuilder();
            if(! com.alibaba.dubbo.common.utils.StringUtils.isBlank(referenceConfig.getGroup())) {
                ret.append(referenceConfig.getGroup()).append("/");
            }
            ret.append(iName);
            if(! com.alibaba.dubbo.common.utils.StringUtils.isBlank(referenceConfig.getVersion())) {
                ret.append(":").append(referenceConfig.getVersion());
            }
            String registry = referenceConfig.getRegistry().getAddress();
            ret.append(":").append(registry);
            return ret.toString();
        }
    };

    private GenericService getServiceByCache(RequestModel requestModel) {
        ReferenceConfig<GenericService> referenceConfig = createReferenceConfig(requestModel);
        //dubbo提供的快取
        ReferenceConfigCache cache = ReferenceConfigCache.getCache("dubboCache",CACHE_KEY_GENERATOR);//自定義cacheKey,KeyGenerator為 group/interface:version:registryAddress:port
        return cache.get(referenceConfig);
    }

自定義快取的key,KeyGenerator為 分組/介面名:版本號:註冊中心地址:註冊中心埠號

3.然後,又遇到問題了。。。。我們呼叫一個dubbo服務,第一次呼叫的時候,dubbo是關閉的,此時快取中存了一個GenericService ,當我們開啟dubbo服務後再次呼叫,依然會失敗,只有將快取清空後呼叫才可以,鑑於此,我使用了google guava的快取來實現;

//快取儲存(介面名稱,泛型service)
    private Cache<String, GenericService> servicesCache= CacheBuilder.newBuilder().expireAfterAccess(
            1L, TimeUnit.MINUTES).maximumSize(200).build();

//cacheKey方法
public String generateKey(ReferenceConfig<?> referenceConfig) {
        String iName = referenceConfig.getInterface();
        if(com.alibaba.dubbo.common.utils.StringUtils.isBlank(iName)) {
            Class<?> clazz = referenceConfig.getInterfaceClass();
            iName = clazz.getName();
        }
        if(com.alibaba.dubbo.common.utils.StringUtils.isBlank(iName)) {
            throw new IllegalArgumentException("No interface info in ReferenceConfig" + referenceConfig);
        }

        StringBuilder ret = new StringBuilder();
        if(! com.alibaba.dubbo.common.utils.StringUtils.isBlank(referenceConfig.getGroup())) {
            ret.append(referenceConfig.getGroup()).append("/");
        }
        ret.append(iName);
        if(! com.alibaba.dubbo.common.utils.StringUtils.isBlank(referenceConfig.getVersion())) {
            ret.append(":").append(referenceConfig.getVersion());
        }
        String registry = referenceConfig.getRegistry().getAddress();
        ret.append(":").append(registry);
        return ret.toString();
 }
//獲取快取中的service
private GenericService getServiceByCache(RequestModel requestModel) {
        ReferenceConfig<GenericService> referenceConfig = createReferenceConfig(requestModel);
        String serviceKey = generateKey(referenceConfig);
        GenericService service = null;

        try {
            service = servicesCache.get(serviceKey.toString(), new Callable<GenericService>() {
                @Override
                public GenericService call() throws Exception {
                    GenericService genericService = referenceConfig.get();
                    servicesCache.put(serviceKey.toString(), genericService);
                    return genericService;
                }
            });
        } catch (java.util.concurrent.ExecutionException e) {
            log.error(e);
        }

        return service;
    }

//refresh
/**
     *
     * @param
     * @return
     */
    private ReferenceConfig createReferenceConfig(RequestModel requestModel) {
        ApplicationConfig applicationConfig = new ApplicationConfig(requestModel.getDubApplicationName());
        //多個註冊中心
        List<RegistryConfig> registries = new ArrayList<>();
        String[] dubboUrls = requestModel.getDubAddress().split(",");
        for(String url:dubboUrls){
            RegistryConfig registry = new RegistryConfig(String.join("", requestModel.getDubProtocol(), "://", url));
            registries.add(registry);
        }
        ConsumerConfig consumerConfig =  new ConsumerConfig();
        if (!StringUtils.isEmpty(requestModel.getGroup())) {
            consumerConfig.setGroup(requestModel.getGroup());
        }
        if (!StringUtils.isEmpty(requestModel.getDubTimeout())) {
            consumerConfig.setTimeout(requestModel.getDubTimeout());
        }
        if (!StringUtils.isEmpty(requestModel.getVersion())) {
            consumerConfig.setVersion(requestModel.getVersion());
        }
        consumerConfig.setRetries(0);
        //介面引用
        ReferenceConfig referenceConfig = new ReferenceConfig<>();
        referenceConfig.setApplication(applicationConfig);
        referenceConfig.setConsumer(consumerConfig);
        referenceConfig.setRegistries(registries);
        referenceConfig.setInterface(requestModel.getInterfaceName());
        if(requestModel.getGroup() != null){
            referenceConfig.setGroup(requestModel.getGroup());
        }
        if(requestModel.getVersion() != null){
            referenceConfig.setVersion(requestModel.getVersion());
        }
        referenceConfig.setGeneric(true);
        return referenceConfig;
    }