服务发现

服务化的普及,令软件系统得以通过分布于网络中不同机器的互相协作来复用功能

最早的服务发现可以直接依赖DNS将一个全限定名翻译为一至多个IP地址或者SRV等其他类型的记录便可

但进入微服务时代后,服务宕机 上线下线变得更加频繁 DNS就力不从心了。

服务注册与发现的实现是随zk-eureka-nacos/consul这条线过来的

服务发现原理

sequenceDiagram    participant 服务消费者    participant 注册中心    participant 服务提供者    服务提供者 ->> 注册中心: 注册 (register)    服务提供者 ->> 注册中心: 续约 (renew)    服务提供者 ->> 注册中心: 下载 (cancel)    服务消费者 ->> 注册中心: 获取注册列表 (get registry)    服务消费者 ->> 服务提供者: 发起远程调用

自理式服务发现

202259211731

代理式服务发现

202259211826

服务发现共性设计

在真实系统中,服务发现中心是整个系统的基础架构 如果它一挂 整个系统就完全崩溃了 所以必须进行高可用支持

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在出现在系统出现网络分区也能继续对外提供服务 不会影响系统操作的正确性场景下 是十分有用的

服务注册中心的实现

AP实现

2022614162525

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: 调用服务

批注 2020-07-03 085520批注 2020-07-03 085612

<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);    }}

服务注册

服务续约

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的访问请求能得到一致性的数据结果,同时系统对网络分割具备容错性,但是它不能保证每次服务的可用性

<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

概念

屏幕截图 2020-09-23 163728

架构

屏幕截图 2020-09-23 163102

注册中心

使用
<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

不同点:

最主要的是Eureka集群中的各个节点是对等的,而Nacos则有主从之分