Decode 问题
http server 或者 rpc server 要解决的一个问题是: 如何解析用户的请求数据, 并把他反序列化为语言中的一个具体的类型. 反序列化的程序需要知道具体的类型 (这在收到请求的时候就已经知道一些信息了, 比如 用户访问的是 EchoService, 那么请求肯定是 EchoRequest, 不管是 EchoRequestV1, 还是 EchoRequestV2), 同时反序列化程序即 decode 程序, 还需要知道 他对应的语言里面的具体结构的信息, 以便新建这个结构, 填充数据, 提交给上层处理. 以一个 EchoService 为例, decode 程序需要从用户请求(如 post http://echo ) 文本或者二进制数据中创建出 EchoRequestV1, 提供给上层处理, 同时这个 decode 函数需要足够通用, 他返回的是可能是一个 Message Interface, 里面是 EchoRequestV1,decode 相关的细节要么通过代码生成的技术提供给 decoder, 要么在 二进制或者文本请求数据(或者 header 等元数据) 中携带这部分信息.
我们先来看看 golang protobuf 是如何解决这个问题的.
Protobuf Unmarshal
简单来说就是根据 生成的 golang 结构体的 Field tag 来做 Unmarshal
- // 生成的 golang 结构体
- type EchoRequest struct {
- A string `protobuf:"bytes,1,opt,name=A,proto3" json:"A,omitempty"`
- }
- // 收到请求, 在 Unmarshal 过程中会调用这个函数
- func (m *EchoRequest) XXX_Unmarshal(b []byte) error {
- return xxx_messageInfo_EchoRequest.Unmarshal(m, b)
- }
- var xxx_messageInfo_EchoRequest proto.InternalMessageInfo
- // InternalMessageInfo 是 Unmarshal 相关信息的存储位置
- // b 是 protocol buffer raw 数据, 而 a 是要 unmarshal 到的结构
- // 基础库不关心具体 unmarshal 类型, 始终 unmarshal 到一个 interface Message
- // 实际上面到结构调用到时候 会是 EchoRequest 类型
- func (a *InternalMessageInfo) Unmarshal(msg Message, b []byte) error {
- // ... 略
- err := u.unmarshal(toPointer(&msg), b)
- return err
- }
- func (u *unmarshalInfo) unmarshal(m pointer, b []byte) error{
- if atomic.LoadInt32(&u.initialized) == 0 {
- // 保存 unmarshal 这个类型的函数, 信息到一个结构里面, 加速重复的 unmarshal
- u.computeUnmarshalInfo()
- }
- // .... 略
- if fn := f.unmarshal; fn != nil {
- var err error
- // unmarshal 这个 field 这里的关键是 unmarshal 到原始 bytes 设置到对应字段的
- // offset 上面去, 里面比较关键的是用了 golang reflect 的 StructField
- // StructField 的 Offset 是固定的, 根据 一个结构的指针的 pointer 以及 Field 的
- // offset 就可以直接用指针设置 结构的某个字段内容了
- b, err = fn(b, m.offset(f.field), wire)
- // ....
- }
- }
- Kubernetes Scheme
kubernetes 解决这个问题的方法很类似. GitHub.com/kubernetes/apimachinery 部分为了解决这个问题而存在的, 当然 kubernetes 的问题更为复杂一些, 由于支持 资源的版本, 他还需要解决版本之间互相转化的问题. 具体的说 apimachinery 解决的是 kubernetes 的 API Object 的 Scheme, typing, encoding, decoding, and conversion 问题.
GVK(GroupVersionKind) 和 GVR(GroupVersionResource) 是 apimachinery 里面的两个重要概念. 区别是 GVK 是一个 Object 概念, 而 GVR 代表一个 Http Path
- Http Path:
- /apis/batch/v2alpha1/(namespaces/<name>)jobs
- | | |
- Apigroup Version Resource
- /apis/extensions/v1alpha1/jobs
- /apis/batch/v2alpha1/jobs
- /apis/batch/v1/jobs
- ------------------------------------------
对象:
- apiVersion: batch/v2alpha1
- kind: Job
- metadata:
- name: aaa
- namespace: tiems
- spec:
- ...
apiVersion: v1/v2alpha1/v1alpha1 彼此之间可以转换 => "storage version" (比如 v1) => Etcd
反序列化使用 API.Scheme + gvk, 而 gvk 中的信息可以从 request 中获取
- gvk := schema.GroupVersionKind{Group: "batch", Version: "v2alpha1", Kind: "Job"}
- obj := API.Scheme.New(gvk)
- codec := API.Codecs.LegacyCodec(gvk.GroupVersion())
- codec.Decode(reqBody, gvk, obj)
- type Job struct {
- metav1.TypeMeta ---> type TypeMeta struct { Kind string; APIVersion string }
- metav1.ObjectMeta ---> type ObjectMeta struct { Name string...}
- Spec JobSpec
- Status JobStatus
- }
代码流程分析
API groups install, 包括了 InstallLegacyAPI,InstallAPIs, LegacyAPI 指 core group 在 /API/v1, 而 named groups 在 /apis/$GROUP_NAME/$VERSION
(a *APIInstaller) Install 里面安装了各种 http path 的处理流程
比如 "POST" /apis/extensions/v1alpha1/jobs => restfulCreateNamedResource
endpoints/handlers/create.go: createHandler => 反序列化语句为 decoder.Decode(body, &defaultGVK, original)
defaultGVK 是默认 GVK 信息来自 path (APIGroup), 但是还是以 body 里面的 gvk 为准
如 runtime/serializer/JSON/JSON.go: Decode 语句, 会先运行 s.meta.Interpret(data), 从中或取真正的 GVK, 信息有不足的时候才会用 defaultGVK 补充
runtime.UseOrCreateObject(s.typer, s.creater, *actual, into)
几个重要的函数 在 init 的时候就执行了 var Scheme = runtime.NewScheme(); var Codecs = serializer.NewCodecFactory(Scheme); var ParameterCodec = runtime.NewParameterCodec(Scheme) 这里是全局的 Scheme. Codecs. ParameterCodec 正因为各种 type 都注册到了(用 scheme.AddKnownTypes ) 这个 scheme, 所以 Codecs 才有所有类型的信息以便于做序列化 反序列化
本质 runtime.UseOrCreateObject 会调用 (s *Scheme) New(kind schema.GroupVersionKind) -> s.gvkToTypekind 从 gvkToType 去找类型, 如果没有注册, 会报错 NotRegisteredErr
创建完成对象之后 则调用 caseSensitiveJsonIterator.Unmarshal 反序列数据到这个对象, 这个就比较简单了, 使用 golang 的 unmarshal 函数就可以, kubernetes 里面用了 https://github.com/json-iterator/go
NewCodecFactory 存储了各种 content-type 以及各种 type 对应的 反序列化工具
Scheme 同时还注册了各种版本类型互相转换的信息, default 变量信息, 通过 DefaultingFunc,AddConversionFunc 函数注册 localSchemeBuilder.Register(addDefaultingFuncs, addConversionFuncs)
image
应用
了解 API-machinery 对于编写 crd controller 或者 aggregation apiserver 很重要, 由于 API-machinery 以及 controller 逻辑的复杂性, 有很多辅助工具可以帮助生成很多相关的代码, 比如 https://github.com/kubernetes-sigs/kubebuilder , https://github.com/rancher/wrangler , 这里我们只关注 API-machinery 相关的代码, 可以找到需要上面流程中提到的注册, 转换代码. 比如这里
image
参考 1
参考 2
来源: https://www.qcloud.com/developer/article/1519826