Dubbo最核心功能——服务暴露的配置、使用及原理

news2024/9/23 13:30:49

系列文章目录

【收藏向】从用法到源码,一篇文章让你精通Dubbo的SPI机制
面试Dubbo ,却问我和Springcloud有什么区别?
超简单,手把手教你搭建Dubbo工程(内附源码)


文章目录

  • 系列文章目录
  • 前言
  • 一、服务暴露的分类
  • 二、本地暴露
    • 1. 配置
    • 2. 实现原理
  • 三、远程暴露
    • 1. 配置
    • 2. 实现原理
  • 四、服务的注解配置
  • 五、服务暴露的触发
    • 1. dubbo 的启动
    • 2. 服务识别——后置处理器
    • 3. 服务暴露的时机
    • 4. 与Spring结合的流程图


前言

今天开始,将正式的进入Dubbo 核心功能的掌握与学习,Dubbo的最核心功能是什么?自然是其RPC功能,而该功能其实分为三个方面:
1.服务暴露
2.服务引用
3.服务调用

三者分别专注于服务提供者、服务消费者、服务调用过程。我们今天就先关注服务暴露,我们至少有这么几个疑问待解决:

  • 服务暴露有几种,分别是怎么实现的
  • 我们如何进行并配置服务暴露
  • Dubbo和Spirng框架是怎么结合的

📕作者简介:战斧,多年开发及管理经验,爱好广泛,致力于创作更多高质量内容
📗本文收录于 Dubbo专栏,有需要者,可直接订阅专栏实时获取更新
📘高质量专栏 RabbitMQ、Spring全家桶 等仍在更新,欢迎指导
📙Zookeeper Redis kafka docker netty等诸多框架,以及架构与分布式专题即将上线,敬请期待


一、服务暴露的分类

看过我们之前的文章 超简单,手把手教你搭建Dubbo工程(内附源码) 的读者,应该有所了解了。即Dubbo 实际上有两种服务暴露的类型,即远程暴露本地暴露

  1. 远程暴露
    远程暴露是指将服务发布到远程注册中心,供消费者调用。Dubbo支持多种远程通信协议,例如dubbo、rmi、hessian、http等。远程暴露的实现原理大致如下:
    服务提供者在启动时,将服务实现类通过协议进行网络传输,发布到注册中心上。
    服务消费者在启动时,从注册中心上获取到服务提供者的地址,然后通过协议进行网络通信,调用服务提供者的方法。
    在这里插入图片描述
  2. 本地暴露
    本地暴露是指将服务发布到本地JVM上,供同一JVM中的其他服务调用。Dubbo的本地通信使用了JVM内部的IPC或者共享内存等方式,从而避免了网络传输的开销。本地暴露的实现原理大致如下:
    服务提供者在启动时,将服务实现类通过代理方式,注入到本地JVM的缓存中。
    服务消费者在启动时,直接从本地JVM缓存中获取到服务提供者的代理对象,然后调用服务提供者的方法。
    在这里插入图片描述

PS:需要注意的是,本地暴露仅限于同一JVM内的服务调用,如果需要跨进程或跨机器进行通信,仍需要使用远程通信方式

二、本地暴露

有的人会问,Dubbo提供本地调用的服务是什么意思?因为大部分人都是把Dubbo当作RPC框架使用的,为什么其还会提供本地暴露这种用法?
应该说:Dubbo的本地暴露实际上是比较少用到的,但对于一些需要高性能、低延迟、本地调用的应用场景,使用本地暴露可以提高服务的性能和可用性。从实际项目上来讲,笔者接触过一些项目,其功能全部实现了对外暴露,也就是说有很多功能,既允许外部远程调用,也能被本地其他方法调用。这个时候,本地的调用如果还走RPC的路子,不仅带来延时,还无谓地浪费了带宽,支持本地调用就很有必要了

1. 配置

在Dubbo 2.2 0版本开始,Dubbo默认在本地以JVM的方式暴露服务,当然,对于老版本而言,还需要进行一些配置。这样的话,在第一步需要在整体配置文件里开启

#开启服务暴露
dubbo.provider.export= true

其次就是在单个接口上,我们需要配置引用的源scope = local

<bean id="demoServiceTarget" class="org.apache.dubbo.samples.local.impl.DemoServiceImpl"/>
<!-- 服务提供者指定scope -->
<dubbo:service interface="org.apache.dubbo.samples.local.api.DemoService" ref="demoServiceTarget" scope="local"/>
<dubbo:reference id="demoService" interface="org.apache.dubbo.samples.local.api.DemoService"/>

2. 实现原理

我们来看进行服务暴露(或者说导出)的基础方法 ServiceConfig 下私有方法 exportUrl

private void exportUrl(URL url, List<URL> registryURLs) {
    String scope = url.getParameter(SCOPE_KEY);
    // 如果配置scope为"none" ,就不进行服务暴露
    if (!SCOPE_NONE.equalsIgnoreCase(scope)) {
        // 除非显式的配置了远程暴露,不然都会进行本地暴露
        if (!SCOPE_REMOTE.equalsIgnoreCase(scope)) {
            exportLocal(url);
        }
        // 除非显式的指定了本地暴露,不然都会进行远程暴露
        if (!SCOPE_LOCAL.equalsIgnoreCase(scope)) {
           // 远程导出省略.....
        }
    }
    // 不难看出,`scope` 字段采取的判断逻辑非常宽容,试想一下,如果填入 scope = "abc" 会发生什么?
    this.urls.add(url);
}

通过上述方法,我们知道了除非指定某接口就是远程暴露,否则都会进行本地暴露。那具体本地暴露是怎么回事呢?我们继续来看

private Protocol protocolSPI = this.getExtensionLoader(Protocol.class).getAdaptiveExtension();
String LOCAL_PROTOCOL = "injvm";
String LOCALHOST_VALUE = "127.0.0.1";

private void exportLocal(URL url) {
    URL local = URLBuilder.from(url)
        .setProtocol(LOCAL_PROTOCOL)
        .setHost(LOCALHOST_VALUE)
        .setPort(0)
        .build();
    local = local.setScopeModel(getScopeModel())
        .setServiceModel(providerModel);
    doExportUrl(local, false);
    logger.info("Export dubbo service " + interfaceClass.getName() + " to local registry url : " + local);
}

private void doExportUrl(URL url, boolean withMetaData) {
    Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);
    if (withMetaData) {
        invoker = new DelegateProviderMetaDataInvoker(invoker, this);
    }
    Exporter<?> exporter = protocolSPI.export(invoker);
    exporters.add(exporter);
}

不难发现,本地暴露并没有采用取巧的方式去做特殊处理,而是老老实实构建了个URL,并且像模像样的进行导出,只是这个URL的设置了协议类型为injvm,主机更是直接127.0.0.1。当然最重要的是 protocolSPI.export(invoker) 这个代码,这里的 protocolSPI 是利用的Dubbo-SPI的自适应机制,所以导致最终执行导出的实现类为 InjvmProtocol (关于Dubbo-SPI的内容可见 一篇文章让你精通Dubbo的SPI机制),我们来看 InjvmProtocolexport 方法

// AbstractProtocol.class
protected final Map<String, Exporter<?>> exporterMap = new ConcurrentHashMap<>();

// InjvmProtocol.class 继承自 AbstractProtocol
@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
    return new InjvmExporter<T>(invoker, invoker.getUrl().getServiceKey(), exporterMap);
}

// InjvmExporter.class
public class InjvmExporter<T> extends AbstractExporter<T> {

    private final String key;

    private final Map<String, Exporter<?>> exporterMap;

    InjvmExporter(Invoker<T> invoker, String key, Map<String, Exporter<?>> exporterMap) {
        super(invoker);
        this.key = key;
        this.exporterMap = exporterMap;
        exporterMap.put(key, this);
    }

    @Override
    public void afterUnExport() {
        exporterMap.remove(key);
    }
}   

可以看到这里其实使用了一个 Map 结构存储了这样的服务暴露的结果,因为这个Map 结构存储在抽象类中,也就是说不管是本地暴露还是哪种RPC协议的暴露,实际都会存储在该位置,并没有什么不同


三、远程暴露

1. 配置

远程暴露的配置其实与本地暴露雷同,只需将 scope 的值赋成 remote 即可,并指定使用的协议

<bean id="demoServiceTarget" class="org.apache.dubbo.samples.local.impl.DemoServiceImpl"/>
<!-- 服务提供者指定scope -->
<dubbo:service interface="org.apache.dubbo.samples.local.api.DemoService" ref="demoServiceTarget" scope="remote"/>
<dubbo:protocol name="dubbo" port="20880" />
<dubbo:reference id="demoService" interface="org.apache.dubbo.samples.local.api.DemoService"/>

2. 实现原理

同本地暴露一样,我们仍然来看基础方法 ServiceConfig 下私有方法 exportUrl

private void exportUrl(URL url, List<URL> registryURLs) {
    String scope = url.getParameter(SCOPE_KEY);
    if (!SCOPE_NONE.equalsIgnoreCase(scope)) {
        if (!SCOPE_REMOTE.equalsIgnoreCase(scope)) {
            // 本地暴露略
        }

        // 远程暴露
        if (!SCOPE_LOCAL.equalsIgnoreCase(scope)) {
            // 远程暴露获取额外设置的协议
            String extProtocol = url.getParameter("ext.protocol", "");
            List<String> protocols = new ArrayList<>();

            if (StringUtils.isNotBlank(extProtocol)) {
                // export original url
                url = URLBuilder.from(url).
                    addParameter(IS_PU_SERVER_KEY, Boolean.TRUE.toString()).
                    removeParameter("ext.protocol").
                    build();
            }
            // 远程暴露
            url = exportRemote(url, registryURLs);
            if (!isGeneric(generic) && !getScopeModel().isInternal()) {
                MetadataUtils.publishServiceDefinition(url, providerModel.getServiceModel(), getApplicationModel());
            }

            if (StringUtils.isNotBlank(extProtocol)) {
                String[] extProtocols = extProtocol.split(",", -1);
                protocols.addAll(Arrays.asList(extProtocols));
            }
            // 对于额外设定的协议,也进行暴露
            for(String protocol : protocols) {
                if(StringUtils.isNotBlank(protocol)){
                    URL localUrl = URLBuilder.from(url).
                        setProtocol(protocol).
                        build();
                    localUrl = exportRemote(localUrl, registryURLs);
                    if (!isGeneric(generic) && !getScopeModel().isInternal()) {
                        MetadataUtils.publishServiceDefinition(localUrl, providerModel.getServiceModel(), getApplicationModel());
                    }
                    this.urls.add(localUrl);
                }
            }
        }
    }
    this.urls.add(url);
}

通过上述方法,我们可以知道,在服务远程暴露的时候,可以设置额外的导出协议,这将导致一个服务将以多个协议的形式进行暴露,但不管是哪种方式,其核心方法都是调用的 exportRemote该方法第一个参数为要暴露的服务的信息,第二个参数为各注册中心的信息,我们来看看该方法。

private URL exportRemote(URL url, List<URL> registryURLs) {
    if (CollectionUtils.isNotEmpty(registryURLs)) {
    	// 遍历各注册中心
        for (URL registryURL : registryURLs) {
        	// 判断是否是service-discovery-registry协议,该协议用于自省架构
            if (SERVICE_REGISTRY_PROTOCOL.equals(registryURL.getProtocol())) {
                url = url.addParameterIfAbsent(SERVICE_NAME_MAPPING_KEY, "true");
            }

            // 本地暴露不予执行
            if (LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
                continue;
            }

            url = url.addParameterIfAbsent(DYNAMIC_KEY, registryURL.getParameter(DYNAMIC_KEY));
            URL monitorUrl = ConfigValidationUtils.loadMonitor(this, registryURL);
            if (monitorUrl != null) {
                url = url.putAttribute(MONITOR_KEY, monitorUrl);
            }

            // For providers, this is used to enable custom proxy to generate invoker
            String proxy = url.getParameter(PROXY_KEY);
            if (StringUtils.isNotEmpty(proxy)) {
                registryURL = registryURL.addParameter(PROXY_KEY, proxy);
            }

            if (logger.isInfoEnabled()) {
                if (url.getParameter(REGISTER_KEY, true)) {
                    logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL.getAddress());
                } else {
                    logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
                }
            }
			// 把服务消息作为一个属性包裹在注册中心url内,并执行导出
            doExportUrl(registryURL.putAttribute(EXPORT_KEY, url), true);
        }

    } else {

        if (logger.isInfoEnabled()) {
            logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
        }

        doExportUrl(url, true);
    }


    return url;
}

可以看到,如果指定了注册中心,会把服务消息包装成在各个注册中心的URL中,我们以ZK为例,当我们向Zookeeper 进行注册时,会获得如下的注册Url
在这里插入图片描述

并进行发布;如果没有,则执行默认的发布,方法都是 doExportUrl,这个我们在本地暴露时已经讲过,会根据协议类型来选择实现类来进行发布,此处显然就是 RegistryProtocol 作为实现类,我们直接找到其注册的位置

// RegistryProtocol
    private void register(Registry registry, URL registeredProviderUrl) {
        registry.register(registeredProviderUrl);
    }

在这里插入图片描述
最后则是以 ZookeeperRegistry 通过ZK客户端向Zookeeper 注册服务信息

// ZookeeperRegistry
public void doRegister(URL url) {
        try {
            checkDestroyed();
            zkClient.create(toUrlPath(url), url.getParameter(DYNAMIC_KEY, true), false);
        } catch (Throwable e) {
            throw new RpcException("Failed to register " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
        }
}

四、服务的注解配置

除了上面通过xml文件进行的配置,Dubbo其实更适合使用注解进行服务的启用,当然由于历代Dubbo的变化,注解也发生了变化

  1. org.apache.dubbo.config.annotation.DubboService (Since:2.7.7)
  2. org.apache.dubbo.config.annotation.Service (Since:2.7.0)
  3. com.alibaba.dubbo.config.annotation.Service (阿里时期)

我们以最新的 @DubboService 来看看,其中很多字段都是与XML的配置完全一样的
在这里插入图片描述
当然,我们上面提到的 scope 也在其中,用法也是完全相同
在这里插入图片描述

五、服务暴露的触发

我们前面说的服务暴露,都是以 ServiceConfig 下私有方法 exportUrl 为起点,但是当应用启动时,这个方法是怎样被触发的呢,我们现在来看这个问题。我们以结合Spring 的项目来进行研究

1. dubbo 的启动

我们在前面进行工程搭建的时候,就提到部署时需要在启动类上写上注解 @EnableDubbo,大部分三方框架都需要以类似的方式执行与Spring工程的融合,我们先来看看加上该注解的原理吧。
在这里插入图片描述
这两个注解顾名思义,一个负责启用默认配置,一个负责加载组件。我们现在看组件部分

在这里插入图片描述
关于注解上再使用 @Import 注解,其实也是诸多框架里经常采用的导入Spring的模式了,该注解能用我们在外层注解上的值进行某些操作,在此处,就是把该目录下的组件扫描进Spring容器中
在这里插入图片描述

2. 服务识别——后置处理器

经过上述的组件扫描,我们要获得什么呢?当然是我们配置的服务或者引用能够正确的装载进Spring容器了

   // DubboComponentScanRegistrar
private void registerServiceAnnotationPostProcessor(Set<String> packagesToScan, BeanDefinitionRegistry registry) {
		// 获取 ServiceAnnotationPostProcessor 的信息,将其的Bean定义注入Spring容器
        BeanDefinitionBuilder builder = rootBeanDefinition(ServiceAnnotationPostProcessor.class);
        builder.addConstructorArgValue(packagesToScan);
        builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
        BeanDefinitionReaderUtils.registerWithGeneratedName(beanDefinition, registry);
}
    
public static String registerWithGeneratedName(AbstractBeanDefinition definition, BeanDefinitionRegistry registry) throws BeanDefinitionStoreException {
        String generatedName = generateBeanName(definition, registry, false);
        // 注入进Spring 容器
        registry.registerBeanDefinition(generatedName, definition);
        return generatedName;
}

这里,我们需要知道,把ServiceAnnotationPostProcessor的Bean定义注入进Spring的原因是什么,因为其是一个后置处理器,它继承了BeanDefinitionRegistryPostProcessor,能负责在Bean工厂创建完成后执行操作,我们在MyBatis+Springboot 启动到SQL执行全流程 和 pringBean生成流程详解 两篇文章中都有解释,此处就不再赘述。来关注下它做了什么。

// 至今为止,Dubbo 经历过几个阶段,使用的服务注解各不相同,此处算做个兼容
private final static List<Class<? extends Annotation>> serviceAnnotationTypes = asList(
            // @since 2.7.7 Add the @DubboService , the issue : https://github.com/apache/dubbo/issues/6007
            DubboService.class,
            // @since 2.7.0 the substitute @com.alibaba.dubbo.config.annotation.Service
            Service.class,
            // @since 2.7.3 Add the compatibility for legacy Dubbo's @Service , the issue : https://github.com/apache/dubbo/issues/4330
            com.alibaba.dubbo.config.annotation.Service.class
    );
    
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    if (this.registry == null) {
        // In spring 3.x, may be not call postProcessBeanDefinitionRegistry()
        this.registry = (BeanDefinitionRegistry) beanFactory;
    }

    // 扫描Bean定义
    String[] beanNames = beanFactory.getBeanDefinitionNames();
    for (String beanName : beanNames) {
        BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
        Map<String, Object> annotationAttributes = getServiceAnnotationAttributes(beanDefinition);
        if (annotationAttributes != null) {
            // process @DubboService at java-config @bean method
            processAnnotatedBeanDefinition(beanName, (AnnotatedBeanDefinition) beanDefinition, annotationAttributes);
        }
    }
    if (!scanned) {
        // 把目录下符合条件的类定义,注册进Spring容器
        scanServiceBeans(resolvedPackagesToScan, registry);
    }
}

3. 服务暴露的时机

首先我们得知道Spring 提供的监听器机制(不了解的可以看这篇:Spring监听器用法与原理详解),知道了在Spring容器刷新完毕后的时候会发送事件 ContextRefreshedEvent

// AbstractApplicationContext.class
    protected void finishRefresh() {
        this.clearResourceCaches();
        this.initLifecycleProcessor();
        this.getLifecycleProcessor().onRefresh();
        this.publishEvent((ApplicationEvent)(new ContextRefreshedEvent(this)));
        if (!NativeDetector.inNativeImage()) {
            LiveBeansView.registerApplicationContext(this);
        }
    }

而Dubbo 正是利用了这个机制来进行启动的,Dubbo内建了一个监听器 DubboDeployApplicationListener 来监听 ContextRefreshedEvent 事件,以此为触发,进行Dubbo模块的启动

 // DubboDeployApplicationListener.class
    private void onContextRefreshedEvent(ContextRefreshedEvent event) {
        ModuleDeployer deployer = moduleModel.getDeployer();
        Assert.notNull(deployer, "Module deployer is null");
        // 启动模块
        Future future = deployer.start();

        // if the module does not start in background, await finish
        if (!deployer.isBackground()) {
            try {
                future.get();
            } catch (InterruptedException e) {
                logger.warn(CONFIG_FAILED_START_MODEL, "", "", "Interrupted while waiting for dubbo module start: " + e.getMessage());
            } catch (Exception e) {
                logger.warn(CONFIG_FAILED_START_MODEL, "", "", "An error occurred while waiting for dubbo module start: " + e.getMessage(), e);
            }
        }
    }

但是,我们还有一个问题,就是我们知道Spring的监听器必须置于Spring容器下,如果是我们自己写代码,类上加个注解@Component就可以解决,但眼下情形显然不同,==这个监听器是如何融入Spring体系并将自身加入进Spring容器的呢?==其实上面就已经提到了
在这里插入图片描述
这里的 DubboSpringInitializer.initialize(registry) 就是把Dubbo 自身的很多内容注册进Spring 容器的,其调用链路为
initialize -> initContext -> DubboBeanUtils.registerCommonBeans(registry)

static void registerCommonBeans(BeanDefinitionRegistry registry) {

    registerInfrastructureBean(registry, ServicePackagesHolder.BEAN_NAME, ServicePackagesHolder.class);

    registerInfrastructureBean(registry, ReferenceBeanManager.BEAN_NAME, ReferenceBeanManager.class);

    // Since 2.5.7 Register @Reference Annotation Bean Processor as an infrastructure Bean
    registerInfrastructureBean(registry, ReferenceAnnotationBeanPostProcessor.BEAN_NAME,
        ReferenceAnnotationBeanPostProcessor.class);

    // TODO Whether DubboConfigAliasPostProcessor can be removed ?
    // Since 2.7.4 [Feature] https://github.com/apache/dubbo/issues/5093
    registerInfrastructureBean(registry, DubboConfigAliasPostProcessor.BEAN_NAME,
        DubboConfigAliasPostProcessor.class);

    // 注册监听器,其中就包含了我们这次提到的 DubboDeployApplicationListener
    registerInfrastructureBean(registry, DubboDeployApplicationListener.class.getName(), DubboDeployApplicationListener.class);
    registerInfrastructureBean(registry, DubboConfigApplicationListener.class.getName(), DubboConfigApplicationListener.class);

    // Since 2.7.6 Register DubboConfigDefaultPropertyValueBeanPostProcessor as an infrastructure Bean
    registerInfrastructureBean(registry, DubboConfigDefaultPropertyValueBeanPostProcessor.BEAN_NAME,
        DubboConfigDefaultPropertyValueBeanPostProcessor.class);

    // Dubbo config initializer
    registerInfrastructureBean(registry, DubboConfigBeanInitializer.BEAN_NAME, DubboConfigBeanInitializer.class);

    // register infra bean if not exists later
    registerInfrastructureBean(registry, DubboInfraBeanRegisterPostProcessor.BEAN_NAME, DubboInfraBeanRegisterPostProcessor.class);
}

4. 与Spring结合的流程图

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/749229.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

idea乱码解决方式大汇总

目录 idea版本&#xff1a; 解决方法&#xff1a; 一、基本方法 1. File -> Settings -> Editor 2. 二、Maven乱码解决方法 三、运行时乱码解决方法 四、因为以前乱设置导致的乱码 idea版本&#xff1a; 解决方法&#xff1a; 一、基本方法 1. File -> Setti…

ETHERCAT主站网关转DEVICENET连接支持ethercat总线的PLC

大家好&#xff0c;今天要和大家分享一款自主研发的通讯网关——远创智控YC-ECTM-DNT。这款产品可是解决了不同协议设备数据交换的麻烦问题&#xff0c;让我们一起来看看它的神奇之处吧&#xff01; 这款通讯网关有什么特别的呢&#xff1f;首先&#xff0c;它可以连接DEVICENE…

vue2 element-ui el-cascader地址省市区分开单独写

使用 npm 或 yarn 安装 element-china-area-data 包&#xff1a; npm install element-china-area-data 在你的代码中导入 element-china-area-data import { regionData } from element-china-area-data let that; 完整代码 <template><div><el-form ref&quo…

Zookeeper概述及部署

Zookeeper概述及部署 一、Zookeeper 定义二、Zookeeper 特点三、Zookeeper 数据结构四、Zookeeper 应用场景五、Zookeeper选举机制● 第一次启动选举机制● 非第一次启动选举机制 六、部署 Zookeeper 集群1.安装前准备2.安装 Zookeeper 一、Zookeeper 定义 Zookeeper是一个开源…

leetcode:LCP 06. 拿硬币(python3解法)

难度&#xff1a;简单 桌上有 n 堆力扣币&#xff0c;每堆的数量保存在数组 coins 中。我们每次可以选择任意一堆&#xff0c;拿走其中的一枚或者两枚&#xff0c;求拿完所有力扣币的最少次数。 示例 1&#xff1a; 输入&#xff1a;[4,2,1] 输出&#xff1a;4 解释&#xff1a…

mysql-分页数据重复

背景说明 分页查询不同页出现重复数据&#xff0c;底层实现都是使用limit select * from tt1 limit 100,50 第101条开始的50条数据 select * from tt1 limit 100 前100条记录 select * from tt1 limit 100 offset 50 从51条开始&#xff0c;显示后面的100条 原因分析 以前碰…

力扣 53. 最大子数组和

题目来源&#xff1a;https://leetcode.cn/problems/maximum-subarray/description/ C题解&#xff08;来源代码随想录&#xff09;&#xff1a;难点在于判断什么时候更新count。 是当nums[i]为正数吗&#xff1f;那要nums没有正数怎么办&#xff1b; 是当nums[i]比之前子数组…

网络监控的关键指标

网络监控是 IT 的支柱&#xff0c;对于防止可能困扰您的业务的计划外中断至关重要。这就是为什么投资网络监控解决方案是一个明智而安全的举动。特别是随着远程工作成为常态&#xff0c;监控工具使公司能够从世界任何地方监控网络&#xff0c;并有助于在整个过程中保持最佳性能…

小程序:页面跳转闪屏

自己的笔记&#xff0c;随手记录。扛精走开。 1、问题描述 进入页面&#xff0c;是一个组件&#xff0c;通过路由传参判断是由哪个页面进入&#xff0c;不同的页面拿的已选值不一样&#xff0c;需要回显值&#xff0c;在编辑数据。此时会出现一个问题&#xff0c;A页面中进来…

运输问题案例

案例1 运输问题 某部门有3个生产同类型产品的产地&#xff0c;生产的产品由4个销售点出售&#xff0c;各工厂的生产量、各销售点的销售量以及各工厂到各销售点的单位运价&#xff08;元/吨&#xff09;如表1所示&#xff0c;求最佳调运方案&#xff1f; 表1 运输信息 产地\销地…

general 未设置cookie的Secure标志位

解决方案: 在配置文件中增加相应配置即可完成配置&#xff1a; <system.web><httpCookies httponlyCookies"true" requireSSl"true"/></system.web>添加完成后如下所示:

ens33没有inet地址

1&#xff09;切换到根用户 su - root 按提示输入密码&#xff08;不切换到根用户没有权限修改文件&#xff09; &#xff08;2&#xff09;输入cd /etc/sysconfig/network-scripts/ &#xff08;3&#xff09;输入vi ifcfg-ens33 ifcfg-ens33 &#xff08;4&#xff09;光标移…

深/浅拷贝

现在有一个我们自定义的Person对象,如何去克隆这个对象? class Person{public int id;Overridepublic String toString() {return "Person{" "id" id };} } public class Test2 {public static void main(String[] args) {Person person1 new Person()…

ATFX国际:美国CPI骤降至3%,高通胀问题或不复存在

ATFX国际&#xff1a;美国通胀率数据搅动国际金融市场。6月未季调CPI年率&#xff0c;最新值3%&#xff0c;低于前值4%和预期值3.1%&#xff1b;6月未季调核心CPI年率&#xff0c;最新值4.8%&#xff0c;低于前值5.3%&#xff0c;低于预期值5%。名义CPI和核心CPI数据双双大降&a…

c++从源文件到可执行文件经历了什么

1.c从源文件到可执行文件经历了什么 在linux平台上 main.c —> a.out 在windows平台上 main.c --> xx.exe gcc -E main.c -o main.i // 预处理 gcc -S main.i -o main.s // 编译 gcc -c main.s -o main.o // 汇编 gcc main.o -o hello //链接

软件测试面试题 —— 整理与解析(3)

&#x1f60f;作者简介&#xff1a;博主是一位测试管理者&#xff0c;同时也是一名对外企业兼职讲师。 &#x1f4e1;主页地址&#xff1a;&#x1f30e;【Austin_zhai】&#x1f30f; &#x1f646;目的与景愿&#xff1a;旨在于能帮助更多的测试行业人员提升软硬技能&#xf…

Python的多线程编程-同步机制Lock实现

在Python的多线程编程中&#xff0c;为避免多个线程同时访问同一个共享资源而发生冲突&#xff0c;需要使用同步机制来确保线程安全&#xff0c;其中Lock就是一个同步机制。 Lock是一个互斥锁&#xff0c;当线程获取了锁&#xff0c;其他线程就不能再获取该锁&#xff0c;直到…

Redis_客户端命令和数据操作(3)

目录 切换数据库 键命令 数据结构 string类型 hash类型 list类型 set类型 zset类型 查看中文value 源码等资料获取方法 切换数据库 redis数据库没有名称&#xff0c;默认有16个&#xff0c;通过0-15来标识&#xff0c;连接redis默认选择第一个数据库&#xff0c;可以…

pytorch 中的执行模式

两种模式&#xff1a; PyTorch 支持两种执行模式&#xff1a;eager mode 和 graph mode。 E 模式&#xff0c;侧重于易用性与灵活性&#xff0c;适合科研人员&#xff0c;用于验证想法&#xff0c;魔改模型&#xff1b; G模式: 侧重于性能方面&#xff0c;适合生产&#xff0c;…

tomcat学习随笔

Tomcat结构与原理 一、组成ServerServiceConnectorProtocolHandlerEndpointProcessor Adaptor ContainerEngineHostContextWrapper 运行热部署jsp类war tomcat根路径目录结构示意图 一、组成 tomcat结构debug示意图 Server tomcat的实例&#xff0c;支持多个Service Service …