點擊進入IT資料庫
前不久作為外部架構(gòu)師完成了某知名快消企業(yè)的一個業(yè)務(wù)中臺建設(shè)。系統(tǒng)上線后,經(jīng)歷了一些重大活動的流量高峰,整體運行穩(wěn)定。最近有空,便將此次架構(gòu)的思路,心得稍作整理在這篇博客中分享一下。不會深入每一個技術(shù)細節(jié),而是把用到的技術(shù)、框架、工具做一個簡單的回顧,作為日后的參考。
業(yè)務(wù)架構(gòu)
業(yè)務(wù)架構(gòu)方面,該系統(tǒng)作為業(yè)務(wù)中臺,主要負責客戶資產(chǎn)管理,包括客戶的卡、券以及其他虛擬資產(chǎ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ù)細節(jié),這里對業(yè)務(wù)架構(gòu)就不做詳細說明了。
技術(shù)架構(gòu)
這個案例中采用了基于SpringBoot的微服務(wù)架構(gòu)。結(jié)合企業(yè)自身的基礎(chǔ)架構(gòu)設(shè)施,進行K8S容器化部署,并采用Kong API Gateway對各業(yè)務(wù)中臺暴露的API接口進行統(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)控制臺進行注冊,添加證書,生成Auth Key供關(guān)聯(lián)方使用。
Kong具有以下一些特性,能夠很好地滿足大型組織對于服務(wù)網(wǎng)關(guān)的需求:
開源(本案例中使用的是Kong的企業(yè)版,提供了原廠服務(wù))
亞毫秒級的響應(yīng)延遲,得益于基于Nginx與OpenResty帶來的超高性能
單節(jié)點25K TPS
認證、授權(quán)、限流、數(shù)據(jù)轉(zhuǎn)換(此案例中會員ID被添加到請求頭中)、日志、統(tǒng)計分析
應(yīng)用架構(gòu)
整個系統(tǒng)采用java開發(fā)后端以及vue開發(fā)前端,應(yīng)用部分共分為4個服務(wù)組件,全部進行容器化部署,并通過Ingress Controller負載均衡對外暴露服務(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ù)表達形式的統(tǒng)一,以及標準的據(jù)轉(zhuǎn)換、校驗、消息綁定、錯誤處理等功能。架構(gòu)師需要對應(yīng)用框架負責,簡明、高效、統(tǒng)一的應(yīng)用框架,能夠提升開發(fā)效率,產(chǎn)出標準一致的代碼,保證交付質(zhì)量。
應(yīng)用框架不在本文的討論范圍內(nèi),而以下一些技巧或第三方包,卻在我們構(gòu)建大多數(shù)SpringBoot應(yīng)用中得到使用。
定制MyBatis
數(shù)據(jù)層框架采用MyBatis,在大型應(yīng)用中MyBatis能夠幫助程序員更好地控制數(shù)據(jù)層交互,并進行調(diào)優(yōu)。一般可以在applicaion.yml中配置MyBatis,但當我們需要讓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;
使用logback日志組件
采用logback日志框架,可以在logback配置文件中指定針對不同的Spring profile在不同的環(huán)境中采用不同的日志級別,并采用不同的appender。同時引入spring-cloud-starter-sleuth依賴,通過設(shè)置traceId,使整個請求全鏈路上的所有日志打印出一致的traceId,大大方便了各系統(tǒng)間生產(chǎn)問題的協(xié)同排查。另外,采用異步方式記錄日志,也有利于降低IO阻塞。
root>
logger>
springProfile>
root>
logger>
springProfile>
SSL加密及密碼安全
全鏈路傳輸加密已成為企業(yè)安全中必不可少的措施。通過在classpath中引入CA頒發(fā)(也可以使用自簽)的jks證書,并在application配置文件中進行簡單配置,便可實現(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
密碼以明文形式存放在配置文件中,也是不安全的。你可以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緩存。當請求到來時,通過API code判斷該請求的響應(yīng)模式:同步或異步。如果是同步請求則直接處理,而如果是異步請求,則發(fā)送到RabbitMq中,再由經(jīng)過封裝的消費者組件進行異步消費,最終達到削峰的目的。
對于開發(fā)人員來說,他們只需要關(guān)注服務(wù)的業(yè)務(wù)邏輯開發(fā),由應(yīng)用框架統(tǒng)一處理服務(wù)的同步,異步切換,消息發(fā)送或失敗時的異常處理,以及死信隊列的維護等工作。
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" ]
vue應(yīng)用的Dockerfile如下,同樣添加了SSL證書,進行傳輸加密:
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;" ]
編寫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,可靈活進行主從切換。Slave可作為Master熱備,也同時可作為讀庫,實現(xià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的負載均衡,達到 mycat的集群的高可用。若mycat容器節(jié)點異常,應(yīng)用自動連接到另外的mycat節(jié)點上。
對數(shù)據(jù)庫的大量操作是讀操作,一般占到所有操作70%以上。所以做讀寫分離還是很有必要的,如果不做讀寫分離,那么從庫也是一種很大的浪費。mycat通過配置很容易做到讀寫分離,在從庫進行讀操作,提升資源利用率,在主庫進行寫操作,減低主庫壓力。
分庫分表
垂直分庫:按照功能劃分,把數(shù)據(jù)分別放到不同的數(shù)據(jù)庫和服務(wù)器。例如:賬戶、資產(chǎn)、交易等業(yè)務(wù)領(lǐng)域不同的數(shù)據(jù)分別放在不同的庫中,分散壓力、減少相互影響、降低耦合,獨立模塊獨立發(fā)布
水平分庫:在垂直分庫不能滿足要求時,再對模型進行水平的 切分,將同一實體,不同范圍的數(shù)據(jù)分散到不同庫中,保持單庫數(shù)量和壓力,提升連接數(shù),達到橫向擴展的目的。
冷熱數(shù)據(jù)方案
熱數(shù)據(jù)緩存
對于高頻使用的熱數(shù)據(jù),如經(jīng)常使用App的客戶信息等,適當增加數(shù)據(jù)庫query cache,提升數(shù)據(jù)庫查詢性能。
在應(yīng)用層使用redis等內(nèi)存緩存部分高頻使用數(shù)據(jù),降低請求響應(yīng)時間,增加系統(tǒng)流暢度,提升客戶體驗。
進行讀寫分離,使用從庫提供數(shù)據(jù)查詢的服務(wù),提升從庫硬件資源利用率,降低主庫讀壓力,增加主庫寫性能。提升整體效率。
冷數(shù)據(jù)歸檔
對于使用頻率很低或基本不使用的冷數(shù)據(jù),如歷史交易、歷史卡券等,進行數(shù)據(jù)的歸檔,提升數(shù)據(jù)庫的性能。
也可提供使用頻率較低的歷史交易查詢功能,使用備庫提供服務(wù)。
對于交易類數(shù)據(jù)建議按日期進行分庫分表,每日交易分為一片或多片,對于歷史交易如1年前交易進行定期遷移和歸檔,提升數(shù)據(jù)庫性能。
DEVOPS與K8S容器化部署
DEVOPS流水線
本案例中,通過基于jenkins的CICD平臺,將應(yīng)用代碼從github代碼庫獲取,使用gradle進行構(gòu)建(前端使用npm構(gòu)建),通過dockerfile打成鏡像后,部署到K8S容器平臺。
在進行持續(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文件的形式進行掛載。需要注意,SpringBoot會優(yōu)先讀取classpath下的配置文件,因此需要在打出springboot應(yīng)用jar包時,先將配置文件排除,并通過容器啟動命令參數(shù)來制定掛載的應(yīng)用配置文件。
-spring.profiles.active=prod
-spring.config.location=/config/application.yml
K8S容器部署
在K8S部署平臺,可以為每一個服務(wù)指定初始的資源,以及節(jié)點數(shù)量配置。比如我們?yōu)镾pringBoot應(yīng)用初始配置,2core 4g的資源配置,節(jié)點數(shù)量則為20個。
根據(jù)需要我們可以采用滾動方式對pod數(shù)量進行伸縮。而不會引起服務(wù)不可用的情況。
另外,我們也可以利用彈性伸縮,基于某些關(guān)鍵指標,如容器的CPU使用量作為閾值,來觸發(fā)容器進行彈性伸縮。在這個案例中,通過彈性伸縮機制,在上班以及中午業(yè)務(wù)高峰時間段內(nèi),將更多pod提供給業(yè)務(wù)服務(wù)組件,而在晚上,則會將pod從業(yè)務(wù)組件收回,提供給需要跑批處理以及異步消費的服務(wù)組件。
運維與監(jiān)控
ELK
ELK是一套解決方案而不是一款軟件, 三個字母分別是三個軟件產(chǎn)品的縮寫。E代表Elasticsearch,負責日志的存儲和檢索;L代表Logstash,負責日志的收集,過濾和格式化;K代表Kibana,負責日志的展示統(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ù),并提供更精細的檢測數(shù)據(jù),為開發(fā)或運維人員定位問題,帶來了極大的幫助。
一些思考
數(shù)據(jù)庫分庫分表方案帶來的代碼侵入問題:MyCat+MySQL雖然在物理上實現(xiàn)了分庫分表,但對于開發(fā)來說帶來了侵入性問題,需要為分片鍵進行特殊的表結(jié)構(gòu)設(shè)計,在進行查詢時也需要額外考慮分片鍵的使用,以提升查詢效率。其他的如事務(wù)的處理,由于分庫的關(guān)系,我們不再依賴事務(wù),而是通過數(shù)據(jù)最終一致性,以及錯誤補償?shù)确绞竭M行處理。
未來數(shù)據(jù)庫的選型:MyCat+MySQL給數(shù)據(jù)庫運維增加了復(fù)雜性,而未來針對超大數(shù)據(jù)量級的應(yīng)用,在硬件資源允許的情況下,可以考慮轉(zhuǎn)向如:TiDB這樣的NewSQL方案進行替代。
JVM優(yōu)化:應(yīng)用上線后,在高并發(fā)情況下曾偶發(fā)Long GC問題,通過分析dump文件,優(yōu)化內(nèi)存使用,進行了解決。另外,對于內(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)驗。
IT架構(gòu)師/技術(shù)大咖的交流圈子,為您提供架構(gòu)體系知識、技術(shù)文章、流行實踐案例、解決方案等,行業(yè)大咖分享交流/同行經(jīng)驗分享互動,期待你的加入!掃碼即可加入哦,隨著材料不斷增多社群會不定期漲價早加入更優(yōu)惠
免責聲明:
本公眾號部分分享的資料來自網(wǎng)絡(luò)收集和整理,所有文字和圖片版權(quán)歸屬于原作者所有,且僅代表作者個人觀點,與本公眾號無關(guān),文章僅供讀者學習交流使用,并請自行核實相關(guān)內(nèi)容,如文章內(nèi)容涉及侵權(quán),請聯(lián)系后臺管理員刪除。
特別聲明:以上內(nèi)容(如有圖片或視頻亦包括在內(nèi))為自媒體平臺“網(wǎng)易號”用戶上傳并發(fā)布,本平臺僅提供信息存儲服務(wù)。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.