Dubbo 3.x源码(16)—Dubbo服务发布导出源码(5)

基于Dubbo 3.1,详细介绍了Dubbo服务的发布与引用的源码。

此前我们学习了Dubbo 3.x源码(15)—Dubbo服务发布导出源码(4),也就是Dubbo远程服务导出export方法的上半部分,也就是doLocalExport源码,将会得到一个Exporter。

现在我们继续学习,在导出远程服务得到Exporter之后,继续通过Registry将其注册到远程注册中心的源码。

  1. Dubbo 3.x源码(11)—Dubbo服务的发布与引用的入口
  2. Dubbo 3.x源码(12)—Dubbo服务发布导出源码(1)
  3. Dubbo 3.x源码(13)—Dubbo服务发布导出源码(2)
  4. Dubbo 3.x源码(14)—Dubbo服务发布导出源码(3)
  5. Dubbo 3.x源码(15)—Dubbo服务发布导出源码(4)
  6. Dubbo 3.x源码(16)—Dubbo服务发布导出源码(5)

文章目录

  • 1 register注册服务到注册中心
  • 2 getRegistry获取注册表
    • 2.1 AbstractRegistryFactory#getRegistry获取注册表
      • 2.1.1 createRegistry创建注册表
    • 2.2 ServiceDiscoveryRegistry的构建
  • 3 register注册服务
    • 3.1 ServiceDiscoveryRegistry应用级服务注册
    • 3.2 ZookeeperRegistry接口级服务注册
  • 4 总结

1 register注册服务到注册中心

此前我们学习过,在远程服务导出协议(RegistryProtocol、InterfaceCompatibleRegistryProtocol)的export方法中,在本地导出服务得到Exporter之后,还需要将其注册到远程注册中心,这样consumer端才能从注册中心获取到服务的相关信息。

// url to registry
/*
 * 基于Dubbo SPI机制根据注册中心url加载具体的注册中心操作类,service-discovery-registry对应着ServiceDiscoveryRegistry
 */
final Registry registry = getRegistry(registryUrl);
//获取需要注册的服务url 内部包含服务的ip、port、pid、服务接口、版本、分组、接口内部的方法名等信息
//dubbo://192.168.31.84:20880/org.apache.dubbo.demo.GreetingService?anyhost=true&application=demo-provider&application.version=1&background=false&delay=5000&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&group=greeting&interface=org.apache.dubbo.demo.GreetingService&methods=hello&pid=10920&revision=1.0.0&service-name-mapping=true&side=provider&timeout=5000&timestamp=1666621305141&version=1.0.0
final URL registeredProviderUrl = getUrlToRegistry(providerUrl, registryUrl);

// decide if we need to delay publish (provider itself and registry should both need to register)
//决定我们是否需要延迟发布(提供者本身和注册中心都需要注册)
boolean register = providerUrl.getParameter(REGISTER_KEY, true) && registryUrl.getParameter(REGISTER_KEY, true);
/*
 * 2 向注册中心注册服务
 */
if (register) {
    register(registry, registeredProviderUrl);
}

2 getRegistry获取注册表

Registry是服务注册中心操作类,Registry同样是基于Dubbo SPI机制根据协议查找的,例如service-discovery-registry服务发现协议对应着ServiceDiscoveryRegistry,而registry服务发现协议由于getRegistryUrl方法中就被替换为了真实的协议地址,因此registryUrl可能对应着不同协议,因此Registry的返回也各不相同,例如nacos协议对应着NacosRegistry,zookeeper协议对应着ZookeeperRegistry。

/**
 * 根据调用者的地址获取注册表的实例
 *
 * @param registryUrl 注册中心url
 * @return 注册表
 */
protected Registry getRegistry(final URL registryUrl) {
    //获取RegistryFactory的自适应实现 RegistryFactory$Adaptive
    RegistryFactory registryFactory = ScopeModelUtil.getExtensionLoader(RegistryFactory.class, registryUrl.getScopeModel()).getAdaptiveExtension();
    //根据url的协议获取对应的RegistryFactory实现,然后调用RegistryFactory实现的getRegistry方法获取注册表
    return registryFactory.getRegistry(registryUrl);
}

2.1 AbstractRegistryFactory#getRegistry获取注册表

该方法是获取注册表的模版方法,属于AbstractRegistryFactory,该方法中,将会根据url获取注册表缓存key,例如service-discovery-registry://47.94.229.245:2181/org.apache.dubbo.registry.RegistryService,或者zookeeper://47.94.229.245:2181/org.apache.dubbo.registry.RegistryService,然后尝试从registries缓存中根据key获取注册表实例,如果找打就直接返回,否则调用createRegistry方法根据url创建一个注册中心,最后将创建的注册中心存入缓存并返回。

/**
 * AbstractRegistryFactory的方法
 * <p>
 * 获取注册表的模版方法
 *
 * @param url 注册中心url
 */
@Override
public Registry getRegistry(URL url) {
    if (registryManager == null) {
        throw new IllegalStateException("Unable to fetch RegistryManager from ApplicationModel BeanFactory. " +
            "Please check if `setApplicationModel` has been override.");
    }
    //应用销毁状态的判断
    Registry defaultNopRegistry = registryManager.getDefaultNopRegistryIfDestroyed();
    if (null != defaultNopRegistry) {
        return defaultNopRegistry;
    }
    //添加interface参数值为RegistryService全路径名、移除timestamp参数、移除export、refer属性
    url = URLBuilder.from(url)
        .setPath(RegistryService.class.getName())
        .addParameter(INTERFACE_KEY, RegistryService.class.getName())
        .removeParameter(TIMESTAMP_KEY)
        .removeAttribute(EXPORT_KEY)
        .removeAttribute(REFER_KEY)
        .build();
    //注册表缓存key,例如service-discovery-registry://47.94.229.245:2181/org.apache.dubbo.registry.RegistryService
    String key = createRegistryCacheKey(url);
    Registry registry = null;
    boolean check = url.getParameter(CHECK_KEY, true) && url.getPort() != 0;

    // Lock the registry access process to ensure a single instance of the registry  锁定注册表访问过程,以确保注册表的单个实例
    registryManager.getRegistryLock().lock();
    try {
        // double check
        // fix https://github.com/apache/dubbo/issues/7265.
        defaultNopRegistry = registryManager.getDefaultNopRegistryIfDestroyed();
        if (null != defaultNopRegistry) {
            return defaultNopRegistry;
        }
        //从registries缓存中根据key获取注册表实例
        registry = registryManager.getRegistry(key);
        //如果有就直接返回
        if (registry != null) {
            return registry;
        }

        // create registry by spi/ioc
        //基于url创建注册中心
        registry = createRegistry(url);
        if (check && registry == null) {
            throw new IllegalStateException("Can not create registry " + url);
        }
        //存入缓存
        if (registry != null) {
            registryManager.putRegistry(key, registry);
        }
    } catch (Exception e) {
        if (check) {
            throw new RuntimeException("Can not create registry " + url, e);
        } else {
            // 1-11 Failed to obtain or create registry (service) object.
            LOGGER.warn("1-11", "", "",
                "Failed to obtain or create registry ", e);
        }
    } finally {
        // Release the lock 解锁
        registryManager.getRegistryLock().unlock();
    }
    //返回注册表
    return registry;
}

2.1.1 createRegistry创建注册表

在应用级服务注册表ServiceDiscoveryRegistry的创建过程中,此时才会将service-discovery-registry服务发现协议替换为真实的注册中心协议,然后创建一个ServiceDiscoveryRegistry返回。

public class ServiceDiscoveryRegistryFactory extends AbstractRegistryFactory {

    @Override
    protected Registry createRegistry(URL url) {
        //初始的url:service-discovery-registry://xxx.xxx.xxx.xxx:2181/org.apache.dubbo.registry.RegistryService?REGISTRY_CLUSTER=registry1&application=demo-provider&application.version=1&dubbo=2.0.2&interface=org.apache.dubbo.registry.RegistryService&pid=42244&registry=zookeeper&timeout=20001
        if (UrlUtils.hasServiceDiscoveryRegistryProtocol(url)) {
            //获取url的registry参数值,默认dubbo
            String protocol = url.getParameter(REGISTRY_KEY, DEFAULT_REGISTRY);
            //将服务发现协议替换为真实的注册中心协议,并且删除url的registry参数
            url = url.setProtocol(protocol).removeParameter(REGISTRY_KEY);
        }
        //替换后的url zookeeper://xxx.xxx.xxx.xxx:2181/org.apache.dubbo.registry.RegistryService?REGISTRY_CLUSTER=registry1&application=demo-provider&application.version=1&dubbo=2.0.2&interface=org.apache.dubbo.registry.RegistryService&pid=41479&timeout=20001
        return new ServiceDiscoveryRegistry(url, applicationModel);
    }

}

对于zookeeper注册中心来说,则是直接创建一个ZookeeperRegistry返回,内部包含了一个zkClient客户端。

@Override
public Registry createRegistry(URL url) {
    return new ZookeeperRegistry(url, zookeeperTransporter);
}

常见注册表的关系如下:
image.png

2.2 ServiceDiscoveryRegistry的构建

ServiceDiscoveryRegistry的构造器中:

  1. 首先会调用父类FailbackRegistry的构造器。
  2. 然后根据url中的注册中心协议构建一个ServiceDiscovery,例如ZookeeperServiceDiscovery,其包含服务发现的通用操作。
  3. 最后获取服务名映射 MetadataServiceNameMapping,可通过服务接口名查找到对应的服务应用的名字。
public ServiceDiscoveryRegistry(URL registryURL, ApplicationModel applicationModel) {
    //父类构造器
    super(registryURL);
    //根据url中的注册中心协议构建一个ServiceDiscovery,例如ZookeeperServiceDiscovery,其包含服务发现的通用操作
    this.serviceDiscovery = createServiceDiscovery(registryURL);
    //获取服务名映射 MetadataServiceNameMapping,可通过服务接口名查找到对应的服务应用的名字
    this.serviceNameMapping = (AbstractServiceNameMapping) ServiceNameMapping.getDefaultExtension(registryURL.getScopeModel());
    super.applicationModel = applicationModel;
}

FailbackRegistry的构造器如下:

  1. 首先会调用父类AbstractRegistry的构造器。
  2. 然后获取重试时间间隔,取url参数retry.period,默认5000ms。
  3. 最后创建一个用于失败重试的时间轮。因为重试任务不会非常多,一圈128个刻度就足够了。
public FailbackRegistry(URL url) {
    //调用父类AbstractRegistry构造器
    super(url);
    //重试时间间隔,取url参数retry.period,默认5000ms
    this.retryPeriod = url.getParameter(REGISTRY_RETRY_PERIOD_KEY, DEFAULT_REGISTRY_RETRY_PERIOD);

    // since the retry task will not be very much. 128 ticks is enough.
    //用于失败重试的时间轮。因为重试任务不会非常多,一圈128个刻度就足够了。
    retryTimer = new HashedWheelTimer(new NamedThreadFactory("DubboRegistryRetryTimer", true), retryPeriod, TimeUnit.MILLISECONDS, 128);
}

AbstractRegistry的构造器如下,主要是本地缓存文件的支持以及加载本地缓存。

protected AbstractRegistry(URL url) {
    //设置registryUrl属性
    setUrl(url);
    //获取注册表管理器
    registryManager = url.getOrDefaultApplicationModel().getBeanFactory().getBean(RegistryManager.class);
    //获取url参数file.cache,是否支持本地缓存,默认true
    localCacheEnabled = url.getParameter(REGISTRY_LOCAL_FILE_CACHE_ENABLED, true);
    //获取注册表缓存执行器sharedScheduledExecutor
    registryCacheExecutor = url.getOrDefaultFrameworkModel().getBeanFactory()
        .getBean(FrameworkExecutorRepository.class).getSharedScheduledExecutor();
    //如果支持本地缓存
    if (localCacheEnabled) {
        // Start file save timer
        //获取url参数save.file,是否同步存储,默认false
        syncSaveFile = url.getParameter(REGISTRY_FILESAVE_SYNC_KEY, false);
        //注册中心缓存文件名 /{user.home}/.dubbo/dubbo-registry-{dubbo.application.name}-{ip}-{post}.cache
        String defaultFilename = System.getProperty(USER_HOME) + DUBBO_REGISTRY + url.getApplication() +
            "-" + url.getAddress().replaceAll(":", "-") + CACHE;
        //获取url参数file,指定的文件名,默认defaultFilename
        String filename = url.getParameter(FILE_KEY, defaultFilename);
        File file = null;

        if (ConfigUtils.isNotEmpty(filename)) {
            //创建文件对象
            file = new File(filename);
            //不存在则创建
            if (!file.exists() && file.getParentFile() != null && !file.getParentFile().exists()) {
                if (!file.getParentFile().mkdirs()) {

                    IllegalArgumentException illegalArgumentException = new IllegalArgumentException(
                        "Invalid registry cache file " + file + ", cause: Failed to create directory " + file.getParentFile() + "!");

                    if (logger != null) {
                        // 1-9 failed to read / save registry cache file.

                        logger.error("1-9", "cache directory inaccessible",
                            "Try adjusting permission of the directory.",
                            "failed to create directory", illegalArgumentException);
                    }

                    throw illegalArgumentException;
                }
            }
        }

        this.file = file;

        // When starting the subscription center,
        // we need to read the local cache file for future Registry fault tolerance processing.
        //加载缓存文件内容到properties集合中,方便注册表容错管理
        loadProperties();
        //推送变更
        notify(url.getBackupUrls());
    }
}

3 register注册服务

该方法内部实际上是调用registry的register方法完成注册。参数url是注册的服务提供者url,内部包含服务提供者的协议、ip、port、pid、服务接口、版本、分组、接口内部的方法名等信息。

/**
 * RegistryProtocol的方法
 * <p>
 * 向注册中心注册服务
 *
 * @param registry              服务注册中心操作类,例如service-discovery-registry对应着ServiceDiscoveryRegistry
 * @param registeredProviderUrl 注册的服务提供者url, 内部包含服务的ip、port、pid、服务接口、版本、分组、接口内部的方法名等信息
 */
private void register(Registry registry, URL registeredProviderUrl) {
    //调用registry的register方法完成注册
    registry.register(registeredProviderUrl);
}

我们下面来看看接口级和应用级别的服务注册的源码,假设真实注册中心都是zookeeper,此时接口级服务注册使用的真实注册表ZookeeperRegistry,而应用级服务注册使用的是ServiceDiscoveryRegistry,但是url已经变成了真实zookeeper协议的url。

3.1 ServiceDiscoveryRegistry应用级服务注册

ServiceDiscoveryRegistry内部最终是调用ServiceDiscovery的register方法完成注册,我们前面就说过,serviceDiscovery中包含服务发现的通用操作。假设注册中心协议为zookeeper,那么serviceDiscovery就是ZookeeperServiceDiscovery。

@Override
public final void register(URL url) {
    //只注册提供者
    if (!shouldRegister(url)) { // Should Not Register
        return;
    }
    //执行注册
    doRegister(url);
}

@Override
public void doRegister(URL url) {
    // fixme, add registry-cluster is not necessary anymore
    url = addRegistryClusterKey(url);
    //调用serviceDiscovery的register方法完成注册
    serviceDiscovery.register(url);
}

ZookeeperServiceDiscovery的register方法实际上是AbstractServiceDiscovery的register方法实现,内部调用metadataInfo的addService方法。

addService方法会根据服务提供者url创建ServiceInfo并添加到services集合中,随后将url加入到exportedServiceURLs缓存中,最后将更新标识位updated改为true。

/**
 * AbstractServiceDiscovery的方法
 *
 * @param url  服务提供者url
 */
@Override
public void register(URL url) {
    //根据url添加MetadataInfo
    metadataInfo.addService(url);
}

/**
 * MetadataInfo的方法
 * @param url 服务提供者url
 */
public synchronized void addService(URL url) {
    // fixme, pass in application mode context during initialization of MetadataInfo.
    if (this.loader == null) {
        this.loader = url.getOrDefaultApplicationModel().getExtensionLoader(MetadataParamsFilter.class);
    }
    //元数据参数过滤器
    List<MetadataParamsFilter> filters = loader.getActivateExtension(url, "params-filter");
    // generate service level metadata
    //生成服务级别元数据
    ServiceInfo serviceInfo = new ServiceInfo(url, filters);
    //存入services缓存,mathKey例如:greeting/org.apache.dubbo.demo.GreetingService:1.0.0:dubbo  value是serviceInfo
    this.services.put(serviceInfo.getMatchKey(), serviceInfo);
    // extract common instance level params
    extractInstanceParams(url, filters);
    //初始化exportedServiceURLs
    if (exportedServiceURLs == null) {
        exportedServiceURLs = new ConcurrentSkipListMap<>();
    }
    //加入到exportedServiceURLs缓存,  key例如:greeting/org.apache.dubbo.demo.GreetingService:1.0.0  value是url的SortedSet<URL>集合
    addURL(exportedServiceURLs, url);
    //设置更新标志为true
    updated = true;
}

注意,上面的方法仅仅是将数据发布到内存中,还没有真正的注册到注册中心。那么,应用级服务注册信息什么时候真正的同步到注册中心的呢?

实际上在DefaultModuleDeployer#startSync方法中,在经过了exportServices服务导出和referServices服务引用之后的onModuleStarted方法中,此时才会进行应用级服务数据的真正注册。

我们这里直接来看看注册后在zookeeper上的数据,可以看到节点路径是应用级数据services/{applicationName}/{ip:port},每一个应用实例才会注册一个节点,节点类型是临时节点,节点内包括服务名称、id、地址、端口、端口、有效载荷(包含存储元数据)等信息。

image.png

3.2 ZookeeperRegistry接口级服务注册

传统的接口级别服务注册使用注册中心协议本身对应的注册表来实现,例如zookeeper协议将会使用ZookeeperRegistry,而他的register方法实际上调用的父类FailbackRegistry实现的register方法。

FailbackRegistry#register方法首先将url加入到registered缓存中,然后从注册失败的缓存failedRegistered中和移除注册失败的缓存failedUnregistered中移除该url。核心方法是调用doRegister方法,向服务器端发送注册请求,该方法由具体的子类实现。

/**
 * FailbackRegistry的方法
 * 
 * 注册服务提供者url
 */
@Override
public void register(URL url) {
    if (!acceptable(url)) {
        logger.info("URL " + url + " will not be registered to Registry. Registry " + this.getUrl() + " does not accept service of this protocol type.");
        return;
    }
    //调用父类AbstractRegistry的register方法,将url加入到registered缓存中
    super.register(url);
    //从注册失败的缓存failedRegistered中移除该url
    removeFailedRegistered(url);
    //从移除注册失败的缓存failedUnregistered中移除该url
    removeFailedUnregistered(url);
    try {
        // Sending a registration request to the server side
        /*
         * 向服务器端发送注册请求,该方法由具体的子类实现
         */
        doRegister(url);
    } catch (Exception e) {
        Throwable t = e;

        // If the startup detection is opened, the Exception is thrown directly.
        boolean check = getUrl().getParameter(Constants.CHECK_KEY, true)
            && url.getParameter(Constants.CHECK_KEY, true)
            && (url.getPort() != 0);
        boolean skipFailback = t instanceof SkipFailbackWrapperException;
        if (check || skipFailback) {
            if (skipFailback) {
                t = t.getCause();
            }
            throw new IllegalStateException("Failed to register " + url + " to registry " + getUrl().getAddress() + ", cause: " + t.getMessage(), t);
        } else {
            logger.error("Failed to register " + url + ", waiting for retry, cause: " + t.getMessage(), t);
        }

        // Record a failed registration request to a failed list, retry regularly
        addFailedRegistered(url);
    }
}

我们来看看ZookeeperRegistry的doRegister方法实现,根据url构建节点路径,/dubbo/{servicePath}/providers/{urlString},例如: /dubbo/org.apache.dubbo.demo.TripleService/providers/tri%3A%2F%2F192.168.1.7%3A50051%2Forg.apache.dubbo.demo.TripleService%3Fanyhost%3Dtrue%26application%3Ddemo-provider%26application.version%3D1%26background%3Dfalse%26delay%3D5000%26deprecated%3Dfalse%26dubbo%3D2.0.2%26dynamic%3Dtrue%26generic%3Dfalse%26interface%3Dorg.apache.dubbo.demo.TripleService%26methods%3Dhello%26pid%3D2624%26revision%3D1.0.0%26service-name-mapping%3Dtrue%26side%3Dprovider%26timeout%3D5000%26timestamp%3D1666705770994%26version%3D1.0.0,节点的值是服务提供者的节点ip。

注意此节点是一个临时节点,当服务关闭时节点删除,值是服务提供者的节点ip。

/**
 * ZookeeperRegistry的方法
 *
 * 注册接口级别的服务提供者url到zookeeper
 * @param url 服务提供者url
 */
@Override
public void doRegister(URL url) {
    try {
        checkDestroyed();
        //根据url构建节点路径,/dubbo/{servicePath}/providers/{url}
        //例如: /dubbo/org.apache.dubbo.demo.TripleService/providers/tri%3A%2F%2F192.168.1.7%3A50051%2Forg.apache.dubbo.demo.TripleService%3Fanyhost%3Dtrue%26application%3Ddemo-provider%26application.version%3D1%26background%3Dfalse%26delay%3D5000%26deprecated%3Dfalse%26dubbo%3D2.0.2%26dynamic%3Dtrue%26generic%3Dfalse%26interface%3Dorg.apache.dubbo.demo.TripleService%26methods%3Dhello%26pid%3D2624%26revision%3D1.0.0%26service-name-mapping%3Dtrue%26side%3Dprovider%26timeout%3D5000%26timestamp%3D1666705770994%26version%3D1.0.0
        //注意此节点是一个临时节点
        zkClient.create(toUrlPath(url), url.getParameter(DYNAMIC_KEY, true));
    } catch (Throwable e) {
        throw new RpcException("Failed to register " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
    }
}

对于接口级的服务注册,在zookeeper中的节点样式如下。

image.png

每个服务的每个接口都有一个单独的url节点,这些url一般来说除了ip和端口不同,其他配置都是相同的,因此存在大量的数据冗余,增加了注册中心的压力,而dubbo3引入的应用级服务注册发现,其目的之一就是解决接口级的服务注册发现的数据冗余问题,后面会讲到。

4 总结

本次我们学习了在导出远程服务得到Exporter之后,继续通过Registry将其注册到远程注册中心的源码,大概逻辑:

  1. 基于Dubbo SPI机制根据注册中心url加载具体的注册中心操作类Registry,应用级服务导出协议service-discovery-registry对应着ServiceDiscoveryRegistry,接口级服务导出协议则会获取真实注册中心协议对应的Registry。
  2. 通过调用Registry#register方法向远程注册中心注册服务提供者url。对于接口级服务导出协议会直接注册到注册中心,而对于应用级服务导出协议则仅仅是存入到本地内存中,在后面才会将服务信息真正的注册。

到这里,我们的Dubbo服务导出过程中的Protocol#export方法的源码就基本学习完毕了(还差一个 exported方法),还记得之前学的内容吗?这个方法实际上是在ServiceConfig#doExportUrl方法中调用的哦!

之前的内容忘了也没关系,下一章我们将会学习最后的exported()方法,并且会对Dubbo服务导出的整体源码流程进行一个比较全面的总结。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C++类和对象引入以及类的介绍使用

文章目录 一、面向过程和面向对象的初步认识二、类的引入2.2 类的引入 三、类的访问限定符及封装3.3 访问限定符3.4 【面试题】C中struct和class的区别3.5 类的两种定义方式 四、封装【面试题】面向对象的三大特性 五、类的作用域六、类的实例化七、类对象模型7.1 类对象的存储…

(Sping Xml方式整合第三方框架)学习Spring的第十天

Spring整合mybatis 1 . 导入Mybatis整合Spring的相关坐标 <dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>5.2.13.RELEASE</version></dependency><dependency><…

基于springboot网上图书商城源码和论文

在Internet高速发展的今天&#xff0c;我们生活的各个领域都涉及到计算机的应用&#xff0c;其中包括网上图书商城的网络应用&#xff0c;在外国网上图书商城已经是很普遍的方式&#xff0c;不过国内的管理网站可能还处于起步阶段。网上图书商城具有网上图书信息管理功能的选择…

新建VM虚拟机-安装centOS7-连接finalshell调试

原文 这里有问题 首先进入/etc/sysconfig/network-scripts/目录 cd /etc/sysconfig/network-scripts/ 然后编辑文件 ifcfg-ens33 vi ifcfg-ens33

探索数字经济:从基础到前沿的奇妙旅程

新一轮技术革命方兴未艾&#xff0c;特别是以人工智能、大数据、物联网等为代表的数字技术革命&#xff0c;催生了一系列新技术、新产业、新模式&#xff0c;深刻改变着世界经济面貌。数字经济已成为重组全球要素资源、重塑全球经济结构、改变全球竞争格局的关键力量。预估到20…

OpenCV 5 - 图像混合处理addWeighted()

图像混合 1 理论-线性混合操作 其中α的取值范围为0~1之间,表示图像的所占的权重 2 混合处理函数addWeighted() 3 代码示例 Mat src1, src2, dst;src1 = imread("./1.png");src2 = imread("./2.png");if (!src1.data && src2.empty()) //判断图片…

word文档怎么做成翻页电子书

随着科技的进步&#xff0c;电子书已成为越来越多人阅读的首选。翻页电子书以其独特的翻页效果和丰富的互动功能&#xff0c;更是受到了广大读者的喜爱。那么&#xff0c;如何将传统的Word文档制作成翻页电子书呢&#xff1f; 一、了解翻页电子书的特点 翻页电子书&#xff0c…

鸿蒙开发【分布式任务调度】解析

鸿蒙OS 分布式任务调度概述 在 HarmonyO S中&#xff0c;分布式任务调度平台对搭载 HarmonyOS 的多设备构筑的“超级虚拟终端”提供统一的组件管理能力&#xff0c;为应用定义统一的能力基线、接口形式、数据结构、服务描述语言&#xff0c;屏蔽硬件差异&#xff1b;支持远程启…

成功解决IndexError: index 0 is out of bounds for axis 1 with size 0.

成功解决IndexError: index 0 is out of bounds for axis 1 with size 0. &#x1f335;文章目录&#x1f335; &#x1f333;引言&#x1f333;&#x1f333;报错分析及解决方案&#x1f333;&#x1f333;参考文章&#x1f333;&#x1f333;结尾&#x1f333; &#x1f333;…

Codeforces Round 921 (Div. 2)补题

We Got Everything Covered!&#xff08;Problem - A - Codeforces&#xff09; 题目大意&#xff1a;要求找出一个长度最短的字符串&#xff0c;满足任意由前k个字母组成的长度为n的字符串都是它的子序列。输出字符串。 思路&#xff1a;这道题我本来想的很简单&#xff0c;…

仅使用 Python 创建的 Web 应用程序(前端版本)第10章_订单列表

本章我们将实现订单列表页面。 完成后的图像如下。 创建过程与之前相同,如下。 No分类内容1Model创建继承BaseDataModel的数据类Order、OrderDetail2Service创建一个 OrderAPIClient3Page定义PageId并创建继承自BasePage的页面类4Application将页面 ID 和页面类对添加到 Mult…

第五季特别篇:一夜杯、游戏之宴 2017.04.26

第五季特别篇&#xff1a;一夜杯、游戏之宴 2017.04.26 OVA 第1话&#xff1a;一夜酒杯 / 一夜杯OVA 第2话&#xff1a;游戏之宴 / 遊戯の宴 OVA 第1话&#xff1a;一夜酒杯 / 一夜杯 遭到独角妖袭击的妖怪夫妇日土和初菜被夏目所救&#xff0c;这对妖怪夫妇制作的酒杯&#xf…

【服务器APP】利用HBuilder X把网页打包成APP

目录 &#x1f33a;1. 概述 &#x1f33c;1.1 新建项目 &#x1f33c;1.2 基础配置 &#x1f33c;1.3 图标配置 &#x1f33c;1.4 启动界面配置 &#x1f33c;1.5 模块配置 &#x1f33c;1.6 打包成APP &#x1f33a;1. 概述 探讨如何将网页转化为APP&#xff0c;这似乎…

LeetCode 热题 100 | 矩阵

目录 1 73. 矩阵置零 2 54. 螺旋矩阵 3 48. 旋转图像 4 240. 搜索二维矩阵 II 菜鸟做题第二周&#xff0c;语言是 C 1 73. 矩阵置零 解题思路&#xff1a; 遍历矩阵&#xff0c;寻找等于 0 的元素&#xff0c;记录对应的行和列将被记录的行的元素全部置 0将被记录的…

SOME/IP 协议介绍(七)传输 CAN 和 FlexRay 帧

SOME/IP 不应仅用于传输 CAN 或 FlexRay 帧。但是&#xff0c;消息 ID 空间需要在两种用例之间进行协调。 传输 CAN/FlexRay 应使用完整的 SOME/IP 标头。 AUTOSAR Socket-Adapter 使用消息 ID 和长度来构建所需的内部 PDU&#xff0c;但不会查看其他字段。因此&#xff0c;必…

「效果图渲染」如何使用VRay渲染逼真的物理模型

使用V-Ray渲染出逼真的物理模型首先要注重材质和光照的真实性。精细调整材质属性&#xff0c;如反射、透明度和质感&#xff0c;确保它们与现实世界中物质的特性相一致。接下来&#xff0c;布置合适的光源&#xff0c;模拟自然光线的行为&#xff0c;创建真实的光影效果。通过这…

Redis -- 前置知识

目录 简要 分布式系统 负载均衡 引入缓存 数据库分表 微服务 小结 简要 redis是存储数据在内存中, 定义变量就是在内存中, 但是redis是在分布式系统中, 才能真正发挥威力, 如果只是单机程序, 那么直接通过变量来存储数据的方式将是最优的选择. …

(bean的创建图)学习Spring的第十天(很重要)

大致框架按如下 第一次细分 bean对象还未创建 操作第一个map 引入BeanFactoryPostProcessor , 即Bean工厂后处理器 , 为Spring很重要的扩展点 BeanFactoryPostProcessor内部的方法 可以对BeaDefinition进行修改 , 也可进行BeanDefinition的注册 ( 原有在xml文件配置的bean…

大模型学习与实践笔记(十四)

使用 OpenCompass 评测 InternLM2-Chat-7B 模型使用 LMDeploy 0.2.0 部署后在 C-Eval 数据集上的性能 步骤1&#xff1a;下载internLM2-Chat-7B 模型,并进行挂载 以下命令将internlm2-7b模型挂载到当前目录下&#xff1a; ln -s /share/model_repos/internlm2-7b/ ./ 步骤2&…

idea docker 内网应用实践

文章目录 前言一、服务器端1.1 离线安装docker1.2 开启docker远程访问1.3 制作对应jdk镜像1.3.1 下载jdk171.3.2 Dockerfile 制作jdk17镜像1.3.3 镜像导出1.3.4 服务器引入镜像 二、Idea 配置2.1 Dockerfile2.2 pom 引入docker插件2.3 idea docker插件配置2.4 打包镜像上传2.5 …