研究意义


虽然 Registry 的松耦合架构解决了日益增长的请求数量和镜像数量的问题,但现有工作表明[8] ,在从此类大型公共 Registry 中拉取镜像到客户端的延迟占容器总启动时间的 76% 。因此,如何减少从远端拉取镜像的延迟开销成为目前研究加速容器启动的重点。


一、用户拉取镜像的请求流程

以 IBM Cloud Container Registry 的架构为例

  1.  用户输入 docker pull 指令,Docker Client 解析完请求后发送 HTTP 给 Docker Server,Docker Daemon 根据请求请求方法类型选择对应 Handler,该 Handler 在结合请求路由创建名为“pull”的 job,进而向注册表服务器发出方法名为 GET 的 http 请求:  https://<registry URL>/v2/<username>/<repository name>/manifests/<image tag>。
  2. 若注册表服务器中存在 manifest,则将 manifest 发给 Docker Daemon,否则返回 404错误代码。
  3. Docker Daemon 解析 manifest,并查询本地存储,得出缺失镜像层,并发出方法名为HEAD 的 http 请求:https://<registry URL>/v2/<username>/<repository name>/blobs/<digest>。
  4. 注册表服务器根据请求信息检索对象存储服务器中是否存在对应镜像层。
  5. 若该镜像层存在,则对象存储服务器返回 200 给注册表服务器。
  6. 进而注册服务器给用户返回 200。
  7. 用 户 通 过 Docker Daemon 发 出 方 法 名 为 GET 的 http 请 求 : https://<registry URL>/v2/<username>/<repository name>/blobs/<digest>。
  8. 注册表服务器给用户返回一个重定向 url。
  9. 通过新 url 找到存放镜像层的实际存储服务器.
  10. 镜像层被取回客户端进行校验,所有镜像层被拉取完成时合并成镜像。

其中 username 是用户名,repository name 是镜像仓库名,v2 是注册表应用程序的版本,digest 是镜像层的 256SHA 值。

二、内存去冗余的镜像缓存策略 L-Cache

1.拉取延迟计算方法分析

根据实际的docker镜像请求流程,确定如下拉取延迟公式

 

copy docker 缓存 docker pull 缓存_运维

即一个镜像层的拉取延迟 = 非拉取镜像的网络请求传输和跳跃产生的延迟开销 + 镜像通过网络传输产生的延迟开销 + 从对象存储服务器中将镜像层从硬盘传入内存产生的延迟开销

具体计算方式如下:

copy docker 缓存 docker pull 缓存_copy docker 缓存_02

copy docker 缓存 docker pull 缓存_copy docker 缓存_03

是除拉取镜像请求之外其他请求在三个组件中传输和跳跃产生的延迟开销。公式包含两个部分:一般请求在网络中传输的延迟开销和镜像元文件在网络中传输的延迟开销,

copy docker 缓存 docker pull 缓存_运维_04

 是广域网中下载速度的倒数,

copy docker 缓存 docker pull 缓存_运维_05

是镜像元文件的数据大小,

copy docker 缓存 docker pull 缓存_容器_06

是普通请求的数据大小, 

copy docker 缓存 docker pull 缓存_运维_07

表示在完整的请求流程中除了拉取镜像层请求的其他所有请求。

copy docker 缓存 docker pull 缓存_容器_08

copy docker 缓存 docker pull 缓存_运维_09

是镜像层在对象存储服务器中通过网络端口传输到用户本机的延迟开销,等于单个镜像层数据大小除以广域网中下载速度。

copy docker 缓存 docker pull 缓存_运维_04

是广域网中下载速度的倒数,

copy docker 缓存 docker pull 缓存_docker_11

是镜像层的数据大小。

copy docker 缓存 docker pull 缓存_服务器_12


copy docker 缓存 docker pull 缓存_运维_13

是对象存储服务器接收到用户拉取镜像层的请求后,将数据从硬盘读入内存所需的时间开销,等于镜像层数据大小除以服务器硬盘的读速度。

copy docker 缓存 docker pull 缓存_容器_14

 是服务器硬盘的读速度的倒数, 

copy docker 缓存 docker pull 缓存_docker_11

是镜像层的数据大小。

 

由上得出:

copy docker 缓存 docker pull 缓存_容器_16

由于磁盘提取速度比网络传输速度快得多,所以优化网络传输阶段

copy docker 缓存 docker pull 缓存_docker_17

能够获得较好的延迟减少

镜像层和镜像元数据的大小无法改变,只能通过减少发送请求数量,通过分析拉取镜像的完成请求流程,我们提出将部分热点镜像缓存至注册表服务器的内存中,例如包含系统文件、依赖库、常用中间件的镜像层即为热点镜像层,当请求命中缓存时即可拉取镜像,节省了后续请求的延迟开销,尤其对于镜像层数据较小的请求流中,降低延迟的效果更加明显。同时,由于磁盘 I/O 的限制,镜像层数据从磁盘拷贝到内存需要的时间也开销不容忽视,于是我们对注册表服务器和对象存储服务器的内存镜像层数据去冗余,提高存储资源利用率,使请求尽可能命中缓存,从而隐藏了磁盘 I/O 的延迟开销。

2.观察分析镜像特点

(1)镜像层与镜像元数据的大小

copy docker 缓存 docker pull 缓存_运维_18

大部分镜像层体积都很小,其中 1K~10K 的镜像数量最多,并且小于 1M 的约占 65%,小于 10MB 的约占 80%。虽然超过 1G 的巨型镜像层不足 5%,但拉取该类镜像的时间开销非常巨大,缓存方案不予考虑这类镜像。——尽可能缓存较小的镜像层,提高缓存的镜像数量从而减少磁盘 I/O的延迟开销,最终节省用户拉取镜像的延迟。

(2)镜像流行度

copy docker 缓存 docker pull 缓存_docker_19

所有注册表中最流行的 layer 都占据较高的比重,这证明了在不同工作负载中总是有热点镜像,结合上一小节中得出 65%的镜像层小于 1M——将小的热点镜像层缓存在注册表服务器内存的方案具有良好的可行性。

(3)注册表服务器的资源情况

 发现第 3 类服务器架构是最接近实际生产中所使用的注册表服务器。如图 3.6 所示,此类
服务器在长期处理业务时内存使用率在 31.51%~39.11%的范围内,平均内存使用率为34.28%,同时其他服务器的平均内存使用率不会超过 50%。——现在 Docker Registry 架构中,负载均衡器智能地调节不同注册表服务器上的负载数量,同时注册表服务器在运行中有较为充裕的存储资源。

3.L-cache缓存策略设计

copy docker 缓存 docker pull 缓存_copy docker 缓存_20


当用户发送拉取镜像层的请求到达注册表时,L-Cache 策略会首先依据该镜像层的 digest 核查


注册表服务器内存,如果镜像层存在则请求命中,注册表服务器将镜像层返回给用户,从而避免了原生处理流程中的后续请求转发,针对体积较小的镜像层,请求转发延迟的代价越大,缓存命中的节省效果越明显。但在注册表服务器的缓存未命中时,L-Cache 策略向后端重新发出拉取请求,将所需镜像层缓存在注册表服务器内存中,并使用 LRU 进行替换操作。



内存去冗余:

注册表服务器从后端存储服务器中拉取的镜像层数据,此镜像也必定缓存在对象存储服务器内存。

L-Cache 策略识别注册表服务器和后端存储服务器 内存中的镜像层 digest ,通过去冗余操作整理后端存储服务器内存,并将注册表服务器内存替换出的热点镜像存储在后端存储服务器内存中,弥补去冗余操作后的存储空间,避免用户拉取这些热度暂时减退的镜像层时,从磁盘拷贝该镜像层至内存的延迟开销,提高资源利用率。


总结:L-cache即在registry端和后端存储端都建立预取缓存,优先去registry内存中找,找不到去后端存储内存中找,只要在这两个地方找到都算缓存命中。缓存未命中的话再去后端存储的硬盘中提取。内存去冗余意思就让registry和后端存储的内存中不要存相同的层,如果registry内存中有了就将后端存储内存中的相同层删掉。


L-cache这一研究点主要是通过在Registry端和后端存储端建立预取缓存存储热度较高的层,当命中时直接提供层给客户端,减少了一次网络请求跳转的时间开销(仅当registry端预取缓存命中时)和从后端存储磁盘中提取层的时间开销。


实验结果


copy docker 缓存 docker pull 缓存_运维_21


 L-cache(带内存去冗余功能)和R-cache(不带内存去冗余功能)的缓存命中率随缓存大小变化对比图


copy docker 缓存 docker pull 缓存_运维_22


 L-cache中registry端预取缓存和后端存储端预取缓存随缓存大小变化对比图


copy docker 缓存 docker pull 缓存_服务器_23

 延迟节省率计算方法为

copy docker 缓存 docker pull 缓存_copy docker 缓存_24

其中

copy docker 缓存 docker pull 缓存_copy docker 缓存_25

为使用 L-cache 或 R-Cache 策略时从注册表服务器中拉取镜像的延迟,

copy docker 缓存 docker pull 缓存_copy docker 缓存_26

 为使用传统的拉取流程所获得镜像的延迟开销

L-Cache 方案比 Docker 原生镜像拉取延迟减少了 7.7%~19.9%,这为加快容器启动提供了一个新的研究方向。但该方案的不足在于注册表服务器内存的缓存命中率较低,下一章提出新的预取策略以进一步优化缓存命中率,降低拉取镜像的延迟开销。

三、基于关联度的镜像层预取策略 LCPA


copy docker 缓存 docker pull 缓存_容器_27

copy docker 缓存 docker pull 缓存_docker_28


图 4.3 中请求的多次跳跃很大程度决定了延迟的下限,特别当后端存储服务器处于高负载时,性能下降会极大地影响拉取镜像的效率。LCPA 策略将一部分镜像存入注册表内存中,合理利用注册表的空闲资源以减轻后端存储服务器的负载。同时,拉取请求处理模块判断请求命中注册表缓存并返回镜像层,相比传统的请求流程减少了一半的请求跳跃次数,极大减少了高延迟。


预取触发点问题:

过去的研究中一般在收到GET manifest请求后就直接开始预取。这会存在以下两个问题:
        1.用户只会pull未拥有的layer,而registry端无法知道用户已经拥有哪些layer,全部预取的话就会导致预取的和实际需要的偏差较大,造成网络资源浪费和命中率的降低。

        2.观察发现部分用户会因为网络拥塞或中断操作,只发送GET manifest请求而没有后续的GET layer请求,如果收到GET manifest就开始预取的话会导致全部预取都浪费掉

解决方法
        本文选择第二次miss的pull layer请求作为触发点,首先使用pull layer作为触发点解决了问题2。选择第二个是因为注册表服务器第一次接收到某个镜像仓库内的未命中请求时,没有前驱请求可以建立关联,无法准确判断是否还有后续请求以及与其他镜像层的相关性。

      

LCPA策略关联性分为三种情况:

为每一个镜像仓库设置独立预取窗口,将拉取请求处理模块发送来的未命中请求保存在相应的预取窗口里,以便记录和查询请求之间对应的镜像层是否产生关联。

1.强关联:客户端pull一个layer一定会pull另一个layer,即所有包含其中一个layer的镜像一定也包含另一个。如镜像存储库1中的2,4,6。

2.弱关联:pull 一个layer可能会pull另一个layer,即一个镜像I_A包含layer_A和layer_B,另一个镜像I_B包含layer_A和layer_C,则layer A和B就是弱关联的。如镜像存储库2中的1和5

3.无关联:两个layer没有任何关系,没有同时包含它们两个的镜像存在,就像镜像存储库2的10和15。

copy docker 缓存 docker pull 缓存_服务器_29

 预取策略:

新请求与历史请求的关系:

1.如果是强关联,预取集合就是新层的所有强关联层

2.如果是弱关联,则计算关联程度

copy docker 缓存 docker pull 缓存_运维_30

3.如果是无关联,清除该预取窗口中所有历史请求,仅保留最新请求,为后继请求提供关联性参考。

获得了预取集合后先检查内存中是否已经缓存了其中的层,然后再进行缓存。

实验评测

 请求命中率:

copy docker 缓存 docker pull 缓存_运维_31

延迟节省率:

copy docker 缓存 docker pull 缓存_docker_32


通过使用从 IBM 云注册表中收集的真实工作负载数据集验证 LCPA 策略的正确性和有效性,即与其他优秀的缓存算法和预取算法进行性能对比。实验结果表明,LCPA 策略可以提高 12%~29% 的命中率,减少 21.1%~49.4% 的延迟开销。