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$AdaptiveRegistryFactory 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.RegistryServiceString 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 {@Overrideprotected 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=20001if (UrlUtils.hasServiceDiscoveryRegistryProtocol(url)) {//获取url的registry参数值,默认dubboString 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=20001return 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,默认5000msthis.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,是否支持本地缓存,默认truelocalCacheEnabled = url.getParameter(REGISTRY_LOCAL_FILE_CACHE_ENABLED, true);//获取注册表缓存执行器sharedScheduledExecutorregistryCacheExecutor = url.getOrDefaultFrameworkModel().getBeanFactory().getBean(FrameworkExecutorRepository.class).getSharedScheduledExecutor();//如果支持本地缓存if (localCacheEnabled) {// Start file save timer//获取url参数save.file,是否同步存储,默认falsesyncSaveFile = url.getParameter(REGISTRY_FILESAVE_SYNC_KEY, false);//注册中心缓存文件名 /{user.home}/.dubbo/dubbo-registry-{dubbo.application.name}-{ip}-{post}.cacheString defaultFilename = System.getProperty(USER_HOME) + DUBBO_REGISTRY + url.getApplication() +"-" + url.getAddress().replaceAll(":", "-") + CACHE;//获取url参数file,指定的文件名,默认defaultFilenameString 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 Registerreturn;}//执行注册doRegister(url);
}@Override
public void doRegister(URL url) {// fixme, add registry-cluster is not necessary anymoreurl = 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添加MetadataInfometadataInfo.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是serviceInfothis.services.put(serviceInfo.getMatchKey(), serviceInfo);// extract common instance level paramsextractInstanceParams(url, filters);//初始化exportedServiceURLsif (exportedServiceURLs == null) {exportedServiceURLs = new ConcurrentSkipListMap<>();}//加入到exportedServiceURLs缓存,  key例如:greeting/org.apache.dubbo.demo.GreetingService:1.0.0  value是url的SortedSet<URL>集合addURL(exportedServiceURLs, url);//设置更新标志为trueupdated = 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中移除该urlremoveFailedRegistered(url);//从移除注册失败的缓存failedUnregistered中移除该urlremoveFailedUnregistered(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 regularlyaddFailedRegistered(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服务导出的整体源码流程进行一个比较全面的总结。

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C#使用IsLeapYear方法判断指定年份是否为闰年

目录 一、判断指定年是否为闰年的2个方法 1.使用IsLeapYear方法判断指定年份是否为闰年 2.使用自定义的算法计算指定年份是否为闰年 二、示例 1.方法1的实例 2.方法2的实例 一、判断指定年是否为闰年的2个方法 1.使用IsLeapYear方法判断指定年份是否为闰年 使用IsLeapY…

《Linux高性能服务器编程》笔记06

Linux高性能服务器编程 本文是读书笔记&#xff0c;如有侵权&#xff0c;请联系删除。 参考 Linux高性能服务器编程源码: https://github.com/raichen/LinuxServerCodes 豆瓣: Linux高性能服务器编程 文章目录 Linux高性能服务器编程第13章 多进程编程13.1 fork 系统调用13…

Spring Boot 整合 Camunda 实现工作流

工作流是我们开发企业应用几乎必备的一项功能&#xff0c;工作流引擎发展至今已经有非常多的产品。最近正好在接触Camunda&#xff0c;所以来做个简单的入门整合介绍。如果您也刚好在调研或者刚开始计划接入&#xff0c;希望本文对您有所帮助。如果您是一名Java开发或Spring框架…

使用强化学习进行神经网络结构搜索的代码以及修改

目录 代码一&#xff08;Using TensorFlow&#xff09;&#xff1a; 代码二&#xff08;Using TensorFlow&#xff09;&#xff1a; 代码三&#xff08;Using PyTorch&#xff09;&#xff1a; 参考&#xff1a; 本人在网上找了三个相关的代码&#xff0c;但是都有问题&…

Web--HTML基础

文章目录 安装环境HTMLhtml框架html基础标签语义标签html特殊符号 安装环境 安装vscode后 安装插件 可以先不写后台直接将前度界面展示出来 自动补全tag&#xff0c;同时修改tag时自动改另一半 在设置里将保存自动格式化的选项勾上 创建一个index.htm文件&#xff0c;这个…

基于sentinel-2 遥感数据的水体提取(水体指数法)

本文框架设置如下&#xff1a; 简单介绍senintel-2数据&#xff1b;如何利用sentinel-2数据获取水体边界/范围 1 Sentinel-2数据介绍及下载方式 有Sentinel-2A/2B两颗卫星&#xff0c;其参数基本一致&#xff0c;因此两颗卫星的数据联合使用很方便。 分辨率有&#xff1a;1…

springboot114基于多维分类的知识管理系统

简介 【毕设源码推荐 javaweb 项目】基于springbootvue 的基于多维分类的知识管理系统 适用于计算机类毕业设计&#xff0c;课程设计参考与学习用途。仅供学习参考&#xff0c; 不得用于商业或者非法用途&#xff0c;否则&#xff0c;一切后果请用户自负。 看运行截图看 第五章…

黑马Java——面向对象进阶(static继承)

1.static静态变量 静态变量是随着类的加载而加载的&#xff0c;优先与对象出现的

Feature Pyramid Grids 原理与代码解析

paper&#xff1a;Feature Pyramid Grids third-party implementation&#xff1a;https://github.com/open-mmlab/mmdetection/blob/master/mmdet/models/necks/fpg.py 存在的问题 基于NAS得到的特征金字塔结构如NAS-FPN展现了良好的性能表现&#xff0c;但用NAS寻找改进结…

如何给openai的assistant添加Functions

我的chatgpt网站&#xff1a; https://chat.xutongbao.top/ {"name": "get_current_datetime","description": "获取北京时间&#xff0c;当前时间&#xff0c;当前日期","parameters": {"type": "object&q…

《GreenPlum系列》GreenPlum初级教程-GreenPlum详细入门教程

文章目录 GreenPlum详细入门教程第一章 GreenPlum介绍1.MPP架构介绍2.GreenPlum介绍3.GreenPlum数据库架构4.GreenPlum数据库优缺点 第二章 GreenPlum单节点安装1.Docker创建centos容器1.1 拉取centos7镜像1.2 创建容器1.3 进入容器1.4 容器和服务器免密操作1.4.1 生成密钥1.4.…

1.8 万 Star!这款 Nginx 可视化配置工具太强了

NginxConfig简介 Nginx Config 是一个强大的 Nginx 配置文件生成器&#xff0c;号称配置 Nginx 服务器所需的唯一工具。 正因为 Nginx 功能强大&#xff0c;所以针对其各个功能的配置项会显得特别多&#xff0c;对于我们来说要记住那么多配置是一件十分头疼的事&#xff0c;甚…