服务发现
服务化的普及,令软件系统得以通过分布于网络中不同机器的互相协作来复用功能
最早的服务发现可以直接依赖DNS将一个全限定名翻译为一至多个IP地址或者SRV等其他类型的记录便可
但进入微服务时代后,服务宕机 上线下线变得更加频繁 DNS就力不从心了。
服务注册与发现的实现是随zk-eureka-nacos/consul这条线过来的
服务发现原理
sequenceDiagram participant 服务消费者 participant 注册中心 participant 服务提供者 服务提供者 ->> 注册中心: 注册 (register) 服务提供者 ->> 注册中心: 续约 (renew) 服务提供者 ->> 注册中心: 下载 (cancel) 服务消费者 ->> 注册中心: 获取注册列表 (get registry) 服务消费者 ->> 服务提供者: 发起远程调用
自理式服务发现
- 自理式结构就是指每个微服务自己完成服务发现
代理式服务发现
- LOAD BALANCER 有单点故障风险和性能风险
服务发现共性设计
- 服务注册:服务通过某些形式将自己的坐标信息通知到服务注册中心
- 服务维护:服务发现框架必须能监控服务健康状况,及时剔除不健康的服务
- 服务发现:消费者可以通过框架将服务名转为具体的坐标
在真实系统中,服务发现中心是整个系统的基础架构 如果它一挂 整个系统就完全崩溃了 所以必须进行高可用支持
sequenceDiagram participant ServiceProvider1 as Service Provider participant ServiceDiscovery1 as Service Discovery participant ServiceDiscovery2 as Service Discovery participant ServiceDiscovery3 as Service Discovery participant ServiceConsumer1 as Service Consumer ServiceProvider1 ->> ServiceDiscovery1: Register / Renew / Cancel ServiceDiscovery1 ->> ServiceDiscovery2: Replicate ServiceDiscovery2 ->> ServiceDiscovery3: Replicate ServiceDiscovery1 ->> ServiceConsumer1: Discovery ServiceConsumer1 ->> ServiceProvider1: Remote Call
服务发现中心有以Eureka的AP注册中心 也有以Consul为代表的CP注册中心
当然也有AP CP随时转换的Nacos
AP在出现在系统出现网络分区也能继续对外提供服务 不会影响系统操作的正确性场景下 是十分有用的
服务注册中心的实现
- 在分布式KV存储中间件上开发自己的框架:zk,redis,etcd
- 基础设施实现:DNS
- 专用框架:Eureka Nacos等
AP实现
Eureka
Eureka是Netflix开发的服务发现框架,Eureka包含两个组件: Eureka Server和Eureka Client.
各个节点启动后,会在Eureka Server中进行注册,这样Eureka Server中的服务注册表中将会存储所有可用服务节点的信息
在应用启动后,将会 向Eureka Server发送心跳,默认周期为30秒
保证AP,eureka在设计时优先保证可用性,每一个节点都是平等的,一部分节点挂掉不会影响到正常节点的工作,不会出现类似zk的选举leader的过程
sequenceDiagram participant ApplicationClient as ApplicationClient participant EurekaServer as EurekaServer participant ApplicationService as ApplicationService ApplicationService ->> EurekaServer: register ApplicationService --) EurekaServer: renew ApplicationClient ->> EurekaServer: 定期获取 ApplicationClient ->> ApplicationService: 调用服务
- 流程
- 依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId></dependency>
- 配置
spring.application.name=eureka-serverserver.port=8001#是否将自己注册到注册中心eureka.client.register-with-eureka=false#是否从注册中心获取注册信息eureka.client.fetch-registry=falseeureka.client.serviceUrl.defaultZone=http://localhost:${server.port}/eureka/
@SpringBootApplication@EnableEurekaServerpublic class EurekaServerApplication { public static void main(String[] args) { SpringApplication.run(EurekaServerApplication.class, args); }}
服务注册
- 获取读锁
- 在注册表查找instance info
- 租约是否存在
- 不存在:创建新租约
- 存在:判断最后更新时间
- 如果更新时间比较大,则更新时间戳
- 设置上线时间
服务续约
eureka: instance: lease-expiration-duration-in-seconds: 10 # 10秒即过期 lease-renewal-interval-in-seconds: 5 # 5秒一次心跳
- 接收服务心跳
flowchart TD A[false] B{查询租约} C{是否为unknown} D[取消租约] E[更新时间] F[统计每分钟续约次数,用于自我保护] B -->|不存在| A B -->|存在| C C -->|是| D C -->|否| E E --> F
失效剔除与自我保护
- 失效剔除
有些时候,我们的服务实例并不一定会正常下线,可能由于内存溢出、网络故障等原因使得服务不能正常工作,而服务注册中心并未收到“服务下线”的请求。为了从服务表中将这些无法提供服务的实例剔除,Eureka Server 在启动的时候会创建一个定时任多默认每隔一一段时间(默认为60秒)将当前清单中超时(默认为90秒)没有续约的服务除出去
- 自我保护
默认情况下,EurekaClient会定时向EurekaServer端发送心跳,如果EurekaServer在一定时间内没有收到EurekaClient发送的心跳,便会把该实例从注册服务列表中剔除(默认是90秒),为了防止只是EurekaClient与EurekaServer之间的网络故障,在短时间内丢失大量的实例心跳,这时候EurekaServer会开启自我保护机制,EurekaServer不会踢出这些服务
在开发中,由于会重复重启服务实例,所以经常会出现以下警告:
EMERGENCY!EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY'RE NOT.RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEGING EXPIRED JUST TO BE SAFE.
所以开发时需要关闭自我保护
eureka: server: enable-self-preservation: false # 关闭自我保护模式(缺省为打开) eviction-interval-timer-in-ms: 1000 # 扫描失效服务的间隔时间(缺省为60*1000ms)
服务下线
- 是否有租约
- 没有租约下线失败
- 否则从注册表中移除
- 设置下线时间
- 添加下线记录
Eureka集群
Eureka 满足AP 牺牲了 C
- 配置
# eureka1spring.application.name=spring-cloud-eurekaserver.port=8001eureka.client.serviceUrl.defaultZone=http://localhost:8002/eureka/
# eureka2spring.application.name=spring-cloud-eurekaserver.port=8002eureka.client.serviceUrl.defaultZone=http://localhost:8001/eureka/
根据两个配置文件启动两个实例
客户端配置
eureka.client.service-url.defaultZone=http://localhost:8001/eureka,http://localhost:8002/eureka
Consul
- 安装
https://www.consul.io/downloads.html
- 启动
consul agent -dev
生产者配置
- 依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId></dependency><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId></dependency><dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-consul-discovery</artifactId></dependency>
- 配置
spring.application.name=consul-producerserver.port=8503spring.cloud.consul.host=localhostspring.cloud.consul.port=8500#注册到consul的服务名称spring.cloud.consul.discovery.serviceName=producer
消费者
依赖同生产者
配置
spring.application.name=consul-consumerserver.port=8504spring.cloud.consul.host=127.0.0.1spring.cloud.consul.port=8500#设置不需要注册到 consul 中spring.cloud.consul.discovery.register=false
- 使用
@RestControllerpublic class ServiceController { @Autowired LoadBalancerClient loadBalancerClient; @Autowired DiscoveryClient discoveryClient; // 获取相关服务实例 @RequestMapping("/services") public Object services(){ return discoveryClient.getInstances("producer"); } // 自动选择服务实例 @RequestMapping("/discover") public Object discover(){ return loadBalancerClient.choose("producer").getUri().toString(); } @RequestMapping("/hi") public String hi(){ ServiceInstance instance = loadBalancerClient.choose("producer"); return new RestTemplate().getForObject(instance.getUri().toString()+"/hi",String.class); }}
zookeeper
保证CP,即任何时刻对zookeeper的访问请求能得到一致性的数据结果,同时系统对网络分割具备容错性,但是它不能保证每次服务的可用性
- 启动zk
- 服务依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId></dependency>
- 服务配置
server.port=8101spring.application.name=zk-producerspring.cloud.zookeeper.connect-string=127.0.0.1:2181
@EnableDiscoveryClient
Nacos
- 服务发现和服务健康监测
- 动态配置服务
- 动态DNS服务
- 服务即其元数据管理
概念
- 地域 物理的数据中心,资源创建成功后不能更换
- 可用区 同一地域内,电力和网络互相独立的物理区域
- 接入点 地域的某个服务的入口域名
- [命名空间](/软件工程/微服务/服务治理/配置中心.html#自定义namespace)
- 配置
- 配置管理 系统配置的编辑、存储、分发、变更管理、历史版本管理、变更审计等
- 配置项 一个具体的可配置的参数与其值域,通常以 param-key=param-value 的形式存在
- 配置集 一组相关或者不相关的配置项的集合
- 配置集ID
- 配置分组
- 配置快照 Nacos 的客户端 SDK 会在本地生成配置的快照 类似于缓存
- 服务
- 服务名
- 服务注册中心
- 服务发现 对服务下的实例的地址和元数据进行探测
- 元信息 服务或者配置的描述信息
- 应用
- 服务分组
- 虚拟集群 同一个服务下的所有服务实例组成一个默认集群
- 实例
- 权重
- 健康检查
- 健康保护阈值 止因过多实例不健康导致流量全部流向健康实例
架构
注册中心
使用
- 生产者
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency>
spring.application.name=providerspring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
- 消费者
@FeignClient("provider")public interface ProviderClient { @GetMapping("/name") String name();}@RestControllerpublic static class Api { @Autowired private ProviderClient client; @GetMapping("/") public String home() { return client.name(); }}
vs Zookeeper & Eureka
不同点:
- Zookeeper采用CP保证数据的一致性的问题
- Eureka采用ap的设计理念架构注册中心,完全去中心化思想
- Nacos.从1.0版本支持CP和AP混合模式集群,默认是采用Ap保证服务可用性,CP的形式底层集群raft协议保证数据的一致性的问题。
最主要的是Eureka集群中的各个节点是对等的,而Nacos则有主从之分