Dubbo源码解析-Provider服务暴露Export源码解析
上篇我们介绍了ServiceBean初始化和依赖注入过程,地址如下
Dubbo源码-Provider服务端ServiceBean初始化和属性注入-CSDN博客
本文主要针Dubbo服务端服务Export过程,从dubbo源码角度进行解析。
Dubbo 服务端暴露细节流程比较长,也是面试过程中比较常问的技术问题,大家可以好好仔细读一下本文。有疑问欢迎留言。
接着说明,读Dubbo源码最好是先对Spring源码有一定的了解。如果大家需要,我也可以针对Spring框架做一系列源码的解读专栏。
不过不用担心,如果需要Spring的源码知识,文章中也会进行Spring源码铺垫介绍的。
如果内容中有没描述清楚的,或者大家在阅读源代码有疑问的,欢迎留言,看到就会及时回复。
为了更清楚的分析解释源码,源代码中部分不重要的内容可能会删减,保留重要内容方便大家理解。
主要内容
- Dubbo中Provider服务Export源码解析
服务Export源码解析
我们都知道服务端在启动的时候,会将自己的服务地址注册到注册中心,那么具体的过程和细节是怎么样的呢?接下来,我们从大概流程和细节流程以及源代码角度进行说明和分析。
为什么有流程总结这一部分,因为好多同学为了应付面试或者觉得细节流程太繁琐或者复杂,只想弄清楚相对简练的过程。所以对服务暴露的细节流程做了一个相对笼统的总结,方便大家理解大概过程,以及应对面试。
Spring知识铺垫
- ServiceBean继承ApplicationListener<ContextRefreshedEvent>,通过监听事件。实现服务发布
- Spring容器启动时,registerListeners()收集上下文中即成继承ApplicationListener的类
- Spring容器启动前完成之后,广播事件finishRefresh()->publishEvent(new ContextRefreshedEvent(this))
- Spring启动之后,触发ServiceBean的onApplicationEvent()事件,调用export()完成服务暴露
流程总结
服务暴露的核心流程在RegistryProtocol.export中,概括如下
- ServiceBean继承ApplicationListener。Spring启动后会触发onApplicationEvent事件
- List<URL> registryURLs = loadRegistries(true)。加载所有Registry,将zookeeper协议URL变为registry协议URL。(多注册中心)
- 遍历Protocols.(多协议)
- 收集配置信息到map,把map转化为Dubbo协议URL。
- 遍历registryURLs.
- 将DubboUrl地址绑定到registryUrl属性中。key为export.
- protocol.export(wrapperInvoker):开始Registry协议URL流转。protocol包装类,调用连。Protocol调用链:QosProtocolWrapper->ProtocolFilterWrapper->ProtocolListenerWrapper->RegistryProtocol
- QosProtocolWrapper.export():开启QosServer。就是一个Netty服务端(服务统计功能如服务列表,服务在线状态等)
- RegistryProtocol.export():protocol.export(invokerDelegete):开始Dubbo协议流转
- doLocalExport(originInvoker):启动netty服务端。Protocol调用链:QosProtocolWrapper->ProtocolFilterWrapper->ProtocolListenerWrapper->DubboProtocol
- ProtocolFilterWrapper:根据Filter生成Invoker调用链
- DubboProtocol:启动NettyServer:根据filter构建invoker执行链,启动nettyserver,构建handler链
- 回到RegistryProtocol,完成服务注册:register(registryUrl, registeredProviderUrl),dubbo协议URL写入到/dubbo/com.*.*/providers/节点下
- 回到RegistryProtocol:对configurators节点注册事件监听:.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
如果还想再简练一点
- 启动NettyServer.
- 注册服务地址到Providers节点下。
- 订阅configurators节点事件
流程细节
说明:细节流程讲述的可能有点过分细致了,几乎细致到源码解析的每一行。包括属性收集/装配/检测/校验等细节也进行了说明。其实重点关注收集后,服务暴露开始的地方。即重点关注红色标注的部分。
核心逻辑和源码
主要关注源码类RegistryProtocol.export()方法,该方法中实现了服务发布流程,
- 调用DubboProtocol.export启动NettyServer
- 调用zkRegistry.registry().完成服务的注册。创建providers节点,子节点写入dubbo服务地址
- 调用zkRegistry.subscribe.完成事件订阅。创建configurators节点,创建监听事件,首次notify节点事件。这个监听事件,其实就是DubboAdmin或者Api方式动态修改服务配置的实现原理。
详细过程
细节其实已经从源代码级别进行分析了,看起来可能比较累。其实也可以跳过前面直接关注红色部分。如果觉得繁琐,也可以直接跳过看源码部分
ServiceBean.export()完成服务暴露具体细节流程
- 设置配置类属性
- .checkoutDefault()
- .创建ProviderConfig(@EnableDubboConfig没有创建ProviderConfig的话)
- .未配置Provider时,则创建provider
- .appendProperties()并设置provider对象属性,遍历set方法。
- 优先从环境变量取属性System.getProperty()
- 从dubbo.property文件中获取
- .创建ProviderConfig(@EnableDubboConfig没有创建ProviderConfig的话)
- .绑定其他配置类属性
- .checkoutDefault()
- 校验配置信息(application,registry,Protocol,mock,stub)
- 暴露服务
- .收集Registry协议:loadRegistries()根据配置Registies,获取注册的url。(获取注册协议)
- 遍历所有registries
- .封装属性到map中(applicaiton,registryConfig等)。 appendParameters(map, application);
- 生成URL。UrlUtils.parseURLs(address, map)->loadRegistries()根据registry中的Adress地址和map中的key,value。生成url
- .zookeeper://192.168.67.139:2184/com.alibaba.dubbo.registry.RegistryService?application=dubbo_provider&dubbo=2.0.2&owner=world&pid=13218×tamp=1709463351616
- URL设registry属性,把协议头换成从zookeeper改为registry
- registry://192.168.67.139:2184/com.alibaba.dubbo.registry.RegistryService?application=dubbo_provider&dubbo=2.0.2&owner=world&pid=13882®istry=zookeeper×tamp=1709465346174
- 返回URL。上面的配置属性都会通过注册中心传递给消费者。
- 收集Dubbo协议,以及暴露Registry和dubbo协议:doExportUrlsFor1Protocol(protocolConfig, registryURLs)完成服务暴露
- .收集配置类到map中application/module/provider/protocolConfig/serviceconfig)
- 把methodsConfig/ArgumentConfig配置同样设置到map中
- .提前生成接口类对应的代理类,Wrapper.getWrapper(interfaceClass)
- .获取接口包含的所有方法,设置key为methods到map中
- 置token到map
- 获取host:如果Protocol和Privoder配置类都没有配置IP的话,就会调用InetAddress.getLocalHost().getHostAddress();获取主机IP。如果利用容器部署,有可能获取到内网IP,导致消费端掉不通。
- .获取端口:如果Protocol和Privoder配置类都没有配置端口的话,通过SPI机制获取Protocol对应的默认端口。(DubboProtocol)
- 把map转成URL,DUBBO协议
- dubbo://192.168.28.25:29015/com.xiangxue.jack.service.UserService?anyhost=true&application=dubbo_provider&bean.name=com.xiangxue.jack.service.UserService&bind.ip=192.168.28.25&bind.port=29015&default.timeout=5000&dubbo=2.0.2&generic=false&interface=com.xiangxue.jack.service.UserService&methods=doKill,queryUser&owner=world&pid=16437&revision=0.0.1-SNAPSHOT&side=provider&timeout=2000×tamp=1709472937360
- 遍历注册地址,远程调用暴露服务(调用关系AbstractProxyInvoker->wrapper->ServiceClass)
- 遍历第一步loadRegistries()中返回的注册协议地址
- 加载URL 监控url. monitorUrl = loadMonitor(registryURL)
- dubbo协议地址URL绑定monitor属性地址
- 获取到invoker对象
- 将Dubbo协议URL地址,添加到registry协议URL属性中。key为export
- registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString())
- 通过SPI获取代理工厂。因为没有@Adaptive注解。通过javassist动态生成一个代理工厂ProxyFactory$Adaptive。
- 最终获取到StubProxyFactoryWrapper持有JavassistProxyFactory的代理工厂
- 调用代理工厂JavassistProxyFactory.getInvoker()返回AbstractProxyInvoker。(持有被代理类【ServiceImpl】,接口类,URL(Registry))
- 生成代理类:Wrapper.getWrapper对需要调用的目标类的包装类,通过javassist技术动态生成的
- 生成Invoker:AbstractProxyInvoker:是Dubbo最底层的Invoker对象,只有通过他才能调用provider端的目标Service服务方法。持有被代理类,接口类型,RegisterURL
- 调用关系AbstractProxyInvoker->wrapper->ServiceClass
- 包装Invoker对象为DelegateProviderMetaDataInvoker,持有Invoker对象和ServiceConfig对象
-
暴露远程服务:registry协议的export,protocol.export(wrapperInvoker)。【前面都是数据准备过程,接下来才是真正的服务暴露流程】
- SPI方式获取Protocol。最终获取到包装类,持有关系:QosProtocolWrapper->ProtocolFilterWrapper->ProtocolListenerWrapper->RegistryProtocol
- 开启QosServer。QosProtocolWrapper.export().就是一个Netty服务端(服务统计功能如服务列表,服务在线状态等)
- 完成服务注册regist.export():RegistryProtocol.export()
- 启动Server:doLocalExport()走到DubboProtocl中启动NettyServer
- .创建InvokerDelegete:持有原始Invoker和Dubbo协议URL
- .服务发布:protocol.export(invokerDelegete)。protocol是SPI动态生成的Protocol$Adaptive,invokerDelegete对应的URL为Dubbo,最终会掉到DubboProtocol。调用关系:QosProtocolWrapper->ProtocolFilterWrapper->ProtocolListenerWrapper->DubboProtocol
- 构建Invoker调用链:ProtocolFilterWrapper.class。SPI获取所有Filters,ProtocolFilterWrapper.buildInvokerChain,通过next指针传递
- 说明:最终执行链的关系:Filter1-》Filter2-〉invokerDelegete->AbstractProxyInvoker->wrapper->ServiceClass
- ProtocolListenerWrapper.export
- DubboProtocol.expot():启动Server
- 获取String url
- 获取key:(服务名称:端口)
- 创建DubboExporter:持有Invoker执行链和key和exporterMap。每个服务都对应一个DubboExporter
- 构建映射关系:Map<String, Exporter<?>> exporterMap。
- openserver():启动服务(每一个@Service修饰的类都会进来,但是同一个IP+端口,只会启动启动一个netty服务端)后面重点讲。
- 获取Address:ip:port
- 从serverMap获取ExchangeServer。
- 如果获取不到则创建createServer(url):启动netty服务进行双端通信,返回ExchangeServer对象。一个主机只会启动一次NettyServer。源码后面重点讲。
- 总结DubboProtocol.expot()1.完成了Netty服务端的启动,2.建立handler的链条关系
- 服务发布监听包装类,其实就是提供一个扩展,服务发布后给一个通知。ListenerExporterWrapper.持有DubboExporter和List<ExporterListener>。
- DubboProtocol.expot():启动Server
- 构建Invoker调用链:ProtocolFilterWrapper.class。SPI获取所有Filters,ProtocolFilterWrapper.buildInvokerChain,通过next指针传递
- .包装ExporterChangeableWrapper类,持有ListenerExporterWrapper和originInvoker【registry协议】
- .registryUrl = getRegistryUrl(originInvoker):获取注册协议zookeeper协议Url
- .zookeeper://192.168.67.139:2184/com.alibaba.dubbo.registry.RegistryService?application=dubbo_provider&dubbo=2.0.2&export=dubbo%3A%2F%2F192.168.3.6%3A20880%2Fcom.xiangxue.jack.async.AsyncService%3Fanyhost%3Dtrue%26application%3Ddubbo_provider%26bean.name%3DServiceBean%3Acom.xiangxue.jack.async.AsyncService%26bind.ip%3D192.168.3.6%26bind.port%3D20880%26dubbo%3D2.0.2%26generic%3Dfalse%26interface%3Dcom.xiangxue.jack.async.AsyncService%26methods%3DasynctoDo%26owner%3Dworld%26pid%3D86931%26revision%3D0.0.1-SNAPSHOT%26side%3Dprovider%26timeout%3D123456%26timestamp%3D1710227953244&owner=world&pid=86931×tamp=1710227939570
- 获取注册类ZookeeperRegistry。getRegistry(originInvoker);
- .registeredProviderUrl= getRegisteredProviderUrl(originInvoker):获提供者地址(写入接口下provider节点)
- dubbo://192.168.3.6:20880/com.xiangxue.jack.async.AsyncService?anyhost=true&application=dubbo_provider&bean.name=ServiceBean:com.xiangxue.jack.async.AsyncService&dubbo=2.0.2&generic=false&interface=com.xiangxue.jack.async.AsyncService&methods=asynctoDo&owner=world&pid=86931&revision=0.0.1-SNAPSHOT&side=provider&timeout=123456×tamp=1710227953244
- .建立服务名称和ProviderInvokerWrapper的映射关系[缓存providerInvokers].ProviderInvokerWrapper持有originInvoker, registryUrl, registeredProviderUrl
- .完成服务注册:register(registryUrl, registeredProviderUrl)。把dubbo协议地址注册到Provider节点下,其实就是创建服务节点(providers节点和dubbo服务地址临时节点);
- SPI获取ZookeeperRegistryFacotry创建Zookeeper,创建zk客户端,注册断线重连监听
- .ZookeeperRegistry.register()
- .缓存注册过的URL:registered
- .创建节点名称:doRegistry(),创建provider持久化节点和dubbo服务地址的临时节点
- 注册监听事件.[以下逻辑是对configurators节点注册事件监听,如果修改了属性则会覆盖客户端的该节点的数据]
- .overrideSubscribeUrl=getSubscribedOverrideUrl(registeredProviderUrl)。获取override的provider协议。后面映射关系可以将provider协议Url理解为接口。(subscribed,notified两个权局缓存)
- .provider://192.168.3.6:20880/com.xiangxue.jack.async.AsyncService?anyhost=true&application=dubbo_provider&bean.name=ServiceBean:com.xiangxue.jack.async.AsyncService&category=configurators&check=false&dubbo=2.0.2&generic=false&interface=com.xiangxue.jack.async.AsyncService&methods=asynctoDo&owner=world&pid=96966&revision=0.0.1-SNAPSHOT&side=provider&timeout=123456×tamp=1710257665285
- .注册监听事件registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener)订阅configurators节点下数据变更事件,path:/dubbo/com.a.b.service/configurators。
- .全局变量缓存:subscribed = new ConcurrentHashMap<URL, Set<NotifyListener>>(),缓存overrideSubscribeUrl和NotifyListener
- 建立overrideListener和 ChildListener的映射
- 创建path:/dubbo/com.a.b/configurators
- 注册监听事件
- 建立dubbo的ChildListener事件类和Curator的事件类的映射
- 事件关系:NotifyListener->ChildListener->CuratorWatch
- 当发生overrite时,zk监听器调用curatorWatch的process方法,最终掉到NotifyListener方法,实现动态修改配置参数功能(下节重点将)
- 创建Empty协议:
- empty://192.168.3.6:20880/com.xiangxue.jack.async.AsyncService?anyhost=true&application=dubbo_provider&bean.name=ServiceBean:com.xiangxue.jack.async.AsyncService&category=configurators&check=false&dubbo=2.0.2&generic=false&interface=com.xiangxue.jack.async.AsyncService&methods=asynctoDo&owner=world&pid=26895&revision=0.0.1-SNAPSHOT&side=provider&timeout=123456×tamp=1710430398847
- notify(url, listener, urls);通知Empty协议或者overide协议。
- 启动Server:doLocalExport()走到DubboProtocl中启动NettyServer
- 将Dubbo协议URL地址,添加到registry协议URL属性中。key为export
- .收集Registry协议:loadRegistries()根据配置Registies,获取注册的url。(获取注册协议)
- 为QOS统计数据做准备.看服务列表,服务在线状态
- publishExportEvent()发布服务暴露事件ServiceBeanExportedEvent,即发布一个服务已经暴露的通知。消费端会通过ApplicationListener关注此事件类型
源码分析
由于provider发布流程代码较长,且流程中其中属性收集,校验等逻辑不是重点。以下我们主要介绍服务发布流程中的核心代码。即RegistryProtocol.export()
- 核心流程RegistryProtocol.export().主要做了三件事。启动NettryServer,注册dubbo服务,订阅configurators节点事件
#RegistryProtocol.export
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {//export invoker//这里会启动server,走到DubboProtocol中去启动serverfinal ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker);//获取注册协议URL registryUrl = getRegistryUrl(originInvoker);//registry providerfinal Registry registry = getRegistry(originInvoker);final URL registeredProviderUrl = getRegisteredProviderUrl(originInvoker);//to judge to delay publish whether or notboolean register = registeredProviderUrl.getParameter("register", true);//建立服务名称和invoke的映射关系ProviderConsumerRegTable.registerProvider(originInvoker, registryUrl, registeredProviderUrl);if (register) {//这里完成了服务注册和事件监听register(registryUrl, registeredProviderUrl);//设置是否注册标识ProviderConsumerRegTable.getProviderWrapper(originInvoker).setReg(true);}//以下逻辑是对configurators节点注册事件监听,如果修改了属性则会覆盖客户端的该节点的数据final URL overrideSubscribeUrl = getSubscribedOverrideUrl(registeredProviderUrl);//zookeeper事件触发后,最终回调的listenerfinal OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);//注册事件registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);//Ensure that a new exporter instance is returned every time exportreturn new DestroyableExporter<T>(exporter, originInvoker, overrideSubscribeUrl, registeredProviderUrl);}
DubboProtocol.export。启动nettyServer.
#RegistryProtocol
private <T> ExporterChangeableWrapper<T> doLocalExport(final Invoker<T> originInvoker) {String key = getCacheKey(originInvoker);ExporterChangeableWrapper<T> exporter = (ExporterChangeableWrapper<T>) bounds.get(key);if (exporter == null) {synchronized (bounds) {exporter = (ExporterChangeableWrapper<T>) bounds.get(key);if (exporter == null) {//真正invoker的静态代理final Invoker<?> invokerDelegete = new InvokerDelegete<T>(originInvoker, getProviderUrl(originInvoker));//这里的协议头是dubbo了,这里会调用DubboProtocol.export()exporter = new ExporterChangeableWrapper<T>((Exporter<T>) protocol.export(invokerDelegete), originInvoker);bounds.put(key, exporter);}}}return exporter;}
#DubboProtocolpublic <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {URL url = invoker.getUrl();// export service.String key = serviceKey(url);DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);exporterMap.put(key, exporter);//export an stub service for dispatching eventBoolean isStubSupportEvent = url.getParameter(Constants.STUB_EVENT_KEY, Constants.DEFAULT_STUB_EVENT);Boolean isCallbackservice = url.getParameter(Constants.IS_CALLBACK_SERVICE, false);if (isStubSupportEvent && !isCallbackservice) {String stubServiceMethods = url.getParameter(Constants.STUB_EVENT_METHODS_KEY);if (stubServiceMethods == null || stubServiceMethods.length() == 0) {if (logger.isWarnEnabled()) {logger.warn(new IllegalStateException("consumer [" + url.getParameter(Constants.INTERFACE_KEY) +"], has set stubproxy support event ,but no stub methods founded."));}} else {stubServiceMethodsMap.put(url.getServiceKey(), stubServiceMethods);}}//启动server,核心代码openServer(url);optimizeSerialization(url);return exporter;}private void openServer(URL url) {// find server.//这个方法会根据@Service注解的类有多少而近来多次,但是同一个ip的netty服务端只会创建一次String key = url.getAddress();//client can export a service which's only for server to invokeboolean isServer = url.getParameter(Constants.IS_SERVER_KEY, true);if (isServer) {ExchangeServer server = serverMap.get(key);if (server == null) {//开启serverserverMap.put(key, createServer(url));} else {// server supports reset, use together with overrideserver.reset(url);}}}
总结:上面内容中,每个从业务流程和源码角度进行了详细分析,如果大家有疑问或者对文章排版任何方面有建议都可以留言评论,看到都会及时回复大家。
知识总结,分享不易,全文手敲,欢迎大家关注点赞评论收藏。
相关文章:
Dubbo源码解析-Provider服务暴露Export源码解析
上篇我们介绍了ServiceBean初始化和依赖注入过程,地址如下 Dubbo源码-Provider服务端ServiceBean初始化和属性注入-CSDN博客 本文主要针Dubbo服务端服务Export过程,从dubbo源码角度进行解析。 Dubbo 服务端暴露细节流程比较长,也是面试过程中…...

在微信小程序中或UniApp中自定义tabbar实现毛玻璃高斯模糊效果
backdrop-filter: blur(10px); 这一行代码表示将背景进行模糊处理,模糊程度为10像素。这会导致背景内容在这个元素后面呈现模糊效果。 background-color: rgb(255 255 255 / .32); 这一行代码表示设置元素的背景颜色为白色(RGB值为0, 0, 0)&a…...

【JavaScript】JavaScript 程序流程控制 ⑥ ( while 循环概念 | while 循环语法结构 )
文章目录 一、while 循环1、while 循环概念2、while 循环语法结构 二、while 循环 - 代码示例1、打印数字2、计算 1 - 10 之和 一、while 循环 1、while 循环概念 在 JavaScript 中 , while 循环 是一种 " 循环控制语句 " , 使用该语句就可以 重复执行一段代码块 , …...

Keil笔记(缘更)
Keil 一、使用Keil时可能会出现的问题1.Project框不见了2.添加文件时找不到3.交换文件位置4.main.c测试报1 warning5.搜索CtrlF 二、模电常识(白话随便版)一、名词解释二、基础门电路 三、STLINK点灯操作1.配置寄存器进行点灯2.使用库函数进行点灯 四.GPIO1.LED闪烁4.按键控制L…...

举4例说明Python如何使用正则表达式分割字符串
在Python中,你可以使用re模块的split()函数来根据正则表达式分割字符串。这个函数的工作原理类似于Python内置的str.split()方法,但它允许你使用正则表达式作为分隔符。 示例 1: 使用单个字符作为分隔符 假设你有一个由逗号分隔的字符串,你可…...
Java 中的 Math. round(-1. 5) 等于多少?
在 Java 中,Math.round() 方法用于四舍五入一个浮点数。这个方法的工作原理是,它会查看要舍入数值的小数点后第一位。如果这一位是 5 或更大,那么整数部分加 1;如果小于 5,整数部分保持不变。 对于 Math.round(-1.5)&…...

MFC界面美化第三篇----自绘按钮(重绘按钮)
1.前言 最近发现读者对我的mfc美化的专栏比较感兴趣,因此在这里进行续写,这里我会计划写几个连续的篇章,包括对MFC按钮的美化,菜单栏的美化,标题栏的美化,list列表的美化,直到最后形成一个完整…...

设计模式|工厂模式
文章目录 1. 工厂模式的三种实现2. 简单工厂模式和工厂方法模式示例3. 抽象工厂模式示例4. 工厂模式与多态的关系5. 工程模式与策略模式的关系6. 面试中可能遇到的问题6.1 **工厂模式的概念是什么?**6.2 **工厂模式解决了什么问题?**6.3 **工厂模式的优点…...

CHAT~(持续更新)
CHAT(持续更新) 实现一个ChatGPT创建API设计页面布局业务操作技术架构 实现安装工具 其他 实现一个ChatGPT 创建API 最简单也最需要信息的一步 继续往下做的前提 此处省略,想要获取接口创建方式联系 设计 页面布局 按照官网布局 业务操作…...

linux系统------------Mysql数据库介绍、编译安装
目录 一、数据库基本概念 1.1数据(Data) 1.2表 1.3数据库 1.4数据库管理系统(DBMS) 数据库管理系统DBMS原理 1.5数据库系统(DBS) 二、数据库发展史 1、第一代数据库 2、第二代数据库 3、第三代数据库 三、关系型数据库 3.1关系型数据库应用 3.2主流的…...

文件操作3
随机读写数据文件 一、随机读写原理 在我们写数据时,有一个光标不断的在随着新写入的数据往后移动; 而读数据时,也有一个看不见光标,随着已经读完的数据,往后移动 这里的文件读写位置标记——可以想象成图形界面里的…...
算法D57 | 动态规划17 | 647. 回文子串 516.最长回文子序列 动态规划总结篇
647. 回文子串 动态规划解决的经典题目,如果没接触过的话,别硬想 直接看题解。 代码随想录 Python: class Solution:def countSubstrings(self, s: str) -> int:n len(s)dp [[0]*n for _ in range(n)]dp[0] [1]*nresult nfor i in range(1, n)…...
go的限流
背景 服务请求下游,oom,排查下来发现是一个下游组件qps陡增导致 但是司内网络框架比较挫,竟然不负责框架内存问题(有内存管理模块,但逻辑又是无限制使用内存) 每个请求一个r、w buffer,请求无限…...

补充--广义表学习
第一章 逻辑结构 (1)A(),A是一个空表,长度为0,深度为1。 (2)B(d,e),B的元素全是原子,d和e,长度为2,深度为1。 (3)C(b,(c,…...
【笔记】KaiOS SPN显示逻辑
更新流程code 1、gonk/dom/system/gonk/radio/RadioInterfaceLayer.jsm handleNetworkStateChanged -> requestNetworkInfo() -> handleRilResponse的getOperator -> handleOperator handleNetworkStateChanged:网络状态变化请求网络信息 this.requestNetworkInfo…...

Visual Basic6.0零基础教学(4)—编码基础,数据类型与变量
编码基础,数据类型与变量 文章目录 编码基础,数据类型与变量前言一、VB中的编程基础二、VB的基本字符集和词汇集1、字符集2、词汇集 VB中的数据类型VB中的变量与常量一.变量和常量的命名规则二.变量声明1.用Dim语句显式声明变量三. 常量 运算符和表达式一. 运算符 1. 算术运算符…...

VPCFormer:一个基于transformer的多视角指静脉识别模型和一个新基准
文章目录 VPCFormer:一个基于transformer的多视角指静脉识别模型和一个新基准总结摘要介绍相关工作单视角指静脉识别多视角指静脉识别Transformer 数据库基本信息 方法总体结构静脉掩膜生成VPC编码器视角内相关性的提取视角间相关关系提取输出融合IFFN近邻感知模块(NPM) patch嵌…...
Android 图形渲染和显示系统关系
SurfaceFlinger:作为 Android 系统中的一个系统服务,SurfaceFlinger 负责管理整个屏幕的渲染和合成工作。它管理和合成多个 Surface,并与硬件加速器以及 Hardware Composer (HWC) 进行交互,最终将图像数据发送给显示硬件进行显示。…...
3.C++:类与对象(下)
一、再谈构造函数 1.1构造函数体赋值 在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值。 class Date { public:Date(int year, int month, int day){_year year;_month month;_day day;}private:int _year;int _month;i…...

iOS开发之SwiftUI
iOS开发之SwiftUI 在iOS开发中SwiftUI与Objective-C和Swift不同,它采用了声明式语法,相对而言SwiftUI声明式语法简化了界面开发过程,减少了代码量。 由于SwiftUI是Apple推出的界面开发框架,从iOS13开始引入,Apple使用…...

JavaScript 中的 ES|QL:利用 Apache Arrow 工具
作者:来自 Elastic Jeffrey Rengifo 学习如何将 ES|QL 与 JavaScript 的 Apache Arrow 客户端工具一起使用。 想获得 Elastic 认证吗?了解下一期 Elasticsearch Engineer 培训的时间吧! Elasticsearch 拥有众多新功能,助你为自己…...

23-Oracle 23 ai 区块链表(Blockchain Table)
小伙伴有没有在金融强合规的领域中遇见,必须要保持数据不可变,管理员都无法修改和留痕的要求。比如医疗的电子病历中,影像检查检验结果不可篡改行的,药品追溯过程中数据只可插入无法删除的特性需求;登录日志、修改日志…...
服务器硬防的应用场景都有哪些?
服务器硬防是指一种通过硬件设备层面的安全措施来防御服务器系统受到网络攻击的方式,避免服务器受到各种恶意攻击和网络威胁,那么,服务器硬防通常都会应用在哪些场景当中呢? 硬防服务器中一般会配备入侵检测系统和预防系统&#x…...
python爬虫:Newspaper3k 的详细使用(好用的新闻网站文章抓取和解析的Python库)
更多内容请见: 爬虫和逆向教程-专栏介绍和目录 文章目录 一、Newspaper3k 概述1.1 Newspaper3k 介绍1.2 主要功能1.3 典型应用场景1.4 安装二、基本用法2.2 提取单篇文章的内容2.2 处理多篇文档三、高级选项3.1 自定义配置3.2 分析文章情感四、实战案例4.1 构建新闻摘要聚合器…...

html-<abbr> 缩写或首字母缩略词
定义与作用 <abbr> 标签用于表示缩写或首字母缩略词,它可以帮助用户更好地理解缩写的含义,尤其是对于那些不熟悉该缩写的用户。 title 属性的内容提供了缩写的详细说明。当用户将鼠标悬停在缩写上时,会显示一个提示框。 示例&#x…...

OPENCV形态学基础之二腐蚀
一.腐蚀的原理 (图1) 数学表达式:dst(x,y) erode(src(x,y)) min(x,y)src(xx,yy) 腐蚀也是图像形态学的基本功能之一,腐蚀跟膨胀属于反向操作,膨胀是把图像图像变大,而腐蚀就是把图像变小。腐蚀后的图像变小变暗淡。 腐蚀…...

中医有效性探讨
文章目录 西医是如何发展到以生物化学为药理基础的现代医学?传统医学奠基期(远古 - 17 世纪)近代医学转型期(17 世纪 - 19 世纪末)现代医学成熟期(20世纪至今) 中医的源远流长和一脉相承远古至…...

七、数据库的完整性
七、数据库的完整性 主要内容 7.1 数据库的完整性概述 7.2 实体完整性 7.3 参照完整性 7.4 用户定义的完整性 7.5 触发器 7.6 SQL Server中数据库完整性的实现 7.7 小结 7.1 数据库的完整性概述 数据库完整性的含义 正确性 指数据的合法性 有效性 指数据是否属于所定…...
C#中的CLR属性、依赖属性与附加属性
CLR属性的主要特征 封装性: 隐藏字段的实现细节 提供对字段的受控访问 访问控制: 可单独设置get/set访问器的可见性 可创建只读或只写属性 计算属性: 可以在getter中执行计算逻辑 不需要直接对应一个字段 验证逻辑: 可以…...
MySQL 索引底层结构揭秘:B-Tree 与 B+Tree 的区别与应用
文章目录 一、背景知识:什么是 B-Tree 和 BTree? B-Tree(平衡多路查找树) BTree(B-Tree 的变种) 二、结构对比:一张图看懂 三、为什么 MySQL InnoDB 选择 BTree? 1. 范围查询更快 2…...