服务发现
服务化的普及,令软件系统得以通过分布于网络中不同机器的互相协作来复用功能
最早的服务发现可以直接依赖DNS将一个全限定名翻译为一至多个IP地址或者SRV等其他类型的记录便可
但进入微服务时代后,服务宕机 上线下线变得更加频繁 DNS就力不从心了。
服务注册与发现的实现是随zk-eureka-nacos/consul这条线过来的
服务发现原理
自理式服务发现
- 自理式结构就是指每个微服务自己完成服务发现
代理式服务发现
- LOAD BALANCER 有单点故障风险和性能风险
服务发现共性设计
- 服务注册:服务通过某些形式将自己的坐标信息通知到服务注册中心
- 服务维护:服务发现框架必须能监控服务健康状况,及时剔除不健康的服务
- 服务发现:消费者可以通过框架将服务名转为具体的坐标
在真实系统中,服务发现中心是整个系统的基础架构 如果它一挂 整个系统就完全崩溃了 所以必须进行高可用支持
服务发现中心有以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的过程
- 流程
- 依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
- 配置
spring.application.name=eureka-server
server.port=8001
#是否将自己注册到注册中心
eureka.client.register-with-eureka=false
#是否从注册中心获取注册信息
eureka.client.fetch-registry=false
eureka.client.serviceUrl.defaultZone=http://localhost:${server.port}/eureka/
@SpringBootApplication
@EnableEurekaServer
public 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秒一次心跳
- 接收服务心跳
失效剔除与自我保护
- 失效剔除
有些时候,我们的服务实例并不一定会正常下线,可能由于内存溢出、网络故障等原因使得服务不能正常工作,而服务注册中心并未收到“服务下线”的请求。为了从服务表中将这些无法提供服务的实例剔除,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
- 配置
# eureka1
spring.application.name=spring-cloud-eureka
server.port=8001
eureka.client.serviceUrl.defaultZone=http://localhost:8002/eureka/
# eureka2
spring.application.name=spring-cloud-eureka
server.port=8002
eureka.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-producer
server.port=8503
spring.cloud.consul.host=localhost
spring.cloud.consul.port=8500
#注册到consul的服务名称
spring.cloud.consul.discovery.serviceName=producer
消费者
依赖同生产者
配置
spring.application.name=consul-consumer
server.port=8504
spring.cloud.consul.host=127.0.0.1
spring.cloud.consul.port=8500
#设置不需要注册到 consul 中
spring.cloud.consul.discovery.register=false
- 使用
@RestController
public 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=8101
spring.application.name=zk-producer
spring.cloud.zookeeper.connect-string=127.0.0.1:2181
@EnableDiscoveryClient
Nacos
- 服务发现和服务健康监测
- 动态配置服务
- 动态DNS服务
- 服务即其元数据管理
概念
- 地域 物理的数据中心,资源创建成功后不能更换
- 可用区 同一地域内,电力和网络互相独立的物理区域
- 接入点 地域的某个服务的入口域名
- 命名空间
- 配置
- 配置管理 系统配置的编辑、存储、分发、变更管理、历史版本管理、变更审计等
- 配置项 一个具体的可配置的参数与其值域,通常以 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=provider
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
- 消费者
@FeignClient("provider")
public interface ProviderClient {
@GetMapping("/name")
String name();
}
@RestController
public 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则有主从之分