參考:appollo官方
文章目錄
- 如何啓動源碼
- 一、apollo整體架構
- 二、portal service發佈配置後,如何通知client
- 1、配置發佈後的實時推送過程
- 步驟一、用戶在Portal操作配置發佈
- 步驟二、Portal調用Admin Service的接口操作發佈
- 步驟三、Admin Service發佈配置後,發送ReleaseMessage給各個Config Service
- 步驟四、Config Service收到ReleaseMessage後,通知對應的客戶端
- Config Service通知客戶端的實現方式:
- 1、客戶端會發起一個Http請求到Config Service的notifications/v2接口,也就是NotificationControllerV2,參見RemoteConfigLongPollService
- 2、NotificationControllerV2不會立即返回結果,而是通過Spring DeferredResult把請求掛起
- 3、如果在60秒內沒有該客戶端關心的配置發佈,那麼會返回Http狀態碼304給客戶端
- 4、如果有該客戶端關心的配置發佈,NotificationControllerV2會調用DeferredResult的setResult方法,傳入有配置變化的namespace信息,同時該請求會立即返回。客戶端從返回的結果中獲取到配置變化的namespace後,會立即請求Config Service獲取該namespace的最新配置。
- 三、客戶端監聽配置原理
- 1、客戶端和服務端保持了一個長連接,從而能第一時間獲得配置更新的推送。(通過Http Long Polling實現)
- 2、客戶端還會定時從Apollo配置中心服務端拉取應用的最新配置。
- 3、客戶端從Apollo配置中心服務端獲取到應用的最新配置後,會保存在內存中
- 4、客戶端會把從服務端獲取到的配置在本地文件系統緩存一份
- 5、應用程序可以從Apollo客戶端獲取最新的配置、訂閱配置更新通知
- 四、整個流程
- 1、通知客戶端配置有更新:是通過 deferredResults 這個對象實現的。
- 2、核心方法如下:
如何啓動源碼
1、源碼結構
2、初始化庫
3、啓動apollo-assembly
application啓動配置如下:
VM options:
-Denv=dev
-Dspring.profiles.active=github
-Deureka.service.url=http://localhost:8080/eureka/
-Dspring.datasource.url=jdbc:mysql://localhost:3309/ApolloConfigDB?characterEncoding=utf8
-Dspring.datasource.username=root
-Dspring.datasource.password=123456
-Dlogging.file=D:/logs/apollo-assembly.log
Program arguments:
–configservice --adminservice
這些配置其實可以參考官方的項目apollo-build-scripts項目
這個項目有一個deom.sh腳本如下:不需要完全看懂所有意思,只需看重點如下:
# meta server url定義的一些變量
config_server_url=http://localhost:8080
admin_server_url=http://localhost:8090
eureka_service_url=$config_server_url/eureka/
portal_url=http://localhost:8070
# JAVA OPTS BASE_JAVA_OPTS:基礎變量
BASE_JAVA_OPTS="-Denv=dev"
CLIENT_JAVA_OPTS="$BASE_JAVA_OPTS -Dapollo.meta=$config_server_url"
#config service和admin service所需的VM options配置參數
SERVER_JAVA_OPTS="$BASE_JAVA_OPTS -Dspring.profiles.active=github -Deureka.service.url=$eureka_service_url"
#portal service所需的VM options配置參數
PORTAL_JAVA_OPTS="$BASE_JAVA_OPTS -Ddev_meta=$config_server_url -Dspring.profiles.active=github,auth -Deureka.client.enabled=false -Dhibernate.query.plan_cache_max_size=192"
#啓動config service和admin service時所帶的Program arguments參數
$SERVICE_JAR start --configservice --adminservice
啓動報錯
注意apollo-assembly這個項目是將config 和admin一起啓動的, 並且是先啓動config 再啓動admin。因爲config這個服務中整合了eureka 服務端,所以要先把eureka服務啓動,然後config 和admin再註冊到eureka。
注:在啓動apollo-configservice的過程中會在日誌中輸出eureka註冊失敗的信息,如com.sun.jersey.api.client.ClientHandlerException: java.net.ConnectException: Connection refused。需要注意的是,這個是預期的情況,因爲apollo-configservice需要向Meta Server(它自己)註冊服務,但是因爲在啓動過程中,自己還沒起來,所以會報這個錯。後面會進行重試的動作,所以等自己服務起來後就會註冊正常了。
4、啓動apollo-portal
application啓動配置如下:
VM options:
-Denv=dev
-Ddev_meta=http://localhost:8080
-Dspring.profiles.active=github,auth
-Deureka.client.enabled=false
-Dhibernate.query.plan_cache_max_size=192
-Dserver.port=8170
-Dspring.datasource.url=jdbc:mysql://localhost:3309/ApolloPortalDB?characterEncoding=utf8
-Dspring.datasource.username=root
-Dspring.datasource.password=123456
-Dlogging.file=D:/logsh/apollo-portal.log
Program arguments:
–portal
這些配置也同樣參考官方的項目apollo-build-scripts項目的deom.sh腳本如下:不需要完全看懂所有意思,只需看重點如下:
#portal service所需的VM options配置參數
PORTAL_JAVA_OPTS="$BASE_JAVA_OPTS -Ddev_meta=$config_server_url -Dspring.profiles.active=github,auth -Deureka.client.enabled=false -Dhibernate.query.plan_cache_max_size=192"
echo "==== starting portal ===="
echo "Portal logging file is $PORTAL_LOG"
# 導入VM options配置參數
export JAVA_OPTS="$PORTAL_JAVA_OPTS -Dlogging.file=./apollo-portal.log -Dserver.port=8070 -Dspring.datasource.url=$apollo_portal_db_url -Dspring.datasource.username=$apollo_portal_db_username -Dspring.datasource.password=$apollo_portal_db_password"
# 啓動portal service 的參數
$PORTAL_JAR start --portal
一、apollo整體架構
apollo服務端大概有3個服務:
- Config Service提供配置的讀取、推送等功能,服務對象是Apollo客戶端
- Admin Service提供配置的修改、發佈等功能,服務對象是Apollo Portal(管理界面)
- Config Service和Admin Service都是多實例、無狀態部署,所以需要將自己註冊到Eureka中並保持心跳
- 在Eureka之上我們架了一層Meta Server用於封裝Eureka的服務發現接口
- Client通過域名訪問Meta Server獲取Config Service服務列表(IP+Port),而後直接通過IP+Port訪問服務,同時在Client側會做load balance、錯誤重試
- Portal通過域名訪問Meta Server獲取Admin Service服務列表(IP+Port),而後直接通過IP+Port訪問服務,同時在Portal側會做load balance、錯誤重試
- 爲了簡化部署,我們實際上會把Config Service、Eureka和Meta Server三個邏輯角色部署在同一個JVM進程中
二、portal service發佈配置後,如何通知client
1、配置發佈後的實時推送過程
步驟一、用戶在Portal操作配置發佈
步驟二、Portal調用Admin Service的接口操作發佈
步驟三、Admin Service發佈配置後,發送ReleaseMessage給各個Config Service
Admin Service發送ReleaseMessage方式:
1、 Admin Service在配置發佈後會往ReleaseMessage表插入一條消息記錄,消息內容就是配置發佈的AppId+Cluster+Namespace,參見DatabaseMessageSender
2、Config Service有一個線程會每秒掃描一次ReleaseMessage表,看看是否有新的消息記錄,參見ReleaseMessageScanner
3、Config Service如果發現有新的消息記錄,那麼就會通知到所有的消息監聽器(ReleaseMessageListener),如NotificationControllerV2,消息監聽器的註冊過程參見ConfigServiceAutoConfiguration
4、NotificationControllerV2得到配置發佈的AppId+Cluster+Namespace後,會通知對應的客戶端
步驟四、Config Service收到ReleaseMessage後,通知對應的客戶端
Config Service通知客戶端的實現方式:
1、客戶端會發起一個Http請求到Config Service的notifications/v2接口,也就是NotificationControllerV2,參見RemoteConfigLongPollService
2、NotificationControllerV2不會立即返回結果,而是通過Spring DeferredResult把請求掛起
3、如果在60秒內沒有該客戶端關心的配置發佈,那麼會返回Http狀態碼304給客戶端
4、如果有該客戶端關心的配置發佈,NotificationControllerV2會調用DeferredResult的setResult方法,傳入有配置變化的namespace信息,同時該請求會立即返回。客戶端從返回的結果中獲取到配置變化的namespace後,會立即請求Config Service獲取該namespace的最新配置。
三、客戶端監聽配置原理
1、客戶端和服務端保持了一個長連接,從而能第一時間獲得配置更新的推送。(通過Http Long Polling實現)
2、客戶端還會定時從Apollo配置中心服務端拉取應用的最新配置。
- 這是一個fallback機制,爲了防止推送機制失效導致配置不更新
- 客戶端定時拉取會上報本地版本,所以一般情況下,對於定時拉取的操作,服務端都會返回304 - Not Modified
- 定時頻率默認爲每5分鐘拉取一次,客戶端也可以通過在運行時指定System Property: apollo.refreshInterval來覆蓋,單位爲分鐘。
3、客戶端從Apollo配置中心服務端獲取到應用的最新配置後,會保存在內存中
4、客戶端會把從服務端獲取到的配置在本地文件系統緩存一份
- 在遇到服務不可用,或網絡不通的時候,依然能從本地恢復配置
5、應用程序可以從Apollo客戶端獲取最新的配置、訂閱配置更新通知
四、整個流程
1、通知客戶端配置有更新:是通過 deferredResults 這個對象實現的。
deferredResults 對象被定成NotificationControllerV2類的成員變量。
客戶端會發起一個長連接到config service服務,url對應了NotificationControllerV2.#pollNotification 方法,並返回DeferredResult對象, 返回這個對象 ,那麼請求並不會立即返回,直到DeferredResult.setResult 被設置值 ,請求才會返回。
那麼這個對象DeferredResult.setResult 何時調用?
- 當配置發佈—》
- 插入消息表----》
- config service 定時描消息表-----》
- 有新的發佈消息-----》
- 調用監聽方法NotificationControllerV2.#handleMessage-----》
- 設置DeferredResult.setResult ------->
- 客戶端調用的NotificationControllerV2.#pollNotification方法------》
- 感受到DeferredResult.setResult 設置,立即返回,否則60s沒有配置更新 ,那麼超時返回304
2、核心方法如下:
@RestController
@RequestMapping("/notifications/v2")
public class NotificationControllerV2 implements ReleaseMessageListener {
//定義deferredResults 成員變量,設置成同步集合,防止併發
//DeferredResultWrapper類型是DeferredResult的包裝類,裏面持有DeferredResult類型的變量
private final Multimap<String, DeferredResultWrapper> deferredResults =
Multimaps.synchronizedSetMultimap(TreeMultimap.create(String.CASE_INSENSITIVE_ORDER, Ordering.natural()));
}
//客戶端定時獲取 最新配置url方法
@GetMapping
public DeferredResult<ResponseEntity<List<ApolloConfigNotification>>> pollNotification(
@RequestParam(value = "appId") String appId,
@RequestParam(value = "cluster") String cluster,
@RequestParam(value = "notifications") String notificationsAsString,
@RequestParam(value = "dataCenter", required = false) String dataCenter,
@RequestParam(value = "ip", required = false) String clientIp) {
// 解析 notificationsAsString 參數,創建 ApolloConfigNotification 數組。
List<ApolloConfigNotification> notifications = null;
//創建deferredResultWrapper 對象
DeferredResultWrapper deferredResultWrapper = new DeferredResultWrapper(bizConfig.longPollingTimeoutInMilli());
deferredResultWrapper.onCompletion(() -> {
// 當這個對象resultWrapper完成setReuslt設置, 註銷已經返回的監聽
for (String key : watchedKeys) {
deferredResults.remove(key, deferredResultWrapper);
}
});
//註冊DeferredResultWrapper 到 `deferredResults` 中,等待配置發生變化後通知。詳見 `#handleMessage(...)` 方法。
for (String key : watchedKeys) {
this.deferredResults.put(key, deferredResultWrapper);
}
//返回DeferredResult對象,直到#handleMessage監聽方法中設置了值返回
//或等待超時返回304
return deferredResultWrapper.getResult();
}
//配置發佈後的通知方法
@Override
public void handleMessage(ReleaseMessage message, String channel) {
logger.info("message received - channel: {}, message: {}", channel, message);
List<DeferredResultWrapper> results = Lists.newArrayList(deferredResults.get(content));
if (results.size() > bizConfig.releaseMessageNotificationBatch()) {
largeNotificationBatchExecutorService.submit(() -> {
logger.debug("Async notify {} clients for key {} with batch {}", results.size(), content,
bizConfig.releaseMessageNotificationBatch());
for (int i = 0; i < results.size(); i++) {
//如果有配置更新,那麼調用了DeferredResultWrapper類對象的setResult方法,
//這個方法就是獲取內部的成量DeferredResult 並設置值。
results.get(i).setResult(configNotification);
}
});
return;
}
}