https://zhuanlan.zhihu.com/p/128552232

作者 | 孙志恒(惠志) 阿里巴巴开发工程师

导读:众所周知,K8s 的持久化存储(Persistent Storage)保证了应用数据独立于应用生命周期而存在,但其内部实现却少有人提及。K8s 内部的存储流程到底是怎样的?PV、PVC、StorageClass、Kubelet、CSI 插件等之间的调用关系又如何,这些谜底将在本文中一一揭晓。

K8s 持久化存储基础

在进行 K8s 存储流程讲解之前,先回顾一下 K8s 中持久化存储的基础概念。

1. 名词解释

  • in-tree:代码逻辑在 K8s 官方仓库中;
  • out-of-tree:代码逻辑在 K8s 官方仓库之外,实现与 K8s 代码的解耦;
  • PV:PersistentVolume,集群级别的资源,由 集群管理员 or External Provisioner 创建。PV 的生命周期独立于使用 PV 的 Pod,PV 的 .Spec 中保存了存储设备的详细信息;
  • PVC:PersistentVolumeClaim,命名空间(namespace)级别的资源,由 用户 or StatefulSet 控制器(根据VolumeClaimTemplate) 创建。PVC 类似于 Pod,Pod 消耗 Node 资源,PVC 消耗 PV 资源。Pod 可以请求特定级别的资源(CPU 和内存),而 PVC 可以请求特定存储卷的大小及访问模式(Access Mode);
  • StorageClass:StorageClass 是集群级别的资源,由集群管理员创建。SC 为管理员提供了一种动态提供存储卷的“类”模板,SC 中的 .Spec 中详细定义了存储卷 PV 的不同服务质量级别、备份策略等等;
  • CSI:Container Storage Interface,目的是定义行业标准的“容器存储接口”,使存储供应商(SP)基于 CSI 标准开发的插件可以在不同容器编排(CO)系统中工作,CO 系统包括 Kubernetes、Mesos、Swarm 等。

2. 组件介绍

  • PV Controller:负责 PV/PVC 绑定及周期管理,根据需求进行数据卷的 Provision/Delete 操作;
  • AD Controller:负责数据卷的 Attach/Detach 操作,将设备挂接到目标节点;
  • Kubelet:Kubelet 是在每个 Node 节点上运行的主要 “节点代理”,功能是 Pod 生命周期管理、容器健康检查、容器监控等;
  • Volume Manager:Kubelet 中的组件,负责管理数据卷的 Mount/Umount 操作(也负责数据卷的 Attach/Detach 操作,需配置 kubelet 相关参数开启该特性)、卷设备的格式化等等;
  • Volume Plugins:存储插件,由存储供应商开发,目的在于扩展各种存储类型的卷管理能力,实现第三方存储的各种操作能力,即是上面蓝色操作的实现。Volume Plugins 有 in-tree 和 out-of-tree 两种;
  • External Provioner:External Provioner 是一种 sidecar 容器,作用是调用 Volume Plugins 中的 CreateVolume 和 DeleteVolume 函数来执行 Provision/Delete 操作。因为 K8s 的 PV 控制器无法直接调用 Volume Plugins 的相关函数,故由 External Provioner 通过 gRPC 来调用;
  • External Attacher:External Attacher 是一种 sidecar 容器,作用是调用 Volume Plugins 中的 ControllerPublishVolume 和 ControllerUnpublishVolume 函数来执行 Attach/Detach 操作。因为 K8s 的 AD 控制器无法直接调用 Volume Plugins 的相关函数,故由 External Attacher 通过 gRPC 来调用。

3. 持久卷使用

Kubernetes 为了使应用程序及其开发人员能够正常请求存储资源,避免处理存储设施细节,引入了 PV 和 PVC。创建 PV 有两种方式:

  • 一种是集群管理员通过手动方式静态创建应用所需要的 PV;
  • 另一种是用户手动创建 PVC 并由 Provisioner 组件动态创建对应的 PV。

下面我们以 NFS 共享存储为例来看二者区别。

静态创建存储卷

静态创建存储卷流程如下图所示:

第一步:集群管理员创建 NFS PV,NFS 属于 K8s 原生支持的 in-tree 存储类型。yaml 文件如下:

apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
nfs:
server: 192.168.4.1
path: /nfs_storage

第二步:用户创建 PVC,yaml 文件如下:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi

通过 kubectl get pv 命令可看到 PV 和 PVC 已绑定:

[root@huizhi ~]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
nfs-pvc Bound nfs-pv-no-affinity 10Gi RWO 4s

第三步:用户创建应用,并使用第二步创建的 PVC。

apiVersion: v1
kind: Pod
metadata:
name: test-nfs
spec:
containers:
- image: nginx:alpine
imagePullPolicy: IfNotPresent
name: nginx
volumeMounts:
- mountPath: /data
name: nfs-volume
volumes:
- name: nfs-volume
persistentVolumeClaim:
claimName: nfs-pvc

此时 NFS 的远端存储就挂载了到 Pod 中 nginx 容器的 /data 目录下。

动态创建存储卷

动态创建存储卷,要求集群中部署有 nfs-client-provisioner 以及对应的 storageclass。

动态创建存储卷相比静态创建存储卷,少了集群管理员的干预,流程如下图所示:

集群管理员只需要保证环境中有 NFS 相关的 storageclass 即可:

kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: nfs-sc
provisioner: example.com/nfs
mountOptions:
- vers=4.1

第一步:用户创建 PVC,此处 PVC 的 storageClassName 指定为上面 NFS 的 storageclass 名称:

kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: nfs
annotations:
volume.beta.kubernetes.io/storage-class: "example-nfs"
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 10Mi
storageClassName: nfs-sc

第二步:集群中的 nfs-client-provisioner 会动态创建相应 PV。此时可看到环境中 PV 已创建,并与 PVC 已绑定。

[root@huizhi ~]# kubectl get pv
NAME CAPACITY ACCESSMODES RECLAIMPOLICY STATUS CLAIM REASON AGE
pvc-dce84888-7a9d-11e6-b1ee-5254001e0c1b 10Mi RWX Delete Bound default/nfs 4s

第三步:用户创建应用,并使用第二步创建的 PVC,同静态创建存储卷的第三步。

K8s 持久化存储流程

1. 流程概览

此处借鉴 @郡宝 在云原生存储课程中的流程图

流程如下:

  1. 用户创建了一个包含 PVC 的 Pod,该 PVC 要求使用动态存储卷;
  2. Scheduler 根据 Pod 配置、节点状态、PV 配置等信息,把 Pod 调度到一个合适的 Worker 节点上;
  3. PV 控制器 watch 到该 Pod 使用的 PVC 处于 Pending 状态,于是调用 Volume Plugin(in-tree)创建存储卷,并创建 PV 对象(out-of-tree 由 External Provisioner 来处理);
  4. AD 控制器发现 Pod 和 PVC 处于待挂接状态,于是调用 Volume Plugin 挂接存储设备到目标 Worker 节点上
  5. 在 Worker 节点上,Kubelet 中的 Volume Manager 等待存储设备挂接完成,并通过 Volume Plugin 将设备挂载到全局目录:**/var/lib/kubelet/pods/[pod uid]/volumes/kubernetes.io~iscsi/[PV
    name]**(以 iscsi 为例);
  6. Kubelet 通过 Docker 启动 Pod 的 Containers,用 bind mount 方式将已挂载到本地全局目录的卷映射到容器中。

更详细的流程如下:

2. 流程详解

不同 K8s 版本,持久化存储流程略有区别。本文基于 Kubernetes 1.14.8 版本。

从上述流程图中可看到,存储卷从创建到提供应用使用共分为三个阶段:Provision/Delete、Attach/Detach、Mount/Unmount。

provisioning volumes

PV 控制器中有两个 Worker:

  • ClaimWorker:处理 PVC 的 add / update / delete 相关事件以及 PVC 的状态迁移;
  • VolumeWorker:负责 PV 的状态迁移。

PV 状态迁移(UpdatePVStatus):

  • PV 初始状态为 Available,当 PV 与 PVC 绑定后,状态变为 Bound;
  • 与 PV 绑定的 PVC 删除后,状态变为 Released;
  • 当 PV 回收策略为 Recycled 或手动删除 PV 的 .Spec.ClaimRef 后,PV 状态变为 Available;
  • 当 PV 回收策略未知或 Recycle 失败或存储卷删除失败,PV 状态变为 Failed;
  • 手动删除 PV 的 .Spec.ClaimRef,PV 状态变为 Available。

PVC 状态迁移(UpdatePVCStatus):

  • 当集群中不存在满足 PVC 条件的 PV 时,PVC 状态为 Pending。在 PV 与 PVC 绑定后,PVC 状态由 Pending 变为 Bound;
  • 与 PVC 绑定的 PV 在环境中被删除,PVC 状态变为 Lost;
  • 再次与一个同名 PV 绑定后,PVC 状态变为 Bound。

Provisioning 流程如下(此处模拟用户创建一个新 PVC):

静态存储卷流程(FindBestMatch):PV 控制器首先在环境中筛选一个状态为 Available 的 PV 与新 PVC匹配。

  • DelayBinding:PV 控制器判断该 PVC 是否需要延迟绑定:1. 查看 PVC 的 annotation 中是否包含http://volume.kubernetes.io/selected-node,若存在则表示该 PVC 已经被调度器指定好了节点(属于 ProvisionVolume),故不需要延迟绑定;2. 若 PVC 的 annotation 中不存在 http://volume.kubernetes.io/selected-node,同时没有 StorageClass,默认表示不需要延迟绑定;若有 StorageClass,查看其 VolumeBindingMode 字段,若为 WaitForFirstConsumer 则需要延迟绑定,若为 Immediate 则不需要延迟绑定;
  • FindBestMatchPVForClaim:PV 控制器尝试找一个满足 PVC 要求的环境中现有的 PV。PV 控制器会将所有的 PV 进行一次筛选,并会从满足条件的 PV 中选择一个最佳匹配的PV。筛选规则:1. VolumeMode 是否匹配;2. PV 是否已绑定到 PVC 上;3. PV 的 .Status.Phase 是否为 Available;4. LabelSelector 检查,PV 与 PVC 的 label 要保持一致;5. PV 与 PVC 的 StorageClass 是否一致;6. 每次迭代更新最小满足 PVC requested size 的 PV,并作为最终结果返回;
  • Bind:PV 控制器对选中的 PV、PVC 进行绑定:1. 更新 PV 的 .Spec.ClaimRef 信息为当前 PVC;2. 更新 PV 的 .Status.Phase 为 Bound;3. 新增 PV 的 annotation : http://pv.kubernetes.io/bound-by-controller: "yes";4. 更新 PVC 的 .Spec.VolumeName 为 PV 名称;5. 更新 PVC 的 .Status.Phase 为 Bound;6. 新增 PVC 的 annotation:http://pv.kubernetes.io/bound-by-controller: "yes" 和 http://pv.kubernetes.io/bind-completed: "yes";

动态存储卷流程(ProvisionVolume):若环境中没有合适的 PV,则进入动态 Provisioning 场景:

  • Before Provisioning:1. PV 控制器首先判断 PVC 使用的 StorageClass 是 in-tree 还是 out-of-tree:通过查看 StorageClass 的 Provisioner 字段是否包含 "http://kubernetes.io/" 前缀来判断;2. PV 控制器更新 PVC 的 annotation:claim.Annotations["http://volume.beta.kubernetes.io/storage-provisioner"] = storageClass.Provisioner;
  • in-tree Provisioning(internal provisioning):1. in-tree 的 Provioner 会实现 ProvisionableVolumePlugin 接口的 NewProvisioner 方法,用来返回一个新的 Provisioner;2. PV 控制器调用 Provisioner 的 Provision 函数,该函数会返回一个 PV 对象;3. PV 控制器创建上一步返回的 PV 对象,将其与 PVC 绑定,Spec.ClaimRef 设置为 PVC,.Status.Phase 设置为 Bound,.Spec.StorageClassName 设置为与 PVC 相同的 StorageClassName;同时新增 annotation:"http://pv.kubernetes.io/bound-by-controller"="yes" 和 "http://pv.kubernetes.io/provisioned-by"=plugin.GetPluginName();
  • out-of-tree Provisioning(external provisioning):1. External Provisioner 检查 PVC 中的 claim.Spec.VolumeName 是否为空,不为空则直接跳过该 PVC;2. External Provisioner 检查 PVC 中的 claim.Annotations["http://volume.beta.kubernetes.io/storage-provisioner"] 是否等于自己的 Provisioner Name(External Provisioner 在启动时会传入--provisioner 参数来确定自己的 Provisioner Name);3. 若 PVC 的 VolumeMode=Block,检查 External Provisioner 是否支持块设备;4. External Provisioner 调用 Provision 函数:通过 gRPC 调用 CSI 存储插件的 CreateVolume 接口;5. External Provisioner 创建一个 PV 来代表该 volume,同时将该 PV 与之前的 PVC 做绑定。

deleting volumes

Deleting 流程为 Provisioning 的反操作:

用户删除 PVC,删除 PV 控制器改变 PV.Status.Phase 为 Released。

当 PV.Status.Phase == Released 时,PV 控制器首先检查 Spec.PersistentVolumeReclaimPolicy 的值,为 Retain 时直接跳过,为 Delete 时:

  • in-tree Deleting:1. in-tree 的 Provioner 会实现 DeletableVolumePlugin 接口的 NewDeleter 方法,用来返回一个新的 Deleter;2. 控制器调用 Deleter 的 Delete 函数,删除对应 volume;3. 在 volume 删除后,PV 控制器会删除 PV 对象;
  • out-of-tree Deleting:1. External Provisioner 调用 Delete 函数,通过 gRPC 调用 CSI 插件的 DeleteVolume 接口;2. 在 volume 删除后,External Provisioner 会删除 PV 对象

Attaching Volumes

Kubelet 组件和 AD 控制器都可以做 attach/detach 操作,若 Kubelet 的启动参数中指定了--enable-controller-attach-detach,则由 Kubelet 来做;否则默认由 AD 控制起来做。下面以 AD 控制器为例来讲解 attach/detach 操作。

AD 控制器中有两个核心变量:

  • DesiredStateOfWorld(DSW):集群中预期的数据卷挂接状态,包含了 nodes->volumes->pods 的信息;
  • ActualStateOfWorld(ASW):集群中实际的数据卷挂接状态,包含了 volumes->nodes 的信息。

Attaching 流程如下:

AD 控制器根据集群中的资源信息,初始化 DSW 和 ASW。

AD 控制器内部有三个组件周期性更新 DSW 和 ASW:

  • Reconciler。通过一个 GoRoutine 周期性运行,确保 volume 挂接/摘除完毕。此期间不断更新 ASW:

in-tree attaching:1. in-tree 的 Attacher 会实现 AttachableVolumePlugin 接口的 NewAttacher 方法,用来返回一个新的 Attacher;2. AD 控制器调用 Attacher 的 Attach 函数进行设备挂接;3. 更新 ASW。

out-of-tree attaching:1. 调用 in-tree 的 CSIAttacher 创建一个 VolumeAttachement(VA)对象,该对象包含了 Attacher 信息、节点名称、待挂接 PV 信息;2. External Attacher 会 watch 集群中的 VolumeAttachement 资源,发现有需要挂接的数据卷时,调用 Attach 函数,通过 gRPC 调用 CSI 插件的 ControllerPublishVolume 接口。

  • DesiredStateOfWorldPopulator。通过一个 GoRoutine 周期性运行,主要功能是更新 DSW:

findAndRemoveDeletedPods - 遍历所有 DSW 中的 Pods,若其已从集群中删除则从 DSW 中移除;
findAndAddActivePods - 遍历所有 PodLister 中的 Pods,若 DSW 中不存在该 Pod 则添加至 DSW。

  • PVC Worker。watch PVC 的 add/update 事件,处理 PVC 相关的 Pod,并实时更新 DSW。

Detaching Volumes

Detaching 流程如下:

  • 当 Pod 被删除,AD 控制器会 watch 到该事件。首先 AD 控制器检查 Pod 所在的 Node 资源是否包含"http://volumes.kubernetes.io/keep-terminated-pod-volumes"标签,若包含则不做操作;不包含则从 DSW 中去掉该 volume;
  • AD 控制器通过 Reconciler 使 ActualStateOfWorld 状态向 DesiredStateOfWorld 状态靠近,当发现 ASW 中有 DSW 中不存在的 volume 时,会做 Detach 操作:

in-tree detaching:1. AD 控制器会实现 AttachableVolumePlugin 接口的 NewDetacher 方法,用来返回一个新的 Detacher;2. 控制器调用 Detacher 的 Detach 函数,detach 对应 volume;3. AD 控制器更新 ASW。

out-of-tree detaching:1. AD 控制器调用 in-tree 的 CSIAttacher 删除相关 VolumeAttachement 对象;2. External Attacher 会 watch 集群中的 VolumeAttachement(VA)资源,发现有需要摘除的数据卷时,调用 Detach 函数,通过 gRPC 调用 CSI 插件的 ControllerUnpublishVolume 接口;3. AD 控制器更新 ASW。

Volume Manager 中同样也有两个核心变量:

  • DesiredStateOfWorld(DSW):集群中预期的数据卷挂载状态,包含了 volumes->pods 的信息;
  • ActualStateOfWorld(ASW):集群中实际的数据卷挂载状态,包含了 volumes->pods 的信息。

Mounting/UnMounting 流程如下:

全局目录(global mount path)存在的目的:块设备在 Linux 上只能挂载一次,而在 K8s 场景中,一个 PV 可能被挂载到同一个 Node 上的多个 Pod 实例中。若块设备格式化后先挂载至 Node 上的一个临时全局目录,然后再使用 Linux 中的 bind mount 技术把这个全局目录挂载进 Pod 中对应的目录上,就可以满足要求。上述流程图中,全局目录即 /var/lib/kubelet/pods/[pod uid]/volumes/kubernetes.io~iscsi/[PV

name]

VolumeManager 根据集群中的资源信息,初始化 DSW 和 ASW。

VolumeManager 内部有两个组件周期性更新 DSW 和 ASW:

  • DesiredStateOfWorldPopulator:通过一个 GoRoutine 周期性运行,主要功能是更新 DSW;
  • Reconciler:通过一个 GoRoutine 周期性运行,确保 volume 挂载/卸载完毕。此期间不断更新 ASW:

unmountVolumes:确保 Pod 删除后 volumes 被 unmount。遍历一遍所有 ASW 中的 Pod,若其不在 DSW 中(表示 Pod 被删除),此处以 VolumeMode=FileSystem 举例,则执行如下操作:

  1. Remove all bind-mounts:调用 Unmounter 的 TearDown 接口(若为 out-of-tree 则调用 CSI 插件的 NodeUnpublishVolume 接口);
  2. Unmount volume:调用 DeviceUnmounter 的 UnmountDevice 函数(若为 out-of-tree 则调用 CSI 插件的 NodeUnstageVolume 接口);
  3. 更新 ASW。

mountAttachVolumes:确保 Pod 要使用的 volumes 挂载成功。遍历一遍所有 DSW 中的 Pod,若其不在 ASW 中(表示目录待挂载映射到 Pod 上),此处以 VolumeMode=FileSystem 举例,执行如下操作:

  1. 等待 volume 挂接到节点上(由 External Attacher or Kubelet 本身挂接);
  2. 挂载 volume 到全局目录:调用 DeviceMounter 的 MountDevice 函数(若为 out-of-tree 则调用 CSI 插件的 NodeStageVolume 接口);
  3. 更新 ASW:该 volume 已挂载到全局目录;
  4. bind-mount volume 到 Pod 上:调用 Mounter 的 SetUp 接口(若为 out-of-tree 则调用 CSI 插件的 NodePublishVolume 接口);
  5. 更新 ASW。

unmountDetachDevices:确保需要 unmount 的 volumes 被 unmount。遍历一遍所有 ASW 中的 UnmountedVolumes,若其不在 DSW 中(表示 volume 已无需使用),执行如下操作:

  1. Unmount volume:调用 DeviceUnmounter 的 UnmountDevice 函数(若为 out-of-tree 则调用 CSI 插件的NodeUnstageVolume接口);
  2. 更新 ASW。

总结

本文先对 K8s 持久化存储基础概念及使用方法进行了介绍,并对 K8s 内部存储流程进行了深度解析。在 K8s 上,使用任何一种存储都离不开上面的流程(有些场景不会用到 attach/detach),环境上的存储问题也一定是其中某个环节出现了故障。

容器存储的坑比较多,专有云环境下尤其如此。不过挑战越多,机遇也越多!目前国内专有云市场在存储领域也是群雄逐鹿,我们敏捷 PaaS 容器团队欢迎大侠的加入,一起共创未来!

参考链接

  1. Kubernetes 社区源码
  2. 【云原生公开课】Kubernetes 存储架构及插件使用(郡宝)
  3. 【云原生公开课】应用存储和持久化数据卷 - 核心知识(至天)
  4. 【kubernetes-design-proposals】volume-provisioning
  5. 【kubernetes-design-proposals】CSI Volume Plugins in Kubernetes Design Doc

云原生应用团队招人啦!

阿里云原生应用平台团队目前求贤若渴,如果你满足:

  • 对容器和基础设施相关领域的云原生技术充满热情,在相关领域如 Kubernetes、Serverless 平台、容器网络与存储、运维平台等云原生基础设施其中某一方向有丰富的积累和突出成果(如产品落地,创新技术实现,开源贡献,领先的学术成果);
  • 优秀的表达能力,沟通能力和团队协作能力;对技术和业务有前瞻性思考;具备较强的 ownership,以结果为导向,善于决策;
  • 至少熟悉 Java、Golang 中的一项编程语言;
  • 本科及以上学历、3 年以上工作经验。

简历可投递至邮箱:huizhi.szh@alibaba-inc.com,如有疑问欢迎加微信咨询:TheBeatles1994。

云原生网络研讨会邀您参加

点击立即预约直播

阿里巴巴云原生关注微服务、Serverless、容器、Service Mesh 等技术领域、聚焦云原生流行技术趋势、云原生大规模的落地实践,做最懂云原生开发者的公众号。”

[转帖]一文读懂 K8s 持久化存储流程的更多相关文章

  1. kubernetes基础——一文读懂k8s

    容器 容器与虚拟机对比图(左边为容器.右边为虚拟机)   容器技术是虚拟化技术的一种,以Docker为例,Docker利用Linux的LXC(LinuX Containers)技术.CGroup(Co ...

  2. [转帖]一文读懂 HTTP/2

    一文读懂 HTTP/2 http://support.upyun.com/hc/kb/article/1048799/ 又小拍 • 发表于:2017年05月18日 15:34:45 • 更新于:201 ...

  3. 一文读懂Redis持久化

    Redis 是一个开源( BSD 许可)的,内存中的数据结构存储系统,它可以用作数据库.缓存和消息中间件.它支持的数据类型很丰富,如字符串.链表.集合.以及散列等,并且还支持多种排序功能. 什么叫持久 ...

  4. 一文读懂k8s rbac 权限验证

    自我认为的k8s三大难点:权限验证,覆盖网络,各种证书. 今天就说一下我所理解的权限验证rbac. 咱不说rbac0,rbac1,rbac2,rbac3.咱就说怎么控制权限就行. 一.前言 1,反正R ...

  5. 一文读懂k8s之Pod安全策略

    导读 Pod容器想要获取集群的资源信息,需要配置角色和ServiceAccount进行授权.为了更精细地控制Pod对资源的使用方式,Kubernetes从1.4版本开始引入了PodSecurityPo ...

  6. 一文读懂Spring MVC执行流程

    说到Spring MVC执行流程,网上有很多这方面的文章介绍,但是都不太详细,作为一个初学者去读会有许多不理解的地方,今天这篇文章记录一下我学习Spring MVC的心得体会 话不多说,先上图:   ...

  7. Java——一文读懂Spring MVC执行流程

    说到Spring MVC执行流程,网上有很多这方面的文章介绍,但是都不太详细,作为一个初学者去读会有许多不理解的地方,今天这篇文章记录一下我学习Spring MVC的心得体会 话不多说,先上图: Sp ...

  8. [转帖]MerkleDAG全面解析 一文读懂什么是默克尔有向无环图

    MerkleDAG全面解析 一文读懂什么是默克尔有向无环图 2018-08-16 15:58区块链/技术 MerkleDAG作为IPFS的核心数据结构,它融合了Merkle Tree和DAG的优点,今 ...

  9. [转帖]从HTTP/0.9到HTTP/2:一文读懂HTTP协议的历史演变和设计思路

    从HTTP/0.9到HTTP/2:一文读懂HTTP协议的历史演变和设计思路   http://www.52im.net/thread-1709-1-2.html     本文原作者阮一峰,作者博客:r ...

  10. 即时通讯新手入门:一文读懂什么是Nginx?它能否实现IM的负载均衡?

    本文引用了“蔷薇Nina”的“Nginx 相关介绍(Nginx是什么?能干嘛?)”一文部分内容,感谢作者的无私分享. 1.引言   Nginx(及其衍生产品)是目前被大量使用的服务端反向代理和负载均衡 ...

随机推荐

  1. 过亿云资源运维管控难?华为云CloudMap带你喝着咖啡做运维

    摘要:华为云站点数字化平台CloudMap携手华为云图引擎GES打造云服务全栈拓扑,网络流量路径和云服务动态依赖等空间关系数据,支撑现网运行态风险识别和分钟级定位定界,构建业界领先的数字化能力. 本文 ...

  2. 云小课|MRS基础原理之Flink组件介绍

    阅识风云是华为云信息大咖,擅长将复杂信息多元化呈现,其出品的一张图(云图说).深入浅出的博文(云小课)或短视频(云视厅)总有一款能让您快速上手华为云.更多精彩内容请单击此处. 摘要:Flink是一个批 ...

  3. 一文带你认识AscendCL

    摘要:AscendCL(Ascend Computing Language,昇腾计算语言)是昇腾计算开放编程框架,是对底层昇腾计算服务接口的封装. 本文分享自华为云社区<[CANN文档速递09期 ...

  4. 每条你收藏的资讯背后,都离不开TA

    摘要:云原生数据库GaussDB(for Redis)不仅提升了阅客的服务效率,让个性化推荐更快更稳,还降低了存储和改造成本,为企业未来发展奠定了云化基础,助力阅客实现更高质量的资讯触达. 随着互联网 ...

  5. 云图说丨应用宕机怎么办?MAS帮您实现业务无缝切换

    摘要: 多云高可用服务(Multi-cloud high Availability Service,简称MAS)源自华为消费者多云应用高可用方案,提供从流量入口.数据到应用层的端到端的业务故障切换及容 ...

  6. Docker 安装 ELK,EFK代替

    ELK 版本因为 前面 Elasticsearch 用的 7.9.3 版本,所以 kibana-7.9.3.logstash-7.9.3 都用 7.9.3 版本 安装配置 Elasticsearch ...

  7. 自用 | Rust 基础学习资料

    Rust语言圣经:Github,GitBook Rustt,RusttT 翻译小组的官方仓库,这里有国外优秀的技术文章.学习教程.新闻资讯的高质量翻译. Rust语言周刊,每周五发布,精选过去一周的技 ...

  8. Tomcat 9.0.26 高并发场景下DeadLock问题排查与修复

    本文首发于 vivo互联网技术 微信公众号 链接:https://mp.weixin.qq.com/s/-OcCDI4L5GR8vVXSYhXJ7w作者:黄卫兵.陈锦霞 一.Tomcat容器 9.0. ...

  9. 当 Rokid 遇上函数计算

    作者:王彬(阿里云解决方案架构师).姚兰天(Rokid 技术专家).聂大鹏(阿里云高级技术专家) 公司背景和业务 Rokid 创立于2014年,是一家专注于人机交互技术的产品平台公司.Rokid 通过 ...

  10. 十五、跨主机通信overlay网络

    系列导航 一.docker入门(概念) 二.docker的安装和镜像管理 三.docker容器的常用命令 四.容器的网络访问 五.容器端口转发 六.docker数据卷 七.手动制作docker镜像 八 ...