关于服务发现:主流服务注册发现框架
须要理解以下三种支流测服务发现框架有哪些性能作用?各自有什么劣势?实现的原理是什么? ConsulEtcdZooKeeper
须要理解以下三种支流测服务发现框架有哪些性能作用?各自有什么劣势?实现的原理是什么? ConsulEtcdZooKeeper
Consul 简介 Consul 是 HashiCorp 公司旗下的一个服务网格解决方案,提供了一个功能齐全的管制立体,具备服务发现、配置和分段性能。这些性能中的每一项都能够依据须要独自应用,也能够一起应用来构建一个残缺的服务网格。在微服务框架中,常常会应用 Consul 来实现服务注册和发现。 Consul 的服务注册和发现性能有以下特点: 基于 Agent,分为 Client 和 Server 两种角色Server 反对分布式、高可用集群部署,反对多集群。集群采纳 RAFT 算法,Gossip 协定服务注册反对 Agent 和 HTTP API 两种形式服务发现反对 DNS 和 HTTP API 两种形式反对服务的健康检查自带 Web UI F5的 MSDA 和 MSRA 工具目前均已反对对接 Consul,接下来就介绍应用办法,前面要用到的 rpm 包能够在以下地址下载。 MSDA下载地址: https://github.com/ChinaModer... MSRA下载地址: https://github.com/ChinaModer... F5 对接 Consul 实现服务发现 咱们先搭建一个简略的 Consul 注册核心,并注册一个 web 服务,以后该服务共注册有两个实例。 接下来咱们在 F5 上装置 msda-consul 的 iApp.LX 安装包,在下图所示的地位导入 rpm 文件。 接下来应用安装包生成的模板新建一个 iApp.LX 利用。 输出一个 iApp.LX 利用名字,模板抉择 msdaconsul。 ...
Apache APISIX 是一个动静、实时、高性能的云原生 API 网关,提供了负载平衡、动静上游、灰度公布、服务熔断、身份认证、可观测性等丰盛的流量治理性能。作为云原生 API 网关,Apache APISIX 也集成了多种服务发现的能力,本文将为您展现在 Apache APISIX 中如何配置 CoreDNS。 背景信息在传统的物理机和虚拟机部署中,各个服务之间的调用能够通过固定 IP + 端口的形式进行。随着云原生时代的到来,企业业务的部署更偏向于云原生容器化。然而在容器化环境中,服务实例的启动和销毁是十分频繁的,如果通过运维人员手动保护不仅工作量大,而且成果也欠佳。因而须要一种机制能够自动检测服务状态,当服务地址呈现变更时,动静绑定新的地址。服务发现机制应运而生。 服务发现简介服务发现机制能够分为两局部: 服务注册核心: 存储服务的主机和端口信息。如果某个容器对外提供计算平均值的服务,咱们应用 average 的服务名作为惟一标识符,那么在服务注册核心就会以键值对(average:192.168.1.21)的形式存储。 服务发现:容许其余用户发现服务注册阶段存储的信息。分为客户端发现模式和服务端发现模式。客户端服务发现模式 在应用客户端发现模式时,客户端通过查问服务注册核心的存储信息,获取可用服务的理论网络地址后,通过负载平衡算法抉择一个可用的服务实例,并将申请发送至该服务。 长处:架构简略,扩大灵便,不便实现负载平衡性能。 毛病:重客户端,强耦合,有肯定开发成本。 客户端发现模式实现逻辑如下: 新服务启动时,被动向注册核心注册,服务注册核心会存储新服务的服务名和地址;当客户端须要这个服务时,会应用服务名向服务注册核心发动查问;服务注册核心返回可用的地址,客户端再依据具体的算法抉择其中一个发动调用。在这个过程中,除了服务注册,服务发现的工作根本由客户端独立实现,注册核心和服务端的地址对客户端也是齐全可见的。 服务端服务发现模式 客户端向 Load Balancer 发送申请,Load Balancer 依据客户端的申请查问服务注册核心,找到可用的服务后转发申请到该服务上,和客户端服务发现模式一样,服务都须要在注册核心进行服务注册和登记。 长处:服务的发现逻辑对客户端是通明的。 毛病:须要额定部署和保护负载均衡器。 服务端发现模式实现逻辑如下: 新服务启动时,被动向注册核心注册,服务注册核心会存储新服务的服务名和地址;当客户端须要某个服务时,会应用服务名向负载均衡器发动查问;负载均衡器依据客户端申请的服务名,代理客户端向服务注册核心发动申请;负载均衡器取得返回的地址后,依据具体的算法抉择其中一个发动调用。应用 CoreDNS 的劣势CoreDNS 是一个用 Go 语言编写的开源 DNS 服务器,因为它的灵活性和可扩展性,罕用于多容器环境中的 DNS 服务和服务发现。CoreDNS 建设在 Caddy 这个 HTTP/2 Web 服务器之上,实现了一个插件链的架构,将很多 DNS 相干的逻辑都形象成了一层一层的插件,实现起来更灵便和易扩大,用户抉择的插件会被编译到最终的可执行文件中,运行效率也十分高。CoreDNS 是首批退出 CNCF(云原生计算基金会)并且是曾经毕业的云原生开源我的项目,也是 Kuberneters 中默认的 DNS 服务。 相比于常见的服务发现框架(Zookeeper 和 Consul),CoreDNS 实现服务发现有哪些劣势呢? 服务发现的原理和计算机网络中重要的基础设施—— DNS 域名零碎比拟类似,DNS 域名零碎把很少变动的域名与常常变动的服务器 IP 地址进行绑定,而服务发现机制则是把很少变动的服务名与服务地址绑定。由此咱们能够借由 DNS 实现相似服务注册核心的性能,只须要将 DNS 中存储的域名转换为服务名即可。因为许多计算机内置了 DNS 性能,所以咱们只须要在原有 DNS 零碎上批改配置就能够了,不须要做太多额定的事件。 ...
微服务架构中,大型简单的零碎按性能或者业务需要垂直切分成更小的子系统,这些子系统以独立部署的子过程存在,它们之间通过网络调用进行通信。这些独立部署的服务如何发现对方成为了首先要解决的问题,所以在微服务架构中往往都会存在一个中心化的注册核心。 Spring 作为 Java 生态中最外围的开发框架,从 Spring MVC 到 Spring Boot 继续一直解放着 Java 开发者的生产力,而 Spring Cloud 是 Spring 面向云原生时代微服务架构给出的答案。 在 Spring Cloud 中,Eureka 就表演了注册核心的角色。Eureka 是一款由 Netflix 开源,应用 Java 语言编写的注册核心服务,其在 Netflix 的基础设施中扮演着重要角色。 APISIX 作为云原生微服务 API 网关,在设计之初就曾经反对了 etcd 作为服务发现,同时也反对 consul 、nacos 、eureka 而 Apache APISIX 作为业界当先的微服务网关,对 Eureka 提供了原生反对。本文将会应用 Spring Cloud 演示我的项目作为案例,为大家展现 Apache APISIX 对接 Eureka 服务发现的次要性能及个性。 筹备阶段本次演示应用 Spring 官网提供的 spring-cloud-netflix 教程作为示例,该教程中提供了应用 SpringBoot 启动的 Eureka Server 作为 Spring Cloud 的注册核心,咱们也应用雷同的形式来启动用于演示的 Eureka 服务端。该我的项目地址请拜访 spring-cloud-samples/eureka。 ...
文|林育智(花名:源三 ) 蚂蚁团体高级专家 专一微服务/服务发现相干畛域 校对|李旭东 本文 8624 字 浏览 18 分钟 |引 言|服务发现是构建分布式系统的最重要的依赖之一, 在蚂蚁团体承当该职责的是注册核心和 Antvip,其中注册核心提供机房内的服务发现能力,Antvip 提供跨机房的服务发现能力。 本文探讨的重点是注册核心和多集群部署状态(IDC 维度),集群和集群之间不波及到数据同步。 PART. 1 背 景回顾注册核心在蚂蚁团体的演进,大略起始于 2007/2008 年,至今演进超过 13 年。时至今日,无论是业务状态还是本身的能力都产生了微小的变动。 简略回顾一下注册核心的历代倒退: V1:引进淘宝的 configserver V2:横向扩大 从这个版本开始,蚂蚁和阿里开始独立的演进,最次要的差别点是在数据存储的方向抉择上。蚂蚁抉择了横向扩大,数据分片存储。阿里抉择了纵向扩大,加大 data 节点的内存规格。 这个抉择影响到若干年后的 SOFARegistry 和 Nacos 的存储架构。 V3 / V4:LDC 反对和容灾 V3 反对 LDC 单元化。 V4 减少了决策机制和运行时列表,解决了单机宕机时须要人工染指解决的问题,肯定水平上晋升高可用和缩小运维老本。 V5:SOFARegistry 前四个版本是 confreg,17 年启动 V5 我的项目 SOFARegistry,指标是: 1.代码可维护性:confreg 代码历史包袱较重 大量模块应用 guice 做依赖治理,但大部分模块是动态类交互,不容易拆散外围模块和扩大模块,不利于产品开源。客户端与服务端的交互模型嵌套简单,了解老本极高且对多语言不敌对。2.运维痛点:引入 Raft 解决 serverlist 的保护问题,整个集群的运维包含 Raft,通过 operator 来简化。 3.鲁棒性:在一致性 hash 环减少多节点备份机制(默认 3 正本),2 正本宕机业务无感。 ...
1.启动一个带ACL 控制的Agent首先,从这个网址下载consul,解压后发现就是个可执行文件,如果不可以执行,chmod +x consul 一下。 为了试验Consul较多的功能,这里我们打算启用一个dev模式,带ACL控制的Consul代理。配置文件config.json如下 { "datacenter":"dc1", "primary_datacenter":"dc1", "data_dir":"/opt/consul/data/", "enable_script_checks":false, "bind_addr":"127.0.0.1", "node_name":"consul-dev", "enable_local_script_checks":true, "log_file":"/opt/consul/log/", "log_level":"info", "log_rotate_bytes":100000000, "log_rotate_duration":"24h", "encrypt":"krCysDJnrQ8dtA7AbJav8g==", "acl":{ "enabled":true, "default_policy":"deny", "enable_token_persistence":true, "tokens":{ "master":"cd76a0f7-5535-40cc-8696-073462acc6c7" } }}下面是参数说明: datacenter 此标志表示代理运行的数据中心。如果未提供,则默认为“dc1”。 Consul拥有对多个数据中心的一流支持,但它依赖于正确的配置。同一数据中心中的节点应在同一个局域网内。primary_datacenter: 这指定了对ACL信息具有权威性的数据中心。必须提供它才能启用ACL。bind_addr: 内部群集通信绑定的地址。这是群集中所有其他节点都应该可以访问的IP地址。默认情况下,这是“0.0.0.0”,这意味着Consul将绑定到本地计算机上的所有地址,并将第一个可用的私有IPv4地址通告给群集的其余部分。如果有多个私有IPv4地址可用,Consul将在启动时退出并显示错误。如果指定“[::]”,Consul将通告第一个可用的公共IPv6地址。如果有多个可用的公共IPv6地址,Consul将在启动时退出并显示错误。 Consul同时使用TCP和UDP,并且两者使用相同的端口。如果您有防火墙,请务必同时允许这两种协议。advertise_addr: 更改我们向群集中其他节点通告的地址。默认情况下,会使用-bind参数指定的地址.server: 是否是server agent节点。connect.enabled: 是否启动Consul Connect,这里是启用的。node_name:节点名称。data_dir: agent存储状态的目录。enable_script_checks: 是否在此代理上启用执行脚本的健康检查。有安全漏洞,默认值就是false,这里单独提示下。enable_local_script_checks: 与enable_script_checks类似,但只有在本地配置文件中定义它们时才启用它们。仍然不允许在HTTP API注册中定义的脚本检查。log-file: 将所有Consul Agent日志消息重定向到文件。这里指定的是/opt/consul/log/目录。log_rotate_bytes:指定在需要轮换之前应写入日志的字节数。除非指定,否则可以写入日志文件的字节数没有限制log_rotate_duration:指定在需要旋转日志之前应写入日志的最长持续时间。除非另有说明,否则日志会每天轮换(24小时。单位可以是"ns", "us" (or "µs"), "ms", "s", "m", "h", 比如设置值为24hencrypt:用于加密Consul Gossip 协议交换的数据。在启动各个server之前,配置成同一个UUID值就行,或者你用命令行consul keygen 命令来生成也可以。acl.enabled: 是否启用acl.acl.default_policy: “allow”或“deny”; 默认为“allow”,但这将在未来的主要版本中更改。当没有匹配规则时,默认策略控制令牌的行为。在“allow”模式下,ACL是黑名单:允许任何未明确禁止的操作。在“deny”模式下,ACL是白名单:阻止任何未明确允许的操作.acl.enable_token_persistence: 可能值为true或者false。值为true时,API使用的令牌集合将被保存到磁盘,并且当代理重新启动时会重新加载。acl.tokens.master: 具有全局管理的权限,也就是最大的权限。它允许操作员使用众所周知的令牌密钥ID来引导ACL系统。需要在所有的server agent上设置同一个值,可以设置为一个随机的UUID。这个值权限最大,注意保管好。接着我们以dev模式启动agent。 ./consul agent -dev -config-file ./config.json 启动完之后,你会发现出现下面的错误这个错误是没有设置agent-token造成的,agent-token主要用于客户端和服务器执行内部操作.比如catalog api的更新,反熵同步等。 ...
本篇只讲下仅仅由于微服务后,需要改变增加的东西。可见:服务发现。拓扑。问题定位。监控。可控:全链路故障演练/压测。配置中心/全流程部署(部署会频繁,扩缩容频繁)本文服务发现问题定位/拓扑:https://segmentfault.com/a/11...全流程部署/配置中心/监控:https://segmentfault.com/a/11...全链路故障演练/压测:https://segmentfault.com/a/11...更多工程:https://segmentfault.com/a/11... 服务发现过程服务发现和注册文章:https://www.nginx.com/blog/se...。这里只讲下公司的应用方案 服务发现背景:替换原有Thrift要配置所有IP一个新服务,人工服务注册:创建一个服务发现节点,路由规则配置;流量调度;人工服务摘除;1.调用方每台机器上有agent.sdk.兜底文件,实时文件,访问disf先本地实时文件,兜底ip 兜底文件的产生:1.1代码扫描 与平台配置结果比对,校验1.2.编译后生成output/__naming__/__self.json1.3.odin构建到disf平台取配置作为兜底文件2.部署包根据部署集群,部署自己集群的__naming__/兜底ps:打包时会额外做:第一次打包生成__naming__目录,兜底包含所有ip,更新推方式更改扫描检查配置,第二次生成.deploy (部署节点信息,odin根据不同节点打包生成每个集群的ip)实时文件产生agent启动时。odin部署系统上线中,启动前会上报集群。到disf-agent。取配置。生成到/home/xiaoju/.service/disf中配置变更时。在平台操作,文件直接推送到agent。常态运行时,agent定期扫描文件版本上报平台,平台校验后下发版本异常文件到agentagent与conf长连接(grpc)交互过自动摘除/发现摘除:php类点多服务公用一个端口,一个unregester会导致所有都。上线时会大量的unregester或者exist出现抖动,机器挂掉不会自动摘除,只是返回一个ip列表(只有服务发现。dirpc负责负载均衡,健康检查,自动摘除)发现:不自动发现,起了服务不一定要正式到线上xrpc功能内置服务发现 (直接获取endpoint等) 支持多语言多协议 标准化日志输出&监控,标准化服务调用规范(统一提供sdk) 容错机制(故障摘除,过载保护,负载均衡) 代码1.获取endpoint信息,生成sign等初始2.server管理服务器,负载均衡,过载保护以逻辑机房为管理力度,minHealthyRatio等配置,ip/port列表,状态,balancer filter 白名单,黑名单,是否跨机房故障摘除 对节点采取投票,访问失败+1,访问成功-1,票数最低为0;每个节点的票数代表了其健康度,值越大说明越不健康,越小越健康; 投票数超过阈值即被认定为不健康,默认指导配置为10,即一个节点的投票数大于10则被认定为不健康。 流量调度只在健康节点进行选择,即非健康节点则认为被故障摘除。 另外节点被标记为非健康的时间超过冷却时长后,下次重新生成健康列表时,则将其当作健康节点对待,即故障恢复。默认指导值为60s。过载保护(只是防止可用节点太少,可用节点被打挂) 防止大量节点被标记为非健康后,流量打到集中的下游上,设置一个最低健康比例,当健康节点数的占比低于最低比例时,按最低比例挑选健康度最好的节点,构建健康列表,即最小可用度。随着业务的不断变迁,理想情况下最小可用度要能根据全链路容量压测进行自动适应。但根据滴滴目前的现状,资源管理还比较粗放,前期这个值可以设置的相对保守。当前最小可用度默认指导配置为0.67,即保证至少有2/3的节点可用,也即最多可剔除对1/3故障节点的访问。 负载均衡只支持在健康节点之间挑选,当最小可用度不足时,通过非健康节点补充。目前已支持Random、Hash两种调度策略,RR暂不支持。3.send4.根据是否成功 vote php是记到apcu(缓存中),不支持就无法投票。这个各自调用方投票记录在不同地方,各自判断,各自摘除。无法相互获取到其他的投票结果。5.记录log servicemesh概述架构上分为数据平面和控制平面两个部分,其中数据平面负责代理微服务之间的通信,具体包含RPC通信、服务发现、负载均衡、降级熔断、限流容错等,数据平面可以认为是微服务框架的通信和服务治理能力独立而成的一个单独的语言无关的进程,并且更注重通用性和扩展性,在Service Mesh中,不再将数据平面代理视为一个个孤立的组件,而是将这些代理连接在一起形成一个全局的分布式网络;控制平面负责对数据平面进行管理,定义服务发现、路由、流量控制、遥测统计等策略,这些策略可以是全局的,也可以通过配置某个数据平面节点单独指定,控制平面通过一定的机制将策略下发到各个数据平面节点,数据平面节点在通信时会使用这些策略。 Istio以Envoy为基础,将Envoy作为默认的数据平面,同时提供强大的控制平面能力。 控制:Pilot、Mixer和Security是Istio 的3大核心组件,分别负责Istio配置管理、策略和遥测以及通信安全的实现。pilot:提供通用的配置管理能力,保证不同平台、不同环境下的配置均能够通过一致的方式对Envoy进行配置和下方,负责Envoy的生命周期管理,启动Envoy,然后监控Envoy的运行状态.启动,调度到其他节点等mixer: 收集遥测统计相关的数据和指标,可以对服务进行全方位的监控,策略控制:对于一些核心资源,需要通过一定的策略,在不同消费服务以及服务的不同实例中进行分配,保证资源能够按照预期进行管理. 数据envoy是一个用 C++ 编写的云原生高性能边缘代理、中间代理和服务代理,作为专门为微服务架构设计的通信总线。Envoy作为一个独立进程,设计为和每个应用程序一块运行,所有的 Envoy 形成一个透明的通信网格,每个应用程序发送消息到本地Envoy代理或从本地Envoy代理接收消息,但不需要知道具体的网络拓扑。
本文来自于我的个人主页:初识 Nacos(上) 学习《Spring Cloud 服务发现新选择》,转载请保留链接 ;)最近在从零接触Alibaba 开源项目Nacos,学习的是小马哥(mercyblitz)的技术周报,之前看了后忘记总结,导致也没有什么印象。所以现在决定学习一章,写一篇学习感悟,并且持续更新下去。首先这一章节主要讲得是服务发现(Service Discovery),作为 Spring Cloud 最核心功能特性之一,受到业界的广泛关注。Spring Cloud 整体架构在现行的 Spring Cloud 服务发现技术体系中,以 Spring Cloud Eureka 为典型代表,它作为官方推荐解决方案,被业 界广泛运用,然而其设计缺陷也非常之明显。还有Spring Cloud Zookeeper和Spring Cloud Consul。那么先介绍这三种的特点吧。Spring Cloud Eureka 特点优点:Spring Cloud 背书 - 推荐服务发现方案CAP 理论 - AP模型,数据最终一致简单易用 - 开箱即用,控制台管理缺点:内存限制 - 客户端上报完整注册信息,造成服务端内存浪费单一调度更新 - 客户端简单轮训更新,增加服务器压力集群伸缩性限制 - 广播式复制模型,增加服务器压力Spring Cloud Zookeeper 特点优点:成熟协调系统 - Dubbo、Spring Cloud等适配方案CAP理论 - CP模型,ZAB算法,强数据一致性缺点:维护成本 - 客户端、Session状态、网络故障伸缩性限制 - 内存、GC、连接Spring Cloud Consul 特点优点:通用方案 - 适用于 Service Mesh、 Java 生态CAP理论 - AP 模型,Raft+Gossip 算法,数据最终一致缺点:可靠性无法保证 - 未经过大规模验证非 Java 生态 - 维护和问题排查困难综上所述,让我得出了Spring Cloud服务发现方案对比结果:那么这三种服务发现的基本模式是怎样的呢?现在来谈谈Spring cloud 服务器发现模式。首先都是服务器启动 - 启动注册中心然后增加客户端依赖 - sping-cloud-start-*最后就是客户端注册 - 记得在XXApplication.java文件中添加@EnableDiscoveryClient,注解开启服务注册与发现功能。以下我以Eureka发现模式为例:首先去Spring Initializr快速创建Eureka服务端和客户端应用程序,然后导入自己的IDE。当然你如果嫌麻烦,也可以直接导入已经写好的工程。然后在resources-application.properties中分别配置好两者的端口号,像客户端这块还需要写好应用名称、以及Eureka 服务器地址。最后我们就直接可以runXXApplication.java了,像我的服务端端口是12345,就访问localhost:12345。页面跳转如下图所示,恭喜你的Eureka服务已经起来了。Eureka-client亦如此,成功run起来后,在之前的服务端页面,也就是localhost:12345,刷新下会在Instances currently registered with Eureka出现EUREKA-CLIENT的状态信息。spring-cloud-alibaba-nacos-discovery 作为 Spring Cloud Alibaba 服务发现的核心模块,其架构基础与 Spring Cloud 现行方案相同,均构建在 Spring Cloud Commons 抽象。因此,它在 Spring Cloud 服务发现的使用上,开发人员将不会心存任何的违和感。Alibaba Nacos 生态介绍从功能特性而言,spring-cloud-alibaba-nacos-discovery 仅是 Nacos 在 Spring Cloud 服务发现的解决方案,Nacos 在 Spring Cloud 中还支持分布式配置的特性。与开源产品不同的是,Nacos 曾经历过中国特色的超大流量考验,以及巨型规模的集群实施,无论从经验积累还是技术沉淀,现行 Spring Cloud 解决方案 都是无法比拟的。然而这并非说明它完美无缺,在内部的评估和讨论中,也发现其中差距和文化差异。为了解决这些问题,讨论将从整体架构和设计思考两个方面,介绍 Nacos 与 Spring 技术栈整合情况,以及与其他开源方案的适配思考,整体上,降低 Nacos 使用门槛,使迁移成本接近为零,达到“一次开发,到处运行”的目的。那么接下来我们通过Github上,Spring Cloud Alibaba项目中官方给出的指导文档来配置启动 Nacos吧。下载注册中心首先需要获取 Nacos Server,支持直接下载和源码构建两种方式。直接下载:Nacos Server 下载页源码构建:进入 Nacos Github 项目页面,将代码 git clone 到本地自行编译打包,参考此文档。推荐使用源码构建方式以获取最新版本启动注册中心启动 Server,进入解压后文件夹或编译打包好的文件夹,找到如下相对文件夹 nacos/bin,并对照操作系统实际情况之下如下命令。Linux/Unix/Mac 操作系统,执行命令 sh startup.sh -m standaloneWindows 操作系统,执行命令 cmd startup.cmd增加第三方依赖首先,修改 pom.xml 文件,引入 Nacos Discovery Starter。 <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency>外部化配置在应用的 /src/main/resources/application.properties 配置文件中配置 Nacos Server 地址 spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848激活服务发现使用 @EnableDiscoveryClient 注解开启服务注册与发现功能(SpringApplication.run) @SpringBootApplication @EnableDiscoveryClient public class ProviderApplication { public static void main(String[] args) { SpringApplication.run(Application.class, args); } @RestController class EchoController { @RequestMapping(value = “/echo/{string}”, method = RequestMethod.GET) public String echo(@PathVariable String string) { return string; } } }应用启动增加配置,在 nacos-discovery-provider-example 项目的 /src/main/resources/application.properties 中添加基本配置信息 spring.application.name=service-provider server.port=18082启动应用,支持 IDE 直接启动和编译打包后启动。IDE直接启动:找到 nacos-discovery-provider-example 项目的主类 ProviderApplication,执行 main 方法启动应用。打包编译后启动:在 nacos-discovery-provider-example 项目中执行 mvn clean package 将工程编译打包,然后执行 java -jar nacos-discovery-provider-example.jar启动应用。验证检验服务发现在浏览器输入此地址http://127.0.0.1:8848/nacos/v1/ns/instances?serviceName=service-provider 并点击跳转,可以看到服务节点已经成功注册到 Nacos Server。 ...
服务注册与服务发现是在分布式服务架构中常常会涉及到的东西,业界常用的服务注册与服务发现工具有 ZooKeeper、etcd、Consul 和 Eureka。Consul 的主要功能有服务发现、健康检查、KV存储、安全服务沟通和多数据中心。Consul 与其他几个工具的区别可以在这里查看 Consul vs. Other Software。为什么需要有服务注册与服务发现?假设在分布式系统中有两个服务 Service-A (下文以“S-A”代称)和 Service-B(下文以“S-B”代称),当 S-A 想调用 S-B 时,我们首先想到的时直接在 S-A 中请求 S-B 所在服务器的 IP 地址和监听的端口,这在服务规模很小的情况下是没有任何问题的,但是在服务规模很大每个服务不止部署一个实例的情况下是存在一些问题的,比如 S-B 部署了三个实例 S-B-1、S-B-2 和 S-B-3,这时候 S-A 想调用 S-B 该请求哪一个服务实例的 IP 呢?还是将3个服务实例的 IP 都写在 S-A 的代码里,每次调用 S-B 时选择其中一个 IP?这样做显得很不灵活,这时我们想到了 Nginx 刚好就能很好的解决这个问题,引入 Nginx 后现在的架构变成了如下图这样:引入 Nginx 后就解决了 S-B 部署多个实例的问题,还做了 S-B 实例间的负载均衡。但现在的架构又面临了新的问题,分布式系统往往要保证高可用以及能做到动态伸缩,在引入 Nginx 的架构中,假如当 S-B-1 服务实例不可用时,Nginx 仍然会向 S-B-1 分配请求,这样服务就不可用,我们想要的是 S-B-1 挂掉后 Nginx 就不再向其分配请求,以及当我们新部署了 S-B-4 和 S-B-5 后,Nginx 也能将请求分配到 S-B-4 和 S-B-5,Nginx 要做到这样就要在每次有服务实例变动时去更新配置文件再重启 Nginx。这样看似乎用了 Nginx 也很不舒服以及还需要人工去观察哪些服务有没有挂掉,Nginx 要是有对服务的健康检查以及能够动态变更服务配置就是我们想要的工具,这就是服务注册与服务发现工具的用处。下面是引入服务注册与服务发现工具后的架构图:在这个架构中:首先 S-B 的实例启动后将自身的服务信息(主要是服务所在的 IP 地址和端口号)注册到注册工具中。不同注册工具服务的注册方式各不相同,后文会讲 Consul 的具体注册方式。服务将服务信息注册到注册工具后,注册工具就可以对服务做健康检查,以此来确定哪些服务实例可用哪些不可用。S-A 启动后就可以通过服务注册和服务发现工具获取到所有健康的 S-B 实例的 IP 和端口,并将这些信息放入自己的内存中,S-A 就可用通过这些信息来调用 S-B。S-A 可以通过监听(Watch)注册工具来更新存入内存中的 S-B 的服务信息。比如 S-B-1 挂了,健康检查机制就会将其标为不可用,这样的信息变动就被 S-A 监听到了,S-A 就更新自己内存中 S-B-1 的服务信息。所以务注册与服务发现工具除了服务本身的服务注册和发现功能外至少还需要有健康检查和状态变更通知的功能。ConsulConsul 作为一种分布式服务工具,为了避免单点故障常常以集群的方式进行部署,在 Consul 集群的节点中分为 Server 和 Client 两种节点(所有的节点也被称为Agent),Server 节点保存数据,Client 节点负责健康检查及转发数据请求到 Server;Server 节点有一个 Leader 节点和多个 Follower 节点,Leader 节点会将数据同步到 Follower 节点,在 Leader 节点挂掉的时候会启动选举机制产生一个新的 Leader。Client 节点很轻量且无状态,它以 RPC 的方式向 Server 节点做读写请求的转发,此外也可以直接向 Server 节点发送读写请求。下面是 Consul 的架构图:Consule 的安装和具体使用及其他详细内容可浏览官方文档。下面是我用 Docker 的方式搭建了一个有3个 Server 节点和1个 Client 节点的 Consul 集群。# 这是第一个 Consul 容器,其启动后的 IP 为172.17.0.5docker run -d –name=c1 -p 8500:8500 -e CONSUL_BIND_INTERFACE=eth0 consul agent –server=true –bootstrap-expect=3 –client=0.0.0.0 -uidocker run -d –name=c2 -e CONSUL_BIND_INTERFACE=eth0 consul agent –server=true –client=0.0.0.0 –join 172.17.0.5docker run -d –name=c3 -e CONSUL_BIND_INTERFACE=eth0 consul agent –server=true –client=0.0.0.0 –join 172.17.0.5#下面是启动 Client 节点docker run -d –name=c4 -e CONSUL_BIND_INTERFACE=eth0 consul agent –server=false –client=0.0.0.0 –join 172.17.0.5启动容器时指定的环境变量 CONSUL_BIND_INTERFACE 其实就是相当于指定了 Consul 启动时 –bind 变量的参数,比如可以把启动 c1 容器的命令换成下面这样,也是一样的效果。docker run -d –name=c1 -p 8500:8500 -e consul agent –server=true –bootstrap-expect=3 –client=0.0.0.0 –bind=’{{ GetInterfaceIP “eth0” }}’ -ui操作 Consul 有 Commands 和 HTTP API 两种方式,进入任意一个容器执行 consul members 都可以有如下的输出,说明 Consul 集群就已经搭建成功了。Node Address Status Type Build Protocol DC Segment2dcf0c824cf0 172.17.0.7:8301 alive server 1.4.4 2 dc1 <all>64746cffa116 172.17.0.6:8301 alive server 1.4.4 2 dc1 <all>77af7d94a8ca 172.17.0.5:8301 alive server 1.4.4 2 dc1 <all>6c71148f0307 172.17.0.8:8301 alive client 1.4.4 2 dc1 <default>代码实践假设现在有一个用 Node.js 写的服务 node-server 需要通过 gRPC 的方式调用一个用 Go 写的服务 go-server。下面是用 Protobuf 定义的服务和数据类型文件 hello.proto。syntax = “proto3”;package hello;option go_package = “hello”;// The greeter service definition.service Greeter { // Sends a greeting rpc SayHello (stream HelloRequest) returns (stream HelloReply) {}}// The request message containing the user’s name.message HelloRequest { string name = 1;}// The response message containing the greetingsmessage HelloReply { string message = 1;}用命令通过 Protobuf 的定义生成 Go 语言的代码:protoc –go_out=plugins=grpc:./hello ./*.proto 会在 hello 目录下得到 hello.pb.go 文件,然后在 hello.go 文件中实现我们定义的 RPC 服务。// hello.gopackage helloimport “context"type GreeterServerImpl struct {}func (g *GreeterServerImpl) SayHello(c context.Context, h *HelloRequest) (*HelloReply, error) { result := &HelloReply{ Message: “hello” + h.GetName(), } return result, nil}下面是入口文件 main.go,主要是将我们定义的服务注册到 gRPC 中,并建了一个 /ping 接口用于之后 Consul 的健康检查。package mainimport ( “go-server/hello” “google.golang.org/grpc” “net” “net/http”)func main() { lis1, _ := net.Listen(“tcp”, “:8888”) lis2, _ := net.Listen(“tcp”, “:8889”) grpcServer := grpc.NewServer() hello.RegisterGreeterServer(grpcServer, &hello.GreeterServerImpl{}) go grpcServer.Serve(lis1) go grpcServer.Serve(lis2) http.HandleFunc("/ping”, func(res http.ResponseWriter, req *http.Request){ res.Write([]byte(“pong”)) }) http.ListenAndServe(":8080", nil)}至此 go-server 端的代码就全部编写完了,可以看出代码里面没有任何涉及到 Consul 的地方,用 Consul 做服务注册是可以做到对项目代码没有任何侵入性的。下面要做的是将 go-server 注册到 Consul 中。将服务注册到 Consul 可以通过直接调用 Consul 提供的 REST API 进行注册,还有一种对项目没有侵入的配置文件进行注册。Consul 服务配置文件的详细内容可以在此查看。下面是我们通过配置文件进行服务注册的配置文件 services.json:{ “services”: [ { “id”: “hello1”, “name”: “hello”, “tags”: [ “primary” ], “address”: “172.17.0.9”, “port”: 8888, “checks”: [ { “http”: “http://172.17.0.9:8080/ping”, “tls_skip_verify”: false, “method”: “GET”, “interval”: “10s”, “timeout”: “1s” } ] },{ “id”: “hello2”, “name”: “hello”, “tags”: [ “second” ], “address”: “172.17.0.9”, “port”: 8889, “checks”: [ { “http”: “http://172.17.0.9:8080/ping”, “tls_skip_verify”: false, “method”: “GET”, “interval”: “10s”, “timeout”: “1s” } ] } ]}配置文件中的 172.17.0.9 代表的是 go-server 所在服务器的 IP 地址,port 就是服务监听的不同端口,check 部分定义的就是健康检查,Consul 会每隔 10秒钟请求一下 /ping 接口以此来判断服务是否健康。将这个配置文件复制到 c4 容器的 /consul/config 目录,然后执行consul reload 命令后配置文件中的 hello 服务就注册到 Consul 中去了。通过在宿主机执行curl http://localhost:8500/v1/catalog/services?pretty就能看到我们注册的 hello 服务。下面是 node-server 服务的代码:const grpc = require(‘grpc’);const axios = require(‘axios’);const protoLoader = require(’@grpc/proto-loader’);const packageDefinition = protoLoader.loadSync( ‘./hello.proto’, { keepCase: true, longs: String, enums: String, defaults: true, oneofs: true });const hello_proto = grpc.loadPackageDefinition(packageDefinition).hello;function getRandNum (min, max) { min = Math.ceil(min); max = Math.floor(max); return Math.floor(Math.random() * (max - min + 1)) + min;};const urls = []async function getUrl() { if (urls.length) return urls[getRandNum(0, urls.length-1)]; const { data } = await axios.get(‘http://172.17.0.5:8500/v1/health/service/hello’); for (const item of data) { for (const check of item.Checks) { if (check.ServiceName === ‘hello’ && check.Status === ‘passing’) { urls.push(${item.Service.Address}:${item.Service.Port}) } } } return urls[getRandNum(0, urls.length - 1)];}async function main() { const url = await getUrl(); const client = new hello_proto.Greeter(url, grpc.credentials.createInsecure()); client.sayHello({name: ‘jack’}, function (err, response) { console.log(‘Greeting:’, response.message); }); }main()代码中 172.17.0.5 地址为 c1 容器的 IP 地址,node-server 项目中直接通过 Consul 提供的 API 获得了 hello 服务的地址,拿到服务后我们需要过滤出健康的服务的地址,再随机从所有获得的地址中选择一个进行调用。代码中没有做对 Consul 的监听,监听的实现可以通过不断的轮询上面的那个 API 过滤出健康服务的地址去更新 urls 数组来做到。现在启动 node-server 就可以调用到 go-server 服务。服务注册与发现给服务带来了动态伸缩的能力,也给架构增加了一定的复杂度。Consul 除了服务发现与注册外,在配置中心、分布式锁方面也有着很多的应用。 ...
前言所谓注册中心,其实是分布式架构演进过程中的产物,在系统中充当一个协调者的角色。但是,为什么需要这样一个协调者的角色呢?我们先来看一个例子,以便理解为什么分布式架构中需要有注册中心。案例小明和小新住在同一家沃尔玛超市附近,他俩都办了会员,经常关注超市的一些优惠活动,元宵节快到了,沃尔玛准备搞一个元宵节特惠活动,需要通知到附近的住户。对于沃尔玛来说,可以安排工作人员电话通知到小明和小新;而对于小明和小新来说,可以去超市咨询相关信息。那么问题来了,住在超市附近的,不只有小明和小新两个消费者,如果每个人都打电话去通知就显得太麻烦了,小明和小新提前在超市了解了相关信息,可是不巧的是,由于各种原因,沃尔玛元宵特惠活动要从上午改到下午才开始,他们又该从何得知呢?其实,沃尔玛关心的是通知能不能传达到附近的住户,小明和小新关心的是沃尔玛优惠活动的详情动态。沃尔玛不必给每个住户挨个电话通知,它只需要在它的微信公众号上推送一条消息即可,小明和小新也不用去超市咨询,只要随时关注沃尔玛公众号的推送消息即可。在上面这个例子中,沃尔玛就是服务提供者,小明和小新是服务消费者,微信公众号类似于注册中心,通过微信公众号,沃尔玛(服务方)和小明、小新(消费方)就“解耦”了。用这个例子来解释注册中心未必恰当,毕竟系统中的服务既可以是服务提供者(Provider),也可以是服务消费者(Consumer),但我想的是以一种更加通俗的方式来解释它,技术日新月异,各种技术、术语层出不穷,容易让人头晕眼花,但万变不离其宗,技术源于现实世界,亦服务于现实世界,在现实世界中,我们思考如何解决问题,技术也必然以同样的思路去解决问题。关于注册中心,更技术层面的解释,大家可以看一下这篇文章:《服务注册中心架构演进》在现有的技术方案中,注册中心主要分为两类,一类是CP类注册中心,另一类是AP类注册中心,Nacos属于后者,为什么会有 CP 和 AP 两种不同类型的注册中心呢?这就不得不提到分布式的一个理论:CAP理论。它是由加州大学的计算机科学家 Eric Brewer 提出,在一个分布式系统中,Consistency(一致性)、Availability(可用性)、Partition tolerance(分区容错性)无法同时满足,正所谓“鱼和熊掌与虾不可兼得也”。CP类注册中心更强调一致性,而AP类注册中心更强调可用性,它们之间的区别,推荐阅读阿里中间件博客的文章:《阿里巴巴为什么不用 ZooKeeper 做服务发现?》, 这篇文章我看了好几遍,虽然不能完全理解,但也能明白十之八九。如果你看完文章后,得到的结论是以后再也不把 Dubbo 和 Zookeeper 结合起来使用了,那么你便错了。因为,对于绝大多数公司的绝大多数系统,无论是 Dubbo + Zookeeper,还是 Dubbo + Nacos,都能够满足需求,有的公司甚至都不需要使用Dubbo,所以,一定要结合实际的业务场景来分析判断。不过,我们作为技术开发人员,了解技术原理是很重要的,唯有了解其底层逻辑,才知道如何做技术选型以及解决疑难杂症。好了,让我们回到Nacos本身,下面将从代码层面分别介绍 Nacos + Spring 和 Nacos + Spring Boot 的使用,我的案例都是基于 Nacos 官网的示例(毕竟官网是最好的学习资料嘛)。Nacos 结合 Spring先来看 Nacos + Spring 的使用:添加 maven 依赖:<dependency> <groupId>com.alibaba.nacos</groupId> <artifactId>nacos-spring-context</artifactId> <version>0.2.2-RC1</version></dependency>使用 @EnableNacosDiscovery 开启 Nacos Spring 的服务发现功能@Configuration@EnableNacosDiscovery(globalProperties = @NacosProperties(serverAddr = “127.0.0.1:8848”))public class NacosDiscovery {}使用 @NacosInjected 注入 Nacos 的NamingService实例,通过NamingService的registerInstance() 向 Nacos Server 注册一个名称为applicationName的服务,当然,你也可以通过 Nacos Open API 方式注册:curl -X PUT ‘http://127.0.0.1:8848/nacos/v1/ns/instance?serviceName=XXX&ip=XXX&port=XXX’,这里我们介绍使用代码的方式。@Configuration@EnableNacosDiscovery(globalProperties = @NacosProperties(serverAddr = “127.0.0.1:8848”))public class NacosDiscovery { @NacosInjected private NamingService namingService; @Value("${server.port}") private int serverPort; @Value("${spring.application.name}") private String applicationName; @PostConstruct public void registerInstance() throws NacosException { namingService.registerInstance(applicationName, “127.0.0.1”, serverPort); }}再写一个Controller来验证服务是否再 Nacos Server 上注册了,代码如下:@RestController@RequestMapping(value = “discovery”)public class NacosDiscoveryController { @NacosInjected private NamingService namingService; @RequestMapping(value = “/get”, method = GET) @ResponseBody public List<Instance> getInstance(@RequestParam String serviceName) throws NacosException { return namingService.getAllInstances(serviceName); }}启动 Nacos Server,安装及启动方式请参考:《欢迎来到Nacos的世界!》然后启动Tomcat,我们先来看看Nacos控制台有什么变化在控制台上,我们可以看到名为nacos-spring-discovery服务实例,点击详情按钮查看实例的详细信息:在浏览器上访问:http://127.0.0.1:8080/discovery/get?serviceName=nacos-spring-discovery,返回结果如下:[{ “instanceId”: “127.0.0.1#8080#{"defaultCheckPort":80,"defaultPort":80,"healthChecker":{"type":"TCP"},"metadata":{},"name":"","useIPPort4Check":true}#nacos-spring-discovery”, “ip”: “127.0.0.1”, “port”: 8080, “weight”: 1.0, “healthy”: true, “cluster”: { “serviceName”: null, “name”: “”, “healthChecker”: { “type”: “TCP” }, “defaultPort”: 80, “defaultCheckPort”: 80, “useIPPort4Check”: true, “metadata”: {} }, “service”: null, “metadata”: {}}]和我们刚才在控制台看到的数据是一致的。以上就是 Nacos 结合 Spring 的实现方式,那么 Nacos 结合 Spring Boot 呢?其实没什么太大区别。Nacos 结合 Spring Boot添加 Starter 依赖:<dependency> <groupId>com.alibaba.boot</groupId> <artifactId>nacos-discovery-spring-boot-starter</artifactId> <version>0.2.1</version></dependency>注意:版本 0.2.x.RELEASE 对应的是 Spring Boot 2.x 版本,版本 0.1.x.RELEASE 对应的是 Spring Boot 1.x 版本。在application.properties中添加如下配置信息:server.port=8080spring.application.name=nacos-springboot-discoverynacos.discovery.server-addr=127.0.0.1:8848添加NacosDiscoveryApplication启动类,使用@NacosInjected注入 Nacos 的 NamingService实例,通过NamingService的registerInstance()向 Nacos Server 注册一个名称为applicationName的服务:@SpringBootApplicationpublic class NacosDiscoveryApplication { @NacosInjected private NamingService namingService; @Value("${server.port}") private int serverPort; @Value("${spring.application.name}") private String applicationName; @PostConstruct public void registerInstance() throws NacosException { namingService.registerInstance(applicationName, “127.0.0.1”, serverPort); } public static void main(String[] args) { SpringApplication.run(NacosDiscoveryApplication.class, args); }}添加NacosDiscoveryController类:@RestController@RequestMapping(value = “discovery”)public class NacosDiscoveryController { @NacosInjected private NamingService namingService; @RequestMapping(value = “/get”, method = GET) @ResponseBody public List<Instance> getInstance(@RequestParam String serviceName) throws NacosException { return namingService.getAllInstances(serviceName); }}启动NacosDiscoveryApplication,观察Nacos控制台在浏览器上访问:http://127.0.0.1:8080/discovery/get?serviceName=nacos-springboot-discovery,返回结果如下:[{ “instanceId”: “127.0.0.1#8080#{"defaultCheckPort":80,"defaultPort":80,"healthChecker":{"type":"TCP"},"metadata":{},"name":"","useIPPort4Check":true}#nacos-springboot-discovery”, “ip”: “127.0.0.1”, “port”: 8080, “weight”: 1.0, “healthy”: true, “cluster”: { “serviceName”: null, “name”: “”, “healthChecker”: { “type”: “TCP” }, “defaultPort”: 80, “defaultCheckPort”: 80, “useIPPort4Check”: true, “metadata”: {} }, “service”: null, “metadata”: {}}]以上两个示例的源码请从这里下载learn-nacos-spring-discoverylearn-nacos-springboot-discovery好了,关于 Nacos 作为注册中心的话题先聊到这里,下一期将介绍 Nacos 作为配置中心的使用,敬请期待!参考资料1、服务注册中心架构演进2、阿里巴巴为什么不用 ZooKeeper 做服务发现?3、Nacos Spring 快速开始4、Nacos Spring Boot 快速开始5、SpringBoot使用Nacos服务发现6、Spring Cloud Alibaba基础教程:使用Nacos实现服务注册与发现 ...