TO BANK解決方案微服務架構的正確姿勢
theme: smartblue
概述
我們公司是一家TO B企業,主要客戶是中大型銀行,區別於to C的業務模式,我們主要是為不同的銀行客戶提供解決方案,他們有個特點,基本都是私有云、本地化部署。那麼在這個過程中我們公司要面臨和很多C端應用服務不一樣的挑戰。
- 不同銀行客戶微服務化程序不一樣,有的使用了某些微服務服務元件
- 不同銀行客戶技術選型不一樣,有的是用eureka作為註冊中心,有的用nacos等
- 不同銀行客戶的微服務規範不一樣,比如光大銀行的服務名必須要加特定的字首和字尾等
- 服務之間支援多種呼叫方式,可以通過服務名呼叫,也要通過url呼叫
- ..........
什麼是微服務化
微服務架構(Microservice Architecture)是一種架構概念,旨在通過將功能分解到各個離散的服務中以實現對解決方案的解耦。
微服務是一種架構風格,一個大型複雜軟體應用由一個或多個微服務組成。系統中的各個微服務可被獨立部署,各個微服務之間是松耦合的。每個微服務僅關注於完成一件任務並很好地完成該任務。在所有情況下,每個任務代表著一個小的業務能力。
單體架構
優點:
- 開發簡單直接,集中式管理,基本不會重複開發
- 功能都在本地,沒有分散式的管理開銷和呼叫開銷。
缺點:
- 開發效率低:所有的開發在一個專案改程式碼,遞交程式碼相互等待,程式碼衝突不斷
- 程式碼維護難:程式碼功能耦合在一起,新人不知道何從下手
- 部署不靈活:構建時間長,任何小修改必須重新構建整個專案,這個過程往往很長
- 穩定性不高:一個微不足道的小問題,可以導致整個應用掛掉
- 擴充套件性不夠:無法滿足高併發情況下的業務需求
微服務架構
優點:
- 每個服務為獨立的業務開發,單獨部署,跑在自己的程序中。
- 自動化測試、部署、運維( DevOps )。
- 快速演化、快速迭代。
- 整個業務由一系列的獨立的服務共同組成系統。
- 高度容錯性、高可用、高併發。
- 天然支援雲原生
缺點:
- 服務規模大,部署、運維、管理難度大。
- 服務間呼叫關係複雜,呼叫鏈路長,排障難度大。
- 服務間通訊成本變大,效能和容錯帶來的挑戰。
- 服務間依賴較多,系統整合、測試難度變大。
- 開發人員技術能力挑戰,各服務間重複程式碼,重複建設等。
- 叢集規模大,功能效能監控、告警帶來的挑戰。
- 大規模分散式,資料一致性帶來的挑戰。
- 需求和版本協調複雜度大大增加,測試難度也增加。
- 對基礎架構要求大大提高,規模大幅增加。
結合我們公司的業務特點,TO B 使用者流量小,儘量部署建議,其實採用單體架構更合適,那是什麼原因讓我們最終選擇走微服務這條道路呢?
- 隨著銀行IT架構的升級,越來越多的銀行客戶採用微服務化架構,微服務和雲原生已經成為客戶招投標的重要指標。
- 公司要持續保持架構的先進性。
公司微服務架構
公司架構發展歷程
第一階段(2010年~2014年):
- 採用傳統的SpringMVC + Mybatis,XML的方式配置
- 部署到獨立的tomcat容器中
第二階段(2015年~2020年):
- 使用springboot框架,版本也從1.3.x —> 2.3.x
- 使用內嵌的tomcat容器,jar包方式啟動
- 規則平臺、後臺等元件之間通過http呼叫
第三階段(2021年~2022年):
- 使用spring cloud微服務框架
- 接入註冊中心、配置中心、閘道器等微服務治理元件
- 拆分出portal服務、名單服務等,服務之間通過open feign呼叫
總體架構
介面請求流程
瀏覽器請求通過nginx代理,轉發到閘道器,閘道器根據服務名路由到不同的服務元件中,不用再像以前,啟動一個節點需要在nginx大量配置ip、埠。
nginx配置:
閘道器配置:
服務拆分
拆分原則:
- 單一職責原、高內聚低耦合
- 服務粒度適中,服務不能太細
- 以業務模型為切入點
- 演進式拆分:剛開始的時候後不要拆的太細,可以隨著迭代過程逐步優化
- 儘量避免環形依賴與雙向依賴
拆分結果:
- portal作為獨立服務,提供使用者、角色、機構管理的能力
- 決策平臺服務
- 後臺服務
- 決策引擎服務
- ......
認證授權
公司在第二階段springboot應用時期,會話管理採用的是spring session的方式,但它不是微服務的主流解決方案, 而且第三方應用、手機端等接入難度大。市面主流都是採用token的方式,因此我們決定通過基於jwt token + 快取的認證方式。
服務治理
註冊中心
什麼是註冊中心? 註冊中心可以說是微服務架構中的”通訊錄“,它記錄了服務和服務地址的對映關係。在分散式架構中,服務會註冊到這裡,當服務需要呼叫其它服務時,就到這裡找到服務的地址,進行呼叫。這就是我們常常說的服務註冊和發現。
目標: 滿足不同的銀行客戶使用不同的註冊中心,比如光大銀行使用consul, 興業銀行使用eureka等。評估了各個銀行的微服務情況,我們的解決方案計劃相容主流的eureka、nacos、consul作為註冊中心。
實現方案:
- 公司微服務核心包中引入nacos, eureka, consul的依賴。
- 在bootstrap.yml整合nacos、eureka、consul的配置。
- 通過實現Spring提供的切入點
EnvironmentPostProcessor
,改寫原生配置中心的配置,以nacos工程為例。
``` public class NacosDiscoveryEnvironmentPostProcessor implements EnvironmentPostProcessor {
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
// 讀取bootstrap.yml配置項中的配置
String discoveryType = environment.getProperty("fit.common.cloud.discovery");
// 判斷是否配置了nacos
boolean nacosEnabled = "nacos".equals(discoveryType);
LinkedHashMap<String, Object> map = new LinkedHashMap<>();
// 設定nacos作為註冊中心是否啟用
map.put("spring.cloud.nacos.discovery.enabled", nacosEnabled);
map.put("ribbon.nacos.enabled", nacosEnabled);
MapPropertySource propertySource = new MapPropertySource("nacosEnableInfo", map);
// 新增到environment中
environment.getPropertySources().addLast(propertySource);
}
} ```
缺點:
- 引入了多餘的依賴
配置中心
配置中心,顧名思義,將配置中心化,說白了就是將配置從應用中抽取出來,統一管理,優雅的解決了配置的動態變更、許可權管理、持久化、運維成本等問題。應用程式自身只是從配置中心拿到自己想要的配置,既不需要去新增管理配置的介面,也不需要自己去實現配置的持久化,更不需要去關心配置何時變化。配置與應用程式隔離開,單獨管理配置。
目標: 滿足不同的銀行客戶有不同的配置中心選型,比如光大銀行使用consul, vivo使用apollo等。評估了各個銀行的微服務情況,我們的解決方案計劃相容主流的nacos、consul、apollo作為配置中心。
實現方案: 實現思路和上面的註冊中心一樣。
服務呼叫
在springcloud微服務的實際專案開發中,多個微服務之間不僅是相對獨立的,而且也是相對關聯的;也就是說,微服務之間需要相互訪問,多個微服務之間的介面可能會被互相多次呼叫,我們稱之為微服務之間的呼叫。
目標:
- 服務訪問支援通過open feign的方式呼叫
- 服務訪問支援修改服務名,因為不通銀行的服務名可能發生變化
- 服務訪問需要支援通過指定ip埠訪問,比如浙商專案的大資料元件由於網路原因,無法通過服務名訪問,需要配置指定的url
實現方案:
- 微服務核心包中新增cloud-feign模組,引入openfeign相關的依賴
- 為了實現feign呼叫過程中,帶上許可權資訊,需要在攔截器中新增token。
``` @Slf4j @Component public class FeignRequestInterceptor implements RequestInterceptor {
private final String AUTHORIZATION_HEADER = "Authorization";
@Autowired
private RestUserAcctProperties restUserAcctProperties;
@Override
public void apply(RequestTemplate requestTemplate) {
ServletRequestAttributes attributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
if (attributes != null) {
HttpServletRequest request = attributes.getRequest();
// 攜帶token
String authorization = request.getHeader(AUTHORIZATION_HEADER);
if (!StringUtils.isEmpty(authorization)) {
requestTemplate.header("Authorization", authorization);
log.debug("feign token filter, url:[{}], token: [{}]", request.getRequestURI(), authorization);
return;
}
}
String encoded = Base64Utils.encodeToString((restUserAcctProperties.getSubscription().getUsername() + ":"
+ restUserAcctProperties.getSubscription().getPassword()).getBytes());
requestTemplate.header("Authorization", "Basic " + encoded);
}
} ```
- 服務提供方,以jar包的形式提供對外開放使用的介面, 比如名單平臺提供namelist-api.jar,暴露可以呼叫的介面。
- 服務呼叫方,依賴服務提供方的jar包,新增對應的Feign註解。
- 1標記顯示使用Feign的註解
-
2標記顯示繼承名單服務暴露的介面,被Feign管理
-
通過
@FeignClient
註解的屬性配置實現服務名的動態調整以及使用url呼叫。 -
name表示呼叫的服務名,支援從配置檔案讀取
- url表示呼叫的地址,如果url有值優先讀取該地址去訪問,支援從配置檔案讀取。
服務熔斷降級
一個服務常常會呼叫別的模組,可能是另外的一個遠端服務、資料庫,或者第三方 API 等。例如,支付的時候,可能需要遠端呼叫銀聯提供的 API;查詢某個商品的價格,可能需要進行資料庫查詢。然而,這個被依賴服務的穩定性是不能保證的。如果依賴的服務出現了不穩定的情況,請求的響應時間變長,那麼呼叫服務的方法的響應時間也會變長,執行緒會產生堆積,最終可能耗盡業務自身的執行緒池,服務本身也變得不可用。
實現方式
- Hystrix
- Sentinel
由於Hystrix已經停止維護,加上Sentinel社群比較活躍,最終選擇適用了Sentinel。
Sentinel 是面向分散式、多語言異構化服務架構的流量治理元件,主要以流量為切入點,從流量路由、流量控制、流量整形、熔斷降級、系統自適應過載保護、熱點流量防護等多個維度來幫助開發者保障微服務的穩定性。
Sentinel 可以簡單的分為 Sentinel 核心庫和 Dashboard。核心庫不依賴 Dashboard,但是結合 Dashboard 可以取得最好的效果,Dashboard可以介面配置規則並且實時生效。
解決方案中實踐:
- 解決方案中只使用了Sentinel 核心庫,沒有適用Dashboard,如果新增Dashboard需要增加額外的元件,增加解決方案的負擔。
- 通過open feign整合Sentinel實現熔斷降級
- 引入sentinel 依賴
- 啟用OpenFeign整合Sentinel的自動配置,解決方案中預設開啟
feign:
sentinel:
enabled: true
- 自定義
Feign.Builder
, 提供預設兜底的降級策略, 返回提示“遠端訪問失敗,介面熔斷”。
- 可以自定義降級策略, 優先使用自定義策略。
問題:
- 目前降級規則使用全域性預設的,比如服務掛掉,會執行降級策略,不支援配置,後續優化。
總結
目前公司的微服務架構的解決方案已經在光大銀行、浙商等銀行中落地,滿足他們的基本功能。但還需要不斷迭代完善,比如服務的限流、鏈路追蹤,分散式事務等需要在後續持續整合到我們的解決方案中。
我正在參與掘金技術社群創作者簽約計劃招募活動,點選連結報名投稿。
- Java7到Java17, Switch語句進化史
- 樂觀鎖思想在JAVA中的實現——CAS
- 一步步帶你設計MySQL索引資料結構
- 我總結了寫出高質量程式碼的12條建議
- 工作這麼多年,我總結的資料傳輸物件 (DTO) 的最佳實踐
- Spring專案中用了這種解耦模式,經理對我刮目相看
- 大資料HDFS憑啥能存下百億資料?
- 5個介面效能提升的通用技巧
- 你的哪些SQL慢?看看MySQL慢查詢日誌吧
- 90%的Java開發人員都會犯的5個錯誤
- 喪心病狂,竟有Thread.sleep(0)這種寫法?
- 為什麼更推薦使用組合而非繼承關係?
- 一個30歲程式設計師的覺醒和進擊
- 推薦8個提高工作效率的IntelliJ外掛
- 公司的這種打包啟動方式,我簡直驚呆了
- 告別醜陋判空,一個Optional類搞定
- 你不知道的Map家族中的那些冷門容器
- SpringBoot 2.x整合Log4j2日誌
- SpringBoot應用自定義logback日誌
- 你確定懂了Java中的序列化機制嗎