注冊中心選型以及Spring Cloud 是如何實(shí)現(xiàn)服務(wù)發(fā)現(xiàn)的

注冊中心

CAP原則

CAP原則又稱CAP定理剔交,指的是在一個分布式系統(tǒng)中卵贱,一致性(Consistency)猴鲫、可用性(Availability)是趴、分區(qū)容錯性(Partition tolerance),這三個要素最多只能同時實(shí)現(xiàn)兩點(diǎn)暂筝,不可能三者兼顧箩言。

CAP 適用場景 解釋
CA 幾乎不存在 在分布式系統(tǒng)中,P必然存在焕襟,除非適用單機(jī)陨收,要提升分區(qū)可靠性,需要通過提升基礎(chǔ)設(shè)施的可靠性實(shí)現(xiàn)
CP 分布式數(shù)據(jù)庫(Redis鸵赖、HBase务漩、zk、etcd) 分布式數(shù)據(jù)庫極端情況下優(yōu)先保證數(shù)據(jù)一致性
AP 大部分對數(shù)據(jù)一致性沒有嚴(yán)格要求的場景 優(yōu)先保證服務(wù)可用

BASE

BASE是Basically Available(基本可用)它褪、Soft state(軟狀態(tài))和 Eventually consistent(最終一致性)三個短語的簡寫饵骨。

BASE是對 CAP 中一致性和可用性權(quán)衡的結(jié)果,其來源于對大規(guī)拿4颍互聯(lián)網(wǎng)系統(tǒng)分布式實(shí)踐的結(jié)論居触,是基于CAP定理逐步演化而來的,其核心思想是即使無法做到強(qiáng)一致性(Strong consistency)老赤,但每個應(yīng)用都可以根據(jù)自身的業(yè)務(wù)特點(diǎn)轮洋,采用適當(dāng)?shù)姆绞絹硎瓜到y(tǒng)達(dá)到最終一致性(Eventual consistency)。接下來我們著重對BASE中的三要素進(jìn)行詳細(xì)講解抬旺∽┣疲基本可用:指分布式系統(tǒng)在出現(xiàn)不可預(yù)知故障的時候,允許損失部分可用性嚷狞。

常見注冊中心比較

Zookeeper Eureka Consul Nacos Etcd
數(shù)據(jù)一致性 CP AP CP AP/CP CP
健康檢查 Keep Alive ClientBeat TCP/HTTP/grpc/Cmd TCP/HTTP/MySql/ClientBeat ClientBeat
負(fù)載均衡策略 Ribbon Fabio 權(quán)重/metadata/Selector
雪崩保護(hù)
自動注銷實(shí)例 ×
訪問協(xié)議 TCP HTTP HTTP/DNS HTTP/DNS HTTP
監(jiān)聽支持
多數(shù)據(jù)中心 ×
跨注冊中心同步 × ×
Spring Cloud集成
Dubbo集成 × ×
K8s集成 × ×
部署難度 4 1 3 2 4
開發(fā)語言 Java Java Go Java Go
功能 分布式數(shù)據(jù)協(xié)同 基于 HTTP 協(xié)議的服務(wù)發(fā)現(xiàn) 多種機(jī)制的服務(wù)發(fā)現(xiàn)和 KV 存儲 多種機(jī)制的服務(wù)發(fā)現(xiàn)、KV 存儲荣堰、配置中心床未、大而全的功能 分布式數(shù)據(jù)協(xié)同
時效性 秒級 取決于具體配置。默認(rèn) 30s 更新服務(wù)實(shí)例信息振坚,90s 才會去剔除失效的節(jié)點(diǎn)薇搁,在這種配置下可能 2 分鐘才能獲取到最新的配置 看具體配置 正常情況下秒級,異常情況取決于具體配置渡八。默認(rèn) 15s

一個基本的注冊中心需要以下 4 個基本的功能:

  • 注冊服務(wù)實(shí)例信息
  • 心跳機(jī)制
  • 剔除失敗的服務(wù)實(shí)例信息
  • 查詢服務(wù)實(shí)例信息操作

zookeeper

zk 本身并不是為了做注冊中心的啃洋,不過其提供的通用樹狀存儲結(jié)構(gòu)和 znode 機(jī)可以間接完成服務(wù)發(fā)現(xiàn)的必要功能。比如我們有 2 個服務(wù) a 和 b

/
├ a
┆ ├ a1
┆ └ a2
└ b
    └ b1

這樣存儲屎鳍,可以通過查詢 a 節(jié)點(diǎn)宏娄,獲取服務(wù) a 下面的實(shí)例信息。

在 zk 中逮壁,可以在使用臨時節(jié)點(diǎn)創(chuàng)建 a1孵坚、a2、b1 這樣的用來存儲服務(wù)實(shí)例信息的節(jié)點(diǎn),當(dāng)服務(wù)實(shí)例關(guān)閉或者通信異常時卖宠,zookeeper 可以自動刪除這些臨時節(jié)點(diǎn)巍杈,這樣就實(shí)現(xiàn)了剔除機(jī)制。

zk扛伍,一旦服務(wù)掛掉筷畦,zk感知到以及通知其他服務(wù)的時效性,服務(wù)注冊到zk之后通知到其他服務(wù)的時效性刺洒,leader掛掉之后可用性是否會出現(xiàn)短暫的問題鳖宾,為了去換取一致性

注冊機(jī)制:客戶端主動創(chuàng)建臨時節(jié)點(diǎn)

心跳機(jī)制:因?yàn)閯?chuàng)建的是臨時節(jié)點(diǎn),依靠 zk 本身的會話機(jī)制

剔除機(jī)制:會話失效后作媚,臨時節(jié)點(diǎn)自動剔除

查詢機(jī)制:使用 zk 協(xié)議去查詢節(jié)點(diǎn)

Eureka

image-20210912133439362

相比于 zookeeper 來說攘滩,Eureka 是專門用來做注冊中心的,本身提供了注冊中心需要的所有的功能纸泡。其提供了 SDK 和 HTTP 接口來訪問 Eureka Server.

其部分 API 如下漂问,更多的查看 Eureka REST operations

Operation HTTP action Description
Register new application instance POST /eureka/v2/apps/appID Input: JSON/XML payload HTTP Code: 204 on success
De-register application instance DELETE /eureka/v2/apps/appID/instanceID HTTP Code: 200 on success
Send application instance heartbeat PUT /eureka/v2/apps/appID/instanceID HTTP Code: * 200 on success * 404 if instanceID doesn’t exist
Query for all instances GET /eureka/v2/apps HTTP Code: 200 on success Output: JSON/XML
Query for all appID instances GET /eureka/v2/apps/appID HTTP Code: 200 on success Output: JSON/XML
Query for a specific appID/instanceID GET /eureka/v2/apps/appID/instanceID HTTP Code: 200 on success Output: JSON/XML

如果不想使用 HTTP 接口,也可以直接使用 Eureka 提供的 Java SDK

Eureka 更側(cè)重于 AP女揭,其通過自我保護(hù)機(jī)制蚤假,可以在網(wǎng)絡(luò)異常的情況下,保留大部分節(jié)點(diǎn)信息吧兔,來防止雪崩的情況

如果 Eureka 服務(wù)器檢測到比預(yù)期數(shù)量多的注冊客戶端以不合適的方式終止了它們的連接磷仰,并且同時等待驅(qū)逐,它們將進(jìn)入自我保護(hù)模式境蔼。 這樣做是為了確保災(zāi)難性網(wǎng)絡(luò)事件不會清除 eureka 注冊表數(shù)據(jù)灶平,并將其向下傳播到所有客戶端。

自我保護(hù)機(jī)制的工作機(jī)制是:如果在15分鐘內(nèi)超過85%的客戶端節(jié)點(diǎn)都沒有正常的心跳箍土,那么Eureka就認(rèn)為客戶端與注冊中心出現(xiàn)了網(wǎng)絡(luò)故障逢享,Eureka Server自動進(jìn)入自我保護(hù)機(jī)制,此時會出現(xiàn)以下幾種情況:

  1. Eureka Server不再從注冊列表中移除因?yàn)殚L時間沒收到心跳而應(yīng)該過期的服務(wù)吴藻。
  2. Eureka Server仍然能夠接受新服務(wù)的注冊和查詢請求瞒爬,但是不會被同步到其它節(jié)點(diǎn)上衣赶,保證當(dāng)前節(jié)點(diǎn)依然可用蹦狂。
  3. 當(dāng)網(wǎng)絡(luò)穩(wěn)定時,當(dāng)前Eureka Server新的注冊信息會被同步到其它節(jié)點(diǎn)中库北。

具體可見 Server Self Preservation Mode

當(dāng) Eureka 進(jìn)入自我保護(hù)機(jī)制的情況下航罗,會造成服務(wù)實(shí)例無法剔除的情況禀横,Client 在查詢的時候可能查詢到已經(jīng)掛掉的實(shí)例信息。

Eureka 是 peer-to-peer 模式伤哺,可能還沒同步數(shù)據(jù)過去燕侠,結(jié)果自己就死了者祖,此時還是可以繼續(xù)從別的機(jī)器上拉取注冊表,但是看到的就不是最新的數(shù)據(jù)了绢彤,但是保證了可用性七问,強(qiáng)一致,最終一致性

注冊機(jī)制:客戶端主動創(chuàng)建節(jié)點(diǎn)信息(使用 SDK 或者 HTTP 接口)

心跳機(jī)制:客戶端主動維持上報(使用 SDK 或者 HTTP 接口茫舶,默認(rèn) 30s 上報一次)

剔除機(jī)制:未收到客戶端 3 次心跳后械巡,服務(wù)端主動刪除

查詢機(jī)制:客戶端主動查詢節(jié)點(diǎn)信息(使用 SDK 或者 HTTP 接口)

Consul

關(guān)于 Consul 和其他注冊中心的對比,因?yàn)?Consul 本身出了文檔這里不在贅敘 Consul VS Other

image-20210912135836507

Consul 本身提供了 Go SDK 和 HTTP 接口, 其中包括服務(wù)注冊饶氏、健康檢查讥耗、服務(wù)查詢、kv 操作等功能的 API, 雖然沒有提供其他的語言的官方 SDK, 但也有一些個人去封裝了疹启」懦蹋或許可以使用非官方的或者自己封裝 HTTP 接口。

相對于 Eureka喊崖,Consul 提供了多種心跳機(jī)制挣磨,包括:

  1. Script + Interval
  2. HTTP + Interval
  3. TCP + Interval
  4. Time to Live (TTL)
  5. Docker + Interval
  6. gRPC + Interval
  7. H2ping + Interval
  8. Alias

注冊機(jī)制:客戶端主動創(chuàng)建節(jié)點(diǎn)信息(使用 SDK 或者 HTTP 接口)

心跳機(jī)制:服務(wù)端根據(jù)你采用心跳機(jī)制對客戶端進(jìn)行心跳測試(和 Eureka、zk 不同荤懂,這里是服務(wù)端向客戶端發(fā)起)

剔除機(jī)制:服務(wù)端未成功檢測到客戶端心跳反應(yīng)后茁裙,服務(wù)端主動刪除

查詢機(jī)制:客戶端主動查詢節(jié)點(diǎn)信息(使用 SDK 或者 HTTP 接口)

Nacos

Nacos 支持基于 DNS 和基于 RPC 的服務(wù)發(fā)現(xiàn)。服務(wù)提供者使用 原生SDK节仿、OpenAPI晤锥、或一個獨(dú)立的Agent TODO注冊 Service 后,服務(wù)消費(fèi)者可以使用DNS TODOHTTP&API查找和發(fā)現(xiàn)服務(wù)廊宪。

Nacos 提供對服務(wù)的實(shí)時的健康檢查矾瘾,阻止向不健康的主機(jī)或服務(wù)實(shí)例發(fā)送請求。Nacos 支持傳輸層 (PING 或 TCP)和應(yīng)用層 (如 HTTP箭启、MySQL霜威、用戶自定義)的健康檢查。 對于復(fù)雜的云環(huán)境和網(wǎng)絡(luò)拓?fù)洵h(huán)境中(如 VPC册烈、邊緣網(wǎng)絡(luò)等)服務(wù)的健康檢查,Nacos 提供了 agent 上報模式和服務(wù)端主動檢測2種健康檢查模式婿禽。Nacos 還提供了統(tǒng)一的健康檢查儀表盤赏僧,幫助您根據(jù)健康狀態(tài)管理服務(wù)的可用性及流量。

image
image-20210912143100127
image-20210912143531008

注冊機(jī)制:客戶端主動創(chuàng)建節(jié)點(diǎn)信息(使用 SDK 或者 HTTP 接口)

心跳機(jī)制:客戶端主動維持上報(使用 SDK 或者 HTTP 接口扭倾,默認(rèn) 30s 上報一次)

剔除機(jī)制:未收到客戶端 3 次心跳后淀零,服務(wù)端主動刪除

查詢機(jī)制:客戶端主動查詢節(jié)點(diǎn)信息(使用 SDK 或者 HTTP 接口)

Spring Cloud 是如何實(shí)現(xiàn)服務(wù)治理的

Spring Cloud Commons 之服務(wù)治理淺析

Spring 在設(shè)計(jì)的時候,通常會考慮方便擴(kuò)展和消除樣板代碼膛壹,在 Spring Clond 同樣存在這樣的設(shè)計(jì)驾中。

在 Spring Cloud 體系中唉堪,Spring Cloud Commons 是最重要的一個項(xiàng)目,其中定義了服務(wù)注冊肩民、服務(wù)發(fā)現(xiàn)唠亚、負(fù)載均衡相關(guān)的接口以及一些公共組件,通過看這個項(xiàng)目持痰,我們可以簡單的理解一下 Spring Cloud 注冊發(fā)現(xiàn)的核心流程灶搜。

Spring Clond Commons 項(xiàng)目中提供了如下的項(xiàng)目結(jié)構(gòu)(在這里省略了部分代碼文件和結(jié)構(gòu))


└── src
    ├── main
    │   ├── java
    │   │   └── org
    │   │       └── springframework
    │   │           └── cloud
    │   │               ├── client
    │   │               │   ├── DefaultServiceInstance.java
    │   │               │   ├── ServiceInstance.java  Spring Cloud 對服務(wù)實(shí)例信息的定義
    │   │               │   ├── discovery 服務(wù)發(fā)現(xiàn)相關(guān)
    │   │               │   │   ├── DiscoveryClient.java
    │   │               │   │   ├── EnableDiscoveryClient.java
    │   │               │   │   ├── EnableDiscoveryClientImportSelector.java
    │   │               │   │   ├── ManagementServerPortUtils.java
    │   │               │   │   ├── ReactiveDiscoveryClient.java
    │   │               │   │   ├── composite
    │   │               │   │   │   ├── CompositeDiscoveryClient.java
    │   │               │   │   │   ├── CompositeDiscoveryClientAutoConfiguration.java
    │   │               │   │   │   └── reactive
    │   │               │   │   │       ├── ReactiveCompositeDiscoveryClient.java
    │   │               │   │   │       └── ReactiveCompositeDiscoveryClientAutoConfiguration.java
    │   │               │   │   ├── health 健康檢查相關(guān)
    │   │               │   │       ├── DiscoveryClientHealthIndicator.java
    │   │               │   │       ├── DiscoveryClientHealthIndicatorProperties.java
    │   │               │   │       ├── DiscoveryCompositeHealthContributor.java
    │   │               │   │       ├── DiscoveryHealthIndicator.java
    │   │               │   │       └── reactive
    │   │               │   │           ├── ReactiveDiscoveryClientHealthIndicator.java
    │   │               │   │           ├── ReactiveDiscoveryCompositeHealthContributor.java
    │   │               │   │           └── ReactiveDiscoveryHealthIndicator.java
    │   │               │   ├── loadbalancer 這下面是負(fù)載均衡相關(guān)邏輯
    │   │               │   └── serviceregistry 服務(wù)注冊相關(guān)
    │   │               │       ├── AbstractAutoServiceRegistration.java
    │   │               │       ├── AutoServiceRegistration.java
    │   │               │       ├── AutoServiceRegistrationAutoConfiguration.java
    │   │               │       ├── AutoServiceRegistrationConfiguration.java
    │   │               │       ├── AutoServiceRegistrationProperties.java
    │   │               │       ├── Registration.java
    │   │               │       ├── ServiceRegistry.java
    │   │               │       ├── ServiceRegistryAutoConfiguration.java
    │   │               ├── commons
    │   │                   ├── httpclient http 工廠類,在配置中可以選擇使用 Apache Http 還是 OKHttp
    │   │                   │   ├── ApacheHttpClientFactory.java
    │   │                   │   └── OkHttpClientFactory.java
    │   │                   └── util
    │   │                      ├── IdUtils.java   通過這工具類來生成實(shí)例 id
    │   │                      └── InetUtils.java Spring Cloud 就是通過這個工具類是獲取服務(wù)項(xiàng)目的 ip 地址的
    │   └── resources
    │       └── META-INF
    │           ├── additional-spring-configuration-metadata.json
    │           └── spring.factories
    └── test
        ├── java 測試相關(guān)代碼

在項(xiàng)目結(jié)構(gòu)中可以看出各個部分對應(yīng)的源碼工窍,在服務(wù)治理中割卖,首先是服務(wù)信息 ServiceInstance , 其中包括

  • 服務(wù)名 ServiceId 這個就是我們類似的 xxx-server (spring.application.name)
  • 服務(wù)實(shí)例唯一標(biāo)識符 InstanceId
  • host
  • port
  • 一些擴(kuò)展信息 metadata, 這個主要用來提供給三方實(shí)現(xiàn)增加以下擴(kuò)展信息
// 為了縮短篇幅,刪除了一些注釋
public interface ServiceInstance {

    default String getInstanceId() {
        return null;
    }

    String getServiceId();

    String getHost();

    int getPort();

    boolean isSecure();

    URI getUri();

    Map<String, String> getMetadata();

    default String getScheme() {
        return null;
    }

}

服務(wù)注冊

Registration 是 Spring Cloud 提供的一個注冊實(shí)現(xiàn)

public interface Registration extends ServiceInstance {
    // 這里面是真沒有代碼
}

服務(wù)注冊的實(shí)際接口是 ServiceRegistry

public interface ServiceRegistry<R extends Registration> {

    /**
     * Registers the registration. A registration typically has information about an
     * instance, such as its hostname and port.
     * @param registration registration meta data
     */
    void register(R registration);

    /**
     * Deregisters the registration.
     * @param registration registration meta data
     */
    void deregister(R registration);

    /**
     * Closes the ServiceRegistry. This is a lifecycle method.
     */
    void close();

    /**
     * Sets the status of the registration. The status values are determined by the
     * individual implementations.
     * @param registration The registration to update.
     * @param status The status to set.
     * @see org.springframework.cloud.client.serviceregistry.endpoint.ServiceRegistryEndpoint
     */
    void setStatus(R registration, String status);

    /**
     * Gets the status of a particular registration.
     * @param registration The registration to query.
     * @param <T> The type of the status.
     * @return The status of the registration.
     * @see org.springframework.cloud.client.serviceregistry.endpoint.ServiceRegistryEndpoint
     */
    <T> T getStatus(R registration);

}

通過實(shí)現(xiàn) ServiceRegistry 即可完成一個簡單服務(wù)注冊功能

服務(wù)發(fā)現(xiàn)

在 discovery 下存在兩個服務(wù)發(fā)現(xiàn)定義接口 DiscoveryClientReactiveDiscoveryClient

其提供了如下功能:

  1. 獲取所有的服務(wù)名稱
  2. 根據(jù)服務(wù)名稱獲取對應(yīng)的服務(wù)實(shí)例列表
public interface DiscoveryClient extends Ordered {

    /**
     * Default order of the discovery client.
     */
    int DEFAULT_ORDER = 0;

    /**
     * A human-readable description of the implementation, used in HealthIndicator.
     * @return The description.
     */
    String description();

    /**
     * Gets all ServiceInstances associated with a particular serviceId.
     * @param serviceId The serviceId to query.
     * @return A List of ServiceInstance.
     */
    List<ServiceInstance> getInstances(String serviceId);

    /**
     * @return All known service IDs.
     */
    List<String> getServices();

    /**
     * Default implementation for getting order of discovery clients.
     * @return order
     */
    @Override
    default int getOrder() {
        return DEFAULT_ORDER;
    }

}

通過實(shí)現(xiàn) DiscoveryClient 即可完成服務(wù)發(fā)現(xiàn)

健康檢測

ReactiveDiscoveryClientHealthIndicator 提供了健康檢測功能

  1. 從 DiscoveryClient 中獲取所有的服務(wù)名列表
  2. 根據(jù)服務(wù)名列表獲取對應(yīng)的服務(wù)實(shí)例列表
  3. 對每個實(shí)例進(jìn)行健康檢測患雏,如果響應(yīng)成功則 UP 否則為 DOWN
public class ReactiveDiscoveryClientHealthIndicator
        implements ReactiveDiscoveryHealthIndicator, Ordered, ApplicationListener<InstanceRegisteredEvent<?>> {

    private final ReactiveDiscoveryClient discoveryClient;

    private final DiscoveryClientHealthIndicatorProperties properties;

    private final Log log = LogFactory.getLog(ReactiveDiscoveryClientHealthIndicator.class);

    private AtomicBoolean discoveryInitialized = new AtomicBoolean(false);

    private int order = Ordered.HIGHEST_PRECEDENCE;

    public ReactiveDiscoveryClientHealthIndicator(ReactiveDiscoveryClient discoveryClient,
            DiscoveryClientHealthIndicatorProperties properties) {
        this.discoveryClient = discoveryClient;
        this.properties = properties;
    }

    @Override
    public void onApplicationEvent(InstanceRegisteredEvent<?> event) {
        if (this.discoveryInitialized.compareAndSet(false, true)) {
            this.log.debug("Discovery Client has been initialized");
        }
    }

    @Override
    public Mono<Health> health() {
        if (this.discoveryInitialized.get()) {
            return doHealthCheck();
        }
        else {
            return Mono.just(
                    Health.status(new Status(Status.UNKNOWN.getCode(), "Discovery Client not initialized")).build());
        }
    }

    private Mono<Health> doHealthCheck() {
        // @formatter:off
        return Mono.justOrEmpty(this.discoveryClient)
                .flatMapMany(ReactiveDiscoveryClient::getServices)
                .collectList()
                .defaultIfEmpty(emptyList())
                .map(services -> {
                    ReactiveDiscoveryClient client = this.discoveryClient;
                    String description = (this.properties.isIncludeDescription())
                            ? client.description() : "";
                    return Health.status(new Status("UP", description))
                            .withDetail("services", services).build();
                })
                .onErrorResume(exception -> {
                    this.log.error("Error", exception);
                    return Mono.just(Health.down().withException(exception).build());
                });
        // @formatter:on
    }

    @Override
    public String getName() {
        return discoveryClient.description();
    }

    @Override
    public int getOrder() {
        return this.order;
    }

    public void setOrder(int order) {
        this.order = order;
    }

}

通過上面的接口定義和自帶的健康檢測邏輯可以看出做一個服務(wù)治理需要實(shí)現(xiàn)的最簡單的邏輯

  1. 實(shí)現(xiàn) ServiceRegistry 功能
  2. 實(shí)現(xiàn) DiscoveryClient 功能

Spring Cloud Consul 實(shí)現(xiàn)

實(shí)現(xiàn) ServiceRegistry 功能

在 Spring Cloud Consul 中鹏溯,首先自定義了 Registration 的實(shí)現(xiàn)

其中 NewService 為 Consul 定義的一些服務(wù)實(shí)例信息

public class ConsulRegistration implements Registration {

    private final NewService service;

    private ConsulDiscoveryProperties properties;

    public ConsulRegistration(NewService service, ConsulDiscoveryProperties properties) {
        this.service = service;
        this.properties = properties;
    }

    public NewService getService() {
        return this.service;
    }

    protected ConsulDiscoveryProperties getProperties() {
        return this.properties;
    }

    public String getInstanceId() {
        return getService().getId();
    }

    public String getServiceId() {
        return getService().getName();
    }

    @Override
    public String getHost() {
        return getService().getAddress();
    }

    @Override
    public int getPort() {
        return getService().getPort();
    }

    @Override
    public boolean isSecure() {
        return this.properties.getScheme().equalsIgnoreCase("https");
    }

    @Override
    public URI getUri() {
        return DefaultServiceInstance.getUri(this);
    }

    @Override
    public Map<String, String> getMetadata() {
        return getService().getMeta();
    }

}

NewService

其包含了服務(wù)的基本信息和 Consul 本身提供一些特有功能如:Tags、Check

// 刪除了通用的 getter淹仑、setter丙挽、toString 方法
public class NewService {
  @SerializedName("ID")
  private String id;
  @SerializedName("Name")
  private String name;
  @SerializedName("Tags")
  private List<String> tags;
  @SerializedName("Address")
  private String address;
  @SerializedName("Meta")
  private Map<String, String> meta;
  @SerializedName("Port")
  private Integer port;
  @SerializedName("EnableTagOverride")
  private Boolean enableTagOverride;
  @SerializedName("Check")
  private NewService.Check check;
  @SerializedName("Checks")
  private List<NewService.Check> checks;

  public NewService() {
  }

  public static class Check {
    @SerializedName("Script")
    private String script;
    @SerializedName("DockerContainerID")
    private String dockerContainerID;
    @SerializedName("Shell")
    private String shell;
    @SerializedName("Interval")
    private String interval;
    @SerializedName("TTL")
    private String ttl;
    @SerializedName("HTTP")
    private String http;
    @SerializedName("Method")
    private String method;
    @SerializedName("Header")
    private Map<String, List<String>> header;
    @SerializedName("TCP")
    private String tcp;
    @SerializedName("Timeout")
    private String timeout;
    @SerializedName("DeregisterCriticalServiceAfter")
    private String deregisterCriticalServiceAfter;
    @SerializedName("TLSSkipVerify")
    private Boolean tlsSkipVerify;
    @SerializedName("Status")
    private String status;
    @SerializedName("GRPC")
    private String grpc;
    @SerializedName("GRPCUseTLS")
    private Boolean grpcUseTLS;

    public Check() {
    }
  }
}

ConsulServiceRegistry 實(shí)現(xiàn) ServiceRegistry

public class ConsulServiceRegistry implements ServiceRegistry<ConsulRegistration> {

    private static Log log = LogFactory.getLog(ConsulServiceRegistry.class);

    private final ConsulClient client;

    private final ConsulDiscoveryProperties properties;

    private final TtlScheduler ttlScheduler;

    private final HeartbeatProperties heartbeatProperties;

    public ConsulServiceRegistry(ConsulClient client, ConsulDiscoveryProperties properties, TtlScheduler ttlScheduler,
            HeartbeatProperties heartbeatProperties) {
        this.client = client;
        this.properties = properties;
        this.ttlScheduler = ttlScheduler;
        this.heartbeatProperties = heartbeatProperties;
    }

    @Override
    public void register(ConsulRegistration reg) {
        log.info("Registering service with consul: " + reg.getService());
        try {
            // 同樣是通過 consul 提供的 api 接口進(jìn)行服務(wù)注冊
            this.client.agentServiceRegister(reg.getService(), this.properties.getAclToken());
            NewService service = reg.getService();
            if (this.heartbeatProperties.isEnabled() && this.ttlScheduler != null && service.getCheck() != null
                    && service.getCheck().getTtl() != null) {
                this.ttlScheduler.add(reg.getInstanceId());
            }
        }
        catch (ConsulException e) {
            if (this.properties.isFailFast()) {
                log.error("Error registering service with consul: " + reg.getService(), e);
                ReflectionUtils.rethrowRuntimeException(e);
            }
            log.warn("Failfast is false. Error registering service with consul: " + reg.getService(), e);
        }
    }

    @Override
    public void deregister(ConsulRegistration reg) {
        if (this.ttlScheduler != null) {
            this.ttlScheduler.remove(reg.getInstanceId());
        }
        if (log.isInfoEnabled()) {
            log.info("Deregistering service with consul: " + reg.getInstanceId());
        }
        this.client.agentServiceDeregister(reg.getInstanceId(), this.properties.getAclToken());
    }

    @Override
    public void close() {

    }

    @Override
    public void setStatus(ConsulRegistration registration, String status) {
        if (status.equalsIgnoreCase(OUT_OF_SERVICE.getCode())) {
            this.client.agentServiceSetMaintenance(registration.getInstanceId(), true);
        }
        else if (status.equalsIgnoreCase(UP.getCode())) {
            this.client.agentServiceSetMaintenance(registration.getInstanceId(), false);
        }
        else {
            throw new IllegalArgumentException("Unknown status: " + status);
        }

    }

  // 服務(wù)實(shí)例狀態(tài)
    @Override
    public Object getStatus(ConsulRegistration registration) {
        String serviceId = registration.getServiceId();
        Response<List<Check>> response = this.client.getHealthChecksForService(serviceId,
                HealthChecksForServiceRequest.newBuilder().setQueryParams(QueryParams.DEFAULT).build());
        List<Check> checks = response.getValue();

        for (Check check : checks) {
            if (check.getServiceId().equals(registration.getInstanceId())) {
                if (check.getName().equalsIgnoreCase("Service Maintenance Mode")) {
                    return OUT_OF_SERVICE.getCode();
                }
            }
        }

        return UP.getCode();
    }
}

ConsulDiscoveryClient 實(shí)現(xiàn) DiscoveryClient

在發(fā)現(xiàn)邏輯中也是通過 consul 提供的 api 接口進(jìn)行查詢

public class ConsulDiscoveryClient implements DiscoveryClient {

    private final ConsulClient client;

    private final ConsulDiscoveryProperties properties;

    public ConsulDiscoveryClient(ConsulClient client, ConsulDiscoveryProperties properties) {
        this.client = client;
        this.properties = properties;
    }

    @Override
    public String description() {
        return "Spring Cloud Consul Discovery Client";
    }

    @Override
    public List<ServiceInstance> getInstances(final String serviceId) {
        return getInstances(serviceId, new QueryParams(this.properties.getConsistencyMode()));
    }

    public List<ServiceInstance> getInstances(final String serviceId, final QueryParams queryParams) {
        List<ServiceInstance> instances = new ArrayList<>();

        addInstancesToList(instances, serviceId, queryParams);

        return instances;
    }

    private void addInstancesToList(List<ServiceInstance> instances, String serviceId, QueryParams queryParams) {
        HealthServicesRequest.Builder requestBuilder = HealthServicesRequest.newBuilder()
                .setPassing(this.properties.isQueryPassing()).setQueryParams(queryParams)
                .setToken(this.properties.getAclToken());
        String queryTag = this.properties.getQueryTagForService(serviceId);
        if (queryTag != null) {
            requestBuilder.setTag(queryTag);
        }
        HealthServicesRequest request = requestBuilder.build();
        Response<List<HealthService>> services = this.client.getHealthServices(serviceId, request);

        for (HealthService service : services.getValue()) {
            instances.add(new ConsulServiceInstance(service, serviceId));
        }
    }

    public List<ServiceInstance> getAllInstances() {
        List<ServiceInstance> instances = new ArrayList<>();

        Response<Map<String, List<String>>> services = this.client
                .getCatalogServices(CatalogServicesRequest.newBuilder().setQueryParams(QueryParams.DEFAULT).build());
        for (String serviceId : services.getValue().keySet()) {
            addInstancesToList(instances, serviceId, QueryParams.DEFAULT);
        }
        return instances;
    }

    @Override
    public List<String> getServices() {
        CatalogServicesRequest request = CatalogServicesRequest.newBuilder().setQueryParams(QueryParams.DEFAULT)
                .setToken(this.properties.getAclToken()).build();
        return new ArrayList<>(this.client.getCatalogServices(request).getValue().keySet());
    }

    @Override
    public int getOrder() {
        return this.properties.getOrder();
    }

}

總結(jié)

簡要的 Spring Cloud Consul 的服務(wù)治理邏輯大致如此,當(dāng)然 Spring Cloud Consul 還要處理大量的細(xì)節(jié)攻人,代碼還是很多的

在 Spring Cloud 體系中 Consul 并不提供服務(wù)請求轉(zhuǎn)發(fā)的功能取试,只是提供對服務(wù)信息的保存、查詢怀吻、健康檢測剔除功能

參考

  1. Consul 官方介紹 https://www.consul.io/docs/intro
  2. Spring Cloud Consul https://github.com/spring-cloud/spring-cloud-consul
  3. Spring Cloud Commons https://github.com/spring-cloud/spring-cloud-commons
  4. Nacos 文檔 https://nacos.io
  5. https://github.com/Netflix/eureka
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末瞬浓,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子蓬坡,更是在濱河造成了極大的恐慌猿棉,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,273評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件屑咳,死亡現(xiàn)場離奇詭異萨赁,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)兆龙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評論 3 398
  • 文/潘曉璐 我一進(jìn)店門杖爽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人紫皇,你說我怎么就攤上這事慰安。” “怎么了聪铺?”我有些...
    開封第一講書人閱讀 167,709評論 0 360
  • 文/不壞的土叔 我叫張陵化焕,是天一觀的道長。 經(jīng)常有香客問我铃剔,道長撒桨,這世上最難降的妖魔是什么查刻? 我笑而不...
    開封第一講書人閱讀 59,520評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮凤类,結(jié)果婚禮上穗泵,老公的妹妹穿的比我還像新娘。我一直安慰自己踱蠢,他們只是感情好火欧,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,515評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著茎截,像睡著了一般苇侵。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上企锌,一...
    開封第一講書人閱讀 52,158評論 1 308
  • 那天榆浓,我揣著相機(jī)與錄音,去河邊找鬼撕攒。 笑死陡鹃,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的抖坪。 我是一名探鬼主播萍鲸,決...
    沈念sama閱讀 40,755評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼擦俐!你這毒婦竟也來了脊阴?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,660評論 0 276
  • 序言:老撾萬榮一對情侶失蹤蚯瞧,失蹤者是張志新(化名)和其女友劉穎嘿期,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體埋合,經(jīng)...
    沈念sama閱讀 46,203評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡备徐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,287評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了甚颂。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蜜猾。...
    茶點(diǎn)故事閱讀 40,427評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖振诬,靈堂內(nèi)的尸體忽然破棺而出瓣铣,到底是詐尸還是另有隱情,我是刑警寧澤贷揽,帶...
    沈念sama閱讀 36,122評論 5 349
  • 正文 年R本政府宣布,位于F島的核電站梦碗,受9級特大地震影響禽绪,放射性物質(zhì)發(fā)生泄漏蓖救。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,801評論 3 333
  • 文/蒙蒙 一印屁、第九天 我趴在偏房一處隱蔽的房頂上張望循捺。 院中可真熱鬧,春花似錦雄人、人聲如沸从橘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,272評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽恰力。三九已至,卻和暖如春旗吁,著一層夾襖步出監(jiān)牢的瞬間踩萎,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工很钓, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留香府,地道東北人。 一個月前我還...
    沈念sama閱讀 48,808評論 3 376
  • 正文 我出身青樓码倦,卻偏偏與公主長得像企孩,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子袁稽,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,440評論 2 359

推薦閱讀更多精彩內(nèi)容