Dubbo 3.x源码(17)—Dubbo服务发布导出源码(6)

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

此前我们学习了Dubbo 3.x源码(16)—Dubbo服务发布导出源码(5),也就是Dubbo远程服务在导出远程服务得到Exporter之后,继续通过Registry将其注册到远程注册中心的源码

实际上,到此我们的Dubbo服务发布导出的源码基本学习完了,本文我们主要学习exported发布服务映射数据的方法,以及对于Dubbo服务发布导出源码做一个整体的总结!

  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)
  7. Dubbo 3.x源码(17)—Dubbo服务发布导出源码(6)

文章目录

  • 1 exported发布服务映射数据
    • 1.1 MetadataServiceNameMapping元数据映射
      • 1.2.1 MappingCacheManager
      • 1.2.2 AbstractCacheManager
    • 1.2 map构建服务映射关系
  • 2 Dubbo服务发布导出总结
    • 2.1 整体流程
    • 2.2 简化流程
    • 2.3 总结

1 exported发布服务映射数据

在ServiceConfig#doExportUrls方法导出服务url到全部注册中心之后,将会调用exported方法(在Dubbo 3.x源码(12)—Dubbo服务发布导出源码(1)中有介绍):

  1. 该方法会遍历已导出的服务url,判断url中是否包含service-name-mapping属性,当存在应用级注册中心协议时才会在exportRemote方法中为url添加该参数。
  2. 如果存在该属性,那么获取MetadataServiceNameMapping,对该服务url调用map方法,将服务接口到服务名的映射关系发布到远程元数据中心
/**
 * ServiceConfig的方法
 * 将服务接口到服务名的映射关系发布到远程元数据中心
 */
protected void exported() {
    exported = true;
    List<URL> exportedURLs = this.getExportedUrls(); //导出的服务url
    exportedURLs.forEach(url -> {
        //如果url中包含service-name-mapping属性,当存在应用级注册中心协议时才会在exportRemote方法中为url添加该参数
        if (url.getParameters().containsKey(SERVICE_NAME_MAPPING_KEY)) {
            //获取MetadataServiceNameMapping
            ServiceNameMapping serviceNameMapping = ServiceNameMapping.getDefaultExtension(getScopeModel());
            try {
                //将服务接口到服务名的映射关系发布到远程元数据中心
                boolean succeeded = serviceNameMapping.map(url);
                if (succeeded) {
                    logger.info("Successfully registered interface application mapping for service " + url.getServiceKey());
                } else {
                    logger.error("5-10", "configuration server disconnected", "", "Failed register interface application mapping for service " + url.getServiceKey());
                }
            } catch (Exception e) {
                logger.error("5-10", "configuration server disconnected", "", "Failed register interface application mapping for service " + url.getServiceKey(), e);
            }
        }
    });
    onExported();
}

1.1 MetadataServiceNameMapping元数据映射

MetadataServiceNameMapping是服务接口到对应的服务应用名的映射关系的维护类,也被称作服务映射,这在consumer应用级服务发现的时候很有用。

public MetadataServiceNameMapping(ApplicationModel applicationModel) {
    //父类AbstractServiceNameMapping构造器
    super(applicationModel);
    //元数据中心MetadataReport实例的存储库实例
    metadataReportInstance = applicationModel.getBeanFactory().getBean(MetadataReportInstance.class);
}

父类AbstractServiceNameMapping构造器如下,映射数据支持本地缓存

public AbstractServiceNameMapping(ApplicationModel applicationModel) {
    this.applicationModel = applicationModel;
    //本地mapping文件缓存支持
    boolean enableFileCache = true;
    //取ApplicationConfig的file.cache属性,默认true,但Dubbo3.1版本似乎没设置默认值,导致无法本地缓存
    Optional<ApplicationConfig> application = applicationModel.getApplicationConfigManager().getApplication();
    if(application.isPresent()) {
        enableFileCache = Boolean.TRUE.equals(application.get().getEnableFileCache()) ? true : false;
    }
    //映射元数据缓存管理器
    //内部有一个Dubbo实现的LUR缓存,默认最多10000个mapping缓存,原理是很简单的继承LinkedHashMap的方式
    this.mappingCacheManager = new MappingCacheManager(enableFileCache,
        applicationModel.tryGetApplicationName(),
        applicationModel.getFrameworkModel().getBeanFactory()
        .getBean(FrameworkExecutorRepository.class).getCacheRefreshingScheduledExecutor());
}

1.2.1 MappingCacheManager

MappingCacheManager的构造器如下,将会获取本地缓存文件的各种信息,然后调用init方法初始化:

/**
 * MappingCacheManager的构造器
 *
 * @param enableFileCache 是否支持本地文件缓存
 * @param name 缓存文件名,默认ApplicationNam
 * @param executorService 将基于文件的缓存从内存刷新到磁盘的执行器cacheRefreshingScheduledExecutor
 */
public MappingCacheManager(boolean enableFileCache, String name, ScheduledExecutorService executorService) {
    //从来自JVM环境变量中的配置
    String filePath = System.getProperty("dubbo.mapping.cache.filePath");
    String fileName = System.getProperty("dubbo.mapping.cache.fileName");
    //默认文件名.mapping
    if (StringUtils.isEmpty(fileName)) {
        fileName = DEFAULT_FILE_NAME;
    }
    //.mapping.{dubbo.application.name}
    if (StringUtils.isNotEmpty(name)) {
        fileName = fileName + "." + name;
    }
    //本地缓存映射最大数量
    String rawEntrySize = System.getProperty("dubbo.mapping.cache.entrySize");
    int entrySize = StringUtils.parseInteger(rawEntrySize);
    entrySize = (entrySize == 0 ? DEFAULT_ENTRY_SIZE : entrySize);
    //文件最大大小
    String rawMaxFileSize = System.getProperty("dubbo.mapping.cache.maxFileSize");
    long maxFileSize = StringUtils.parseLong(rawMaxFileSize);
    //初始化
    init(enableFileCache, filePath, fileName, entrySize,  maxFileSize, 50, executorService);
}

1.2.2 AbstractCacheManager

AbstractCacheManager的init方法如下:

  1. 初始化一个Dubbo实现的LUR缓存,默认最多10000个mapping缓存,原理是很简单的继承LinkedHashMap的方式。
  2. 构建文件缓存服务,启动定时调度任务执行mapping缓存刷盘操作。
/**
 * AbstractCacheManager的方法
 *
 * @param enableFileCache 是否支持本地文件缓存
 * @param filePath 文件路径
 * @param fileName 文件名
 * @param entrySize 缓存映射最大数量,默认10000
 * @param fileSize 文件最大大小
 * @param interval 调度任务间隔时间
 * @param executorService 文件持久化调度任务执行器
 */
protected void init(boolean enableFileCache, String filePath, String fileName, int entrySize, long fileSize, int interval, ScheduledExecutorService executorService) {
    //Dubbo实现的LUR缓存,默认最多10000个mapping缓存,原理是很简单的继承LinkedHashMap的方式
    this.cache = new LRUCache<>(entrySize);

    try {
        //缓存存储服务
        cacheStore = FileCacheStoreFactory.getInstance(filePath, fileName, enableFileCache);
        Map<String, String> properties = cacheStore.loadCache(entrySize);
        logger.info("Successfully loaded " + getName() + " cache from file " + fileName + ", entries " + properties.size());
        for (Map.Entry<String, String> entry : properties.entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue();
            this.cache.put(key, toValueType(value));
        }
        // executorService can be empty if FileCacheStore fails
        if (executorService == null) {
            this.executorService = Executors.newSingleThreadScheduledExecutor(new NamedThreadFactory("Dubbo-cache-refreshing-scheduler", true));
        } else {
            this.executorService = executorService;
        }
        //缓存刷新调度任务
        this.executorService.scheduleWithFixedDelay(new CacheRefreshTask<>(this.cacheStore, this.cache, this, fileSize), 10, interval, TimeUnit.MINUTES);
    } catch (Exception e) {
        logger.error("Load mapping from local cache file error ", e);
    }
}

1.2 map构建服务映射关系

MetadataServiceNameMapping的map方法,将服务接口到服务名的映射关系发布到所有远程元数据中心,对于zookeeper元数据中心来说,每一个服务接口都对应一个节点,节点路径为dubbo/mapping/{serviceInterface},值就是该接口所在的服务应用名,也就是dubbo.applicaation.name。

/**
 * MetadataServiceNameMapping的方法
 * <p>
 * Simply register to all metadata center
 * 将服务接口到服务名的映射关系发布到所有远程元数据中心
 */
@Override
public boolean map(URL url) {
    if (CollectionUtils.isEmpty(applicationModel.getApplicationConfigManager().getMetadataConfigs())) {
        logger.warn("No valid metadata config center found for mapping report.");
        return false;
    }
    //服务接口全部路径名
    String serviceInterface = url.getServiceInterface();  
    if (IGNORED_SERVICE_INTERFACES.contains(serviceInterface)) {
        return true;
    }

    boolean result = true;
    for (Map.Entry<String, MetadataReport> entry : metadataReportInstance.getMetadataReports(true).entrySet()) {
        MetadataReport metadataReport = entry.getValue();
        String appName = applicationModel.getApplicationName();
        try {
            //MetadataReport支持直接注册服务-应用程序映射,该方法目前版本默认返回false
            if (metadataReport.registerServiceAppMapping(serviceInterface, appName, url)) {
                // MetadataReport support directly register service-app mapping
                continue;
            }

            boolean succeeded;
            //当前重试次数,最多6次
            int currentRetryTimes = 1;
            String newConfigContent = appName;
            do {
                //尝试根据服务名获取已存在的映射项,对于ZookeeperMetadataReport来说,就是获取 mapping/{serviceInterface} 这个节点的值
                ConfigItem configItem = metadataReport.getConfigItem(serviceInterface, DEFAULT_MAPPING_GROUP);
                String oldConfigContent = configItem.getContent();
                if (StringUtils.isNotEmpty(oldConfigContent)) {
                    //如果此前映射包含了当前appName,那么算作成功
                    boolean contains = StringUtils.isContains(oldConfigContent, appName);
                    if (contains) {
                        // From the user's perspective, it means successful when the oldConfigContent has contained the current appName. So we should not throw an Exception to user, it will confuse the user.
                        succeeded = true;
                        break;
                    }
                    //否则,旧的appName和新的appName通过 , 拼接起来
                    newConfigContent = oldConfigContent + COMMA_SEPARATOR + appName;
                }
                //尝试创建或者更新映射关系,对于ZookeeperMetadataReport来说,就是创建或者更新 mapping/{serviceInterface} 这个节点
                succeeded = metadataReport.registerServiceAppMapping(serviceInterface, DEFAULT_MAPPING_GROUP, newConfigContent, configItem.getTicket());
            } while (!succeeded && currentRetryTimes++ <= CAS_RETRY_TIMES);

            if (!succeeded) {
                result = false;
            }
        } catch (Exception e) {
            result = false;
            logger.warn("Failed registering mapping to remote." + metadataReport, e);
        }
    }

    return result;
}

zookeeper中的服务映射关系节点如下:

image.png
有了服务映射,那么就能通过服务接口来查询对应的服务应用名了,然后继续根据服务名获取服务实例信息,这在Dubbo 3.x 之后consumer应用级服务发现的时候很有用。

2 Dubbo服务发布导出总结

2.1 整体流程

到此,我们基本学习了Dubbo服务发布的全流程(除了应用级服务数据的远程注册之外,这个后面单独讲),现在我们来梳理一下整体流程,注意下面的流程非常的常,但是如果你能看完这些流程,那么我相信你在面试的时候也能够比较简单的说出重点出来:

  1. 首先是Dubbo服务发布的入口,在Dubbo 3.1中,它位于监听器DubboDeployApplicationListener#onApplicationEvent方法中,在spring容器启动的最后一个步也就是refresh方法内部最后的finishRefresh方法中,将会向所有监听器发布一个ContextRefreshedEvent事件,表示容器刷新完毕,此时就会触发这个方法的调用
  2. onApplicationEvent方法内部通过DefaultModuleDeployer#start方法开启服务的导出、引用等操作,最终在startSync方法内部的exportServices方法中,将会获取全部的dubbo服务bean实例ServiceConfigBase,然后依次调用exportServiceInternal方法进行导出。
  3. exportServiceInternal方法中会判断是否开启了异步导出,如果开启了那么使用线程池导出,默认没开启,那么就走同步导出。通过服务ServiceConfig本身的export方法执行导出,对于已导出的服务将会加入exportedServices列表中。
  4. ServiceConfigBase#export方法中,会判断是否需要延迟导出服务,也就是delay属性,如果不需要那么立即执行doExport方法导出服务,否则获取服务导出线程池,异步的执行doExport方法导出服务,延迟时间为配置的delay毫秒。默认立即导出。
  5. doExport方法中包含服务导出的骨干逻辑:首先通过doExportUrls方法导出服务url到全部注册中心,这是核心方法。导出服务url之后,调用exported方法尝将服务接口到服务名的映射关系发布到远程元数据中心,这是Dubbo3应用级服务注册发现所必须的步骤。
    1. doExportUrls方法中,首先构建并注册对应服务的serviceDescriptorproviderModel,然后通过loadRegistries加载全部的注册中心url地址信息,最后遍历当前服务接口支持的协议,依次调用doExportUrlsFor1Protocol方法,尝试将每个协议向多个注册中心url进行服务导出。
      1. loadRegistries方法用于加载注册中心url地址,获取全部注册中心配置集合,然后对每个注册中心的配置拼接好注册中心url,协议将被改写为注册中心协议。
        1. 在Dubbo3.1版本中,如果url没有特殊参数,那么默认情况下,每个注册中心都会生成两个注册中心协议url,一个service-discovery-registry开头的协议表示服务级别发现注册中心,一个registry开头的协议表示接口级别发现注册中心。也就是说,服务将会同时进行接口级别和服务级别的注册。
      2. doExportUrlsFor1Protocol方法中,会根据协议参数和当前服务元数据,构建出一个服务导出协议url,然后调用exportUrl方法继续导出。
      3. exportUrl方法中,获取scope属性判断服务导出的范围,none代表不导出、local仅导出到本地JVM、remote会导出到远程,默认为null,表示会同时导出到本地JVM和远程,分别调用exportLocal和exportRemote方法。在远程导出完毕之后,还会调用MetadataUtils#publishServiceDefinition方法发布服务元数据信息。
        1. exportLocal 方法用于本地导出,用不上注册中心协议url,主要用于本jvm的消费者调用本jvm上的服务提供者,不涉及远程网络调用。
          1. 将服务导出协议url改写为injvm协议的url,ip地址固定为127.0.0.1,端口为0。可以看到所谓的本地导出,没有监听端口,没有远程调用,但是仍然会走dubbo的Filter和Listener。随后调用doExportUrl方法执行协议导出。
        2. exportRemote方法用于远程导出,涉及到服务注册、启动服务端nettyserver等逻辑,用于远程网络调用。
          1. 遍历全部注册中心协议url添加参数,例如如果是应用级注册中心,那么为url添加service-name-mapping=true参数。
          2. 在注册中心协议url内部的attributes中添加属性,key为export,value为服务导出协议url,随后调用doExportUrl方法执行协议导出。
    2. 在doExportUrls方法导出服务url到全部注册中心之后,将会调用exported方法。
      1. 该方法会遍历已导出的服务url,判断url中是否包含service-name-mapping属性,当存在应用级注册中心协议时才会在exportRemote方法中为url添加该参数。
      2. 如果存在该属性,那么获取MetadataServiceNameMapping,对该服务url调用map方法,将服务接口到服务名的映射关系发布到远程元数据中心。
      3. 在zookeeper元数据中心,对应的映射节点目录为:dubbo/mapping/{serviceInterface},节点值就是该接口所在的服务应用名,也就是dubbo.applicaation.name。

exportLocal 和exportRemote方法最终都会调用doExportUrl方法,该方法是服务导出的核心方法,也是面试常回答的地方:

  1. 首先通过代理服务工厂proxyFactory#getInvoker方法将ref、interfaceClass、url包装成一个Invoker可执行体实例,Invoker可以统一调用方式,屏蔽调用细节
    1. 这里的proxyFactory是ProxyFactory的自适应扩展实现,即ProxyFactory$Adaptive,也就是说会根据传入的url中的参数proxy的值选择对应的代理工厂实现类,而默认实现就是JavassistProxyFactory。
      1. JavassistProxyFactory将会利用javassist动态创建了Class对应的Wrapper对象,动态生成的Wrapper类改写invokeMethod方法,其内部会被改写为根据接口方法名和参数直接调用ref对应名字的方法,避免通过Jdk的反射调用方法带来的性能问题。
      2. 然后创建一个AbstractProxyInvoker匿名实现类对象返回,重写了doInvoke方法,内部实际调用的wrapper#invokeMethod方法。
  2. 获取到可执行对象Invoker之后,通过协议protocolSPI对invoker进行服务导出,获取Exporter实例,然后将exporter加入到exporters缓存集合中。
    1. 这里的protocolSPI是Protocol的自适应扩展实现,即Protocol$Adaptive,也就是说会根据传入的url中的protocol选择对应的Protocol SPI实现类,而默认实现就是dubbo协议,即DubboProtocol。本地导出的injvm协议对应InjvmProtocol,需要导出到接口级注册中心的registry对应InterfaceCompatibleRegistryProtocol,需要导出到应用级注册中心的service-discovery-registry对应RegistryProtocol。
    2. 由于Dubbo SPI wrapper机制的存在,返回的Protocol就经过了几层的wrapper的包装。Dubbo 3.1默认经过了三层包装,即ProtocolSerializationWrapper -> ProtocolFilterWrapper -> ProtocolListenerWrapper -> 具体的Protocol实现。
      1. ProtocolSerializationWrapper会将导出的服务url存入FrameworkServiceRepository仓库内部的providerUrlsWithoutGroup缓存中。
      2. ProtocolFilterWrapper将会为Invoker添加各种Filter,形成InvokerChain。
      3. ProtocolListenerWrapper将返回的Exporter包装为ListenerExporterWrapper,内部包含了一个Invoker和一个监听器列表。
    3. 当获取到经过包装的Protocol之后,将会调用Protocol#export方法进行服务的导出。
      1. 对于本地导出,也就是InjvmProtocol,本地导出并没有涉及到注册中心以及网络服务器,它仅仅是基于Invoer构建一个InjvmExporter,并且存入到exporterMap这个缓存map集合中,key构成规则为{group}/{serviceInterfaceName}:{version}:{port}。后续调用时,将会从exporterMap找到Exporter,然后找到Invoker进行调用。
    4. 对于远程导出就比较复杂,包括接口级注册中心的registry对应InterfaceCompatibleRegistryProtocol,应用级注册中心的service-discovery-registry对应RegistryProtocol。大概步骤如下:
      1. 需要从注册中心url的attributes属性中,获取真实的服务导出url,然后调用doLocalExport方法进行服务导出,该方法内部实际上就是重复前面的Protocol$Adaptive#export的过程。
        1. 此时,将会调用真实协议对应的Protocol实现,例如dubbo协议对应着DubboProtocol,而在这些协议的export方法中,除了构建Exportor加入exporterMap缓存之外,还会调用openServer方法,开启一个服务提供者端服务器,监听端口,这样就能接收consumer的远程调用请求。
        2. 同ip同端口(同一个dubbo服务端)的Dubbo应用中,多个Dubbo Service将会使用同一个服务器,即只有在第一次调用openServer的时候才会创建服务器。ip就是服务器的ip,端口就是20880端口。
        3. 创建服务器的时候,默认使用netty作为底层通信库,即创建一个netty服务端。
      2. 然后基于Dubbo SPI机制根据注册中心url加载具体的注册中心操作类Registry,应用级服务导出协议service-discovery-registry对应着ServiceDiscoveryRegistry,接口级服务导出协议则会获取真实注册中心协议对应的Registry。
      3. 通过调用Registry#register方法向远程注册中心注册服务提供者url。对于接口级服务导出协议会直接注册到注册中心,而对于应用级服务导出协议则仅仅是存入到本地内存中,在后面才会将服务信息真正的注册(DefaultModuleDeployer#startSync方法最后的onModuleStarted方法中)。
      4. 将Exporter包装为一个DestroyableExporter返回。

2.2 简化流程

  1. 首先是Dubbo服务发布的入口,在spring容器启动的最后一个步也就是refresh方法内部最后的finishRefresh方法中,将会向所有监听器发布一个ContextRefreshedEvent事件,表示容器刷新完毕。在Dubbo 3.1中,有个监听器DubboDeployApplicationListener,能监听该事件,在监听到该事件之后的onApplicationEvent方法中,会触发服务的导出和引用。这就是入口。然后遍历所有的Dubbo服务实例ServiceConfigBase,一次导出。
  2. 调用loadRegistries方法获取所有注册中心配置,组装成服务注册协议url。在Dubbo3.1中,默认一个注册中心配置将会构建出两条服务注册协议url,service-discovery-registry协议表示服务级别发现注册中心,registry协议表示接口级别发现注册中心。
  3. 遍历当前服务支持的协议,对于每个协议都尝试注册到所有注册中心url上。调用doExportUrlsFor1Protocol方法根据协议和服务元数据构建服务导出url,后续开始进行exportLocal本地导出和exportRemote远程导出,默认都会导出。所谓本地导出,就是本jvm的消费者调用本jvm上的服务提供者,不需要通络通信。远程导出自然就是服务于不同应用之间的通信了。
    1. 通过proxyFactory.getInvoker方法构建Invoker,提供了统一的调用入口,屏蔽底层细节。Invoker内部封装了ref,即服务接口的实现,同时默认基于javassist动态构建wrapper来避免反射的调用,提升性能。
    2. 基于协议获取Protocol,然后调用export方法进行服务导出得到Exporter,Exporter中包装了Filter过滤器、Listener监听器等逻辑,得到Exporter后会存入exporterMap这个缓存map中,后续可以查找到。
    3. 首次远程服务导出的时候,还会创建服务器,默认使用netty作为底层通信库,创建了服务器之后会启动绑定ip和端口,例如20880端口,此时可以监听远程请求。这是远程导出才会有的逻辑。
    4. 获取注册中心协议获取操作类Registry,通过调用register方法向远程注册中心注册服务提供者url信息。这是远程导出才会有的逻辑。

2.3 总结

Protocol、Invoker和Exporter属于远程调用层(Protocol):封将RPC调用,以Invocation和Result为中心,扩展接口为Protocol、Invoker和Exporter。

  1. Protocol是服务域,它是Invoker暴露和引用的主功能入口,它负责Invoker的生命周期管理。
  2. Invoker是实体域,它是Dubbo的核心模型,其它模型都向它靠扰,或转换成它,它代表一个可执行体,可向它发起invoke调用,它有可能是一个本地的实现,也可能是一个远程的实现,也可能一个集群实现。
  3. Exporter内部封装了Invoker,并且给予Dubbo SPI wrapper机制,封装了Filter和lister。

为什么创建Invoker?

Invoker对象实际上是一个动态创建的AbstractProxyInvoker匿名实现类对象,内部包含了ref,也就是服务接口的实现类对象,ref内部封装了真正的服务调用逻辑,同时它invoke方法,可以基于方法名、参数类型、参数列表调用接口方法,所有的服务都可以基于此方法进行方法调用,这样就提供了统一的调用入口,屏蔽了底层调用细节。

同时它的doInvoke方法,内部实际调用的基于javassist动态创建的Wrapper对象的invokeMethod方法,避免了反射调用方法的开销。

最后,我们的Dubbo服务发布导出源码终于基本学习完了,实际上学习的入口就是在DefaultModuleDeployer#startSync方法中的exportServices服务导出方法。之后会继续调用referServices方法进行服务引用,而在之后的onModuleStarted方法中,此时才会进行应用级服务数据的真正远程注册。

后续我们将学习referServices方法,也就是Dubbo 3.x 的服务引用的源码。

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

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

相关文章

git的分支操作

目录 简介&#xff1a; 操作&#xff1a;查看 操作&#xff1a;创建 操作&#xff1a;切换​编辑 操作&#xff1a;本地分支推送到远程 操作&#xff1a;git merge [name]合并分支​编辑 简介&#xff1a; 在Git中&#xff0c;可以通过分支来管理和处理不同的版本和功能。分…

DSP系统时钟总结

一、stm32中断偏移向量介绍 1.1 为什么要设置中断向量偏移 上图可以看出程序上电先进入0x08000000开始运行&#xff0c;紧接着执行复位中断向量&#xff0c;然后执行复位中断程序&#xff0c;然后进入main函数。 如果想要app的中断正常运行&#xff0c;那就必须手动设置中断向…

在本地电脑上打开服务器里面的localhost网址

远程连接服务器&#xff0c;启动了一个服务 显示访问地址为&#xff1a;http://127.0.0.1:7860 在本地浏览器将127.0.0.1改成服务器ip但是无法访问 解决办法&#xff1a; 1. ssh新建一个远程连接&#xff0c;将服务器的7860端口重定向到本机 ssh -L 18097:127.0.0.1:7860 us…

LeetCode 54 螺旋矩阵

题目描述 螺旋矩阵 给你一个 m 行 n 列的矩阵 matrix &#xff0c;请按照 顺时针螺旋顺序 &#xff0c;返回矩阵中的所有元素。 示例 1&#xff1a; 输入&#xff1a;matrix [[1,2,3],[4,5,6],[7,8,9]] 输出&#xff1a;[1,2,3,6,9,8,7,4,5]示例 2&#xff1a; 输入&#x…

qt-C++笔记之QStringList、QList<QString>、QString、QChar、QList<QChar>区别

qt-C笔记之QStringList、QList、QString、QChar、QList区别 —— 杭州 2024-01-30 凌晨0:27 参考博文&#xff1a;qt-C笔记之QStringList code review! 文章目录 qt-C笔记之QStringList、QList<QString>、QString、QChar、QList<QChar>区别1.Qt的字符容器类1.QSt…

PHP抽奖设置中奖率,以及防高并发

一、中奖率,先在后台设定好奖项名称,抽奖份数,以及中奖百分比 奖品表draw 二、 借助文件排他锁,在处理下单请求的时候,用flock锁定一个文件,如果锁定失败说明有其他订单正在处理,此时要么等待要么直接提示用户"服务器繁忙" 阻塞(等待)模式,一般都是用这个模…

五大架构之一:系统架构数据流风格

系统架构数据流风格详细介绍 系统架构数据流风格是一种软件体系结构风格&#xff0c;它强调了系统内部不同部分之间的数据流动。这种风格侧重于描述系统中的数据处理过程&#xff0c;以及数据是如何从一个组件传递到另一个组件的。以下是系统架构数据流风格的详细介绍&#xff…

-1- Python环境安装

1、Python安装 1、Windows安装Python 进入python官网&#xff1a;Welcome to Python.org点击 download——>all releases&#xff1b;建议选择3.7.2版本&#xff08;网页链接&#xff1a;Python Release Python 3.7.2 | Python.org&#xff09;&#xff1b;下拉&#xff0…

云原生数据库GaiaDB的核心技术演进

导读 越来越强调云原生的环境下&#xff0c;存算分离作为一种新的架构理念&#xff0c;已经是大势所趋。新的技术架构带来新的问题和挑战&#xff0c;GaiaDB 在自研过程中采用Quorum分布式协议、高性能网络、高可靠分布式存储引擎等技术实现更高的性能和可用性。 本文针对一系列…

Opencv——霍夫变换

霍夫直线变换 霍夫直线变换(Hough Line Transform)用来做直线检测 为了加升大家对霍夫直线的理解,我在左图左上角大了一个点,然后在右图中绘制出来经过这点可能的所有直线 绘制经过某点的所有直线的示例代码如下,这个代码可以直接拷贝运行 import cv2 as cv import matplot…

element -table,多行或列合并

需求&#xff1a;后端返回的表格数据&#xff0c;如果某列值一样&#xff0c;前端表格样式需要合并他们&#xff0c;需要合并的列的行数未知&#xff08;所以需要有数据后遍历后端数据对需要合并的属性进行计数&#xff09;即动态遍历表格合并 效果 - 重点方法&#xff1b;ta…

《金融电子化》昆仑银行在应用性能监控(APM)平台的实践与探索

《金融电子化》昆仑银行在应用性能监控&#xff08;APM&#xff09;平台的实践与探索 中国人民银行印发的《金融科技发展规划&#xff08;2022-2025年&#xff09;》是对金融科技发展的重要引领。规划强调了金融科技在推动金融行业现代化转型、提升金融服务效率和风险防控水平…

【LeetCode: 25. K 个一组翻转链表 + 链表 + 递归】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

大数据学习之Redis,十大数据类型的具体应用(三)

目录 3.7 Redis位图&#xff08;bitmap&#xff09; 概念 需求 是什么 说明 能干嘛? 基本命令 3.7 Redis位图&#xff08;bitmap&#xff09; 概念 由0和1状态表现的二进制位的bit数组 需求 用户是否登陆过&#xff1f;Y / N 广告是否被点击过&#xff1f; 钉钉打…

Swift Vapor 教程(项目创建)

The future of web development. 在初次接触 Swift Vapor 时&#xff0c;感觉代码比较清爽&#xff0c;用起来逻辑比较清晰。 困难点&#xff1a; Swift Vapor 使用了JWT管理三方库&#xff0c;比较吃网络Swift Vapor 搭建环境比较复杂初次使用Swift Vapor 尽量不要使用MySql。…

关于 IntelliJ IDEA 中 Schedule for Addition 的问题

IntelliJ IDEA是一款强大的Java集成开发环境&#xff0c;由JetBrAIns公司开发。它以其智能代码编辑、代码分析工具、自动代码补全、强大的调试功能和内建的版本控制等特性而闻名。此外&#xff0c;它还支持Kotlin、Groovy、Scala和Android开发等多种语言和框架。 IntelliJ IDE…

Django模型(五)

一、数据的条件查询 参考文档:QuerySet API 参考 | Django 文档 | Django 1.1、常用检索字段 字段检索,是在字段名后加 __ 双下划线,再加关键字,类似 SQL 语句中的 where 后面的部分, 如: 字段名__关键字 exact :判断是否等于value,一般不使用,而直接使用 =contai…

【QT】坐标系统和坐标变换

目录 1 坐标变换函数 1.1 坐标平移 1.2 坐标旋转 1.3 缩放 1.4 状态保存与恢复 2 坐标变换绘图实例 2.1 绘制3个五角星的程序 2.2 绘制五角星的PainterPath的定义 3 视口和窗口 3.1 视口和窗口的定义与原理 3.2 视口和窗口的使用实例 4 绘图叠加的效果 1 坐标变换函数 QPainter…

高通GAIA V3命令参考手册的研读学习(十三):GAIA通知

如前文《高通GAIA V3命令参考手册的研读学习&#xff08;四&#xff09;》所述&#xff0c;PDU一共有四种&#xff0c;前面已经讲了命令、回应以及错误码&#xff0c;现在来看最后一种&#xff1a;通知。 4. QTIL GAIA通知 通知发送的方向&#xff0c;是由设备发送到移动应用…

CI/CD 管道安全:构建和部署之外的最佳实践

鉴于对快速创新和敏捷方法论采用的需求&#xff0c;持续集成/持续部署 (CI/CD) 管道已成为构建所有 DevOps 流程的基础。他们是高效交付的支柱。 事实上&#xff0c;根据持续交付状态报告&#xff0c;使用 CI/CD 工具与所有指标上更好的软件交付性能相关。 这些管道给组织带…