Dubbo 路由及负载均衡性能优化

作者:vivo 互联网中间件团队- Wang Xiaochuang

本文主要介绍在vivo内部针对Dubbo路由模块及负载均衡的一些优化手段,主要是异步化+缓存,可减少在RPC调用过程中路由及负载均衡的CPU消耗,极大提升调用效率。

一、概要

vivo内部Java技术栈业务使用的是Apache Dubbo框架,基于开源社区2.7.x版本定制化开发。在海量微服务集群的业务实践中,我们发现Dubbo有一些性能瓶颈的问题会极大影响业务逻辑的执行效率,尤其是在集群规模数量较大时(提供方数量>100)时,路由及负载均衡方面有着较大的CPU消耗,从采集的火焰图分析高达30%。为此我们针对vivo内部常用路由策略及负载均衡进行相关优化,并取得了较好的效果。接下来主要跟大家分析一下相关问题产生的根源,以及我们采用怎样的方式来解决这些问题。当前vivo内部使用的Dubbo的主流版本是基于2.7.x进行相关定制化开发。

二、背景知识

2.1 Dubbo客户端调用流程

1. 相关术语介绍

图片

2. 主要流程

客户端通过本地代理Proxy调用ClusterInvoker,ClusterInvoker从服务目录Directory获取服务列表后经过路由链获取新的服务列表、负载均衡从路由后的服务列表中根据不同的负载均衡策略选取一个远端Invoker后再发起远程RPC调用。

图片

2.2 Dubbo路由机制

Dubbo的路由机制实际是基于简单的责任链模式实现,同时Router继承了Comparable接口,自定义的路由可以设置不同的优先级进而定制化责任链上Router的顺序。基于责任链模式可以支持多种路由策略串行执行如就近路由+标签路由,或条件路由+就近路由等,且路由的配置支持基于接口级的配置也支持基于应用级的配置。常见的路由方式主要有:就近路由,条件路由,标签路由等。具体的执行过程如下图所示:

图片

1. 核心类

Dubbo路由的核心类主要有:RouterChain、RouterFactory 与 Router 。

(1)RouterChain

RouterChain是路由链的入口,其核心字段有

  • invokers(List<invoker> 类型)

初始服务列表由服务目录Directory设置,当前RouterChain要过滤的Invoker集合

  • builtinRouters(List类型)

当前RouterChain包含的自动激活的Router集合

  • routers(List类型)

包括所有要使用的路由由builtinRouters加上通过addRouters()方法添加的Router对象

RouterChain核心逻辑:

public class RouterChain<T> {// 注册中心最后一次推送的服务列表private List<Invoker<T>> invokers = Collections.emptyList();// 所有路由,包括原生Dubbo基于注册中心的路由规则如“route://” urls .private volatile List<Router> routers = Collections.emptyList();// 初始化自动激活的路由private List<Router> builtinRouters = Collections.emptyList();private RouterChain(URL url) {//通过ExtensionLoader加载可自动激活的RouterFactoryList<RouterFactory> extensionFactories = ExtensionLoader.getExtensionLoader(RouterFactory.class).getActivateExtension(url, ROUTER_KEY);// 由工厂类生成自动激活的路由策略List<Router> routers = extensionFactories.stream().map(factory -> factory.getRouter(url)).collect(Collectors.toList());initWithRouters(routers);}// 添加额外路由public void addRouters(List<Router> routers) {List<Router> newRouters = new ArrayList<>();newRouters.addAll(builtinRouters);newRouters.addAll(routers);Collections.sort(newRouters, comparator);this.routers = newRouters;}public List<Invoker<T>> route(URL url, Invocation invocation) {List<Invoker<T>> finalInvokers = invokers;// 遍历全部的Router对象,执行路由规则for (Router router : routers) {finalInvokers = router.route(finalInvokers, url, invocation);}return finalInvokers;}
}

(2)RouterFactory为Router的工厂类

RouterFactory接口定义:

@SPI
public interface RouterFactory {@Adaptive("protocol")Router getRouter(URL url);
}

(3)Router

Router是真正的路由实现策略,由RouterChain进行调用,同时Router继承了Compareable接口,可以根据业务逻辑设置不同的优先级。

Router主要接口定义:

public interface Router extends Comparable<Router> {/**** @param invokers   带过滤实例列表* @param url        消费方url* @param invocation 会话信息* @return routed invokers* @throws RpcException*/<T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException;/*** 当注册中心的服务列表发现变化,或有动态配置变更会触发实例信息的变化* 当时2.7.x的Dubbo并没有真正使用这个方法,可基于此方法进行路由缓存* @param invokers invoker list* @param <T>      invoker's type*/default <T> void notify(List<Invoker<T>> invokers) {}}

2. 同机房优先路由的实现

为方便大家了解路由的实现,给大家展示一下就近路由的核心代码逻辑:

public <T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL consumerUrl, Invocation invocation) throws RpcException {if (!this.enabled) {return invokers;}// 获取本地机房信息String local = getSystemProperty(LOC);if (invokers == null || invokers.size() == 0) {return invokers;}List<Invoker<T>> result = new ArrayList<Invoker<T>>();for (Invoker invoker: invokers) {// 获取与本地机房一致的invoker并加入列表中String invokerLoc = getProperty(invoker, invocation, LOC);if (local.equals(invokerLoc)) {result.add(invoker);}}if (result.size() > 0) {if (fallback){// 开启服务降级,available.ratio = 当前机房可用服务节点数量 / 集群可用服务节点数量int curAvailableRatio = (int) Math.floor(result.size() * 100.0d / invokers.size());if (curAvailableRatio <= availableRatio) {return invokers;}}return result;} else if (force) {return result;} else {return invokers;}}

2.3 Dubbo负载均衡

Dubbo的负载均衡实现比较简单基本都是继承抽象类进行实现,主要作用就是根据具体的策略在路由之后的服务列表中筛选一个实例进行远程RPC调用,默认的负载均衡策略是随机。

整体类图如下所示:

图片

LoadBalance接口定义:

@SPI(RandomLoadBalance.NAME)
public interface LoadBalance {/*** 从服务列表中筛选一个.** @param invokers   invokers.* @param url        refer url* @param invocation invocation.* @return selected invoker.*/@Adaptive("loadbalance")<T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException;}

随机负载均衡核心代码解析:

 // 预热过程权重计算static int calculateWarmupWeight(int uptime, int warmup, int weight) {int ww = (int) (uptime / ((float) warmup / weight));return ww < 1 ? 1 : (Math.min(ww, weight));}int getWeight(Invoker<?> invoker, Invocation invocation) {int weight;URL url = invoker.getUrl();// 多注册中心场景下的,注册中心权重获取if (UrlUtils.isRegistryService(url)) {weight = url.getParameter(REGISTRY_KEY + "." + WEIGHT_KEY, DEFAULT_WEIGHT);} else {weight = url.getMethodParameter(invocation.getMethodName(), WEIGHT_KEY, DEFAULT_WEIGHT);if (weight > 0) {// 获取实例启动时间long timestamp = invoker.getUrl().getParameter(TIMESTAMP_KEY, 0L);if (timestamp > 0L) {long uptime = System.currentTimeMillis() - timestamp;if (uptime < 0) {return 1;}// 获取预热时间int warmup = invoker.getUrl().getParameter(WARMUP_KEY, DEFAULT_WARMUP);if (uptime > 0 && uptime < warmup) {weight = calculateWarmupWeight((int)uptime, warmup, weight);}}}}return Math.max(weight, 0);}@Overrideprotected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {// Number of invokersint length = invokers.size();// Every invoker has the same weight?boolean sameWeight = true;// the weight of every invokersint[] weights = new int[length];// the first invoker's weightint firstWeight = getWeight(invokers.get(0), invocation);weights[0] = firstWeight;// The sum of weightsint totalWeight = firstWeight;for (int i = 1; i < length; i++) {int weight = getWeight(invokers.get(i), invocation);// save for later useweights[i] = weight;// SumtotalWeight += weight;if (sameWeight && weight != firstWeight) {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++) {offset -= weights[i];if (offset < 0) {return invokers.get(i);}}}// If all invokers have the same weight value or totalWeight=0, return evenly.return invokers.get(ThreadLocalRandom.current().nextInt(length));}

预热解释

预热是为了让刚启动的实例流量缓慢增加,因为实例刚启动时各种资源可能还没建立连接,相关代码可能还是处于解释执行,仍未变为JIT执行,此时业务逻辑较慢,不应该加载过大的流量,否则有可能造成较多的超时。Dubbo默认预热时间为10分钟,新部署的实例的流量会在预热时间段内层线性增长,最终与其他实例保持一致。Dubbo预热机制的实现就是通过控制权重来实现。如默认权重100,预热时间10分钟,则第一分钟权重为10,第二分钟为20,以此类推。

具体预热效果图如下:

图片

三、问题分析

使用Dubbo的业务方反馈,他们通过火焰图分析发现Dubbo的负载均衡模块+路由模块占用CPU超过了30%,框架层面的使用率严重影响了业务逻辑的执行效率急需进行优化。通过火焰图分析,具体占比如下图,其中该机器在业务忙时的CPU使用率在60%左右,闲时在30%左右。

图片

通过火焰图分析,负载均衡主要的消耗是在 getWeight方法。

图片

路由的主要消耗是在route方法:

  • 同机房优先路由

图片

  • 接口级标签路由+应用级标签路由

图片

这些方法都有一个特点,那就是遍历执行。如负载均衡,针对每一个invoker都需要通过getWeight方法进行权重的计算;就近路由的router方法对于每一个invoker都需要通过url获取及机房信息进行匹配计算。

我们分析一下getWeight及router时间复杂度,发现是O(n)的时间复杂度,而且路由是由路由链组成的,每次每个 Router的route方法调用逻辑都会遍历实例列表,那么当实例列表数量过大时,每次匹配的计算的逻辑过大,那么就会造成大量的计算成本,导致占用大量cpu,同时也导致路由负载均衡效率低下。

综上所述,罪恶的的根源就是遍历导致的,当服务提供方数量越多,影响越大。

四、优化方案

知道了问题所在,我们来分析一下是否有优化空间。

4.1 路由优化

1. 优化一:关闭无效路由

通过火焰图分析,我们发现有部分业务即使完全不使用应用级的标签路由,原生的TagRouter也存在遍历逻辑,原因是为了支持静态的标签路由,其实这部分的开销也不少,那对于根本不会使用应用级标签路由的可以手动进行关闭。关闭方式如下:

  • 客户端统一关闭

dubbo.consumer.router=-tag
  • 服务级别关闭
  • 注解方式:

@DubboReference(parameters = {"router","-tag"})
  • xml方式:

<dubbo:reference id="demoService" check="false" interface="com.dubbo.study.n.api.DemoService" router="-tag" />

2. 优化二:提前计算路由结果并进行缓存

每次路由目前都是进行实时计算,但是在大多数情况下,我们的实例列表是稳定不变的,只有在发布窗口或配置变更窗口内实例列表才会发生变更,那我们是否可以考虑缓存呢。如就近路由,可以以机房为key进行机房实例的全量缓存。针对接口级标签路由可以缓存不同标签值指定的实例信息。

我们知道路由的执行过程是责任链模式,每一个Router的实例列表入参实际上是一个Router的结果,可参考公式:target = rn(…r3(r2(r1(src))))。那么所有的路由可以基于注册中心推送的原始服务列表进行路由计算并缓存,然后不同的路由结果相互取交集就能得到最终的结果,当实例信息发生变更时,缓存失效并重新计算。

3. 缓存更新时机

当注册中心或者动态配置有变更时,相关通知会给到服务目录Directory,Directory收到通知后会重新创建服务列表,并把服务列表同步到路由链RouterChain,RouterChain再按顺序通知其链上的Router,各个Router再进行缓存清除并重新进行路由结果的计算及进行缓存。相关时序图如下所示:

图片

4. 具体路由流程

进入具体路由方法时,先判断是否存在缓存的路由值,且缓存值的epoch必须与上一个路由的epoch需一致,此时缓存才生效,然后缓存值与上个Router的结果取交集。

如果不存在缓存或epoch不一致则重新进行实时的路由计算。

图片

引入epoch的原因主要是保证各个路由策略缓存信息的一致性,保证所有的缓存计算都是基于同一份原始数据。当实例信息发生变更时,epoch会自动进行更新。

5. BitMap引入

上文我们说到,不同的路由策略之间的结果是取交集的,然后最终的结果才送入负载均衡流程。那如何在缓存的同时,加快交集的计算呢。答案就是基于位图:BitMap。

BitMap的基本原理就是用一个bit位来存放某种状态,适用于大规模数据的查找及位运算操作。如在路由场景,先基于全量的推送数据进行计算缓存。如果某个实例被路由选中,则其值为1,若两个路由的结果要取交集,那直接对BitMap进行"&"运行即可。

全量缓存示意图:

图片

路由交集计算示步骤:

按照路由链依次计算,tagRouter->vivoTag->vivoNearestRouter

(1)tagRouter计算逻辑:

  1. 按照Invocation计算出目标的Tag,假设是tag1

  2. 然后从缓存Cache根据key:tag1,取出对应的targetAddrPool

  3. 将原始传入的addrPool与targetAddrPool,得到结果resultAddrPool

  4. 将resultAddrPool传入vivoTagRouter

(2)vivoTag计算逻辑:

  1. 按照Invocation计算出目标的Tag,假设是tabB

  2. 然后从缓存Cache根据key:tag1,取出对应的targetAddrPool

  3. 将上一次传入的addrPool与targetAddrPool,得到结果resultAddrPooll

  4. 将resultAddrPool传入vivoNearestRouter

(3)vivoNearestRouter计算逻辑

  1. 从环境变量取出当前机房,假设是bj01

  2. 然后从缓存Cache根据key:bj01,取出对应的targetAddrPool

  3. 将上一次传入的addrPool与targetAddrPool,取出resultAddrPool

  4. 将上一次传入的addrPool与targetAddrPool,得到结果resultAddrPool

  5. 将resultAddrPool为最终路由结果,传递给LoadBalance

图片

6. 基于缓存的同机房优先路由源码解析

缓存刷新:

/*** Notify router chain of the initial addresses from registry at the first time.* Notify whenever addresses in registry change.*/public void setInvokers(List<Invoker<T>> invokers) {// 创建带epoch的BitListthis.invokers = new BitList<Invoker<T>>(invokers == null ? Collections.emptyList() : invokers,createBitListEpoch());routers.forEach(router -> router.notify(this.invokers));}

同机房优先路由源码解读:

public <T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL consumerUrl, Invocation invocation) throws RpcException {…………//省略非核心代码BitList<Invoker<T>> bitList = (BitList<Invoker<T>>) invokers;//获取路由结果BitList<Invoker<T>> result = getNearestInvokersWithCache(bitList);if (result.size() > 0) {if (fallback) {// 开启服务降级,available.ratio = 当前机房可用服务节点数量 / 集群可用服务节点数量int curAvailableRatio = (int) Math.floor(result.size() * 100.0d / invokers.size());if (curAvailableRatio <= availableRatio) {return invokers;}}return result;} else if (force) {return result;} else {return invokers;}}   /*** 获取缓存列表* @param invokers* @param <T>* @return*/private <T> BitList<Invoker<T>> getNearestInvokersWithCache(BitList<Invoker<T>> invokers) {ValueWrapper valueWrapper = getCache(getSystemProperty(LOC));// 是否存在缓存if (valueWrapper != null) {BitList<Invoker<T>> invokerBitList = (BitList<Invoker<T>>) valueWrapper.get();// 缓存的epoch与源列表是否一致if (invokers.isSameEpoch(invokerBitList)) {BitList<Invoker<T>> tmp = invokers.clone();// 结果取交集return tmp.and(invokerBitList);}}// 缓存不存在 实时计算放回return getNearestInvokers(invokers);}/*** 新服务列表通知* @param invokers* @param <T>*/@Overridepublic <T> void notify(List<Invoker<T>> invokers) {clear();if (invokers != null && invokers instanceof BitList) {BitList<Invoker<T>> bitList = (BitList<Invoker<T>>) invokers;// 设置最后一次更新的服务列表lastNotify = bitList.clone();if (!CollectionUtils.isEmpty(invokers) && this.enabled) {// 获取机房相同的服务列表并进行缓存setCache(getSystemProperty(LOC), getNearestInvokers(lastNotify));}}}

4.2 负载均衡优化

1. 优化一

针对getWeight方法,我们发现有部分业务逻辑较为消耗cpu,但是在大多数场景下业务方并不会使用到,于是进行优化。

getWeight方法优化:

优化前:
//这里主要要用多注册中心场景下,注册中心权重的获取,绝大多数情况下并不会有这个逻辑if (UrlUtils.isRegistryService(url)) {weight = url.getParameter(REGISTRY_KEY + "." + WEIGHT_KEY, DEFAULT_WEIGHT);}  
优化后:if (invoker instanceof ClusterInvoker && UrlUtils.isRegistryService(url)) {weight = url.getParameter(REGISTRY_KEY + "." + WEIGHT_KEY, DEFAULT_WEIGHT);}

2. 优化二

遍历是罪恶的源泉,而实例的数量决定这罪恶的深浅,我们有什么办法减少负载均衡过程中的遍历呢。一是根据group及version划分不同的集群,但是这需要涉及到业务方代码或配置层面的改动,会带来额外的成本。所以我们放弃了。

二是没有什么是加一层解决不了的问题,为了尽量减少进入负载均衡的节点数量,考虑新增一个垫底的路由策略,在走完所有的路由策略后,若节点数量>自定义数量后,进行虚拟分组,虚拟分组的策略也可进行自定义,然后随机筛选一组进入负载均衡。此时进入负载均衡的实例数量就会有倍数的下降。

需要注意的是分组路由必须保证是在路由链的最后一环,否则会导致其他路由计算错误。

图片

分组路由示意:

/*** * @param invokers 待分组实例列表* @param groupNum 分组数量* @param <T>* @return*/public <T> List<Invoker<T>> doGroup(List<Invoker<T>> invokers, int groupNum) {int listLength = invokers.size() / groupNum;List<Invoker<T>> result = new ArrayList<>(listLength);int random = ThreadLocalRandom.current().nextInt(groupNum);for (int i = random; i < invokers.size(); i = i + groupNum) {result.add(invokers.get(i));}return result;}

五、优化效果

针对优化前和优化后,我们编写Demo工程分别压测了不配置路由/配置就近+标签路由场景。Provider节点梯度设置100/500/1000/2000/5000,TPS在1000左右,记录了主机的cpu等性能指标,并打印火焰图。发现,配置路由后,采用相同并发,优化后的版本tps明显高于优化前版本,且新版本相较于没有配置路由时tps显著提高,下游节点数大于2000时,tps提升达到100%以上,下游节点数越多,AvgCpu优化效果越明显,并且路由及负载均衡CPU占比明显更低,详细数据可见下表:

图片

图片

备注:-tag,表示显示禁用原生Dubbo应用级标签路由。该路由默认开启。

六、总结

经过我们关闭不必要的路由逻辑、对路由缓存+异步化计算、新增分组路由等优化后,Dubbo在负载均衡及路由模块整体的性能有了显著的提升,为业务方节省了不少CPU资源。在正常业务场景下当提供方数量达到2000及以上时,tps提升可达100%以上,消费方平均CPU使用率下降约27%,且提供方数量越多优化效果越明显。但是我们也发现当前的随机负载均衡依然还是会消耗一定的CPU资源,且只能保证流量是均衡的。当前我们的应用基本部署在虚拟机及容器上。这两者均存在超卖的状况,且同等配置的宿主机性能存在较大差异等问题。最终会导致部分请求超时、无法最大化利用提供方的资源。我们下一步将会引入Dubbo 3.2的自适应负载均衡并进行调优减少其CPU使用率波动较大的问题,其次我们自身也扩展了基于CPU负载均衡的单一因子算法,最终实现不同性能的机器CPU负载趋于均衡,最大程度发挥集群整体的性能。

参考资料:

  1. Dubbo 负载均衡

  2. Dubbo 流量管控

  3. Dubbo 3 StateRouter:下一代微服务高效流量路由

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/121691.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

工厂干洗店洗鞋店系统,校园洗护小程序来了

洗鞋店小程序&#xff0c;干洗店软件&#xff0c;洗护行业小程序,上门取衣小程序,预约干洗小程序,校园干洗店小程序,工厂干洗店小程序,干洗店小程序开发&#xff0c;成品软件开发 洗衣工厂软件、功能强大&#xff01; 包含以下主要功能&#xff1a; * 用户选择洗护用品&#x…

OS的Alarm定时器调度机制

调度表触发的任务在编译时就被静态定义&#xff0c;任务的触发时间和执行顺序是固定的。这种方式适用于已知的、固定的任务触发模式&#xff0c;例如周期性任务或事件驱动任务。而使用 Alarm 机制触发的任务具有更大的灵活性。Alarm 允许在运行时动态地设置和修改任务的触发时间…

薛定谔的猫重出江湖?法国初创公司AliceBob研发猫态量子比特

总部位于巴黎的初创公司Alice&Bob使用超导芯片的两个相反的量子态&#xff08;他们称之为“猫态量子比特”芯片&#xff09;来帮助开发量子计算的不同自旋方式。&#xff08;图片来源&#xff1a;网络&#xff09; 有的人认为&#xff0c;构建量子计算机的模块模仿了著名的…

链动2+1全新9.0版本 无限链动收益

一个平台能否长期存活取决于它是否有一个支撑其持续发展的商业模式。蜂群精选深谙用户心理&#xff0c;对链动21模式进行改造&#xff0c;创新出一种同时具备裂变能力和高效吸引用户留存的新玩法。 链动21模式在整个架构上都是完整的&#xff0c;可以说是一个非常出色的营销模式…

ruoyi-plus创建模块、自动生成代码

ruoyi-plus自动生成代码 1、创建模块 复制其他部分的resouce过来 修改yml文件 2 修改Nacos 2.1 修改数据库文件 复制其他数据库的链接 &#xff0c;改为自己新建的数据库名字 修改为自己要生成的数据库 新建数据库的yaml文件 3 重启docker的ruoyi-gen服务 docker re…

ClickHouse快速了解

简介 ClickHouse是一个开源列式数据库管理系统&#xff08;DBMS&#xff09;&#xff0c;用于在线分析处理&#xff08;OLAP&#xff09;&#xff1a; 列式存储&#xff1a;与传统的行式数据库不同&#xff0c;ClickHouse以列的形式存储数据&#xff0c;这使得在分析大量数据时…

docker - DockerFile 编写 指令

文章目录 前言docker - DockerFile 编写 指令1. FROM2. MAINTAINER3. RUN4. CMD5. LABEL6. EXPOSE7. ENV8. ADD9. COPY10. ENTRYPOINT11. VOLUME12. USER13. WORKDIR14. ARG15. ONBUILD16. STOPSIGNAL 前言 如果您觉得有用的话&#xff0c;记得给博主点个赞&#xff0c;评论&am…

解决Windows出现找不到mfcm90u.dll无法打开软件程序的方法

今天&#xff0c;我非常荣幸能够在这里与大家分享关于mfc90u.dll丢失的5种解决方法。在我们日常使用电脑的过程中&#xff0c;可能会遇到一些软件或系统错误&#xff0c;其中之一就是mfc90u.dll丢失。那么&#xff0c;mfc90u.dll究竟是什么文件呢&#xff1f;接下来&#xff0c…

Hbase基本使用,读写原理,性能优化学习

文章目录 HBase简介HBase定义HBase数据模型**HBase** **逻辑结构****HBase** **物理存储结构****HBase** **基本架构** HBase 入门**HBase** **安装部署****HBase** 配置文件**HBase** 启动停止**HBase** **访问页面****HBase** **高可用****HBase Shell****HBase API**HBaseCo…

centos ubantu IP一直变化,远程连接不上问题

文章目录 一、为什么IP地址会变1.主机DHCP导致 二、解决IP地址变化1.centos2.ubantu 总结 虚拟机能连接为互联网,但下一次启动IP地址再发生变化,无法使用ssh远程连接 一、为什么IP地址会变 1.主机DHCP导致 虚拟机系统(ubantu,centos…)启动后会向本地申请IP地址租约,租聘的I…

通过python操作neo4j

在neo4j中创建结点和关系 创建结点 创建电影结点 例如&#xff1a;创建一个Movie结点&#xff0c;这个结点上带有三个属性{title:‘The Matrix’, released:1999, tagline:‘Welcome to the Real World’} CREATE (TheMatrix:Movie {title:The Matrix, released:1999, tagl…

LeetCode 1465. 切割后面积最大的蛋糕

矩形蛋糕的高度为 h 且宽度为 w&#xff0c;给你两个整数数组 horizontalCuts 和 verticalCuts&#xff0c;其中&#xff1a; horizontalCuts[i] 是从矩形蛋糕顶部到第 i 个水平切口的距离 verticalCuts[j] 是从矩形蛋糕的左侧到第 j 个竖直切口的距离 请你按数组 horizontalC…

ChatGLM推出第三代基座大模型在论文阅读、文档摘要和财报分析等方面提升超过50%推理成本降低一半...

“ 智谱AI发布了第三代基座大模型ChatGLM3&#xff0c;在模型性能、功能支持、开源序列等方面进行了全面升级。ChatGLM3在语义、数学、推理、代码、知识等不同角度的数据集上测评显示&#xff0c;具有在10B以下的基础模型中最强的性能。同时&#xff0c;ChatGLM3还支持多模态理…

Ubuntu 22.04 开机闪logo后卡在/dev/sda3: clean

环境 Vmware 17.0.0&#xff0c;CPU 2&#xff0c;内存4G&#xff0c;硬盘50G Ubuntu 22.04 问题描述 开机 --> 显示两行代码 --> 显示ubuntu logo --> 左上显示两个代码卡住不动 原因分析 1、网上大多说显卡驱动&#xff0c;最近没安装相关软件&#xff0c;也没…

Xilinx MicroBlaze定时器中断无法返回主函数问题解决

最近在使用Xilinx 7系列FPGA XC7A100T时&#xff0c;运行MicroBlaze软核处理器&#xff0c;添加了AXI TIMER IP核&#xff0c;并使能定时器溢出中断&#xff0c;发现定时器触发中断后&#xff0c;无法返回主函数的问题&#xff0c;最后发现修改编译器优化等级就正常了。 FPGA型…

VirtualBox 安装 麒麟Linux

为了验证Oracle EM是否可以管理麒麟OS和其上的Oracle数据库&#xff0c;今天试着在VirtualBox上装了麒麟Linux&#xff0c;也就是银河麒麟。整个过程比较顺畅。 选定ISO文件后&#xff0c;操作系统自动识别为Red Hat。勾选“跳过自动安装”&#xff1a; 内存和CPU选的默认值&…

Qt重定向QDebug,Qt/C++开源作品39-日志输出增强版V2022

Qt重定向QDebug&#xff0c;自定义一个简易的日志管理类 Chapter1 Qt重定向QDebug&#xff0c;自定义一个简易的日志管理类0.前言1.最简单的操作运行结果2.实现一个简易的日志管理类 Chapter2 Qt::Qt Log日志模块Qt Log日志模块官方解释官方Demo思路 Chapter3 QT日志模块的个性…

ssm164学院学生论坛的设计与实现+vue

项目名称&#xff1a;ssm164学院学生论坛的设计与实现vue 点击这里进入源码目录 声明&#xff1a; 适用范围&#xff1a; 本文档适用于广泛的学术和教育用途&#xff0c;包括但不限于个人学习、毕业设计和课程设计。免责声明&#xff1a; 特此声明&#xff0c;本文仅供参考学…

C# 图解教程 第5版 —— 第12章 枚举

文章目录 12.1 枚举12.1.1 设置底层类型和显式值12.1.2 隐式成员编号 12.2 位标志12.2.1 Flags 特性12.2.2 使用位标志的示例&#xff08;*&#xff09; 12.3 关于枚举的更多内容 12.1 枚举 枚举是值类型。只有一种类型的成员&#xff1a;命名的整数值常量。 每个枚举成员都被…

学习Java应该关注哪些网站?

前言 下面是我总结的一些不错的网站&#xff0c;可以收藏看一下哈~希望对你有帮助 一、入门教程类 主要是教程性质的网站&#xff0c;主要是新手学习参考以及相关知识的内容参考 1、菜鸟教程&#xff08;https://www.runoob.com&#xff09; 2、Java学习笔记&#xff08;http…