個人簡介

劉思賢(微博@starlight36),愛油科技架構師、PMP。主要負責業務平臺架構設計,DevOps實施和研發過程持續改進等,關注領域驅動設計與微服務、建設高效團隊和工程師文化培養。

摘要

本次分享主要介紹了愛油科技基於Docker和Spring Cloud將整體業務微服務化的一些實踐經驗,主要包括:

  • 微服務架構的分層和框架選型
  • 服務發現和配置管理
  • 服務整合和服務質量保證
  • 基於領域驅動設計
  • 實施DevOps

    從單體應用到微服務

單體應用

優點

  • 小而美,結構簡單易於開發實現
  • 部署門檻低,單個Jar包或者網站打包即可部署
  • 可快速實現多例項部署

缺點

  • 隨著業務發展更多的需求被塞進系統,體系結構逐漸被侵蝕反應堆林立
  • 被技術綁架,難以為特定業務選擇平臺或框架,儘管可能有更適宜的技術做這件事
  • 協作困難,不同業務的團隊在一個系統上進行開發相互衝突
  • 難以擴充套件,為了熱點業務而不得不同時擴容全部業務,或者難以繼續擴容

架構拆分

拆分:按行分層,按列分業務

在我們的微服務體系中,所有的服務被劃分為了三個層次:

  1. 基礎設施層:為所有業務提供基礎設施,包括服務註冊、資料庫和NoSQL、物件儲存、訊息佇列等基礎設施服務,這一層通常是由成熟元件、第三方服務組成。
  2. 業務服務層:業務微服務,根據業務領域每個子域單獨一個微服務,分而治之。
  3. 接入層:直接對外提供服務,例如網站、API介面等。接入層不包含複雜的業務邏輯,只做呈現和轉換。

專案中我們主要關注業務服務層和接入層,對於沒有足夠運維力量的我們,基礎設施使用雲服務是省事省力的選擇。

業務服務層我們給他起名叫作Epic,接入層我們起名Rune,建立之初便訂立了如下原則:

  1. 業務邏輯層內所有服務完全對等,可相互呼叫
  2. 業務邏輯層所有服務必須是無狀態的
  3. 接入層所有服務可呼叫業務邏輯層所有服務,但接入層內部同層服務之間不可呼叫
  4. 接入層不能包含業務邏輯程式碼
  5. 所有微服務必須執行在Docker容器裡

業務邏輯層我們主要使用使用Java,接入層我們主要使用PHP或Node。後來隨著團隊的成長,逐步將接入層全部遷移至Node。

框架選型

愛油科技作為一家成品油行業的初創型公司,需要面對非常複雜的業務場景,而且隨著業務的發展,變化的可能性非常高。所以在微服務架構設計之初,我們就期望我們的微服務體系能:

  • 不繫結到特定的框架、語言
  • 服務最好是Restful風格
  • 足夠簡單,容易落地,將來能擴充套件
  • 和Docker相容性好

目前常見的微服務相關框架:

  • Dubbo、DubboX
  • Spring Cloud
  • Motan
  • Thrift、gRPC

這些常見的框架中,Dubbo幾乎是唯一能被稱作全棧微服務框架的“框架”,它包含了微服務所需的幾乎所有內容,而DubboX作為它的增強,增加了REST支援。

它優點很多,例如:

  • 全棧,服務治理的所有問題幾乎都有現成答案
  • 可靠,經過阿里實踐檢驗的產品
  • 實踐多,社群有許多成功應用Dubbo的經驗

不過遺憾的是:

  • 已經停止維護
  • 不利於裁剪使用
  • “過於Java”,與其他語言相容性一般

Motan是微博平臺微服務框架,承載了微博平臺千億次呼叫業務。

優點是:

  • 效能好,源自於微博對高併發和實時性的要求
  • 模組化,結構簡單,易於使用
  • 與其他語言相容性好

不過:

  • 為“短平快”業務而生,即業務簡單,追求高效能高併發。

Apache Thrift、gRPC等雖然優秀,並不能算作微服務框架,自身並不包括服務發現等必要特性。

如果說微服務少不了Java,那麼一定少不了Spring,如果說少不了Spring,那麼微服務“官配”Spring Cloud當然是值得斟酌的選擇。

優點:

  • “不做生產者,只做搬運工”
  • 簡單方便,幾乎零配置
  • 模組化,鬆散耦合,按需取用
  • 社群背靠Spring大樹

不足:

  • 輕量並非全棧
  • 沒解決RPC的問題
  • 實踐案例少

根據我們的目標,我們最終選擇了Spring Cloud作為我們的微服務框架,原因有4點:

  1. 雖然Dubbo基礎設施更加完善,但結構複雜,我們很難吃得下,容易出坑
  2. 基於Apache ThriftgRPC自研,投入產出比很差
  3. 不想過早引入RPC以防濫用,Restful風格本身就是一種約束。
  4. 做選擇時,Motan還沒有釋出

Spring Cloud

Spring Cloud是一個整合框架,將開源社群中的框架整合到Spring體系下,幾個重要的家族專案:

  • spring-boot,一改Java應用程式執行難、部署難,甚至無需Web容器,只依賴JRE即可
  • spring-cloud-netflix,整合Netflix優秀的元件Eureka、Hystrix、Ribbon、Zuul,提供服務發現、限流、客戶端負載均衡和API閘道器等特性支援
  • spring-cloud-config,微服務配置管理
  • spring-cloud-consul,整合Consul支援

服務發現和配置管理

Spring Cloud Netflix提供了Eureka服務註冊的整合支援,不過沒選它是因為:

  • 更適合純Java平臺的服務註冊和發現
  • 仍然需要其他分散式KV服務做後端,沒解決我們的核心問題

Docker作為支撐平臺的重要技術之一,Consul幾乎也是我們的必選服務。因此我們覺得一事不煩二主,理所應當的Consul成為我們的服務註冊中心。

Consul的優勢:

  • 使用Raft一致性演算法,能保證分散式叢集內各節點狀態一致
  • 提供服務註冊、服務發現、服務狀態檢查
  • 支援HTTP、DNS等協議
  • 提供分散式一致性KV儲存

也就是說,Consul可以一次性解決我們對服務註冊發現、配置管理的需求,而且長期來看也更適合跟不同平臺的系統,包括和Docker排程系統進行整合。

最初打算自己開發一個Consul和Spring Cloud整合的元件,不過幸運的是,我們做出這個決定的時候,spring-cloud-consul剛剛釋出了,我們可以拿來即用,這節約了很多的工作量。

因此藉助Consul和spring-cloud-consul,我們實現了

  • 服務註冊,引用了srping-cloud-consul的專案可以自動註冊服務,也可以通過HTTP介面手動註冊,Docker容器也可以自動註冊
  • 服務健康狀態檢查,Consul可以自動維護健康的服務列表
  • 異構系統可以直接通過Consul的HTTP介面拉取並監視服務列表,或者直接使用DNS解析服務
  • 通過分散式一致性KV儲存進行微服務的配置下發
  • 為一些業務提供選主和分散式鎖服務

當然也踩到了一些坑:

spring-cloud-consul服務註冊時不能正確選判本地ip地址。對於我們的環境來說,無論是在伺服器上,還是Docker容器裡,都有多個網路介面同時存在,而spring-cloud-consul在註冊服務時,需要先選判本地服務的IP地址,判斷邏輯是以第一個非本地地址為準,常常錯判。因此在容器中我們利用entrypoint指令碼獲取再通過環境變數強制指定。

1.
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/usr/bin/env bash
set -e
# If service runs as Rancher service, auto set advertise ip address
# from Rancher metadata service.
if [ -n "$RUN_IN_RANCHER" ]; then
echo "Waiting for ip address..."
# Waiting for ip address
sleep 5
RANCHER_MS_BASE=http://rancher-metadata/2015-12-19
PRIMARY_IP=`curl -sSL $RANCHER_MS_BASE/self/container/primary_ip`
SERVICE_INDEX=`curl -sSL $RANCHER_MS_BASE/self/container/service_index`
if [ -n "$PRIMARY_IP" ]; then
export SPRING_CLOUD_CONSUL_DISCOVERY_HOSTNAME=$PRIMARY_IP
fi
echo "Starting service #${SERVICE_INDEX-1} at $PRIMARY_IP."
fi

我們的容器執行在Rancher中,所以可以利用Rancher的metadata服務來獲取容器的IP地址,再通過SPRING_CLOUD_CONSUL_DISCOVERY_HOSTNAME環境變數來設定服務發現的註冊地址。基於其他容器排程平臺也會很相似。

另外一些服務中內建了定時排程任務等,多例項啟動時需要單節點執行排程任務。通過Consul的分散式鎖服務,我們可以讓獲取到鎖的節點啟用排程任務,沒獲取到的節點等待獲取鎖。

服務整合

為了方便開發人員使用,微服務框架應當簡單容易使用。對於很多微服務框架和RPC框架來說,都提供了很好的機制。在Spring Cloud中通過OpenFeign實現微服務之間的快速整合:

服務方宣告一個Restful的服務介面,和普通的Spring MVC控制器幾乎別無二致:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@RestController
@RequestMapping("/users")
public class UserResource {
@RequestMapping(value = "{id}", method = RequestMethod.GET, produces = "application/json")
public UserRepresentation findOne(@PathVariable("id") String id) {
User user = this.userRepository.findByUserId(new UserId(id));
if (user == null || user.getDeleted()) {
throw new NotFoundException("指定ID的使用者不存在或者已被刪除。");
}
return new UserRepresentation(user);
}
}

相關文章