http server 或者 rpc server 要解决的一个问题是:如何解析用户的请求数据,并把他反序列化为语言中的一个具体的类型。反序列化的程序需要知道具体的类型(这在收到请求的时候就已经知道一些信息了,比如 用户访问的是 EchoService,那么请求肯定是 EchoRequest,不管是 EchoRequestV1,还是 EchoRequestV2), 同时反序列化程序即 decode 程序,还需要知道 他对应的语言里面的具体结构的信息,以便新建这个结构,填充数据,提交给上层处理。以一个 EchoService 为例,decode 程序需要从用户请求(如 post http://echo ) 文本或者二进制数据中创建出 EchoRequestV1,提供给上层处理,同时这个 decode 函数需要足够通用,他返回的是可能是一个 Message Interface, 里面是 EchoRequestV1,decode 相关的细节要么通过代码生成的技术提供给 decoder,要么在 二进制或者文本请求数据(或者 header等元数据)中携带这部分信息。
我们先来看看 golang protobuf 是如何解决这个问题的。
简单来说就是根据 生成的 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 解决这个问题的方法很类似。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-machinery 对于编写 crd controller 或者 aggregation apiserver很重要,由于api-machinery 以及 controller 逻辑的复杂性,有很多辅助工具可以帮助生成很多相关的代码,比如 kubebuilder, wrangler, 这里我们只关注 api-machinery 相关的代码,可以找到需要上面流程中提到的注册,转换代码. 比如这里
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。