Spring Cloud 整合 Consul 實現服務註冊、發現

採用微服務架構的系統,由按照業務劃分、職責單一的多個服務單元組成,並且每個服務可能又有多個服務實例。相比傳統單體架構,微服務服務單元粒度比較小、服務數量較多、服務之間的依賴比較複雜,服務之間調用往往會形成網狀結構,爲了更好地統一管理服務實例,註冊中心應運而生。

服務註冊是指服務提供者向註冊中心註冊服務實例,主要將其服務信息如服務名稱、IP 地址、端口等註冊到註冊中心。

服務發現是指服務消費者需要調用其他服務時,註冊中心將服務提供者的服務信息如服務名稱、IP 地址、端口等告知服務消費者。

enter image description here

微服務開發過程中,註冊中心至關重要,可以說它是整個微服務架構系統的心臟。當前常用的註冊中心有 Consul、Zookeeper、Eureka、etcd 等,各常用註冊中心的關鍵特點見下圖所示。

enter image description here

從上表可以看出,無論是 Consul 提供的主要功能,還是 Spring Cloud 對其集成的支持,都比較完善,且運維的複雜度也較低,比如對容器化技術 Docker 有比較好的支持。而 Eureka 在設計上比較符合場景,但仍需持續的完善。綜上分析,本課實戰環節中,我最終選擇了 Consul 作爲服務的註冊中心。如需要了解 Consul 的更多詳情,請訪問官網

Windows 環境中安裝 Consul

我們首先到 Consul 官網下載64位 Consul 安裝包 consul_1.2.1_windows_amd64.zip,如果讀者 PC 是32位的,下載 Consul 32位安裝包即可。

然後,將安裝包解壓到磁盤的某一目錄下,我選擇解壓在 F:\springCloud 目錄下,解壓後的目錄爲 F:\springCloud\consul_1.2.1_windows_amd64。完成後,再將當前路勁添加到環境變量 PATH 中。

之後,進入 CMD,運行 consul agent -dev 啓動 Consul 服務端。

在瀏覽器輸入地址:http://localhost:8500,看到如下界面,則表示安裝成功。

enter image description here

利用 Spring Cloud 及 Consul 實現服務註冊

首先,在 pom 文件中添加依賴 Consul 客戶端、Spring Cloud 依賴:

<!--Consul 客戶端依賴 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery
</artifactId>
</dependency>

<!-- Spring Cloud 依賴-->
<dependencyManagement>
<dependencies>
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-dependencies</artifactId>
    <version>${spring-cloud.version}</version>
    <type>pom</type>
    <scope>import</scope>
    </dependency>
</dependencies>
</dependencyManagement>

注意: 添加 Spring Cloud 依賴時,選擇的 Spring Cloud 版本必須和 Spring Boot 版本相匹配,否則程序啓動時會報錯。版本對應關係如下:

enter image description here

本課中,選擇了 Spring Cloud 的穩定版本 Dalston,因此 Spring Boot 應選擇 1.5.14.RELEASE 版本。

然後,在 application.yml 配置文件中添加如下內容:

server:
  ##服務在8080端口暴露出來
  port: 8080

spring:
  cloud:
    consul:
      ##Consul所在主機ip
      host: localhost
      ##Consul監聽端口
      port: 8500
      discovery:
        ##配置服務註冊到Consul上
        register: true
        ##配置服務健康檢查地址,供Consul調用
        healthCheckPath: /health
        ##Consul 健康檢查頻率
        healthCheckInterval: 2s
        ##配置註冊到Consul服務id
        instance-id: order-service
  application:
    ##應用服務名稱
    name: order-service

之後,在 controller 包中添加 HealthCheckController,並添加供 Consul 調用的健康檢查接口:

@RestController
public class HealthCheckController {

    //RequestMapping中的url地址需和配置文件中保持一致
    @RequestMapping("/health")
    public String healthCheck(){
        return "ok";
    }
}

接着,在 Lesson6Application 類上添加使應用啓動並向 Consul 註冊服務的註解 @EnableDiscoveryClient

@SpringBootApplication
@EnableDiscoveryClient
public class Lesson6Application {
    public static void main(String[] args) {
        SpringApplication.run(Lesson6Application.class, args);
    }
}

最後,在 CMD 中執行命令 consul agent -dev 啓動 Consul 註冊中心,之後再啓動應用程序,觀察到此時 order-service 服務已經註冊到註冊中心,如下圖所示。

enter image description here

通過以上過程,我們已將服務註冊到 Consul 註冊中心。當前我們只是會應用了,但對利用 Consul 進行服務註冊的源碼不太清楚。接下來,我將向大家介紹下服務註冊的主要源碼,以便大家在遇到服務註冊相關問題時能更好地解決。

(1)在主類上必須添加註解 @EnableDiscoveryClient,只有這樣,服務註冊、心跳檢測相關配置信息才能被自動加載。

(2)內嵌 Tomcat 容器啓動後,發佈 EmbeddedServletContainerInitializedEvent 事件:

protected void finishRefresh() {
    super.finishRefresh();
    //啓動內嵌tomcat容器
    EmbeddedServletContainer localContainer = startEmbeddedServletContainer();
    if (localContainer != null) {
        //發佈EmbeddedServletContainerInitializedEvent事件
        publishEvent(
                    new EmbeddedServletContainerInitializedEvent(this, localContainer));
        }
    }

(3)AbstractDiscoveryLifecycle 類監聽 EmbeddedServletContainerInitializedEvent 事件,並調用其 start 方法將服務註冊到 Consul 註冊中心上:

public void start() {
    //判斷是否需要將服務註冊到註冊中心
    if (!isEnabled()) {
        if (logger.isDebugEnabled()) {
            logger.debug("Discovery Lifecycle disabled. Not starting");
            }
            return;
        }

    if (this.port.get() != 0 && getConfiguredPort() == 0)  {
        setConfiguredPort(this.port.get());
    }

    if (!this.running.get() && getConfiguredPort() > 0) {
            //調用register方法註冊服務
            register();
            if (shouldRegisterManagement()) {
                registerManagement();
            }

            this.context.publishEvent(new InstanceRegisteredEvent<>(this,
                    getConfiguration()));
            this.running.compareAndSet(false, true);
        }
    }

(4)第三步中的 register 方法最終調用 AgentConsulClient 類中的 agentServiceRegister 方法註冊服務:

public Response<Void> agentServiceRegister(NewService newService, String token) {
    UrlParameters tokenParam = token != null ? new SingleUrlParameters("token", token) : null;
    //json格式服務參數    
    String json = GsonFactory.getGson().toJson(newService);
    //拼接url地址,利用httpClient請求Consul服務端註冊服務
    RawResponse rawResponse = rawClient.makePutRequest("/v1/agent/service/register", json, tokenParam);

    if (rawResponse.getStatusCode() == 200) {
        return new Response<Void>(null, rawResponse);
    } else {
        throw new OperationException(rawResponse);
    }
}

以上四個步驟給出了服務註冊的主要代碼,有興趣的讀者可以按照上面思路跟蹤代碼做更進一步地學習。

利用 Spring Cloud 及 Consul 客戶端實現服務發現

爲了演示服務發現實現過程,我們再新建一個項目 lesson6-client,同時在 pom 文件中也添加 Consul 客戶端依賴:

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>

然後,在 application.yml 中添加配置:

spring:
  application:
    name: service-client
  cloud:
    consul:
      ##consul服務端所在pc機ip
      host: localhost
      ##consul服務端監聽端口
      port: 8500
      discovery:
        ##不需要註冊到consul上
        register: false

之後,在主程序類上添加註解 @EnableDiscoveryClient

接着,新建 ApiController,通過服務名稱從註冊中心中獲取訂單服務的 IP 和 Port(服務發現:根據服務名稱從註冊中心獲取訂單服務 IP 及端口),並利用 RestTemplate 實例根據訂單 ID 獲取訂單信息接口 getOrderDetailInfoById,並進行調用,代碼如下:

@RequestMapping("/api")
@RestController
public class ApiController {
    @Autowired
    private LoadBalancerClient loadBalancer;

    @RequestMapping("/getOrderDetailInfoById")
    public Object getOrderDetailInfoById(Integer orderId){
    //根據服務名稱從註冊中心獲取訂單服務order-service實例
    ServiceInstance serviceInstance = loadBalancer.choose("order-service");

    //拼接調用訂單服務(order-service)接口url地址
    String url = "http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort()
                + "/order/getOrderDetailInfoById?orderId={1}";
    //利用RestTemplate實例調用訂單服務接口
    return new RestTemplate().getForObject(url,OrderEntity.class,100);
    }
}

最後,啓動應用程序,並利用 Postman 調用接口:

http://localhost:8081/api/getOrderDetailInfoById?orderId=100

輸出結果如下:

enter image description here

結語

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章