APP實(shí)時(shí)配置系統(tǒng)【達(dá)達(dá)技術(shù)】

APP實(shí)時(shí)配置系統(tǒng)

背景

隨著達(dá)達(dá)業(yè)務(wù)的快速發(fā)展,產(chǎn)品上經(jīng)常需要對(duì)APP的邏輯進(jìn)行更精準(zhǔn)快速的變更,最初通過發(fā)布新版本的方式來調(diào)整邏輯,由于APP版本更新覆蓋需要一定的周期,這樣做的效果不是很理想。后面我們采用了友盟在線參數(shù)來對(duì)一些參數(shù)進(jìn)行動(dòng)態(tài)配置,但友盟的在線參數(shù)只是一個(gè)簡單的key-value配置,無法為配置的key附帶更多的業(yè)務(wù)邏輯(比如限定城市和用戶),并且配置變更以后也無法立即通知APP,因此我們?cè)O(shè)計(jì)了達(dá)達(dá)APP配置系統(tǒng)。

目的

從產(chǎn)品上來說,APP配置就是需要準(zhǔn)確及時(shí)的針對(duì)不同類型的用戶,執(zhí)行不同的業(yè)務(wù)邏輯。在達(dá)達(dá)的業(yè)務(wù)場景下,我們經(jīng)常會(huì)對(duì)APP按城市和用戶的維度做一些配置。例如:

控制一個(gè)功能入口的顯示和隱藏

比如我們會(huì)對(duì)某些優(yōu)質(zhì)配送員開放達(dá)達(dá)商城入口,讓他們可以購買達(dá)達(dá)裝備;而有些配送員則不開放。

配置APP中的文案

根據(jù)不同的運(yùn)營策略,我們經(jīng)常要實(shí)時(shí)調(diào)整我們APP內(nèi)的文案。

下面左圖是顯示達(dá)達(dá)商城功能,右圖是隱藏達(dá)達(dá)商城功能:

從技術(shù)上來講,需要滿足如下要求:

實(shí)時(shí)性

服務(wù)端的配置發(fā)生更新,必須要在盡可能短的時(shí)間內(nèi)通知APP進(jìn)行配置的變更,尤其對(duì)于需要盡快生效的配置更為重要,比如線上服務(wù)器壓力比較大,需要及時(shí)通知APP增加刷新訂單的等待時(shí)間以降低服務(wù)器壓力,如果該配置通知到APP的時(shí)間比較長,那這條配置也就沒有什么意義了,所以實(shí)時(shí)性也是非常重要的一個(gè)要求。同時(shí)配置變更通知也需要對(duì)用戶保證透明性,讓用戶在無感知的情況下進(jìn)行配置變更。

高并發(fā)

該系統(tǒng)面向的是達(dá)達(dá)公司所有的APP設(shè)備,設(shè)備數(shù)量是非常多的,如果配置發(fā)生更新,必須要考慮大量設(shè)備同時(shí)訪問服務(wù)端來更新配置對(duì)服務(wù)器造成的壓力,以及在這種高并發(fā)訪問下如何保證服務(wù)的可用性。

低客戶端流量使用

因?yàn)樵撓到y(tǒng)主要面向APP端,流量因素也是需要考慮的一個(gè)問題,為了節(jié)省客戶端流量,APP獲取更新時(shí)不需要每次更新都獲取全部配置,只需要獲取在上次更新后發(fā)生變更的字段,需要支持配置的增量刪除/新增/修改等功能

設(shè)計(jì)及實(shí)現(xiàn)

根據(jù)需求我們?cè)O(shè)計(jì)并實(shí)現(xiàn)了達(dá)達(dá)APP實(shí)時(shí)配置系統(tǒng),包含服務(wù)端,Android端和iOS端。

APP配置系統(tǒng)的設(shè)計(jì)如下圖所示:

服務(wù)端的架構(gòu)主要分為數(shù)據(jù)庫層/緩存層(本地緩存層+Redis緩存層)/配置計(jì)算層

配置信息存儲(chǔ)在數(shù)據(jù)庫中

使用Redis存儲(chǔ)緩存配置信息減小直接讀取數(shù)據(jù)庫的壓力,同時(shí)使用本地緩存與Redis同步,配置直接從本地緩存讀取,減少網(wǎng)絡(luò)IO次數(shù),提高系統(tǒng)性能,減少請(qǐng)求響應(yīng)時(shí)間

配置計(jì)算層主要用來進(jìn)行配置的篩選工作,根據(jù)不同的請(qǐng)求條件篩選對(duì)應(yīng)配置,并實(shí)現(xiàn)配置的增量更新等功能

在與APP的交互方面,在安卓端我們主要使用了HTTP+推送的方案,每當(dāng)配置發(fā)生更新,會(huì)通過信鴿推送發(fā)送一條透傳消息給APP,APP收到推送后,執(zhí)行HTTP請(qǐng)求拉取配置。iOS端因?yàn)锳PNS推送的字節(jié)限制,以及APP離線時(shí)無法控制讓推送內(nèi)容不在通知欄顯示等原因,所以采用了Socket長連接方案來實(shí)現(xiàn),APP啟動(dòng)時(shí)會(huì)建立一條長連接通道,每當(dāng)配置發(fā)生更新,服務(wù)端會(huì)主動(dòng)將更新的配置發(fā)送給客戶端。

實(shí)時(shí)性

為滿足實(shí)時(shí)性的要求,我們?cè)瓉泶蛩闶褂猛扑蛠韺?shí)現(xiàn),但是使用推送會(huì)面臨兩個(gè)問題:

推送到達(dá)時(shí)間無法估計(jì)

iOS推送如果應(yīng)用不在線,會(huì)在通知欄展示,無法滿足對(duì)用戶透明的需求。

因?yàn)橐陨蟽蓚€(gè)問題,我們對(duì)iOS使用了長連接來實(shí)現(xiàn)配置的主動(dòng)推送,APP啟動(dòng)后會(huì)建立一條與服務(wù)器端的長連接,并維持心跳,每當(dāng)服務(wù)器發(fā)生配置變更,會(huì)實(shí)時(shí)的通知在線的APP更新配置,如果APP離線會(huì)在在線后收到配置更新的推送,保證配置更新的通知不會(huì)在通知欄展示,既解決了實(shí)時(shí)性的要求,也滿足了對(duì)用戶透明的需要。

高并發(fā)

為了解決前面所描述的短時(shí)間高并發(fā)的問題,我們主要從兩個(gè)方面來解決這個(gè)問題:

APP端:收到配置變更的通知后,會(huì)隨機(jī)延遲0-30s再去服務(wù)器拉取配置,防止瞬間的高并發(fā)訪問導(dǎo)致服務(wù)端異常

服務(wù)端:為了提升服務(wù)端性能,減少網(wǎng)絡(luò)IO的時(shí)間,增加了本地緩存層,數(shù)據(jù)直接從本地緩存存取,本地緩存與Redis保持同步更新,使用Zookeeper的節(jié)點(diǎn)監(jiān)聽功能,當(dāng)Redis發(fā)生修改時(shí)主動(dòng)讓Zookeeper通知本地緩存進(jìn)行配置數(shù)據(jù)的更新同步;

低客戶端流量使用

為了解決節(jié)省流量的問題,我們使用了如下兩個(gè)方案:

使用ProtocolBuf進(jìn)行數(shù)據(jù)傳輸

使用該工具的原因主要有以下兩點(diǎn):

序列化和反序列化性能非常高

序列化后字節(jié)數(shù)少,非常適合移動(dòng)端的數(shù)據(jù)傳輸

基于以上兩個(gè)特點(diǎn),使用該框架可以很好的解決流量和性能的問題。

配置增量更新

所有的配置都帶有一個(gè)版本號(hào),配置變更會(huì)基于版本號(hào)進(jìn)行變動(dòng),APP端會(huì)持有一個(gè)本地的版本號(hào),每次更新請(qǐng)求,在請(qǐng)求服務(wù)端時(shí)會(huì)將APP本地的版本號(hào)提供給服務(wù)端,服務(wù)端根據(jù)APP的本地版本號(hào)與服務(wù)器的版本號(hào)進(jìn)行配置的計(jì)算和合并后將發(fā)生變動(dòng)的配置發(fā)送給客戶端,避免每次將所有配置發(fā)送給客戶端,只要發(fā)送發(fā)生變動(dòng)的配置即可。

服務(wù)端

為了實(shí)現(xiàn)前面所描述的低流量的需要,我們使用了增量更新的方案來減少客戶端的流量消耗,基于版本號(hào)的方式實(shí)現(xiàn)了配置的增量更新,方案如下:

配置版本(增量更新)的設(shè)計(jì)

客戶端保存一個(gè)版本號(hào)用于標(biāo)識(shí)APP當(dāng)前配置的版本,每次APP增量更新配置的時(shí)候會(huì)將這個(gè)版本號(hào)發(fā)送給服務(wù)端,服務(wù)端返回該版本號(hào)之后發(fā)生的所有變動(dòng)

服務(wù)端實(shí)現(xiàn)邏輯:

計(jì)算從版本0開始到最新版本之間的有效配置

計(jì)算從APP版本號(hào)到最新版本之間刪除的配置

將生效配置和刪除配置作配置合并操作,生成最后需要更新的增量配置

配置合并的邏輯如下:

有效配置中有,刪除項(xiàng)中沒有,若版本≤APP的本地版本號(hào),該配置無變動(dòng),不需要通知客戶端

有效配置中有,刪除項(xiàng)中沒有,若版本>APP的本地版本號(hào),該配置有變動(dòng),需要通知客戶端更新配置

有效配置中有,刪除項(xiàng)中也有,配置可能變動(dòng),需要通知客戶端更新配置

有效配置中沒有,刪除項(xiàng)中有,配置項(xiàng)被刪除,需要通知客戶端刪除配置

Android端

設(shè)計(jì)結(jié)構(gòu)圖如下:

要點(diǎn)如下:

使用了推送的方式來保證APP能夠?qū)崟r(shí)更新配置

使用了sqlite來同步更新服務(wù)器的配置,作為客戶的讀取配置的數(shù)據(jù)源

為了安全性和靈活性,使用了本地廣播(LocalBroadcast)的方式來通知前臺(tái)頁面(Activity)實(shí)時(shí)更新界面

核心代碼如下:

ConfigList configList = responseBody.getContentAs(ConfigList.class);

if(!Arrays.isEmpty(configList.getResult())) {

for(Config config : configList.getResult()) {

config.setUserId(HttpInterceptor.getUserId());

Config localConfig = getConfig(config.getParamName());

Intent intent = new Intent();

intent.setAction(action(config.getParamName()));

if(config.isDelete()) {

//本地有,但服務(wù)器配置已被刪除,本地也要被刪除

if(localConfig != null)

db.delete(Config.class

, WhereBuilder.b("paramName","=",

config.getParamName())

.and("userId","=", HttpInterceptor.getUserId()));

intent.putExtra(Extras.CONFIG, config);

}elseif(localConfig == null) {

//本地沒有,則新增一個(gè)配置

db.save(config);

intent.putExtra(Extras.CONFIG, config);

}else{

//本地有,服務(wù)器上配置被修改,本地也要被修改

localConfig.setParamValue(config.getParamValue());

db.update(localConfig,"paramValue");

intent.putExtra(Extras.CONFIG, localConfig);

}

localBroadcastManager.sendBroadcast(intent);

}

}

代碼主要邏輯如下:

如果從服務(wù)端拿到一個(gè)刪除的配置,則刪除本地配置

如果從服務(wù)端拿到一個(gè)新增的配置,則插入本地?cái)?shù)據(jù)庫

否則,則更新本地的配置

最后,發(fā)送配置變更廣播通知相應(yīng)的界面更新UI

iOS端

設(shè)計(jì)結(jié)構(gòu)圖如下:

要點(diǎn)如下:

與服務(wù)端維持長連接,保證配置實(shí)時(shí)更新到客戶端

archive配置信息,優(yōu)先從本地讀取,減少異常情況下對(duì)業(yè)務(wù)的不利影響

業(yè)務(wù)邏輯通過KVO方式使用配置信息,保證客戶端配置更新時(shí),業(yè)務(wù)邏輯及時(shí)響應(yīng)

核心代碼如下:

- (void)readCommonConfigWithKey:(NSString*)keyString

configType:(configType)type

Finish:(finishReadConfigInfo)finish {

if(isEmptyString(keyString)) {

finish(nil);

return;

}

DDAppConfigModel*configModel = nil;

if(type==configTypeCommon) {

configModel = [self.commonConfigDictInfo objectForKey:keyString];

if(configModel == nil) {

[self updateConfigWithKey:keyStringtype:typefinish:finish];

}else{

dispatch_async(dispatch_get_main_queue(), ^{

finish(configModel);

});

}

}elseif(type==configTypeAppoint) {

[self updateConfigWithKey:keyStringtype:typefinish:finish];

}

}

代碼主要邏輯如下:

業(yè)務(wù)邏輯請(qǐng)求配置時(shí),優(yōu)先從內(nèi)存讀取

內(nèi)存沒有,從本地?cái)?shù)據(jù)存儲(chǔ)中查找

本地沒有,向服務(wù)端拉取配置信息

最后,通過block方式通知調(diào)用方,并返回結(jié)果

小結(jié)

根據(jù)上面的設(shè)計(jì),達(dá)達(dá)APP配置系統(tǒng)已經(jīng)實(shí)現(xiàn)了如下功能:

可以針對(duì)所有用戶、某個(gè)城市的用戶、某個(gè)平臺(tái)(iOS,Android)的用戶、某個(gè)APP(達(dá)達(dá),商家,派樂趣)的用戶以及某些指定id的用戶配置參數(shù),來實(shí)現(xiàn)不同用戶不同客戶端的業(yè)務(wù)邏輯定制

配置生效后,可以給配置用戶發(fā)送推送,讓APP主動(dòng)獲取配置的變更,并實(shí)時(shí)對(duì)APP的邏輯進(jìn)行調(diào)整

APP每次去服務(wù)器拉取配置的時(shí)候,服務(wù)器會(huì)根據(jù)客戶端已有的配置信息返回配置變更的增量,以便節(jié)省流量。

更多關(guān)于達(dá)達(dá)技術(shù)的文章,敬請(qǐng)關(guān)注達(dá)達(dá)技術(shù)公眾號(hào)。

#Android#App#iOS#配置系統(tǒng)#長連接

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,506評(píng)論 19 139
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,810評(píng)論 25 709
  • 點(diǎn)擊查看原文 Web SDK 開發(fā)手冊(cè) SDK 概述 網(wǎng)易云信 SDK 為 Web 應(yīng)用提供一個(gè)完善的 IM 系統(tǒng)...
    layjoy閱讀 14,288評(píng)論 0 15
  • 國家電網(wǎng)公司企業(yè)標(biāo)準(zhǔn)(Q/GDW)- 面向?qū)ο蟮挠秒娦畔?shù)據(jù)交換協(xié)議 - 報(bào)批稿:20170802 前言: 排版 ...
    庭說閱讀 12,309評(píng)論 6 13
  • 讀了今天的文章,不禁要感慨,莫道君行早,更有早行人。 身處大郊區(qū),九點(diǎn)上班,班車十分鐘只有的路程,所以剛開始工作的...
    小多媛媛閱讀 274評(píng)論 0 0

友情鏈接更多精彩內(nèi)容