Dubbo源码解析-服务注册(五)

一、服务注册

当确定好了最终的服务配置后,Dubbo就会根据这些配置信息生成对应的服务URL,比如:

dubbo://192.168.65.221:20880/org.apache.dubbo.springboot.demo.DemoService?
application=dubbo-springboot-demo-provider&timeout=3000

这个URL就表示了一个Dubbo服务,服务消费者只要能获得到这个服务URL,就知道了关于这个
Dubbo服务的全部信息,包括服务名、支持的协议、ip、port、各种配置。

确定了服务URL之后,服务注册要做的事情就是把这个服务URL存到注册中心(比如Zookeeper)中去,说的再简单一点,就是把这个字符串存到Zookeeper中去,这个步骤其实是非常简单的,实现这个功能的源码在RegistryProtocol中的export()方法中,最终服务URL存在了Zookeeper的/dubbo/接口名/providers目录下。

但是服务注册并不仅仅就这么简单,既然上面的这个URL表示一个服务,并且还包括了服务的一些配置信息,那这些配置信息如果改变了呢?比如利用Dubbo管理台中的动态配置功能(注意,并不是配置中心)来修改服务配置,动态配置可以应用运行过程中动态的修改服务的配置,并实时生效。

如果利用动态配置功能修改了服务的参数,那此时就要重新生成服务URL并重新注册到注册中心,这样服务消费者就能及时的获取到服务配置信息。而对于服务提供者而言,在服务注册过程中,还需要能监听到动态配置的变化,一旦发生了变化,就根据最新的配置重新生成服务URL,并重新注册到中心。

一、接口级注册

一、接口级注册原理

 首先,我们可以通过配置dubbo.application.register-mode来控制:

1. instance:表示只进行应用级注册
2. interface:表示只进行接口级注册
3. all:表示应用级注册和接口级注册都进行,默认

在Dubbo3.0之前,Dubbo是接口级注册,服务注册就是把接口名以及服务配置信息注册到注册中心中,我们把dubbo.application.register-mode设置为interface,看到注册中心(zookeeper)存储的数据格式大概为:

总结来说就是:

接口名1:dubbo://192.168.65.221:20880/接口名1?application=应用名
接口名2:dubbo://192.168.65.221:20880/接口名2?application=应用名
接口名3:dubbo://192.168.65.221:20880/接口名3?application=应用名

key是接口名,value就是服务URL,上面的内容就表示现在有一个应用,该应用下有3个接口,应用实例部署在192.168.65.221,此时,如果给该应用增加一个实例,实例ip为192.168.65.222,那么新的实例也需要进行服务注册,会向注册中心新增3条数据:

接口名1:dubbo://192.168.65.221:20880/接口名1?application=应用名
接口名2:dubbo://192.168.65.221:20880/接口名2?application=应用名
接口名3:dubbo://192.168.65.221:20880/接口名3?application=应用名接口名1:dubbo://192.168.65.222:20880/接口名1?application=应用名
接口名2:dubbo://192.168.65.222:20880/接口名2?application=应用名
接口名3:dubbo://192.168.65.222:20880/接口名3?application=应用名

可以发现,如果一个应用中有3个Dubbo服务,那么每增加一个实例,就会向注册中心增加3条记录,那如果一个应用中有10个Dubbo服务,那么每增加一个实例,就会向注册中心增加10条记录,注册中心的压力会随着应用实例的增加而剧烈增加。
反过来,如果一个应用有3个Dubbo服务,5个实例,那么注册中心就有15条记录,此时增加一个
Dubbo服务,那么注册中心就会新增5条记录,注册中心的压力也会剧烈增加。

所以这就是接口级注册的弊端。

二、源码流程分析

dubbo服务启动的时候首先会来到doExportUrls方法:

private void doExportUrls() {ModuleServiceRepository repository = getScopeModel().getServiceRepository();ServiceDescriptor serviceDescriptor = repository.registerService(getInterfaceClass());providerModel = new ProviderModel(getUniqueServiceName(),ref,serviceDescriptor,this,getScopeModel(),serviceMetadata);repository.registerProvider(providerModel);List<URL> registryURLs = ConfigValidationUtils.loadRegistries(this, true);for (ProtocolConfig protocolConfig : protocols) {String pathKey = URL.buildKey(getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), group, version);// In case user specified path, register service one more time to map it to path.repository.registerService(pathKey, interfaceClass);doExportUrlsFor1Protocol(protocolConfig, registryURLs);}
}

ConfigValidationUtils.loadRegistries方法根据服务配置获取注册信息registryURLs,内容如下

registry://localhost:2181/org.apache.dubbo.registry.RegistryService?REGISTRY_CLUSTER=registryConfig&application=dubbo-demo-annotation-provider&dubbo=2.0.2&pid=21604&register-mode=interface&registry=zookeeper&timestamp=1731940701781

最终由于url带有registry,所以UrlUtils.isRegistry(invoker.getUrl())为true

public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {if (UrlUtils.isRegistry(invoker.getUrl())) {return protocol.export(invoker);}FilterChainBuilder builder = getFilterChainBuilder(invoker.getUrl());return protocol.export(builder.buildInvokerChain(invoker, SERVICE_FILTER_KEY, CommonConstants.PROVIDER));
}

dubbo根据url利用spi机制获取到RegistryProtocol,最终调用到export方法,这里会完成服务的暴
漏与注册,服务暴漏就是下面这行代码做的事情,执行完这行代码之后完成服务注册。
ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker, providerUrl);

public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {URL registryUrl = getRegistryUrl(originInvoker);// url to export locallyURL providerUrl = getProviderUrl(originInvoker);final URL overrideSubscribeUrl = getSubscribedOverrideUrl(providerUrl);final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);Map<URL, NotifyListener> overrideListeners = getProviderConfigurationListener(providerUrl).getOverrideListeners();overrideListeners.put(registryUrl, overrideSubscribeListener);providerUrl = overrideUrlWithConfig(providerUrl, overrideSubscribeListener);//export invokerfinal ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker, providerUrl);// url to registryfinal Registry registry = getRegistry(registryUrl);final URL registeredProviderUrl = getUrlToRegistry(providerUrl, registryUrl);// decide if we need to delay publishboolean register = providerUrl.getParameter(REGISTER_KEY, true);if (register) {register(registry, registeredProviderUrl);}// register stated url on provider modelregisterStatedUrl(registryUrl, registeredProviderUrl, register);exporter.setRegisterUrl(registeredProviderUrl);exporter.setSubscribeUrl(overrideSubscribeUrl);if (!registry.isServiceDiscovery()) {// Deprecated! Subscribe to override rules in 2.6.x or before.registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);}notifyExport(exporter);//Ensure that a new exporter instance is returned every time exportreturn new DestroyableExporter<>(exporter);
}

由于我们配置的注册中心为zookeeper,所以Registry registry = getRegistry(registryUrl)获取到的就是ZookeeperRegistry

而register(registry, registeredProviderUrl)就是利用ZookeeperRegistry中的zkClient向zookeeper中写入一条数据:

public void doRegister(URL url) {try {checkDestroyed();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);}
}

此时的registeredProviderUrl就是要注册的接口的真实信息

dubbo://192.168.43.38:20880/org.apache.dubbo.demo.GreetingService?anyhost=true&application=dubbo-demo-annotation-provider&background=false&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=org.apache.dubbo.demo.GreetingService&metadata-type=remote&methods=hello&pid=21604&register-mode=interface&release=&side=provider&timestamp=1731941219264

二、应用级注册

一、应用级注册原理

上面已经分析过,如果一个应用中有N个Dubbo服务,那么每增加一个实例,就会向注册中心增加N条记录;反过来如果有M个实例,此时增加一个Dubbo服务,那么注册中心就会新增M条记录,注册中心的压力会随着应用实例和服务的增加而剧烈增加;

注册中心的数据越多,数据就变化的越频繁,比如修改服务的timeout,那么对于注册中心和应用都需要消耗资源用来处理数据变化,所以为了降低注册中心的压力,Dubbo3.0支持了应用级注册。而一旦采用应用级注册,最终注册中心的数据存储就变成为:

应用名:192.168.65.221:20880
应用名:192.168.65.222:20880

表示在注册中心中,只记录应用所对应的实例信息(IP+绑定的端口),这样只有一个应用的实例增加了,那么注册中心的数据才会增加,而不关心一个应用中到底有多少个Dubbo服务。

这样带来的好处就是,注册中心存储的数据变少了,注册中心中数据的变化频率变小了(那服务的配置如果发生了改变怎么办呢?后面会讲),并且使用应用级注册,使得 Dubbo3 能实现与异构微服务体系如Spring Cloud等在地址发现层面更容易互通, 为连通 Dubbo与其他微服务体系提供可行方案。

应用级注册带来了好处,但是对于Dubbo来说又出现了一些新的问题,比如:原本,服务消费者可以直接从注册中心就知道某个Dubbo服务的所有服务提供者以及相关的协议、ip、port、配置等信息,那现在注册中心上只有ip、port,那对于服务消费者而言:服务消费者怎么知道现在它要用的某个Dubbo服务,也就是某个接口对应的应用是哪个呢?

对于这个问题,在进行服务导出的过程中,会在Zookeeper中存一个映射关系,在服务导出的最后一步,在ServiceConfig的exported()方法中,会保存这个映射关系:

接口名:应用名

这个映射关系存在Zookeeper的/dubbo/mapping目录下,存了这个信息后,消费者就能根据接口
名找到所对应的应用名了。

消费者知道了要使用的Dubbo服务在哪个应用,那也就能从注册中心中根据应用名查到应用的所有实例信息(ip+port),也就是可以发送方法调用请求了,但是在真正发送请求之前,还得知道服务的配置信息,对于消费者而言,它得知道当前要调用的这个Dubbo服务支持什么协议、timeout是多少,那服务的配置信息从哪里获取呢?

之前的服务配置信息是直接从注册中心就可以获取到的,就是服务URL后面,但是现在不行了,现在需要从服务提供者的元数据服务获取,在应用启动过程中会进行服务导出和服务引入,然后就会暴露一个应用元数据服务,其实这个应用元数据服务就是一个Dubbo服务(Dubbo框架内置的,自己实现的),消费者可以调用这个服务来获取某个应用中所提供的所有Dubbo服务以及服务配置信息,这样就能知道服务的配置信息了。后面分析服务引入时,会进一步分析具体细节。

我们可以通过配置dubbo.application.register-mode设置为instance来控制服务为应用级注册。

不管是什么注册,都需要存数据到注册中心,而Dubbo3的源码实现中会根据所配置的注册中心生成两个URL(不是服务URL,可以理解为注册中心URL,用来访问注册中心的):

1、service-discovery-registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=dubbospringboot-
demoprovider&
dubbo=2.0.2&pid=13072&qos.enable=false&registry=zookeeper&timestamp=16517555016602、registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=dubbo-springboot-demoprovider&
dubbo=2.0.2&pid=13072&qos.enable=false&registry=zookeeper&timestamp=1651755501660

这两个URL只有schema不一样,一个是service-discovery-registry,一个是registry,而registry是
Dubbo3之前就存在的,也就代表接口级服务注册,而service-discovery-registry就表示应用级服务注册。

在服务注册相关的源码中,当调用RegistryProtocol的export()方法处理registry://时,会利用
ZookeeperRegistry把服务URL注册到Zookeeper中去,这个前面已经看到了,这就是接口级注册。

而类似,当调用RegistryProtocol的export()方法处理service-discovery-registry://时,会利用
ServiceDiscoveryRegistry来进行相关逻辑的处理,那是不是就是在这里把应用信息注册到注册中心去呢?并没有这么简单。

1、首先,不可能每导出一个服务就进行一次应用注册,太浪费了,应用注册只要做一次就行了
2、另外,如果一个应用支持了多个端口,那么应用注册时只要挑选其中一个端口作为实例端口就可以了(该端口只要能接收到数据就行)
3、前面提到,应用启动过程中要暴露应用元数据服务,所以在此处也还是要收集当前所暴露的服务配置信息,以提供给应用元数据服务

所以ServiceDiscoveryRegistry在注册一个服务URL时,并不会往注册中心存数据,而只是把服务URL存到到一个MetadataInfo对象中,MetadataInfo对象中就保存了当前应用中所有的Dubbo服务信息(服务名、支持的协议、绑定的端口、timeout等)

前面提到过,在应用启动的最后,才会进行应用级注册,而应用级注册就是当前的应用实例上相关的信息存入注册中心,包括:

1. 应用的名字
2. 获取应用元数据的方式
3. 当前实例的ip和port
4. 当前实例支持哪些协议以及对应的port

确定好实例信息后之后,就进行最终的应用注册了,就把实例信息存入注册中心的/services/应用名目录下:

可以看出services节点下存的是应用名,应用名的节点下存的是实例ip和实例port,而ip和port这个节点中的内容就是实例的一些基本信息。

额外,我们可以配置dubbo.metadata.storage-type,默认是local,可以通过配置改为remote:

dubbo.application.name=dubbo-springboot-demo-provider
dubbo.application.metadata-type=remote

这个配置其实跟应用元数据服务有关系:

1. 如果为local,那就会启用应用元数据服务,最终服务消费者就会调用元数据服务获取到应用元数据信息
2. 如果为remote,那就不会暴露应用元数据服务,那么服务消费者从元数据中心获取应用元数据呢?

元数据中心,它其实就是用来减轻注册中心的压力的,Dubbo会把服务信息完整的存一份到元数据中心,元数据中心也可以用Zookeeper来实现,在暴露完元数据服务之后,在注册实例信息到注册中心之前,就会把MetadataInfo存入元数据中心,比如:

节点内容为:

{"app": "dubbo-demo-annotation-provider","revision": "454d743e98b436191b6d836c12ce06a8","services": {"org.apache.dubbo.demo.DemoService:dubbo": {"name": "org.apache.dubbo.demo.DemoService","protocol": "dubbo","path": "org.apache.dubbo.demo.DemoService","params": {"side": "provider","release": "","methods": "sayHello,sayHelloAsync","deprecated": "false","dubbo": "2.0.2","interface": "org.apache.dubbo.demo.DemoService","service-name-mapping": "true","register-mode": "instance","generic": "false","metadata-type": "remote","application": "dubbo-demo-annotation-provider","background": "false","dynamic": "true","REGISTRY_CLUSTER": "registryConfig","anyhost": "true"}},"org.apache.dubbo.demo.GreetingService:dubbo": {"name": "org.apache.dubbo.demo.GreetingService","protocol": "dubbo","path": "org.apache.dubbo.demo.GreetingService","params": {"side": "provider","release": "","methods": "hello","deprecated": "false","dubbo": "2.0.2","interface": "org.apache.dubbo.demo.GreetingService","service-name-mapping": "true","register-mode": "instance","generic": "false","metadata-type": "remote","application": "dubbo-demo-annotation-provider","background": "false","dynamic": "true","REGISTRY_CLUSTER": "registryConfig","anyhost": "true"}}},"initiated": false
}

这里面就记录了当前实例上提供了哪些服务以及对应的协议。元数据中心和元数据服务提供的功能是一样的,都可以用来获取某个实例的MetadataInfo。

二、源码流程解析

还是来到doExportUrls方法中,可以看到此时url前缀内容为service-discovery-registry

然后来到registryProtocol中的export方法,此时获取到的为ServiceDiscoveryRegistry

那么最终会来到ServiceDiscoveryRegistry的doRegister方法中

public void doRegister(URL url) {// fixme, add registry-cluster is not necessary anymoreurl = addRegistryClusterKey(url);serviceDiscovery.register(url);
}

 最终将url信息进行封装添加到元数据信息中去

public void register(URL url) {metadataInfo.addService(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 metadataServiceInfo serviceInfo = new ServiceInfo(url, filters);this.services.put(serviceInfo.getMatchKey(), serviceInfo);// extract common instance level paramsextractInstanceParams(url, filters);if (exportedServiceURLs == null) {exportedServiceURLs = new ConcurrentSkipListMap<>();}addURL(exportedServiceURLs, url);updated.compareAndSet(false, true);
}

可以看到将服务url包装为了serviceInfo,存入到了services中

导出完某个Dubbo服务后,就会调用MetadataUtils.publishServiceDefinition把服务接口名:应用名存入元数据中心。

zk上的metadata信息如下:

{"parameters": {},"canonicalName": "org.apache.dubbo.demo.GreetingService","codeSource": "file:/E:/dubbo/dubbo-3.0/dubbo-demo/dubbo-demo-interface/target/classes/","methods": [{"name": "hello","parameterTypes": [],"returnType": "java.lang.String","annotations": []}],"types": [{"type": "java.lang.String"}],"annotations": []
}

服务导出后会调用serviceConfig.exported方法,最终会调用serviceNameMapping.map方法将接口名与应用名的映射关系设置到zk上

protected void exported() {exported = true;List<URL> exportedURLs = this.getExportedUrls();exportedURLs.forEach(url -> {if (url.getParameters().containsKey(SERVICE_NAME_MAPPING_KEY)) {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("Failed register interface application mapping for service " + url.getServiceKey());}} catch (Exception e) {logger.error("Failed register interface application mapping for service " + url.getServiceKey(), e);}}});onExported();
}

当导出了所有的服务后,会执行exportMetadataService(),将MetadataInfo存入元数据中心,并进行元数据服务的暴漏;然后执行registerServiceInstance将实例信息存入注册中心,完成应用注册

public void prepareApplicationInstance() {if (hasPreparedApplicationInstance.get()) {return;}if (isRegisterConsumerInstance()) {exportMetadataService();if (hasPreparedApplicationInstance.compareAndSet(false, true)) {// register the local ServiceInstance if requiredregisterServiceInstance();}}
}

实例信息注册内容,也就是services目录中的内容,格式为如下:
应用名:192.168.65.221:20880

至此,dubbo的应用级服务注册流程完毕。总结一下:
1. 在导出某个Dubbo服务URL时,会把服务URL存入MetadataInfo中
2. 导出完某个Dubbo服务后,就会把服务接口名:应用名存入元数据中心(可以用Zookeeper实现)
3. 导出所有服务后,完成服务引入后,判断要不要启动元数据服务,如果要就进行导出,固定使用Dubbo协议
5. 将MetadataInfo存入元数据中心
6. 确定当前实例信息(应用名、ip、port、endpoint),将实例信息存入注册中心,完成应用注册

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

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

相关文章

计算机网络-理论部分(二):应用层

网络应用体系结构 Client-Server客户-服务器体系结构&#xff1a;如Web&#xff0c;FTP&#xff0c;Telnet等Peer-Peer&#xff1a;点对点P2P结构&#xff0c;如BitTorrent 应用层协议定义了&#xff1a; 交换的报文类型&#xff0c;请求or响应报文类型的语法字段的含义如何…

麒麟时间同步搭建chrony服务器

搭建chrony服务器 在本例中&#xff0c;kyserver01&#xff08;172.16.200.10&#xff09;作为客户端&#xff0c;同步服务端时间&#xff1b;kyserver02&#xff08;172.16.200.11&#xff09;作为服务端&#xff0c;提供时间同步服务。 配置服务端&#xff0c;修改以下内容…

学习threejs,通过SkinnedMesh来创建骨骼和蒙皮动画

&#x1f468;‍⚕️ 主页&#xff1a; gis分享者 &#x1f468;‍⚕️ 感谢各位大佬 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍⚕️ 收录于专栏&#xff1a;threejs gis工程师 文章目录 一、&#x1f340;前言1.1 ☘️THREE.SkinnedMesh 蒙皮网格…

HarmonyOS NEXT应用元服务开发Intents Kit(意图框架服务)习惯推荐方案概述

一、习惯推荐是HarmonyOS学习用户的行为习惯后做出的主动预测推荐。 1.开发者将用户在应用/元服务内的使用行为向HarmonyOS共享&#xff0c;使得HarmonyOS可以基于共享的数据学习用户的行为习惯。 2.在HarmonyOS学习到用户的行为习惯后&#xff0c;会给用户推荐相应功能&#x…

华为防火墙技术基本概念学习笔记

1.防火墙概述 1.1防火墙与交换机、路由器对比 路由器与交换机的本质是转发&#xff0c;防火墙的本质是控制。 防火墙与路由器、交换机是有区别的。路由器用来连接不同的网络&#xff0c;通过路由协议保证互联互通&#xff0c;确保将报文转发到目的地;交换机则通常用来组建局域…

shell(5)字符串运算符和逻辑运算符

声明&#xff01; 学习视频来自B站up主 **泷羽sec** 有兴趣的师傅可以关注一下&#xff0c;如涉及侵权马上删除文章&#xff0c;笔记只是方便各位师傅的学习和探讨&#xff0c;文章所提到的网站以及内容&#xff0c;只做学习交流&#xff0c;其他均与本人以及泷羽sec团队无关&a…

帆软report参数栏宽度设置

最右边的竖杠被放大后在拉回来

Mysql-DQL语句

文章目录 DQL 语句简单查询查询表所有数据查询指定列 别名查询清除重复值查询结果参与运算 &#x1f3e1;作者主页&#xff1a;点击&#xff01; &#x1f916;Mysql专栏&#xff1a;点击&#xff01; ⏰️创作时间&#xff1a;2024年11月16日11点39分 DQL 语句 DQL 语句数据…

MySQL数据库3——函数与约束

一.函数 1.字符串函数 MySQL中内置了很多字符串函数&#xff0c;常用的几个如下&#xff1a; 使用方法&#xff1a; SELECT 函数名(参数);注意&#xff1a;MySQL中的索引值即下标都是从1开始的。 2.数值函数 常见的数值函数如下&#xff1a; 使用方法&#xff1a; SELECT…

Vue.js 插槽 Slots 实际应用 最近重构项目的时候遇到的...

前端开发中 插槽 Slots 是一个重要的概念 我们可以查看一下vue.js的官方文档 https://cn.vuejs.org/guide/components/slots 类似于连接通道一样 可以把核心代码逻辑搬到另外的地方 做一个引用 而原先的地方可能并不能这样书写 对于这个概念我在vue的官方文档里面找到了…

5G 现网信令参数学习(3) - RrcSetup(2)

前一篇&#xff1a;5G 现网信令参数学习(3) - RrcSetup(1) 目录 1. rlf-TimersAndConstants 2. spCellConfigDedicated 2.1 initialDownlinkBWP 2.1.1 pdcch-Config 2.1.1.1 controlResourceSetToAddModList 2.1.1.2 searchSpacesToAddModList 2.1.2 pdsch-Config 2.1…

MySQL(5)【数据类型 —— 字符串类型】

阅读导航 引言一、char&#x1f3af;基本语法&#x1f3af;使用示例 二、varchar&#x1f3af;基本语法&#x1f3af;使用示例 三、char 和 varchar 比较四、日期和时间类型1. 基本概念2. 使用示例 五、enum 和 set&#x1f3af;基本语法 引言 之前我们聊过MySQL中的数值类型&…

【蓝桥杯C/C++】翻转游戏:多种实现与解法解析

文章目录 &#x1f4af;题目&#x1f4af;问题分析解法一&#xff1a;减法法解法二&#xff1a;位运算解法解法三&#xff1a;逻辑非解法解法四&#xff1a;条件运算符解法解法五&#xff1a;数组映射法不同解法的比较 &#x1f4af;小结 &#x1f4af;题目 在蓝桥镇&#xff0…

深度学习之人脸检测

在目标检测领域可以划分为了人脸检测与通用目标检测&#xff0c;往往人脸这方面会有专门的算法&#xff08;包括人脸检测、人脸识别、人脸其他属性的识别等等&#xff09;&#xff0c;并且和通用目标检测&#xff08;识别&#xff09;会有一定的差别&#xff0c;着主要来源于人…

docker busybox作为initContainers

一、上传到私有仓储 docker pull busybox:1.33.1 docker tag busybox:1.33.1 192.168.31.185/public/busybox:1.33.1 docker push 192.168.31.185/public/busybox:1.33.1 --- apiVersion: apps/v1 kind: Deploymentspec:containers:- env:- name: ASPNETCORE_ENVIRONMENTvalue…

Java实现两数交换

文章目录 实现两数交换方法一、&#xff08;数组的方式进行交换&#xff09;方法二、&#xff08;对象的方式进行交换&#xff09;总结 实现两数交换 实现两数交换&#xff0c;没有办法通过直接传递数字达到交换的结果&#xff0c;定义的int型变量是被存储在栈空间上的&#xf…

GOLANG+VUE后台管理系统

1.截图 2.后端工程截图 3.前端工程截图

Axure PR 9 穿梭框 设计交互

​大家好&#xff0c;我是大明同学。 这期内容&#xff0c;我们将深入探讨Axure中穿梭筛选宽元件设计与交互技巧。 穿梭筛选框元件 创建穿梭筛选框所需的元件 左穿梭筛选框 1.打开一个新的 RP 文件并在画布上打开 Page 1。 2.现在画布上创建一个背景&#xff0c;在元件库中…

【东莞石碣】戴尔R740服务器维修raid硬盘问题

1&#xff1a;石碣某塑料工厂下午报修一台戴尔R740服务器硬盘故障&#xff0c;催的还比较着急。 2&#xff1a;工程师经过跟用户确认故障的问题以及故障服务器型号和故障硬盘型号&#xff0c;产品和配件确认好后&#xff0c;公司仓库确认有该款硬盘现货&#xff0c;DELL 12T S…

介绍一下strncpy(c基础)

strncpy是strcpy的进阶版&#xff0c;都是把一个字符串赋值给另一个字符串。但不同的是strncpy可以选择复制几个字符&#xff08;可以完全替代strcpy&#xff09; 链接介绍一下strcpy函数&#xff08;c基础&#xff09;-CSDN博客 格式 #include<string.h> strncpy(ar…