gRPC配置化的結(jié)構(gòu)體源碼怎么寫,相信很多沒有經(jīng)驗(yàn)的人對此束手無策,為此本文總結(jié)了問題出現(xiàn)的原因和解決方法,通過這篇文章希望你能解決這個問題。
10年積累的網(wǎng)站設(shè)計(jì)、做網(wǎng)站經(jīng)驗(yàn),可以快速應(yīng)對客戶對網(wǎng)站的新想法和需求。提供各種問題對應(yīng)的解決方案。讓選擇我們的客戶得到更好、更有力的網(wǎng)絡(luò)服務(wù)。我雖然不認(rèn)識你,你也不認(rèn)識我。但先網(wǎng)站制作后付款的網(wǎng)站建設(shè)流程,更有集美免費(fèi)網(wǎng)站建設(shè)讓你可以放心的選擇與我們合作。
DialOptions 是最重要的一環(huán),負(fù)責(zé)配置每一次 rpc 請求的時候的一應(yīng)選擇。
先來看看這個的結(jié)構(gòu)
鏈接
// dialOptions configure a Dial call. dialOptions are set by the DialOption // values passed to Dial. type dialOptions struct { unaryInt UnaryClientInterceptor streamInt StreamClientInterceptor chainUnaryInts []UnaryClientInterceptor chainStreamInts []StreamClientInterceptor cp Compressor dc Decompressor bs backoff.Strategy block bool insecure bool timeout time.Duration scChan <-chan ServiceConfig authority string copts transport.ConnectOptions callOptions []CallOption // This is used by v1 balancer dial option WithBalancer to support v1 // balancer, and also by WithBalancerName dial option. balancerBuilder balancer.Builder // This is to support grpclb. resolverBuilder resolver.Builder channelzParentID int64 disableServiceConfig bool disableRetry bool disableHealthCheck bool healthCheckFunc internal.HealthChecker minConnectTimeout func() time.Duration defaultServiceConfig *ServiceConfig // defaultServiceConfig is parsed from defaultServiceConfigRawJSON. defaultServiceConfigRawJSON *string }
由于命名非常規(guī)范,加上注釋很容易看懂每一個 field 配置的哪一條屬性。如果掠過看的 大概有 壓縮解壓器,超時阻塞設(shè)置,認(rèn)證安全轉(zhuǎn)發(fā),負(fù)載均衡,服務(wù)持久化的信息存儲 ,配置,心跳檢測等。
其一應(yīng)函數(shù)方法都是設(shè)置 其中字段的。
這里是 grpc 設(shè)計(jì)較好的地方,通過函數(shù)設(shè)置,同時設(shè)有生成函數(shù)的函數(shù)。什么意思呢?首先結(jié)合圖來理解,這也是整個 grpc 設(shè)置的精華部分
cdn.nlark.com/yuque/0/2019/svg/176280/1564060510943-3faee934-4035-474e-ac4d-b8ae9ee446b1.svg">
這里的意思是 , DialOptions 是一個導(dǎo)出接口,實(shí)現(xiàn)函數(shù)是 apply 同時接受參數(shù) dialOptions 來修改它。
而實(shí)際上,是使用 newFuncDialOption 函數(shù)包裝一個 修改 dialOptions 的方法給 funcDialOption 結(jié)構(gòu)體,在實(shí)際 Dial 調(diào)用的時候 是使用閉包 調(diào)用 funcDialOption 結(jié)構(gòu)體的 apply 方法。
可以在這里看一下 Dial 方法的源碼(Dial 調(diào)用的是 DialContext
起作用的就是 opt.apply()
func DialContext(ctx context.Context, target string, opts ...DialOption) (conn *ClientConn, err error) { cc := &ClientConn{ target: target, csMgr: &connectivityStateManager{}, conns: make(map[*addrConn]struct{}), dopts: defaultDialOptions(), blockingpicker: newPickerWrapper(), czData: new(channelzData), firstResolveEvent: grpcsync.NewEvent(), } ··· for _, opt := range opts { opt.apply(&cc.dopts) } ··· }
這里的 options 可以說是 client 發(fā)起 rpc 請求的核心中轉(zhuǎn)站。
另一個重要的接口,同時也集中在 dialOptions 結(jié)構(gòu)體中初始化處理的是
callOptions []CallOption
CallOption 是一個接口,定義在 rpc_util 包內(nèi)
// CallOption configures a Call before it starts or extracts information from // a Call after it completes. type CallOption interface { // before is called before the call is sent to any server. If before // returns a non-nil error, the RPC fails with that error. before(*callInfo) error // after is called after the call has completed. after cannot return an // error, so any failures should be reported via output parameters. after(*callInfo) }
操作的是 callInfo 結(jié)構(gòu)里的數(shù)據(jù),其被包含在 dialOptions
結(jié)構(gòu)體中,
即每一次 dial 的時候進(jìn)行調(diào)用。
同時它自身定義很有意思,操作的是 callInfo
結(jié)構(gòu)體
// callInfo contains all related configuration and information about an RPC. type callInfo struct { compressorType string failFast bool stream ClientStream maxReceiveMessageSize *int maxSendMessageSize *int creds credentials.PerRPCCredentials contentSubtype string codec baseCodec maxRetryRPCBufferSize int }
可以看到 callInfo 中字段用來表示 單次調(diào)用中獨(dú)有的自定義選項(xiàng)如 壓縮,流控,認(rèn)證,編解碼器等。
簡單看一個 CallOption 接口的實(shí)現(xiàn)
// Header returns a CallOptions that retrieves the header metadata // for a unary RPC. func Header(md *metadata.MD) CallOption { return HeaderCallOption{HeaderAddr: md} } // HeaderCallOption is a CallOption for collecting response header metadata. // The metadata field will be populated *after* the RPC completes. // This is an EXPERIMENTAL API. type HeaderCallOption struct { HeaderAddr *metadata.MD } func (o HeaderCallOption) before(c *callInfo) error { return nil } func (o HeaderCallOption) after(c *callInfo) { if c.stream != nil { *o.HeaderAddr, _ = c.stream.Header() } }
重點(diǎn)看到,實(shí)際操作是在 before 和 after 方法中執(zhí)行,它們會在 Client 發(fā)起請求的時候自動執(zhí)行,顧名思義,一個在調(diào)用前執(zhí)行,一個在調(diào)用后執(zhí)行。
這里可以看出,這里也是通過函數(shù)返回一個擁有這兩個方法的結(jié)構(gòu)體,注意這一個設(shè)計(jì),可以作為你自己的 Option 設(shè)計(jì)的時候的參考。
有兩種方法讓 Client 接受你的 CallOption 設(shè)置
在 Client 使用方法的時候直接作為 參數(shù)傳遞,將剛才所說的函數(shù)-返回一個實(shí)現(xiàn)了 CallOption 接口的結(jié)構(gòu)體。
在 生成 Client 的時候就傳遞設(shè)置。具體如下
通過 dialOptions.go 中的 函數(shù) grpc.WithDefaultCallOptions()
這個函數(shù)會將 CallOption 設(shè)置到 dialOptions 中的字段 []CallOption 中。
// WithDefaultCallOptions returns a DialOption which sets the default // CallOptions for calls over the connection. func WithDefaultCallOptions(cos ...CallOption) DialOption { return newFuncDialOption(func(o *dialOptions) { o.callOptions = append(o.callOptions, cos...) }) }
有沒有感覺有點(diǎn)不好理解?給你們一個實(shí)例
使用的第一種方法
response, err := myclient.MyCall(ctx, request, grpc.CallContentSubtype("mycodec"))
使用第二種方法
myclient := grpc.Dial(ctx, target, grpc.WithDefaultCallOptions(grpc.CallContentSubtype("mycodec")))
這里假設(shè) 我們設(shè)置了一個 mycodec 的譯碼器。馬上下面解釋它的設(shè)計(jì)。
值得注意的是, 我好像只提到了在 Client 調(diào)用時設(shè)置,callOption 只在客戶端設(shè)置的情況是不是讓大家感到困惑。
實(shí)際上 gRPC server 端會自動檢測 callOption 的設(shè)置,并檢測自己是否支持此項(xiàng)選擇,如果不支持則會返回失敗。也就是說,在 Server 端注冊的所有 Codec 譯碼器之后,Client 直接使用相應(yīng)的設(shè)置就好了。
在 gRPC 中 Codec 有兩個接口定義,一個是 baseCodec 包含正常的 Marshal 和 Unmarshal 方法,另一個是擁有名字的 Codec 定義在 encoding 包內(nèi),這是由于在注冊 registry 的時候會使用到這個方法。
type Codec interface { // Marshal returns the wire format of v. Marshal(v interface{}) ([]byte, error) // Unmarshal parses the wire format into v. Unmarshal(data []byte, v interface{}) error // String returns the name of the Codec implementation. This is unused by // gRPC. String() string }
就是這個方法
// RegisterCodec registers the provided Codec for use with all gRPC clients and // servers. // // The Codec will be stored and looked up by result of its Name() method, which // should match the content-subtype of the encoding handled by the Codec. This // is case-insensitive, and is stored and looked up as lowercase. If the // result of calling Name() is an empty string, RegisterCodec will panic. See // Content-Type on // https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#requests for // more details. // // NOTE: this function must only be called during initialization time (i.e. in // an init() function), and is not thread-safe. If multiple Compressors are // registered with the same name, the one registered last will take effect. func RegisterCodec(codec Codec) { if codec == nil { panic("cannot register a nil Codec") } if codec.Name() == "">
同時 encoding 包中還定義了 Compressor 接口,參照 Codec 理解即可。
// Compressor is used for compressing and decompressing when sending or // receiving messages. type Compressor interface { // Compress writes the data written to wc to w after compressing it. If an // error occurs while initializing the compressor, that error is returned // instead. Compress(w io.Writer) (io.WriteCloser, error) // Decompress reads data from r, decompresses it, and provides the // uncompressed data via the returned io.Reader. If an error occurs while // initializing the decompressor, that error is returned instead. Decompress(r io.Reader) (io.Reader, error) // Name is the name of the compression codec and is used to set the content // coding header. The result must be static; the result cannot change // between calls. Name() string }
這個包對應(yīng) context 中的 Value field 也就是 key-value 形式的存儲
在其他包中簡寫是 MD
type MD map[string][]string
實(shí)現(xiàn)了完善的存儲功能,從單一讀寫到批量(采用 pair 模式,...string 作為參數(shù),len(string)%2==1 時會報(bào)錯,由于會有孤立的沒有配對的元信息。
另外幾個函數(shù)是實(shí)現(xiàn)了從 context 中的讀取和寫入(這里的寫入是 使用 context.WithValue 方法,即生成 parent context 的 copy。
值得注意的是,在 MetaData 結(jié)構(gòu)體中, value 的結(jié)構(gòu)是 []string 。
同時 key 不可以以 "grpc-" 開頭,這是因?yàn)樵?grpc 的 internal 包中已經(jīng)保留了。
更為重要的是 在 context 中的讀取方式,其實(shí)是 MetaData 結(jié)構(gòu)對應(yīng)的是 context Value 中的 value 值,而 key 值設(shè)為 一個空結(jié)構(gòu)體同時區(qū)分輸入輸入
type mdIncomingKey struct{}
type mdOutgoingKey struct{}
看完上述內(nèi)容,你們掌握gRPC配置化的結(jié)構(gòu)體源碼怎么寫的方法了嗎?如果還想學(xué)到更多技能或想了解更多相關(guān)內(nèi)容,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,感謝各位的閱讀!