代码技巧——dubbo泛化调用

现在有一种业务场景:

(1)业务方B调用任务系统A,提交一个任务,由A保证任务尽可能的被执行成功(系统A自带幂等、重试),在A完成任务的执行后将执行情况同步回调通知B应用;

(2)因此,B应用在提交任务时需要把自己的回调地址作为参数传递给A,这个参数被定义为dubbo接口的全路径+方法名+分组+版本号;

(3)系统A不可能提前知晓这个dubbo接口并注册对应的consumer,因此只能通过dubbo的「泛化调用」方式调用业务方B的dubbo接口,即Dubbo的调用方,在不引入服务接口类的情况下,远程调用其他Dubbo服务;

"泛化接口调用方式主要用于客户端没有API接口及模型类元的情况,参数及返回值中的所有POJO均用Map表示,通常用于框架集成,比如:实现一个通用的服务测试框架,可通过GenericService调用所有服务实现。"——使用泛化调用 | Apache Dubbo

下面给出代码示例;

(1)核心方法

import com.alibaba.fastjson.JSON;
import com.google.common.collect.Maps;
import com.internet.bocfg.audit.client.model.AuditFlowCallbackRequest;
import com.internet.bocfg.audit.client.model.AuditFlowCallbackResponse;
import com.internet.bocfg.audit.constant.AuditConstants;
import com.internet.bocfg.audit.exception.AuditBusinessException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.dubbo.config.ApplicationConfig;
import org.apache.dubbo.config.ReferenceConfig;
import org.apache.dubbo.config.RegistryConfig;
import org.apache.dubbo.config.utils.ReferenceConfigCache;
import org.apache.dubbo.rpc.service.GenericService;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.Map;

/**
 * @author Akira
 * @description dubbo泛化调用服务
 * @date 2022/8/28
 */
@Slf4j
@Service
public class DubboGenericService {

    /**
     * 缓存已经注册过的GenericService
     */
    private static final Map<String, GenericService> stubCache = Maps.newConcurrentMap();

    private final ApplicationConfig applicationConfig = new ApplicationConfig("bocfg-audit");

    @Resource(name = "myRegistryConfig")
    private RegistryConfig registryConfig;

    /**
     * (泛化)远程调用
     *
     * @param serviceName 接口路径
     * @param methodName  方法名
     * @param group       分组
     * @param version     版本
     * @param request     自定义的dubbo接口参数
     */
    private void remoteInvoke(String serviceName, String methodName, String group, String version,
                              AuditFlowCallbackRequest request) {
        try {
            // 先尝试从缓存中拿GenericService 设置缓存的原因:ReferenceConfig实例很重,封装了与注册中心的连接以及与provider的连接,需要缓存,否则重复生成ReferenceConfig可能造成性能问题并且会有内存和连接泄漏
            String stubKey = stubKey(serviceName, methodName, version);
            GenericService genericService = stubCache.get(stubKey);
            // 未取到则准备初始化当前GenericService
            if (genericService == null) {
                ReferenceConfig<GenericService> reference = new ReferenceConfig<>();
                // 连接相关的配置,包括:zk地址、应用名、协议等
                reference.setApplication(applicationConfig);
                reference.setRegistry(registryConfig);
                // 调用相关的配置,包括:声明为泛化接口、不检查状态、负载均衡方式、超时时间等
                reference.setGeneric(true);
                reference.setLoadbalance("roundrobin");
                reference.setCheck(false);
                reference.setTimeout(AuditConstants.AUDIT_CALLBACK_DEFALUT_TIMEOUT);
                // 调用目标provider相关的配置,包括:接口、group、version、
                reference.setInterface(serviceName);
                reference.setGroup(StringUtils.isBlank(group) ? AuditConstants.AUDIT_CALLBACK_DEFALUT_GROUP : group);
                reference.setVersion(StringUtils.isBlank(version) ? AuditConstants.AUDIT_CALLBACK_DEFALUT_VERSION : version);
                // 这里优先使用dubbo内置的简单缓存工具类进行缓存,若没有则放入自己定义的缓存stubCache中
                ReferenceConfigCache cache = ReferenceConfigCache.getCache();
                genericService = cache.get(reference);
                if (genericService != null) {
                    stubCache.putIfAbsent(stubKey, genericService);
                }
                genericService = stubCache.get(stubKey);
            }
            // 至此,拿到了dubbo接口的genericService
            if (genericService == null) {
                throw new IllegalStateException("No provider available: " + stubKey);
            }
            // 泛化调用的参数:方法名、方法参数类型全路径、方法参数
            String[] parameterTypes = new String[]{AuditConstants.AUDIT_CALLBACK_REQUEST_CLASSNAME};
            Object[] args = new Object[]{request};
            Object result = genericService.$invoke(methodName, parameterTypes, args);
            if (result != null) {
                // 泛化调用返参为Object类型,这里做一个参数转换
                AuditFlowCallbackResponse callbackResponse = JSON.parseObject(JSON.toJSONString(result), AuditFlowCallbackResponse.class);
                if (!callbackResponse.isSuccess()) {
                    log.warn("callbackResponse fail. serviceName={} methodName={} version={} request={} return={}", serviceName, methodName, version, JSON.toJSONString(request), JSON.toJSONString(callbackResponse));
                    throw new AuditBusinessException("auditCallback fail:" + callbackResponse.getMsg());
                }
            }
        } catch (Throwable e) {
            log.error("dubbo remoteInvoke fail. serviceName={} methodName={} version={} auditFlowCallbackRequest={}", serviceName, methodName, version, JSON.toJSONString(request));
            throw e;
        }
    }

    /**
     * 拼接dubbo的GenericService缓存stubCache(map)中的存根key
     */
    private String stubKey(String serviceName, String method, String version) {
        return serviceName + "$" + method + "$" +
                (StringUtils.isBlank(version) ? AuditConstants.AUDIT_CALLBACK_DEFALUT_VERSION : version);
    }

}

(2)RegistryConfig配置

import com.internet.vivocfg.client.ConfigManager;
import org.apache.dubbo.config.RegistryConfig;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class PropertyConfig {

    @Bean("myRegistryConfig")
    public RegistryConfig imExchangeConfig() {
        RegistryConfig registryConfig = null;
        registryConfig = new RegistryConfig(ConfigManager.get("dubbo.zk.registry.address"));
        registryConfig.setProtocol("zookeeper");
        registryConfig.setClient("curator");
        return registryConfig;
    }
}

参考:

使用泛化调用 | Apache Dubbo

dubbo实战之“泛化调用”探索 - 博客园

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值