皮皮网
皮皮网

【周口苹果源码】【阶梯分行 源码】【解压内核源码】grpcchannel源码

来源:源码编辑器网址卡片 发表时间:2024-12-22 14:43:53

1.golang:context介绍
2.Java教程:dubbo源码解析-网络通信
3.gRPC 基础概念详解
4.Dubbo源码解析:网络通信

grpcchannel源码

golang:context介绍

       1 前言

       æœ€è¿‘实现系统的分布式日志与事务管理时,在寻求所谓的全局唯一Goroutine ID无果之后,决定还是简单利用Context机制实现了基本的想法,不够高明,但是好用.于是对它当初的设计比较好奇,便有了此文.

       Context是golang官方定义的一个package,它定义了Context类型,里面包含了Deadline/Done/Err方法以及绑定到Context上的成员变量值Value,具体定义如下:

typeContextinterface{ //返回Context的超时时间(超时返回场景)Deadline()(deadlinetime.Time,okbool)//在Context超时或取消时(即结束了)返回一个关闭的channel//即如果当前Context超时或取消时,Done方法会返回一个channel,然后其他地方就可以通过判断Done方法是否有返回(channel),如果有则说明Context已结束//故其可以作为广播通知其他相关方本Context已结束,请做相关处理.Done()<-chanstruct{ }//返回Context取消的原因Err()error//返回Context相关数据Value(keyinterface{ })interface{ }}那么到底什么Context?

       å¯ä»¥å­—面意思可以理解为上下文,比较熟悉的有进程/线程上线文,关于golang中的上下文,一句话概括就是: goroutine的相关环境快照,其中包含函数调用以及涉及的相关的变量值. 通过Context可以区分不同的goroutine请求,因为在golang Severs中,每个请求都是在单个goroutine中完成的.

       æœ€è¿‘在公司分析gRPC源码,proto文件生成的代码,接口函数第一个参数统一是ctx context.Context接口,公司不少同事都不了解这样设计的出发点是什么,其实我也不了解其背后的原理.今天趁着妮妲台风妹子正面登陆深圳,全市停工,停课,停业,在家休息找了一些资料研究把玩一把.

       Context通常被译作上下文,它是一个比较抽象的概念.在公司技术讨论时也经常会提到上下文.一般理解为程序单元的一个运行状态,现场,快照,而翻译中上下又很好地诠释了其本质,上下上下则是存在上下层的传递,上会把内容传递给下.在Go语言中,程序单元也就指的是Goroutine.

       æ¯ä¸ªGoroutine在执行之前,都要先知道程序当前的执行状态,通常将这些执行状态封装在一个Context变量中,传递给要执行的Goroutine中. 上下文则几乎已经成为传递与请求同生存周期变量的标准方法.在网络编程下,当接收到一个网络请求Request,处理Request时,我们可能需要开启不同的Goroutine来获取数据与逻辑处理,即一个请求Request,会在多个Goroutine中处理. 而这些Goroutine可能需要共享Request的一些信息;同时当Request被取消或者超时的时候,所有从这个Request创建的所有Goroutine也应该被结束.

       æ³¨ï¼šå…³äºŽgoroutine的理解可以移步这里.

2 为什么使用context

       ç”±äºŽåœ¨golang severs中,每个request都是在单个goroutine中完成,并且在单个goroutine(不妨称之为A)中也会有请求其他服务(启动另一个goroutine(称之为B)去完成)的场景,这就会涉及多个Goroutine之间的调用.如果某一时刻请求其他服务被取消或者超时,则作为深陷其中的当前goroutine B需要立即退出,然后系统才可回收B所占用的资源. 即一个request中通常包含多个goroutine,这些goroutine之间通常会有交互.

       é‚£ä¹ˆ,如何有效管理这些goroutine成为一个问题(主要是退出通知和元数据传递问题),Google的解决方法是Context机制,相互调用的goroutine之间通过传递context变量保持关联,这样在不用暴露各goroutine内部实现细节的前提下,有效地控制各goroutine的运行.

       å¦‚此一来,通过传递Context就可以追踪goroutine调用树,并在这些调用树之间传递通知和元数据. 虽然goroutine之间是平行的,没有继承关系,但是Context设计成是包含父子关系的,这样可以更好的描述goroutine调用之间的树型关系.

3 怎么使用context

       ç”Ÿæˆä¸€ä¸ªContext主要有两类方法:

3.1 顶层Context:Background

       è¦åˆ›å»ºContext树,首先就是要创建根节点

//返回一个空的Context,它作为所有由此继承Context的根节点funcBackground()Context

       è¯¥Context通常由接收request的第一个goroutine创建,它不能被取消,没有值,也没有过期时间,常作为处理request的顶层context存在.

3.2 下层Context:WithCancel/WithDeadline/WithTimeout

       æœ‰äº†æ ¹èŠ‚点之后,接下来就是创建子孙节点.为了可以很好的控制子孙节点,Context包提供的创建方法均是带有第二返回值(CancelFunc类型),它相当于一个Hook,在子goroutine执行过程中,可以通过触发Hook来达到控制子goroutine的目的(通常是取消,即让其停下来).再配合Context提供的Done方法,子goroutine可以检查自身是否被父级节点Cancel:

select{ case<-ctx.Done()://dosomeclean…}

       æ³¨ï¼šçˆ¶èŠ‚点Context可以主动通过调用cancel方法取消子节点Context,而子节点Context只能被动等待.同时父节点Context自身一旦被取消(如其上级节点Cancel),其下的所有子节点Context均会自动被取消.

       æœ‰ä¸‰ç§åˆ›å»ºæ–¹æ³•ï¼š

//带cancel返回值的Context,一旦cancel被调用,即取消该创建的contextfuncWithCancel(parentContext)(ctxContext,cancelCancelFunc)//带有效期cancel返回值的Context,即必须到达指定时间点调用的cacel方法才会被执行funcWithDeadline(parentContext,deadlinetime.Time)(Context,CancelFunc)//带超时时间cancel返回值的Context,类似Deadline,前者是时间点,后者为时间间隔//相当于WithDeadline(parent,time.Now().Add(timeout)).funcWithTimeout(parentContext,timeouttime.Duration)(Context,CancelFunc)

       ä¸‹é¢æ¥çœ‹æ”¹ç¼–自Advanced Go Concurrency Patterns视频提供的一个简单例子:

packagemainimport("context""fmt""time")funcsomeHandler(){ //创建继承Background的子节点Contextctx,cancel:=context.WithCancel(context.Background())godoSth(ctx)//模拟程序运行-Sleep5秒time.Sleep(5*time.Second)cancel()}//每1秒work一下,同时会判断ctx是否被取消,如果是就退出funcdoSth(ctxcontext.Context){ vari=1for{ time.Sleep(1*time.Second)select{ case<-ctx.Done():fmt.Println("done")returndefault:fmt.Printf("work%dseconds:\n",i)}i++}}funcmain(){ fmt.Println("start...")someHandler()fmt.Println("end.")}

       è¾“出结果:

       æ³¨æ„,此时doSth方法中case之done的fmt.Println("done")并没有被打印出来.

       è¶…时场景:

packagemainimport("context""fmt""time")functimeoutHandler(){ //创建继承Background的子节点Contextctx,cancel:=context.WithTimeout(context.Background(),3*time.Second)godoSth(ctx)//模拟程序运行-Sleep秒time.Sleep(*time.Second)cancel()//3秒后将提前取消doSthgoroutine}//每1秒work一下,同时会判断ctx是否被取消,如果是就退出funcdoSth(ctxcontext.Context){ vari=1for{ time.Sleep(1*time.Second)select{ case<-ctx.Done():fmt.Println("done")returndefault:fmt.Printf("work%dseconds:\n",i)}i++}}funcmain(){ fmt.Println("start...")timeoutHandler()fmt.Println("end.")}

       è¾“出结果:

4 context是一个优雅的设计吗?

       ç¡®å®ž,通过引入Context包,一个request范围内所有goroutine运行时的取消可以得到有R效的控制.但是这种解决方式却不够优雅.

4.1 context 像病毒一样扩散

       ä¸€æ—¦ä»£ç ä¸­æŸå¤„用到了Context,传递Context变量(通常作为函数的第一个参数)会像病毒一样蔓延在各处调用它的地方. 比如在一个request中实现数据库事务或者分布式日志记录, 创建的context,会作为参数传递到任何有数据库操作或日志记录需求的函数代码处. 即每一个相关函数都必须增加一个context.Context类型的参数,且作为第一个参数,这对无关代码完全是侵入式的.

       æ›´å¤šè¯¦ç»†å†…容可参见:Michal Strba 的context-should-go-away-go2文章

       Google Group上的讨论可移步这里.

4.2 Context 不仅仅只是cancel信号

       Context机制最核心的功能是在goroutine之间传递cancel信号,但是它的实现是不完全的.

       Cancel可以细分为主动与被动两种,通过传递context参数,让调用goroutine可以主动cancel被调用goroutine.但是如何得知被调用goroutine什么时候执行完毕,这部分Context机制是没有实现的.而现实中的确又有一些这样的场景,比如一个组装数据的goroutine必须等待其他goroutine完成才可开始执行,这是context明显不够用了,必须借助sync.WaitGroup.

funcserve(lnet.Listener)error{ varwgsync.WaitGroupvarconnnet.Connvarerrerrorfor{ conn,err=l.Accept()iferr!=nil{ break}wg.Add(1)gofunc(cnet.Conn){ deferwg.Done()handle(c)}(conn)}wg.Wait()returnerr}4.3 context.value

       context.Value相当于goroutine的TLS(Thread Local Storage),但它不是静态类型安全的,任何结构体变量都必须作为字符串形式存储.同时,所有context都会在其中定义变量,很容易造成命名冲突.

5 总结

       context包通过构建树型关系的Context,来达到上一层Goroutine能对传递给下一层Goroutine的控制.对于处理一个Request请求操作,需要采用context来层层控制Goroutine,以及传递一些变量来共享.

       Context对象的生存周期一般仅为一个请求的处理周期.即针对一个请求创建一个Context变量(它为Context树结构的根);在请求处理结束后,撤销此ctx变量,释放资源.

       æ¯æ¬¡åˆ›å»ºä¸€ä¸ªGoroutine,要么将原有的Context传递给Goroutine,要么创建一个子Context并传递给Goroutine.

       Context能灵活地存储不同类型,不同数目的值,并且使多个Goroutine安全地读写其中的值.

       å½“通过父Context对象创建子Context对象时,可同时获得子Context的一个撤销函数,这样父Context对象的创建环境就获得了对子Context将要被传递到的Goroutine的撤销权.

       åœ¨å­Context被传递到的goroutine中,应该对该子Context的Done信道(channel)进行监控,一旦该信道被关闭(即上层运行环境撤销了本goroutine的执行),应主动终止对当前请求信息的处理,释放资源并返回.

6 致谢

       pkg/context

       context-should-go-away-go2

       ç†è§£ Go Context 机制

       context-isnt-for-cancellation

       context-is-for-cancelation

       thread-local-a-convenient-abomination

Java教程:dubbo源码解析-网络通信

       在之前的内容中,我们探讨了消费者端服务发现与提供者端服务暴露的相关内容,同时了解到消费者端通过内置的负载均衡算法获取合适的调用invoker进行远程调用。接下来,我们聚焦于远程调用过程,即网络通信的周口苹果源码细节。

       网络通信位于Remoting模块中,支持多种通信协议,包括但不限于:dubbo协议、rmi协议、hessian协议、ty进行网络通讯,NettyClient.doOpen()方法中可以看到Netty的相关类。序列化接口包括但不限于:Serialization接口、Hessian2Serialization接口、Kryo接口、FST接口等。

       序列化方式如Kryo和FST,性能往往优于hessian2,能够显著提高序列化性能。这些高效Java序列化方式的引入,可以优化Dubbo的序列化过程。

       在配置Dubbo RPC时,引入Kryo和FST非常简单,只需在RPC的XML配置中添加相应的属性即可。

       关于服务消费方发送请求,Dubbo框架定义了私有的阶梯分行 源码RPC协议,消息头和消息体分别用于存储元信息和具体调用消息。消息头包括魔数、数据包类型、消息体长度等。消息体包含调用消息,如方法名称、参数列表等。请求编码和解码过程涉及编解码器的使用,编码过程包括消息头的写入、序列化数据的存储以及长度的写入。解码过程则涉及消息头的读取、序列化数据的解析以及调用方法名、参数等信息的提取。

       提供方接收请求后,服务调用过程包含请求解码、调用服务以及返回结果。解码过程在NettyHandler中完成,通过ChannelEventRunnable和DecodeHandler进一步处理请求。服务调用完成后,通过Invoker的invoke方法调用服务逻辑。响应数据的编码与请求数据编码过程类似,涉及数据包的构造与发送。

       服务消费方接收调用结果后,首先进行响应数据解码,获得Response对象,并传递给下一个处理器NettyHandler。解压内核源码处理后,响应数据被派发到线程池中,此过程与服务提供方接收请求的过程类似。

       在异步通信场景中,Dubbo在通信层面为异步操作,通信线程不会等待结果返回。默认情况下,RPC调用被视为同步操作。Dubbo通过CompletableFuture实现了异步转同步操作,通过设置异步返回结果并使用CompletableFuture的get()方法等待完成。

       对于异步多线程数据一致性问题,Dubbo使用编号将响应对象与Future对象关联,确保每个响应对象被正确传递到相应的Future对象。通过在创建Future时传入Request对象,可以获取调用编号并建立映射关系。线程池中的线程根据Response对象中的调用编号找到对应的Future对象,将响应结果设置到Future对象中,供用户线程获取。

       为了检测Client端与Server端的连通性,Dubbo采用双向心跳机制。HeaderExchangeClient初始化时,开启两个定时任务:发送心跳请求和处理重连与断连。心跳检测定时任务HeartbeatTimerTask确保连接空闲时向对端发送心跳包,而ReconnectTimerTask则负责检测连接状态,当判定为超时后,客户端选择重连,帝国源码92服务端采取断开连接的措施。

gRPC 基础概念详解

       gRPC基础概念详解

       本文将为您介绍gRPC的基础概念。首先通过关系图直观展示基础概念之间的关联,介绍异步gRPC的Server和Client的逻辑;然后介绍RPC的类型,阅读和抓包分析gRPC的通信过程协议,以及gRPC上下文;最后,分析grpc.pb.h文件的内容,包括Stub的能力、Service的种类以及与核心库的关系。

       一、基本概念概览

       上图展示了gRPC的基础概念及其关系。其中包括:Service(定义)、RPC、API、Client、Stub、Channel、Server、Service(实现)、ServiceBuilder等。

       以example/helloworld为例进行说明

       .proto文件定义了服务Greeter和APISayHello。

       类GreeterClient是Client,是对Stub的封装,通过Stub可以真正调用RPC请求。

       Channel提供一个与特定gRPC server的主机和端口建立的连接。

       Stub是源码转换app在Channel的基础上创建而成的。

       Server端需要实现对应的RPC,所有的RPC组成了Service:

       Server的创建需要一个Builder,添加上监听的地址和端口,注册上该端口上绑定的服务,最后构建出Server并启动。

       RPC和API的区别

       RPC(Remote Procedure Call)是一次远程过程调用的整个动作,而API(Application Programming Interface)是在实现RPC中的不同语言的具体接口。一个RPC可能对应多种API,比如同步的、异步的、回调的。一次RPC是对某个API的一次调用,例如:

       不管是哪种类型的RPC,都是由Client发起请求。

       二、异步相关概念

       无论是Client还是Server,异步gRPC都利用CompletionQueueAPI进行异步操作。基本流程如下:

       官方文档Asynchronous-API tutorial中有详细介绍,包括异步client和server的解释,对应文件greeter_async_client.cc和greeter_async_server.cc。

       1. 异步Client

       greeter_async_client.cc是异步Client的Demo,逻辑相对简单。

       2. 异步Server

       RequestSayHello函数没有详细说明,文档和注释中的解释可能不够清晰。通过增加日志打印,可以更清晰展示Server的逻辑。

       3. 关系图

       通过关系图展示异步Client和异步Server的逻辑。右侧为创建对象中的内存内容,左侧使用相同颜色的小块进行表示。

       4. 异步Client 2

       在example/cpp/helloworld中,还有另一个异步Client,对应文件greeter_async_client2.cc。此示例中使用两个线程分别进行请求发送和返回处理。

       5. 回调方式的异步调用

       在example/cpp/helloworld中,提供回调相关的Client和Server。使用回调方式结构与同步方式相似,但并发方式不同。可以通过文件对比查看其中差异。

       5.1 回调Client

       发送单个请求,在调用SayHello时,除了传入Request、Reply的地址,还需要传入接收Status的回调函数。

       例子中只有一个请求,因此在SayHello后直接通过condition_variable的wait函数等待回调结束,然后进行后续处理。这种情况下不能进行并发,与同步请求类似。大规模并发时需要额外封装。

       5.2 回调Server

       与同步Server不同之处在于...

       三、流相关概念

       可以按照Client和Server一次发送/返回的是单个消息还是多个消息,将gRPC分为:

       1. Server对RPC的实现

       Server需要实现proto中定义的RPC,每种RPC的实现都需要将ServerContext作为参数输入。

       2. Client对RPC的调用

       Client调用一元(Unary)RPC时,像调用普通函数一样,除了传入ClientContext之外,将Request和Response的地址传入,返回的是RPC状态。

       四、通信协议

       本节通过介绍gRPC协议文档描述和对helloworld的抓包,来说明gRPC是如何基于HTTP2传输的。

       1. ABNF语法

       ABNF语法是一种描述协议的标准,gRPC协议也是使用ABNF语法描述,包括几种常见的运算符。

       2. 请求协议

       

*       表示element会重复多次(最少0次),理解概况描述。

       3. 返回协议

       ()表示括号中的内容作为单个元素对待,/表示前后两个元素可选其一。Response的定义说明,可以有两种返回形式。

       五、上下文

       gRPC支持上下文的传递,其主要用途有:

       客户端添加自定义的metadata key-value对没有特别的区分,而服务端添加的,则有initial和trailing两种metadata的区别。

       六、生成代码

       通过protoc生成gRPC相关的文件,除了用于消息体定义的xxx.pb.h和xxx.pb.cc文件之外,就是定义RPC过程的xxx.grpc.pb.h和xxx.grpc.pb.cc。以helloworld.proto生成的文件为例,看看.xxx.grpc.pb文件具体定义了些什么。

       1. Stub

       .proto中的一个service只有一个Stub,该类中会提供对应每个RPC的所有同步、异步、回调等方式的函数,而该类继承自接口类StubInterface。

       2. Service

       有几个概念都叫Service:proto文件中RPC的集合、proto文件中service产生源文件中的Greeter::Service类、gRPC框架中的::grpc::Service类。本小节说的Service就是helloworld.grpc.pb.h中的Greeter::Service。

       3. Service的种类

       helloworld.grpc.pb.h文件中7种Service中有3对Service的实际含义相同,实际只剩下4种Service。

       4. 与::grpc核心库的关系

       Stub类中主要是用到gRPCChannel和不同类型RPC对应的实现方法。

       总结

       通过本文,您应该对gRPC的基础概念有了全面的了解。从基本概念到异步操作、流相关概念、通信协议、上下文到生成代码,每个环节都进行了详细的阐述。希望本文能帮助您在gRPC开发中更加得心应手。

Dubbo源码解析:网络通信

       <dubbo源码解析:深入理解网络通信

       在之前的章节中,我们已经了解了消费者如何通过服务发现和负载均衡机制找到提供者并进行远程调用。本章将重点解析网络通信的实现细节。

       网络通信主要在Dubbo的Remoting模块中进行,涉及多种通信协议,包括dubbo协议、RMI、Hessian、HTTP、WebService、Thrift、REST、gRPC、Memcached和Redis等。每个协议都有其特定的优缺点,如Dubbo协议适用于高并发场景,而RMI则使用标准JDK序列化。

       Dubbo的序列化机制支持多种方式,如Hessian2、Kryo、FST等。近年来,高效序列化技术如Kryo和FST的出现,可提升性能,只需在配置中简单添加即可优化。

       关于数据格式和粘包拆包问题,Dubbo采用私有RPC协议,消息头存储元信息,如魔法数和数据类型,消息体则包含调用信息。消费者发送请求时,会通过MockClusterInvoker封装服务降级逻辑,然后通过序列化转换为网络可传输的数据格式。

       服务提供方接收请求时,首先对数据包进行解码,确认其格式正确性,然后调用服务逻辑。提供方返回调用结果时,同样经过序列化和编码,最后通过NettyChannel发送给消费者。

       在心跳检测方面,Dubbo采用双向心跳机制,客户端和服务端定期发送心跳请求以维持连接。此外,还通过定时任务处理重连和断连,确保连接的稳定性和可靠性。

       总的来说,Dubbo的网络通信模块精细且灵活,通过多种协议和优化技术确保服务调用的高效和可靠性。

相关栏目:综合