JAVA微服務(wù)拆分架構(gòu)(業(yè)務(wù),規(guī)劃,設(shè)計,實現(xiàn))
一、微服務(wù)演變過程版本1.0
幾年前,小明和小皮一起創(chuàng)業(yè)做網(wǎng)上超市。小明負(fù)責(zé)程序開發(fā),小皮負(fù)責(zé)其他事宜。當(dāng)時互聯(lián)網(wǎng)還不發(fā)達,網(wǎng)上超市還是藍(lán)海。只要功能實現(xiàn)了就能隨便賺錢。所以他們的需求很簡單,只需要一個網(wǎng)站掛在公網(wǎng),用戶能夠在這個網(wǎng)站上瀏覽商品、購買商品;另外還需一個管理后臺,可以管理商品、用戶、以及訂單數(shù)據(jù)。
我們整理一下功能清單:
- 網(wǎng)站
-
- 用戶注冊、登錄功能
- 商品展示
- 下單
- 管理后臺
-
- 用戶管理
- 商品管理
- 訂單管理
由于需求簡單,小明左手右手一個慢動作,網(wǎng)站就做好了。管理后臺出于安全考慮,不和網(wǎng)站做在一起,小明右手左手慢動作重播,管理網(wǎng)站也做好了??傮w架構(gòu)圖如下:
![]()
小明揮一揮手,找了家云服務(wù)部署上去,網(wǎng)站就上線了。上線后好評如潮,深受各類肥宅喜愛。小明小皮美滋滋地開始躺著收錢。
版本2.0
好景不長,沒過幾天,各類網(wǎng)上超市緊跟著拔地而起,對小明小皮造成了強烈的沖擊。
在競爭的壓力下,小明小皮決定開展一些營銷手段:
- 開展促銷活動。比如元旦全場打折,春節(jié)買二送一,情人節(jié)狗糧優(yōu)惠券等等。
- 拓展渠道,新增移動端營銷。除了網(wǎng)站外,還需要開發(fā)移動端App,微信小程序等。
- 精準(zhǔn)營銷。利用歷史數(shù)據(jù)對用戶進行分析,提供個性化服務(wù)。
這些活動都需要程序開發(fā)的支持。小明拉了同學(xué)小紅加入團隊。小紅負(fù)責(zé)數(shù)據(jù)分析以及移動端相關(guān)開發(fā)。小明負(fù)責(zé)促銷活動相關(guān)功能的開發(fā)。
因為開發(fā)任務(wù)比較緊迫,小明小紅沒有好好規(guī)劃整個系統(tǒng)的架構(gòu),隨便拍了拍腦袋,決定把促銷管理和數(shù)據(jù)分析放在管理后臺里,微信和移動端APP另外搭建。通宵了幾天后,新功能和新應(yīng)用基本完工。這時架構(gòu)圖如下:
![]()
這一階段存在很多不合理的地方:
- 網(wǎng)站和移動端應(yīng)用有很多相同業(yè)務(wù)邏輯的重復(fù)代碼。
- 數(shù)據(jù)有時候通過數(shù)據(jù)庫共享,有時候通過接口調(diào)用傳輸。接口調(diào)用關(guān)系雜亂。
- 單個應(yīng)用為了給其他應(yīng)用提供接口,漸漸地越改越大,包含了很多本來就不屬于它的邏輯。應(yīng)用邊界模糊,功能歸屬混亂。
- 管理后臺在一開始的設(shè)計中保障級別較低。加入數(shù)據(jù)分析和促銷管理相關(guān)功能后出現(xiàn)性能瓶頸,影響了其他應(yīng)用。
- 數(shù)據(jù)庫表結(jié)構(gòu)被多個應(yīng)用依賴,無法重構(gòu)和優(yōu)化。
- 所有應(yīng)用都在一個數(shù)據(jù)庫上操作,數(shù)據(jù)庫出現(xiàn)性能瓶頸。特別是數(shù)據(jù)分析跑起來的時候,數(shù)據(jù)庫性能急劇下降。
- 開發(fā)、測試、部署、維護愈發(fā)困難。即使只改動一個小功能,也需要整個應(yīng)用一起發(fā)布。有時候發(fā)布會不小心帶上了一些未經(jīng)測試的代碼,或者修改了一個功能后,另一個意想不到的地方出錯了。為了減輕發(fā)布可能產(chǎn)生的問題的影響和線上業(yè)務(wù)停頓的影響,所有應(yīng)用都要在凌晨三四點執(zhí)行發(fā)布。發(fā)布后為了驗證應(yīng)用正常運行,還得盯到第二天白天的用戶高峰期……
- 團隊出現(xiàn)推諉扯皮現(xiàn)象。關(guān)于一些公用的功能應(yīng)該建設(shè)在哪個應(yīng)用上的問題常常要爭論很久,最后要么干脆各做各的,或者隨便放個地方但是都不維護。
盡管有著諸多問題,但也不能否認(rèn)這一階段的成果:快速地根據(jù)業(yè)務(wù)變化建設(shè)了系統(tǒng)。不過緊迫且繁重的任務(wù)容易使人陷入局部、短淺的思維方式,從而做出妥協(xié)式的決策。在這種架構(gòu)中,每個人都只關(guān)注在自己的一畝三分地,缺乏全局的、長遠(yuǎn)的設(shè)計。長此以往,系統(tǒng)建設(shè)將會越來越困難,甚至陷入不斷推翻、重建的循環(huán)。
版本3.0
幸好小明和小紅是有追求有理想的好青年。意識到問題后,小明和小紅從瑣碎的業(yè)務(wù)需求中騰出了一部分精力,開始梳理整體架構(gòu),針對問題準(zhǔn)備著手改造。
要做改造,首先你需要有足夠的精力和資源。如果你的需求方(業(yè)務(wù)人員、項目經(jīng)理、上司等)很強勢地一心追求需求進度,以致于你無法挪出額外的精力和資源的話,那么你可能無法做任何事……
在編程的世界中,最重要的便是抽象能力。微服務(wù)改造的過程實際上也是個抽象的過程。小明和小紅整理了網(wǎng)上超市的業(yè)務(wù)邏輯,抽象出公用的業(yè)務(wù)能力,做成幾個公共服務(wù):
- 用戶服務(wù)
- 商品服務(wù)
- 促銷服務(wù)
- 訂單服務(wù)
- 數(shù)據(jù)分析服務(wù)
各個應(yīng)用后臺只需從這些服務(wù)獲取所需的數(shù)據(jù),從而刪去了大量冗余的代碼,就剩個輕薄的控制層和前端。這一階段的架構(gòu)如下:
![]()
這個階段只是將服務(wù)分開了,數(shù)據(jù)庫依然是共用的,所以一些煙囪式系統(tǒng)的缺點仍然存在:
- 數(shù)據(jù)庫成為性能瓶頸,并且有單點故障的風(fēng)險。
- 數(shù)據(jù)管理趨向混亂。即使一開始有良好的模塊化設(shè)計,隨著時間推移,總會有一個服務(wù)直接從數(shù)據(jù)庫取另一個服務(wù)的數(shù)據(jù)的現(xiàn)象。
- 數(shù)據(jù)庫表結(jié)構(gòu)可能被多個服務(wù)依賴,牽一發(fā)而動全身,很難調(diào)整。
如果一直保持共用數(shù)據(jù)庫的模式,則整個架構(gòu)會越來越僵化,失去了微服務(wù)架構(gòu)的意義。因此小明和小紅一鼓作氣,把數(shù)據(jù)庫也拆分了。所有持久化層相互隔離,由各個服務(wù)自己負(fù)責(zé)。另外,為了提高系統(tǒng)的實時性,加入了消息隊列機制。架構(gòu)如下:
![]()
完全拆分后各個服務(wù)可以采用異構(gòu)的技術(shù)。比如數(shù)據(jù)分析服務(wù)可以使用數(shù)據(jù)倉庫作為持久化層,以便于高效地做一些統(tǒng)計計算;商品服務(wù)和促銷服務(wù)訪問頻率比較大,因此加入了緩存機制等。
還有一種抽象出公共邏輯的方法是把這些公共邏輯做成公共的框架庫。這種方法可以減少服務(wù)調(diào)用的性能損耗。但是這種方法的管理成本非常高昂,很難保證所有應(yīng)用版本的一致性。 數(shù)據(jù)庫拆分也有一些問題和挑戰(zhàn):比如說跨庫級聯(lián)的需求,通過服務(wù)查詢數(shù)據(jù)顆粒度的粗細(xì)問題等。但是這些問題可以通過合理的設(shè)計來解決。總體來說,數(shù)據(jù)庫拆分是一個利大于弊的。
微服務(wù)架構(gòu)還有一個技術(shù)外的好處,它使整個系統(tǒng)的分工更加明確,責(zé)任更加清晰,每個人專心負(fù)責(zé)為其他人提供更好的服務(wù)。在單體應(yīng)用的時代,公共的業(yè)務(wù)功能經(jīng)常沒有明確的歸屬。最后要么各做各的,每個人都重新實現(xiàn)了一遍;要么是隨機一個人(一般是能力比較強或者比較熱心的人)做到他負(fù)責(zé)的應(yīng)用里面。在后者的情況下,這個人在負(fù)責(zé)自己應(yīng)用之外,還要額外負(fù)責(zé)給別人提供這些公共的功能——而這個功能本來是無人負(fù)責(zé)的,僅僅因為他能力較強/比較熱心,就莫名地背鍋(這種情況還被美其名曰能者多勞)。結(jié)果最后大家都不愿意提供公共的功能。長此以往,團隊里的人漸漸變得各自為政,不再關(guān)心全局的架構(gòu)設(shè)計。
問題:
如果沒有注冊中心,URL異常麻煩; 大量配置,遠(yuǎn)程訪問很麻煩; 大量配置,配置沒有統(tǒng)一管理; 服務(wù)的熔斷降級; 鏈路追蹤; 需要一些服務(wù)進行支撐;版本4.0
網(wǎng)關(guān)可以做,統(tǒng)一的鑒權(quán),權(quán)限控制。
![]()
二、微服務(wù)拆分劃分原則(面試:考核業(yè)務(wù)劃分的想法)
我們拆分微服務(wù)的時候需要按照某些原則進行拆分.
- 基于業(yè)務(wù)邏輯
-
- 將系統(tǒng)中的業(yè)務(wù)按照職責(zé)范圍進行識別,職責(zé)相同的劃分為一個單獨的服務(wù)。
- 基于穩(wěn)定性
-
- 將系統(tǒng)中的業(yè)務(wù)模塊按照穩(wěn)定性進行排序。穩(wěn)定的、不經(jīng)常修改的劃分一塊;將不穩(wěn)定的,經(jīng)常修改的劃分為一個獨立服務(wù)。比如日志服務(wù)、監(jiān)控服務(wù)都是相對穩(wěn)定的服務(wù),可以歸到一起。
- 基于可靠性
-
- 同樣,將系統(tǒng)中的業(yè)務(wù)模塊按照可靠性進行排序。對可靠性要求比較高的核心模塊歸在一起,對可靠性要求不高的非核心模塊歸在一塊。
- 這種拆分的高明可以很好的規(guī)避因為一顆老鼠屎壞了一鍋粥的單體弊端,同時將來要做高可用方案也能很好的節(jié)省機器或帶寬的成本。
- 基于高性能
-
- 同上,將系統(tǒng)中的業(yè)務(wù)模塊按照對性能的要求進行優(yōu)先級排序。把對性能要求較高的模塊獨立成一個服務(wù),對性能要求不高的放在一起。比如全文搜索,商品查詢和分類,秒殺就屬于高性能的核心模塊。
基于上訴的拆分原則,我們可以針對騾窩窩項目進行微服務(wù)的拆分:
- 游記服務(wù)
-
- 目的地管理
- 旅游攻略
- 旅游日記
- 評論服務(wù)
-
- 旅游功能評論
- 旅游日記評論
- 景點評論
- 旅行社評論
- 用戶服務(wù)
-
- 用戶個人中心
- 用戶積分相關(guān)
- 黑名單/白名單
- 粉絲關(guān)注
- 消息服務(wù)
-
- 短信通知
- 郵件通知
- 站內(nèi)信
- 搜索服務(wù)
-
- 攻略搜索
- 游記搜搜
- 用戶搜索
- 景點搜索
假如我們按照業(yè)務(wù)來劃分,根據(jù)粒度大小,可能存在以下兩種:
- 第一種分為商品、交易、用戶3個服務(wù);
- 第二種分為商品、訂單、支付、物流、買家、賣家6個服務(wù)。
3 VS 6,這該怎么辦?
如果你的團隊只有9個人,那么分成3個是合理的,如果有18個人,那么6個服務(wù)是合理的。這里引入團隊成員進行協(xié)助拆分。
在拆分遇到爭議的時候,一般情況下我們增加一項拆分條件,雖然不是充要條件,但至少我們的答案會更加接近真理。
除了業(yè)務(wù)可能存在爭議,其他的劃分也會有爭議,比如一個獨立的服務(wù)到底需要多少人員的配置?
為什么說是三個人分配一個服務(wù)(當(dāng)然,成員主要是后端人員)?
- 假設(shè)是1個人,請個假、生個病都不行。一個人會遇到單點的問題,所以不合理。
- 假設(shè)是2個人,終于有備份了,但是抽離一個后,剩下1個壓力還是很大,不合理。
- 假設(shè)是3個人,抽離一個還有2個在。而且數(shù)字3是個穩(wěn)定而神奇數(shù)字,用得好事半功倍。特別是遇到技術(shù)討論,3個人相對周全,如果是2個可能會各持己見,帶有自我的偏見和盲區(qū)。
那么這個3是不是就是穩(wěn)定的數(shù)量呢?
假設(shè)你做的是邊開飛機邊換引擎的重寫工作,那么前期3個人都可能捉襟見肘。但是到了服務(wù)后期,你可能1個就夠了。
微服務(wù)實踐項目結(jié)構(gòu)圖
![]()
基礎(chǔ)模塊搭建01.parent模塊
管理統(tǒng)一依賴;
依賴:
org.springframework.boot
spring-boot-starter-parent
2.1.4.RELEASE
1.8
Greenwich.SR1
org.springframework.cloud
spring-cloud-dependencies
${spring-cloud.version}
pom
import
02.注冊中心Euraka
提供服務(wù)的注冊和發(fā)現(xiàn)功能;
依賴:eureka-server
org.springframework.cloud
spring-cloud-starter.NETflix-eureka-server
配置application.yml
server:
port: 8761
eureka:
instance:
hostname: localhost
client:
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/euraka
fetch-registry: false
register-with-eureka: false
03.配置中心Config-Server
依賴
eureka-client:
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
config-server:
org.springframework.cloud
spring-cloud-config-server
配置application.yml
server:
port: 9100
spring:
application:
name: config-server
cloud:
config:
server:
git:
uri:
username:
password:
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
04.網(wǎng)關(guān)Zuul
依賴
eureka-client:
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
netflix-zuul:
org.springframework.cloud
spring-cloud-starter-netflix-zuul
config-client:
org.springframework.cloud
spring-cloud-config-client
配置
bootstrap.yml:
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
spring:
application:
name: zuul-server
cloud:
config:
discovery:
enabled: true
service-id: config-server
label: master
name: zuul-server
遠(yuǎn)程托管平臺的文件:zuul-server.yml:
port: 9000
ribbon:
ConnectTimeout: 3000
ReadTimeout: 60000
zuul:
sensitive-headers:
forceOriginalQueryStringEncoding: true #強制采用原始請求的編碼格式,即不對Get請求參數(shù)做編解碼
ignored-patterns: /*-server/** #忽略匹配這個格式的路徑
05.公共模塊common
依賴
lombok:
org.projectlombok
lombok
web:
org.springframework.boot
spring-boot-starter-web
06.服務(wù)接口父模塊provider-api
![]()
07.服務(wù)父模塊provider-server
![]()
前端項目
本來由前端人員寫好的前端項目;
創(chuàng)建frontend-website組件,和任何項目沒有關(guān)系,引入依賴:
org.springframework.boot
spring-boot-starter-parent
2.1.4.RELEASE
org.springframework.boot
spring-boot-starter-web
創(chuàng)建啟動類。
啟動發(fā)出注冊請求,發(fā)現(xiàn)訪問的是8080端口,微服務(wù)的項目,不再是直接訪問到某個Tomcat服務(wù)器了。而是需要通過網(wǎng)關(guān):
微服務(wù)結(jié)構(gòu),請求發(fā)到 zuul 網(wǎng)關(guān),當(dāng)zuul接受到請求后,首先會由前置過濾器進行處理,然后在由路由過濾器具體把請求轉(zhuǎn)發(fā)到后端應(yīng)用,然后在執(zhí)行后置過濾器把執(zhí)行結(jié)果返回到請求方。
修改static/js/vue/common.js中的url訪問的端口;
![]()
微服務(wù)架構(gòu)發(fā)送請求到返回數(shù)據(jù)的執(zhí)行流程:
![]()
聚合服務(wù)website-server結(jié)構(gòu)&依賴關(guān)系處理
在provider-server模塊中添加依賴,因為下面三個依賴是其模塊下所有的server模塊都要用到的,所以公共所有的依賴在方法父項目中添加,子項目可以繼承到:
org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
org.springframework.cloud
spring-cloud-config-client
配置文件
bootstrap.yml
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
spring:
application:
name: website-server
cloud:
config:
discovery:
enabled: true
service-id: config-server
label: master
name: website-server
碼云的website-server.yml
server:
port: 8082
定義路由規(guī)則
碼云的zuul-server.yml
zuul:
routes:
member-server-route:
path: /website/**
service-id: website-server
加啟動類啟動測試:
跨域問題:直接在網(wǎng)關(guān)位置就可以處理跨域的問題:
跨域原因:域名、端口、ip不一樣的時候會產(chǎn)生跨域問題。默認(rèn)不允許跨域的原因:對被訪問的域外資源來說,服務(wù)器壓力變大了,假設(shè)不信任的惡意攻擊可能導(dǎo)致訪問量過大宕機。所有需要對信任的域名做配置。預(yù)檢請求會有一個緩存,除了第一次之外其他的都是一次請求。詳情見單獨的文檔;
![]()
在zuul-server中設(shè)置跨域:
解決跨域的問題:設(shè)置的位置,在zuul-server中的啟動類中添加設(shè)置:
// 解決跨域問題
@Bean
public CorsFilter corsFilter() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
final CorsConfiguration config = new CorsConfiguration();
// 允許cookies跨域
config.setAllowCredentials(true);
// #允許向該服務(wù)器提交請求的URI,*表示全部允許,在SpringMVC中,如果設(shè)成*,會自動轉(zhuǎn)成當(dāng)前請求頭中的Origin
config.addAllowedOrigin("*");
// #允許訪問的頭信息,*表示全部
config.addAllowedHeader("*");
// 預(yù)檢請求的緩存時間(秒),即在這個時間段里,對于相同的跨域請求不會再預(yù)檢了
config.setMaxAge(18000L);
// 允許提交請求的方法,*表示全部允許
config.addAllowedMethod("OPTIONS");
config.addAllowedMethod("HEAD");
// 允許Get的請求方法
config.addAllowedMethod("GET");
config.addAllowedMethod("PUT");
config.addAllowedMethod("POST");
config.addAllowedMethod("DELETE");
config.addAllowedMethod("PATCH");
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
測試發(fā)現(xiàn)跨域的錯誤信息已經(jīng)沒有了。接下來寫controller來處理我們的請求;
響應(yīng)類&統(tǒng)一異常:
controller的設(shè)計:返回類型需要確定
![]()
返回值:以前單體項目返回的是Object;現(xiàn)在是微服務(wù)項目,我們希望有一個狀態(tài)碼,成功帶上數(shù)據(jù),錯誤帶上錯誤信息:
![]()
返回類型應(yīng)該是什么樣的?那么應(yīng)該怎么定義字段:
![]()
所有的返回對象都是Result對象,具體的數(shù)據(jù)封裝在result對象的data中;
![]()
在controller中,會調(diào)用到各種業(yè)務(wù)層的業(yè)務(wù)方法:業(yè)務(wù)層中必須考慮到執(zhí)行過程中出現(xiàn)異常的情況,需要回滾:
![]()
設(shè)計的錯誤狀態(tài)碼和錯誤信息是一一對應(yīng)的,應(yīng)該是常量(枚舉類或者自定義類來封裝他們)
![]()
自定義異常:來解決業(yè)務(wù)方法執(zhí)行過程中出現(xiàn)的異常,即拋出的異常里要包含了剛剛封裝了錯誤狀態(tài)碼和錯誤信息的對象,在throw的時候返回給調(diào)用者,由調(diào)用者(controller)來處理。
![]()
業(yè)務(wù)層的業(yè)務(wù)方法中寫操作等,必須要保持一致性(事務(wù)),當(dāng)出現(xiàn)異常的時候需要將異常throw出去。
![]()
聚合服務(wù)中,需要對有可能出現(xiàn)的各種異常進行統(tǒng)一的處理,AOP思想。出現(xiàn)異常,將封裝了狀態(tài)碼和異常信息的result返回給前臺,前臺才能夠接受到具體的錯誤和錯誤信息。
![]()
實現(xiàn):異常的處理基本的類是所有的微服務(wù)都需要有的,所以放到common中
CodeMsg
* 封裝狀態(tài)碼和信息
@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class CodeMsg implements Serializable {
private Integer code;
private String msg;
Result
* 返回前臺的數(shù)據(jù)類型
* @param
@Setter
@Getter
public class Result implements Serializable {
public static final int SUCCESS_CODE = 200;//成功碼.
public static final String SUCCESS_MESSAGE = "操作成功";//成功信息.
public static final int ERROR_CODE = 500000;//錯誤碼.
public static final String ERROR_MESSAGE = "系統(tǒng)異常";//錯誤信息.
private int code;
private String msg;
private T data;
public Result(){}
private Result(int code, String msg, T data){
this.code = code;
this.msg = msg;
this.data = data;
public static Result success(T data){
return new Result(SUCCESS_CODE,SUCCESS_MESSAGE,data);
public static Result success(String msg,T data){
return new Result(SUCCESS_CODE,msg,data);
public static Result error(CodeMsg codeMsg){
return new Result(codeMsg.getCode(),codeMsg.getMsg(),null);
public static Result defaultError(){
return new Result(ERROR_CODE,ERROR_MESSAGE,null);
public boolean hasError(){
//狀態(tài)嗎!=200 說明有錯誤.
return this.code!=SUCCESS_CODE;
BusinessException
* 自定義異常
@Setter
@Getter
public class BusinessException extends RuntimeException {
private CodeMsg codeMsg;
public BusinessException(CodeMsg codeMsg){
this.codeMsg = codeMsg;
CommonExceptionAdvice
* 公共的sdvice,不貼 @ControllerAdvice 注解,因為它是讓其他服務(wù)來繼承的基類
public class CommonExceptionAdvice {
@ExceptionHandler(Exception.class)
@ResponseBody
public Result hanlderDefaultException(Exception e){
e.getMessage();
return Result.defaultError();
會員服務(wù):結(jié)構(gòu)&依賴關(guān)系處理模塊provider-api中添加依賴:
因為該模塊下的所有的api都要提供給其他的服務(wù)來調(diào)用,所以需要在api的父項目中添加這三個依賴:
cn.wolfcode.xloud.luowowo
common
1.0.0
org.springframework.cloud
spring-cloud-starter-openfeign
org.springframework.cloud
spring-cloud-starter-netflix-hystrix
模塊member-api:暴露會員服務(wù)接口
用戶的信息使用mongodb存儲的,所以要引入mongodb的依賴:
org.springframework.boot
spring-boot-starter-data-mongodb
true
UserInfo
@Setter
@Getter
@Document("userInfo")@ToString
public class UserInfo implements Serializable {
public static final int GENDER_SECRET = 0; //保密
public static final int GENDER_MALE = 1; //男
public static final int GENDER_FEMALE = 2; //女
public static final int STATE_NORMAL = 0; //正常
public static final int STATE_DISABLE = 1; //凍結(jié)
@Id
protected String id;
private String nickname; //昵稱
private String phone; //手機
private String email; //郵箱
private String password; //密碼
private int gender = GENDER_SECRET; //性別
private int level = 0; //用戶級別
private String city; //所在城市
private String headImgUrl; //頭像
private String info; //個性簽名
private int state = STATE_NORMAL; //狀態(tài)
模塊member-server:會員服務(wù),api負(fù)責(zé)暴露接口,這里負(fù)責(zé)具體的業(yè)務(wù)
添加依賴:
member-api依賴、redis依賴、fastjson依賴
cn.wolfcode.xloud.luowowo
menber-api
1.0.0
org.springframework.boot
spring-boot-starter-data-redis
com.alibaba
fastjson
1.2.47
bootstrap.yml
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
spring:
application:
name: member-server
cloud:
config:
discovery:
enabled: true
service-id: config-server
label: master
name: member-server,redis
member-server.yml
server:
port: 8080
spring:
data:
mongodb:
uri: mongodb://192.168.20.130:27017/member
創(chuàng)建微服務(wù)對應(yīng)的數(shù)據(jù)庫,拆分以前單體項目的表:
![]()
![image-2020051621441662
在模塊menber-server中準(zhǔn)備repository接口,來訪問mongodb數(shù)據(jù)庫的接口;
@Repository
public interface UserInfoRepository extends MongoRepository {
* 通過號碼查詢用戶信息
* @param phone
* @return
UserInfo findByPhone(String phone);
* 通過號碼和莫密碼查詢用戶信息
* @param username
* @param password
* @return
UserInfo getByPhoneAndPassword(String username, String password);
服務(wù)之間調(diào)用:
聚合服務(wù)需要調(diào)用會員服務(wù)中的方法。需要在menber-api中用feign負(fù)載均衡將menber-server的業(yè)務(wù)方法暴露出來一個接口;
// 類似controller
@FeignClient(name = "member-server") //負(fù)載訪問的服務(wù)
public interface MemberFeignApi {
@RequestMapping("/checkPhone") //映射方法
Result checkPhone(@RequestParam("phone") String phone); //映射參數(shù)
返回類型是
Result
的原因:
![]()
因為,基礎(chǔ)服務(wù)也有可能出現(xiàn)異常,直接返會封裝了錯誤狀態(tài)碼和錯誤信息的Result 回去,聚合服務(wù)可以直接通過result中boolean值來區(qū)分對異常的處理情況;
在member-server中,需要對接口做具體的實現(xiàn)了:
![]()
需要在server中對異常做統(tǒng)一的處理:
每個服務(wù)的異常有不相同的,比如注冊的錯和登錄的錯就不一樣,怎么體現(xiàn)各個服務(wù)之間的異常差異呢?可能會有其他的異常比如 連接數(shù)據(jù)庫超時的異常。自己實現(xiàn)具體的CoreMsg:
* member-server服務(wù)中返回的異常結(jié)果:封裝code和msg
public class MemberServerCodeMsg extends CodeMsg {
//用父類的構(gòu)造器來完成初始化操作
public MemberServerCodeMsg(Integer code, String msg){
super(code, msg);
public static final MemberServerCodeMsg DEFAULT_ERROR =
new MemberServerCodeMsg(500100, "會員服務(wù)繁忙!");
public static final MemberServerCodeMsg PHONE_EXIST_ERROR =
new MemberServerCodeMsg(500101, "手機號碼已存在!");
異常的advice增強類,繼承增強基類:
* 需要在該增強類中處理member-server中特有的異常
@ControllerAdvice
public class MemberServerExceptionAdvice extends CommonExceptionAdvice {
* 處理普通的Exception
* @param ex
* @return
@ExceptionHandler(Exception.class)
@ResponseBody
public Result handlerDefault(Exception ex){
ex.printStackTrace();
return Result.error(MemberServerCodeMsg.DEFAULT_ERROR); //返回自己的默認(rèn)異常
那具體拋出哪一個server中的異常就看在呢一個server中出現(xiàn)異常了;
![]()
熔斷降級:
既然可能在某個服務(wù)中會出現(xiàn)異常,那我們就不能讓出現(xiàn)異常的時候使服務(wù)出現(xiàn)宕機而影響其它服務(wù)的正常運行。所以應(yīng)該要想到對服務(wù)做熔斷降級的處理;
* 對服務(wù)可能出現(xiàn)異常做熔斷降級處理
@Component
public class UserInfoFeignHystrix implements UserInfoFeignApi{
@Override
public Result checkPhone(String phone) {
//熔斷降級之后,應(yīng)該做的有些處理
return null;
回退類和方法完成之后,在api接口的注解上加上:
![]()
注意:feign默認(rèn)是沒有開啟Hystrix的,需要添加配置進行開啟:直接放到碼云中的website-server的配置文件中
# hystrix默認(rèn)是關(guān)閉的,需要手動開啟一下,否則一直是超時的
feign:
hystrix:
enabled: true
到此,member-server的模塊就完成了,當(dāng)服務(wù)出現(xiàn)熔斷會調(diào)用到降級方法;
注入服務(wù)接口對象,完成遠(yuǎn)程調(diào)用:
注入需要引入menber-api的依賴:
返回的數(shù)據(jù)應(yīng)該是什么樣的,怎么解決出現(xiàn)異常的情況:
![]()
實現(xiàn)自己服務(wù)的返回CodeMsg:
public class WebsiteServerCodeMsg extends CodeMsg {
public WebsiteServerCodeMsg(Integer code, String msg) {
super(code, msg);
public static final WebsiteServerCodeMsg MEMBER_SERVER_ERROR =
new WebsiteServerCodeMsg(500201, "會員服務(wù)繁忙!");
public static final WebsiteServerCodeMsg DEFAULT_ERROR =
new WebsiteServerCodeMsg(500200, "聚合服務(wù)繁忙!");
controller
@RestController
@RequestMapping("/userRegister")
public class UserRegisterController {
@Autowired
private UserInfoFeignApi userInfoFeignApi;
@RequestMapping("/checkPhone")
public Result checkPhone(String phone){
//檢查號碼
Result result = userInfoFeignApi.checkPhone(phone);
//result返回的情況有3種,需要區(qū)分開
if (result == null){
//返回null。說明member-server服務(wù)宕機了,對應(yīng)的CodeMsg應(yīng)該重新設(shè)計
return Result.error(WebsiteServerCodeMsg.MEMBER_SERVER_ERROR);
//到這里,表示遠(yuǎn)程調(diào)用成功,遠(yuǎn)程的服務(wù)返回的是一個result,直接返回即可
//返回參數(shù)
return result;
還需要在啟動類上加注解:@EnableFeignClients,spring才能掃描到API接口,才能創(chuàng)建代理對象,代理對象才能發(fā)起服務(wù)器之間的遠(yuǎn)程調(diào)用。
![]()
website-server對異常的統(tǒng)一處理:
* 對website-server的異常統(tǒng)一處理
@ControllerAdvice
public class WebsiteServerExceptionAdvice extends CommonExceptionAdvice{
@ExceptionHandler(Exception.class)
@ResponseBody
public Result handlerDefaultException(Exception ex){
ex.printStackTrace();
return Result.error(WebsiteServerCodeMsg.DEFAULT_ERROR);
啟動測試:
兩處錯誤:
![]()
最終:
微服務(wù)架構(gòu)逐漸成熟,如何做到相對獨立,成了當(dāng)前重點考慮的問題。
我將分割了以下基礎(chǔ)模塊:
user-service 用戶認(rèn)證服務(wù)
shopping-cart-service 購物車服務(wù)
info-version-service 信息版本服務(wù)
user-edit-service 用戶編輯服務(wù)
order-service 訂單服務(wù)
seller-info-service 店鋪信息
discount-Service 折扣 促銷服務(wù)
inventory-service 庫存服務(wù)
account-service 賬戶會員服務(wù)
payment-service 支付組件
promotion-service 促銷服務(wù)Base
Search-service 搜索服務(wù)
concern-Service 互動服務(wù)
tracks-service 足跡
Cms Search-service CMS搜索服務(wù)
Seller-service 商戶認(rèn)證服務(wù)
product-Mgr-service 商品管理服務(wù)
Order-mgr-service 訂單服務(wù)
Catalog-mgr-service 產(chǎn)品目錄
Seller-edit-service 商戶編輯服務(wù)
sendout-service 發(fā)貨服務(wù)/推送
Grant-service 權(quán)限服務(wù)
employee-service 雇員認(rèn)證服務(wù)
employee-mgr-service 雇員服務(wù)
Promotion-mgr-Service 促銷管理
static-service 靜態(tài)化服務(wù)
product-Mgr-service 商品管理服務(wù)
Order-mgr-service 訂單服務(wù)
Catalog-mgr-service 產(chǎn)品目錄
dict-service 字典管理服務(wù)
Cms-service CMS服務(wù)
Seller-edit-service 商戶編輯服務(wù)
![]()






