在 Kubernetes(简称 K8s,一个可移植容器的编排管理工具)体系中,etcd 存储集群的数据信息,kube-apiserver 作为统一入口,任何对数据的操作都必须经过 kube-apiserver。因此 Dubbo 想要以 Kubernetes 作为注册中心,必须调用 kube-apiserver 获取服务地址列表,那是以什么样的机制保持信息的可靠性、实时性、顺序性、高性能呢?答案就是基于 List/Watch 的 Informer 组件

01

List/Watch 机制介绍

Aliware

List / Watch 机制是 Kubernetes 中实现集群控制模块最核心的设计之一,它采用统一的异步消息处理机制,保证了消息的实时性、可靠性、顺序性和性能等,为声明式风格的API奠定了良好的基础。

List 是向 kube-apiserver 调用 list API 获取资源列表,基于 HTTP 短链接实现。

Watch则是向 kube-apiserver 调用 watch API 监听资源变更事件,基于 HTTP 长链接,通过 Chunked transfer encoding(分块传输编码) 来实现消息通知。

当客户端调用 watch API 时,kube-apiserver 在 response 的 HTTP Header 中设置 Transfer-Encoding 的值为 chunked,表示采用分块传输编码,客户端收到该信息后,便和服务端连接,并等待下一个数据块,即资源的事件信息。例如:

$ curl -i http://{kube-api-server-ip}:8080/api/v1/watch/endpoints?watch=yes
HTTP/1.1 200 OK
Content-Type: application/json
Transfer-Encoding: chunked
Date: Thu, 14 Seo 2022 20:22:59 GMT
Transfer-Encoding: chunked{"type":"ADDED", "object":{"kind":"Endpoints","apiVersion":"v1",...}}
{"type":"ADDED", "object":{"kind":"Endpoints","apiVersion":"v1",...}}
{"type":"MODIFIED", "object":{"kind":"Endpoints","apiVersion":"v1",...}}

02

Dubbo 基于 Watch 的服务发现

Aliware

以上图为例,dubbo-kubernetes 服务发现以 Kubernetes 为 Registry ,provider 注册地址到注册中心,consumer 从注册中心读取和订阅 provider 地址列表。在 Dubbo3.1 版本之前,consumer 订阅是通过 Fabric8 Kubernetes Java Client 提供的 watch API 实现,监听 kube-apiserver 中资源的 create、update 和 delete 事件,如下:

private void watchEndpoints(ServiceInstancesChangedListener listener, String serviceName) {Watch watch = kubernetesClient.endpoints().inNamespace(namespace).withName(serviceName).watch(new Watcher<Endpoints>() {// 资源更改的事件回调@Overridepublic void eventReceived(Action action, Endpoints resource) {notifyServiceChanged(serviceName, listener);...}});...
}
private void notifyServiceChanged(String serviceName, ServiceInstancesChangedListener listener) {ServiceInstancesChangedEvent event = new ServiceInstancesChangedEvent(serviceName, getInstances(serviceName));...listener.onEvent(event);...
}

监听到资源变化后,调用 notifyServiceChanged 方法从 kube-apiserver 全量拉取资源 list 数据,保持 Dubbo 本地侧服务列表。

@Override
public List<ServiceInstance> getInstances(String serviceName){// 直接调用kube-apiserverEndpoints endpoints = kubernetesClient.endpoints().inNamespace(namespace).withName(serviceName).get();return toServiceInstance(endpoints, serviceName);
}

这样的操作存在很严重的问题,由于 watch 对应的回调函数会将更新的资源返回,Dubbo 社区考虑到维护成本较高,之前并没有在本地维护关于 CRD 资源的缓存,这样每次监听到变化后调用 list 从 kube-apiserver 获取对应 serviceName 的 endpoints 信息,无疑增加了一次对 kube-apiserver 的直接访问。

kubernetes-client 为解决客户端需要自行维护缓存的问题,推出了 informer 机制。

03

Informer 机制介绍

Aliware

Informer 模块是 Kubernetes 中的基础组件,以 List/Watch 为基础,负责各组件与 kube-apiserver 的资源与事件同步。Kubernetes 中的组件,如果要访问 Kubernetes 中的 Object,绝大部分情况下会使用 Informer 中的 Lister()方法,而非直接调用 kube-apiserver。

以 Pod 资源为例,介绍下 informer 的关键逻辑(与下图步骤一一对应):

  1. Informer 在初始化时,Reflector 会先调用 List 获得所有的 Pod,同时调用 Watch 长连接监听 kube-apiserver。

  2. Reflector 拿到全部 Pod 后,将 Add Pod 这个事件发送到 DeltaFIFO。

  3. DeltaFIFO 随后 pop 这个事件到 Informer 处理。

  4. Informer 向 Indexer 发布 Add Pod 事件。

  5. Indexer 接到通知后,直接操作 Store 中的数据(key->value 格式)。

  6. Informer 触发 EventHandler 回调。

  7. 将 key 推到 Workqueue 队列中。

  8. 从 WorkQueue 中 pop 一个 key。

  9. 然后根据 key 去 Indexer 取到 val。根据当前的 EventHandler 进行 Add Pod 操作(用户自定义的回调函数)。

  10. 随后当 Watch 到 kube-apiserver 资源有改变的时候,再重复 2-9 步骤。

(来源于 kubernetes/sample-controller)

01

Informer 关键设计

  • 本地缓存:Informer 只会调用 K8s List 和 Watch 两种类型的 API。Informer 在初始化的时,先调用 List 获得某种 resource 的全部 Object,缓存在内存中; 然后,调用 Watch API 去 watch 这种 resource,去维护这份缓存; 最后,Informer 就不再调用 kube-apiserver。Informer 抽象了 cache 这个组件,并且实现了 store 接口,后续获取资源直接通过本地的缓存来进行获取。

  • 无界队列:为了协调数据生产与消费的不一致状态,在客户端中通过实现了一个无界队列 DeltaFIFO 来进行数据的缓冲,当 reflector 获取到数据之后,只需要将数据推到到 DeltaFIFO 中,则就可以继续 watch 后续事件,从而减少阻塞时间,如上图 2-3 步骤所示。

  • 事件去重:在 DeltaFIFO 中,如果针对某个资源的事件重复被触发,则就只会保留相同事件最后一个事件作为后续处理,有 resourceVersion 唯一键保证,不会重复消费。

  • 复用连接:每一种资源都实现了 Informer 机制,允许监控不同的资源事件。为了避免同一个资源建立多个 Informer,每个 Informer 使用一个 Reflector 与 apiserver 建立链接,导致 kube-apiserver 负载过高的情况,K8s 中抽象了 sharedInformer 的概念,即共享的 Informer, 可以使同一类资源 Informer 共享一个 Reflector。内部定义了一个 map 字段,用于存放所有 Infromer 的字段。针对同一资源只建立一个连接,减小 kube-apiserver 的负载。

04

Dubbo 引入 Informer 机制后的服务发现

Aliware

Dubbo 3.1.1 后引入 Informer 机制,Informer 组件会利用其特性在 consumer 侧内存中维护 Kubernetes 环境中的所有地址列表。

01

资源监听由 Watch API 更换为 Informer API

以 Endpoints 为例,将原本的 watch 替换为 Informer,回调函数分别为 onAdd、onUpdate、onDelete,回调参数传的都是 Informer store 中的资源全量值。

/*** 监听Endpoints*/
private void watchEndpoints(ServiceInstancesChangedListener listener, String serviceName) {SharedIndexInformer<Endpoints> endInformer = kubernetesClient.endpoints().inNamespace(namespace).withName(serviceName).inform(new ResourceEventHandler<Endpoints>() {@Overridepublic void onUpdate(Endpoints oldEndpoints, Endpoints newEndpoints) {notifyServiceChanged(serviceName, listener, toServiceInstance(newEndpoints, serviceName));}// 省略掉onAdd和onDelete...});...
}/*** 通知订阅者Service改变*/
private void notifyServiceChanged(String serviceName, ServiceInstancesChangedListener listener, List<ServiceInstance> serviceInstanceList) {ServiceInstancesChangedEvent event = new ServiceInstancesChangedEvent(serviceName, serviceInstanceList);// 发布事件listener.onEvent(event);
}

02

getInstances() 优化

引入 Informer 后,无需直接调用 List 接口,而是直接从 Informer 的 store 中获取,减少对 kube-apiserver 的直接调用。

public List<ServiceInstance> getInstances(String serviceName) {Endpoints endpoints = null;SharedIndexInformer<Endpoints> endInformer = ENDPOINTS_INFORMER.get(serviceName);if (endInformer != null) {// 直接从informer的store中获取Endpoints信息List<Endpoints> endpointsList = endInformer.getStore().list();if (endpointsList.size() > 0) {endpoints = endpointsList.get(0);}}// 如果endpoints经过上面处理仍为空,属于异常情况,那就从kube-apiserver拉取if (endpoints == null) {endpoints = kubernetesClient.endpoints().inNamespace(namespace).withName(serviceName).get();}return toServiceInstance(endpoints, serviceName);
}

05

结论

Aliware

优化为 Informer 后,Dubbo 的服务发现不用每次直接调用 kube-apiserver,减小了 kube-apiserver 的压力,也大大减少了响应时间,助力 Dubbo 从传统架构迁移到 Kubernetes 中。

Dubbo-kubernetes 基于 Informer 服务发现优化之路相关推荐

  1. 蚂蚁金服服务注册中心 SOFARegistry 解析 | 服务发现优化之路

    SOFAStack Scalable Open Financial  Architecture Stack 是蚂蚁金服自主研发的金融级分布式架构,包含了构建金融级云原生架构所需的各个组件,是在金融场景 ...

  2. 一文了解 Kubernetes 中的服务发现

    原文链接:一文了解 Kubernetes 中的服务发现 Kubernetes 服务发现是一个经常让我产生困惑的主题之一.本文分为两个部分: 网络方面的背景知识 深入了解 Kubernetes 服务发现 ...

  3. kubernetes上的服务发现-CoreDNS配置

    参考: 官方网站,https://coredns.io/ CoreDNS安装,https://my.oschina.net/u/2306127/blog/1618543 CoreDNS使用手册,htt ...

  4. 从零开始入门 | Kubernetes 中的服务发现与负载均衡

    作者 | 阿里巴巴技术专家  溪恒 一.需求来源 为什么需要服务发现 在 K8s 集群里面会通过 pod 去部署应用,与传统的应用部署不同,传统应用部署在给定的机器上面去部署,我们知道怎么去调用别的机 ...

  5. Kubernetes集群服务发现Service资源LoadBalancer类型详解(二十九)

    Kubernetes集群服务发现Service资源LoadBalancer类型详解 1.LoadBalancer类型的service资源概念 LoadBalancer和Nodeport非常相似,目的都 ...

  6. 猪八戒网Nginx的动态服务发现演进之路

    随着业务访问量的直线增长,业务项目数量也越来越多,期间各个业务项目的频繁上线.回滚.动态扩容与缩容等,促使了微服务架构的流行,又新引入了容器化部署发布方式,当容器发布及重建的时候,实例IP将会发生变化 ...

  7. Prometheus 基于k8s服务发现通过Cadvisor监控Kubernetes

    Prometheus服务发现 Prometheus添加被监控端支持两种方式: • 静态配置:手动配置 • 服务发现:动态发现需要监控的Target实例 支持服务发现的来源 • azure_sd_con ...

  8. 一文详解 Kubernetes 中的服务发现,运维请收藏

    K8S 服务发现之旅 Kubernetes 服务发现是一个经常让我产生困惑的主题之一.本文分为两个部分: 网络方面的背景知识 深入了解 Kubernetes 服务发现 要了解服务发现,首先要了解背后的 ...

  9. 浅谈 Kubernetes 中的服务发现

    原文:https://nigelpoulton.com/blog/f/demystifying-kubernetes-service-discovery Kubernetes 服务发现是一个经常让我产 ...

最新文章

  1. [转] 为什么javascript是单线程的却能让AJAX异步调用?
  2. WEB安全_csrf攻击
  3. Gson 字符串与对象相互转换工具类
  4. 会linux基本命令是脚本语言吗,如何理解Linux Shell和基本Shell脚本语言?
  5. 任正非:不向美国人民学习他们的伟大,就永远战胜不了美国
  6. python打印一个对象的所有属性_python打印出所有的对象/模块的属性代码详解
  7. 多行文字省略(涵盖标点符号,中英文等复杂字符串)
  8. 异常检测 and GAN网络(2)
  9. python打开浏览本地html文件_python解析本地HTML文件
  10. oracle sqlldr decode,SQLLDR应用举例
  11. Could Not find resource [logback.groovy] ; Cound Not find resource [logback-test.xml]
  12. 笛卡尔树(知识总结+板子整理)
  13. 搭建手机文件服务器,普通用户的低成本家庭文件服务器(伪NAS)的搭建(手机备份篇)...
  14. Git 学习进展 (补发)
  15. Android App签名(证书)校验过程源码分析
  16. 使用freenom注册免费顶级域名并在梅林上使用DDNS
  17. 【胡搞的不能AC的题解,暴力搜索一发博弈问题】1995 三子棋 - 51Nod
  18. 高等学校学生公寓消防安全设计及管理设计要点
  19. 戴尔DELL SCV/SC系列存储故障 Storage Center停机错误的解决方案
  20. 国内真正永久免费的OA办公系统

热门文章

  1. Python数据可视化:pyecharts库绘制K线图
  2. 怎么在Linux中后台启动服务,查看和关闭后台运行程序
  3. 解决usbisp不识别无法烧录Atmega328P,Arduino不识别问题
  4. WB(白平衡) ISO(感光度) 曝光补偿 详解
  5. 精简Windows Defender,关闭superfetch
  6. AP设备漫游阈值设置
  7. iOS NFC读取tag功能实现
  8. js原型对象和原型链理解
  9. android 日历选择器(酒店专用)
  10. 安装gitlab-runner,注册runner到gitlab