前不久作為架構(gòu)師完成了某知名快消企業(yè)的一個業(yè)務(wù)中臺建設(shè)。系統(tǒng)上線后,經(jīng)歷了雙十一活動的流量高峰,整體運行穩(wěn)定。最近有空,便將此次架構(gòu)的思路,心得稍作整理在這篇博客中分享一下。不會深入每一個技術(shù)細(xì)節(jié),而是把用到的技術(shù)、框架、工具做一個簡單的回顧,作為日后的參考。
業(yè)務(wù)架構(gòu)
業(yè)務(wù)架構(gòu)方面,該系統(tǒng)作為業(yè)務(wù)中臺,主要負(fù)責(zé)客戶資產(chǎn)管理,包括客戶的卡、券以及其他虛擬資產(chǎn)。通過對外暴露標(biāo)準(zhǔn)restful接口的方式提供服務(wù)。服務(wù)的調(diào)用方包括自有渠道的App、小程序,以及合作伙伴渠道,包括招行、阿里等。而系統(tǒng)本身也會通過服務(wù)網(wǎng)關(guān)去調(diào)用公司內(nèi)部的其他業(yè)務(wù)系統(tǒng)接口,如通過客戶中心接口同步會員信息等。
根據(jù)目前的統(tǒng)計,這個業(yè)務(wù)中臺,每日的服務(wù)調(diào)用量在700萬次左右,有活動時也會超過1000萬次。而大部分交易,發(fā)生在上班、午休以及下午3點左右(下午茶)的時間段內(nèi)。
?
由于涉及到客戶業(yè)務(wù)細(xì)節(jié),這里對業(yè)務(wù)架構(gòu)就不做詳細(xì)說明了。
技術(shù)架構(gòu)
這個案例中采用了基于SpringBoot的微服務(wù)架構(gòu)。結(jié)合企業(yè)自身的基礎(chǔ)架構(gòu)設(shè)施,進(jìn)行K8S容器化部署,并采用Kong API Gateway對各業(yè)務(wù)中臺暴露的API接口進(jìn)行統(tǒng)一管理。
Kong API Gateway
隨著微服務(wù)架構(gòu)在企業(yè)中的流行,原來大而全的系統(tǒng)被拆分為粒度較小的中臺,而系統(tǒng)中的大部分功能則被以restful API形式提供的服務(wù)所取代,這使得IT系統(tǒng)能夠更加快速地響應(yīng)業(yè)務(wù)變化帶來的挑戰(zhàn),但同時隨著服務(wù)的增加,如何有效管理這些服務(wù)卻成為難題。
?
在一些中小型項目中,我們一般都會采用Spring Cloud的技術(shù)棧,并選擇Spring Cloud Gateway來作服務(wù)網(wǎng)關(guān)。然而,對于一些大型企業(yè),則需要全局考慮服務(wù)的治理,網(wǎng)關(guān)性能,以及其他擴展功能。
在這個案例中,企業(yè)使用了Kong作為API網(wǎng)關(guān)。中臺將需要開放外部使用的API,通過網(wǎng)關(guān)控制臺進(jìn)行注冊,添加證書,生成Auth Key供關(guān)聯(lián)方使用。
Kong具有以下一些特性,能夠很好地滿足大型組織對于服務(wù)網(wǎng)關(guān)的需求:
- 開源(本案例中使用的是Kong的企業(yè)版,提供了原廠服務(wù))
- 亞毫秒級的響應(yīng)延遲,得益于基于Nginx與OpenResty帶來的超高性能
- 單節(jié)點25K TPS
- 認(rèn)證、授權(quán)、限流、數(shù)據(jù)轉(zhuǎn)換(此案例中會員ID被添加到請求頭中)、日志、統(tǒng)計分析
?
應(yīng)用架構(gòu)
整個系統(tǒng)采用JAVA開發(fā)后端以及vue開發(fā)前端,應(yīng)用部分共分為4個服務(wù)組件,全部進(jìn)行容器化部署,并通過Ingress Controller負(fù)載均衡對外暴露服務(wù):
- 資產(chǎn)服務(wù):提供客戶資產(chǎn)相關(guān)的服務(wù)接口
- 資產(chǎn)消費者服務(wù):MQ監(jiān)聽服務(wù),異步處理資產(chǎn)相關(guān)請求
- 控制臺服務(wù):資產(chǎn)管理運維類服務(wù)接口,供控制臺前端使用
- 控制臺前端服務(wù):使用Vue開發(fā)的控制臺前端應(yīng)用(如下圖)
?
SpringBoot
除控制臺前端外,其他三個組件均采用目前主流的java微服務(wù)框架SpringBoot 2.3.4開發(fā)(考慮到穩(wěn)定性,未使用最新的2.4版本)。
本案例中,通過開發(fā)應(yīng)用框架,實現(xiàn)了系統(tǒng)中數(shù)據(jù)表達(dá)形式的統(tǒng)一,以及標(biāo)準(zhǔn)的據(jù)轉(zhuǎn)換、校驗、消息綁定、錯誤處理等功能。架構(gòu)師需要對應(yīng)用框架負(fù)責(zé),簡明、高效、統(tǒng)一的應(yīng)用框架,能夠提升開發(fā)效率,產(chǎn)出標(biāo)準(zhǔn)一致的代碼,保證交付質(zhì)量。
應(yīng)用框架不在本文的討論范圍內(nèi),而以下一些技巧或第三方包,卻在我們構(gòu)建大多數(shù)SpringBoot應(yīng)用中得到使用。
####定制MyBatis 數(shù)據(jù)層框架采用MyBatis,在大型應(yīng)用中MyBatis能夠幫助程序員更好地控制數(shù)據(jù)層交互,并進(jìn)行調(diào)優(yōu)。一般可以在applicaion.yml中配置MyBatis,但當(dāng)我們需要讓MyBatis支持更多定制特性(如:多數(shù)據(jù)庫支持)時,可以通過定義SqlSessionFactory bean來實現(xiàn)。
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean sfb = new SqlSessionFactoryBean();
sfb.setDataSource(dataSource);
sfb.setVfs(SpringBootVFS.class);
Properties props = new Properties();
props.setProperty("dialect", dataConfiguration.getDialect());
props.setProperty("reasonable", String.valueOf(dataConfiguration.isPageReasonable()));
PageHelper pagePlugin = new PageHelper();
pagePlugin.setProperties(props);
Interceptor[] plugins = {pagePlugin};
sfb.setPlugins(plugins);
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
sfb.setMapperLocations(resolver.getResources("classpath*:mappers/"+ dataConfiguration.getDialect()+"/*.xml"));
sfb.setTypeAliasesPackage("com.xxx.bl.core.data.model");
SqlSessionFactory factory = sfb.getObject();
factory.getConfiguration().setMapUnderscoreToCamelCase(true);
// factory.getConfiguration().addInterceptor(new CoreResultSetHandler());
factory.getConfiguration().setCallSettersOnNulls(dataConfiguration.isCallSettersOnNulls());
return factory;
}
復(fù)制代碼
使用logback日志組件
采用logback日志框架,可以在logback配置文件中指定針對不同的Spring profile在不同的環(huán)境中采用不同的日志級別,并采用不同的appender。同時引入spring-cloud-starter-sleuth依賴,通過設(shè)置traceId,使整個請求全鏈路上的所有日志打印出一致的traceId,大大方便了各系統(tǒng)間生產(chǎn)問題的協(xié)同排查。另外,采用異步方式記錄日志,也有利于降低IO阻塞。
<springProfile name="stg">
<root level="error">
<appender-ref ref="STDOUT"/>
<appender-ref ref="SAVE-ERROR-TO-FILE-STG"/>
</root>
<logger name="org.xxx" level="error" additivity="false">
<appender-ref ref="STDOUT"/>
<appender-ref ref="ASYNC-SAVE-TO-FILE-STG"/>
</logger>
</springProfile>
<springProfile name="prod">
<root level="error">
<appender-ref ref="STDOUT"/>
<appender-ref ref="SAVE-ERROR-TO-FILE-PROD"/>
</root>
<logger name="org.xxx" level="error" additivity="false">
<appender-ref ref="ASYNC-SAVE-TO-FILE-PROD"/>
</logger>
</springProfile>
復(fù)制代碼
SSL加密及密碼安全
全鏈路傳輸加密已成為企業(yè)安全中必不可少的措施。通過在classpath中引入CA頒發(fā)(也可以使用自簽)的jks證書,并在application配置文件中進(jìn)行簡單配置,便可實現(xiàn)SpringBoot應(yīng)用的SSL加密。
ssl:
enabled: true
key-store: classpath:xxx.net.jks
key-store-type: JKS
key-store-password: RUIEIoUD
key-password: RUIEIoUD
require-ssl: true
復(fù)制代碼
密碼以明文形式存放在配置文件中,也是不安全的。你可以jasypt加密配置文件中使用到的密碼,或者直接使用Key-Vault方案,比如本案例中會分別在微軟云環(huán)境中使用Azure Key Vault或本地IDC中使用Cyberark Conjur方案。
###同步與異步服務(wù) 我們并沒有使用Spring Webflux來支持reactive特性,因為,這會增加開發(fā)復(fù)雜度,并且Webflux雖然改善了Web容器阻塞機制,但并不能從根本上解決高并發(fā)請求到來時的阻塞問題。
在這個案例中,通過搭建了3個節(jié)點的RabbitMq鏡像集群,作為消息中間件,并通過應(yīng)用框架的支持,實現(xiàn)了服務(wù)的同步異步切換功能。我們將對外提供的服務(wù)注冊到數(shù)據(jù)庫中,在應(yīng)用啟動時,讀入redis緩存。當(dāng)請求到來時,通過API code判斷該請求的響應(yīng)模式:同步或異步。如果是同步請求則直接處理,而如果是異步請求,則發(fā)送到RabbitMq中,再由經(jīng)過封裝的消費者組件進(jìn)行異步消費,最終達(dá)到削峰的目的。
?
對于開發(fā)人員來說,他們只需要關(guān)注服務(wù)的業(yè)務(wù)邏輯開發(fā),由應(yīng)用框架統(tǒng)一處理服務(wù)的同步,異步切換,消息發(fā)送或失敗時的異常處理,以及死信隊列的維護(hù)等工作。
Dockerfile
案例中的四個組件需要實現(xiàn)容器化部署,分別為SpringBoot應(yīng)用與Vue應(yīng)用創(chuàng)建Dockerfile。
典型的SpringBoot應(yīng)用Dockerfile如下,一般情況下大型組織會構(gòu)建私有鏡像倉庫,通過私有倉庫拉取鏡像的速度更快,能夠節(jié)省CICD的時間。
FROM openjdk:11-jre
#FROM cargo.xxx.net/library/openjdk:11-jre
ARG JAR_FILE=console-service/build/libs/*.jar
COPY ${JAR_FILE} app.jar
EXPOSE 9002
EXPOSE 9003
ENTRYPOINT [ "java", "-jar", "/app.jar" ]
復(fù)制代碼
vue應(yīng)用的Dockerfile如下,同樣添加了SSL證書,進(jìn)行傳輸加密:
FROM cargo.xxx.net/library/nginx:stable-alpine
COPY /dist /usr/share/nginx/html/console
COPY nginx.conf /etc/nginx/nginx.conf
ARG KEY_FILE=stg.xxx.net.key
ARG PEM_FILE=stg.xxx.net.pem
COPY ${KEY_FILE} /etc/ssl/certs/cert.key
COPY ${PEM_FILE} /etc/ssl/certs/cert.pem
EXPOSE 80
CMD [ "nginx", "-c", "/etc/nginx/nginx.conf", "-g", "daemon off;" ]
復(fù)制代碼
編寫dockerfile時有以下一些注意事項:
- 基礎(chǔ)鏡像:盡可能推薦選擇官方鏡像
- 選擇大小適中的版本:如果選擇的基礎(chǔ)鏡像過大,啟動后需要消耗更多的資源,影響系統(tǒng)性能。如果太小,則可能缺失關(guān)鍵功能。
- 利用緩存:將dockerfile中不易變動的內(nèi)容寫在dockerfile最前。
##數(shù)據(jù)庫架構(gòu) 在賬戶數(shù)據(jù)上億,交易數(shù)據(jù)幾百億的系統(tǒng),需要采用分庫分表方案。本案例中,采用了MyCat+MySQL的數(shù)據(jù)庫架構(gòu)方案。采用mycat代理Master與Slave,可靈活進(jìn)行主從切換。Slave可作為Master熱備,也同時可作為讀庫,實現(xiàn)讀寫分離。備庫除作為準(zhǔn)實時的備份外,也可作為運維庫或提供大數(shù)據(jù)平臺數(shù)據(jù)抽取。
同時采用1主2從1備的雙機房設(shè)計
- Master到Slave使用半同步方案,保證從庫數(shù)據(jù)一致性。
- Master異常時,通過mycat切換至Slave,Slave轉(zhuǎn)換為新Master
- Master異常恢復(fù)后,先將原Master設(shè)置為Slave,數(shù)據(jù)同步完成后,再切換回正式Master
mycat高可用
mycat采用k8s容器化運行,使用k8s service來實現(xiàn)mycat的負(fù)載均衡,達(dá)到mycat的集群的高可用。若mycat容器節(jié)點異常,應(yīng)用自動連接到另外的mycat節(jié)點上。
對數(shù)據(jù)庫的大量操作是讀操作,一般占到所有操作70%以上。所以做讀寫分離還是很有必要的,如果不做讀寫分離,那么從庫也是一種很大的浪費。 mycat通過配置很容易做到讀寫分離,在從庫進(jìn)行讀操作,提升資源利用率,在主庫進(jìn)行寫操作,減低主庫壓力。
?
分庫分表
- 垂直分庫:按照功能劃分,把數(shù)據(jù)分別放到不同的數(shù)據(jù)庫和服務(wù)器。例如:賬戶、資產(chǎn)、交易等業(yè)務(wù)領(lǐng)域不同的數(shù)據(jù)分別放在不同的庫中,分散壓力、減少相互影響、降低耦合,獨立模塊獨立發(fā)布
- 水平分庫:在垂直分庫不能滿足要求時,再對模型進(jìn)行水平的 切分,將同一實體,不同范圍的數(shù)據(jù)分散到不同庫中,保持單庫數(shù)量和壓力,提升連接數(shù),達(dá)到橫向擴展的目的。
?
冷熱數(shù)據(jù)方案
熱數(shù)據(jù)緩存
- 對于高頻使用的熱數(shù)據(jù),如經(jīng)常使用App的客戶信息等,適當(dāng)增加數(shù)據(jù)庫query cache,提升數(shù)據(jù)庫查詢性能。
- 在應(yīng)用層使用redis等內(nèi)存緩存部分高頻使用數(shù)據(jù),降低請求響應(yīng)時間,增加系統(tǒng)流暢度,提升客戶體驗。
- 進(jìn)行讀寫分離,使用從庫提供數(shù)據(jù)查詢的服務(wù),提升從庫硬件資源利用率,降低主庫讀壓力,增加主庫寫性能。提升整體效率。
冷數(shù)據(jù)歸檔
- 對于使用頻率很低或基本不使用的冷數(shù)據(jù),如歷史交易、歷史卡券等,進(jìn)行數(shù)據(jù)的歸檔,提升數(shù)據(jù)庫的性能。
- 也可提供使用頻率較低的歷史交易查詢功能,使用備庫提供服務(wù)。
- 對于交易類數(shù)據(jù)建議按日期進(jìn)行分庫分表,每日交易分為一片或多片,對于歷史交易如1年前交易進(jìn)行定期遷移和歸檔,提升數(shù)據(jù)庫性能。
DEVOPS與K8S容器化部署
####DEVOPS流水線 本案例中,通過基于jenkins的CICD平臺,將應(yīng)用代碼從github代碼庫獲取,使用gradle進(jìn)行構(gòu)建(前端使用npm構(gòu)建),通過dockerfile打成鏡像后,部署到K8S容器平臺。
?
在進(jìn)行持續(xù)集成的過程中,同時加入了安全檢查,合規(guī)檢查以及單元測試(SpringBoot應(yīng)用使用JUnit,Vue前端應(yīng)用使用Jest測試框架)的步驟,以保證每一次發(fā)布的質(zhì)量。 ###ConfigMap ConfigMap用于將應(yīng)用的配置信息與程序的分離,這種方式不僅可以實現(xiàn)應(yīng)用程序被的復(fù)用,而且還可以通過不同的配置實現(xiàn)更靈活的功能。本案例中,SpringBoot應(yīng)用在K8S部署時,便將application.yml文件以ConfigMap文件的形式進(jìn)行掛載。需要注意,SpringBoot會優(yōu)先讀取classpath下的配置文件,因此需要在打出springboot應(yīng)用jar包時,先將配置文件排除,并通過容器啟動命令參數(shù)來制定掛載的應(yīng)用配置文件。
-spring.profiles.active=prod
-spring.config.location=/config/application.yml
復(fù)制代碼
###K8S容器部署 在K8S部署平臺,可以為每一個服務(wù)指定初始的資源,以及節(jié)點數(shù)量配置。比如我們?yōu)镾pringBoot應(yīng)用初始配置,2core 4g的資源配置,節(jié)點數(shù)量則為20個。
?
根據(jù)需要我們可以采用滾動方式對pod數(shù)量進(jìn)行伸縮。而不會引起服務(wù)不可用的情況。
?
另外,我們也可以利用彈性伸縮,基于某些關(guān)鍵指標(biāo),如容器的CPU使用量作為閾值,來觸發(fā)容器進(jìn)行彈性伸縮。在這個案例中,通過彈性伸縮機制,在上班以及中午業(yè)務(wù)高峰時間段內(nèi),將更多pod提供給業(yè)務(wù)服務(wù)組件,而在晚上,則會將pod從業(yè)務(wù)組件收回,提供給需要跑批處理以及異步消費的服務(wù)組件。
?
運維與監(jiān)控
ELK
ELK是一套解決方案而不是一款軟件, 三個字母分別是三個軟件產(chǎn)品的縮寫。 E代表Elasticsearch,負(fù)責(zé)日志的存儲和檢索; L代表Logstash, 負(fù)責(zé)日志的收集,過濾和格式化;K代表Kibana,負(fù)責(zé)日志的展示統(tǒng)計和數(shù)據(jù)可視化。
?
Dynatrace
Dynatrace可能是目前最優(yōu)秀的應(yīng)用性能管理工具(APM),它既能監(jiān)控基礎(chǔ)設(shè)施如服務(wù)器,K8S容器,又能自動發(fā)現(xiàn)并監(jiān)控在容器內(nèi)運行的動態(tài)微服務(wù),了解它們?nèi)绾螆?zhí)行、相互之間如何通信,還能立即檢測出性能不佳的微服務(wù)。在我們的案例中,通過定制dashboard添加我們所需要關(guān)注的監(jiān)控數(shù)據(jù)。
?
Dynatrace還能自動識別服務(wù),并提供更精細(xì)的檢測數(shù)據(jù),為開發(fā)或運維人員定位問題,帶來了極大的幫助。
?
一些思考
- 數(shù)據(jù)庫分庫分表方案帶來的代碼侵入問題:MyCat+MySQL雖然在物理上實現(xiàn)了分庫分表,但對于開發(fā)來說帶來了侵入性問題,需要為分片鍵進(jìn)行特殊的表結(jié)構(gòu)設(shè)計,在進(jìn)行查詢時也需要額外考慮分片鍵的使用,以提升查詢效率。其他的如事務(wù)的處理,由于分庫的關(guān)系,我們不再依賴事務(wù),而是通過數(shù)據(jù)最終一致性,以及錯誤補償?shù)确绞竭M(jìn)行處理。
- 未來數(shù)據(jù)庫的選型:MyCat+MySQL給數(shù)據(jù)庫運維增加了復(fù)雜性,而未來針對超大數(shù)據(jù)量級的應(yīng)用,在硬件資源允許的情況下,可以考慮轉(zhuǎn)向如:TiDB這樣的NewSQL方案進(jìn)行替代。
- JVM優(yōu)化:應(yīng)用上線后,在高并發(fā)情況下曾偶發(fā)Long GC問題,通過分析dump文件,優(yōu)化內(nèi)存使用,進(jìn)行了解決。另外,對于內(nèi)存變化較大的應(yīng)用,也可以考慮使用jdk13,并開啟ZGC。
- 緩存優(yōu)化:案例中通過redis緩存服務(wù)配置信息,每次服務(wù)響應(yīng)時都需要讀取redis,這給redis造成了不小的壓力,通過引入Guava cache,在本地建立緩存副本實現(xiàn)多級緩存,并設(shè)定合理的失效時間,能夠顯著降低對redis的壓力。
- 通過應(yīng)用框架實現(xiàn)低代碼:在應(yīng)用框架上的投資是非常值得的,通過將共性問題集中在應(yīng)用框架中解決,可以在一定程度上實現(xiàn)低代碼平臺的特性。開發(fā)人員也能更專注于業(yè)務(wù)邏輯的實現(xiàn)。
- 開發(fā)管理:通過讓每位開發(fā)人員充分理解應(yīng)用框架,并形成解決同類問題的統(tǒng)一Pattern,能夠明顯提高開發(fā)效率,減少低質(zhì)量代碼的產(chǎn)生。
今天先記錄到這里,隨著實踐的深入,相信后面還會有更多新的補充,也歡迎大家一起分享經(jīng)驗。
作者:技匠
鏈接:https://juejin.cn/post/6925238390161932301