Spring Cloud 之 Eureka.
一、微服務概述
1. 什麼是微服務
簡單地說, 微服務是系統架構上的一種設計風格, 它的主旨是將一個原本獨立的系統拆分成多個小型服務,這些小型服務都在各自獨立的程序中執行,服務之間基於 RPC 進行通訊協作。 被拆分成的每一個小型服務都圍繞著系統中的某一項或一些耦合度較高的業務功能進行構建, 並且每個服務都維護著自身的資料儲存(劃重點,每個微服務都有自己的資料庫例項)、 業務開發、自動化測試案例以及獨立部署機制。
2. 微服務的特性
- 服務元件化:一個獨立的系統拆成多個小型服務。
- 以業務劃分服務:微服務應該以業務來劃分,而不是按能力或其他因素來劃分(比如之前做的一個專案直接將快取能力建成了一個微服務元件)。
- 智慧端點和啞管道:服務之間通過 RPC 的方式呼叫,通常會使用以下兩種服務呼叫方式:
第一種:使用 HTTP 的 RESTfl API 或輕量級的訊息傳送協議, 實現資訊傳遞與服務呼叫的觸發。
第二種:通過在輕量級訊息總線上傳遞訊息, 類似 RabbitMQ 等 一些提供可靠非同步交換的中介軟體。 - 去中心化處理:不同的微服務元件可以選擇不同的技術方案,甚至可以選擇不同的語言。只有實現了對技術平臺的透明, 才能更好地發揮不同語言對不同業務處理能力的優勢, 從而打造更為強大的大型系統。
- 去中心化管理資料:採用分散式資料庫,不同的微服務元件有著自己單獨的資料庫例項。雖然資料管理的去中心化可以讓資料管理更加細緻化,通過採用更合適的技術可讓資料儲存和效能達到最優。但是,由於資料儲存於不同的資料庫例項中後,資料一致性也成為微服務架構中亟待解決的問題之一。
- 基礎設施自動化:自動化測試(每次部署前的強心劑, 儘可能地獲得對正在執行的軟體的信心)、自動化部署(解放煩瑣枯燥的重複操作以及對多環境的配置管理)等。
- 容錯設計:在微服務架構中,快速檢測出故障源並儘可能自動恢復服務是必須被設計和考慮的。
- 演進式設計:沒有必要一開始就把服務拆分的又細又多,可以隨著業務系統的發展,把壓力大的服務或者穩定不變化的模組做拆分合並動作。
2. 微服務的缺陷
- 運維的成本提高。這個是不可避免的,原來只需要運維一個單一獨立的系統,現在要管理幾個或者幾十個微服務。
- 介面的一致性問題。A 微服務修改了介面,需要呼叫方B、C 服務同時做出修改。除非開發過程中嚴格遵守開閉原則(在這個敏捷流式開發的背景下,幾乎很難做到)。
- 分散式的複雜性。網路延遲、分散式事務、非同步訊息等。
tips:分散式事務本身的實現難度就非常大,所以在微服務架構中,我們更強調在各服務之間進行 “ 無事務 ” 的呼叫,而對於資料一致性,只要求資料在最後的處理狀態是一致的即可;若在過程中發現錯誤,通過補償機制來進行處理,使得錯誤資料能夠達到最終的一致性。
二、Spring Cloud 簡介
Spring Cloud 是一個基於Spring Boot實現的微服務架構開發工具。它為微服務架構中涉及的配置管理、服務治理、斷路器、智慧路由、微代理、控制匯流排、全域性鎖、決策競選、分散式會話和叢集狀態管理等操作提供了一種簡單的開發方式。
Spring Cloud 的出現,可以說是對微服務架構的巨大支援和強有力的技術後盾。它是一個解決微服務架構實施的綜合性解決框架,它整合了諸多被廣泛實踐和證明過的框架作為實施的基礎部件,又在該體系基礎上建立了一些非常優秀的邊緣元件。舉個 Dubbo 和 Spring Cloud 差異性的例子:在使用 Dubbo 開發過程中,分散式配置中心(百度的 Disconf、Netflix的Archaius、360的QConf、淘寶的 Diamond 等)、連結跟蹤(京東的 Hydra、Twitter的 Zipkin 等)...一系列需要的元件,我都要去找第三方進行整合,還要考慮版本相容的問題。而 Spring Cloud 就是一個微服務解決方案的“全家桶”,幾乎我需要的全部微服務元件,我都能在其中找到“原裝元件”:分散式配置中心(Config)、連結跟蹤(Sleuth)、批量任務(Task),而且可以完美相容。
三、Eureka 簡介
服務治理體系可以說是微服務架構中最為核心和基礎的模組, 它主要用來實現各個微服務例項的自動化註冊與發現。服務治理體系中的三個核心角色: 服務註冊中心、 服務提供者以及服務消費者。而 Eureka Server 就承擔了 Spring Cloud 的服務註冊中心。接下來捋一捋 Eureka Server 進行服務治理的過程:
- 服務註冊:”服務提供者”在啟動的時候會通過傳送 REST 請求的方式將自己註冊到 Eureka Server 上,同時帶上了自身服務的一些元資料資訊(hostName 之類的)。Eureka Server 接收到這個 REST 請求之後,將元資料資訊儲存在一個雙層結構Map中,其中第一層的key是服務名,第二層的key是具體服務的例項名。
- 服務同步:由於服務註冊中心之間互相註冊為服務(Eureka Server 高可用場景),當服務提供者傳送註冊請求到一個服務註冊中心時,它會將該請求轉發給叢集中相連的其他註冊中心, 從而實現註冊中心之間的服務同步。通過服務同步,兩個服務提供者的服務資訊就可以通過這兩臺服務註冊中心中的任意一臺獲取到。
- 服務續約:在註冊完服務之後,“服務提供者”會維護一個心跳用來持續告訴 Eureka Sever "我還活著 ”, 以防止Eureka Server 的 “ 剔除任務 ” 將該服務例項從服務列表中排除出去。
- 服務消費:當我們啟動“服務消費者”的時候,它會發送一個 REST 請求給服務註冊中心,來獲取上面註冊的服務清單 。為了效能考慮, Eureka Serer會維護一份只讀的服務清單來返回給客戶端,同時該快取清單會每隔30秒更新一次。
- 服務呼叫:“服務消費者”在 獲取服務清單後,通過服務名可以獲得具體提供服務的例項名和該例項的元資料資訊。因為有這些服務例項的詳細資訊,所以客戶端可以根據自己的需要決定具體呼叫哪個例項,在 Ribbon 中會預設採用輪詢的方式進行呼叫,從而實現客戶端的負載均衡。
- 服務下線:服務例項進行正常的關閉操作時,它會觸發一個服務下線的 REST 請求給 Eureka Server,告訴服務註冊中心:“我要下線了 ”。服務端在接收到請求之後,將該服務狀態置為下線(DOWN), 並把該下線事件傳播出去。
- 失效剔除:有些時候,我們的服務例項並不一定會正常下線,可能由於記憶體溢位、網路故障等原因使得服務不能正常工作,而服務註冊中心並未收到 “服務下線 ” 的請求。為了從服務列表中將這些無法提供服務的例項剔除,Eureka Server 在啟動的時候會建立一個定時任務,預設每隔 一段時間(預設為60秒) 將當前清單中超時(預設為90秒)沒有續約的服務剔除出去。
- 自我保護:Eureka Server在執行期間,會統計心跳失敗的比例在15分鐘之內是否低於85%, 如果出現低於的情況(在單機除錯的時候很容易滿足, 實際在生產環境上通常是由於網路不穩定導致),Eureka Server 會將當前的例項註冊資訊保護起來, 讓這些例項不會過期, 儘可能保護這些註冊資訊。
四、Eureka 實戰
SpringBoot 版本號:2.1.6.RELEASE
SpringCloud 版本號:Greenwich.RELEASE
1. 服務註冊中心
- pom.xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
<version>2.1.0.RELEASE</version>
</dependency>
- application.yml
server:
port: 1111
eureka:
instance:
hostname: localhost
prefer-ip-address: true
client:
# 表示不向註冊中心註冊自己
register-with-eureka: false
# 註冊中心的職責是維護例項,不需要去檢索服務
fetch-registry: false
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
server:
# 是否要開啟自我保護機制
enable-self-preservation: true
eureka 的配置項主要有三項:instance、client、server。“instance”維護該服務的例項資訊,包括 hostname、port 這類描述例項特徵的元資料資訊;“client”主要是服務註冊資料的配置,比如超時時間、服務快取時間等;“server”是服務註冊中心特有的配置,配置 Eureka Server 的相關配置項,比如上面的是否開啟自我保護。
- Application.java
//啟動一個服務註冊中心
@EnableEurekaServer
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
至此,我們一個Eureka Server — 服務註冊中心就搭建好了。
2. 服務提供者
- pom.xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
- application.yml
server:
port: 2222
spring:
application:
name: cloud-eureka-client
eureka:
# 服務註冊相關的配置資訊
client:
service-url:
defaultZone: http://localhost:1111/eureka/
instance:
# 是否優先使用IP地址作為主機名的標識
prefer-ip-address: true
就這樣,我們的一個 Eureka Client 算是註冊到 Eureka Server 上了。接下來,讓我們試試用 DiscoveryClient 發現我們的服務資訊:
// 自動化配置, 建立 DiscoveryClient 介面針對 Eureka 客戶端的 EurekaDiscoveryClient 例項
@EnableDiscoveryClient
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application .class, args);
}
}
@RestController
public class HelloController {
private final Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private DiscoveryClient discoveryClient;
@Value("${spring.application.name}")
private String serviceId;
@RequestMapping(value = "/hello", method = RequestMethod.GET)
public String index() {
List<ServiceInstance> instances = discoveryClient.getInstances(serviceId);
ServiceInstance instance = instances.get(0);
logger.info("/hello, host:" + instance.getHost() + ", serviceId:" + instance.getServiceId());
return "Hello World";
}
}
3. 服務消費者
有了服務註冊中心和服務提供者,我們試試用 RestTemplate 呼叫一次服務吧!
@RestController
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;
@RequestMapping("/ribbon-consumer")
public String helloConsumer() {
// 這裡訪問的是服務名,而不是一個具體的地址(為了實現負載均衡策略),在服務治理框架中,這是一個非常重要的特性。
ResponseEntity<String> result = restTemplate.getForEntity("http://cloud-eureka-client/hello", String.class);
return result.getBody();
}
}
五、附加
- 預設情況下,Eureka 使用 Jersey 和 XStream 配合 JSON 作為 Server 與 Client 之間的通訊協議。
- YAML 的意思其實是: Yet Another Markup Language — 仍是一種標記語言(這個看著有點想笑)。
- 在 SpringBoot 的屬性配置檔案中,可以通過使用 ${random} 配置來產生隨機的 int 值、long 值或者 string 字串。
- 在 Spring Boot 中,多環境配置的檔名需要滿足 application-{profile}.properties的格式, 其中{profile}對應你的環境標識。通過啟動引數 --spring.profiles.active=test 來指定啟用的環境變數。