当前位置: 首页 > 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…...

TMS FNC UI Pack 5.4.0 for Delphi 12

TMS FNC UI Pack是适用于 Delphi 和 C Builder 的多功能 UI 控件的综合集合&#xff0c;提供跨 VCL、FMX、LCL 和 TMS WEB Core 等平台的强大功能。这个统一的组件集包括基本工具&#xff0c;如网格、规划器、树视图、功能区和丰富的编辑器&#xff0c;确保兼容性和简化的开发。…...

Redis主从架构

Redis&#xff08;Remote Dictionary Server&#xff09;是一个开源的、高性能的键值对存储系统&#xff0c;广泛应用于缓存、消息队列、实时分析等场景。为了提高系统的可用性、可靠性和读写性能&#xff0c;Redis提供了主从复制&#xff08;Master-Slave Replication&#xf…...

logback动态获取nacos配置

文章目录 前言一、整体思路二、使用bootstrap.yml三、增加环境变量四、pom文件五、logback-spring.xml更改总结 前言 主要是logback动态获取nacos的配置信息,结尾完整代码 项目springcloudnacosplumelog&#xff0c;使用的时候、特别是部署的时候&#xff0c;需要改环境&#…...

KETTLE安装部署V2.0

一、前置准备工作 JDK&#xff1a;下载JDK (1.8)&#xff0c;安装并配置 JAVA_HOME 环境变量&#xff0c;并将其下的 bin 目录追加到 PATH 环境变量中。如果你的环境中已存在&#xff0c;可以跳过这步。KETTLE&#xff08;8.2&#xff09;压缩包&#xff1a;LHR提供关闭防火墙…...

[RabbitMQ] 保证消息可靠性的三大机制------消息确认,持久化,发送方确认

&#x1f338;个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 &#x1f3f5;️热门专栏: &#x1f9ca; Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm1001.2014.3001.5482 &#x1f355; Collection与…...

aws服务--机密数据存储AWS Secrets Manager(1)介绍和使用

一、介绍 1、简介 AWS Secrets Manager 是一个完全托管的服务,用于保护应用程序、服务和 IT 资源中的机密信息。它支持安全地存储、管理和访问应用程序所需的机密数据,比如数据库凭证、API 密钥、访问密钥等。通过 Secrets Manager,你可以轻松管理、轮换和访问这些机密信息…...

Java设计模式笔记(一)

Java设计模式笔记&#xff08;一&#xff09; &#xff08;23种设计模式由于篇幅较大分为两篇展示&#xff09; 一、设计模式介绍 1、设计模式的目的 让程序具有更好的&#xff1a; 代码重用性可读性可扩展性可靠性高内聚&#xff0c;低耦合 2、设计模式的七大原则 单一职…...

Unity3d C# 实现一个基于UGUI的自适应尺寸图片查看器(含源码)

前言 Unity3d实现的数字沙盘系统中&#xff0c;总有一些图片或者图片列表需要点击后弹窗显示大图&#xff0c;这个弹窗在不同尺寸分辨率的图片查看处理起来比较麻烦&#xff0c;所以&#xff0c;需要图片能够根据容器的大小自适应地进行缩放&#xff0c;兼容不太尺寸下的横竖图…...

【es6进阶】vue3中的数据劫持的最新实现方案的proxy的详解

vuejs中实现数据的劫持,v2中使用的是Object.defineProperty()来实现的&#xff0c;在大版本v3中彻底重写了这部分&#xff0c;使用了proxy这个数据代理的方式&#xff0c;来修复了v2中对数组和对象的劫持的遗留问题。 proxy是什么 Proxy 用于修改某些操作的默认行为&#xff0…...

w~视觉~3D~合集3

我自己的原文哦~ https://blog.51cto.com/whaosoft/12538137 #SIF3D 通过两种创新的注意力机制——三元意图感知注意力&#xff08;TIA&#xff09;和场景语义一致性感知注意力&#xff08;SCA&#xff09;——来识别场景中的显著点云&#xff0c;并辅助运动轨迹和姿态的预测…...

专注于网站营销服务/谷歌google官方下载

1.创建index.jsp页面。新建表单 注意&#xff1a; action 填写 input&#xff0c;这个之后再web.xml配置时要注意对应。 method 提交 postt <body> <form action"input" method "post"> 用户名&#xff1a;<input type "text"…...

wordpress 仿站/企业营销网站制作

背景&#xff1a;在做项目时&#xff0c;经常会遇到这样的表结构在主表的中有一列保存的是用逗号隔开ID。如&#xff0c;当一个员工从属多个部门时、当一个项目从属多个城市时、当一个设备从属多个项目时&#xff0c;很多人都会在员工表中加入一个deptIds VARCHAR(1000)列(本文…...

古风网站建设模板/seo入门基础教程

一、什么是序列化 在我们存储数据或者网络传输数据的时候&#xff0c;需要对我们的对象进行处理&#xff0c;把对象处理成方便存储和传输的数据格式。这个过程叫序列化&#xff0c;不同的序列化结果也不同&#xff0c;但目的是一样的&#xff0c;都是为了存储和传输 在Python中…...

封面设计网站/买卖交易平台

游戏中的内容总是在不断的优化&#xff0c;不断地给玩家们提供一些更加便利的系统&#xff0c;可以让玩家在游戏中更加轻松一些&#xff0c;初代老玩家们看到这些系统都表示既非常羡慕&#xff0c;开荒时的困难是新玩家们无法想象到的。在本周的维护更新之后&#xff0c;游戏中…...

汕头建设网站/如何引流与推广

碰到一个诡异的bitmap回收问题&#xff0c;抛出了使用了recycled的bitmap。最终分析是Bitmap.createBitmap(Bitmap bitmap)造成的&#xff0c;Bitmap.createBitmap(。。。)都有此可能。这个方法的注释如下&#xff1a;Returns an immutable bitmap from subset of the source b…...

贾汪徐州网站开发/优化关键词排名推广

详细介绍了Wes的想法.在自定义PathEvaluator中,您需要设置分支状态以记住第一个关系的方向.所有后续访问都会检查最后一个关系是否与方向匹配.在伪代码中&#xff1a;class SameDirectionPathEvaluator implements PathEvaluator {public Evaluation evaluate(Path path, Branc…...