简介: 阿里云边缘容器服务上线 1 年后,正式开源了云原生边缘计算解决方案 OpenYurt,跟其他开源的容器化边缘计算方案不同的地方在于:OpenYurt 秉持 Extending your native Kubernetes to edge 的理念,对 Kubernetes 系统零修改,并提供一键式转换原生 Kubernetes 为 OpenYurt,让原生 K8s 集群具备边缘集群能力。

作者 | 何淋波(新胜)
来源 | 阿里巴巴云原生公众号

OpenYurt:延伸原生 K8s 的能力到边缘

阿里云边缘容器服务上线 1 年后,正式开源了云原生边缘计算解决方案 OpenYurt,跟其他开源的容器化边缘计算方案不同的地方在于:OpenYurt 秉持 Extending your native Kubernetes to edge 的理念,对 Kubernetes 系统零修改,并提供一键式转换原生 Kubernetes 为 OpenYurt,让原生 K8s 集群具备边缘集群能力。

同时随着 OpenYurt 的持续演进,也一定会继续保持如下发展理念:

  • 非侵入式增强 K8s
  • 保持和云原生社区主流技术同步演进

OpenYurt 如何解决边缘自治问题

想要实现将 Kubernetes 系统延展到边缘计算场景,那么边缘节点将通过公网和云端连接,网络连接有很大不可控因素,可能带来边缘业务运行的不稳定因素,这是云原生和边缘计算融合的主要难点之一。

解决这个问题,需要使边缘侧具有自治能力,即当云边网络断开或者连接不稳定时,确保边缘业务可以持续运行。在 OpenYurt 中,该能力由 yurt-controller-manager 和 YurtHub 组件提供。

1. YurtHub 架构

在之前的文章中,我们详细介绍了 YurtHub 组件的能力。其架构图如下:

图片链接

YurtHub 是一个带有数据缓存功能的“透明网关”,和云端网络断连状态下,如果节点或者组件重启,各个组件(kubelet/kube-proxy 等)将从 YurtHub 中获取到业务容器相关数据,有效解决边缘自治的问题。这也意味着我们需要实现一个轻量的带数据缓存能力的反向代理。

2. 第一想法

实现一个缓存数据的反向代理,第一想法就是从 response.Body 中读取数据,然后分别返回给请求 client 和本地的 Cache 模块。伪代码如下:

func HandleResponse(rw http.ResponseWriter, resp *http.Response) {
bodyBytes, _ := ioutil.ReadAll(resp.Body)
go func() {
// cache response on local disk
cacher.Write(bodyBytes)
} // client reads data from response
rw.Write(bodyBytes)
}

当深入思考后,在 Kubernetes 系统中,上述实现会引发下面的问题:

  • 问题 1:流式数据需要如何处理(如: K8s 中的 watch 请求),意味 ioutil.ReadAll() 一次调用无法返回所有数据。即如何可以返回流数据同时又缓存流数据。
  • 问题 2:同时在本地缓存数据前,有可能需要对传入的 byte slice 数据先进行清洗处理。这意味着需要修改 byte slice,或者先备份 byte slice 再处理。这样会造成内存的大量消耗,同时针对流式数据,到底申请多大的 slice 也不好处理。

3. 优雅实现探讨

针对上面的问题,我们将问题逐个抽象,可以发现更优雅的实现方法。

  • 问题 1:如何对流数据同时进行读写

针对流式数据的读写(一边返回一边缓存),如下图所示,其实需要的不过是把 response.Body(io.Reader) 转换成一个 io.Reader 和一个 io.Writer。或者说是一个 io.Reader 和 io.Writer 合成一个 io.Reader。这很容易就联想到 Linux 里面的 Tee 命令。

而在 Golang 中 Tee 命令是实现就是io.TeeReader,那问题 1 的伪代码如下:

func HandleResponse(rw http.ResponseWriter, resp *http.Response) {
// create TeeReader with response.Body and cacher
newRespBody := io.TeeReader(resp.Body, cacher) // client reads data from response
io.Copy(rw, newRespBody)
}

通过 TeeReader 的对 Response.Body 和 Cacher 的整合,当请求 client 端从 response.Body 中读取数据时,将同时向 Cache 中写入返回数据,优雅的解决了流式数据的处理。

  • 问题 2:如何在缓存前先清洗流数据

如下图所示,缓存前先清洗流数据,请求端和过滤端需要同时读取 response.Body(2 次读取问题)。也就是需要将 response.Body(io.Reader) 转换成两个 io.Reader。

也意味着问题 2 转化成:问题 1 中缓存端的 io.Writer 转换成 Data Filter 的 io.Reader。其实在 Linux 命令中也能找到类似命令,就是管道。因此问题 2 的伪代码如下:

func HandleResponse(rw http.ResponseWriter, resp *http.Response) {
pr, pw := io.Pipe()
// create TeeReader with response.Body and Pipe writer
newRespBody := io.TeeReader(resp.Body, pw)
go func() {
// filter reads data from response
io.Copy(dataFilter, pr)
} // client reads data from response
io.Copy(rw, newRespBody)
}

通过 io.TeeReader 和 io.PiPe,当请求 client 端从 response.Body 中读取数据时,Filter 将同时从 Response 读取到数据,优雅的解决了流式数据的 2 次读取问题。

YurtHub 实现

最后看一下 YurtHub 中相关实现,由于 Response.Body 为 io.ReadCloser,所以实现了 dualReadCloser。同时 YurtHub 可能也面临对 http.Request 的缓存,所以增加了 isRespBody 参数用于判定是否需要负责关闭 response.Body。

// https://github.com/openyurtio/openyurt/blob/master/pkg/yurthub/util/util.go#L156
// NewDualReadCloser create an dualReadCloser object
func NewDualReadCloser(rc io.ReadCloser, isRespBody bool) (io.ReadCloser, io.ReadCloser) {
pr, pw := io.Pipe()
dr := &dualReadCloser{
rc: rc,
pw: pw,
isRespBody: isRespBody,
} return dr, pr
} type dualReadCloser struct {
rc io.ReadCloser
pw *io.PipeWriter
// isRespBody shows rc(is.ReadCloser) is a response.Body
// or not(maybe a request.Body). if it is true(it's a response.Body),
// we should close the response body in Close func, else not,
// it(request body) will be closed by http request caller
isRespBody bool
} // Read read data into p and write into pipe
func (dr *dualReadCloser) Read(p []byte) (n int, err error) {
n, err = dr.rc.Read(p)
if n > 0 {
if n, err := dr.pw.Write(p[:n]); err != nil {
klog.Errorf("dualReader: failed to write %v", err)
return n, err
}
} return
} // Close close two readers
func (dr *dualReadCloser) Close() error {
errs := make([]error, 0)
if dr.isRespBody {
if err := dr.rc.Close(); err != nil {
errs = append(errs, err)
}
} if err := dr.pw.Close(); err != nil {
errs = append(errs, err)
} if len(errs) != 0 {
return fmt.Errorf("failed to close dualReader, %v", errs)
} return nil
}

在使用 dualReadCloser 时,可以在httputil.NewSingleHostReverseProxy的modifyResponse()方法中看到。代码如下:

// https://github.com/openyurtio/openyurt/blob/master/pkg/yurthub/proxy/remote/remote.go#L85
func (rp *RemoteProxy) modifyResponse(resp *http.Response) error {rambohe-ch, 10 months ago: • hello openyurt
// 省略部分前置检查
rc, prc := util.NewDualReadCloser(resp.Body, true)
go func(ctx context.Context, prc io.ReadCloser, stopCh <-chan struct{}) {
err := rp.cacheMgr.CacheResponse(ctx, prc, stopCh)
if err != nil && err != io.EOF && err != context.Canceled {
klog.Errorf("%s response cache ended with error, %v", util.ReqString(req), err)
}
}(ctx, prc, rp.stopCh) resp.Body = rc
}

总结

OpenYurt 于 2020 年 9 月进入 CNCF 沙箱后,持续保持了快速发展和迭代,在社区同学一起努力下,目前已经开源的能力有:

  • 边缘自治
  • 边缘单元化管理
  • 云边协同运维
  • 一键式无缝转换能力

同时在和社区同学的充分讨论下,OpenYurt 社区也发布了2021 roadmap,欢迎有兴趣的同学来一起贡献。

本文为阿里云原创内容,未经允许不得转载。

【OpenYurt 深度解析】边缘网关缓存能力的优雅实现的更多相关文章

  1. Deep Learning模型之:CNN卷积神经网络(一)深度解析CNN

    http://m.blog.csdn.net/blog/wu010555688/24487301 本文整理了网上几位大牛的博客,详细地讲解了CNN的基础结构与核心思想,欢迎交流. [1]Deep le ...

  2. Kafka深度解析

    本文转发自Jason’s Blog,原文链接 http://www.jasongj.com/2015/01/02/Kafka深度解析 背景介绍 Kafka简介 Kafka是一种分布式的,基于发布/订阅 ...

  3. mybatis 3.x源码深度解析与最佳实践(最完整原创)

    mybatis 3.x源码深度解析与最佳实践 1 环境准备 1.1 mybatis介绍以及框架源码的学习目标 1.2 本系列源码解析的方式 1.3 环境搭建 1.4 从Hello World开始 2 ...

  4. Kafka深度解析(如何在producer中指定partition)(转)

    原文链接:Kafka深度解析 背景介绍 Kafka简介 Kafka是一种分布式的,基于发布/订阅的消息系统.主要设计目标如下: 以时间复杂度为O(1)的方式提供消息持久化能力,即使对TB级以上数据也能 ...

  5. 《SEO深度解析——全面挖掘搜索引擎优化的核心秘密》

    <SEO深度解析——全面挖掘搜索引擎优化的核心秘密> 基本信息 作者: 痞子瑞 出版社:电子工业出版社 ISBN:9787121224041 上架时间:2014-2-28 出版日期:201 ...

  6. 深度解析CNN

    [1]Deep learning简介 [2]Deep Learning训练过程 [3]Deep Learning模型之:CNN卷积神经网络推导和实现 [4]Deep Learning模型之:CNN的反 ...

  7. Hibernate 3 深度解析--苏春波

    Hibernate 3 深度解析   Hibernate 作为 Java ORM 模式的优秀开源实现, 当下已经成为一种标准,为饱受 JDBC 折磨的 Java 开发者带来了“福音.快速的版本更新,想 ...

  8. Feign Ribbon Hystrix 三者关系 | 史上最全, 深度解析

    史上最全: Feign Ribbon Hystrix 三者关系 | 深度解析 疯狂创客圈 Java 分布式聊天室[ 亿级流量]实战系列之 -25[ 博客园 总入口 ] 前言 疯狂创客圈(笔者尼恩创建的 ...

  9. Uber的API生命周期管理平台边缘网关(Edge Gateway)的设计实践

    设计边缘网关(Edge Gateway),一个高可用和高可扩展的自助服务网关,用于配置.管理和监控 Uber 每个业务领域的 API. Uber 的 API 网关的演进 2014 年 10 月,优步开 ...

  10. 华为全栈AI技术干货深度解析,解锁企业AI开发“秘籍”

    摘要:针对企业AI开发应用中面临的痛点和难点,为大家带来从实践出发帮助企业构建成熟高效的AI开发流程解决方案. 在数字化转型浪潮席卷全球的今天,AI技术已经成为行业公认的升级重点,正在越来越多的领域为 ...

随机推荐

  1. MyBatisPlus常用功能总结!(附项目示例)

    这篇主要是总结一下MybatisPlus一些常用的场景,目前主要有以下几点: 完整的CURD操作示例 逻辑删除功能示例 自动填充功能示例 分页插件功能示例 有关一些其它重要的功能比如 条件生成器.主键 ...

  2. 投屏项目中Sink端CPU占用过高问题

    一.背景 今天来总结一下,自己在项目中遇到的一个CPU占用过高的问题,详细的结束从发现到定位在到解决问题的过程. 原因是性能测试那边提出了一个bug,就是在投屏过程中,平板端也就是Sink端功耗非常高 ...

  3. CMakeLists.txt 编写要点 && 一个关于install()的深坑

    PS:要转载请注明出处,本人版权所有. PS: 这个只是基于<我自己>的理解, 如果和你的原则及想法相冲突,请谅解,勿喷. 前置说明   本文作为本人csdn blog的主站的备份.(Bl ...

  4. C++ kmalloc、kzalloc、vmalloc的区别

    1. kmalloc 函数原型: void *kmalloc(size_t size, gfp_t flags): kmalloc() 申请的内存位于物理内存映射区域,而且在物理上也是连续的,它们与真 ...

  5. 三维模型OBJ格式轻量化的数据压缩与性能平衡分析

    三维模型OBJ格式轻量化的数据压缩与性能平衡分析 三维模型的OBJ格式轻量化数据压缩在保持性能的同时,可以减小文件大小.提高加载速度和节省存储空间.然而,在进行数据压缩时,需要权衡压缩比率和模型质量之 ...

  6. Java 8 内存管理原理解析及内存故障排查实践

    作者:vivo 互联网服务器团队-  Zeng Zhibin 介绍Java8虚拟机的内存区域划分.内存垃圾回收工作原理解析.虚拟机内存分配配置,介绍各垃圾收集器优缺点及场景应用.实践内存故障场景排查诊 ...

  7. C# 人脸比对服务,自带模型,离线部署

    软件说明 基于以下开源项目,做了再次封装 GitHub - ViewFaceCore/ViewFaceCore: C# 超简单的离线人脸识别库.( 基于 SeetaFace6 ) 可以一键开启服务: ...

  8. Redis高可用之战:主从架构

    ★ Redis24篇集合 1 主从模式介绍 在笔者的另外两篇文章 <Redis系列:RDB内存快照提供持久化能力>.<Redis稳定性之战:AOF日志支撑数据持久化>中,我们介 ...

  9. GID:旷视提出全方位的检测模型知识蒸馏 | CVPR 2021

    论文提出的GID框架能够自动选择可辨别目标用于知识蒸馏,而且综合了feature-based.relation-based和response-based知识,全方位蒸馏,适用于不同的检测框架中.从实验 ...

  10. KingbaseES V8R3 集群运维案例 -- cluster.log无日志输出问题诊断

    案例说明: KingbaseES V8R3集群正常运行期间,现场发现cluster.log日志无任何信息输出,针对这一问题做了复现及提出解决方案.后现场检查发现,cluster.log文件曾被删除: ...