Dubbo源码解析第一期:如何使用Netty4构建RPC
一、背景
早期学习和使用Dubbo的时候(那时候Dubbo还没成为Apache顶级项目),写过一些源码解读,但随着Dubbo发生了翻天覆地的变化,那些文章早已过时,所以现在计划针对最新的Apache Dubbo源码来进行“阅读理解”,希望和大家一起再探Dubbo的实现。由于能力有限,如果文章有错误的地方,欢迎大家留言指正。
本期的主题是Dubbo如何使用Netty4构建RPC来通讯。
二、Server端视角
我们看看作为服务提供方,Apache Dubbo是如何使用Netty4的。
2.1 Netty的线程组
线程组作为Netty的Reactor设计核心组件,在这里自然少不了,我们看到org.apache.dubbo.remoting.transport.netty4.NettyServer中有如下两个方法:
int DEFAULT_IO_THREADS = Math.min(Runtime.getRuntime().availableProcessors() + 1, 32);String IO_THREADS_KEY = "iothreads";String EVENT_LOOP_BOSS_POOL_NAME = "NettyServerBoss";String EVENT_LOOP_WORKER_POOL_NAME = "NettyServerWorker";protected EventLoopGroup createBossGroup() {return NettyEventLoopFactory.eventLoopGroup(1, EVENT_LOOP_BOSS_POOL_NAME);}protected EventLoopGroup createWorkerGroup() {return NettyEventLoopFactory.eventLoopGroup(getUrl().getPositiveParameter(IO_THREADS_KEY, Constants.DEFAULT_IO_THREADS),EVENT_LOOP_WORKER_POOL_NAME);}
分别用来构建Boss线程组和Worker线程组,Boss线程组负责创建连接,连接创建成功后由Worker线程组来负责处理和发送请求。注意Boss线程组只设置了1个线程,而且没有设置修改接口,通常1个也是够用的,如果设置成可配的会更好。而Worker线程组默认值是当前可用核数和32中取最小值,注意,是最小值,不是最大值,意味着如果你单个Provider实例上可用的核超过32,那么一定要设置IO_THREADS_KEY,否则可能无法达到最大吞吐量(特别是IO密集型应用)。上面代码也给出了这两个线程组的线程池的名称,如果不清楚现在跑的是多少,可以jstack命令看下。注意,为了阐述方便,上面的四个属性定义来自org.apache.dubbo.remoting.Constants。
2.1 Netty的ChannelHandler
Netty的可扩展性主要来自其核心组件之一ChannelHandler,ChannelHandler能帮助我们解决拆包&粘包、协议编解码、权限校验等RPC常遇到的问题。那我们来看看现在Dubbo的Provider端用到了哪些ChannelHandler,同样,我们看NettyServer#initServerBootstrap方法:
protected void initServerBootstrap(NettyServerHandler nettyServerHandler) {boolean keepalive = getUrl().getParameter(KEEP_ALIVE_KEY, Boolean.FALSE);bootstrap.group(bossGroup, workerGroup)// 根据是否支持EPoll来返回EpollServerSocketChannel或者NioServerSocketChannel.channel(NettyEventLoopFactory.serverSocketChannelClass())// SO_REUSEADDR设置为true表示允许重用本地地址和端口。如果服务器意外关闭后再次启动,可以立即绑定到之前使用的地址和端口。.option(ChannelOption.SO_REUSEADDR, Boolean.TRUE)// 表示禁用Nagle算法。Nagle算法会将小的网络包合并成较大的包来提高网络效率,但会增加数据传输的延迟。一般对延迟敏感的场景都会禁用Nagle算法。.childOption(ChannelOption.TCP_NODELAY, Boolean.TRUE)// 开启TCP的心跳机制,用于检测连接是否还活着.childOption(ChannelOption.SO_KEEPALIVE, keepalive)// PooledByteBufAllocator是Netty提供的一种内存分配器实现,它可以重用ByteBuf对象的内存,提高内存的利用率和性能。.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT).childHandler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {int closeTimeout = UrlUtils.getCloseTimeout(getUrl());NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec(), getUrl(), NettyServer.this);// 处理HTTPSch.pipeline().addLast("negotiation", new SslServerTlsHandler(getUrl()));ch.pipeline()// 解码,读数据的时候用.addLast("decoder", adapter.getDecoder())// 编码,写数据的时候用.addLast("encoder", adapter.getEncoder())// 注意,这是Netty提供的ChannelHandler.addLast("server-idle-handler", new IdleStateHandler(0, 0, closeTimeout, MILLISECONDS))// NettyServerHandler可是核心.addLast("handler", nettyServerHandler);}});}
我们重点关注下childHandler中添加的ChannelHandler。在这之前,我们先对Channel中的ChannelPipeline和ChanelHandler进行简单介绍,我们知道每个连接对应一个Channel,而每个Channel对应一个ChannelPipeline,用来组织和管理连接上面的处理器ChannelHandler(责任链的方式)。ChannelHandler分为ChannelInboundHandler和ChannelOutboundHandler两大类,分别管理入站(读)和出站(写)的流程定制,当然,为了使用方便,Netty提供了很多开箱即用的ChannelHandler,便于我们轻松实现HTTP、WebSocket等协议。
SslServerTlsHandler
这是上面通过childHandler添加的第一个ChannelHandler(它属于Netty中ByteToMessageDecoder的子类,ByteToMessageDecoder的主要用途是将接收到的字节数据解码为消息对象,并将解码后的消息对象传递给下一个处理器进行后续的业务处理。继承它可以方便的处理多种数据格式,例如二进制数据、文本数据等),看这Handler的名称就知道是做什么的,用来进行SSL|TLS加解密。在其实现的decode方法中,如果发现ByteBuf是支持SSL|TLS,那么会立马在当前Channel的ChannelPipeline中加入SslHandler(Netty自带的支持SSL|TLS握手的ChannelHandler),注意,是加载当前SslServerTlsHandler之后。为了能在SSL|TLS解码后能继续执行SslServerTlsHandler的逻辑,在SslHandler后面又新加了一个SslServerTlsHandler(其中sslDetected=true,说明数据包到这里已经解包完成),用于校验协议。
当然,如果SslServerTlsHandler发现你没有使用SSL|TLS(Netty中的SslHandler#isEncrypted能判断ByteBuf是否进行过加密),那么会直接把自己从ChannelPipeline中删除。这里给出SslServerTlsHandler#decode代码:
@Overrideprotected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list)throws Exception {// Will use the first five bytes to detect a protocol.if (byteBuf.readableBytes() < 5) {return;}// SSL|TLS 校验完成后会创建一个新的SslServerTlsHandler,其中sslDetected会设置成trueif (sslDetected) {return;}CertManager certManager =url.getOrDefaultFrameworkModel().getBeanFactory().getBean(CertManager.class);ProviderCert providerConnectionConfig = certManager.getProviderConnectionConfig(url, channelHandlerContext.channel().remoteAddress());// 如果没启用SSL|TLS,那么获取到的providerConnectionConfig就会是null,从而删除并退出该Handlerif (providerConnectionConfig == null) {ChannelPipeline p = channelHandlerContext.pipeline();p.remove(this);return;}if (isSsl(byteBuf)) {SslContext sslContext = SslContexts.buildServerSslContext(providerConnectionConfig);enableSsl(channelHandlerContext, sslContext);return;}if (providerConnectionConfig.getAuthPolicy() == AuthPolicy.NONE) {ChannelPipeline p = channelHandlerContext.pipeline();p.remove(this);return;}logger.error(INTERNAL_ERROR, "", "", "TLS negotiation failed when trying to accept new connection.");channelHandlerContext.close();}
NettyCodecAdapter.InternalDecoder
之前已经给了NettyServer#initServerBootstrap方法,其中可以发现这个内部解码器InternalDecoder是继SslServerTlsHandler的下一个ChannelHandler,由于它和SslServerTlsHandler一样,是ByteToMessageDecoder的子类,所以我们直接看其decode方法:
private class InternalDecoder extends ByteToMessageDecoder {@Overrideprotected void decode(ChannelHandlerContext ctx, ByteBuf input, List<Object> out) throws Exception {// ChannelBuffer是Dubbo对通道缓冲区的抽象,这里的NettyBackedChannelBuffer就是其Netty缓冲区第一个实现,NettyBackedChannelBuffer内部直接使用的是Netty的ByteBufChannelBuffer message = new NettyBackedChannelBuffer(input);// Dubbo中的NettyChannel是衔接Netty Channel和Dubbo内部通道抽象的桥梁,其中NettyChannel就有Dubbo的解码器NettyChannel channel = NettyChannel.getOrAddChannel(ctx.channel(), url, handler);do {int saveReaderIndex = message.readerIndex();// 这里的codec默认是DubboCountCodec实例,支持MultiMessage解码,这里返回的msg已经是org.apache.dubbo.remoting.exchange.Request对象Object msg = codec.decode(channel, message);if (msg == Codec2.DecodeResult.NEED_MORE_INPUT) {message.readerIndex(saveReaderIndex);break;} else {// is it possible to go here ?if (saveReaderIndex == message.readerIndex()) {throw new IOException("Decode without read data.");}if (msg != null) {out.add(msg);}}} while (message.readable());}}
1. DubboCountCodec(implements Codec2):用来处理MultiMessage类型的消息,Dubbo Consumer可以通过MultiMessage来一次性发送多个请求,但一般没人这么用,大家更愿意传递一个List来让下游Dubbo Provider来批量处理,上述InternalDecoder#decode方法内部调用了其decode方法,decode方法会继续触发其他org.apache.dubbo.remoting.Codec2实现类的调用。
2. DubboCodec(extends ExchangeCodec):DubboCountCodec#decode内部会调用其父类ExchangeCodec#decode方法,主要用于将Dubbo协议中的请求和响应消息进行序列化和反序列化。看到这里,难道Dubbo没用LengthFieldBasedFrameDecoder?这可是Netty中解决TCP拆包、粘包的神器!没有,Dubbo自己在ExchangeCodec#decode方法中自己解决了粘包、拆包问题,通过返回DecodeResult.NEED_MORE_INPUT来表示需要更多数据。ExchangeCodec#decode方法最后又将调用DubboCodec#decodeBody来解码body部分,这里给出Dubbo的协议图:
我们看看DubboCodec解码body部分的代码(注意DubboCodec可以解码也可以编码,这里为了简单起见,只展示解码部分):
@Overrideprotected Object decodeBody(Channel channel, InputStream is, byte[] header) throws IOException {byte flag = header[2], proto = (byte) (flag & SERIALIZATION_MASK);// get request id.long id = Bytes.bytes2long(header, 4);// decode request.Request req;Object data;req = new Request(id);req.setVersion(Version.getProtocolVersion());req.setTwoWay((flag & FLAG_TWOWAY) != 0);// get data length.int len = Bytes.bytes2int(header, 12);req.setPayload(len);DecodeableRpcInvocation inv;// 如果你实现了自定义解析器,那么就使用自定义的if (customByteAccessor != null) {inv = customByteAccessor.getRpcInvocation(channel, req, new UnsafeByteArrayInputStream(readMessageData(is)), proto);} else {// 否则用Dubbo内置的inv = new DecodeableRpcInvocation(frameworkModel,channel,req,new UnsafeByteArrayInputStream(readMessageData(is)),proto);}// DecodeableRpcInvocation来通过协议对应的序列化器来反序列化inv.decode();data = inv;req.setData(data);return req;}
可以看到,DubboCodec将使用DecodeableRpcInvocation#decode来完成最终解码过程。需要注意的是,我们可以通过设置DECODE_IN_IO_THREAD_KEY("decode.in.io.thread")参数来让Dubbo决定整个decode操作是否在IO线程中执行,默认是在IO线程执行。
2. DecodeableRpcInvocation(extends RpcInvocation):
前面我们说真正的反序列化是在该类中实现的,我们看下代码:
@Overridepublic Object decode(Channel channel, InputStream input) throws IOException {int contentLength = input.available();this.put(Constants.CONTENT_LENGTH_KEY, contentLength);// 通过请求中的SerializationId来选择对应的序列化对象进行数据的反序列化ObjectInput in = CodecSupport.getSerialization(serializationType).deserialize(channel.getUrl(), input);this.put(SERIALIZATION_ID_KEY, serializationType);// 设置Dubbo版本号String dubboVersion = in.readUTF();request.setVersion(dubboVersion);setAttachment(DUBBO_VERSION_KEY, dubboVersion);// 设置需要调用的接口标识String path = in.readUTF();setAttachment(PATH_KEY, path);String version = in.readUTF();setAttachment(VERSION_KEY, version);// Do provider-level payload checks.String keyWithoutGroup = keyWithoutGroup(path, version);// 为了防止数据过载,在Dubbo Consumer端可以设置payLoad,这里检查数据是否超过payLoad限制checkPayload(keyWithoutGroup);// 设置需要调用的方法标识setMethodName(in.readUTF());// 参数类型的全限定名,如果有多个参数,通过;分隔String desc = in.readUTF();setParameterTypesDesc(desc);ClassLoader originClassLoader = Thread.currentThread().getContextClassLoader();try {Object[] args = DubboCodec.EMPTY_OBJECT_ARRAY;Class<?>[] pts = DubboCodec.EMPTY_CLASS_ARRAY;if (desc.length() > 0) {// 根据参数描述来拿到对应的Class对象pts = drawPts(path, version, desc, pts);// 根据参数的Class和值,解析到真正的参数对象args = drawArgs(in, pts);}setParameterTypes(pts);Map<String, Object> map = in.readAttachments();if (CollectionUtils.isNotEmptyMap(map)) {addObjectAttachments(map);}decodeArgument(channel, pts, args);} catch (ClassNotFoundException e) {throw new IOException(StringUtils.toString("Read invocation data failed.", e));} finally {Thread.currentThread().setContextClassLoader(originClassLoader);if (in instanceof Cleanable) {((Cleanable) in).cleanup();}}return this;}
我们可以看到,先是通过CodecSupport.getSerialization来获取对应的序列化对象,然后通过它来反序列化数据(我们可以通过看org.apache.dubbo.common.serialize.Serialization有哪些实现类来了解Dubbo默认支持的协议类型)。
NettyServerHandler
当轮到NettyServerHandler执行的时候,由于前面的ChannelHandler处理,请求数据已经反序列化成了org.apache.dubbo.remoting.exchange.Request对象,到了NettyServerHandler#received内部,就轮到一些列 org.apache.dubbo.remoting.ChannelHandler 的子类来处理了:
MultiMessageHandler
如果是MultiMessage类型的消息,那么for循环去调用内部handler去处理,否则只调用handler处理一次。
HeartbeatHandler
在Channel上设置读的时间戳,便于后续判断该Channel是否存活,同时判断请求是不是HeartBeatRequest类型,如果是,则直接应答。否则继续执行下一个handler。
AllChannelHandler
根据请求的url来获取处理的线程池,然后将请求和DecodeHandler实例构造成ChannelEventRunnable对象来扔进线程池中执行。
@Overridepublic void received(Channel channel, Object message) throws RemotingException {ExecutorService executor = getPreferredExecutorService(message);try {// 这里的handler是DecodeHandler对象executor.execute(new ChannelEventRunnable(channel, handler, ChannelState.RECEIVED, message));} catch (Throwable t) {if (message instanceof Request && t instanceof RejectedExecutionException) {sendFeedback(channel, (Request) message, t);return;}throw new ExecutionException(message, channel, getClass() + " error when process received event .", t);}}
DecodeHandler
前面说了AllChannelHandler之后的逻辑是在另一个线程中执行的,毫无疑问,在这个新的线程中,主要是用来调用DecodeHandler的来进行解码,在Dubbo Provider接收客户端请求的场景下,调用的是received方法:
@Overridepublic void received(Channel channel, Object message) throws RemotingException {// 如果之前设置的不是在IO线程中解码,那么就在这个线程中解码if (message instanceof Decodeable) {decode(message);}// 如果是Request对象,那么得对消息负载(真正的请求对象)进行解码if (message instanceof Request) {decode(((Request) message).getData());}if (message instanceof Response) {decode(((Response) message).getResult());}// handler是HeaderExchangeHandler对象handler.received(channel, message);}
HeaderExchangeHandler
该Handler根据请求的交互方式来决定如何处理请求(例如有的交互方式是不需要应答的),处理真正调用,比如会拿Request.data(RpcInvocation),最终会去调用DubboProtocol#ExchangeHandler的reply方法来拿到一个异步结果对象,并设置完成时候res的状态码和结果的赋值:
void handleRequest(final ExchangeChannel channel, Request req) throws RemotingException {Response res = new Response(req.getId(), req.getVersion());// find handler by message class.Object msg = req.getData();try {// handler是DubboProtocol#ExchangeHandler对象CompletionStage<Object> future = handler.reply(channel, msg);future.whenComplete((appResult, t) -> {try {if (t == null) {res.setStatus(Response.OK);res.setResult(appResult);} else {res.setStatus(Response.SERVICE_ERROR);res.setErrorMessage(StringUtils.toString(t));}channel.send(res);} catch (RemotingException e) {logger.warn(TRANSPORT_FAILED_RESPONSE,"","","Send result to consumer failed, channel is " + channel + ", msg is " + e);}});} catch (Throwable e) {res.setStatus(Response.SERVICE_ERROR);res.setErrorMessage(StringUtils.toString(e));channel.send(res);}}
相关文章:

Dubbo源码解析第一期:如何使用Netty4构建RPC
一、背景 早期学习和使用Dubbo的时候(那时候Dubbo还没成为Apache顶级项目),写过一些源码解读,但随着Dubbo发生了翻天覆地的变化,那些文章早已过时,所以现在计划针对最新的Apache Dubbo源码来进行“阅读理解…...

unity刷新grid,列表
获取UIGrid 组件,更新列表 listParent.GetComponent().repositionNow true;...

蓝桥杯备赛 day 3 —— 高精度(C/C++,零基础,配图)
目录 🌈前言: 📁 高精度的概念 📁 高精度加法和其模板 📁 高精度减法和其模板 📁 高精度乘法和其模板 📁 高精度除法和其模板 📁 总结 🌈前言: 这篇文…...

人形机器人创新发展顶层设计与关键技术布局
系列文章目录 前言 随着新一轮科技革命和产业变革的深入推进,我国高度重视人形机器人的创新发展,提出了一系列具有前瞻性和战略性的指导意见。规划指出,到2025年,我国将初步建立人形机器人创新体系,攻克“大脑”、“小…...

C语言-算法-最小生成树
【模板】最小生成树 题目描述 如题,给出一个无向图,求出最小生成树,如果该图不连通,则输出 orz。 输入格式 第一行包含两个整数 N , M N,M N,M,表示该图共有 N N N 个结点和 M M M 条无向边。 接下来 M M M 行…...

android 扫描某个包下的所有类
注意事项 如果在用Android Studio开发过程中,如果新增了类,扫描不到。只能把APP卸载了,才能扫描到。 可能是Instance Run 的影响。 后面研究一下这篇文章,看看能不能解决 Android 遍历Apk下的所有类文件 package com.trs.nmip.…...

远程ssh 不通的原因之一
背景:我都想大喊一声,我上网是通的, ping网址是通的,ping www.baidu.com 是通的, 怎么都远程不了,报超时;嘿, 别人远程就能行。我都想挠头了。 目录 1. 先 ping 自己,…...

wamp集成环境部署
Windows下Apache服务器搭建 第一步:下载Windows下的最新ZIP压缩包 推荐下载网址:http://www.apachelounge.com/download/ 为了让Apache服务器发挥更好的性能,请根据自己的系统选择下载,如果不清楚自己的系统是64位还是32位&am…...

使用antd design pro 及后端nodejs express 结合minio进行文件的上传和下载管理
使用Ant Design Pro前端框架结合Node.js Express后端服务以及MinIO作为对象存储,实现文件上传和下载管理的基本步骤如下: 1. 安装所需依赖 在Node.js Express项目中安装minio客户端库: npm install minio --save 在前端项目(假…...

Unity常用的优化技巧集锦
Unity性能优化是面试的时候经常被问道的一些内容,今天给大家分享一些常用的Unity的优化技巧和思路,方便大家遇到问题时候参考与学习。 对啦!这里有个游戏开发交流小组里面聚集了一帮热爱学习游戏的零基础小白,也有一些正在从事游…...

c++动态调用dll
在C中动态调用DLL(动态链接库)可以使用Windows API函数。以下是一个简单的示例,演示如何动态加载和调用DLL中的函数: #include <windows.h> #include <iostream>int main() { // 加载DLL HMODULE hModule LoadLibrar…...

使用Python自动化操作手机,自动执行常见任务,例如滑动手势、呼叫、发送短信等等
使用Python自动化操作手机,自动执行常见任务,例如滑动手势、呼叫、发送短信等等。 此自动化脚本将帮助你使用 Python 中的 Android 调试桥 (ADB) 自动化你的智能手机。下面我将展示如何自动执行常见任务,例如滑动手势、呼叫、发送短信等等。 您可以了解有关 ADB 的更多信息,…...

E - Souvenir(图论典型例题)
思路:对于有很多询问的题,一般都是先初始化。我们求出每个点到其他点的最短路径以及相同路径下最大的价值和即可。 代码: #include <bits/stdc.h> #define pb push_back #define a first #define b second using namespace std; type…...

【SpringBoot篇】添加富文本编辑器操作
文章目录 🍔使用步骤⭐首先我们需要安装富文本编辑器⭐在<script>中引入富文本编辑器⭐富文本图片上传接口⭐初始化富文本编辑器⭐调用 初始化富文本编辑器的方法🎈新增🎈编辑🎈保存 ⭐添加按钮⭐实现viewEditor函数&#x…...

前台vue配置
前台 vue环境 1.傻瓜式安装node: 官网下载:https://nodejs.org/zh-cn/2.安装cnpm: >: npm install -g cnpm --registryhttps://registry.npm.taobao.org3.安装vue最新脚手架: >: cnpm install -g vue/cli注:如果2、3步报错,清除缓…...

牛客周赛 Round 18 解题报告 | 珂学家 | 分类讨论计数 + 状态DP
前言 整体评价 前三题蛮简单的,T4是一个带状态的DP,这题如果用背包思路去解,不知道如何搞,感觉有点头痛。所以最后还是选择状态DP来求解。 欢迎关注 珂朵莉 牛客周赛专栏 珂朵莉 牛客小白月赛专栏 A. 游游的整数翻转 这题最好…...

CentOS防火墙基本操作
CentOS操作系统中的防火墙可以使用firewalld或iptables来进行配置。 firewalld(默认): 查看当前状态:systemctl status firewalld 开启/关闭防火墙服务:sudo systemctl start/stop firewalld 设置开机自动启动/不启…...

Shell脚本的编程规范和变量类型
一. 了解编程 1.程序编程风格 面向过程语言 开发的时候 需要一步一步执行 问题规模小,可以步骤化,按部就班处理 以指令为中心,数据服务于指令 C,shell 面向对象语言 开发的时候 将任务当成一个整体 将编程看成是一个…...

C++面试:跳表
目录 跳表介绍 跳表的特点: 跳表的应用场景: C 代码示例: 跳表的特性 跳表示例 总结 跳表(Skip List)是一种支持快速搜索、插入和删除的数据结构,具有相对简单的实现和较高的查询性能。下面是跳表…...

掌握C++20的革命性特性:Concepts
掌握C20的革命性特性:Concepts C20 的新特性 C20 引入了 Concepts,这是一种用于限制类和函数模板的模板类型和非类型参数的命名要求。Concepts 是作为编译时评估的谓词,用于验证传递给模板的模板参数。Concepts 的主要目的是使模板相关的编…...

win11开机后频繁刷新桌面,任务栏不显示,文件资源管理器explorer报错ntdll.dll
win11 开机后桌面频繁刷新,cpu 暴涨,任务栏不出现。 不知道是安装了什么软件还是系统升级造成的,好长时间不重启电脑了,然后重启了下电脑。 结果就是: 现象描述 重启后 输入密码进入后 变得巨慢。好久才进入的桌面。…...

解决Git添加.gitignore文件后不生效的问题
1. 问题描述 如上图所示,在已存在.gitignore文件且已经提交过的Git管理的项目中,其中.class、.jar文件以及.idea目录内的内容全部都还是被Git管理了,可见.gitignore文件并没有生效。 2. 原因发现 .gitignore文件只能作用于 Untracked Files…...

Shell Linux学习笔记
注意:该文章摘抄之百度,仅当做学习笔记供小白使用,若侵权请联系删除! 目录 什么是shell ? Linux正则匹配 grep tar与unzip echo history 重定向 shell 单双引号 位置参数 预定义变量 运算 正则表达式 字符截取命令 …...

kingbase常用SQL总结之锁等待信息
锁信息与等待事件 分析kingbase(pg)数据库锁等待、死锁时需要我们准确的定位等锁或者死锁相关的事务。关于获取锁等待信息或者死锁信息已有经典的SQL可以直接使用,但是需要我们先了解sql语句获取的每个字段的意义。 获取到锁等待事务不能完全…...

「优选算法刷题」:长度最小的子数组
一、题目 给定一个含有 n 个正整数的数组和一个正整数 target 。 找出该数组中满足其总和大于等于 target 的长度最小的 连续子数组 [numsl, numsl1, ..., numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。 示例 1: 输…...

RuoYi-Cloud本地部署--详细教程
文章目录 1、gitee项目地址2、RuoYi-Cloud架构3、本地部署3.1 下载项目3.2 idea打开项目3.3 启动nacos3.4 若依数据库准备3.5 启动redis3.6 修改nacos中的各个模块的配置文件3.7 启动ruoyi前端项目3.8 启动各个微服务模块 4、启动成功 1、gitee项目地址 https://gitee.com/y_p…...

如何优雅的发布一个 TypeScript 软件包?
向 NPM 发布软件包本身并不是一个特别困难的挑战。但是,配置你的 TypeScript 项目以取得成功可能是一个挑战。你的软件包能在大多数项目上运行吗?用户能否使用类型提示和自动完成功能?它能与 ES Modules (ESM) 和 CommonJS (CJS) 风格的导入一…...

总结的太到位:python 多线程系列详解
前言: 上vip课的时候每次讲到框架的执行,就会有好学的同学问用多线程怎么执行,然后我每次都会说在测开课程会详细讲解,这并不是套路,因为如果你不理解多线程,不清楚什么时候该用什么时候不该用,…...

惬意上手Python —— 装饰器和内置函数
1. Python装饰器 Python中的装饰器是一种特殊类型的函数,它允许用户在不修改原函数代码的情况下,增加或修改函数的行为。 具体来说,装饰器的工作原理基于Python的函数也是对象这一事实,可以被赋值给变量、作为参数传递给其他函数或者作为其他…...

python 调用dll
在Python中,可以使用ctypes库来调用DLL文件。ctypes库是一个标准库,用于在Python中加载共享库(例如DLL文件)并调用其中的函数。 以下是一个简单的示例,演示如何使用ctypes库调用DLL文件中的函数: import c…...