<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Kubernetes 資源對象序列化實(shí)現(xiàn)

          共 22186字,需瀏覽 45分鐘

           ·

          2021-06-09 07:57

          序列化和反序列化在很多項(xiàng)目中都有應(yīng)用,Kubernetes也不例外。Kubernetes中定義了大量的API對象,為此還單獨(dú)設(shè)計(jì)了一個(gè)(https://github.com/kubernetes/api),方便多個(gè)模塊引用。API對象在不同的模塊之間傳輸(尤其是跨進(jìn)程)可能會用到序列化與反序列化,不同的場景對于序列化個(gè)格式又不同,比如grpc協(xié)議用protobuf,用戶交互用yaml(因?yàn)閥aml可讀性強(qiáng)),etcd存儲用json。Kubernetes反序列化API對象不同于我們常用的json.Unmarshal()函數(shù)(需要傳入對象指針),Kubernetes需要解析對象的類型(Group/Version/Kind),根據(jù)API對象的類型構(gòu)造API對象,然后再反序列化。因此,Kubernetes定義了Serializer接口,專門用于API對象的序列化和反序列化。本文引用源碼為kubernetes的release-1.21分支。

          Serializer

          因?yàn)镵ubernetes需要支持json、yaml、protobuf三種數(shù)據(jù)格式的序列化和反序列化,有必要抽象序列化和反序列化的統(tǒng)一接口,源碼鏈接:https://github.com/kubernetes/apimachinery/blob/release-1.21/pkg/runtime/interfaces.go#L86

          // Serializer是用于序列化和反序列化API對象的核心接口,
          type Serializer interface {
              // Serializer繼承了編碼器和解碼器,編碼器就是用來序列化API對象的,序列化的過程稱之為編碼;反之,反序列化的過程稱之為解碼。
              // 關(guān)于編/解碼器的定義下面有注釋。
           Encoder
           Decoder
          }

          // 序列化的過程稱之為編碼,實(shí)現(xiàn)編碼的對象稱之為編碼器(Encoder)
          type Encoder interface {
              // Encode()將對象寫入流??梢詫ncode()看做為json(yaml).Marshal(),只是輸出變?yōu)閕o.Writer。
           Encode(obj Object, w io.Writer) error

              // Identifier()返回編碼器的標(biāo)識符,當(dāng)且僅當(dāng)兩個(gè)不同的編碼器編碼同一個(gè)對象的輸出是相同的,那么這兩個(gè)編碼器的標(biāo)識符也應(yīng)該是相同的。
              // 也就是說,編碼器都有一個(gè)標(biāo)識符,兩個(gè)編碼器的標(biāo)識符可能是相同的,判斷標(biāo)準(zhǔn)是編碼任意API對象時(shí)輸出都是相同的。
              // 標(biāo)識符有什么用?標(biāo)識符目標(biāo)是與CacheableObject.CacheEncode()方法一起使用,CacheableObject又是什么東東?后面有介紹。
           Identifier() Identifier
          }

          // 標(biāo)識符就是字符串,可以簡單的理解為標(biāo)簽的字符串形式,后面會看到如何生成標(biāo)識符。
          type Identifier string

          // 反序列化的過程稱之為解碼,實(shí)現(xiàn)解碼的對象稱之為解碼器(Decoder)
          type Decoder interface {
              // Decode()嘗試使用Schema中注冊的類型或者提供的默認(rèn)的GVK反序列化API對象。
              // 如果'into'非空將被用作目標(biāo)類型,接口實(shí)現(xiàn)可能會選擇使用它而不是重新構(gòu)造一個(gè)對象。
              // 但是不能保證輸出到'into'指向的對象,因?yàn)榉祷氐膶ο蟛槐WC匹配'into'。
              // 如果提供了默認(rèn)GVK,將應(yīng)用默認(rèn)GVK反序列化,如果未提供默認(rèn)GVK或僅提供部分,則使用'into'的類型補(bǔ)全。
           Decode(data []byte, defaults *schema.GroupVersionKind, into Object) (Object, *schema.GroupVersionKind, error)
          }

          我們平時(shí)工作中最常用的序列化格式包括json、yaml以及protobuf,非常"巧合",Kubernetes也是用這幾種序列化格式。以json為例,編碼器和解碼器可以等同于json.Marshal()和json.Unmarshal(),定義成interface是對序列化與反序列化的統(tǒng)一抽象。為什么我們平時(shí)很少抽象(當(dāng)然有些讀者是有抽象的,我們不能一概而論),是因?yàn)槲覀児ぷ髦锌赡苤挥玫揭环N序列化格式,所以抽象顯得沒那么必要。而Kubernetes中,這三種都是需要的,yaml的可視化效果好,比如我們寫的各種yaml文件;而API對象存儲在etcd中是json格式,在用到grpc的地方則需要protobuf格式。

          再者,Kubernetes對于Serializer的定義有更高的要求,即根據(jù)序列化的數(shù)據(jù)中的元數(shù)據(jù)自動識別API對象的類型(GVK),這在Decoder.Decode()接口定義中已經(jīng)有所了解。而我們平時(shí)使用json.Marshal()的時(shí)候傳入了指定類型的對象指針,相比于Kubernetes對于反序列化的要求,我們使用的相對更"靜態(tài)"。

          綜上所述,抽象Serializer就有必要了,尤其是RecognizingDecoder可以解碼任意格式的API對象就可以充分體現(xiàn)這種抽象的價(jià)值。

          json

          json.Serializer實(shí)現(xiàn)了將API對象序列化成json數(shù)據(jù)和從json數(shù)據(jù)反序列化API對象,源碼鏈接:https://github.com/kubernetes/apimachinery/blob/release-1.21/pkg/runtime/serializer/json/json.go#L100

          // Serializer實(shí)現(xiàn)了runtime.Serializer接口。
          type Serializer struct {
              // MetaFactory從json數(shù)據(jù)中提取GVK(Group/Version/Kind),下面有MetaFactory注釋。
              // MetaFactory很有用,解碼時(shí)如果不提供默認(rèn)的GVK和API對象指針,就要靠MetaFactory提取GVK了。
              // 當(dāng)然,即便提供了供默認(rèn)的GVK和API對象指針,提取的GVK的也是非常有用的,詳情參看Decode()接口的實(shí)現(xiàn)。
           meta    MetaFactory
              // SerializerOptions是Serializer選項(xiàng),可以看做是配置,下面有注釋。
           options SerializerOptions
              // runtime.ObjectCreater根據(jù)GVK構(gòu)造API對象,在反序列化時(shí)會用到,其實(shí)它就是Schema。
              // runtime.ObjectCreater的定義讀者可以自己查看源碼,如果對Schema熟悉的讀者這都不是事。
           creater runtime.ObjectCreater
              // runtime.ObjectTyper根據(jù)API對象返回可能的GVK,也是用在反序列化中,其實(shí)它也是Schema。
              // 這個(gè)有什么用?runtime.Serializer.Decode()接口注釋說的很清楚,在json數(shù)據(jù)和默認(rèn)GVK無法提供的類型元數(shù)據(jù)需要用輸出類型補(bǔ)全。
           typer   runtime.ObjectTyper
              // 標(biāo)識符,Serializer一旦被創(chuàng)建,標(biāo)識符就不會變了。
           identifier runtime.Identifier
          }

          // SerializerOptions定義了Serializer的選項(xiàng).
          type SerializerOptions struct {
              // true: 序列化/反序列化yaml;false: 序列化/反序列化json
              // 也就是說,json.Serializer既可以序列化/反序列json,也可以序列化/反序列yaml。
           Yaml bool

              // Pretty選項(xiàng)僅用于Encode接口,輸出易于閱讀的json數(shù)據(jù)。當(dāng)Yaml選項(xiàng)為true時(shí),Pretty選項(xiàng)被忽略,因?yàn)閥aml本身就易于閱讀。
              // 什么是易于閱讀的?舉個(gè)例子就立刻明白了,定義測試類型為:
              // type Test struct {
              //     A int
              //     B string    
              // }
              // 則關(guān)閉和開啟Pretty選項(xiàng)的對比如下:
              // {"A":1,"B":"2"}
              // {
              //   "A": 1,
              //   "B": "2"
              // }
              // 很明顯,后者更易于閱讀。易于閱讀只有人看的時(shí)候才有需要,對于機(jī)器來說一點(diǎn)價(jià)值都沒有,所以這個(gè)選項(xiàng)使用范圍還是比較有限的。
           Pretty bool

              // Strict應(yīng)用于Decode接口,表示嚴(yán)謹(jǐn)?shù)?。那什么是?yán)謹(jǐn)?shù)??筆者很難用語言表達(dá),但是以下幾種情況是不嚴(yán)謹(jǐn)?shù)模?/span>
              // 1. 存在重復(fù)字段,比如{"value":1,"value":1};
              // 2. 不存在的字段,比如{"unknown": 1},而目標(biāo)API對象中不存在Unknown屬性;
              // 3. 未打標(biāo)簽字段,比如{"Other":"test"},雖然目標(biāo)API對象中有Other字段,但是沒有打`json:"Other"`標(biāo)簽
              // Strict選項(xiàng)可以理解為增加了很多校驗(yàn),請注意,啟用此選項(xiàng)的性能下降非常嚴(yán)重,因此不應(yīng)在性能敏感的場景中使用。
              // 那什么場景需要用到Strict選項(xiàng)?比如Kubernetes各個(gè)服務(wù)的配置API,對性能要求不高,但需要嚴(yán)格的校驗(yàn)。
           Strict bool
          }

          MetaFactory

          MetaFactory類型名定義其實(shí)挺忽悠人的,工廠類都是用來構(gòu)造對象的,MetaFactory的功能雖然也是構(gòu)造類型元數(shù)據(jù)的,但是它更像是一個(gè)解析器,所以筆者認(rèn)為"MetaParser"更加貼切,源碼鏈接:https://github.com/kubernetes/apimachinery/blob/release-1.21/pkg/runtime/serializer/json/meta.go#L28

          type MetaFactory interface {
              // 解析json數(shù)據(jù)中的元數(shù)據(jù)字段,返回GVK(Group/Version/Kind)。
              // 如果MetaFactory就這么一個(gè)接口函數(shù),筆者認(rèn)為叫解釋器或者解析器更加合理。
           Interpret(data []byte) (*schema.GroupVersionKind, error)
          }

          // SimpleMetaFactory是MetaFactory的一種實(shí)現(xiàn),用于檢索在json中由"apiVersion"和"kind"字段標(biāo)識的對象的類型和版本。
          type SimpleMetaFactory struct {
          }

          // Interpret()實(shí)現(xiàn)了MetaFactory.Interpret()接口。
          func (SimpleMetaFactory) Interpret(data []byte) (*schema.GroupVersionKind, error) {
              // 定義一種只有apiVersion和kind兩個(gè)字段的匿名類型
           findKind := struct {
            // +optional
            APIVersion string `json:"apiVersion,omitempty"`
            // +optional
            Kind string `json:"kind,omitempty"`
           }{}
              // 只解析json中apiVersion和kind字段,這個(gè)玩法有點(diǎn)意思,但是筆者認(rèn)為這個(gè)方法有點(diǎn)簡單粗暴。
              // 讀者可以嘗試閱讀json.Unmarshal(),該函數(shù)會遍歷整個(gè)json,開銷不小,其實(shí)必要性不強(qiáng),因?yàn)橹恍枰猘piVersion和kind字段。
              // 試想一下,如果每次反序列化一個(gè)API對象都要有一次Interpret()和Decode(),它的開銷相當(dāng)于做了兩次反序列化。
           if err := json.Unmarshal(data, &findKind); err != nil {
            return nil, fmt.Errorf("couldn't get version/kind; json parse error: %v", err)
           }
              // 將apiVersion解析為Group和Version
           gv, err := schema.ParseGroupVersion(findKind.APIVersion)
           if err != nil {
            return nil, err
           }
              // 返回API對象的GVK
           return &schema.GroupVersionKind{Group: gv.Group, Version: gv.Version, Kind: findKind.Kind}, nil
          }

          Decode

          其實(shí)Serializer的重頭戲在解碼,因?yàn)榻獯a需要考慮的事情比較多,比如提取類型元數(shù)據(jù)(GVK),根據(jù)類型元數(shù)據(jù)構(gòu)造API對象,當(dāng)然還要考慮復(fù)用傳入的API對象。而編碼就沒有這么復(fù)雜,所以理解了解碼的實(shí)現(xiàn),編碼就基本可以忽略不計(jì)了。源碼鏈接:https://github.com/kubernetes/apimachinery/blob/release-1.21/pkg/runtime/serializer/json/json.go#L209

          // Decode實(shí)現(xiàn)了Decoder.Decode(),嘗試從數(shù)據(jù)中提取的API類型(GVK),應(yīng)用提供的默認(rèn)GVK,然后將數(shù)據(jù)加載到所需類型或提供的'into'匹配的對象中:
          // 1. 如果into為*runtime.Unknown,則將提取原始數(shù)據(jù),并且不執(zhí)行解碼;
          // 2. 如果into的類型沒有在Schema注冊,則使用json.Unmarshal()直接反序列化到'into'指向的對象中;
          // 3. 如果'into'不為空且原始數(shù)據(jù)GVK不全,則'into'的類型(GVK)將用于補(bǔ)全GVK;
          // 4. 如果'into'為空或數(shù)據(jù)中的GVK與'into'的GVK不同,它將使用ObjectCreater.New(gvk)生成一個(gè)新對象;
          // 成功或大部分錯(cuò)誤都會返回GVK,GVK的計(jì)算優(yōu)先級為originalData > default gvk > into.
          func (s *Serializer) Decode(originalData []byte, gvk *schema.GroupVersionKind, into runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) {
           data := originalData
              // 如果配置選項(xiàng)為yaml,則將yaml格式轉(zhuǎn)為json格式,是不是有一種感覺:“臥了個(gè)槽”!所謂可支持json和yaml,就是先將yaml轉(zhuǎn)為json!
              // 那我感覺我可以支持所有格式,都轉(zhuǎn)為json就完了唄!其實(shí)不然,yaml的使用場景都是需要和人交互的地方,所以對于效率要求不高(qps低)。
              // 那么實(shí)現(xiàn)簡單易于維護(hù)更重要,所以這種實(shí)現(xiàn)并沒有什么毛病。
           if s.options.Yaml {
                  // yaml轉(zhuǎn)json
            altered, err := yaml.YAMLToJSON(data)

            if err != nil {
             return nilnil, err
            }
            data = altered
           }
              // 此時(shí)data是json了,以后就不用再考慮yaml選項(xiàng)了

              // 解析類型元數(shù)據(jù),原理已經(jīng)在MetaFactory解釋過了。
           actual, err := s.meta.Interpret(data)
           if err != nil {
            return nilnil, err
           }

              // 解析類型元數(shù)據(jù)大部分情況是正確的,除非不是json或者apiVersion格式不對。
              // 但是GVK三元組可能有所缺失,比如只有Kind,Group/Version,其他字段就用默認(rèn)的GVK補(bǔ)全。
              // 這也體現(xiàn)出了原始數(shù)據(jù)中的GVK的優(yōu)先級最高,其次是默認(rèn)的GVK。gvkWithDefaults()函數(shù)下面有注釋。
           if gvk != nil {
            *actual = gvkWithDefaults(*actual, *gvk)
           }

              // 如果'into'是*runtime.Unknown類型,需要返回*runtime.Unknown類型的對象。
              // 需要注意的是,此處復(fù)用了'into'指向的對象,因?yàn)榉祷氐念愋团c'into'指向的類型完全匹配。
           if unk, ok := into.(*runtime.Unknown); ok && unk != nil {
                  // 輸出原始數(shù)據(jù)
            unk.Raw = originalData
                  // 輸出數(shù)據(jù)格式為JSON,那么問題來了,不是'data'才能保證是json么?如s.options.Yaml == true,originalData是yaml格式才對。
                  // 所以筆者有必要提一個(gè)issue,看看官方怎么解決,如此明顯的一個(gè)問題為什么沒有暴露出來?
                  // 筆者猜測:runtime.Unknown只在內(nèi)部使用,而內(nèi)部只用json格式,所以自然不會暴露出來。
            unk.ContentType = runtime.ContentTypeJSON
                  // 輸出GVK。
            unk.GetObjectKind().SetGroupVersionKind(*actual)
            return unk, actual, nil
           }

              // 'into'不為空,通過into類型提取GVK,這樣在原始數(shù)據(jù)中的GVK和默認(rèn)GVK都沒有的字段用into的GVK補(bǔ)全。
           if into != nil {
                  // 判斷'into'是否為runtime.Unstructured類型
            _, isUnstructured := into.(runtime.Unstructured)
                  // 獲取'into'的GVK,需要注意的是返回的是一組GVK,types[0]是推薦的GVK。
            types, _, err := s.typer.ObjectKinds(into)
            switch {
                  // 'into'的類型如果沒有被注冊或者為runtime.Unstructured類型,則直接反序列成'into'指向的對象。
                  // 沒有被注冊的類型自然無法構(gòu)造對象,而非結(jié)構(gòu)體等同于map[string]interface{},不可能是API對象(因?yàn)锳PI對象必須是結(jié)構(gòu)體)。
                  // 所以這兩種情況直接反序列化到'into'對象就可以了,此時(shí)與json.Unmarshal()沒什么區(qū)別。
            case runtime.IsNotRegisteredError(err), isUnstructured:
             if err := caseSensitiveJSONIterator.Unmarshal(data, into); err != nil {
              return nil, actual, err
             }
             return into, actual, nil
                  // 獲取'into'類型出錯(cuò),多半是因?yàn)椴皇侵羔?/span>
            case err != nil:
             return nil, actual, err
                  // 用'into'的GVK補(bǔ)全未設(shè)置的GVK,所以GVK的優(yōu)先級:originalData > default gvk > into
            default:
             *actual = gvkWithDefaults(*actual, types[0])
            }
           }

              // 如果沒有Kind
           if len(actual.Kind) == 0 {
            return nil, actual, runtime.NewMissingKindErr(string(originalData))
           }
              // 如果沒有Version
           if len(actual.Version) == 0 {
            return nil, actual, runtime.NewMissingVersionErr(string(originalData))
           }
              // 那么問題來了,為什么不判斷Group?Group為""表示"core",比如我們寫ymal的時(shí)候Kind為Pod,apiVersion是v1,并沒有設(shè)置Group。

              // 從函數(shù)名字可以看出復(fù)用'into'或者重新構(gòu)造對象,復(fù)用的原則是:如果'into'注冊的一組GVK有任何一個(gè)與*actual相同,則復(fù)用'into'。
              // runtime.UseOrCreateObject()源碼讀者感興趣可以自己看下
           obj, err := runtime.UseOrCreateObject(s.typer, s.creater, *actual, into)
           if err != nil {
            return nil, actual, err
           }

              // 反序列化對象,caseSensitiveJSONIterator暫且不用關(guān)心,此處可以理解為json.Unmarshal()。
              // 當(dāng)然,讀者非要知道個(gè)究竟,可以看看代碼,筆者此處不注釋。
           if err := caseSensitiveJSONIterator.Unmarshal(data, obj); err != nil {
            return nil, actual, err
           }

              // 如果是非strict模式,可以直接返回了。其實(shí)到此為止就可以了,后面是針對strict模式的代碼,是否了解并不重要。
           if !s.options.Strict {
            return obj, actual, nil
           }

              // 筆者第一眼看到下面的以為看錯(cuò)了,但是擦了擦懵逼的雙眼,發(fā)現(xiàn)就是YAMLToJSON。如果原始數(shù)據(jù)是json不會有問題么?
              // 筆者查看了一下yaml.YAMLToJSONStrict()函數(shù)注釋:由于JSON是YAML的子集,因此通過此方法傳遞JSON應(yīng)該是沒有任何操作的。
              // 除非存在重復(fù)的字段,會解析出錯(cuò)。所以此處就是用來檢測是否有重復(fù)字段的,當(dāng)然,如果是yaml格式順便轉(zhuǎn)成了json。
              // 感興趣的讀者可以閱讀源碼,筆者只要知道它的功能就行了,就不“深究”了。
           altered, err := yaml.YAMLToJSONStrict(originalData)
           if err != nil {
            return nil, actual, runtime.NewStrictDecodingError(err.Error(), string(originalData))
           }
              // 接下來會因?yàn)槲粗淖侄螆?bào)錯(cuò),比如對象未定義的字段,未打標(biāo)簽的字段等。
              // 此處使用DeepCopyObject()等同于新構(gòu)造了一個(gè)對象,而這個(gè)對象其實(shí)又沒什么用,僅作為一個(gè)臨時(shí)的變量使用。
           strictObj := obj.DeepCopyObject()
           if err := strictCaseSensitiveJSONIterator.Unmarshal(altered, strictObj); err != nil {
            return nil, actual, runtime.NewStrictDecodingError(err.Error(), string(originalData))
           }
           // 返回反序列化的對象、GVK,所謂的strict模式無非是再做了一次轉(zhuǎn)換和反序列化來校驗(yàn)數(shù)據(jù)的正確性,結(jié)果直接丟棄。
              // 所以說strict沒有必要不用開啟,除非你真正理解他的作用并且能夠承受帶來的后果。
           return obj, actual, nil
          }

          // gvkWithDefaults()利用defaultGVK補(bǔ)全actual中未設(shè)置的字段。
          // 需要注意的是,參數(shù)'defaultGVK'只是一次調(diào)用相對于actual的默認(rèn)GVK,不是Serializer.Decode()的默認(rèn)GVK。
          func gvkWithDefaults(actual, defaultGVK schema.GroupVersionKind) schema.GroupVersionKind {
              // actual如果沒有設(shè)置Kind則用默認(rèn)的Kind補(bǔ)全
           if len(actual.Kind) == 0 {
            actual.Kind = defaultGVK.Kind
           }
              // 如果Group和Version都沒有設(shè)置,則用默認(rèn)的Group和Version補(bǔ)全。
              // 為什么必須是都沒有設(shè)置?缺少Version或者Group有什么問題么?下面的代碼給出了答案。
           if len(actual.Version) == 0 && len(actual.Group) == 0 {
            actual.Group = defaultGVK.Group
            actual.Version = defaultGVK.Version
           }
              // 如果Version未設(shè)置,則用默認(rèn)的Version補(bǔ)全,但是前提是Group與默認(rèn)Group相同。
              // 因?yàn)镚roup不同的API即便Kind/Version相同可能是兩個(gè)完全不同的類型,比如自定義資源(CRD)。
           if len(actual.Version) == 0 && actual.Group == defaultGVK.Group {
            actual.Version = defaultGVK.Version
           }
              // 如果Group未設(shè)置而Version與默認(rèn)的Version相同,為什么不用默認(rèn)的Group補(bǔ)全?
              // 前面已經(jīng)解釋過了,應(yīng)該不用再重復(fù)了。
           return actual
          }

          Encode

          相比于解碼,編碼就簡單很多,直接按照選項(xiàng)(yaml、pretty)編碼就行了,源碼鏈接:https://github.com/kubernetes/apimachinery/blob/release-1.21/pkg/runtime/serializer/json/json.go#L297

          // Encode()實(shí)現(xiàn)了Encoder.Encode()接口。
          func (s *Serializer) Encode(obj runtime.Object, w io.Writer) error {
              // CacheableObject允許對象緩存其不同的序列化數(shù)據(jù),以避免多次執(zhí)行相同的序列化,這是一種出于效率考慮設(shè)計(jì)的類型。
              // 因?yàn)橥粋€(gè)對象可能會多次序列化json、yaml和protobuf,此時(shí)就需要根據(jù)編碼器的標(biāo)識符找到對應(yīng)的序列化數(shù)據(jù)。
           if co, ok := obj.(runtime.CacheableObject); ok {
                  // CacheableObject筆者不再注釋了,感興趣的讀者可以自行閱讀源碼。
                  // 其實(shí)根據(jù)傳入的參數(shù)也能猜出來具體實(shí)現(xiàn):利用標(biāo)識符查一次map,如果有就輸出,沒有就調(diào)用一次s.doEncode()。
            return co.CacheEncode(s.Identifier(), s.doEncode, w)
           }
              // 非CacheableObject對象,就執(zhí)行一次json的序列化。
           return s.doEncode(obj, w)
          }

          // doEncode()類似于于json.Marshal(),只是寫入是io.Writer而不是[]byte。
          func (s *Serializer) doEncode(obj runtime.Object, w io.Writer) error {
              // 序列化成yaml?
           if s.options.Yaml {
                  // 序列化對象為json
            json, err := caseSensitiveJSONIterator.Marshal(obj)
            if err != nil {
             return err
            }
                  // json->yaml
            data, err := yaml.JSONToYAML(json)
            if err != nil {
             return err
            }
                  // 寫入io.Writer。
            _, err = w.Write(data)
            return err
           }

              // 輸出易于理解的格式?那么問題來了,為什么輸出yaml不需要這個(gè)判斷?很簡單,yaml就是易于理解的。
           if s.options.Pretty {
                  // 序列化對象為json
            data, err := caseSensitiveJSONIterator.MarshalIndent(obj, """  ")
            if err != nil {
             return err
            }
                  // 寫入io.Writer。
            _, err = w.Write(data)
            return err
           }

              // 非pretty模式,用我們最常用的json序列化方法,無非我們最常用方法是json.Marshal()。
              // 如果需要寫入io.Writer,下面的代碼是標(biāo)準(zhǔn)寫法。
           encoder := json.NewEncoder(w)
           return encoder.Encode(obj)
          }

          identifier

          前面筆者提到了,編碼器標(biāo)識符可以看做是標(biāo)簽的字符串形式,大家應(yīng)該很熟悉Kubernetes中的標(biāo)簽選擇器,符合標(biāo)簽選擇器匹配規(guī)則的所有API對象都可以看做"同質(zhì)"。同樣的道理,編碼器也有自己的標(biāo)簽,標(biāo)簽相同的所有編碼器是同質(zhì)的,即編碼同一個(gè)API對象的結(jié)果都是一樣的。編碼器標(biāo)識符的定義沒有那么復(fù)雜,就是簡單的字符串,匹配也非常簡單,標(biāo)識符相等即為匹配,所以標(biāo)識符可以理解為標(biāo)簽的字符串形式。源碼鏈接:https://github.com/kubernetes/apimachinery/blob/release-1.21/pkg/runtime/serializer/json/json.go#L66

          // identifier()根據(jù)給定的選項(xiàng)計(jì)算編碼器的標(biāo)識符。
          func identifier(options SerializerOptions) runtime.Identifier {
              // 編碼器的唯一標(biāo)識符是一個(gè)map[string]string,不同屬性的組合形成更了唯一性。
           result := map[string]string{
                  // 名字是json,表明是json編碼器。
            "name":   "json",
                  // 輸出格式為yaml或json
            "yaml":   strconv.FormatBool(options.Yaml),
                  // 是否為pretty模式
            "pretty": strconv.FormatBool(options.Pretty),
           }
              // 序列化成json,生成最終的標(biāo)識符,json序列化是標(biāo)簽的一種字符串形式。
           identifier, err := json.Marshal(result)
           if err != nil {
            klog.Fatalf("Failed marshaling identifier for json Serializer: %v", err)
           }
              // 也就是說,只要yaml和pretty選項(xiàng)相同的任意兩個(gè)json.Serializer,任何時(shí)候編碼同一個(gè)API對象輸出一定是相同的。
              // 所以當(dāng)API對象被多個(gè)編碼器多次編碼時(shí),以編碼器標(biāo)識符為鍵利用緩沖避免重復(fù)編碼。
           return runtime.Identifier(identifier)
          }

          yaml

          其實(shí)json.Serializer支持json和ymal,那么還有必要定義yaml.Serializer么?畢竟構(gòu)造兩個(gè)json.Serializer對象,一個(gè)開啟yaml選型,一個(gè)關(guān)閉yaml選項(xiàng)就可以了。來看看yaml.Serializer的定義,源碼鏈接:https://github.com/kubernetes/apimachinery/blob/release-1.21/pkg/runtime/serializer/yaml/yaml.go#L26

          // yamlSerializer實(shí)現(xiàn)了runtime.Serializer()。
          // 沒有定義Serializer類型,而是一個(gè)包內(nèi)的私有類型yamlSerializer,如果需要使用這個(gè)類型必須通過包內(nèi)的公有接口創(chuàng)建。
          type yamlSerializer struct {
              // 直接繼承了runtime.Serializer,不用想肯定是json.Serializer。
           runtime.Serializer
          }

          // NewDecodingSerializer()向支持json的Serializer添加yaml解碼支持。
          // 也就是說yamlSerializer編碼json,解碼yaml,當(dāng)然從接口名字看,調(diào)用這個(gè)接口估計(jì)只需要用解碼能力吧。
          // 好在筆者檢索了一下源碼,yamlSerializer以及NewDecodingSerializer()沒有引用的地方,應(yīng)該是歷史遺留的代碼。
          func NewDecodingSerializer(jsonSerializer runtime.Serializer) runtime.Serializer {
           return &yamlSerializer{jsonSerializer}
          }

          // Decode()實(shí)現(xiàn)了Decoder.Decode()接口。
          func (c yamlSerializer) Decode(data []byte, gvk *schema.GroupVersionKind, into runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) {
              // yaml->json
           out, err := yaml.ToJSON(data)
           if err != nil {
            return nilnil, err
           }
              // 反序列化json
           data = out
           return c.Serializer.Decode(data, gvk, into)
          }

          總結(jié)

          1. json.Serializer可以實(shí)現(xiàn)json和yaml兩種數(shù)據(jù)格式的序列化/反序列化,而yaml.Serializer基本不用了;
          2. MetaFactory的功能就是提取apiVersion和kind字段,然后返回GVK;
          3. json.Serializer也可以像json/yaml.Unmarshal()一樣使用,只要傳入的'into'的類型沒有在Schema中注冊就可以了;
          4. json.Serializer在反序列化之前需要計(jì)算API對象的GVK,計(jì)算原則是優(yōu)先使用json中的GVK,如果GVK有殘缺,則采用默認(rèn)GVK補(bǔ)全,最后用'into'指向的對象類型補(bǔ)全;
          5. 其實(shí)runtime.Serializer只是比json/yaml.Unmarshal()多了類型提取并構(gòu)造對象的過程,但是依然存在無法通用的問題,即解碼json和yaml需要不同的對象,這就要RecognizingDecoder來解決了;

          原文鏈接:https://github.com/jindezgm/k8s-src-analysis/blob/master/apimachinery/runtime/Serializer.md

          瀏覽 57
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  自拍偷拍视频区 | 屄无码| 色五月乱伦小说 | 激情开心成人 | jiZZJIZZ成熟丰满少妇 |