基於騰訊雲微服務引擎(TSE) ,輕鬆實現雲上全鏈路灰度釋出
作者:劉遠
1. 概述
軟體開發過程中,應用釋出非常頻繁,通常情況下,開發或運維人員會將系統裡所有服務同時上線,使得所有使用者都使用上新版本。這樣的操作時常會導致釋出失敗,或因釋出前修改程式碼,線上出現 Bug。
假設一個線上商城,每天都有大量的使用者訪問,如果直接在所有使用者中部署新版本應用,一旦出現問題,所有使用者都可能受到影響。相比之下,通過引入灰度釋出策略,先將新版本的應用部署到少量的使用者中,檢查是否存在問題,如果沒有,再逐步擴充套件到更多的使用者中,由此解決全量釋出的各種弊端。
灰度釋出是一種軟體釋出策略,它允許你在生產環境中漸進式部署應用,新版本只對部分使用者可見,在問題出現時儘量減少影響。在微服務體系架構中,服務間的依賴關係錯綜複雜,有時單個服務發版依賴多個服務同時執行聯動,對這個新版本服務的上下游進行分組驗證,這是微服務架構中特有的全鏈路灰度釋出場景。
使用騰訊雲微服務引擎 TSE 提供的閘道器和服務治理能力,可以在不修改任何業務程式碼的情況下,視覺化配置灰度規則,實現雲上輕鬆易用的全鏈路灰度釋出。
圖1-1 全鏈路灰度場景架構
接下來演示雲原生 API 閘道器+北極星網格構建的全鏈路灰度釋出能力。下圖模擬的雲書城應用,由4個後端的微服務組成,採用 Spring Boot + Dubbo 實現,呼叫鏈包含:收藏功能、購買功能,使用者管理功能和訂單功能。使用者通過前端頁面訪問書城,進行內容瀏覽和書籍下單。
圖1-2 雲書城前端頁面
2. 環境
2.1 雲元件版本
本次實踐採用如下元件:
● TSE-雲原生閘道器:v2.5.1
● TSE-北極星網格: v1.12.0.1
● TKE:v1.20
● APM-SkyWalking: v8.5.0
我們將應用部署在騰訊雲 TKE 叢集中,在實際生產中,全鏈路灰度對於應用的部署模式沒有限制性要求,無論是 CVM 虛擬機器,還是自建容器環境,都能應用此方案。
2.2 灰度服務準備
專案模擬書城收藏服務改版,對 addUserFavoriteBook 介面進行修改:當前基線版本點選【收藏】按鈕後,僅顯示成功收藏字樣,程式碼示例如下:
public Response<String> addUserFavoriteBook(Long userId, Long isbn) {
Response<String> resp = new Response<String>(ResponseCode.SUCCESS);
try {
FavoritesInfo entity = new FavoritesInfo(userId, isbn);
if (favoritesPersistCmpt.favoriteExist(entity)) {
resp.setMsg("已收藏(version:1.0.0)");
return resp;
}
favoritesPersistCmpt.addUserFavorite(entity);
resp.setMsg("收藏成功");
BookInfoDto dto = storeClient.getBookInfoByIsbn(isbn);
cacheCmpt.cashUserFavoriteBook(userId, dto);
} catch (Exception e) {
logger.error("failed to add FavoritesInfo", e);
resp.setFailue("服務異常,請稍後重試!");
}
return resp;
}
灰度版本修改後,頁面點選【收藏】,會詳細顯示使用者 ID 及書籍 ID,程式碼示例如下:
public Response<String> addUserFavoriteBook(Long userId, Long isbn) {
Response<String> resp = new Response<String>(ResponseCode.SUCCESS);
try {
FavoritesInfo entity = new FavoritesInfo(userId, isbn);
if (favoritesPersistCmpt.favoriteExist(entity)) {
resp.setMsg("已收藏(version:2.0.0)");
return resp;
}
favoritesPersistCmpt.addUserFavorite(entity);
resp.setMsg("使用者 userId = " + userId + " 成功收藏 book isbn = " + isbn);
BookInfoDto dto = storeClient.getBookInfoByIsbn(isbn);
cacheCmpt.cashUserFavoriteBook(userId, dto);
} catch (Exception e) {
logger.error("failed to add FavoritesInfo", e);
resp.setFailue("服務異常,請稍後重試!");
}
return resp;
}
為了方便檢視全鏈路服務當前版本,各服務將應用版本號回傳給前端,在前端頁面上顯示。
圖2-1 基線版本收藏服務
圖2-2 灰度版本收藏服務
2.3 北極星網格接入
雲書城架構中,服務發現能力目前是通過 Nacos 實現,在全鏈路灰度釋出中,服務間需要使用到治理能力,我們採用北極星網格對註冊發現功能進行替換。專案選擇 Polaris-Dubbo 框架方式接入,通過更新北極星程式碼依賴,無需修改程式碼即可完成。對比原專案,有以下幾點變化:
● 修改 POM.XML 檔案,增加北極星-Dubbo 服務註冊、服務熔斷及服務路由依賴包。
//服務註冊外掛
<dependency>
<groupId>com.tencent.polaris</groupId>
<artifactId>dubbo-registry-polaris</artifactId>
<version>${polaris.version}</version>
</dependency>
//服務熔斷外掛
<dependency>
<groupId>com.tencent.polaris</groupId>
<artifactId>dubbo-circuitbreaker-polaris</artifactId>
<version>${polaris.version}</version>
</dependency>
//服務路由外掛
<dependency>
<groupId>com.tencent.polaris</groupId>
<artifactId>dubbo-router-polaris</artifactId>
<version>${polaris.version}</version>
</dependency>
● 在配置檔案中,修改 Dubbo 應用註冊中心接入地址。
dubbo.registry.address=polaris://x.x.x.x:8091?username=polaris&password=*****
修改後的專案,程式碼保持 Dubbo 標準方式進行註冊及呼叫,無需變更。
//註冊服務(服務端)
@DubboService(version = "${provicer.service.version}")
public class ProviderServiceImpl implements ProviderService {}
//服務呼叫(消費端)
@DubboReference(version = "1.0.0")
private ProviderService providerService;
2.4 容器服務部署
完成上述修改後,對微服務應用重新編譯打包,推送至映象倉庫。在 TKE 叢集中,我們以 Deployment 方式下發應用。其中,收藏服務將基線版本和灰度版本都部署在叢集中,其他服務僅部署一個版本,使用服務治理能力進行流量路由。
● 基線版本收藏服務 YAML:
apiVersion: apps/v1
kind: Deployment
metadata:
name: favorites-service
namespace: qcbm
labels:
app: favorites-service
version: v1
spec:
replicas: 1
selector:
matchLabels:
app: favorites-service
version: v1
template:
metadata:
labels:
app: favorites-service
version: v1
spec:
containers:
- name: favorites-service
image: ccr.ccs.tencentyun.com/qcbm/favorites-service-polaris
env:
- name: MYSQL_HOST
valueFrom:
configMapKeyRef:
key: MYSQL_HOST
name: qcbm-env
optional: false
- name: REDIS_HOST
valueFrom:
configMapKeyRef:
key: REDIS_HOST
name: qcbm-env
optional: false
- name: MYSQL_ACCOUNT
valueFrom:
secretKeyRef:
key: MYSQL_ACCOUNT
name: qcbm-keys
optional: false
- name: MYSQL_PASSWORD
valueFrom:
secretKeyRef:
key: MYSQL_PASSWORD
name: qcbm-keys
optional: false
- name: REDIS_PASSWORD
valueFrom:
secretKeyRef:
key: REDIS_PASSWORD
name: qcbm-keys
optional: false
ports:
- containerPort: 20880
protocol: TCP
● 灰度版本收藏服務YAML:
apiVersion: apps/v1
kind: Deployment
metadata:
name: favorites-service-new
namespace: qcbm
labels:
app: favorites-service-new
version: v1
spec:
replicas: 1
selector:
matchLabels:
app: favorites-service-new
version: v1
template:
metadata:
labels:
app: favorites-service-new
version: v1
spec:
containers:
- name: favorites-service-new
image: ccr.ccs.tencentyun.com/qcbm/favorites-service-new-polaris
env:
- name: MYSQL_HOST
valueFrom:
configMapKeyRef:
key: MYSQL_HOST
name: qcbm-env
optional: false
- name: REDIS_HOST
valueFrom:
configMapKeyRef:
key: REDIS_HOST
name: qcbm-env
optional: false
- name: MYSQL_ACCOUNT
valueFrom:
secretKeyRef:
key: MYSQL_ACCOUNT
name: qcbm-keys
optional: false
- name: MYSQL_PASSWORD
valueFrom:
secretKeyRef:
key: MYSQL_PASSWORD
name: qcbm-keys
optional: false
- name: REDIS_PASSWORD
valueFrom:
secretKeyRef:
key: REDIS_PASSWORD
name: qcbm-keys
optional: false
ports:
- containerPort: 20880
protocol: TCP
● 前端頁面服務以 Service 方式部署,通過 Ingress 模式對接雲原生閘道器,提供叢集外部訪問能力
apiVersion: v1
kind: Service
metadata:
name: qcbm-front
namespace: qcbm
spec:
ports:
- name: http
port: 80
targetPort: 80
protocol: TCP
selector:
app: qcbm-front
version: v1
type: NodePort
2.5 雲原生閘道器接入
雲原生閘道器支援將流量直通到 Service 所在的 Pod,無需通過 NodePort 中轉。在控制檯裡繫結 TKE 叢集,輸入 Service 名,閘道器通過 Endpoint 裡收集 Pod IP,在閘道器裡自動生成 Kong Services 和 Upstream。一旦 TKE Service 發生變化,Ingress Controller 會動態更新 Upstream 裡的 Target 資訊。
後續操作基於 Kong 裡自動生成的 Services,配置基線及灰度閘道器路由規則。
圖2-3 雲原生閘道器繫結TKE叢集服務
圖2-4 雲原生閘道器自動生成 Services
圖2-5 雲原生閘道器自動生成 Upstreams
2.6 鏈路追蹤接入
單體系統時代追蹤的範圍只侷限於棧追蹤,而在微服務環境中,追蹤不只限於呼叫棧,一個外部請求需要內部若干服務的聯動,完整的一次請求會跨越多個服務。鏈路追蹤的主要目的是排查故障,如當前問題點處於呼叫鏈的哪一部分,各服務間輸入輸出是否符合預期,通過鏈路追蹤,可以檢視到服務間的網路傳輸資訊,以及各服務內部的呼叫堆疊資訊。
採用 APM 的 SkyWalking 協議方式進行上報,首先修改 SkyWalking 資料夾裡的 agent.config 檔案,配置接入點、Token 、自定義空間和服務名稱。
collector.backend_service=x.x.x.x:11800
agent.authentication=xxxxxx
agent.service_name=favorites-service-new
agent.namespace=QCBM
在 Dockerfile 中,修改應用程式的啟動命令列,以 JavaAgent 方式指定 SkyWalking Agent 的路徑 :
java -javaagent:/app/skywalking/skywalking-agent.jar -jar favorites-service-new.jar
部署後,可以在控制檯裡驗證應用拓撲正確性。
圖2-6 應用拓撲圖
3. 解決方案
通過四個階段的操作,實現收藏服務的全鏈路灰度釋出,分別是例項打標、閘道器路由、微服務路和標籤透傳。
圖3-1 全鏈路灰度釋出方案
3.1 例項打標及標籤透傳
例項打標,指的是通過例項標籤標識不同的應用,將基線版本與灰度版本區分開。一般有兩種方式進行例項打標:一是框架自動同步,將應用名,環境變數等做為例項標籤;二是用 K8S 部署時的 CRD Label 作為例項標籤。本實踐中使用 Dubbo 框架裡的 applicaiton 欄位來區分基線版本和灰度版本應用。
圖3-2 閘道器路由規則
閘道器層對灰度流量進行了染色,在微服務呼叫過程中,需要將染色標籤在每一跳進行傳遞,使得各微服務都可以識別到灰度流量,並進行正確路由處理。
圖3-3 標籤透傳示意圖
外部染色標籤在入口處,以 HTTP Header 方式存在,在 Dubbo-Gateway 服務處,編碼將 HTTP Header 轉化為 Dubbo attachment,使得染色標籤在微服務內部中繼續透傳,最終根據 attachment 裡的取值做服務間呼叫依據。
private FavoriteService add(FavoriteService favoriteService, String result) {
logger.info("header:{}", result);
RpcContext.getContext().setAttachment("gray", result == null ? "false" : result);
return favoriteService;
}
3.2 閘道器路由
閘道器作為系統流量入口,負責將外部流量按照一定的使用者特徵,切分流入灰度版本和基線版本。並對灰度流量進行染色打標,供服務治理中心動態路由規則匹配使用。在實際生產中,一般有三種分流的方法:
● 通過匹配使用者請求的 Header 引數,進行流量區分。
● 通過匹配使用者請求的 Host 特徵,進行流量區分。
● 通過流量百分比進行區分,使用雲原生閘道器能力,將其中一部分流量進行染色處理。
本次實踐針對前兩種切分方式進行介紹。
圖3-4 閘道器路由示意圖
3.3 微服務路由
北極星網格在全鏈路灰度中,充當服務治理中心的角色,解決架構中註冊發現、故障容錯、流量控制和安全問題。通過北極星網格控制檯中的配置,把基線和灰度請求,路由到不同的例項分組上,並將灰度請求固定在灰度版本服務間進行傳遞和處理。
圖3-5 動態路由示意圖
我們建立了2條服務間動態路由規則,基線和灰度請求按照不同匹配規則,路由至對應例項分組。實現中,北極星基於請求訊息內容來對請求匹配,並根據優先順序進行流量排程。
圖3-6 治理中心路由規則
4. 場景
4.1 通過Header特徵全鏈路灰度
1)場景說明
如果客戶端訪問希望統一域名,比如實踐中的 gray.qcbm.yunnative.com,我們可以通過傳入不同的 Header,把請求分別路由到基線和灰度環境。當生產環境中存在多個客戶分組,或多條灰度路由規則,也可以通過雲原生閘道器進行自定義 Header 染色,使用不同染色標籤,進行服務間路由搭配。
圖4-1 通過Header特徵全鏈路灰度
2)配置方法
在雲原生閘道器上建立兩條路由規則:
● qcbm-front-router-web,HOST為gray.qcbm.yunnative.com,Header為app:web,路由到Dubbo-Gateway服務
● qcbm-front-router-mobile,HOST為gray.qcbm.yunnative.com,Header為app:mobile,路由到Dubbo-Gateway服務,開啟染色(gray:true)
圖4-2 雲原生閘道器路由規則
服務治理中心可以直接使用現成的 app:web 或 app:mobile 標籤路由,也可以對路由請求新增染色,使用染色標籤路由,優化複雜環境管理。這裡我們開啟雲原生閘道器的 Request-Transformer 外掛,對 qcbm-front-router-mobile 路由報文進行修改,新增 gray:true 頭,使用該染色標識進行路由管理。
圖4-3 路由染色外掛
圖4-4 新增染色標識
qcbm-front-router-mobile 路由規則的請求到達 Dubbo-Gateway 後,一旦需要訪問收藏服務(FavoriteService),gray:true 染色標籤會命中北極星網格灰度服務路由,選擇呼叫 remote.application為favorites-service-new 的服務例項。此例項分組為我們部署的 favorites-service-new 灰度版本 deployment。
圖4-5 灰度服務路由
qcbm-front-router-web 路由規則的請求會命中無染色標籤的基線服務路由,呼叫 remote.application 為 favorites-service 的服務例項。此例項分組為我們部署的 favorites-service 基線版本 deployment。
圖4-6 基線服務路由
3)結果驗證
我們借用 chrome 瀏覽器外掛 ModHeader,對訪問請求按需新增 Header。
● 當新增 app:mobile 後,瀏覽器訪問 gray.qcbm.yunnative.com,我們的訪問鏈路如下:
[雲原生閘道器] --> [Dubbo-Gateway] --> [Favorite-Service-New](灰度)
頁面顯示如下:
圖4-7 灰度請求頁面
同時,也可以通過鏈路監控觀察到,gateway-service(基線服務)正確的請求到 favorite-service-new(灰度服務),同時 favorite-service-new 正確請求到 store-service(基線服務):
圖4-8 灰度請求鏈路詳情
● 當新增 app:web 後,瀏覽器訪問 gray.qcbm.yunnative.com,此時我們的訪問鏈路如下:
[雲原生閘道器] --> [Dubbo-Gateway] --> [Favorite-Service](基線)
頁面顯示如下:
圖4-9 基線請求頁面
通過鏈路監控,可以觀察到,gateway-service(基線服務)正確的請求到 favorite-service(基線服務),同時 favorite-service 正確請求到 store-service(基線服務):
圖4-10 基線請求鏈路詳情
在北極星網格中,我們可以針對鏈路的每一跳配置路由規則,每個主調服務都可以定義屬於自己的匹配規則。
4.2 通過域名特徵全鏈路灰度
1)場景說明
同樣的,也可以採用域名對請求進行區分,預期 web 端使用者採用 gray.web.yunnative.com 訪問基線環境;mobile 端使用者採用 gray.mobile.yunnative.com 訪問灰度環境。這種分流方式,適用於閘道器根據使用者登入資訊,動態分流的場景,不同的使用者在登入時,登入模組根據驗證資訊,返回302報文,給予不同的重定向域名,使用者此時使用不同的域名去訪問,雲原生閘道器通過 HOST 來做流量區分,動態染色 HTTP 請求。
圖4-11 通過域名特徵全鏈路灰度
2)配置方法
在雲原生閘道器上建立兩條路由規則:
● qcbm-front-router-web,HOST 為 gray.web.yunnative.com,路由到 Dubbo-Gateway 服務。
● qcbm-front-router-mobile,HOST 為 gray.mobile.yunnative.com,路由到 Dubbo-Gateway 服務,開啟染色(gray:true)。
和場景1類似,qcbm-front-router-mobile 路由規則的請求到達 Dubbo-Gateway 後,一旦訪問收藏服務(FavoriteService),gray:true 染色標籤會命中北極星網格灰度路由,呼叫 remote.application 為 favorites-service-new 的例項分組;而 qcbm-front-router-web 路由規則的請求會命中無染色標籤的網格基線路由,呼叫 remote.application 為 favorites-service 的例項分組,訪問基線環境。
3)結果驗證
● 瀏覽器訪問gray.mobile.yunnative.com時,染色標籤會被打上,此時訪問鏈路如下:
[雲原生閘道器] --> [Dubbo-Gateway] --> [Favorite-Service-New](灰度)
頁面顯示如下:
圖4-12 灰度請求頁面
同時,也可以通過鏈路監控觀察到,gateway-service(基線服務)正確的請求到 favorite-service-new(灰度服務),同時 favorite-service-new 正確請求到 store-service(基線服務):
圖4-13 灰度請求鏈路詳情
● 當訪問gray.web.yunnative.com時,無染色標籤,此時我們的鏈路如下:
[雲原生閘道器] --> [Dubbo-Gateway] --> [Favorite-Service](基線)
頁面顯示如下:
圖4-14 灰度請求頁面
通過鏈路監控,可以觀察到,gateway-service(基線服務)正確的請求到 favorite-service(基線服務),同時 favorite-service 正確請求到 store-service(基線服務):
圖4-15 基線請求鏈路詳情
4.3 灰度服務故障轉移
1)場景說明
在灰度釋出過程中,可以通過監測系統性能和使用者反饋來評估新功能的質量。如果新功能在測試期間表現良好,可以繼續將其推向更多使用者,替換原版本應用。如果出現任何問題,可以對灰度服務進行訪問熔斷處理,及時修復問題,然後繼續灰度測試。
圖4-16 灰度服務故障轉移
2)配置方法
在北極星網格上配置熔斷規則,配合多例項分組路由規則,實現灰度服務故障 Failover。在全鏈路灰度場景基礎上,在北極星網格控制檯加上一條熔斷規則。
● 當 delUserFavoriteBook 介面錯誤率大於10%,閾值大於10個,熔斷 Favorite-Service-New 灰度服務例項分組,同一 Deployment 裡的所有 Pod 進入半開狀態。
● 半開時間60秒,期間一旦請求成功則認為服務恢復,關閉熔斷。
圖4-17 灰度服務熔斷規則
接下來,在網格灰度路由中,新增低優先順序例項分組,該分組為基線例項。一旦灰度例項分組被熔斷,請求會去訪問基線例項分組,直到灰度服務修復,熔斷關閉。
圖4-18 灰度服務路由規則
3)結果驗證
部署一個新的“故障“收藏服務,Dubbo 程式延用 application=favorites-service-new 標籤(為區分應用,這裡故障灰度服務命名為 Favorites-Service-New-Bad),保證原路由規則可用。該“故障”程式修改了收藏服務的 delUserFavoriteBook 介面程式碼,當訪問時直接丟擲異常,模擬服務故障。程式碼如下所示:
public Response<String> delUserFavoriteBook(Long userId, Long isbn) {
String hostAddress;
try {
hostAddress = InetAddress.getLocalHost().getHostAddress();
} catch (Exception e) {
hostAddress = "ip獲取失敗";
}
throw new RuntimeException("刪除收藏-故障 ip:" + hostAddress);
}
瀏覽器訪問 gray.mobile.yunnative.com 時,此時訪問鏈路如下:
[雲原生閘道器] --> [Dubbo-Gateway] --> [Favorite-Service-New-Bad](故障灰度)
頁面顯示如下:
圖4-19 灰度請求頁面
進入收藏頁面,點選【刪除】,程式報錯,顯示呼叫異常。
圖4-20 灰度服務刪除報錯
通過鏈路追蹤,也可以檢視到服務異常。
圖4-21 應用呼叫拓撲
圖4-22 灰度服務刪除鏈路錯誤
當故障錯誤大於10次,favorite-service-new 灰度例項分組被熔斷,灰度路由進行低優先順序目標選擇,流量回源至基線例項分組favorite-service,此時測試刪除功能正常,因為此時我們的訪問鏈路重新變為:
[雲原生閘道器] --> [Dubbo-Gateway] --> [Favorite-Service](基線正常)
頁面顯示如下,服務呼叫已回源:
圖4-22 灰度請求頁面(已回源)
5. 總結
在灰度釋出實施前,需要按照如下三方面,對整體流程進行計劃:
● 目標:在開始灰度釋出之前,需要了解發布的目標是什麼,比如說是測試新版本的效能,還是功能相容性等,以便在灰度時進行對應的測試和觀測。
● 灰度策略:有很多種灰度策略可供選擇,例如按使用者特徵來灰度、按流量來灰度、按地域來灰度等。通過系統使用者的特點,選擇最合適的灰度策略。
● 灰度範圍:在灰度釋出過程中,應當能隨時控制哪些使用者可以訪問新版本,當出現問題時,能將請求快速回滾到舊版本上。
灰度釋出過程中,確認流量是否已經按計劃切換到灰度例項分組,通過監控和日誌,檢查各服務是否正常執行,是否符合預期。
確定本次釋出成功後,可以依次對老版本分組的例項進行滾動升級,多次升級完成灰度釋出,一旦出現錯誤執行回退,有序控制釋出節奏。最後,根據實際應用情況,刪除或保留閘道器和治理中心的動態路由規則。
騰訊雲TSE提供了完整的全鏈路灰度釋出解決方案,適用各種釋出流程,無需侵入程式碼,通過視覺化配置灰度規則,有效地解決了微服務全鏈路灰度釋出難實現的問題,讓灰度釋出更便捷、更順利。
- Apache Pulsar 技術系列 - Pulsar 總覽
- 解決創新業務的三大架構難題,央廣購物用對了這個關鍵策略
- 詳解 Apache Pulsar 訊息生命週期
- 8年服務百萬客戶,這家SaaS公司是懂雲原生的
- 基於騰訊雲微服務引擎(TSE) ,輕鬆實現雲上全鏈路灰度釋出
- 騰訊雲基於 Apache Pulsar 跨地域複製功能實現租戶跨叢集遷移
- 面向異構技術棧和基礎設施的服務治理標準化
- Pulsar 在騰訊雲的穩定性實踐
- 迎接2023 | 北極星開源一週年,感恩禮傾情相送
- Apache Pulsar 技術系列 – 基於不同部署策略和配置策略的容災保障
- 輕量級SaaS化應用資料鏈路構建方案的技術探索及落地實踐
- 微服務架構下路由、多活、灰度、限流的探索與挑戰
- PolarisMesh北極星 V1.11.3 版本釋出
- 千億級、大規模:騰訊超大 Apache Pulsar 叢集效能調優實踐
- Apache Pulsar 系列 —— 深入理解 Bookie GC 回收機制
- 騰訊雲微服務引擎 TSE 產品動態
- 千億級、大規模:騰訊超大 Apache Pulsar 叢集效能調優實踐
- TSF微服務治理實戰系列(三)——服務限流
- 如何解決 Spring Cloud 下測試環境路由問題
- TSF微服務治理實戰系列(二)——服務路由