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

Dubbo源码解析-服务调用(七)

一、服务调用流程

服务在订阅过程中,把notify 过来的urls 都转成了invoker,不知道大家是否还记得前面的rpc 过程,protocol也是在服务端和消费端各连接子一个invoker,如下图:

这张图主要展示rpc 主流程,消费端想要远程调用时,他是调用invoker#invoke方法;服务端收到网络请求时,也是直接触发invoker#invoke 方法。,Dubbo 设计invoker 实体的初衷,就是想要统一操作,无论你要做什么方法调用,都请使用invoker 来包装后,使用invoker#invoke来调用这个动作,简化来看,rpc 过程即是如此:

消费端invoker#invoke ------>网络---->服务端invoker#invoke--->ref 服务

 上面的链路是个简化的路径,但在实际的dubbo 调用中,此链条可能会有局部的多层嵌套,如:

消费端invoker#invoke ------>容错策略--->网络---->服务端invoker#invoke--->ref服务

那么此时要重新定义链条吗?那不是个好主意。Dubbo 的做法是这样,将容错策略也包装成invoker 对象: 

FailfastClusterInvoker#invoke--->protocolInvoker.invoke-->网络---->服务端invoker#invoke--->ref 服务

依次类推,dubbo 内部有非常多的invoker 包装类,它们层层嵌套,但rpc流程不关心细节,只傻瓜式地调用其invoke 方法,剩下的逻辑自会传递到最后一个invoker 进行网络调用。 

服务引入时,最终又对生成invoker的做了一层代理,所以熟悉动态代理的知道,当执行服务调用时最先执行到的是InvokerInvocationHandler#invoke方法

public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {try {return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));} catch (Throwable fromJavassist) {// try fall back to JDK proxy factorytry {T proxy = jdkProxyFactory.getProxy(invoker, interfaces);logger.error("Failed to generate proxy by Javassist failed. Fallback to use JDK proxy success. " +"Interfaces: " + Arrays.toString(interfaces), fromJavassist);return proxy;} catch (Throwable fromJdk) {logger.error("Failed to generate proxy by Javassist failed. Fallback to use JDK proxy is also failed. " +"Interfaces: " + Arrays.toString(interfaces) + " Javassist Error.", fromJavassist);logger.error("Failed to generate proxy by Javassist failed. Fallback to use JDK proxy is also failed. " +"Interfaces: " + Arrays.toString(interfaces) + " JDK Error.", fromJdk);throw fromJavassist;}}
}

二、Dubbo过滤器链

Filter(过滤器)在很多框架中都有使用过这个概念,基本上的作用都是类似的,在请求处理前或者处理后做一些通用的逻辑,而且Filter 可以有多个,支持层层嵌套。

Dubbo 的Filter 实现入口是在ProtocolFilterWrapper,因为ProtocolFilterWrapper 是Protocol 的包装类,所以会在SPI 加载的Extension 的时候被自动包装进来。当然filter 要发挥作用,必定还是要
在嵌入到RPC 的调用线中(你马上应该反应过来,嵌入的办法就是包装成invoker)
ProtocolFilterWrapper 作为包装类,会成为其它protocol 的修饰加强外层。因此,protocol 的export 和refer 方法,首先是调用ProtocolFilterWrapper 类的。

暴露服务代码:

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));
}

引入服务代码:

public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {if (UrlUtils.isRegistry(url)) {return protocol.refer(type, url);}FilterChainBuilder builder = getFilterChainBuilder(url);return builder.buildInvokerChain(protocol.refer(type, url), REFERENCE_FILTER_KEY, CommonConstants.CONSUMER);
}

可以看到,两者原来的invoker 对象,都由builder#buildInvokerChain 做了一层包装。
来看一下filterChain 的逻辑

public <T> Invoker<T> buildInvokerChain(final Invoker<T> originalInvoker, String key, String group) {Invoker<T> last = originalInvoker;URL url = originalInvoker.getUrl();//......(省略部分代码)if (!CollectionUtils.isEmpty(filters)) {for (int i = filters.size() - 1; i >= 0; i--) {final Filter filter = filters.get(i);final Invoker<T> next = last;last = new CopyOfFilterChainNode<>(originalInvoker, next, filter);}return new CallbackRegistrationInvoker<>(last, filters);}return last;
}

逻辑较为简单:
1、所有filter 包装进invoker 对象中,invoke 方法直接调对应的filter#invoke。
2、filter 对象首尾相联,前一个filter#invoke 参数,传入后一个filter 的invoker对象
3、最后一个filter.invoke 参数中,直接传原始的invoker 对象
4、filter 的所有获取,按扩展点方式得到
ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group); 

所以这里创建RpcInvocation,执行invoke方法,中间会经过一系列过滤器filter#invoke后,就会来到FailoverClusterInvoker#invoke方法中。

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {RpcInvocation rpcInvocation = new RpcInvocation(serviceModel, method, invoker.getInterface().getName(), protocolServiceKey, args);String serviceKey = url.getServiceKey();rpcInvocation.setTargetServiceUniqueName(serviceKey);// invoker.getUrl() returns consumer url.RpcServiceContext.setRpcContext(url);if (serviceModel instanceof ConsumerModel) {rpcInvocation.put(Constants.CONSUMER_MODEL, serviceModel);rpcInvocation.put(Constants.METHOD_MODEL, ((ConsumerModel) serviceModel).getMethodModel(method));}if (ProfilerSwitch.isEnableSimpleProfiler()) {ProfilerEntry parentProfiler = Profiler.getBizProfiler();ProfilerEntry bizProfiler;boolean containsBizProfiler = false;if (parentProfiler != null) {containsBizProfiler = true;bizProfiler = Profiler.enter(parentProfiler, "Receive request. Client invoke begin.");} else {bizProfiler = Profiler.start("Receive request. Client invoke begin.");}rpcInvocation.put(Profiler.PROFILER_KEY, bizProfiler);try {return invoker.invoke(rpcInvocation).recreate();} finally {Profiler.release(bizProfiler);if (!containsBizProfiler) {int timeout;Object timeoutKey = rpcInvocation.getObjectAttachmentWithoutConvert(TIMEOUT_KEY);if (timeoutKey instanceof Integer) {timeout = (Integer) timeoutKey;} else {timeout = url.getMethodPositiveParameter(methodName, TIMEOUT_KEY, DEFAULT_TIMEOUT);}long usage = bizProfiler.getEndTime() - bizProfiler.getStartTime();if ((usage / (1000_000L * ProfilerSwitch.getWarnPercent())) > timeout) {StringBuilder attachment = new StringBuilder();for (Map.Entry<String, Object> entry : rpcInvocation.getObjectAttachments().entrySet()) {attachment.append(entry.getKey()).append("=").append(entry.getValue()).append(";\n");}logger.warn(String.format("[Dubbo-Consumer] execute service %s#%s cost %d.%06d ms, this invocation almost (maybe already) timeout\n" +"invocation context:\n%s" +"thread info: \n%s",protocolServiceKey, methodName, usage / 1000_000, usage % 1000_000,attachment, Profiler.buildDetail(bizProfiler)));}}}}return invoker.invoke(rpcInvocation).recreate();
}

下面代码主要做了三件事情:

1、List<Invoker<T>> invokers = list(invocation);获取当前服务可执行的所有invokers数量。

2、LoadBalance loadbalance = initLoadBalance(invokers, invocation);利用SPI机制获取当前负载均衡策略LoadBalance。

3、doInvoke(invocation, invokers, loadbalance)。负载均衡策略会选择出一个invoker然后执行。 

public Result invoke(final Invocation invocation) throws RpcException {checkWhetherDestroyed();InvocationProfilerUtils.enterDetailProfiler(invocation, () -> "Router route.");List<Invoker<T>> invokers = list(invocation);InvocationProfilerUtils.releaseDetailProfiler(invocation);LoadBalance loadbalance = initLoadBalance(invokers, invocation);RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);InvocationProfilerUtils.enterDetailProfiler(invocation, () -> "Cluster " + this.getClass().getName() + " invoke.");try {return doInvoke(invocation, invokers, loadbalance);} finally {InvocationProfilerUtils.releaseDetailProfiler(invocation);}
}

 我们知道一个服务的invokers都存在了registryDirectory中

protected List<Invoker<T>> list(Invocation invocation) throws RpcException {return getDirectory().list(invocation);
}public List<Invoker<T>> list(Invocation invocation) throws RpcException {if (destroyed) {throw new RpcException("Directory already destroyed .url: " + getUrl());}BitList<Invoker<T>> availableInvokers;// use clone to avoid being modified at doList().if (invokersInitialized) {availableInvokers = validInvokers.clone();} else {availableInvokers = invokers.clone();}List<Invoker<T>> routedResult = doList(availableInvokers, invocation);return Collections.unmodifiableList(routedResult);
}

然后回利用路由规则调用routerChain.route方法,路由出正确的invokers,具体路由功能后面再讲解。

public List<Invoker<T>> doList(BitList<Invoker<T>> invokers, Invocation invocation) {if (forbidden && shouldFailFast) {// 1. No service provider 2. Service providers are disabledthrow new RpcException(RpcException.FORBIDDEN_EXCEPTION, "No provider available from registry " +getUrl().getAddress() + " for service " + getConsumerUrl().getServiceKey() + " on consumer " +NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion() +", please check status of providers(disabled, not registered or in blacklist).");}if (multiGroup) {return this.getInvokers();}try {// Get invokers from cache, only runtime routers will be executed.List<Invoker<T>> result = routerChain.route(getConsumerUrl(), invokers, invocation);return result == null ? BitList.emptyList() : result;} catch (Throwable t) {logger.error("Failed to execute router: " + getUrl() + ", cause: " + t.getMessage(), t);return BitList.emptyList();}
}

三、负载均衡

下面这行代码会获取到当前配置的负载均衡策略

LoadBalance loadbalance = initLoadBalance(invokers, invocation)

最终也是利用SPI机制加载,ExtensionLoader#getExtension(DEFAULT_LOADBALANCE) ,而默认的负载均衡策略可以看到是随机。

protected LoadBalance initLoadBalance(List<Invoker<T>> invokers, Invocation invocation) {ApplicationModel applicationModel = ScopeModelUtil.getApplicationModel(invocation.getModuleModel());if (CollectionUtils.isNotEmpty(invokers)) {return applicationModel.getExtensionLoader(LoadBalance.class).getExtension(invokers.get(0).getUrl().getMethodParameter(RpcUtils.getMethodName(invocation), LOADBALANCE_KEY, DEFAULT_LOADBALANCE));} else {return applicationModel.getExtensionLoader(LoadBalance.class).getExtension(DEFAULT_LOADBALANCE);}
}

然后继续执行来到FailoverClusterInvoker#doInvoke方法,首先获取执行次数for循环进行调用,只要不是正常返回,就再试一次,默认的len为3次,这也是dubbo基础常识,在这里可以体现出来。

然后调用下面的select方法获取到一个invoker,最终会执行到loadBalance#doSelect方法,当然不同的负载均衡策略获取invoker的算法也不同,这里应用了模板模式。

public Result doInvoke(Invocation invocation, final List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {List<Invoker<T>> copyInvokers = invokers;checkInvokers(copyInvokers, invocation);String methodName = RpcUtils.getMethodName(invocation);int len = calculateInvokeTimes(methodName);// retry loop.RpcException le = null; // last exception.List<Invoker<T>> invoked = new ArrayList<Invoker<T>>(copyInvokers.size()); // invoked invokers.Set<String> providers = new HashSet<String>(len);for (int i = 0; i < len; i++) {//Reselect before retry to avoid a change of candidate `invokers`.//NOTE: if `invokers` changed, then `invoked` also lose accuracy.if (i > 0) {checkWhetherDestroyed();copyInvokers = list(invocation);// check againcheckInvokers(copyInvokers, invocation);}Invoker<T> invoker = select(loadbalance, invocation, copyInvokers, invoked);invoked.add(invoker);RpcContext.getServiceContext().setInvokers((List) invoked);boolean success = false;try {Result result = invokeWithContext(invoker, invocation);if (le != null && logger.isWarnEnabled()) {logger.warn("Although retry the method " + methodName+ " in the service " + getInterface().getName()+ " was successful by the provider " + invoker.getUrl().getAddress()+ ", but there have been failed providers " + providers+ " (" + providers.size() + "/" + copyInvokers.size()+ ") from the registry " + directory.getUrl().getAddress()+ " on the consumer " + NetUtils.getLocalHost()+ " using the dubbo version " + Version.getVersion() + ". Last error is: "+ le.getMessage(), le);}success = true;return result;} catch (RpcException e) {if (e.isBiz()) { // biz exception.throw e;}le = e;} catch (Throwable e) {le = new RpcException(e.getMessage(), e);} finally {if (!success) {providers.add(invoker.getUrl().getAddress());}}}throw new RpcException(le.getCode(), "Failed to invoke the method "+ methodName + " in the service " + getInterface().getName()+ ". Tried " + len + " times of the providers " + providers+ " (" + providers.size() + "/" + copyInvokers.size()+ ") from the registry " + directory.getUrl().getAddress()+ " on the consumer " + NetUtils.getLocalHost() + " using the dubbo version "+ Version.getVersion() + ". Last error is: "+ le.getMessage(), le.getCause() != null ? le.getCause() : le);
}

RandomLoadBalance#doSelect方法,具体代码逻辑不做详细解释,主要是根据服务设置的权重进行随机选取,权重大的被选中的概率就会大一点,有兴趣的可以断点研究下,这里只是让大家了解负载均衡就是这么简单的东西,我们也可以创建一个自己的负载均衡策略,只需要继承AbstractLoadBalance并实现自己的doSelect方法,然后还需要配置指定用我们所写的负载均衡策略,从代码流程上可以看出来,dubbo的负载均衡策略其实是客户端负载均衡。

protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {// Number of invokersint length = invokers.size();if (!needWeightLoadBalance(invokers,invocation)){return invokers.get(ThreadLocalRandom.current().nextInt(length));}// Every invoker has the same weight?boolean sameWeight = true;// the maxWeight of every invokers, the minWeight = 0 or the maxWeight of the last invokerint[] weights = new int[length];// The sum of weightsint totalWeight = 0;for (int i = 0; i < length; i++) {int weight = getWeight(invokers.get(i), invocation);// SumtotalWeight += weight;// save for later useweights[i] = totalWeight;if (sameWeight && totalWeight != weight * (i + 1)) {sameWeight = false;}}if (totalWeight > 0 && !sameWeight) {// If (not every invoker has the same weight & at least one invoker's weight>0), select randomly based on totalWeight.int offset = ThreadLocalRandom.current().nextInt(totalWeight);// Return a invoker based on the random value.for (int i = 0; i < length; i++) {if (offset < weights[i]) {return invokers.get(i);}}}// If all invokers have the same weight value or totalWeight=0, return evenly.return invokers.get(ThreadLocalRandom.current().nextInt(length));
}

 那么dubbo中已经支持的负载均衡策略也有很多,依次是ConsistentHashLoadBalance(一次性哈希),LeastActiveLoadBalance(最少活跃数),RandomLoadBalance(按照权重随机),RoundRobinLoadBalance(轮训),ShortestResponseLoadBalance(最短响应策略);不过生产环境中权重策略跟轮训策略是使用的最多的两种负载均衡方式。

四、集群容错

集群容错的实现方式其实在生成invoker的时候已经看到了,dubbo会将生成的invoker最外层在包装一层ClusterInvoker,这里又利用到了一种设计模式叫做装饰者模式。因为远程服务调用过程总肯定会先执行到ClusterInvoker#doInvoke方法,那么每个容错ClusterInvoker实现类会按照自己的方式去执行,当然我们也能继承AbstractClusterInvoker方法,然后实现doInvoke方法,然后配置自己的集群容错策略,所以这个功能跟负载均衡一样简单。

Dubbo为应对不同的业务场景和需求,提供了以下几种集群容错策略,具体源码有兴趣的可以单独研究:

1. FailoverClusterInvoker(默认)
失败一次自动切换,自动重试其他invoker,默认三次。适用于读操作多、对实时性要求不高的场景。
2. Failfast
快速失败,立即报错,只发起一次调用。适用于非幂等性操作或实时性要求极高的场景。
3. Failsafe
失败安全,出现异常时直接忽略。适用于对结果不敏感的操作,如日志记录。
4. Failback
失败自动恢复,记录失败请求,定时重发。适用于可以异步处理且对实时性要求不高的操作。
5. Forking
并行调用多个服务器,只要一个成功即返回。注意提高响应速度的同时,需注意资源消耗和并行度控制。
6. Broadcast
广播调用所有服务提供者,任一报错即报错。适用于需要全局一致性的场景,如全局缓存更新。
选择合适的集群容错策略,对于优化Dubbo应用的性能和可靠性至关重要。

到此Dubbo的服务调用过程讲解完毕。

相关文章:

Dubbo源码解析-服务调用(七)

一、服务调用流程 服务在订阅过程中&#xff0c;把notify 过来的urls 都转成了invoker&#xff0c;不知道大家是否还记得前面的rpc 过程&#xff0c;protocol也是在服务端和消费端各连接子一个invoker&#xff0c;如下图&#xff1a; 这张图主要展示rpc 主流程&#xff0c;消费…...

svn 崩溃、 cleanup失败 怎么办

在使用svn的过程中&#xff0c;可能出现整个svn崩溃&#xff0c; 例如cleanup 失败的情况&#xff0c;类似于 这时可以下载本贴资源文件并解压。 或者直接访问网站 SQLite Download Page 进行下载 解压后得到 sqlite3.exe 放到发生问题的svn根目录的.svn路径下 右键呼出pow…...

【Linux系列】NTP时间同步服务器搭建完整指南

在分布式系统和高可用环境中&#xff0c;时间同步是至关重要的。特别是对于银行、金融等关键业务系统&#xff0c;精准的时间同步不仅关系到系统的稳定性&#xff0c;还直接影响交易处理、日志管理、日终结算等功能。本文将介绍NTP&#xff08;Network Time Protocol&#xff0…...

go 结构体方法

在 Go 语言中&#xff0c;结构体方法是指附加到结构体类型上的函数。这些方法可以通过结构体的实例来调用。方法的接收者&#xff08;receiver&#xff09;指定了该方法属于哪个结构体类型。接收者可以是一个值类型或指针类型。 定义结构体方法 下面是如何为一个结构体定义方…...

DHCP服务(包含配置过程)

目录 一、 DHCP的定义 二、 使用DHCP的好处 三、 DHCP的分配方式 四、 DHCP的租约过程 1. 客户机请求IP 2. 服务器响应 3. 客户机选择IP 4. 服务器确定租约 5. 重新登录 6. 更新租约 五、 DHCP服务配置过程 一、 DHCP的定义 DHCP&#xff08;Dynamic Host Configur…...

uniapp内嵌的webview H5与应用通信

H5端&#xff1a; 1、找到index.html引入依赖 <script type"text/javascript" src"https://unpkg.com/dcloudio/uni-webview-js0.0.3/index.js"></script> 2、在需要通讯处发送消息 uni.postMessage({data:{code:200,msg:"处理完成&q…...

Android OpenGL ES详解——绘制圆角矩形

1、绘制矩形 代码如下&#xff1a; renderer类&#xff1a; package com.example.roundrectimport android.content.Context import android.opengl.GLES30 import android.opengl.GLSurfaceView.Renderer import com.opengllib.data.VertexArray import com.opengllib.prog…...

网络基础二

文章目录 协议定制&#xff0c;序列化和反序列化应用层网络版计算器协议的定制序列反序列化序列化未复用版 反序列化 TCP是面向字节流的&#xff0c;你怎么保证&#xff0c;你读取上来的数据&#xff0c;是‘’一个“ “完整””的报文呢&#xff1f; 我们没有区分字符串里面有…...

从Full-Text Search全文检索到RAG检索增强

从Full-Text Search全文检索到RAG检索增强 时光飞逝&#xff0c;转眼间六年过去了&#xff0c;六年前铁蛋优化单表千万级数据查询性能的场景依然历历在目&#xff0c;铁蛋也从最开始做CRUD转行去了大数据平台开发&#xff0c;混迹包装开源的业务&#xff0c;机缘巧合下做了实时…...

springMVC 全局异常统一处理

全局异常处理⽅式⼀: 1、配置简单异常处理器 配置 SimpleMappingExceptionResolver 对象: <!-- 配置全局异常统⼀处理的 Bean &#xff08;简单异常处理器&#xff09; --> <bean class"org.springframework.web.servlet.handler.SimpleMappingExceptionReso…...

qt ubuntu i386 系统

sudo ln -s cmake-3.31.0-linux-x86_64/bin/* /usr/local/bin 【Ubuntu20.4安装QT6 - CSDN App】Ubuntu20.4安装QT6_ubuntu安装qt6-CSDN博客 sudo ../configure -release -platform linux-g-64 -static -nomake examples -nomake demos -no-qt3support -no-script -no-scriptt…...

BUUCTF—Reverse—helloword(6)

一道安卓逆向的签到题 下载附件 使用JADX-gui反编译工具打开&#xff08;注意配环境&#xff09;&#xff0c;找到主函数 jadx 本身就是一个开源项目&#xff0c;源代码已经在 Github 上开源了 官方地址&#xff1a;GitHub - skylot/jadx: Dex to Java decompiler 发现flag …...

深入解析下oracle date底层存储方式

之前我们介绍了varchar2和char的数据库底层存储格式&#xff0c;今天我们介绍下date类型的数据存储格式&#xff0c;并通过测试程序快速获取一个日期。 一、环境搭建 1.1&#xff0c;创建表 我们还是创建一个测试表t_code&#xff0c;并插入数据&#xff1a; 1.2&#xff0c;…...

Elasticsearch 开放推理 API 增加了对 IBM watsonx.ai Slate 嵌入模型的支持

作者&#xff1a;来自 Elastic Saikat Sarkar 使用 Elasticsearch 向量数据库构建搜索 AI 体验时如何使用 IBM watsonx™ Slate 文本嵌入。 Elastic 很高兴地宣布&#xff0c;通过集成 IBM watsonx™ Slate 嵌入模型&#xff0c;我们的开放推理 API 功能得以扩展&#xff0c;这…...

如何搭建一个小程序:从零开始的详细指南

在当今数字化时代&#xff0c;小程序以其轻便、无需下载安装即可使用的特点&#xff0c;成为了连接用户与服务的重要桥梁。无论是零售、餐饮、教育还是娱乐行业&#xff0c;小程序都展现了巨大的潜力。如果你正考虑搭建一个小程序&#xff0c;本文将为你提供一个从零开始的详细…...

NFS搭建

NFS搭建 单节点安装配置服务器安装配置启动并使NFS服务开机自启客户端挂载查看是否能发现服务器的共享文件夹创建挂载目录临时挂载自动挂载 双节点安装配置服务器安装配置服务端配置NFS服务端配置Keepalived编辑nfs_check.sh监控脚本安装部署RsyncInofity 客户端 单节点安装配置…...

RNN与LSTM,通过Tensorflow在手写体识别上实战

简介&#xff1a;本文从RNN与LSTM的原理讲起&#xff0c;在手写体识别上进行代码实战。同时列举了优化思路与优化结果&#xff0c;都是基于Tensorflow1.14.0的环境下&#xff0c;希望能给您的神经网络学习带来一定的帮助。如果您觉得我讲的还行&#xff0c;希望可以得到您的点赞…...

Docker部署FastAPI实战

在现代 Web 开发领域&#xff0c;FastAPI 作为一款高性能的 Python 框架&#xff0c;正逐渐崭露头角&#xff0c;它凭借简洁的语法、快速的执行速度以及出色的类型提示功能&#xff0c;深受开发者的喜爱。而 Docker 容器化技术则为 FastAPI 应用的部署提供了便捷、高效且可移植…...

【Python数据分析五十个小案例】电影评分分析:使用Pandas分析电影评分数据,探索评分的分布、热门电影、用户偏好

博客主页&#xff1a;小馒头学python 本文专栏: Python数据分析五十个小案例 专栏简介&#xff1a;分享五十个Python数据分析小案例 在现代电影行业中&#xff0c;数据分析已经成为提升用户体验和电影推荐的关键工具。通过分析电影评分数据&#xff0c;我们可以揭示出用户的…...

Vue2学习记录

前言 这篇笔记&#xff0c;是根据B站尚硅谷的Vue2网课学习整理的&#xff0c;用来学习的 如果有错误&#xff0c;还请大佬指正 Vue核心 Vue简介 Vue (发音为 /vjuː/&#xff0c;类似 view) 是一款用于构建用户界面的 JavaScript 框架。 它基于标准 HTML、CSS 和 JavaScr…...

线程与协程

1. 线程与协程 1.1. “函数调用级别”的切换、上下文切换 1. 函数调用级别的切换 “函数调用级别的切换”是指&#xff1a;像函数调用/返回一样轻量地完成任务切换。 举例说明&#xff1a; 当你在程序中写一个函数调用&#xff1a; funcA() 然后 funcA 执行完后返回&…...

最新SpringBoot+SpringCloud+Nacos微服务框架分享

文章目录 前言一、服务规划二、架构核心1.cloud的pom2.gateway的异常handler3.gateway的filter4、admin的pom5、admin的登录核心 三、code-helper分享总结 前言 最近有个活蛮赶的&#xff0c;根据Excel列的需求预估的工时直接打骨折&#xff0c;不要问我为什么&#xff0c;主要…...

Unit 1 深度强化学习简介

Deep RL Course ——Unit 1 Introduction 从理论和实践层面深入学习深度强化学习。学会使用知名的深度强化学习库&#xff0c;例如 Stable Baselines3、RL Baselines3 Zoo、Sample Factory 和 CleanRL。在独特的环境中训练智能体&#xff0c;比如 SnowballFight、Huggy the Do…...

Python Ovito统计金刚石结构数量

大家好,我是小马老师。 本文介绍python ovito方法统计金刚石结构的方法。 Ovito Identify diamond structure命令可以识别和统计金刚石结构,但是无法直接输出结构的变化情况。 本文使用python调用ovito包的方法,可以持续统计各步的金刚石结构,具体代码如下: from ovito…...

三分算法与DeepSeek辅助证明是单峰函数

前置 单峰函数有唯一的最大值&#xff0c;最大值左侧的数值严格单调递增&#xff0c;最大值右侧的数值严格单调递减。 单谷函数有唯一的最小值&#xff0c;最小值左侧的数值严格单调递减&#xff0c;最小值右侧的数值严格单调递增。 三分的本质 三分和二分一样都是通过不断缩…...

Docker拉取MySQL后数据库连接失败的解决方案

在使用Docker部署MySQL时&#xff0c;拉取并启动容器后&#xff0c;有时可能会遇到数据库连接失败的问题。这种问题可能由多种原因导致&#xff0c;包括配置错误、网络设置问题、权限问题等。本文将分析可能的原因&#xff0c;并提供解决方案。 一、确认MySQL容器的运行状态 …...

五子棋测试用例

一.项目背景 1.1 项目简介 传统棋类文化的推广 五子棋是一种古老的棋类游戏&#xff0c;有着深厚的文化底蕴。通过将五子棋制作成网页游戏&#xff0c;可以让更多的人了解和接触到这一传统棋类文化。无论是国内还是国外的玩家&#xff0c;都可以通过网页五子棋感受到东方棋类…...

【Linux】Linux安装并配置RabbitMQ

目录 1. 安装 Erlang 2. 安装 RabbitMQ 2.1.添加 RabbitMQ 仓库 2.2.安装 RabbitMQ 3.配置 3.1.启动和管理服务 4. 访问管理界面 5.安装问题 6.修改密码 7.修改端口 7.1.找到文件 7.2.修改文件 1. 安装 Erlang 由于 RabbitMQ 是用 Erlang 编写的&#xff0c;需要先安…...

土建施工员考试:建筑施工技术重点知识有哪些?

《管理实务》是土建施工员考试中侧重实操应用与管理能力的科目&#xff0c;核心考查施工组织、质量安全、进度成本等现场管理要点。以下是结合考试大纲与高频考点整理的重点内容&#xff0c;附学习方向和应试技巧&#xff1a; 一、施工组织与进度管理 核心目标&#xff1a; 规…...

链式法则中 复合函数的推导路径 多变量“信息传递路径”

非常好&#xff0c;我们将之前关于偏导数链式法则中不能“约掉”偏导符号的问题&#xff0c;统一使用 二重复合函数&#xff1a; z f ( u ( x , y ) , v ( x , y ) ) \boxed{z f(u(x,y),\ v(x,y))} zf(u(x,y), v(x,y))​ 来全面说明。我们会展示其全微分形式&#xff08;偏导…...