这么做钓鱼网站/想做一个网站

这么做钓鱼网站,想做一个网站,wordpress搭建tag页面,建设个人网站需要多少钱张海东, ‍多点生活(成都)云原生开发工程师。本篇主要介绍 Pilot 源码中的 ServiceEntryStore 及其推送 xDS 的流程。本文为 Istio Pilot 源码分析系列的第二篇文章。Istio Pilot 源码分析(一)了解了 Pilot 源码的基本…

张海东, ‍多点生活(成都)云原生开发工程师。

本篇主要介绍 Pilot 源码中的 ServiceEntryStore 及其推送 xDS 的流程。

本文为 Istio Pilot 源码分析系列的第二篇文章。

Istio Pilot 源码分析(一)

了解了 Pilot 源码的基本结构和启动流程之后,我们可以深入探索 Pilot 究竟是怎么下发 xDS 协议的,以及协议的生成逻辑。相信大家都会有这些疑问:控制面与数据面详细的交互过程是什么?到底什么时候才会增量推送?增量推送判断的逻辑是什么?非 Kubernetes 原生的服务(如存在于虚拟机的服务、 Dubbo 服务等)到底是怎么注册并且经过一系列转化下发至数据面的?

带着这些问题,开始我们今天对 Pilot 的探索。

注:本文基于 istio release-1.7 分支分析,其他版本的代码结构会有所不同。

ServiceEntryStore

在多点落地 ServiceMesh 的过程中,大量的用到了 ServiceEntry ,每一个 Dubbo 服务都会映射一个 ServiceEntry 创建在 Kubernetes 里。 ServiceEntry 的作用就是将集群外部的服务注册到 Pilot 中,再统一由 ServiceController 进行管理。相应的,管理外部服务实例的对象为 WorkloadEntry , ServiceEntry 可以通过 LabelSelector 筛选出自身对应的实例。

ServiceEntry 是作为 CR (Custome Resource) 保存在 Kubernetes 集群里的(也可以通过 MCP 服务直接发送给 Pilot ),暂时只讨论在集群中创建 CR 的情况。在上一篇源码分析中我们介绍到, Pilot 是通过 ConfigController 来监听创建在集群中的 CR 的, ServiceEntry 也不例外,保存这些 CR 的 ConfigStore 会被转化为 ServiceEntryStore 中的 store (转化的详情见上一篇源码分析),这就是最终 Pilot 存储 ServiceEntry 的地方。当监听的资源推送更改的事件时,会触发 ServiceEntryStore 对应的 handler 处理后续的流程。

我们先来看一下 ServiceEntryStore 的结构和它提供的方法:

// istio/pilot/pkg/serviceregistry/serviceentry/servicediscovery.go:61
// ServiceEntryStore communicates with ServiceEntry CRDs and monitors for changes
type ServiceEntryStore struct {XdsUpdater model.XDSUpdater  // 用来接收 EnvouXdsServer 的接口,主要用来 Push 相应的 xDS 更新请求store      model.IstioConfigStore // 保存 ServiceEntry 实例的地方storeMutex sync.RWMutex  // 读写 store 时需要的锁// 以 hostname/namespace 以及类型(是服务还是实例)等作为索引的服务实例表instances map[instancesKey]map[configKey][]*model.ServiceInstance// seWithSelectorByNamespace 保存了每个 namespace 里所有的 ServiceEntry,也是作为一个索引供 handler 使用seWithSelectorByNamespace map[string][]servicesWithEntryrefreshIndexes            bool...
}

可以看到除了 XdsUpdater 和 store 两个必须的结构外,其余大部分都是些资源的缓存和索引(索引键不同),为后续 handler 处理事件时提供便利。除了结构,还需要关注两个比较重要的 handler :

// WorkloadEntry 变化时的处理逻辑
func (s *ServiceEntryStore) workloadEntryHandler(old, curr model.Config, event model.Event) {}
// ServiceEntry 变化时的处理逻辑
func (s *ServiceEntryStore) serviceEntryHandler(old, curr model.Config, event model.Event) {}

这两个 handler 的业务逻辑后文中再详细讨论,先来回忆下 ServiceEntryStore 的初始化流程:

img

在 Server 初始化 ServiceController 的时候,通过调用 NewServiceDiscovery() 方法初始化 ServiceEntryStore ,这里除了将 EnvoyXdsServer 和 IstioConfigStore 与 ServiceEntryStore 关联起来外,最重要的就是向 ConfigController 注册了 ServiceEntry 和 WorkloadEntry 的事件 Handler:

func NewServiceDiscovery(configController model.ConfigStoreCache, store model.IstioConfigStore, xdsUpdater model.XDSUpdater) *ServiceEntryStore {s := &ServiceEntryStore{XdsUpdater:            xdsUpdater,store:                 store,ip2instance:           map[string][]*model.ServiceInstance{},instances:             map[instancesKey]map[configKey][]*model.ServiceInstance{},workloadInstancesByIP: map[string]*model.WorkloadInstance{},refreshIndexes:        true,}if configController != nil {configController.RegisterEventHandler(gvk.ServiceEntry, s.serviceEntryHandler)configController.RegisterEventHandler(gvk.WorkloadEntry, s.workloadEntryHandler)}return s
}

这样在 ConfigController 监听到资源变化的时候,就会调用 serviceEntryHandler 和 workloadEntryHandler 来处理事件了。这两个 handler 的目的都是向 EnvoyXdsServer 推送相应的 xDS 资源变化。

workloadEntryHandler

首先来分析服务实例 WorkloadEntry 的更新是如何下发 xDS 的:

img

seWithSelectorByNamespace 和 instances 如上述 ServiceEntryStore 结构介绍中的注释,前者缓存了各个 namespace 中所有的 ServiceEntry ,后者则是所有服务节点 WorkloadEntry 的缓存。

当有新的 WorkloadEntry 变化时,先从 seWithSelectorByNamespace 中读取同一 namespace 中的 ServiceEntry ,遍历它们并与 WorkloadEntry 的 Label 进行比对,确定是关联的服务后,依据获取的服务创建 ServiceInstance 。 ServiceInstance 是 Pilot 抽象出的描述具体服务对应实例的结构:

type ServiceInstance struct {Service     *Service       `json:"service,omitempty"`ServicePort *Port          `json:"servicePort,omitempty"`Endpoint    *IstioEndpoint `json:"endpoint,omitempty"`
}

创建了新的 ServiceInstance 后,需要及时更新实例的索引表 s.instances :

if event != model.EventDelete {s.updateExistingInstances(key, instances)
} else {s.deleteExistingInstances(key, instances)
}

之后将新创建的 ServiceInstance 传入 ServiceEntryStore 专门处理 EDS 的函数 s.edsUpdate() 。在做进一步处理时,需要再刷新一遍索引表,调用 maybeRefreshIndexes() 避免其他协程的工作导致索引表更新不及时,完成后开启读锁,从服务实例索引表 s.Instances 中查找我们要处理的实例。如果是删除事件,先前更新索引表的时候已经删除了,所以这里是查不到 allInstances 的,直接向 EnvouXdsServer 发送删除 EDS 的请求。

// edsUpdate triggers an EDS update for the given instances
func (s *ServiceEntryStore) edsUpdate(instances []*model.ServiceInstance) {allInstances := []*model.ServiceInstance{}// Find all keys we need to lookupkeys := map[instancesKey]struct{}{}for _, i := range instances {keys[makeInstanceKey(i)] = struct{}{}}s.maybeRefreshIndexes()s.storeMutex.RLock()for key := range keys {for _, i := range s.instances[key] {allInstances = append(allInstances, i...)}}s.storeMutex.RUnlock()// This was a deleteif len(allInstances) == 0 {for k := range keys {_ = s.XdsUpdater.EDSUpdate(s.Cluster(), string(k.hostname), k.namespace, nil)}return}...
}

如果实例有更新则直接发送更新 EDS 的请求:

// edsUpdate triggers an EDS update for the given instances
func (s *ServiceEntryStore) edsUpdate(instances []*model.ServiceInstance) {...endpoints := make(map[instancesKey][]*model.IstioEndpoint)for _, instance := range allInstances {port := instance.ServicePortkey := makeInstanceKey(instance)endpoints[key] = append(endpoints[key],&model.IstioEndpoint{Address:         instance.Endpoint.Address,EndpointPort:    instance.Endpoint.EndpointPort,ServicePortName: port.Name,Labels:          instance.Endpoint.Labels,UID:             instance.Endpoint.UID,ServiceAccount:  instance.Endpoint.ServiceAccount,Network:         instance.Endpoint.Network,Locality:        instance.Endpoint.Locality,LbWeight:        instance.Endpoint.LbWeight,TLSMode:         instance.Endpoint.TLSMode,})}for k, eps := range endpoints {_ = s.XdsUpdater.EDSUpdate(s.Cluster(), string(k.hostname), k.namespace, eps)}
}

完整的 workloadEntryHandler() 代码如下:

func (s *ServiceEntryStore) workloadEntryHandler(old, curr model.Config, event model.Event) {wle := curr.Spec.(*networking.WorkloadEntry)key := configKey{kind:      workloadEntryConfigType,name:      curr.Name,namespace: curr.Namespace,}...s.storeMutex.RLock()// We will only select entries in the same namespaceentries := s.seWithSelectorByNamespace[curr.Namespace]s.storeMutex.RUnlock()// if there are no service entries, return now to avoid taking unnecessary locksif len(entries) == 0 {return}log.Debugf("Handle event %s for workload entry %s in namespace %s", event, curr.Name, curr.Namespace)instances := []*model.ServiceInstance{}for _, se := range entries {workloadLabels := labels.Collection{wle.Labels}if !workloadLabels.IsSupersetOf(se.entry.WorkloadSelector.Labels) {// Not a match, skip this onecontinue}instance := convertWorkloadEntryToServiceInstances(wle, se.services, se.entry)instances = append(instances, instance...)}if event != model.EventDelete {s.updateExistingInstances(key, instances)} else {s.deleteExistingInstances(key, instances)}s.edsUpdate(instances)
}

接下来就是 EnvoyXdsServer 来处理这次 EDS 的更新请求了。首先 EnvoyXdsServer 会判断此次 EDS 更新是全量下发还是增量下发,然后创建 PushRequest 发送至 EnvoyXdsServer 统一用来接收推送请求的 pushChannel 。

func (s *DiscoveryServer) EDSUpdate(clusterID, serviceName string, namespace string,istioEndpoints []*model.IstioEndpoint) error {inboundEDSUpdates.Increment()// 判断是否是全量下发fp := s.edsUpdate(clusterID, serviceName, namespace, istioEndpoints)s.ConfigUpdate(&model.PushRequest{Full: fp,ConfigsUpdated: map[model.ConfigKey]struct{}{{Kind:      gvk.ServiceEntry,Name:      serviceName,Namespace: namespace,}: {}},Reason: []model.TriggerReason{model.EndpointUpdate},})return nil
}

pushChannel 后续的处理流程和 EDS 是否增量更新将在下文讨论 EnvoyXdsServer 的时候再分析,这里不再赘述。

serviceEntryHandler

了解了 WorkloadEntry 的更新是如何处理之后,我们再来看下 serviceEntryHandler 是如何处理 ServiceEntry 的:

img

serviceEntryHandler 会将 ServiceEntry 转化为一组 Pilot 内部抽象的服务,每个不同的 Hosts 、 Address 都会对应一个 Service ,并且初始化一个名为 configsUpdated 的 map 来保存是否有 ServiceEntry 需要更新,以及创建了多个 slice 分别保存该新增、删除、更新和没有变化的服务:

func (s *ServiceEntryStore) serviceEntryHandler(old, curr model.Config, event model.Event) {cs := convertServices(curr)configsUpdated := map[model.ConfigKey]struct{}{}var addedSvcs, deletedSvcs, updatedSvcs, unchangedSvcs []*model.Service...
}

根据不同的事件类型,更新不同的 slice :

switch event {
case model.EventUpdate:os := convertServices(old)if selectorChanged(old, curr) {// Consider all services are updated.mark := make(map[host.Name]*model.Service, len(cs))for _, svc := range cs {mark[svc.Hostname] = svcupdatedSvcs = append(updatedSvcs, svc)}for _, svc := range os {if _, f := mark[svc.Hostname]; !f {updatedSvcs = append(updatedSvcs, svc)}}} else {addedSvcs, deletedSvcs, updatedSvcs, unchangedSvcs = servicesDiff(os, cs)}
case model.EventDelete:deletedSvcs = cs
case model.EventAdd:addedSvcs = cs
default:// this should not happenunchangedSvcs = cs
}

比较特别的是,当事件为更新事件时,会和老的 Service 列表进行比对。先看是否有某个服务的 Selector 发生了变化,如果发生了变化,需要将新老服务列表里的所有服务都加入到更新列表中。如果 Selector 没有发生变化,通过 serviceDiff() 挨个比对新老服务列表中的服务,对应保存至新增、删除、更新和未变化的 slice 中。

将服务归类后,把需要变化的服务都写入 configsUpdated 中:

for _, svcs := range [][]*model.Service{addedSvcs, deletedSvcs, updatedSvcs} {for _, svc := range svcs {configsUpdated[model.ConfigKey{Kind:      gvk.ServiceEntry,Name:      string(svc.Hostname),Namespace: svc.Attributes.Namespace}] = struct{}{}}
}

由于 serviceDiff() 只会比对 Service 结构,并不会对比 Endpoints 是否变化,所以当有 unchangedSvcs 时,可能需要对这些服务的 xDS 做增量更新(只更新 EDS ),也可能是全量更新。什么时候会全量更新呢?当服务的 Resolution 为 DNS 时(可以阅读文档了解 Resolution[1] ), Endpoint 的 address 都是全域名,需要更新 CDS 才行。

if len(unchangedSvcs) > 0 {// If this service entry had endpoints with IPs (i.e. resolution STATIC), then we do EDS update.// If the service entry had endpoints with FQDNs (i.e. resolution DNS), then we need to do// full push (as fqdn endpoints go via strict_dns clusters in cds).currentServiceEntry := curr.Spec.(*networking.ServiceEntry)oldServiceEntry := old.Spec.(*networking.ServiceEntry)if currentServiceEntry.Resolution == networking.ServiceEntry_DNS {if !reflect.DeepEqual(currentServiceEntry.Endpoints, oldServiceEntry.Endpoints) {// fqdn endpoints have changed. Need full pushfor _, svc := range unchangedSvcs {configsUpdated[model.ConfigKey{Kind:      gvk.ServiceEntry,Name:      string(svc.Hostname),Namespace: svc.Attributes.Namespace}] = struct{}{}}}}
}

当 unchangedSvcs 的 Resolution 为 STATIC 时,只需要增量的更新 EDS 即可:

if len(unchangedSvcs) > 0 && !fullPush {// IP endpoints in a STATIC service entry has changed. We need EDS update// If will do full-push, leave the edsUpdate to that.// XXX We should do edsUpdate for all unchangedSvcs since we begin to calculate service// data according to this "configsUpdated" and thus remove the "!willFullPush" condition.instances := convertInstances(curr, unchangedSvcs)key := configKey{kind:      serviceEntryConfigType,name:      curr.Name,namespace: curr.Namespace,}// If only instances have changed, just update the indexes for the changed instances.s.updateExistingInstances(key, instances)s.edsUpdate(instances)return
}

如果 configsUpdated 中有值,则需要做 fullPush ,先更新这些服务的 EDS ,再向 pushChannel 发送 fullPush 的 PushRequest :

if fullPush {// When doing a full push, for added and updated services trigger an eds update// so that endpoint shards are updated.var instances []*model.ServiceInstanceif len(addedSvcs) > 0 {instances = append(instances, convertInstances(curr, addedSvcs)...)}if len(updatedSvcs) > 0 {instances = append(instances, convertInstances(curr, updatedSvcs)...)}if len(unchangedSvcs) > 0 {currentServiceEntry := curr.Spec.(*networking.ServiceEntry)oldServiceEntry := old.Spec.(*networking.ServiceEntry)// Non DNS service entries are sent via EDS. So we should compare and update if such endpoints change.if currentServiceEntry.Resolution != networking.ServiceEntry_DNS {if !reflect.DeepEqual(currentServiceEntry.Endpoints, oldServiceEntry.Endpoints) {instances = append(instances, convertInstances(curr, unchangedSvcs)...)}}}s.edsUpdate(instances)// If service entry is deleted, cleanup endpoint shards for services.for _, svc := range deletedSvcs {s.XdsUpdater.SvcUpdate(s.Cluster(), string(svc.Hostname), svc.Attributes.Namespace, model.EventDelete)}pushReq := &model.PushRequest{Full:           true,ConfigsUpdated: configsUpdated,Reason:         []model.TriggerReason{model.ServiceUpdate},}s.XdsUpdater.ConfigUpdate(pushReq)
}

至此, ServiceEntryStore 是如何处理 ServiceEntry 和 WorkloadEntry 的逻辑就介绍完了。其余像 ServiceEntry 选择集群内的 Pods 、 Kubernetes 原生 Service 选择 WorkloadEntry 的用法读者感兴趣可以自行研究相关源码。

其余注册中心的处理逻辑如 kube 、 mcp 等可继续关注本系列的其他文章。读者也可以自行尝试走读分析:

// 相关源码目录
kube: pilot/pkg/serviceregistry/kube
mcp: pilot/pkg/serviceregistry/mcp

接下来我们介绍 Pilot Server 中的核心, EnvoyXdsServer 。

引用链接

[1] Resolution: https://istio.io/latest/docs/reference/config/networking/service-entry/#ServiceEntry-Resolution

云原生社区 Istio SIG

云原生社区是 ServiceMesher 的姊妹社区,扫码下面二维码,回复 0924 即可加入云原生社区 Istio SIG,和包括本文作者一起交流 Istio 经验。 

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

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

相关文章

Pytorch中的 torch.Tensor() 和 torch.tensor() 的区别

直接在搜索引擎里进行搜索,可以看到官方文档中两者对应的页面: 分别点击进去,第一个链接解释了什么是 torch.Tensor: torch.Tensor 是一个包含单一数据类型元素的多维矩阵(数组)。 正因为 torch.Tensor 只包…

leetcote34. 在排序数组中查找元素的第一个和最后一个位置

一:题目 二&#xff1a;上码&#xff08;暴力二分&#xff09; // class Solution { // public: // /** // 思路:1.首先这是一个升序的 那么相同的一定是会相连的// */// vector<int> searchRange(vector<int>& nums, int target) {// …

Git 图形化操作之合并提交记录

Git 图形化操作之合并提交记录独立观察员 2020 年 9 月 24 日目录1、显示日志2、合并提交记录3、推送合并的提交前言&#xff1a;当我们使用 Git 时&#xff0c;有时会遇到刚提交推送完一次修改&#xff0c;发现漏了该某处&#xff0c;只好又提交推送一次&#xff0c;这样在提交…

Pytorch中的 torch.as_tensor() 和 torch.from_numpy() 的区别

之前我写过一篇文章&#xff0c;比较了 torch.Tensor() 和 torch.tensor() 的区别&#xff0c;而这两者都是深拷贝的方法&#xff0c;返回张量的同时&#xff0c;会在内存中创建一个额外的数据副本&#xff0c;与原数据不共享内存&#xff0c;所以不受原数据改变的影响。 这里…

chrome禁止三方cookie,网站登录不了怎么办

背景新版chrome(80)浏览器默认屏蔽所有三方cookie已经不是什么新闻了&#xff0c;具体原因这里不去深究&#xff0c;有大量相关文章介绍&#xff0c;由于目前许多网站都依赖三方cookie&#xff0c;因此该特性的推出还是造成了一些的影响&#xff0c;比如收集用户信息的广告商&a…

leetcode69. x 的平方根

一:题目 二:上码 class Solution { public:/**思路:1.因为我们的 ans的平方 < x 那么我们就可以用二分法来做 不断缩小左右范围来确定 ans**/int mySqrt(int x) {int left 0; int right x;int ans 0;while (left < right) {long mid (right-left)/2 left;if (mid*…

初识ABP vNext(11):聚合根、仓储、领域服务、应用服务、Blob储存

点击上方蓝字"小黑在哪里"关注我吧聚合根仓储领域服务BLOB储存应用服务单元测试模块引用前言在前两节中介绍了ABP模块开发的基本步骤&#xff0c;试着实现了一个简单的文件管理模块&#xff1b;功能很简单&#xff0c;就是基于本地文件系统来完成文件的读写操作&…

leetcode367. 有效的完全平方数

一:题目 二:上码 class Solution { public:/**完全平方数:若一个数能表示成某个整数的平方的形式&#xff0c;则称这个数为完全平方数思路:1.我们将num先折半,因为它是某个整数的平方&#xff0c;而这个数的范围肯定不会超过num的一半2.那么这就相当于在[left,num/2]中查找某个…

跟我一起学.NetCore之文件系统应用及核心浅析

前言在开发过程中&#xff0c;肯定避免不了读取文件操作&#xff0c;比如读取配置文件、上传和下载文件、Web中html、js、css、图片等静态资源的访问&#xff1b;在配置文件读取章节中有说到&#xff0c;针对不同配置源数据读取由对应的IConfigurationProvider进行读取&#xf…

深度学习入门笔记(1)——导论部分

此笔记来源于 Sebastian Raschka 的 Introduction to Deep Learning 系列课程。 首先介绍的是传统的编程范式&#xff0c;假设我们想实现垃圾邮件识别的功能&#xff0c;传统的方法就是由程序员来找出垃圾邮件的规则并对其进行编程&#xff0c;得到一个垃圾邮件识别的程序。 机…

深度学习入门笔记(2)—— 感知器

最经典的神经元模型&#xff0c;从左到右依次是&#xff1a;输入、权重、加权和、阈值、输出。加权和又叫做 Net Input&#xff0c;符号为 z&#xff0c;当 z 的值大于阈值时输出 1&#xff0c;小于阈值时输出 0。 实现与门和或门&#xff0c;权重为 1&#xff0c;阈值分别为 1…

创建一个对象时,在一个类当中 静态代码块 和普通代码块构造方法 的顺序?

一:前言须知 普通代码块&#xff0c;在创建对象实例的时候&#xff0c;会被调用&#xff0c;每创建一次&#xff0c;就调用一次静态代码块&#xff0c;在类加载的时候执行&#xff0c;并且只会执行一次类加载的时机: 创建对象实例的时候&#xff08;new&#xff09;创建子类实…

ASP.NET Core 基于声明的访问控制到底是什么鬼?

从ASP.NET 4.x到ASP.NET Core&#xff0c;内置身份验证已从基于角色的访问控制(RBAC)转变为基于声明的访问控制(CBAC)。我们常用的HttpContext.User属性ASP.NET 4.0时代是IPrincipal类型&#xff0c;ASP.NETCore现在强化为ClaimsPrincipal类型。本文就一起来看看这难缠的、晦涩…

回溯的问题合集(Leetcode题解-Python语言)

78. 子集 class Solution:def subsets(self, nums: List[int]) -> List[List[int]]:ans []cur []def dfs(i):if i len(nums):ans.append(cur.copy())return# 包括 nums[i]cur.append(nums[i])dfs(i1)# 不包括 nums[i]cur.pop()dfs(i1)dfs(0)return ans要找出所有子集&a…

一个对象的创建流程

一:流程 加载Person类的信息,(也就是加载Person.class文件 只加载一次) 这个就是类加载的几个过程加载 ,将.class文件转化成二进制流加载到JVM的内存的方法区中&#xff0c;并在堆中生成一个Class对象验证准备解析初始化 该实例堆当中开辟空间 每个类的实例都会记得自己是由哪…

RabbitMq如何确保消息不丢失

上篇写了掌握Rabbitmq几个重要概念&#xff0c;从一条消息说起&#xff0c;这篇来总结关于消息丢失让人头痛的事情。网络故障、服务器重启、硬盘损坏等都会导致消息的丢失。消息从生产到消费主要结果以下几个阶段如下图。①生产阶段&#xff0c;生产者创建消息&#xff0c;经过…

LEETCODE PATTERNS Neetcode 刷题记录(Leetcode题解-Python语言)

LEETCODE PATTERNS 官网在这个链接&#xff0c;Neetcode 官网在这个链接 If input array is sorted then 遇到有序数组用二分或双指针 Binary searchTwo pointers If asked for all permutations/subsets then 求排列或子集用回溯 Backtracking If given a tree then 遇到树就用…

蓝桥杯-单词分析

一:题目 题目描述 小蓝正在学习一门神奇的语言&#xff0c;这门语言中的单词都是由小写英文字母组 成&#xff0c;有些单词很长&#xff0c;远远超过正常英文单词的长度。小蓝学了很长时间也记不住一些单词&#xff0c;他准备不再完全记忆这些单词&#xff0c;而是根据单词中哪…

.NET Core 使用 Consul 服务注册发现

Consul是一个用来实现分布式系统服务发现与配置的开源工具。它内置了服务注册与发现框架、分布一致性协议实现、健康检查、Key/Value存储、多数据中心方案&#xff0c;不再需要依赖其他工具&#xff0c;使用起来也较为简单。Consul官网&#xff1a;https://www.consul.io开源地…

蓝桥杯-成绩统计

一:题目 题目描述 小蓝给学生们组织了一场考试&#xff0c;卷面总分为 100 分&#xff0c;每个学生的得分都是一个 0 到 100 的整数。 如果得分至少是 60 分&#xff0c;则称为及格。如果得分至少为 85 分&#xff0c;则称为优秀。 请计算及格率和优秀率&#xff0c;用百分数…