採用微服務架構的系統,由按照業務劃分、職責單一的多個服務單元組成,並且每個服務可能又有多個服務實例。相比傳統單體架構,微服務服務單元粒度比較小、服務數量較多、服務之間的依賴比較複雜,服務之間調用往往會形成網狀結構,爲了更好地統一管理服務實例,註冊中心應運而生。
服務註冊是指服務提供者向註冊中心註冊服務實例,主要將其服務信息如服務名稱、IP 地址、端口等註冊到註冊中心。
服務發現是指服務消費者需要調用其他服務時,註冊中心將服務提供者的服務信息如服務名稱、IP 地址、端口等告知服務消費者。
微服務開發過程中,註冊中心至關重要,可以說它是整個微服務架構系統的心臟。當前常用的註冊中心有 Consul、Zookeeper、Eureka、etcd 等,各常用註冊中心的關鍵特點見下圖所示。
從上表可以看出,無論是 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
,看到如下界面,則表示安裝成功。
利用 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 版本相匹配,否則程序啓動時會報錯。版本對應關係如下:
本課中,選擇了 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 服務已經註冊到註冊中心,如下圖所示。
通過以上過程,我們已將服務註冊到 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
輸出結果如下: