Istio Pilot架构解析

本文节选自 ServiceMesher 社区联合编写的《Istio Handbook——Istio 服务网格进阶实战》。

本书地址:https://github.com/servicemesher/istio-handbook/

在应用从单体架构向微服务架构演进的过程中,微服务之间的服务发现、负载均衡、熔断、限流等服务治理需求是无法回避的问题。

在 Service Mesh 出现之前,通常的做法是将这些基础功能以 SDK 的形式嵌入业务代码中,但是这种强耦合的方案会增加开发的难度,增加维护成本,增加质量风险。比如 SDK 需要新增新特性,业务侧也很难配合 SDK 开发人员进行升级,所以很容易造成 SDK 的版本碎片化问题。如果再存在跨语言应用间的交互,对于多语言 SDK 的支持也非常的低效。一方面是相当于相同的代码以不同语言重复实现,实现这类代码既很难给开发人员带来成就感,团队稳定性难以保障;另一方面是如果实现这类基础框架时涉及到了语言特性,其他语言的开发者也很难直接翻译。

而 Service Mesh 的本质则是将此类通用的功能沉淀至 sidecar 中,由 sidecar 接管服务的流量并对其进行治理。在这个思路下,可以通过流量劫持的手段,做到代码零侵入性。这样可以让业务开发人员更关心业务功能。而底层功能由于对业务零侵入,也使得基础功能的升级和快速的更新迭代成为可能。

Istio 是近年来 Service Mesh 的代表作,而 Istio 流量管理的核心组件就是是 Pilot。Pilot 主要功能就是管理和配置部署在特定 Istio 服务网格中的所有 sidecar 代理实例。它管理 sidecar 代理之间的路由流量规则,并配置故障恢复功能,如超时、重试和熔断。

Pilot 架构

Pilot 架构(图片来自Istio官方网站)

根据上图, Pilot 几个关键的模块如下。

抽象模型 (Abstract Model)

为了实现对不同服务注册中心 (Kubernetes、consul) 的支持,Pilot 需要对不同的输入来源的数据有一个统一的存储格式,也就是抽象模型。

抽象模型中定义的关键成员包括 HostName(service 名称)、Ports(service 端口)、Address(service ClusterIP)、Resolution (负载均衡策略) 等。

平台适配器 (Platform adapters)

Pilot 的实现是基于平台适配器(Platform adapters) 的,借助平台适配器 Pilot 可以实现服务注册中心数据到抽象模型之间的数据转换。

例如 Pilot 中的 Kubernetes 适配器通过 Kubernetes API 服务器得到 Kubernetes 中 service 和 pod 的相关信息,然后翻译为抽象模型提供给 Pilot 使用。

通过平台适配器模式, Pilot 还可以从 Consul 等平台中获取服务信息,还可以开发适配器将其他提供服务发现的组件集成到 Pilot 中。

xDS API

Pilot 使用了一套起源于 Envoy 项目的标准数据面 API 来将服务信息和流量规则下发到数据面的 sidecar 中。这套标准数据面 API,也叫 xDS。

Sidecar 通过 xDS API 可以动态获取 Listener (监听器)、Route (路由)、Cluster (集群)及 Endpoint (集群成员)配置:

  • LDS,Listener 发现服务:Listener 监听器控制 sidecar 启动端口监听(目前只支持 TCP 协议),并配置 L3/L4 层过滤器,当网络连接达到后,配置好的网络过滤器堆栈开始处理后续事件。

  • RDS,Router 发现服务:用于 HTTP 连接管理过滤器动态获取路由配置,路由配置包含 HTTP 头部修改(增加、删除 HTTP 头部键值),virtual hosts (虚拟主机),以及 virtual hosts 定义的各个路由条目。

  • CDS,Cluster 发现服务:用于动态获取 Cluster 信息。

  • EDS,Endpoint 发现服务:用与动态维护端点信息,端点信息中还包括负载均衡权重、金丝雀状态等,基于这些信息,sidecar 可以做出智能的负载均衡决策。

通过采用该标准 API, Istio 将控制面和数据面进行了解耦,为多种数据平面 sidecar 实现提供了可能性。例如蚂蚁金服开源的 Golang 版本的 Sidecar MOSN (Modular Observable Smart Network)。

用户 API (User API)

Pilot 还定义了一套用户 API, 用户 API 提供了面向业务的高层抽象,可以被运维人员理解和使用。

运维人员使用该 API 定义流量规则并下发到 Pilot ,这些规则被 Pilot 翻译成数据面的配置,再通过标准数据面 API 分发到 sidecar 实例,可以在运行期对微服务的流量进行控制和调整。

通过运用不同的流量规则,可以对网格中微服务进行精细化的流量控制,如按版本分流、断路器、故障注入、灰度发布等。

Pilot 实现

Pilot 实现(根据原图片重绘,原图来自Istio官方网站)

图中实线连线表示控制流,虚线连线表示数据流。带 [pilot] 的组件表示为 Pilot 组件,图中关键的组件如下:

  • Discovery service:即 pilot-discovery,主要功能是从 Service provider(如 kubernetes 或者 consul )中获取服务信息,从 Kubernetes API Server 中获取流量规则(Kubernetes CRD Resource),并将服务信息和流量规则转化为数据面可以理解的格式,通过标准的数据面 API 下发到网格中的各个 sidecar 中。

  • agent:即 pilot-agent 组件,该进程根据 Kubernetes API Server 中的配置信息生成 Envoy 的配置文件,负责启动、监控 sidecar 进程。

  • proxy:既 sidecar proxy,是所有服务的流量代理,直接连接 pilot-discovery ,间接地从 Kubernetes 等服务注册中心获取集群中微服务的注册情况。

  • service A/B:使用了 Istio 的应用,如 Service A/B,的进出网络流量会被 proxy 接管。

下面介绍下 Pilot 相关的组件 pilot-agent、pilot-discovery 的关键实现。

pilot-agent

pilot-agent 负责的主要工作如下:

  • 生成 sidecar 的配置

  • Sidecar 的启动与监控

生成 sidecar 配置

Sidecar 的配置主要在 pilot-agent 的 init 方法与 proxy 命令处理流程的前半部分生成。其中 init 方法为 pilot-agent 二进制的命令行配置大量的 flag 与默认值,而 proxy 命令处理流程则负责将这些 flag 组装成为 ProxyConfig 对象以启动 Envoy。下面分析几个相对重要的配置。

//go 语言,源码摘自 pilot-agent,role 角色定义
role = &model.Proxy{}
...type Proxy struct {// ClusterID 用于指代 proxy 所在集群名称ClusterID string// Type 用于标记 proxy 运行模式Type NodeTypeIPAddresses []stringID stringDNSDomain string...
}

role 默认的对象为 proxy,关键参数如下:

  • Type:pilot-agent 的 role 有两种运行模式。根据 role.Type 变量定义,最新版本有2个类型, sidecarrouter 。默认是 sidecar

  • IPAddress, ID:可以接受参数,依据注册中心的类型,给予默认值。默认处理方式是 Kubernetes。在 Kubernetes 默认值下,IPAddress 默认为 INSTANCE_IP,ID 默认为 POD_NAME,DNSDomain 默认为 default.svc.cluster.local

  • Istio 可以对接的第三方注册中心有 Kubernetes、Consul、MCP、Mock。

//go 语言,源码摘自 pilot-agent ,envoy 启动代理及监听器
envoyProxy := envoy.NewProxy(envoy.ProxyConfig{Config:              proxyConfig,            //Envoy 的配置,如目录等Node:                role.ServiceNode(),    //role 的字符串拼接 node.Type~ip~ID~DNSDomain 格式NodeIPs:             role.IPAddresses,PodName:             podName,PodNamespace:        podNamespace,PodIP:               podIP,...})// envoy 的代理
agent := envoy.NewAgent(envoyProxy, features.TerminationDrainDuration())// envoy 的监控和程序,会监听证书变化和启动 envoy 
watcher := envoy.NewWatcher(tlsCerts, agent.Restart)
go watcher.Run(ctx)// 监听停止信号
go cmd.WaitSignalFunc(cancel)// envoy 主循环,阻塞等待停止信号
return agent.Run(ctx)

Envoy 配置文件及命令行参数主要有2个:

  • Envoy 的启动目录默认为/usr/local/bin/envoy

  • Envoy 的启动参数相关代码在func (e *envoy) args中。

//go 语言,源码摘自 pilot-agent ,envoy 启动参数
startupArgs := []string{"-c", fname,"--restart-epoch", fmt.Sprint(epoch),"--drain-time-s", fmt.Sprint(int(convertDuration(e.Config.DrainDuration) / time.Second)),"--parent-shutdown-time-s", fmt.Sprint(int(convertDuration(e.Config.ParentShutdownDuration) / time.Second)),"--service-cluster", e.Config.ServiceCluster,"--service-node", e.Node,"--max-obj-name-len", fmt.Sprint(e.Config.StatNameLength),"--local-address-ip-version", proxyLocalAddressType,"--log-format", fmt.Sprintf("[Envoy (Epoch %d)] ", epoch) + "[%Y-%m-%d %T.%e][%t][%l][%n] %v",}

Envoy 启动参数关键释义:

  • –restart-epochepoch 决定了Envoy 热重启的顺序,第一个 Envoy 进程对应的 epoch 为0,后面新建的 Envoy 进程对应 epoch 顺序递增1

  • –drain-time-s:在 pilot-agent init 函数中指定默认值为2秒,可通过 pilot-agent proxy 命令的 drainDuration flag 指定

  • –parent-shutdown-time-s:在 pilot-agent init 函数中指定默认值为3秒,可通过 pilot-agent proxy 命令的 parentShutdownDuration flag 指定

  • –service-cluster:在 pilot-agent init 函数中指定默认值为 istio-proxy ,可通 pilot-agent proxy 命令的 serviceCluster flag 指定

  • –service-node:将 role 的字符串拼接成 node.Type~ip~ID~DNSDomain 格式

Sidecar 的启动与监控

  • 创建 envoy 对象,结构体包含 proxyConfigrole.serviceNodeloglevelpilotSAN(service account name)等。

  • 创建 agent 对象,包含前面创建的 envoy 结构体,一个 epochsmap,1个 channelstatusCh

  • 创建 watcher ,包含证书和 agent.Restart 方法并启动协程执行 watcher.Run

  • watcher.Run 首先执行 agent.Restart,启动 Envoy 。然后启动协程调用 watchCerts ,用于监控各种证书,如果证书文件发生变化,则重新生成证书签名并重启 Envoy。

  • 创建 context,启动协程调用 cmd.WaitSignalFunc 以等待进程接收到 SIGINT, SIGTERM 信号,接受到信号之后通过 context 通知 agentagent 接到通知后调用 terminate 来 kill 所有 Envoy 进程,并退出 agent 进程

  • agent.Run 主进程堵塞,监听 statusCh,这里的 status 其实就是 exitStatus,在监听到 exitStatus 后,会删除当前 epochs 中的 channel 资源。

pilot-discovery

pilot-discovery 扮演服务注册中心、Istio 控制平面到 sidecar 之间的桥梁作用。pilot-discovery 的主要功能如下:

  • 监控服务注册中心(如 Kubernetes)的服务注册情况。在 Kubernetes 环境下,会监控 serviceendpointpodnode 等资源信息。

  • 监控 Istio 控制面信息变化,在 Kubernetes 环境下,会监控包括 RouteRuleVirtualServiceGatewayEgressRuleServiceEntry 等以 Kubernetes CRD 形式存在的 Istio 控制面配置信息。

  • 将上述两类信息合并组合为 sidecar 可以理解的(遵循 Envoy data plane api 的)配置信息,并将这些信息以 gRPC 协议提供给 sidecar。

pilot-discovery 关键实现逻辑如下:

初始化及启动

//go 语言,源码摘自 pilot-discovery,pilot-discovery 初始化及启动的关键部分,省去异常处理// 创建 discoveryServer 对象并启动
discoveryServer, err := bootstrap.NewServer(serverArgs)
discoveryServer.Start(stop)// discoveryServer 对象的具体创建方法
func NewServer(args *PilotArgs) (\*Server, error) {//环境变量e := &model.Environment{...}s := &Server{clusterID:      getClusterID(args),                                //集群idenvironment:    e,                                                //环境变量EnvoyXdsServer: envoyv2.NewDiscoveryServer(e, args.Plugins),     //Pilot 针对 Envoy v2 xds APIs 的 gRPC 实现,用于通知 envoy 配置更新...}s.initKubeClient(args)s.initMeshConfiguration(args, fileWatcher)        s.initConfigController(args)                    s.initServiceControllers(args)s.initDiscoveryService(args)...
}
...gRPC服务启动
func (s *Server) Start(stop <-chan struct{}) error {go func() {s.grpcServer.Serve(s.GRPCListener)}()
}

pilot-discovery 的初始化主要在 pilot-discovery 的 init 方法和在 discovery 命令处理流程中调用的 bootstrap.NewServer 完成,关键步骤如下:

  • 创建 Kubernetes apiserver client(initKubeClient),可以在 pilot-discovery 的 discovery 命令的 kubeconfig flag 中提供文件路径,默认为空。

  • 读取 mesh 配置(initMeshConfiguration),包含 MixerCheckServerMixerReportServerProxyListenPortRdsRefreshDelayMixerAddress 等一些列配置,默认 mesh 配置文件"/etc/istio/config/mesh"。

  • 初始化与配置存储中心的连接(initConfigController 方法)对 Istio 做出的各种配置,比如 route rulevirtualservice 等,需要保存在配置存储中心(config store)内。

  • 配置与服务注册中心(service registry)的连接(initServiceControllers 方法)

  • 初始化 discovery 服务(initDiscoveryService),将 discovery 服务注册为 Config Controller 和 Service Controller 的 Event Handler,监听配置和服务变化消息。

  • 启动 gRPC Server 并接收来自 Envoy 端的连接请求。

  • 接收 sidecar 端的 xDS 请求,从 Config Controller、Service Controller 中获取配置和服务信息,生成响应消息发送给 sidecar。

  • 监听来自 Config Controller 、Service Controller 的变化消息,并将配置、服务变化内容通过 xDS 接口推送到 sidecar。

配置信息监控与处理

ConfigController 是 Pilot 实现配置信息监控与处理的核心,它关联的几个关键的结构体如下:

//go 语言,源码摘自 pilot-discovery,pilot-discovery 实现配置监听的关键部分// 用于存储 route rule、virtualservice 等流量配置信息
type ConfigStore interface {Schemas() collection.SchemasGet(typ resource.GroupVersionKind, name, namespace string) *ConfigList(typ resource.GroupVersionKind, namespace string) ([]Config, error)Create(config Config) (revision string, err error)Update(config Config) (newRevision string, err error)Delete(typ resource.GroupVersionKind, name, namespace string) errorVersion() stringGetResourceAtVersion(version string, key string) (resourceVersion string, err error)GetLedger() ledger.LedgerSetLedger(ledger.Ledger) error
}// 扩展了 ConfigStore 存储,并提供资源处理的注册函数,使用此函数注册后,资源变更会回调 handler 处理
type ConfigStoreCache interface {RegisterEventHandler(kind resource.GroupVersionKind, handler func(Config, Config, Event))Run(stop <-chan struct{})HasSynced() bool
}//controller 实现了 ConfigStore 接口和 ConfigStoreCache 接口
type controller struct {client *Clientqueue  queue.Instancekinds  map[resource.GroupVersionKind]*cacheHandler
}type Task func() error// controller 的 queue 的类型,包装了 Task 任务
type Instance interface {Push(task Task)Run(<-chan struct{})
}//initServiceControllers 下的 kubernets 下的 Controller ,由 initKubeRegistry 创建
func NewController(client kubernetes.Interface, options Options) *Controller {c := &Controller{client:                     client,queue:                      queue.NewQueue(1 * time.Second),...}...registerHandlers(c.services, c.queue, "Services", c.onServiceEvent)

ConfigController 用于处理 Istio 流控 CRD, 如 VirtualServiceDestinationRule 等。

  • ConfigStore 对象利用 client-go 库从 Kubernetes 获取 RouteRuleVirtualService 等 CRD 形式存在控制面信息,转换为 model 包下的 Config 对象,对外提供 GetListCreateUpdateDelete 等 CRUD 服务。

  • ConfigStoreCache 则主要扩展了:注册 Config 变更事件处理函数 RegisterEventHandler 、开始处理流程的 Run 方法。

Pilot 中,目前实现了 ConfigStoreCachecontroller 主要有以下五种:

  • crd/controller/controller.go

  • serviceregistry/mcp/controller.go

  • kube/gateway/controller.go

  • kube/ingress/controller.go

  • memory/controller.go

其中比较关键的是 crd controller。CRD 是 CustomResourceDefinition 的缩写 ,CRD Contriller 利用 SharedIndexInformer 实现对 CRD 资源的 list/watch。将 AddUpdateDelete 事件涉及到的 CRD 资源对象封装为一个 Task ,并 push 到 ConfigControllerqueue 里,queue 队列始终处于监听状态,只要队列中有内容,就会回调 task 函数执行。关键代码的实现如下:

//go 语言,源码摘自 pilot-discovery,pilot-discovery 实现配置监听的关键部分,接上一段代码中的 registerHandlersfunc registerHandlers(informer cache.SharedIndexInformer, q queue.Instance, otype string,handler func(interface{}, model.Event) error) {informer.AddEventHandler(cache.ResourceEventHandlerFuncs{AddFunc: func(obj interface{}) {...q.Push(...)...},UpdateFunc: func(old, cur interface{}) {...q.Push(...)...},DeleteFunc: func(obj interface{}) {...q.Push(...)...},})
}//queue 的实现,始终等待执行 task
func (q *queueImpl) Run(stop <-chan struct{}) {...for {if len(q.tasks) == 0 {return}task, q.tasks = q.tasks[0], q.tasks[1:]task()}
}

小结

本节为大家介绍了 Pilot 的架构和基本实现,后面我们将为大家介绍 istiod 中其他两个组件 Citadel 和 Galley。

本书由阿里云高级技术专家王夕宁撰写,详细介绍 Istio 的基本原理与开发实战,包含大量精选案例和参考代码可以下载,可快速入门Istio开发。基于 Istio 1.4 版本,图书为彩印。Gartner认为,2020年服务网格将成为所有领先的容器管理系统的标配技术。本书适合所有对微服务和云原生感兴趣的读者,推荐大家对本书进行深入的阅读。

也欢迎大家关注 ServiceMesher 社区联合编写的 Istio Handbook:https://github.com/servicemesher/istio-handbook/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/310482.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据结构与算法--二叉查找树实现原理

二叉查找树 二叉树的一个重要应用就是他在查询中的使用&#xff0c;假设书中每个节点存储一项数据。在我们的案例中&#xff0c;任意复杂的项在java中都容易处理&#xff0c;但为了简单还是假设都是整数。还假设他们都是不重复的整数&#xff0c;使二叉树称为二叉查找树的性质…

[C++STL]C++实现queue容器适配器

代码如下: #include <iostream> #include <deque>using namespace std;template<typename T,typename Con deque<T>> class Queue { public:Queue(){}void Push(const T & x){_c.push_back(x);}void Pop(){_c.pop_front();}T &Back(){return…

当模板方法遇到了委托函数,你的代码又可以精简了

现如今当你翻看一些开源项目源码的时候&#xff0c;你会发现现在到处充斥着委托函数&#xff0c;如Func,Action,Predicate&#xff0c;确实现在的C#在函数式编程 的路上越来越成为主流&#xff0c;越来越显示威力&#xff0c;曾经的一些经典设计模式写法&#xff0c;在函数式下…

数据结构与算法--面试必问AVL树原理及实现

数据结构与算法–AVL树原理及实现 AVL&#xff08;Adelson-Velskii 和landis&#xff09;树是带有平衡条件的二叉查找树&#xff0c;这个平衡条件必须容易实现&#xff0c;并且保证树的深度必须是O(logN)。因此我们让一棵AVL树中每个节点的左子树和右子树的高度最多相差1&…

MySQL8.0新特性

在这之前Mysql的版本是5.7&#xff0c;也是目前使用最广泛的一个版本。现在新版本跳过了6和7直接来到了8&#xff0c;那么V6和V7版本去哪里了呢&#xff1f;比较靠谱的说法是v6用作了内部的其他用途而v7的话是因为mysql有个产品叫做clusterdb他有7这个版本&#xff0c;所以这个…

数据结构与算法--B树原理及实现

B树 前几篇文中讨论的数据结构我们都是假设所有的数据都存储在计算机的主存中。可说总要那么海量的数据需要通过个中数据结构去存储&#xff0c;我们不可能有这么多内存区存放这些数据。那么意味着我们需要将他们放磁盘。所以这个时候范问时间复杂度O决定了他是否能适合存储磁盘…

[C++STL]C++实现priority_queue容器适配器

代码如下: #pragma once #include <iostream> #include <vector> using namespace std;template<typename T> struct Less {bool operator()(const T &a, const T &b){return a < b;} };template<typename T> struct Greater {bool operat…

为什么要用内插字符串代替string.format

知道为什么要用内插字符串&#xff0c;只有踩过坑的人才能明白&#xff0c;如果你曾今使用string.format超5个以上占位符&#xff0c;那其中的痛苦我想你肯定是能够共鸣的。一&#xff1a;痛苦经历先上一段曾今写过的一段代码&#xff0c;大家来体会一下&#xff1a;LogHelper.…

SpringCloud Alibaba 框架下公司架构图

上一篇&#xff1a;Docker容器实战思维 下一篇&#xff1a;分布式事务理论模型

C++ 实现堆

代码如下(小根堆): #include <iostream> #include <assert.h> using namespace std;//小根堆,堆排序从大到小排 class Heap { public:Heap():data(nullptr), size(0), capacity(0) {}~Heap(){delete[] data;data nullptr;size 0;capacity 0;}/*void Swap(int *…

iPhone上运行Linux也要来了

FOSSBRTES 报道称&#xff0c;用户很快将可以通过双启动功能&#xff0c;像在 Android 设备上那样在 iPhone 上运行 Linux。目前&#xff0c;iOS 越狱极客、开发人员 Raffaele 以及 mcg29 已在其 Github 页面上发布了详细说明&#xff0c;详细介绍了如何双启动 64 位 iOS 设备。…

数据结构与算法--图论,最短路算法,拓扑排序算法

图论若干定义 图&#xff08;graph&#xff09;G&#xff08;V,E&#xff09;由定点vertex的集合V&#xff0c; 和边edge的集合E组成。每一条边都是一个点对点&#xff08;v&#xff0c;w&#xff09;&#xff0c;其中 v,w 属于V集合的子集 如果点对点 是有序的&#xff0c;那…

谁说.NET不适合搞大数据、机器学习和人工智能

SciSharp StackSciSharp STACK: https://scisharp.github.io/SciSharp/基于.NET的开源生态系统&#xff0c;用于数据科学、机器学习和AI。SciSharp将所有主要的ML/AI框架从Python引入.NET.特点为.NET开发者.NET开发者使用他们所了解和喜爱的工具可以最高效的工作。我们的使命是…

C++ 泛型编程 实现红黑树RBTree

代码如下: #include <iostream> #include <ctime> using namespace std;enum COLOR {BLACK,RED };template<typename T> struct RBTreeNode {RBTreeNode<T> * _parent;RBTreeNode<T> * _left;RBTreeNode<T> * _right;T _val;COLOR _color…

数据结构与算法--图论-深度优先搜索及其应用

深度优先搜索 深度优先搜索&#xff08;depth-first search&#xff09; 是对先序遍历&#xff08;preorder traversal&#xff09;的推广&#xff0c;我们从某个顶点v开始处理v&#xff0c;然后递归的遍历所有与v邻接顶点。如果这个过程是对一棵树进行&#xff0c;那么&#…

.NET Core技术研究-主机

前一段时间&#xff0c;和大家分享了 ASP.NET Core技术研究-探秘Host主机启动过程但是没有深入说明主机的设计。今天整理了一下主机的一些知识&#xff0c;结合先前的博文&#xff0c;完整地介绍一下.NET Core的主机的设计和构建启动过程。一、什么是主机主机是一个封装了应用资…

数据结构与算法--贪婪算法

贪婪算法 贪婪算法分阶段地工作。在每个阶段&#xff0c;可以认为所做决定是最好的&#xff0c;而不考虑将来的后果。通常这意味着选择的是某个局部最优。这种“当前能获得的最优就拿”的策略是这类算法的名字来源。当算法终止时候&#xff0c;我们希望的到累积的局部最优解就…

[C++STL]C++ 实现map容器和set容器

代码如下: #pragma once #include <iostream> using namespace std;enum COLOR {BLACK, RED };template<class V>//迭代器声明&#xff0c;定义在后面 struct RBTreeIterator;template<typename V> struct RBTreeNode {RBTreeNode<V> * _parent;RBTre…

多角度让你彻底明白yield语法糖的用法和原理及在C#函数式编程中的作用

如果大家读过dapper源码&#xff0c;你会发现这内部有很多方法都用到了yield关键词&#xff0c;那yield到底是用来干嘛的&#xff0c;能不能拿掉&#xff0c;拿掉与不拿掉有多大的差别&#xff0c;首先上一段dapper中精简后的Query方法&#xff0c;先让大家眼见为实。private s…

C++泛型编程实现哈希表(闭散列---线性探测)

代码如下: #include <iostream> #include <vector> using namespace std;enum STATE {EXIST,DELETE,EMPTY };template<typename K,typename V> struct HashNode {pair<K, V> _kv;STATE _state EMPTY; };template<typename K,typename V> class…