当前位置: 首页 > news >正文

Dubbo的服务暴漏与服务发现源码详解

服务暴漏

如果配置需要刷新则根据配置优先级刷新服务配置

如果服务已经导出,则直接返回

是否异步导出(全局或者服务级别配置了异步,则需要异步导出服务)

服务暴漏入口DefaultModuleDeployer#exportServices

private void exportServices() {//从服务配置缓存查询缓存的所有服务配置,然后挨个服务发布for (ServiceConfigBase sc : configManager.getServices()) {exportServiceInternal(sc);}
}
private void exportServiceInternal(ServiceConfigBase sc) {ServiceConfig<?> serviceConfig = (ServiceConfig<?>) sc;//如果配置需要刷新则根据配置优先级刷新服务配置if (!serviceConfig.isRefreshed()) {serviceConfig.refresh();}//服务已经暴漏过就返回if (sc.isExported()) {return;}//是否异步导出(全局或者服务级别配置了异步,则需要异步导出服务)if (exportAsync || sc.shouldExportAsync()) {// 使用线程池导出服务ExecutorService executor = executorRepository.getServiceExportExecutor();CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {try {if (!sc.isExported()) {sc.export();exportedServices.add(sc);}} catch (Throwable t) {logger.error(CONFIG_FAILED_EXPORT_SERVICE, "", "", "Failed to async export service config: " + getIdentifier() + " , catch error : " + t.getMessage(), t);}}, executor);asyncExportingFutures.add(future);} else {//同步导出if (!sc.isExported()) {sc.export();exportedServices.add(sc);}}
}

ServiceConfig#export()

ServiceConfigBase 的模板方法

@Override
public void export() {if (this.exported) {return;}// ensure start module, compatible with old api usagegetScopeModel().getDeployer().start();synchronized (this) {//DCLif (this.exported) {return;}if (!this.isRefreshed()) {this.refresh();}//服务导出配置配置为false则不导出if (this.shouldExport()) {this.init();if (shouldDelay()) {//延迟暴漏doDelayExport();} else {//导出服务doExport();}}}
}

ServiceConfig#init

public void init() {if (this.initialized.compareAndSet(false, true)) {// load ServiceListeners from extension// 加载服务监听器扩展ExtensionLoader<ServiceListener> extensionLoader = this.getExtensionLoader(ServiceListener.class);this.serviceListeners.addAll(extensionLoader.getSupportedExtensionInstances());}//provider的配置传递给元数据配置对象initServiceMetadata(provider);// 元数据设置接口和引用serviceMetadata.setServiceType(getInterfaceClass());serviceMetadata.setTarget(getRef());//元数据key格式 group/服务接口:版本号serviceMetadata.generateServiceKey();
}

ServiceConfig#doExport

protected synchronized void doExport() {if (unexported) {throw new IllegalStateException("The service " + interfaceClass.getName() + " has already unexported!");}if (exported) {return;}//服务路径,为空则设计接口名if (StringUtils.isEmpty(path)) {path = interfaceName;}// 导出URLdoExportUrls();exported();
}

ServiceConfig#doExportUrls

导出URL了逻辑

private void doExportUrls() {ModuleServiceRepository repository = getScopeModel().getServiceRepository();ServiceDescriptor serviceDescriptor;//ref 是否是 ServerServicefinal boolean serverService = ref instanceof ServerService;if (serverService) {serviceDescriptor = ((ServerService) ref).getServiceDescriptor();repository.registerService(serviceDescriptor);} else {//注册服务,解析服务接口将服务方法等描述信息存放在了服务存储//ModuleServiceRepository类型对象的成员变量services中serviceDescriptor = repository.registerService(getInterfaceClass());}//provider领域模型 ,封装了一些提供者需要的基本属性,同时内部解析分装方法信息providerModel = new ProviderModel(serviceMetadata.getServiceKey(),// 服务实现类ref,// 服务描述,包含了服务接口的方法信息serviceDescriptor,//当前的模型getScopeModel(),// 元数据对象,接口加载器serviceMetadata, interfaceClassLoader);// Compatible with dependencies on ServiceModel#getServiceConfig(), and will be removed in a future versionproviderModel.setConfig(this);providerModel.setDestroyRunner(getDestroyRunner());repository.registerProvider(providerModel);// 获取配置的注册中心列表,同时转换成URL// 核心 根据是否双注册配置返回URL信息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);// stub service will use generated service nameif (!serverService) {// In case user specified path, register service one more time to map it to path.// 存储服务接口信息repository.registerService(pathKey, interfaceClass);}// 核心  根据协议导出到配置到注册中心doExportUrlsFor1Protocol(protocolConfig, registryURLs);}providerModel.setServiceUrls(urls);
}

服务注册

双注册配置

dubbo.application.register-mode=all

默认值为all代表应用级注册和接口级注册,配置值解释:

  • all 双注册
  • instance 应用级注册 service-discovery-registry:……
  • interface 接口级注册 registry:……
List<URL> registryURLs = ConfigValidationUtils.loadRegistries(this, true);

loadRegistries

public static List<URL> loadRegistries(AbstractInterfaceConfig interfaceConfig, boolean provider) {// check && override if necessaryList<URL> registryList = new ArrayList<>();ApplicationConfig application = interfaceConfig.getApplication();// 获取到接口配置注册的地址List<RegistryConfig> registries = interfaceConfig.getRegistries();if (CollectionUtils.isNotEmpty(registries)) {for (RegistryConfig config : registries) {// try to refresh registry in case it is set directly by user using config.setRegistries()if (!config.isRefreshed()) {config.refresh();}String address = config.getAddress();if (StringUtils.isEmpty(address)) {address = ANYHOST_VALUE;}if (!RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(address)) {Map<String, String> map = new HashMap<String, String>();AbstractConfig.appendParameters(map, application);AbstractConfig.appendParameters(map, config);map.put(PATH_KEY, RegistryService.class.getName());AbstractInterfaceConfig.appendRuntimeParameters(map);if (!map.containsKey(PROTOCOL_KEY)) {map.put(PROTOCOL_KEY, DUBBO_PROTOCOL);}List<URL> urls = UrlUtils.parseURLs(address, map);for (URL url : urls) {url = URLBuilder.from(url).addParameter(REGISTRY_KEY, url.getProtocol()).setProtocol(extractRegistryType(url)).setScopeModel(interfaceConfig.getScopeModel()).build();// provider delay register state will be checked in RegistryProtocol#exportif (provider || url.getParameter(SUBSCRIBE_KEY, true)) {registryList.add(url);}}}}}return genCompatibleRegistries(interfaceConfig.getScopeModel(), registryList, provider);
}

genCompatibleRegistries

private static List<URL> genCompatibleRegistries(ScopeModel scopeModel, List<URL> registryList, boolean provider) {List<URL> result = new ArrayList<>(registryList.size());// 每个注册中心增加服务注册中心地址的配置registryList.forEach(registryURL -> {if (provider) {// for registries enabled service discovery, automatically register interface compatible addresses.String registerMode;//注册协议配置了service-discovery-registry 走这个逻辑if (SERVICE_REGISTRY_PROTOCOL.equals(registryURL.getProtocol())) {registerMode = registryURL.getParameter(REGISTER_MODE_KEY, ConfigurationUtils.getCachedDynamicProperty(scopeModel, DUBBO_REGISTER_MODE_DEFAULT_KEY, DEFAULT_REGISTER_MODE_INSTANCE));if (!isValidRegisterMode(registerMode)) {registerMode = DEFAULT_REGISTER_MODE_INSTANCE;}// 添加应用级别配置result.add(registryURL);// 判断是否添加接口级别注册if (DEFAULT_REGISTER_MODE_ALL.equalsIgnoreCase(registerMode)&& registryNotExists(registryURL, registryList, REGISTRY_PROTOCOL)) {URL interfaceCompatibleRegistryURL = URLBuilder.from(registryURL).setProtocol(REGISTRY_PROTOCOL).removeParameter(REGISTRY_TYPE_KEY).build();result.add(interfaceCompatibleRegistryURL);}} else {//双注册模式配置查询 对应参数为dubbo.application.register-mode 默认值为allregisterMode = registryURL.getParameter(REGISTER_MODE_KEY, ConfigurationUtils.getCachedDynamicProperty(scopeModel, DUBBO_REGISTER_MODE_DEFAULT_KEY, DEFAULT_REGISTER_MODE_ALL));if (!isValidRegisterMode(registerMode)) {registerMode = DEFAULT_REGISTER_MODE_INTERFACE;}//如果满足应用级别注册,添加应用级别注册if ((DEFAULT_REGISTER_MODE_INSTANCE.equalsIgnoreCase(registerMode) || DEFAULT_REGISTER_MODE_ALL.equalsIgnoreCase(registerMode))&& registryNotExists(registryURL, registryList, SERVICE_REGISTRY_PROTOCOL)) {URL serviceDiscoveryRegistryURL = URLBuilder.from(registryURL).setProtocol(SERVICE_REGISTRY_PROTOCOL).removeParameter(REGISTRY_TYPE_KEY).build();result.add(serviceDiscoveryRegistryURL);}// 如果满足接口注册配置,添加接口级别注册if (DEFAULT_REGISTER_MODE_INTERFACE.equalsIgnoreCase(registerMode) || DEFAULT_REGISTER_MODE_ALL.equalsIgnoreCase(registerMode)) {result.add(registryURL);}}FrameworkStatusReportService reportService = ScopeModelUtil.getApplicationModel(scopeModel).getBeanFactory().getBean(FrameworkStatusReportService.class);reportService.reportRegistrationStatus(reportService.createRegistrationReport(registerMode));} else {result.add(registryURL);}});return result;
}

ServiceConfig#doExportUrlsFor1Protocol(protocolConfig, registryURLs);

导出服务配置到本地和注册中心

private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {//生成协议配置Map<String, String> map = buildAttributes(protocolConfig);// remove null key and null valuemap.keySet().removeIf(key -> StringUtils.isEmpty(key) || StringUtils.isEmpty(map.get(key)));// init serviceMetadata attachments// 协议配置放到元数据 attachments 中serviceMetadata.getAttachments().putAll(map);// 协议配置和默认的协议配置转成URLURL url = buildUrl(protocolConfig, map);// 导出URLexportUrl(url, registryURLs);
}

导出URL ServiceConfig#exportUrl(url, registryURLs)

private void exportUrl(URL url, List<URL> registryURLs) {String scope = url.getParameter(SCOPE_KEY);// don't export when none is configuredif (!SCOPE_NONE.equalsIgnoreCase(scope)) {// export to local if the config is not remote (export to remote only when config is remote)// 没有明确指定远程导出,开启本地导出if (!SCOPE_REMOTE.equalsIgnoreCase(scope)) {exportLocal(url);}// 没右明确指定本地导出,开启远程导出// export to remote if the config is not local (export to local only when config is local)if (!SCOPE_LOCAL.equalsIgnoreCase(scope)) {// export to extra protocol is used in remote exportString extProtocol = url.getParameter("ext.protocol", "");List<String> protocols = new ArrayList<>();// export original urlurl = 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 (!extProtocol.equals("")) {String[] extProtocols = extProtocol.split(",", -1);protocols.addAll(Arrays.asList(extProtocols));}// export extra protocolsfor(String protocol : protocols) {if(!protocol.equals("")){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);
}

导出服务到本地

核心方法 exportLocal(url); 本地调用使用了 injvm 协议,是一个伪协议,它不开启端口,不发起远程调用,只在 JVM 内直接关联,但执行了 Dubbo 的 Filter 链。

exportLocal 中主要更新了URL (协议转换成了injvm,host改为127.0.0.1 ,port 0)然后调用doExportUrl

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 URL exportRemote(URL url, List<URL> registryURLs) {if (CollectionUtils.isNotEmpty(registryURLs)) {// 遍历所有注册中心地址,挨个注册for (URL registryURL : registryURLs) {//如果是应用级注册service-discovery-registry 为协议URL 参数service-name-mapping为trueif (SERVICE_REGISTRY_PROTOCOL.equals(registryURL.getProtocol())) {url = url.addParameterIfAbsent(SERVICE_NAME_MAPPING_KEY, "true");}//if protocol is only injvm ,not registerif (LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {continue;}// url 添加动态配置 dynamicurl = 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 invokerString proxy = url.getParameter(PROXY_KEY);if (StringUtils.isNotEmpty(proxy)) {registryURL = registryURL.addParameter(PROXY_KEY, proxy);}// log infoif (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);}}// 核心导出URLdoExportUrl(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;
}

ServiceConfig#doExportUrl

导出到本地 和 导出到注册中心公用了 doExportUrl 方法

private void doExportUrl(URL url, boolean withMetaData) {//adaptor 扩展类型处理,javassist 获取 代理invokerInvoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);if (withMetaData) {//远程导出,元数据也是用invoker包装一下invoker = new DelegateProviderMetaDataInvoker(invoker, this);}Exporter<?> exporter = protocolSPI.export(invoker);exporters.add(exporter);
}

JavassistProxyFactory类型的getInvoker方法

ProxyFactory 使用了dubbo的SPI机制,默认加载 javassist 的 spi对应类使用

public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {try {// TODO Wrapper cannot handle this scenario correctly: the classname contains '$'//创建实际服务提供者的代理类型,代理类型后缀为DubboWrapfinal Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);//返回一个 继承 AbstractProxyInvoker 并重写 doInvoke 的类,调用wrapper.invokeMethodreturn new AbstractProxyInvoker<T>(proxy, type, url) {@Overrideprotected Object doInvoke(T proxy, String methodName,Class<?>[] parameterTypes,Object[] arguments) throws Throwable {return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);}};} catch (Throwable fromJavassist) {// log info and exception}
}

使用协议导出 export

Exporter<?> exporter = protocolSPI.export(invoker);

protocolSPI使用的DubboSPI机制,通过spi的wapper机制,可以知道这里面的调用会有一个调用链,每一个export都会经过如下调用链

  1. 协议序列化机制 ProtocolSerializationWrapper
  2. 协议过滤器Wrapper ProtocolFilterWrapper
  3. 协议监听器Wrapper ProtocolListenerWrapper
  4. QOS的协议Wrapper QosProtocolWrapper (运维相关使用)
  5. 具体协议的Protocol 例如 InjvmProtocol ,DubboProtocol

调用链中通过**UrlUtils.isRegistry(invoker.getUrl())**来判断是否是应用级别注册 (service-discovery-registry)

img

ProtocolSerializationWrapper

@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {//这里主要逻辑是将服务提供者url添加到服务存储仓库中getFrameworkModel(invoker.getUrl().getScopeModel()).getServiceRepository().registerProviderUrl(invoker.getUrl());return protocol.export(invoker);
}

ProtocolFilterWrapper

@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {//注册中心的协议导出直接执行if (UrlUtils.isRegistry(invoker.getUrl())) {//服务发现service-discovery-registry的导出会走这个逻辑return protocol.export(invoker);}//过滤器调用链FilterChainBuilder的扩展对象查询FilterChainBuilder builder = getFilterChainBuilder(invoker.getUrl());//这里分为2步 生成过滤器调用链 然后使用链表中的节点调用  这里值查询provider类型的过滤器return protocol.export(builder.buildInvokerChain(invoker, SERVICE_FILTER_KEY, CommonConstants.PROVIDER));
}

ProtocolListenerWrapper

@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {//注册中心地址则直接导出if (UrlUtils.isRegistry(invoker.getUrl())) {//服务发现service-discovery-registry的导出会走这个逻辑return protocol.export(invoker);}// 先导出对象 再创建过滤器包装对象 执行监听器逻辑return new ListenerExporterWrapper<T>(protocol.export(invoker),Collections.unmodifiableList(ScopeModelUtil.getExtensionLoader(ExporterListener.class, invoker.getUrl().getScopeModel()).getActivateExtension(invoker.getUrl(), EXPORTER_LISTENER_KEY)));
}

QosProtocolWrapper

@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {//注册中心导出的时候开启QOS 默认端口22222if (UrlUtils.isRegistry(invoker.getUrl())) {startQosServer(invoker.getUrl());return protocol.export(invoker);}return protocol.export(invoker);
}
startQosServer(invoker.getUrl());

启动Qos服务,使用的Netty模型 NioEventLoopGroup

导出本地和注册中心的Protocol区别

InjvmProtocol 的导出方法

本地使用协议 injvm

@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {return new InjvmExporter<T>(invoker, invoker.getUrl().getServiceKey(), exporterMap);
}

RegistryProtocol

RegistryProtocol 的 export 包含了一下四部分

  • 根据最新配置覆盖配置
  • 导出协议端口开启TCP服务
  • 注册到注册中心
  • 通知服务启动了
@Override
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {URL registryUrl = getRegistryUrl(originInvoker);// url to export locallyURL providerUrl = getProviderUrl(originInvoker);// Subscribe the override data// FIXME When the provider subscribes, it will affect the scene : a certain JVM exposes the service and call//  the same service. Because the subscribed is cached key with the name of the service, it causes the//  subscription information to cover.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 invoker//本地导出协议开启端口final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker, providerUrl);// url to registry// 通过URL获取注册中心的Registry操作对象final Registry registry = getRegistry(registryUrl);//转换成向注册中心注册的URLfinal 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);//是否向注册中心注册if (register) {//这里有两种情况 //接口级注册会将接口级服务提供者数据直接注册到Zookeper上面,//服务发现(应用级注册)这里仅仅会将注册数据转换为服务元数据等后面来发布元数据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);
}

doLocalExport本地导出协议开启端口

private <T> ExporterChangeableWrapper<T> doLocalExport(final Invoker<T> originInvoker, URL providerUrl) {String key = getCacheKey(originInvoker);return (ExporterChangeableWrapper<T>) bounds.computeIfAbsent(key, s -> {Invoker<?> invokerDelegate = new InvokerDelegate<>(originInvoker, providerUrl);//protoco对象是dubbo自动生成的适配器对象protocol$Adaptive 适配器对象会根据当前协议的参数来查询具体的协议扩展对象return new ExporterChangeableWrapper<>((Exporter<T>) protocol.export(invokerDelegate), originInvoker);});
}

protocol.export()也会经过调用链,最后一个Protocol是DubboProtocol

DubboProtocol

@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {checkDestroyed();// 从invoker 获取URLURL url = invoker.getUrl();// export service.String key = serviceKey(url);// 创建导出需要的ExpoterDubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);//export a stub service for dispatching eventboolean isStubSupportEvent = url.getParameter(STUB_EVENT_KEY, DEFAULT_STUB_EVENT);boolean isCallbackService = url.getParameter(IS_CALLBACK_SERVICE, false);if (isStubSupportEvent && !isCallbackService) {String stubServiceMethods = url.getParameter(STUB_EVENT_METHODS_KEY);if (stubServiceMethods == null || stubServiceMethods.length() == 0) {if (logger.isWarnEnabled()) {logger.warn(PROTOCOL_UNSUPPORTED, "", "", "consumer [" + url.getParameter(INTERFACE_KEY) +"], has set stub proxy support event ,but no stub methods founded.");}}}//创建服务,开启服务端口openServer(url);optimizeSerialization(url);return exporter;
}
openServer开启服务端口

RPC协议的TCP通信

private void openServer(URL url) {checkDestroyed();// find server.String key = url.getAddress();// client can export a service which only for server to invokeboolean isServer = url.getParameter(IS_SERVER_KEY, true);if (isServer) {ProtocolServer server = serverMap.get(key);if (server == null) {synchronized (this) {server = serverMap.get(key);if (server == null) {serverMap.put(key, createServer(url));return;}}}// server supports reset, use together with overrideserver.reset(url);}
}
createServer 创建协议服务

为地址创建协议服务

private ProtocolServer createServer(URL url) {url = URLBuilder.from(url)// send readonly event when server closes, it's enabled by default.addParameterIfAbsent(CHANNEL_READONLYEVENT_SENT_KEY, Boolean.TRUE.toString())// enable heartbeat by default.addParameterIfAbsent(HEARTBEAT_KEY, String.valueOf(DEFAULT_HEARTBEAT)).addParameter(CODEC_KEY, DubboCodec.NAME).build();// 服务端使用的网络 默认是nettyString transporter = url.getParameter(SERVER_KEY, DEFAULT_REMOTING_SERVER);if (StringUtils.isNotEmpty(transporter) && !url.getOrDefaultFrameworkModel().getExtensionLoader(Transporter.class).hasExtension(transporter)) {throw new RpcException("Unsupported server type: " + transporter + ", url: " + url);}//交换器ExchangeServer server;try {// 创建交换器server = Exchangers.bind(url, requestHandler);} catch (RemotingException e) {throw new RpcException("Fail to start server(url: " + url + ") " + e.getMessage(), e);}transporter = url.getParameter(CLIENT_KEY);if (StringUtils.isNotEmpty(transporter) && !url.getOrDefaultFrameworkModel().getExtensionLoader(Transporter.class).hasExtension(transporter)) {throw new RpcException("Unsupported client type: " + transporter);}DubboProtocolServer protocolServer = new DubboProtocolServer(server);loadServerProperties(protocolServer);return protocolServer;
}

相关文章:

Dubbo的服务暴漏与服务发现源码详解

服务暴漏 如果配置需要刷新则根据配置优先级刷新服务配置 如果服务已经导出&#xff0c;则直接返回 是否异步导出&#xff08;全局或者服务级别配置了异步&#xff0c;则需要异步导出服务&#xff09; 服务暴漏入口DefaultModuleDeployer#exportServices private void exp…...

Python 的IDE——PyCharm

IDE介绍与安装 介绍 集成开发环境&#xff08;IDE&#xff09; 集成开发环境(IDE,integrated Development Environment) —— 集成开发软件需要的所有工具&#xff0c;一般包括以下工具&#xff1a; 图形用户界面 代码编辑器(支持代码补全、自动缩进) 编译器/解释器 调试器…...

01 C语言使用链表实现队列(Queue、FIFO)模块

01 C语言使用链表实现队列&#xff08;Queue、FIFO&#xff09;模块 作者将狼才鲸创建日期2023-03-08Gitee源码仓库地址&#xff1a;C语言使用链表实现队列&#xff08;Queue、FIFO&#xff09;模块 Linux原生的队列KFIFO一次只能操作一个队列&#xff0c;操作变长元素时&…...

2.2操作系统-进程管理:前趋图、前趋图与PV操作

2.1操作系统-进程管理&#xff1a;前趋图\前趋图与PV操作前趋图前趋图与PV操作练习前趋图与PV操作&#xff0c;一般出现了&#xff0c;分值在2~3分左右&#xff0c;技巧性很强。 前趋图 前趋图是为了描述一个程序的各部分间的依赖关系&#xff0c;或者是一个大的计算的各个子…...

凤凰游攻略

凤凰游攻略1 装备&#x1f4e6;1.1 证件1.2 日常用品1.3 药品1.4 衣物1.5 洗漱用品2 交通&#x1f697;3 住宿&#x1f3e0;4 美食&#x1f355;5 拍照&#x1f4f7;5.1 租苗族服5.1.1 单租服装5.1.2 服装化妆5.2 一条龙旅拍6 路线&#x1f5fa;️景点&#x1f3d9;️7 注意⚠️…...

Nginx 高可用方案

准备工作 10.10.4.5 10.10.4.6 VIP&#xff1a;10.10.4.10 两台虚拟机。安装好Nginx 安装Nginx 更新yum源文件&#xff1a; rpm -ivh http://nginx.org/packages/centos/7/noarch/RPMS/nginx-release-centos-7-0.el7.ngx.noarch.rpm wget -O /etc/yum.repos.d/CentOS-Ba…...

Linux基本指令

文章目录 常用Linux命令常见Linux指令 1、ls指令 语法&#xff1a;ls [选项][目录或文件] 功能&#xff1a;对于目录&#xff0c;该命令列出该目录下的所有子目录与文件。对于文件&#xff0c;将列出文件名以及其他信息。常用选项&#xff1a; -a 列出目录下的所有文件&#xf…...

Linux系统基础命令(二)

一、浏览和切换目录 ls命令&#xff1a;列出文件和目录&#xff0c;主要用于列出文件和目录 CentOS的终端默认是有颜色标注的。一般来说&#xff1a;蓝色--->目录&#xff1b;绿色-->可执行文件&#xff1b;红色--->压缩文件&#xff1b;浅蓝色--->链接文件&#…...

【C++】C++11——简介|列表初始|简化声明|nullptr与范围for|STL中的变化

文章目录一、C11简介二、列表初始化三、简化声明四、nullptr与范围for五、STL中一些变化一、C11简介 在2003年C标准委员会曾经提交了一份技术勘误表(简称TC1)&#xff0c;使得C03这个名字已经取代了C98称为C11之前的最新C标准名称。不过由于TC1主要是对C98标准中的漏洞进行修复…...

Python -- 函数

文章目录1、一个简单的函数2、多参数函数3、返回值3.1、简单的返回3.2、返回列表和字典4、传入列表5、传入任意数量的实参5.1、以元组和字典的形式5.2、形参的排列顺序6、将函数储存在模块中1、一个简单的函数 函数用关键字def来定义&#xff0c;传参时不用指定参数类型 para&…...

Pytorch中utils.data 与torchvision简介

Pytorch中utils.data 与torchvision简介1 数据处理工具概述2 utils.data简介3 torchvision简介3.1 transforms3.2 ImageFolder1 数据处理工具概述 Pytorch涉及数据处理&#xff08;数据装载、数据预处理、数据增强等&#xff09;主要工具包及相互关系如下图所示&#xff0c;主…...

学习 Python 之 Pygame 开发魂斗罗(十)

学习 Python 之 Pygame 开发魂斗罗&#xff08;十&#xff09;继续编写魂斗罗1. 解决敌人不开火的问题2. 创建爆炸效果类3. 为敌人跳入河中增加爆炸效果4. 玩家击中敌人继续编写魂斗罗 在上次的博客学习 Python 之 Pygame 开发魂斗罗&#xff08;九&#xff09;中&#xff0c;…...

Keepalive+LVS群集部署

KeepaliveLVS群集部署一、Keepalive概述1、什么是Keepalive2、Keepalive工作原理3、Keepalive主要模块及作用4、Keepalived 服务重要功能&#xff08;1&#xff09;管理 LVS 负载均衡软件&#xff08;2&#xff09;支持故障自动切换&#xff08;3&#xff09;实现 LVS 负载调度…...

数组、指针总结【面试题】

文章目录0. 补充知识数组笔试题1. 一维数组1.1 字符数组1.1.1 sizeof1.1.2 strlen1.2 二维数组2. 指针笔试题0. 补充知识 在进入数组与指针的练习时&#xff0c;我们先来复习以下以下的知识点&#xff0c;这可以帮助我们更好的理解下面练习 数组是一组能存放相同类型的类型的元…...

七色电子标签

机种名 电子会议桌牌 型号 ESL_7color_7.3_D 外观尺寸 176.2x137.15x80mm 产品重量 268g 可视区域 163.297.92mm 外观颜色 银色 供电方式 锂电池供电2300mAh&#xff08;Type-C 接口可充电&#xff09; 显示技术 E-INK电子纸&#xff0c;双屏 像素 800x480 像…...

大数据是什么?发展前景怎么样

关于大数据的解释&#xff0c;比较官方的定义是指无法在一定时间范围内用常规软件工具进行捕捉、管理和处理的数据集合&#xff0c;是需要新处理模式才能具有更强的决策力、洞察发现力和流程优化能力的海量、高增长率和多样化的信息资产。简单来说&#xff0c;大数据就是结构化…...

MYSQL必知必会 | 查询相关

汇总数据 聚集函数 有时只需要汇总数据&#xff0c;并不需要把数据实际检索出来&#xff0c;所以MySql提供了专门的函数 聚集函数&#xff1a;运行在行组上&#xff0c;计算和返回单个值的函数 函数说明AVG()返回某列平均值COUNT()返回某列的行数MAX()返回某列最大值MIN()返…...

Java学习环境一站说明(保姆级详细教学)

1.Java开发环境搭建官网下载www.oracle.com2.安装注意&#xff1a;1.选择安装位置时尽量不要安装到C盘&#xff0c;路径中不要有空格以及中文的存在2.开发人员安装的jdk中包含了jre&#xff0c;所以不需要单独安装jre3.环境变量配置打开高级系统设置2.点击环境变量3.在系统变量…...

05-Oracle中的对象(视图,索引,同义词,系列)

本章主要内容&#xff1a; 1.视图管理&#xff1a;视图新增&#xff0c;修改&#xff0c;删除&#xff1b; 2.索引管理&#xff1a;索引目的&#xff0c;创建&#xff0c;修改&#xff0c;删除&#xff1b; 3.同义词管理&#xff1a;同义词的作用&#xff0c;创建&#xff0…...

如何通过websoket实现即时通讯+断线重连?

本篇博客只是一个demo&#xff0c;具体应用还要结合项目实际情况&#xff0c;以下是目录结构&#xff1a; 1.首先通过express搭建一个本地服务器 npm install express 2.在serve.js中自定义测试数据 const express require(express); const app express(); const http req…...

大话软工笔记—需求分析概述

需求分析&#xff0c;就是要对需求调研收集到的资料信息逐个地进行拆分、研究&#xff0c;从大量的不确定“需求”中确定出哪些需求最终要转换为确定的“功能需求”。 需求分析的作用非常重要&#xff0c;后续设计的依据主要来自于需求分析的成果&#xff0c;包括: 项目的目的…...

Java 8 Stream API 入门到实践详解

一、告别 for 循环&#xff01; 传统痛点&#xff1a; Java 8 之前&#xff0c;集合操作离不开冗长的 for 循环和匿名类。例如&#xff0c;过滤列表中的偶数&#xff1a; List<Integer> list Arrays.asList(1, 2, 3, 4, 5); List<Integer> evens new ArrayList…...

STM32F4基本定时器使用和原理详解

STM32F4基本定时器使用和原理详解 前言如何确定定时器挂载在哪条时钟线上配置及使用方法参数配置PrescalerCounter ModeCounter Periodauto-reload preloadTrigger Event Selection 中断配置生成的代码及使用方法初始化代码基本定时器触发DCA或者ADC的代码讲解中断代码定时启动…...

什么是库存周转?如何用进销存系统提高库存周转率?

你可能听说过这样一句话&#xff1a; “利润不是赚出来的&#xff0c;是管出来的。” 尤其是在制造业、批发零售、电商这类“货堆成山”的行业&#xff0c;很多企业看着销售不错&#xff0c;账上却没钱、利润也不见了&#xff0c;一翻库存才发现&#xff1a; 一堆卖不动的旧货…...

C++ 基础特性深度解析

目录 引言 一、命名空间&#xff08;namespace&#xff09; C 中的命名空间​ 与 C 语言的对比​ 二、缺省参数​ C 中的缺省参数​ 与 C 语言的对比​ 三、引用&#xff08;reference&#xff09;​ C 中的引用​ 与 C 语言的对比​ 四、inline&#xff08;内联函数…...

css3笔记 (1) 自用

outline: none 用于移除元素获得焦点时默认的轮廓线 broder:0 用于移除边框 font-size&#xff1a;0 用于设置字体不显示 list-style: none 消除<li> 标签默认样式 margin: xx auto 版心居中 width:100% 通栏 vertical-align 作用于行内元素 / 表格单元格&#xff…...

优选算法第十二讲:队列 + 宽搜 优先级队列

优选算法第十二讲&#xff1a;队列 宽搜 && 优先级队列 1.N叉树的层序遍历2.二叉树的锯齿型层序遍历3.二叉树最大宽度4.在每个树行中找最大值5.优先级队列 -- 最后一块石头的重量6.数据流中的第K大元素7.前K个高频单词8.数据流的中位数 1.N叉树的层序遍历 2.二叉树的锯…...

【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的“no matching...“系列算法协商失败问题

【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的"no matching..."系列算法协商失败问题 摘要&#xff1a; 近期&#xff0c;在使用较新版本的OpenSSH客户端连接老旧SSH服务器时&#xff0c;会遇到 "no matching key exchange method found"​, "n…...

[ACTF2020 新生赛]Include 1(php://filter伪协议)

题目 做法 启动靶机&#xff0c;点进去 点进去 查看URL&#xff0c;有 ?fileflag.php说明存在文件包含&#xff0c;原理是php://filter 协议 当它与包含函数结合时&#xff0c;php://filter流会被当作php文件执行。 用php://filter加编码&#xff0c;能让PHP把文件内容…...

Oracle11g安装包

Oracle 11g安装包 适用于windows系统&#xff0c;64位 下载路径 oracle 11g 安装包...