Dubbogo 详解

news2024/11/28 18:48:16

Dubbogo 详解

简介

dubbo功能很强大的微服务开发框架,支持多种通信协议,并具有流量治理的功能。

dubbo在有了大转变,拥抱了云原生,从哪些方面可以体现呢?

  1. 推出了自己的Trip协议
  2. 修复了服务发现的级别,之前是方法级别,现在是服务级别
  3. 提供了服务发现,流量控制和XDS的结合

当然,dubbo还是那个dubbo,原来的配置都有,并且支持跨语言通信,并且方便我们开发,提供了cli的工具来使用,所以我们的通信变成了下面的样子

  • 先写IDL
  • 实现对应的服务,调用对应的接口。

dubbo有对应的示例代码:

https://github.com/apache/dubbo-go-samples

dubbo的架构和分层

我想引用官网上的图来清晰的说明,dubbo的抽象是很重要的。

dubbo中有几个重要的接口和对象

  • invoker:表示一个可调用的抽象对象,不管是从loadBalance,还是从cluster,还是dictionary,都表示的是一个invoker。
  • Invocation:调用的上下文。通过它可以调用此次调用时候的参数名字,参数类型,返回值等等一系列的数据

具体的可以参考官网:

https://cn.dubbo.apache.org/zh-cn/overview/mannual/java-sdk/reference-manual/architecture/code-architecture/
在这里插入图片描述

在这里插入图片描述

这是从dubbo的官网上copy的图,源码的分析按照这个图来看,很清晰,可能会有一点点的不一样,但这也没太大的问题。

在这里插入图片描述

分为服务端和客户端:

服务端:构建代理对象,接受服务端的请求。

客户端:构建代理对象,请求服务端。

这里需要处理几个问题

  1. 怎么在服务端和客户端调用的基础上,将dubbo这一套东西加上去

    每个通信协议在dubbo中都有自己的通信协议的实现,当让他们也是一个invoker,它作为baseInvoker,在此基础上增加各种各样的invoker(cluster,loadbalance,filter等)。

    对于客户端而言,也有baseInvoker,客户端而言,只有filter。

  2. go中是没有动态代理的概念的,所以,对于客户端来说,怎么生成代理对象。

    代理对象不能再运行的时候生成,那可以提早生成。

    dubbo提供IDL的工具实现了此功能,提早已经生成了好了代理对象,用来做真正的调用。

    并且生成了一个对应的ClientImpl,它是一个结构体,结构体中的属性是对应的方法,此时就可以利用GO的反射来动态的修改属性,这样在构建的时候就做到了。

服务端创建一个exporter开始

code:

type GreeterProvider struct {
	api.UnimplementedGreeterServer
}

func (s *GreeterProvider) SayHello(ctx context.Context, in *api.HelloRequest) (*api.User, error) {
	logger.Infof("Dubbo3 GreeterProvider get user name = %s\n", in.Name)
	return &api.User{Name: "Hello " + in.Name, Id: "12345", Age: 21}, nil
}

// export DUBBO_GO_CONFIG_PATH= PATH_TO_SAMPLES/direct/go-server/conf/dubbogo.yml
func main() {
	config.SetProviderService(&GreeterProvider{})
	if err := config.Load(); err != nil {
		panic(err)
	}
	select {}
}

将对应的实现类提供给dubbo,并且启动。

  1. 注册到dubbo中

    在整个dubbo没有启动之前,这里的注册只是放在一个map中,需要保证key是唯一。

  2. 启动

    启动分为五步

    • 初始化config
    • 初始化provider
    • 初始化consumer
    • 初始化shutdown

初始化好此provider的配置,配置叫做ServiceConfig

调用Export来做真正的服务创建操作。

会通过具体的通信协议创建对应的invoker,并且结合filter。并且将服务注册到注册中心。

总体流程是这样,现在回答几个问题:

  1. dubbo中是怎么调用到真正的provider,也就是说dubbo是怎么和provider关联的。
  2. Invoker(调用者)是怎么构建的?

问题一:

dubbo是通过反射来调用的。

原理如下:

dubbo的trip协议是grpc的基础上改进的,所以要从这一点入手,从grpc我们知道,在启动的时候要注册相应的服务,在dubbo这里,从生产的pb文件中入手
在这里插入图片描述

会将此服务描述信息告诉grpc,当调用来了之后,就会调用到对应的方法中。这里我们以_Greeter_SayHello_Handler为例,在此方法中,会构建invocation,然后在调用dubbo构建的invoker对象,就完成连接了。

在这里插入图片描述

代理对象是什么?

代理对象就是dubbo生成的invoker,在构建好invoker之后,会将它作为属性放在provider中,之后在客户端调用的时候可以通过反射来调用对应的方法。

dubbo为grpc的服务规定了接口。通过此接口就可以获取代理对象,获取服务的描述性息
在这里插入图片描述

并且官网提供了可拓展点和完整的调用说明:
https://cn.dubbo.apache.org/zh-cn/overview/mannual/java-sdk/reference-manual/architecture/service-invocation/
在这里插入图片描述

在什么时候注册grpc服务,并且设置代理对象的?

在构建好invoker对象之后,会调用XXX_SetProxyImpl方法,设置值。
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

问题 二:

invoker是dubbo中很重要的概念,表示的是一个可调用者,通过这个概念,dubbo高度抽象了服务,
在这里插入图片描述

官网:

https://cn.dubbo.apache.org/zh-cn/overview/mannual/java-sdk/advanced-features-and-usage/service/fault-tolerent-strategy/

invoker表示可用的服务,不管是loadbalance、router、director等,都是一个可用的invoker。

在抽象的invoker都有基础的实现,剩下的filter,cluster都是基于此继续操作的。我们来看invoker的构建过程

在准备好服务方的配置之后,就开始挨个启动服务了,每个服务的定义信息叫做ServiceConfig,调用Export来初始化此服务,并且注册到注册中心。

我们知道dubbo中服务的定义信息传递全都靠url,在Export中,主要有下面的几个步骤

  1. 获取注册中心,创建代理工厂(用于代理对象的创建)
  2. 构建url
  3. 通过不同的通信协议构建不同的Invoker,并且启动底层具体通信协议对应的服务器。
  4. 服务发布到注册中心

这里我们只说Invoker的构建。对应到代理里面如下:

	invoker := proxyFactory.GetInvoker(ivkURL) // 通过代理工厂创建对应的代理类
	exporter := extension.GetProtocol(protocolwrapper.FILTER).Export(invoker) // 从extension中获取对应的底层通信协议来构建invoker

先看代理工厂如何创建Invoker

在这里插入图片描述

基础的调用就是ProxyInvoker,等请求来了之后,通过一系列filter,会走到这里,通过反射来调用具体的方法。

不同的通信协议如何构建invoker

在这里插入图片描述

extension是一个拓展点,也是通过它逐渐的让dubbo丰满起来。

filter对应的wrapper是ProtocolFilterWrapper,通过它基于ProxyInvoker来构建最终的Invoker,最后通过最终的通信协议的来构建最终的Invoker,从而启动服务器,注册服务。

在dubbo的protocol包下面列举出了支持的通信协议

在这里插入图片描述

我们来查看dubbo3种trip的通信协议

上面已经介绍过了,主要是确定序列化类型,设置invoker代理。启动服务器

还有一个问题

最终是怎么通过反射调用的,并且何时设置的反射呢

直接去ProxyInvoker中查看invoke方法

在这里插入图片描述

这里对反射要说明一下,反射调用的时候需要确定参数的个数,结合实际,参数个数就一个或者两个,其中必须用context,剩下的要么是无参,要么就一个request对象。

查看serviceMap.Register方法可知,在注册的时候会解析此结构体中的方法,并且设置封装为Service对象

客户端创建一个Ref开始

客户端的构建思路和上面的差不多,不过多了注册中心,cluster的构建。

还是从几个问题开始

  1. 代理对象的创建和关联
  2. invoker对象的构建流程

整个流程从ReferenceConfig.Refer方法开始

代理对象的创建

按照dubbo的逻辑,对象的创建必须去proxyFactory中,但这里是整个filter,cluster构建好了之后,在基于此构建代理对象,因为这里的代理对象是最终给用户侧来调用的,所以,肯定是放在最后一步的。

最终的入口在DefaultProxyFactory.GetProxy中,此方法中会创建Proxy对象

type Proxy struct {
	rpc         common.RPCService // 代理还要保留origin
	invoke      protocol.Invoker // 生成的invoke对象,这是代表整个dubbo服务的invoke,此invoke中包含了 cluster,load等等流量治理的功能
	callback    interface{}
	attachments map[string]string
	implement   ImplementFunc // 动态函数,这是一个函数类型,会通过此函数来实现普通服务和dubbo服务的对接
	once        sync.Once
}

在这里插入图片描述

这里只是设置了proxy,并没有做方法绑定操作,真正的是在ReferenceConfig.Implement中实现的。他会调用到刚刚创建的proxy对象中的implement方法,从而给客户端动态绑定属性,达到调用dubbo服务的功能。这是重点,我们源码分析一下

源码:proxy.DefaultProxyImplementFunc

func DefaultProxyImplementFunc(p *Proxy, v common.RPCService) {
	// 开始反射操作
	valueOf := reflect.ValueOf(v)

	valueOfElem := valueOf.Elem()
	// 这是重点,最终会将客户端中的属性方法,都设置为此函数,然后在此函数里面做调用。
    // 当我们调用方法的时候,会调用到这里来,
	makeDubboCallProxy := func(methodName string, outs []reflect.Type) func(in []reflect.Value) []reflect.Value {
		return func(in []reflect.Value) []reflect.Value {
			var (
				err            error
				inv            *invocation_impl.RPCInvocation
				inIArr         []interface{}
				inVArr         []reflect.Value
				reply          reflect.Value
				replyEmptyFlag bool
			)
			if methodName == "Echo" {
				methodName = "$echo"
			}
			// 确定参数和返回值等信息
			if len(outs) == 2 { // return (reply, error)
				if outs[0].Kind() == reflect.Ptr {
					reply = reflect.New(outs[0].Elem())
				} else {
					reply = reflect.New(outs[0])
				}
			} else { // only return error
				replyEmptyFlag = true
			}

			start := 0
			end := len(in)
			invCtx := context.Background()
			// retrieve the context from the first argument if existed
			if end > 0 {
				if in[0].Type().String() == "context.Context" {
					if !in[0].IsNil() {
						// the user declared context as method's parameter
						invCtx = in[0].Interface().(context.Context)
					}
					start += 1
				}
			}

			if end-start <= 0 {
				inIArr = []interface{}{}
				inVArr = []reflect.Value{}
			} else if v, ok := in[start].Interface().([]interface{}); ok && end-start == 1 {
				inIArr = v
				inVArr = []reflect.Value{in[start]}
			} else {
				inIArr = make([]interface{}, end-start)
				inVArr = make([]reflect.Value, end-start)
				index := 0
				for i := start; i < end; i++ {
					inIArr[index] = in[i].Interface()
					inVArr[index] = in[i]
					index++
				}
			}
			
			inv = invocation_impl.NewRPCInvocationWithOptions(invocation_impl.WithMethodName(methodName),
				invocation_impl.WithArguments(inIArr),
				invocation_impl.WithCallBack(p.callback), invocation_impl.WithParameterValues(inVArr))
			if !replyEmptyFlag {
				inv.SetReply(reply.Interface()) // 这里的reply就是response
			}

			for k, value := range p.attachments {
				inv.SetAttachment(k, value)
			}

			// add user setAttachment. It is compatibility with previous versions.
			atm := invCtx.Value(constant.AttachmentKey)
			if m, ok := atm.(map[string]string); ok {
				for k, value := range m {
					inv.SetAttachment(k, value)
				}
			} else if m2, ok2 := atm.(map[string]interface{}); ok2 {
				// it is support to transfer map[string]interface{}. It refers to dubbo-java 2.7.
				for k, value := range m2 {
					inv.SetAttachment(k, value)
				}
			}
			// 调用服务,这里用的是闭包
			result := p.invoke.Invoke(invCtx, inv)
			err = result.Error()
			// cause is raw user level error
			cause := perrors.Cause(err)
			if err != nil {
				// if some error happened, it should be log some info in the separate file.
				if throwabler, ok := cause.(java_exception.Throwabler); ok {
					logger.Warnf("[CallProxy] invoke service throw exception: %v , stackTraceElements: %v", cause.Error(), throwabler.GetStackTrace())
				} else {
					// entire error is only for printing, do not return, because user would not want to deal with massive framework-level error message
					logger.Warnf("[CallProxy] received rpc err: %v", err)
				}
			} else {
				logger.Debugf("[CallProxy] received rpc result successfully: %s", result)
			}
			if len(outs) == 1 {
				return []reflect.Value{reflect.ValueOf(&cause).Elem()}
			}
			if len(outs) == 2 && outs[0].Kind() != reflect.Ptr {
				return []reflect.Value{reply.Elem(), reflect.ValueOf(&cause).Elem()}
			}
			return []reflect.Value{reply, reflect.ValueOf(&cause).Elem()}
		}
	}

	if err := refectAndMakeObjectFunc(valueOfElem, makeDubboCallProxy); err != nil {
		logger.Errorf("The type or combination type of RPCService %T must be a pointer of a struct. error is %s", v, err)
		return
	}
}

在这里插入图片描述

invoker的构建是什么样子?

对应的代码在ReferenceConfig.Refer

  1. 构建url
  2. 通过不同的通信协议构建不同的base invoker,对于trip来说就是DubboInvoker
  3. 基于此Invoker,增加filter功能
  4. 增加cluster
  5. 构建代理对象

DubboInvoker是什么样子

在这里插入图片描述

在这里插入图片描述

cluster

dubbo提供的集群容错的策略,cluster中的交互逻辑如下:

在这里插入图片描述

可以看到,整体的流程是获取所有的可用的invoker,然后通过路由,负载均衡,选择出一个invoker来做调用。

官网链接:

https://cn.dubbo.apache.org/zh-cn/overview/mannual/java-sdk/advanced-features-and-usage/service/fault-tolerent-strategy/

源码:https://github.com/apache/dubbo-go/tree/main/cluster/cluster

支持的策略如下:

理解了上面的逻辑,这里的代码比较好理解。

在这里插入图片描述

failover

快速切换,规定了重试次数,超过之后就失败。

在这里插入图片描述

failfast

只调用一次。失败就失败了

在这里插入图片描述

failsafe

在这里插入图片描述

会打印错误日志,并且返回一个空对象回去

forking

在这里插入图片描述

并行调用(默认为2),主要有一个成功,就返回,规定超时时间(1s)就返回失败

broadcast

在这里插入图片描述

广播调用,等待所有的调用结束之后才返回

available

在这里插入图片描述

zoneaware

统一地区的优先访问,其实就是做load balance 之前增加了一层筛选

adaptivesvc

自适应的cluster,需要搭配p2c load balance 使用,这个比较复杂,需要搭配filter使用。之后出文章详解。

failback

失败恢复,将失败的请求放在队列里面,重试,但没有返回值

这里需要一个机制,重试机制的实现

在Java中是放在时间轮里面实现的,go用ticker实现定时触发,一秒一次。

默认重试3次,重试队列长度为100

重试队列

在这里插入图片描述

处理过程

在这里插入图片描述

在这里插入图片描述

当失败之后,就会将调用放在一个定时任务的队列里面,每隔一秒就会将所有的任务执行一次(任务的间隔必须大于5秒),如果失败之后,通过条件决定是否要放在队列中。

loadBalance

官网:https://cn.dubbo.apache.org/zh-cn/overview/core-features/load-balance/

源码:https://github.com/apache/dubbo-go/tree/main/cluster/loadbalance

负载均衡从一堆可用的invoker中选一个出来。

consistenthashing

一致性hash,确定的入参,确定的提供者,适用于有状态请求。

但当invoker发生了变化,会重新计算hash分配。这里需要构建hash环,说到hash环就得有虚拟节点,虚拟节点是为了更好的分配平均,在这里每个物理节点会对应160个虚拟节点(默认)

在这里插入图片描述

用crc生成一个hashcode,从而生成hash环,每个方法对应一个hash环,并且hashcode发生了变化,就会重构hash环。下面的代码分为三步

hash环构建
在这里插入图片描述

每个物理节点有160个虚拟节点,并且用节点的ip+端口生成address,之后分为两次循环,外层40,内层4次,生成hashcode,并且保存虚拟节点和物理节点的映射关系。最后对生成的hash环做排序,方便之后的查找。

hash环查找

通过key生成hashcode,然后用找到第一个大于等于当前hash值的key,之后返回对应的物理节点

在这里插入图片描述

hash函数

好的函数很重要,可以让请求分配的更加的均匀。

在这里插入图片描述

这我看不懂了,看看GPT的回答吧

在这里插入图片描述

leastactive

最近最少活跃,需要配合filter继续当前节点的活跃情况,如果有多个相同情况的invoke。就通过权重来选择一个,如果还有相同的,就随机选择一个。

在这里插入图片描述

关于随机匹配,会产生一个总权重的随机值,占的比重大的为负数的概率低。

random

加权随机,按照权重做随机

在这里插入图片描述

ringhash

和服务网格相关,这里不做过多介绍

roundrobin

加权轮询,它的算法和Nginx平滑加权轮询算法,默认权重相同。

https://cn.dubbo.apache.org/zh-cn/overview/core-features/load-balance/#roundrobin
在这里插入图片描述

具体可看官网的解释,主要是为了在保证轮询的情况下,尽可能的分布均匀。

轮询算法需要保存上一次选择的节点的状态。

这里分为两步

选择节点

在这里插入图片描述

保存节点状态

在这里插入图片描述

p2c

自适应的算法。

这种算法需要结合filter来实现,之后在详细的说

dubbogo项目分层很清晰,没有像Java那样花里胡哨的代码,也没有线程池,时间轮等这些算法,好理解。


到这里就结束了。dubbo

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

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

相关文章

Bug竞技场【已经修复】

目录 1.基础知识 2.最佳组合 2.1 铁男-螳螂 2.2 弟弟组合 海克斯抽卡bug 1.基础知识 背景&#xff1a;美测服-美服-马服-可以有效地减少bug率 复盘是为了更好的战斗&#xff01; 提前观看一些视频资料也是如此。 通过看直播博主的经验&#xff0c;可以让你关注到本来对战的…

利用Canvas根据经纬度绘制轨迹(一)

根据经纬度坐标绘制轨迹图形 一段时间没更新了&#xff0c;主人最近有点懒~ 前段时间有个需求&#xff0c;在uniapp App端实现轨迹绘制&#xff0c;于是先用html实现看看效果~ 效果图 html <canvasid"canvasId"width"300"height"300"style&…

DOS命令(windows)

DOS命令&#xff08;windows&#xff09; 目录 1. 打开命令提示符。2. 切换至根。3. 当前路径。4. 切换至上级路径。5. 查看当前目录。6. 查看文件内容。7. 删除文件。8. 进入长文件夹名时缩写。9. 复制文件。10. 移动文件。 1. 打开命令提示符。 命令&#xff1a;winR 输入&a…

【Go】Go 语言开发工具GoLand 使用(二十二)

往期回顾&#xff1a; Go 语言教程–介绍&#xff08;一&#xff09;Go 语言教程–语言结构&#xff08;二&#xff09;Go 语言教程–语言结构&#xff08;三&#xff09;Go 语言教程–数据类型&#xff08;四&#xff09;Go 语言教程–语言变量&#xff08;五&#xff09;Go …

STM32外设系列—TB6612FNG

本文涉及到定时器和串口的知识&#xff0c;详细内容可见博主STM32速成笔记专栏。 文章目录 一、TB6612简介二、TB6612使用方法2.1 TB6612引脚连接2.2 控制逻辑2.3 电机调速 三、实战项目3.1 项目简介3.2 初始化GPIO3.3 PWM初始化3.3 电机控制程序3.4 串口接收处理函数 一、TB66…

PHP反序列化漏洞之魔术方法

一、魔术方法 PHP魔术方法&#xff08;Magic Methods&#xff09;是一组特殊的方法&#xff0c;它们在特定的情况下会被自动调用&#xff0c;用于实现对象的特殊行为或提供额外功能。这些方法的名称都以双下划线开头和结尾&#xff0c;例如: __construct()、__toString()等。 …

java项目之网络视频播放器(ssm+mysql+jsp)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于ssm的网络视频播放器。技术交流和部署相关看文章末尾&#xff01; 开发环境&#xff1a; 后端&#xff1a; 开发语言&#xff1a;Java 框架&a…

Arduino RP2040 两个CDC虚拟串口通讯

Arduino RP2040 两个CDC虚拟串口通讯 &#x1f3ac;通讯效果演示&#xff1a; &#x1f33f;基于Earle F. Philhower的固件开发平台&#xff1a; https://github.com/earlephilhower/arduino-pico&#x1f516;USB配置参考&#xff1a;https://arduino-pico.readthedocs.io/en/…

【算法基础:数学知识】4.1 质数

文章目录 质数例题列表866. 试除法判定质数&#xff08;质数的判定&#xff09;867. 分解质因数&#xff08;&#xff09;868. 筛质数埃氏筛欧氏筛 / 线性筛 相关链接 质数 定义&#xff1a;质数是指在大于1的自然数中&#xff0c;除了1和它本身以外不再有其他因数的自然数。 …

安装Electron时报错command sh -c node install.js

在安装Electron报如下错误 在指令后面添加 --ignore-scripts&#xff0c;意思是npm 将不会运行在package.json中指定的scripts npm install --save -dev electron --ignore-scripts

Linux系统安装部署MySQL完整教程(图文详解)

前言&#xff1a;最近网上翻阅了大量关于Linux安装部署MySQL的教程&#xff0c;在自己部署的时候总是存在一些小问题&#xff0c;例如&#xff1a;版本冲突&#xff0c;配置失败和启动失败等等&#xff0c;功夫不负有心人&#xff0c;最后还是安装部署成功了&#xff0c;所以本…

《TCP IP网路编程》第九章

第 9 章 套接字的多种可选项 我们进行套接字编程时往往只关注数据通信&#xff0c;而忽略了套接字具有的不同特性。但是&#xff0c;理解这些特性并根据实际需要进行更改也很重要。下面列出了一些套接字可选项。 从表中可以看出&#xff0c;套接字可选项是分层的。 IPPROTO_IP …

[java安全]TemplatesImpl在Shiro550反序列化

文章目录 【java安全】TemplatesImpl在Shiro550反序列化Shiro的原理Shiro反序列化产生演示攻击过程payload使用key加密 构造不含数组的GadGets简单调用链 改造cc6为CommonsCollctionsShiro完整POC触发Shiro550漏洞进阶POC总结 【java安全】TemplatesImpl在Shiro550反序列化 Sh…

论文工具——写论文好用的绘图工具(甘特图+流程图+网络模型图+泳道图)

文章目录 引言正文手动画图的在线画图工具tldraw开源免费ProcessOnDraw.io 网络模型图工具NN-SVG设置参数自动生成Netron上传模型自动生成PlotNeuralNet编码生成 总结 引言 在写HiFi-GAN论文的代码阅读过程中&#xff0c;我发现仅仅通过文字来描述网络结构&#xff0c;不够详细…

ORB-SLAM2学习笔记4之KITTI开源数据集运行ORB-SLAM2生成轨迹并用evo工具评估轨迹

文章目录 0 引言1 KITTI数据集1.1 下载数据1.2 真值轨迹格式转换 2 单目ORB-SLAM22.1 运行ORB-SLAM22.2 evo评估轨迹(tum格式)2.2.1 载入和对比轨迹2.2.2 计算绝对轨迹误差 3 双目ORB-SLAM23.1 运行ORB-SLAM23.2 evo评估轨迹(kitti格式)3.2.1 载入和对比轨迹3.2.2 计算绝对轨迹…

Linux服务器安装部署MongoDB数据库 - 无公网IP远程连接「内网穿透」

文章目录 前言1.配置Mongodb源2.安装MongoDB数据库3.局域网连接测试4.安装cpolar内网穿透5.配置公网访问地址6.公网远程连接7.固定连接公网地址8.使用固定公网地址连接 前言 MongoDB是一个基于分布式文件存储的数据库。由 C 语言编写&#xff0c;旨在为 WEB 应用提供可扩展的高…

升级dubbo3方案

dubbo3 新特性 1. Dubbo3 应用级服务发现设计 显著降低服务发现过程的资源消耗&#xff0c;包括提升注册中心容量上限、降低消费端地址解析资源占用等&#xff0c;使得 Dubbo3 框架能够支持更大规模集群的服务治理&#xff0c;实现无限水平扩容。适配底层基础设施服务发现模型…

【VScode/VS】解决头文件路径问题

vs 中明明包含了头文件所在路径&#xff0c;但是却找不到头文件 首先&#xff0c;将要添加的压缩包解压&#xff0c;放在任意一个盘里&#xff0c;注意&#xff0c;我们在代码里要添加的头文件路径是 #include <tensorflow/c/c_api.h> 接下来我们要添加在VS中的所有路径…

CHI协议保序之trans order保序

一致性系统中&#xff0c;使用三种保序方式&#xff1b; Transaction ordering □ 除了 comp response 来规定 RN 发出的 requeset 的执行顺序之外&#xff0c;还有一种 order 机制来定义RN<->HN,HN<->SN 之间&#xff0c;命令执行的顺序&#xff1a; □ 该机制通…

C语言编程---案例练习

文章目录 格式化输出 格式化输出 %d&#xff0c;输出整数&#xff1b; %f&#xff0c;输出浮点数&#xff1b;%.3f 保留三位小数&#xff1b; %e&#xff0c;输出双精度浮点数&#xff1b; %c&#xff0c;输出单个字符&#xff1b;将字符格式化%d&#xff0c;即转ASCII码&…