`
youaremoon
  • 浏览: 33122 次
  • 性别: Icon_minigender_1
  • 来自: 重庆
社区版块
存档分类
最新评论

dubbo源码分析-consumer端3-Invoker创建流程

阅读更多

        从前面一篇创建注册中心的流程当中,我们知道在从注册中心获取到provider的连接信息后,会通过连接创建Invoker。代码见com.alibaba.dubbo.registry.integration.RegistryDirectory的toInvokers方法:

// protocol实现为com.alibaba.dubbo.rpc.Protocol$Adpative, 
//   之前已经讲过,这是dubbo在运行时动态创建的一个类;
// serviceType为服务类的class, 如demo中的com.alibaba.dubbo.demo.DemoService;
// providerUrl为服务提供方注册的连接;
// url为providerUrl与消费方参数的合并
invoker = new InvokerDelegete<T>(protocol.refer(serviceType, url), url, providerUrl);

         此处url的protocol为dubbo,因此protocol.refer最终会调用com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol.refer,同时Protocol存在两个wrapper类,分别为:

com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrapper、

com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper。在dubbo中存在wrapper类的类会被wrapper实例包装后返回,因此在protocol.refer方法调用的时候,会先经过wrapper类。由于这里的复杂性,我们先不讲wrapper类里的refer实现,直接跳到DubboProtocol.refer。

        url的demo如下:

dubbo://30.33.47.127:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-consumer&check=false&....

       DubboProtocol的refer代码如下:

    public <T> Invoker<T> refer(Class<T> serviceType, URL url) throws RpcException {
        // 创建一个DubboInvoker
        DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url, getClients(url), invokers);
        // 将invoker加入到invokers这个Set中
        invokers.add(invoker);
        return invoker;
    }

    // 创建连接Client,该Client主要负责建立连接,发送数据等
    private ExchangeClient[] getClients(URL url){
        //是否共享连接
        boolean service_share_connect = false;
        int connections = url.getParameter(Constants.CONNECTIONS_KEY, 0);
        // 如果connections不配置,则共享连接,否则每服务每连接,
        // 共享连接的意思是对于同一个ip+port的所有服务只创建一个连接,
        // 如果是非共享连接则每个服务+(ip+port)创建一个连接
        if (connections == 0){
            service_share_connect = true;
            connections = 1;
        }
        
        ExchangeClient[] clients = new ExchangeClient[connections];
        for (int i = 0; i < clients.length; i++) {
            if (service_share_connect){
                clients[i] = getSharedClient(url);
            } else {
                clients[i] = initClient(url);
            }
        }
        return clients;
    }

    /**
     *获取共享连接 
     */
    private ExchangeClient getSharedClient(URL url){
        // 以address(ip:port)为key进行缓存
        String key = url.getAddress();
        ReferenceCountExchangeClient client = referenceClientMap.get(key);
        if ( client != null ){
            // 如果连接存在了则引用数加1,引用数表示有多少个服务使用了此client,
            // 当某个client调用close()时,引用数减一,
            // 如果引用数大于0,表示还有服务在使用此连接, 不会真正关闭client
            // 如果引用数为0,表示没有服务在用此连接,此时连接彻底关闭
            if ( !client.isClosed()){
                client.incrementAndGetCount();
                return client;
            } else {
//                logger.warn(new IllegalStateException("client is closed,but stay in clientmap .client :"+ client));
                referenceClientMap.remove(key);
            }
        }
        // 调用initClient来初始化Client
        ExchangeClient exchagneclient = initClient(url);
        // 使用ReferenceCountExchangeClient进行包装
        client = new ReferenceCountExchangeClient(exchagneclient, ghostClientMap);
        referenceClientMap.put(key, client);
        ghostClientMap.remove(key);
        return client; 
    }

    /**
     * 创建新连接.
     */
    private ExchangeClient initClient(URL url) {
        // 获取client参数的值,为空则获取server参数的值,默认为netty
        String str = url.getParameter(Constants.CLIENT_KEY, url.getParameter(Constants.SERVER_KEY, Constants.DEFAULT_REMOTING_CLIENT));
        
        String version = url.getParameter(Constants.DUBBO_VERSION_KEY);
        // 如果是1.0.x版本,需要兼容
        boolean compatible = (version != null && version.startsWith("1.0."));
        // 加入codec参数,默认为dubbo,即DubboCodec
        url = url.addParameter(Constants.CODEC_KEY, Version.isCompatibleVersion() && compatible ? COMPATIBLE_CODEC_NAME : DubboCodec.NAME);
        //默认开启心跳,默认每60s发送一次心跳包
        url = url.addParameterIfAbsent(Constants.HEARTBEAT_KEY, String.valueOf(Constants.DEFAULT_HEARTBEAT));
        
        // BIO存在严重性能问题,暂时不允许使用
        if (str != null && str.length() > 0 && ! ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str)) {
            throw new RpcException("Unsupported client type: " + str + "," +
                    " supported client type is " + StringUtils.join(ExtensionLoader.getExtensionLoader(Transporter.class).getSupportedExtensions(), " "));
        }
        
        ExchangeClient client ;
        try {
            //设置连接应该是lazy的 
            if (url.getParameter(Constants.LAZY_CONNECT_KEY, false)){
                client = new LazyConnectExchangeClient(url ,requestHandler);
            } else {
                client = Exchangers.connect(url ,requestHandler);
            }
        } catch (RemotingException e) {
            throw new RpcException("Fail to create remoting client for service(" + url
                    + "): " + e.getMessage(), e);
        }
        return client;
    }

         可以看到client创建由com.alibaba.dubbo.remoting.exchange.Exchanges处理,其代码如下:

    public static ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException {
        if (url == null) {
            throw new IllegalArgumentException("url == null");
        }
        if (handler == null) {
            throw new IllegalArgumentException("handler == null");
        }
        url = url.addParameterIfAbsent(Constants.CODEC_KEY, "exchange");
        // 默认通过HeaderExchanger.connect创建
        return getExchanger(url).connect(url, handler);
    }

    public static Exchanger getExchanger(URL url) {
        // 默认type为header,因此默认的Exchanger为com.alibaba.dubbo.remoting.exchange.support.header.HeaderExchanger
        String type = url.getParameter(Constants.EXCHANGER_KEY, Constants.DEFAULT_EXCHANGER);
        return getExchanger(type);
    }

    public static Exchanger getExchanger(String type) {
        return ExtensionLoader.getExtensionLoader(Exchanger.class).getExtension(type);
    }

         HeaderExchanger的connect代码如下:

    public ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException {
        return new HeaderExchangeClient(Transporters.connect(url, new DecodeHandler(new HeaderExchangeHandler(handler))));
    }

         这里简单介绍下这些类的作用:

        HeaderExchangeHandler: ExchangeHandler的代理,HeaderExchangeHandler将数据封装后调用ExchangeHandler的连接/断开/发送请求/接收返回数据/捕获异常等方法;

        DecodeHandler: 也是一个代理,在HeaderExchangeHandler的功能之上加入了解码功能;

        Transporters.connect默认得到的是NettyTransporter:创建NettyClient, 该client是真正的发起通讯的类;

        NettyClient在初始化的时候会做一些比较重要的事情,我们先看下:

 

    public NettyClient(final URL url, final ChannelHandler handler) throws RemotingException {
        super(url, wrapChannelHandler(url, handler));
    }

    protected static ChannelHandler wrapChannelHandler(URL url, ChannelHandler handler){
        // 设置threadName, 设置默认的threadpool类型,
        // 
        url = ExecutorUtil.setThreadName(url, CLIENT_THREAD_POOL_NAME);
        url = url.addParameterIfAbsent(Constants.THREADPOOL_KEY, Constants.DEFAULT_CLIENT_THREADPOOL);
        // 对handler再次进行包装
        return ChannelHandlers.wrap(handler, url);
    }

         我们知道前面得到的包装对象DecodeHandler,而ChannelHandlers.wrap对该Handler再次进行包装:

    protected ChannelHandler wrapInternal(ChannelHandler handler, URL url) {
        return new MultiMessageHandler(new HeartbeatHandler(ExtensionLoader.getExtensionLoader(Dispatcher.class)
                                        .getAdaptiveExtension().dispatch(handler, url)));
    }

         这些包装类在之前handler的基础上加入的功能:

 

        dispatch生成的对象AllChannelHandler:加入线程池,所有方法都异步的调用;

        HeartbeatHeandler: 心跳包的发送和接收到心跳包后的处理;

        MultiMessageHandler:如果接收到的消息为MultiMessage,则将其拆分为单个Message给后面的Handler处理;

   在看看NettyClient在构造方法中还做了哪些操作:

 

    // 调用了父类com.alibaba.dubbo.remoting.transport.AbstractClient的构造方法
    public AbstractClient(URL url, ChannelHandler handler) throws RemotingException {
        ...省略部分代码...
        try {
            // 
            doOpen();
        } catch (Throwable t) {
            close();
            throw new RemotingException(url.toInetSocketAddress(), null, 
                                        "Failed to start " + getClass().getSimpleName() + " " + NetUtils.getLocalAddress() 
                                        + " connect to the server " + getRemoteAddress() + ", cause: " + t.getMessage(), t);
        }
        try {
            // connect.
            connect();
            if (logger.isInfoEnabled()) {
                logger.info("Start " + getClass().getSimpleName() + " " + NetUtils.getLocalAddress() + " connect to the server " + getRemoteAddress());
            }
        } catch (RemotingException t) {
            if (url.getParameter(Constants.CHECK_KEY, true)) {
                close();
                throw t;
            } else {
                // 如果check为false,则连接失败时Invoker依然可以创建
                logger.warn("Failed to start " + getClass().getSimpleName() + " " + NetUtils.getLocalAddress()
                             + " connect to the server " + getRemoteAddress() + " (check == false, ignore and retry later!), cause: " + t.getMessage(), t);
            }
        } catch (Throwable t){
            close();
            throw new RemotingException(url.toInetSocketAddress(), null, 
                    "Failed to start " + getClass().getSimpleName() + " " + NetUtils.getLocalAddress() 
                    + " connect to the server " + getRemoteAddress() + ", cause: " + t.getMessage(), t);
        }
        
        ...省略部分代码...
    }

          可以看到在构造方法处已经开始创建连接,netty如何创建连接此处不再详细介绍,可以看看之前的netty介绍。需要注意的时连接失败的时候,如果check参数为false则Invoker依然可以创建,否则在初始化阶段会报异常。

 

    回过头来看看HeaderExchangeClient,改类创建了一个发送心跳包的定时任务:

 

    public HeaderExchangeClient(Client client){
        if (client == null) {
            throw new IllegalArgumentException("client == null");
        }
        this.client = client;
        this.channel = new HeaderExchangeChannel(client);
        String dubbo = client.getUrl().getParameter(Constants.DUBBO_VERSION_KEY);
        // 默认为60秒发一次心跳包,如果连续3个心跳包无响应则表示连接断开 
       this.heartbeat = client.getUrl().getParameter( Constants.HEARTBEAT_KEY, dubbo != null && dubbo.startsWith("1.0.") ? Constants.DEFAULT_HEARTBEAT : 0 );
        this.heartbeatTimeout = client.getUrl().getParameter( Constants.HEARTBEAT_TIMEOUT_KEY, heartbeat * 3 );
        if ( heartbeatTimeout < heartbeat * 2 ) {
            throw new IllegalStateException( "heartbeatTimeout < heartbeatInterval * 2" );
        }
        startHeatbeatTimer();
    }

    private void startHeatbeatTimer() {
        stopHeartbeatTimer();
        if ( heartbeat > 0 ) {
            heatbeatTimer = scheduled.scheduleWithFixedDelay(
                    new HeartBeatTask( new HeartBeatTask.ChannelProvider() {
                        public Collection<Channel> getChannels() {
                            return Collections.<Channel>singletonList( HeaderExchangeClient.this );
                        }
                    }, heartbeat, heartbeatTimeout),
                    heartbeat, heartbeat, TimeUnit.MILLISECONDS );
        }
    }

       我们知道,在socket通讯时,数据发送方和接收方必须建立连接,而建立的连接是否可用,为了探测连接是否可用,可以通过发送简单的通讯包并看是否收到回包的方式,这就是心跳。如果没有心跳包,则很有可能连接的一方已经断开或者中间线路故障,双方都不知道这种情况。 因此心跳包很有必要引入。心跳包的实现比较简单,这里简单介绍下,不再贴具体代码:通过拦截(代理)所有的发送/接收数据的方法,记录下最后一次read(接收数据)、write(发送数据)的时间,如果都大于心跳的时间阈值(如上面的60s)则发送一条数据给对方,该数据的格式不重要,只要有心跳的标识(即对方可以解析出这是一个心跳包)即可,对方接收到数据以后也会返回一个应答的包,如果发送方接收到回包,则最后一次read时间将会被充值为当前时间,表示连接未断开。如果发送方一直未收到回包,则指定时间(如上面的60s)后再次发送心跳包。如果多次(如上面的3次)发送均未收到回包(心跳超时),则判断连接已经断开。此时根据应用的需求断开连接或者重新连接。在dubbo中,如果心跳超时则进行重连。

        除了心跳以外,我们可以看到HeaderExchangeChannel对client再次进行了封装,它的作用是将要发送的实际数据封装成com.alibaba.dubbo.remoting.exchange.Request对象。

        最终获得的HeaderExchangeChannel被封装到HeaderExchangeClient中,传入到DubboInvoker,最终DubboProtocol.refer返回了DubboInvoker。但流程还未结束,还记得我们一开头提起的wrapper类吧。下面来看看这两个类还做了哪些操作。

        DubboProtocol.refer执行后,进入到ProtocolFilterWrapper,其refer代码如下:

 

    public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
        if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
            return protocol.refer(type, url);
        }
        // protocol为dubbo时执行到这里
        return buildInvokerChain(protocol.refer(type, url), Constants.REFERENCE_FILTER_KEY, Constants.CONSUMER);
    }

    private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
        // 初始的last为刚刚创建的DubboInvoker
        Invoker<T> last = invoker;
        // 加载group为consumer的Filter,加载到的Filter依次为:
        // com.alibaba.dubbo.rpc.filter.ConsumerContextFilter
        // com.alibaba.dubbo.rpc.protocol.dubbo.filter.FutureFilter 
        // com.alibaba.dubbo.monitor.support.MonitorFilter 
        List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);
        if (filters.size() > 0) {
            // filter从最后一个开始依次封装,最终形成一个链,调用顺序为filters的顺序
            for (int i = filters.size() - 1; i >= 0; i --) {
                final Filter filter = filters.get(i);
                final Invoker<T> next = last;
                last = new Invoker<T>() {

                    public Class<T> getInterface() {
                        return invoker.getInterface();
                    }

                    public URL getUrl() {
                        return invoker.getUrl();
                    }

                    public boolean isAvailable() {
                        return invoker.isAvailable();
                    }

                    public Result invoke(Invocation invocation) throws RpcException {
                        return filter.invoke(next, invocation);
                    }

                    public void destroy() {
                        invoker.destroy();
                    }

                    @Override
                    public String toString() {
                        return invoker.toString();
                    }
                };
            }
        }
        return last;
    }

         再看看ProtocolListenerWrapper:

 

    public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
        if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
            return protocol.refer(type, url);
        }
        return new ListenerInvokerWrapper<T>(protocol.refer(type, url), 
                Collections.unmodifiableList(
                        ExtensionLoader.getExtensionLoader(InvokerListener.class)
                        .getActivateExtension(url, Constants.INVOKER_LISTENER_KEY)));
    }

    // ListenerInvokerWrapper构造方法
    public ListenerInvokerWrapper(Invoker<T> invoker, List<InvokerListener> listeners){
        if (invoker == null) {
            throw new IllegalArgumentException("invoker == null");
        }
        this.invoker = invoker;
        this.listeners = listeners;
        if (listeners != null && listeners.size() > 0) {
            for (InvokerListener listener : listeners) {
                if (listener != null) {
                    try {
                        // 直接触发referred方法
                        listener.referred(invoker);
                    } catch (Throwable t) {
                        logger.error(t.getMessage(), t);
                    }
                }
            }
        }
    }

        listener在consumer初始化和destroy时生效,不影响正常的执行,默认情况下listeners为空。      

 

        到这里InvokerDelegete的生成基本上完成了,结合第一篇consumer的介绍,我们可以得到下图(后续我们再讲讲各个类的具体实现):

 

 

 

 

  • 大小: 96 KB
分享到:
评论

相关推荐

    MATLAB自定义函数,移动平均(Moving Average)、Savitzky-Golay滤波(SG滤波) 和 邻域平均滤波(Adjacent Averaging) 算法三种算法实现

    以下是对移动平均(Moving Average)、Savitzky-Golay滤波(SG滤波) 和 邻域平均滤波(Adjacent Averaging) 算法实现信号处理。移动平均 vs. 邻域平均:二者数学本质相同,均为窗口内均值计算。差异仅在于实现时的命名习惯(如“邻域平均”更强调局部邻域操作)。 SG滤波:基于最小二乘多项式拟合,通过保留高阶导数信息(如峰形曲率)实现高保真平滑。 选择移动平均/邻域平均: 实时性要求高(如传感器数据流处理)。 信号特征简单,无需保留高频细节(如温度趋势分析)。 对实时性要求高或噪声简单,可用移动平均。 选择SG滤波: 信号峰形关键(如FBG中心波长检测),优先选SG滤波。 光谱分析、色谱峰检测等需保留峰形特征的场景。 信号含复杂高频成分但需抑制随机噪声(如ECG信号去噪)。 边缘处理策略 镜像填充('symmetric'):减少边界突变,适合多数信号。 常数填充('constant'):适合信号首尾平稳的场景。 截断处理:输出数据变短,适合后续插值。

    FreeRTOS-KEY调用挂起和恢复.zip

    基于STM32F1系列FreeRTOS的移植使用 详细移植过程可以参考: FreeRTOS实战(二)章节:https://blog.csdn.net/manongdky/category_12968613.html?spm=1001.2014.3001.5482

    【MATLAB时间序列预测】MATLAB实现PSO-ELM粒子群优化算法(PSO)优化极限学习机(ELM)时间序列预测的详细项目实例(含模型描述及示例代码)

    内容概要:本文档详细介绍了如何使用MATLAB实现粒子群优化算法(PSO)优化极限学习机(ELM)进行时间序列预测的项目实例。项目背景指出,PSO通过模拟鸟群觅食行为进行全局优化,ELM则以其快速训练和强泛化能力著称,但对初始参数敏感。结合两者,PSO-ELM模型能显著提升时间序列预测的准确性。项目目标包括提高预测精度、降低训练时间、处理复杂非线性问题、增强模型稳定性和鲁棒性,并推动智能化预测技术的发展。面对数据质量问题、参数优化困难、计算资源消耗、模型过拟合及非线性特征等挑战,项目采取了数据预处理、PSO优化、并行计算、交叉验证等解决方案。项目特点在于高效的优化策略、快速的训练过程、强大的非线性拟合能力和广泛的适用性。; 适合人群:对时间序列预测感兴趣的研究人员、数据科学家以及有一定编程基础并希望深入了解机器学习优化算法的工程师。; 使用场景及目标:①金融市场预测,如股票走势预测;②气象预报,提高天气预测的准确性;③交通流量预测,优化交通管理;④能源需求预测,确保能源供应稳定;⑤医疗健康预测,辅助公共卫生决策。; 其他说明:文档提供了详细的模型架构描述和MATLAB代码示例,涵盖数据预处理、PSO优化、ELM训练及模型评估等关键步骤,帮助读者全面理解和实践PSO-ELM模型。

    线描城市教学课件素材模板.pptx

    线描城市教学课件素材模板

    前端水印方案:Vue3SVG的防截图实现.pdf

    文档支持目录章节跳转同时还支持阅读器左侧大纲显示和章节快速定位,文档内容完整、条理清晰。文档内所有文字、图表、函数、目录等元素均显示正常,无任何异常情况,敬请您放心查阅与使用。文档仅供学习参考,请勿用作商业用途。 Vue 3是一款备受瞩目的JavaScript框架,它采用了基于Proxy的响应式系统,显著提升了性能和调试能力。其Composition API带来了更高效的逻辑组织方式,使代码复用变得轻而易举。Tree-shaking支持让打包后的文件体积更小,进一步优化了应用性能。Vue 3还与TypeScript深度集成,提供了更完善的类型推导,让开发过程更加顺畅。无论是构建大型应用还是小型项目,Vue 3都能凭借其出色的性能和灵活的架构,帮助开发者高效完成任务,是现代Web开发的理想选择。

    手绘卡通汽球儿童教学课件素材模板.pptx

    手绘卡通汽球儿童教学课件素材模板

    COMSOL环境下圆偏振光与偏振转换技术的理论与实践探究

    内容概要:本文深入探讨了COMSOL环境中圆偏振光与偏振转换技术的应用。首先介绍了圆偏振光的概念及其在多个科学和技术领域的重要意义,如光学仪器、量子计算和生物医学。接着阐述了偏振转换的原理及其广泛应用,特别是在光学仪器、通信和显示技术方面。最后,通过具体案例展示了如何利用COMSOL进行圆偏振斜入射的模拟与分析,包括建模、参数设定、模拟运行及结果解读,揭示了不同条件下偏振转换的效果。 适合人群:对光学仿真有兴趣的研究人员、工程师及高校学生。 使用场景及目标:①理解圆偏振光的基础理论;②掌握偏振转换的技术原理;③学会使用COMSOL进行相关仿真实验。 阅读建议:本文提供了详细的理论背景和实践指导,建议读者先熟悉基本概念再逐步深入到具体的模拟操作中,以便更好地掌握所学知识并在实践中加以运用。

    深度学习Python实现基于KOA-CNN-BiLSTM开普勒算法优化卷积双向长短期记忆神经网络数据分类预测的详细项目实例(含模型描述及示例代码)

    内容概要:本文介绍了基于KOA-CNN-BiLSTM的深度学习模型在数据分类预测中的应用。该模型结合了卷积神经网络(CNN)和双向长短期记忆网络(BiLSTM),以高效提取空间和时序特征。为了解决传统CNN和LSTM的局限性,项目引入了Kepler算法进行参数优化,提升了模型的收敛速度和预测精度。项目通过优化模型结构、采用数据增强与正则化技术、引入Kepler算法解决了模型训练复杂度高、过拟合和参数优化困难等问题。文中还展示了模型的具体架构及其在多个领域的应用潜力,并提供了详细的代码示例。 适合人群:具备一定机器学习和深度学习基础的研发人员,尤其是对CNN、LSTM及优化算法感兴趣的从业者和研究人员。 使用场景及目标:① 提升数据分类与预测任务的准确率和效率;② 应用于金融、医疗、自然语言处理、物流与交通、环境监测等领域;③ 掌握结合CNN和BiLSTM的多模态特征提取方法;④ 学习Kepler算法在深度学习模型中的应用。 阅读建议:读者应具备一定的深度学习基础,重点理解CNN与BiLSTM的结合方式及Kepler算法的作用机制。建议结合代码示例进行实践,以便更好地掌握模型的设计与实现。

    【MATLAB例程】二维卡尔曼滤波的完整代码,仿真程序,线性系统的卡尔曼滤波(KF),带有误差统计与输出

    该代码提供了一个完整的二维卡尔曼滤波实现,涵盖了从初始化、状态更新到结果可视化的全过程。适合用于学习卡尔曼滤波的基本原理和实际应用。

    【大数据处理】Hadoop数据倾斜成因分析与综合解决方案:从预处理到任务参数调优全流程解析

    内容概要:文章深入剖析了Hadoop数据倾斜的原因及解决方案。数据倾斜主要源于数据分布不均衡、Key的Hash分布不均、业务特性或操作设计问题以及输入文件特性四个方面。针对这些问题,文中提出了涵盖预处理、Map、Shuffle、Reduce阶段及特定场景的多种优化措施。 适用人群:从事大数据开发、运维的技术人员,尤其是对Hadoop有一定了解并遇到数据倾斜问题的工程师; 使用场景及目标:①帮助技术人员识别数据倾斜现象,如任务进度停滞、节点OOM等;②提供从预处理到任务执行各阶段的优化策略,以解决数据倾斜带来的性能瓶颈; 其他说明:数据倾斜是Hadoop集群中常见的性能问题,解决它需要综合考虑数据特征、业务逻辑及系统配置,文章提供的方案具有较强的实用性和指导意义,建议读者根据实际情况选择合适的优化手段。

    化工领域NMP回收服务全球市场分析:预计2031年全球NMP回收服务市场规模将达到106万吨

    N-甲基吡咯烷酮(NMP)是一种具有高极性、高沸点、低粘度、低挥发性、高热稳定性和化学稳定性的非质子溶剂。作为高性能溶剂,其广泛应用于锂离子电池制造、化工生产等多个领域。 NMP原料来源可分为合成NMP与再生NMP两类。合成NMP指通过化学合成工艺制得的NMP产品,其工业生产路线以γ-丁内酯(GBL)与单甲基胺为原料经缩合反应生成。再生NMP则指对使用后的NMP废液进行回收提纯 NMP废液特性: 高浓度NMP:废液中NMP含量较高,因NMP强溶解性可能混合多种有机物及无机物 低毒性但具刺激性:虽较其他有机溶剂毒性低,但高浓度接触仍对人体皮肤及眼睛产生刺激 处理难度大:因高沸点与强溶解性,单纯物理蒸发或自然挥发难以处理,需采用特定回收净化技术 严格环保要求:尤其在电池制造领域,NMP纯度要求极高,再生处理后的NMP纯度须达到同等标准,否则将影响产品质量与环境安全 NMP回收模式: 委托加工模式:回收企业为客户提供闭环循环服务,直接回收客户废液并提纯后返还。该模式可降低客户处理成本,实现资源循环利用 购销模式:回收企业采购上游供应商的NMP废液,经处理提纯后销售给下游客户,通过购销差价盈利 内部循环模式:大型企业集团自建回收处理设施,实现废液中NMP的内部循环利用。例如三菱重工在国内外建有溶剂回收装置,特别是随着全球锂电池需求增长,其海外工厂陆续采用现场回收设备,无需第三方处理即可实现NMP的直接回收提纯。 据QYResearch调研团队最新报告“全球NMP回收服务市场报告2025-2031”显示,预计2031年全球NMP回收服务市场规模将达到106万吨,未来几年年复合增长率CAGR为10.0%。

    QGraphics绘制圆盘不同坐标位置的厚度值,含缩放实现

    QGraphics绘制圆盘不同坐标位置的厚度值,含缩放实现

    交友盲盒小程序版本+全开源版本.zip

    前段时间花钱买的小程序源码 感觉太单调了 全开源版本小程序 这个小程序是云开发的不需要服务器域名 支持流量主wx支付。超级能吸引年轻人的一款小程序 版本新增: 1.Ui美化 2.星座匹配(通过星座进行盲盒) 3.后台管理(可以实时看到用户数量) 4.支付S I P 9功能(后台可以设置支付金额) 5.骗审核模式(可以快速帮助大家通过小程序审核实现上线) 企业小程序可以对接wx支付,非企业小程序只能流量主。 搭建出来一直放那界面单调 就不想运营了 放在那也没啥用 拿出来分享一下吧

    数据埋点方案:Vue3+Grafana的监控看板.pdf

    文档支持目录章节跳转同时还支持阅读器左侧大纲显示和章节快速定位,文档内容完整、条理清晰。文档内所有文字、图表、函数、目录等元素均显示正常,无任何异常情况,敬请您放心查阅与使用。文档仅供学习参考,请勿用作商业用途。 Vue 3是一款备受瞩目的JavaScript框架,它采用了基于Proxy的响应式系统,显著提升了性能和调试能力。其Composition API带来了更高效的逻辑组织方式,使代码复用变得轻而易举。Tree-shaking支持让打包后的文件体积更小,进一步优化了应用性能。Vue 3还与TypeScript深度集成,提供了更完善的类型推导,让开发过程更加顺畅。无论是构建大型应用还是小型项目,Vue 3都能凭借其出色的性能和灵活的架构,帮助开发者高效完成任务,是现代Web开发的理想选择。

    性能追踪方案:Vue3PerformanceAPI的监控.pdf

    文档支持目录章节跳转同时还支持阅读器左侧大纲显示和章节快速定位,文档内容完整、条理清晰。文档内所有文字、图表、函数、目录等元素均显示正常,无任何异常情况,敬请您放心查阅与使用。文档仅供学习参考,请勿用作商业用途。 Vue 3是一款备受瞩目的JavaScript框架,它采用了基于Proxy的响应式系统,显著提升了性能和调试能力。其Composition API带来了更高效的逻辑组织方式,使代码复用变得轻而易举。Tree-shaking支持让打包后的文件体积更小,进一步优化了应用性能。Vue 3还与TypeScript深度集成,提供了更完善的类型推导,让开发过程更加顺畅。无论是构建大型应用还是小型项目,Vue 3都能凭借其出色的性能和灵活的架构,帮助开发者高效完成任务,是现代Web开发的理想选择。

    【数据库设计】Visio绘制ER图详细教程:实体关系图的自定义模板与高效绘制方法

    内容概要:本文档详细介绍了使用Visio绘制ER图的方法,首先阐述了ER图的三个基本要素:实体、属性、关系,并解释了Visio中没有现成模板的问题以及解决方案,即通过自定义模具的方式添加所需的图形元素。接着描述了绘制ER图的两种主要方式:手动绘制和利用Visio的反向工程技术。对于手动绘制,文中以留言板数据库为例,具体演示了从创建实体、设置属性到建立实体间关系的全过程。而对于反向工程,则强调了其高效性,支持多种数据库类型,如Access、MSSQL、Excel等,并给出了详细的步骤说明,包括设置反向工程参数、选择数据库路径、指定生成的表等关键环节。 适合人群:适合有一定数据库基础知识,尤其是正在学习或从事数据库设计工作的人员,包括但不限于数据库管理员、软件开发者、系统分析师等。 使用场景及目标:①帮助用户掌握Visio绘制ER图的基本技能,能够独立完成简单数据库的ER图设计;②利用Visio的反向工程功能快速生成复杂数据库的ER图,提高工作效率;③理解实体、属性、关系三者之间的逻辑关联,为后续数据库设计提供理论依据。 阅读建议:建议读者按照文档中的步骤逐步操作练习,同时结合实际项目需求,灵活运用所学知识,特别是对于反向工程部分,可以尝试不同类型的数据库以加深理解。

    QC方法在提高箭载仪器支架装配合格率上的应用研究.zip

    QC方法在提高箭载仪器支架装配合格率上的应用研究.zip

    自动化测试实战:Vue3+Vitest+TestingLibrary指南.pdf

    文档支持目录章节跳转同时还支持阅读器左侧大纲显示和章节快速定位,文档内容完整、条理清晰。文档内所有文字、图表、函数、目录等元素均显示正常,无任何异常情况,敬请您放心查阅与使用。文档仅供学习参考,请勿用作商业用途。 Vue 3是一款备受瞩目的JavaScript框架,它采用了基于Proxy的响应式系统,显著提升了性能和调试能力。其Composition API带来了更高效的逻辑组织方式,使代码复用变得轻而易举。Tree-shaking支持让打包后的文件体积更小,进一步优化了应用性能。Vue 3还与TypeScript深度集成,提供了更完善的类型推导,让开发过程更加顺畅。无论是构建大型应用还是小型项目,Vue 3都能凭借其出色的性能和灵活的架构,帮助开发者高效完成任务,是现代Web开发的理想选择。

    大屏数据可视化:Vue3+Echarts性能优化方案.pdf

    文档支持目录章节跳转同时还支持阅读器左侧大纲显示和章节快速定位,文档内容完整、条理清晰。文档内所有文字、图表、函数、目录等元素均显示正常,无任何异常情况,敬请您放心查阅与使用。文档仅供学习参考,请勿用作商业用途。 Vue 3是一款备受瞩目的JavaScript框架,它采用了基于Proxy的响应式系统,显著提升了性能和调试能力。其Composition API带来了更高效的逻辑组织方式,使代码复用变得轻而易举。Tree-shaking支持让打包后的文件体积更小,进一步优化了应用性能。Vue 3还与TypeScript深度集成,提供了更完善的类型推导,让开发过程更加顺畅。无论是构建大型应用还是小型项目,Vue 3都能凭借其出色的性能和灵活的架构,帮助开发者高效完成任务,是现代Web开发的理想选择。

    知识问答系统:Vue3+BERT的语义理解实现.pdf

    文档支持目录章节跳转同时还支持阅读器左侧大纲显示和章节快速定位,文档内容完整、条理清晰。文档内所有文字、图表、函数、目录等元素均显示正常,无任何异常情况,敬请您放心查阅与使用。文档仅供学习参考,请勿用作商业用途。 Vue 3是一款备受瞩目的JavaScript框架,它采用了基于Proxy的响应式系统,显著提升了性能和调试能力。其Composition API带来了更高效的逻辑组织方式,使代码复用变得轻而易举。Tree-shaking支持让打包后的文件体积更小,进一步优化了应用性能。Vue 3还与TypeScript深度集成,提供了更完善的类型推导,让开发过程更加顺畅。无论是构建大型应用还是小型项目,Vue 3都能凭借其出色的性能和灵活的架构,帮助开发者高效完成任务,是现代Web开发的理想选择。

Global site tag (gtag.js) - Google Analytics