Dubbo-kubernetes 基于Informer服务发现优化之路

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

List/Watch 机制介绍


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


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


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


当客户端调用watch API时,kube-apiserverresponseHTTP 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",...}}


image.png


Dubbo 基于 Watch 的服务发现


image.png


以上图为例,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>() {
            // 资源更改的事件回调
            @Override
            public 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-apiserver
    Endpoints endpoints = kubernetesClient.endpoints().inNamespace(namespace)
            .withName(serviceName).get();
    return toServiceInstance(endpoints, serviceName);
}


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


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



Informer 机制介绍



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

image.png

以 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步骤。


image.png

(来源于kubernetes/sample-controller)


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的负载。


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


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


资源监听由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>() {
                @Override
                public 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);
}


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);
}


结论


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



相关实践学习
深入解析Docker容器化技术
Docker是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux机器上,也可以实现虚拟化,容器是完全使用沙箱机制,相互之间不会有任何接口。Docker是世界领先的软件容器平台。开发人员利用Docker可以消除协作编码时“在我的机器上可正常工作”的问题。运维人员利用Docker可以在隔离容器中并行运行和管理应用,获得更好的计算密度。企业利用Docker可以构建敏捷的软件交付管道,以更快的速度、更高的安全性和可靠的信誉为Linux和Windows Server应用发布新功能。 在本套课程中,我们将全面的讲解Docker技术栈,从环境安装到容器、镜像操作以及生产环境如何部署开发的微服务应用。本课程由黑马程序员提供。 &nbsp; &nbsp; 相关的阿里云产品:容器服务 ACK 容器服务 Kubernetes 版(简称 ACK)提供高性能可伸缩的容器应用管理能力,支持企业级容器化应用的全生命周期管理。整合阿里云虚拟化、存储、网络和安全能力,打造云端最佳容器化应用运行环境。 了解产品详情: https://www.aliyun.com/product/kubernetes
相关文章
|
Kubernetes 安全 Ubuntu
k8s学习-CKS真题-Dockerfile和deployment优化
k8s学习-CKS真题-Dockerfile和deployment优化
263 0
|
存储 边缘计算 数据管理
Docker 存储驱动解析:选择最适合你的存储方案,优化容器化部署性能和数据管理
Docker 存储驱动解析:选择最适合你的存储方案,优化容器化部署性能和数据管理
568 0
|
11月前
|
Kubernetes 网络协议 Nacos
OpenAI 宕机思考丨Kubernetes 复杂度带来的服务发现系统的风险和应对措施
Kubernetes 体系基于 DNS 的服务发现为开发者提供了很大的便利,但其高度复杂的架构往往带来更高的稳定性风险。以 Nacos 为代表的独立服务发现系统架构简单,在 Kubernetes 中选择独立服务发现系统可以帮助增强业务可靠性、可伸缩性、性能及可维护性,对于规模大、增长快、稳定性要求高的业务来说是一个较理想的服务发现方案。希望大家都能找到适合自己业务的服务发现系统。
450 92
|
8月前
|
存储 负载均衡 测试技术
ACK Gateway with Inference Extension:优化多机分布式大模型推理服务实践
本文介绍了如何利用阿里云容器服务ACK推出的ACK Gateway with Inference Extension组件,在Kubernetes环境中为多机分布式部署的LLM推理服务提供智能路由和负载均衡能力。文章以部署和优化QwQ-32B模型为例,详细展示了从环境准备到性能测试的完整实践过程。
|
10月前
|
存储 人工智能 弹性计算
NVIDIA NIM on ACK:优化生成式AI模型的部署与管理
本文结合NVIDIA NIM和阿里云容器服务,提出了基于ACK的完整服务化管理方案,用于优化生成式AI模型的部署和管理。
|
存储 Kubernetes 监控
深度解析Kubernetes在微服务架构中的应用与优化
【10月更文挑战第18天】深度解析Kubernetes在微服务架构中的应用与优化
539 0
|
Kubernetes Cloud Native 应用服务中间件
Kubernetes 自动伸缩策略:优化资源利用率
【8月更文第29天】在现代云原生环境中,应用的流量往往具有不可预测性。为了应对这种变化,Kubernetes 提供了多种自动伸缩机制来动态调整应用实例的数量和每个实例分配的资源。本文将深入探讨两种主要的自动伸缩工具:水平 Pod 自动伸缩器 (HPA) 和垂直 Pod 伸缩器 (VPA),并提供实际的应用示例。
396 1
|
Kubernetes 监控 开发者
|
Kubernetes 监控 Cloud Native
"解锁K8s新姿势!Cobra+Client-go强强联手,打造你的专属K8s监控神器,让资源优化与性能监控尽在掌握!"
【8月更文挑战第14天】在云原生领域,Kubernetes以出色的扩展性和定制化能力引领潮流。面对独特需求,自定义插件成为必要。本文通过Cobra与Client-go两大利器,打造一款监测特定标签Pods资源使用的K8s插件。Cobra简化CLI开发,Client-go则负责与K8s API交互。从初始化项目到实现查询逻辑,一步步引导你构建个性化工具,开启K8s集群智能化管理之旅。
268 2

热门文章

最新文章

推荐镜像

更多