服务拆分之《Dubbo服务跨云通信》

 

2022年10月开始,公司从阿里请来的架构师将全力推进服务拆分这个计划。实际上这个计划早就提上日程了,只是没有一个带头大哥带着把这个事情搞起来,因为这个系统太庞大了,还非常的复杂,当时就没有哪一个人是对系统完全了解的,很多底层的非核心的原子服务干啥的都不清楚。平时也就核心业务所涉及到的几个服务是被大家所熟知的,其他的都是有待考察的系统。就在这样的背景下,就直接开干了。

所有的服务都从帆一云迁移到阿里云上,所以,所有的中间件也是购买的现成的阿里云的云产品。所有的服务都迁移过来了,发现整体流程跑不通,面对这种问题的解决思路提前就已经定好了,我们都知道在迁移的过程中会有很多想想不到的问题,然后真的是遇到什么问题就解决什么问题。前面还好,遇到的都是偏硬件、偏网络和安全的问题,这些问题在运维的协助下就轻松搞定了。因为还没有涉及到业务,就很容易解决。但是后面就遇到了一个非常棘手的问题,就是所有的dubbo服务现在都跑在阿里云上, 但是系统中的有些服务调用的是没有迁移的接口,这些接口就是车联的接口。存储了人车关系的数据和车辆相关的信息。

迁移的过程中,为了减少blocking的时间,我们每天会有晨会和晚上总结的会议,大家在会议上把遇到的blocking的问题提出来,因为有很多的问题都是相识的,所以在一起分享一下基本都能解决。后面我也是提议在Confluence上共享遇到的问题和解决方案,这样,大家遇到问题的第一时间到这个文档里面去找,找不到再去自己解决,解决后把解决方案同步到这里。实际上这种共享的思维一直被使用,到我走的时候我们还在不断的去填充自己的踩过的坑。

分析问题原因,主要原因就是原来的大家的所有的服务为了调用方便,都注册到了同一个ZK集群上,然后C端、B端、车联有一部份服务就都是发布到这个注册中心上,这样服务调用就少了很多的沟通成本。然后,对应C端迁移计划来讲,我们只是把C端的服务迁移出来了,对原来dubbo服务直接调用的接口是没有法办调用的,因为夸云了。实际上,很多问题如果只是从技术角度解决的话是很容易的,dubbo是支持多个注册中心的,它可以把我们调用的那几个接口发布到阿里云的注册中心上就可以了,但是,难就难在业务的方的妥协。车联不同意,也不想这么麻烦的耽误自己的项目计划,因为都是人力成本嘛,理解。

我的解决方案是使用代理服务。整理思路就是我们去搞一个代理服务,发布在原有的集群中,这个proxyService 是可以像原来那样调用车联的服务,然后提供一个http的对外接口。。这样一来,阿里云服务通过dubbo调用的车联的接口就可以通过http的方式调用proxyService暴露的接口。这样还存在一个问题,就是要将调用车联的接口的全部识别出来,然后在客户端发起调用的时候进行过滤拦截,使用http进行调用即可。这样完整的联路就打通了。听起来没有什么问题,但实际上还会遇到问题,站在对方的角度考虑问题是,你部署自己的服务得有自己的机器,不然你的服务出现了问题会影响到他们现在有的服务,然后就是一系列的是发邮件走流程搞基础配置,最终才把服务发布起来。

经过多次评审,接受多方的挑战,最终这样的方案通过并落地了。总是,是不容易。以下是自定义的Dubbo

复制代码
public class CustomStubProxyFactoryWrapper implements ProxyFactory {
    private static final Logger LOGGER = LoggerFactory.getLogger(StubProxyFactoryWrapper.class);
    private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(HttpRouter.class);
    private final ProxyFactory proxyFactory;
    private Protocol protocol;
    static String active = System.getProperty("spring.profiles.active");

    public CustomStubProxyFactoryWrapper(ProxyFactory proxyFactory) {
        this.proxyFactory = proxyFactory;
    }

    public void setProtocol(Protocol protocol) {
        this.protocol = protocol;
    }

    public <T> T getProxy(Invoker<T> invoker) throws RpcException {
        logger.info("*************rpc代理已经启用{}***************", active);
        T proxy = Proxy.getProxy(this.getInterfaces(invoker)).newInstance(new CustomInvocationHandler(invoker));
        if (GenericService.class != invoker.getInterface()) {
            String stub = invoker.getUrl().getParameter("stub", invoker.getUrl().getParameter("local"));
            if (ConfigUtils.isNotEmpty(stub)) {
                Class<?> serviceType = invoker.getInterface();
                if (ConfigUtils.isDefault(stub)) {
                    if (invoker.getUrl().hasParameter("stub")) {
                        stub = serviceType.getName() + "Stub";
                    } else {
                        stub = serviceType.getName() + "Local";
                    }
                }

                try {
                    Class<?> stubClass = ReflectUtils.forName(stub);
                    if (!serviceType.isAssignableFrom(stubClass)) {
                        throw new IllegalStateException("The stub implementation class " + stubClass.getName() + " not implement interface " + serviceType.getName());
                    }

                    try {
                        Constructor<?> constructor = ReflectUtils.findConstructor(stubClass, serviceType);
                        proxy = constructor.newInstance(proxy);
                        URL url = invoker.getUrl();
                        if (url.getParameter("dubbo.stub.event", false)) {
                            url = url.addParameter("dubbo.stub.event.methods", StringUtils.join(Wrapper.getWrapper(proxy.getClass()).getDeclaredMethodNames(), ","));
                            url = url.addParameter("isserver", Boolean.FALSE.toString());

                            try {
                                this.export(proxy, invoker.getInterface(), url);
                            } catch (Exception var9) {
                                LOGGER.error("export a stub service error.", var9);
                            }
                        }
                    } catch (NoSuchMethodException var10) {
                        throw new IllegalStateException("No such constructor \"public " + stubClass.getSimpleName() + "(" + serviceType.getName() + ")\" in stub implementation class " + stubClass.getName(), var10);
                    }
                } catch (Throwable var11) {
                    LOGGER.error("Failed to create stub implementation class " + stub + " in consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion() + ", cause: " + var11.getMessage(), var11);
                }
            }
        }

        return proxy;
    }

    public Class<?>[] getInterfaces(Invoker invoker) throws RpcException {
        Class<?>[] interfaces = null;
        String config = invoker.getUrl().getParameter("interfaces");
        if (config != null && config.length() > 0) {
            String[] types = Constants.COMMA_SPLIT_PATTERN.split(config);
            if (types != null && types.length > 0) {
                interfaces = new Class[types.length + 2];
                interfaces[0] = invoker.getInterface();
                interfaces[1] = EchoService.class;

                for(int i = 0; i < types.length; ++i) {
                    interfaces[i + 1] = ReflectUtils.forName(types[i]);
                }
            }
        }

        if (interfaces == null) {
            interfaces = new Class[]{invoker.getInterface(), EchoService.class};
        }

        return interfaces;
    }

    public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) throws RpcException {
        return this.proxyFactory.getInvoker(proxy, type, url);
    }

    private <T> Exporter<T> export(T instance, Class<T> type, URL url) {
        return this.protocol.export(this.proxyFactory.getInvoker(instance, type, url));
    }
}
复制代码

创建自定义的starter,需要设置默认配置信息:

使用自定义的SPI。

 

程序员,多学技术,思路打开。突然想起来了一句话,是用来描述艾弗森的。大概意思是:你用一百种方式过他,而他只用一种方式过你。  

 贴几个地址:

https://cn.dubbo.apache.org/zh-cn/docs/references/spis/proxy-factory/

posted @   Eular  阅读(11)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示