引用維基百科:spring
微服務是一種架構風格,一個大型複雜軟件應用由一個或多個微服務組成。系統中的各個微服務可被獨立部署,各個微服務之間是鬆耦合的。每一個微服務僅關注於完成一件任務並很好地完成該任務。在全部狀況下,每一個任務表明着一個小的業務能力。數據庫
微服務架構的一些通用特性編程
什麼是 springcloud?json
Spring Cloud是伴隨着微服務的概念誕生的.基於 SpringBoot.後端
和 SpringCloud 密切相關的Netflix是一傢什麼公司?數組
Netflix是世界上最大的在線影片租賃提供商,向它的670萬名顧客提供超過85,000部DVD電影的租賃服務,並且能向顧客提供4000多部影片或者電視劇的在線觀看服務。公司的成功源自於可以提供超大數量的DVD,並且可以讓顧客快速方便的挑選影片,同時免費遞送。Netflix已經連續五次被評爲顧客最滿意的網站.緩存
爲何 Netflix 要作 SpringCould? Netflix 和 SpringCloud 的前世此生.tomcat
一、首先,Netflix是一家作視頻的網站,能夠這麼說該網站上的美劇應該是最火的。安全
二、Netflix是一家沒有CTO的公司,正是這樣的組織架構能使產品與技術無縫的溝通,從而能快速迭代出更優秀的產品。在當時軟件敏捷開發中,Netflix的更新速度不亞於當年的微信後臺變動,雖然微信比Netflix遲發展,可是當年微信的灰度發佈和敏捷開發應該算是業界最猛的。服務器
三、Netflix因爲作視頻的緣由,訪問量很是的大,從而促使其技術快速的發展在背後支撐着,也正是如此,Netflix開始把總體的系統往微服務上遷移。
四、Netflix的微服務作的不是最先的,可是確是最大規模的在生產級別微服務的嘗試。也正是這種大規模的生產級別嘗試,在服務器運維上依託AWS雲。固然AWS雲一樣受益於Netflix的大規模業務不斷的壯大。
五、Netflix的微服務大規模的應用,在技術上毫無保留的把一整套微服務架構核心技術棧開源了出來,叫作Netflix OSS,也正是如此,在技術上依靠開源社區的力量不斷的壯大。
六、Spring Cloud是構建微服務的核心,而Spring Cloud是基於Spring Boot來開發的。
七、Pivotal在Netflix開源的一整套核心技術產品線的同時,作了一系列的封裝,就變成了Spring Cloud;雖然Spring Cloud到如今爲止不僅有Netflix提供的方案能夠集成,還有不少方案,但Netflix是最成熟的。
SpringCloud 有哪些牛逼的功能?
SpringCloud 號稱擁有五虎將(即最經常使用的五個功能). 哪五虎將?
還有一些其餘的功能, 好比事件總線, 配置管理 API, 輪詢框架, Consul 可與 Docker 無縫集成, Sleuth 日誌收集工具包, Data Flow 大數據操做工具, Security 安全工具包, Zookeeper 操做 ZK 的工具包, Stream 數據流操做開發包, 封裝了 Redis, Rabbit, Kafka. Ribbon 負載均衡, Feign 聲明式的 HTTP 客戶端, Task 提供計劃任務管理,任務調度框架, Cluster 提供 Leadership 選舉, 相似 Zookeeper 選舉. Starters 爲 SpringCloud 提供開箱即用的依賴管理.
讓咱們先來看看傳統IT架構面臨的一些問題:
咱們爲何要使用 SpringCloud ?
現有的框架爲多個 SpringBoot 的框架, 最明顯的一個問題就是, 重複代碼太多, 直接致使的問題就是修改一處, 其他地方都要修改, 代碼難以維護. 好比一個User 這張表,可能每一個 SpringBoot 都須要 User 表的查詢功能, 可是除了 User 模塊, 其他模塊也要建立 User 相關的類和配置文件,一旦 User 表字段更改, 全部相關模塊都要更改, 使人恐懼. 用 SpringCloud 能解決這個問題嗎? 能. 只須要 User 開放一個 關於 User 表的查詢接口, 其餘模塊調用此接口,就能實現以前的功能. 避免了大量的重複代碼. 提升了代碼的可維護性.
第二個問題就是應用擴展問題, 只要企業快速發展, 全部的後端都不可避免的要實施分佈式, 將一個大的服務拆分爲一個個小服務, 保證系統的快速迭代和快速擴展. A 應用故障不會致使 B 應用也故障. 現有的框架沒法支持橫向擴展和快速迭代, 以前的架構成爲咱們的痛點。 用 SpringCloud 能解決這個問題嗎? SpringCloud 爲分佈式和微服務而生, 拆分巨型應用, 使得每一個模塊都獨立, 根據業務拆分服務, 也可根據業務的改變合併服務, SpringCloud 支持集羣部署異常簡單, 且自帶軟負載均衡, 配合 Zuul 網關實現服務認證, 安全過濾等功能. SpringCloud 自帶的斷路器可以很好的容災, 當某個服務不通時, 不會影響整個服務致使雪崩性的效應. 而且若是某個服務須要迭代, 其他模塊可絲絕不受影響. 更改架構後能承受更高的併發和用戶量。
第三個問題是原有的代碼沒法支持分庫分表,原有的表所有都再一個庫種,業務高度耦合,難以維護,爲了踐行微服務「去中心化」數據管理的理念,每一個服務管理其自有數據庫,咱們必須將表根據業務進行分割,以應對後期分庫。
原有架構不清晰,水平擴展和快速迭代沒有成熟和現行的技術方案,使用成熟的 SpringCloud 方案能夠減小項目風險,提升應對風險的能力和應對業務快速變化的要求。
快速開始已經有不少文章.
目前咱們重構老代碼的主要方向是:
遇到了哪些問題呢?
@RequestParam()
註解, 也可使用@PathVariable
路徑傳參. 也可使用@RequestBody
註解傳對象,關於這三個註解的用法,須要注意一下他們的用法和坑點. **
@RequestParam
用法@RequestParam
配合 GET 請求註解用於標註普通類型的參數, 好比8個基本類型和他們的包裝類或者 String 類型. 也可用使用在 Map 上, 例如fun(@RequestParam ("map")Map<K,V> map)
, 也可使用在 Date 類型上, 例如 fun(@RequestParam("date") Date date)
,注意
, 只要有參數, 且參數是基本類型或者是包裝類型和 String 類型, 必須
使用該註解配合 GET 請求. 不然確定報錯. **
@PathVariable()
用法該註解用於配合路徑中的佔位符使用, 注意
: 該註解必須在子類上也寫上註解, 也就是說, 服務提供方的 API 接口 若是使用了該註解, 那麼實現該接口的子類必須在參數前加上此註解, 這三個註解都不支持繼承,由於Spring 是不能識別該註解應該做用於哪一個參數, 若是不寫, 就會獲得 Null 值, 致使錯誤. 那麼 @RequestParam
須要寫嗎? 答案是不須要寫的, 由於@RequestParam
是 url 傳參, Spring 支持將參數名稱映射到參數上給定的參數上. 所以, 使用該註解時須要注意
的是: 必須在子類中加入該註解. 還有, 該註解不能傳相似版本號的數字(如:1.2.1), 會致使 http 解析時去掉最後的小數點. 建議使用 URL中的www.google.com?name=tom
傳參. ** @RequestBody
用法
若是參數不少, 難道咱們要一個一個參數寫上去嗎? 不須要, 咱們可使用@RequestBody
註解配合 POST 請求, 將多個參數封裝成一個對象, 固然 @RerquestParam
註解配合 GET 加上 Map 參數也能夠, 可是不建議使用 Map 做爲參數, Map 會出現不少問題, 咱們稍後再講. 回到咱們的@RequestBody
註解的用法上面來, 注意
: 這個註解也必須在子類上聲明, 不然參數沒法映射. 由於 Spring 映射時找的是子類, 而該註解的做用是: 解析Body 裏的內容變成 JSON, 而後映射到參數中, 若是不寫, Spring 將不知道如何映射. 由於 Spirng 也是支持用流獲取參數的. 而且該註解也是 Swagger 文檔框架的基礎. 注意
, 一個方法中只能有一個@RequestBody
註解. 注意
: 該註解要求 Http 消息頭中包含: Content-Type:Application/json.
總結一下上面關於三個註解的內容.
1. 實現類除@RequestParam(若是參數是map則須要加) 不須要加註解,其他都要加註解; 2. map 能夠用get傳也能夠用post傳; 3. get 請求不能有一個或多個複雜對象(頂多用一個map代替多個參數); 4. post 請求有且只能有一個複雜對象,必須使用@RequestBody 註解; 5. set 類型使用post 請求 + @RequestBody 註解; 6. 不能使用數組,包裝類型也不行,可用List代替:get請求+@RequestParam, Post請求中的 @RequestBody,jackSon工具沒法解析數組,由於數組沒有類型信息.而get請求框架會自動參數隱射; 7. Date 類型 使用post + @RequestBody或者,使用 get + @RequestParam; 8. 註解必須搭配使用,GetMapping + @RequestParam, @PostMapping + @RequestBody;
data=new Date()
, 在 xml 配置文件中則是create_date <= #{date}
, 本來這樣寫是沒有任何問題的. 可是一旦把 Map 做爲網絡調用的參數, 當你把 Date 類型傳過去的時候, 而 Value 的泛型又是 Objece, 那麼那邊接受到的就是 Long 類型的時間戳, 在 xml 文件中就會出現create_date <= 1213244343432
, 很明顯, 就會溢出報錯. 因此, 咱們後期都將作了特殊處理, 相似下面這樣: public void function(Map<String, Object> map){ Long date = (Long)map.get("date"); // 將Long 類型轉換成 Date 類型 map.put("date", new Date(date)); }
因此, 之後遇到須要傳遞 Date 類型的時候, 儘可能不要使用 Map, 若是使用 Map, Value 的泛型也儘可能不要寫 Object.
3 . 還有一個問題就是分頁插件的問題, 咱們使用的 pageHelper 的開源分頁插件, 原理是在當前線程中放入一個 ThreadLocal 的 Page 對象, 當調用動態代理了 mybatis 的方法時, 通過代理對象 invoke 方法時會觸發繼承了 Mybatis 攔截器的分頁插件, 分頁插件會從 ThreadLocal 中取出分頁對象, 進行分頁. 執行結束後會刪除該 Page 對象. 那麼咱們發生一個什麼故障呢? 原有的分頁是這樣寫的:
public List<User> getPageUser(User user){ // 該方法進行分頁, 構造一個 Page 對象, 將 Page 對象做爲 ThreadLocal // 放入當前線程 PageHelper.start(page, rows); // 執行查詢 SQL userMapper.select(user); }
這段代碼以前寫是沒有任何問題的, 可是, 因爲 UserMapper 被抽取出來成爲公共部分代碼. 因此這個地方被重構成:
public List<User> getPageUser(User user){ // 該方法進行分頁, 構造一個 Page 對象, 將 Page 對象做爲 ThreadLocal // 放入當前線程 PageHelper.start(page, rows); // 注意: 這裏變成了網絡調用, 執行 SQL 的線程已經在另外一個虛擬機中. userClient.select(user); }
能夠看到, userMapper
變成了 userClient
, 原有的 SQL 變成了網絡調用. 那麼這個時候會發生什麼事情呢? ` PageHelper.start(page, rows); 放入線程的
Page 對象將不會被消費, 也不會被刪除, 由於當前線程根本不會執行
Mybatis 的方法, 更不會進入攔截器, 而容器使用的是線程池, 當該方法結束後, 攜帶着
Page 對象的線程會隨機分配一個任務執行
SQL, 若是該
SQL 不支持分頁, 可是分頁插件發現該線程中含有
Page 對象, 就會強行分頁, 致使錯誤, 而這個錯誤很難排查. 因此, 當使用
PageHelper.start(page, rows);方法後, 在本任務執行結束以前必定要在本虛擬機中跟一條
SQL , 也就是跟一個
Mybatis 的方法. 不然將會影響其餘線程中
SQL` 的執行. 致使報錯.
還有就是, 重構後, 實體類的全限定名都變化了, 若是使用緩存,或者是任何依賴序列化的中間件, 都會由於類名不對致使沒法反序列化爲新實體類而報錯. 因此應該在保證安全的狀況下, 將緩存清空.
關於斷路器的使用, 斷路器的做用是, 若是 Feign
調用失敗, 而且重試屢次失敗(個人測試是5秒以內連續10次失敗以後), 就會觸發斷路器, 也就是咱們重寫的方法, 剛開始, 咱們直接在斷路器中返回了默認值, 好比若是是對象就返回 null, 若是是容器就返回空, 若是是基本類型就返回默認值, 可是, 仔細一想不對, 若是業務代碼將 null 和默認值做爲邏輯判斷怎麼辦, 實際上, 網絡調用失敗返回 null
和調用成功返回 null
是不一樣的. 所以, 咱們將觸發斷路器方法中的內容改成了拋出異常. 避免影響老代碼的邏輯, 能夠在新代碼的使用中, 針對斷路器如何返回值作出新的約定. 防止斷路器和業務返回內容混淆. 斷路器何時會恢復調用呢?答案是5秒以後斷路器會變成半開閉的狀態,若是有服務請求,就會嘗試調用一次,若是成功則關閉斷路器,若是失敗,則開啓斷路器。5秒以後就又變成了半開閉的狀態。
還有一個坑點就是: 如何傳遞 Header
, , 咱們須要的網絡調用中傳遞 Header
, 而咱們現有的 Header
都是存放在 ThreadLocal
中的, 難道咱們要寫一個 Feign
的配置類, 並在每一個配置類上加上一個@Header
的註解? 或者咱們要將 Header
放在參數中傳遞嗎? 答案是沒必要的. 咱們只須要攔截 Http Request
, 在 Http
請求前加入咱們須要的 Header
. 而這就引出了第七個問題。
SpringCloud Feign 和斷路器爲了服務的高可用和應用健壯性, 提供了二種隔離策略
: 線程池隔離
和信號量隔離
,
簡單說一下什麼是
線程池隔離
, SpringCloud將服務調用的主線程和 Feign 調用的Http請求分開存放在不一樣的線程池, 爲何要這麼作呢?試想一下若是不分開存放, 當主線程調用時超時,那麼 tomcat 就會阻塞大量線程,影響整個服務,但,若是使用線程池隔離
, Feign 請求會開闢新的線程,就算超時也不影響總體應用的,更不影響tomcat 線程池中的線程,保證了容器的安全。
再說一下什麼是
信號量隔離
, 信號量就是系統設置的併發請求數,若是設置的信號量爲10, 那麼該服務接口的請求併發數就爲10, 超過10就進行服務降級,忽略請求。信號量隔離是使用tomcat中的主線程進行服務請求,所以,不能算是隔離,只能算是限流,限制請求的數量,即便目標服務不通,也不會拖垮整個 tomcat 中的服務。若是請求的服務速度很快,併發很高,而且提供方服務穩定。那麼使用信號量
是合算的。由於不會有線程的上下文切換。不然使用線程池隔離
比較合算。
那麼對於咱們來講,
隔離策略
帶給咱們什麼影響呢? 第6點的時候咱們說,咱們須要將Header傳遞, 而Header放在ThreadLocal 中,因此,線程池隔離
的策略難以無縫支持ThreadLocal 中的Header。 除非特殊配置或者寫在參數中,但這須要修改大量代碼,咱們想使用切面的方式將Header 透明的傳遞。信號量隔離
策略和適合咱們,既能保證服務的穩定性,也能保證服務中Header的傳遞。因此咱們選擇了信號量隔離
。
因爲分佈式調用出錯調試較爲複雜, 所以, 之前返回客戶端只是服務端錯誤
, 如今使用@ExceptionHandler(HystrixRuntimeException.class)
註解, 細化每一個異常, 用以返回不一樣的錯誤信息, 方便排錯.
測試用例在以前貌似不怎麼重要, 由於一個 debug 能夠一路調通, 但分佈式的可能須要跨越多層調用, 所以, 單元測試就顯得很重要, 能將錯誤慢慢分割. 從而更加快速精準的定位異常緣由, 所以, 重構後增長了大量的測試用例, 用以排錯.
統一配置類, 使用@ComponentScans()
註解掃描一個共同的配置模塊, 方便重構和配置.不然, 大量的配置類將不一樣統一配置, 咱們將疲於奔命.
SpringCloud Feign 客戶端第一次遠程調用可能會失敗, 緣由是因爲Spring 是懶加載的, 調用Feign須要加載不少類,須要一些時間, 而Feign 默認超時一秒就會認爲服務調用失敗, 拋出異常。 所以咱們測試時只須要關注第二次調用便可。
1. 什麼是微服務? 什麼是 springcloud? 2. 咱們爲何使用 SpringCloud? 3. 如何使用 SpringCloud? 如何 Quick Start? 4. 遷移過程當中,老代碼使用 springcloud 須要注意哪些坑?