title: 微服務總結
date: 2020/02/20 10:33
author: yujx
前言:系統架構演變
注:微服務架構之前都是前后端未分離的時代。
單體應用架構
當網站流量很小時,只需一個應用,將所有功能都部署在一起,以減少部署節點和成本。
比如說一個電商系統,里面會包含很多用戶管理,商品管理,訂單管理,物流管理等等很多模塊,我們會把它們做成一個web項目,然后部署到一臺tomcat服務器上。
優點:
- 項目架構簡單,小型項目的話,開發成本低
- 項目部署在一個Tomcat上,易于部署
缺點:
- 全部功能集成在一個工程中,對于大型項目來講不易開發和維護
- 項目模塊之間緊密耦合,單點容錯率低,并發能力差(一個功能掛掉,其它全部歇菜。單個Tomcat的并發有限制)
- 無法針對不同模塊進行針對性優化和水平擴展
垂直應用架構
當訪問量逐漸增大,單一應用無法滿足需求,單一應用只能依靠增加節點來應對,但是這時候會發現并不是所有的模塊都會有比較大的訪問量。
還是以上面的電商為例子,用戶訪問量的增加可能影響的只是用戶和訂單模塊,但是對消息模塊的影響就比較小. 那么此時我們希望只多增加幾個訂單模塊,而不增加消息模塊。此時單體應用就做不到了,垂直應用就應運而生了。
垂直應用架構,就是將原來的一個應用拆成互不相干的幾個應用,以提升效率。
比如我們可 以將上面電商的單體應用拆分成:
- 電商系統(商品管理、訂單管理)
- 后臺系統(用戶管理、訂單管理、客戶管理)
- CMS系統(廣告管理、營銷管理)
這樣拆分完畢之后,一旦用戶訪問量變大,只需要增加電商系統的節點就可以了,而無需增加后臺和CMS的節點。
優點:
- 系統拆分實現了流量分擔,解決了并發問題
- 可以針對不同模塊進行優化和水平擴展(集群)
- 一個系統的問題不會影響到其他系統,提高容錯率
缺點:系統間相互獨立,無法互相調用,從而導致會有很多重復開發工作
分布式架構
當垂直應用越來越多,重復的業務代碼就會越來越多。這時候,我們就思考可不可以將重復的代碼抽取出來,做成統一的業務層作為獨立的服務,然后由前端控制層調用不同的業務層服務呢?
這就產生了新的分布式系統架構。它將把工程拆分成表現層和服務層兩個部分,服務層中包含業務 邏輯。表現層只需要處理和頁面的交互,業務邏輯都是調用服務層的服務來實現。
優點:抽取公共的功能為服務層,提高代碼復用性
缺點:系統間耦合度變高,調用關系錯綜復雜,難以維護
注:由于當時前后端還未分離,所以一定要有一個web層進行前端代碼的書寫。(我認為的,不一定對)
SOA架構(面向服務架構)
在分布式架構下,當服務越來越多,容量的評估,小服務資源的浪費等問題逐漸顯現,此時需增加一個調度中心對集群進行實時管理。此時,用于資源調度和治理中心是關鍵。
注1:資源就是指的我們的服務。
注2:面向服務架構引出了一個概念 —— 服務治理(SOA governance)
SOA是一種粗粒度、松耦合服務架構,服務之間通過簡單、精確定義接口進行通訊,不涉及底層編程接口和通訊模型。SOA可以看作是B/S模型、Web Service技術之后的自然延伸。
服務治理,也稱為SOA治理,是指用來管理SOA的采用和實現的過程。以下是在2006年時IBM對于服務治理要點的總結:
- 服務定義(服務的范圍、接口和邊界)
- 服務部署生命周期(各個生命周期階段)
- 服務版本治理(包括兼容性)
- 服務遷移(啟用和退役)
- 服務注冊中心(依賴關系)
- 服務消息模型(規范數據模型)
- 服務監視(進行問題確定)
- 服務所有權(企業組織)
- 服務測試(重復測試)
- 服務安全(包括可接受的保護范圍)
限于當時的技術發展水平,廣大軟件設計與開發人員對于SOA和服務治理的技術認知還主要停留在Web Service和ESB總線等技術和規范上,并沒有真正在軟件開發中得以充分落地。
中心化:服務之間互相調用通過ESB實現,這樣的缺點,網上寫了很多,這里不贅述了。
去中心化:服務注冊到注冊中心,當調用時,服務調用時,調用方通過注冊中心獲取要調用的地址,進行調用(當然,還可以客戶端緩存下來)
優點:使用注冊中心\ESB解決了服務間調用關系的自動調節
缺點:
- 服務間會有依賴關系,一旦某個環節出錯會影響較大(服務雪崩)
- 服務關系復雜,運維、測試部署困難
微服務
微服務是由以單一應用程序構成的小服務,自己擁有自己的行程與輕量化處理,服務依業務功能設計,以全自動的方式部署,與其他服務使用 HTTP API 通信。同時服務會使用最小的規模的集中管理 (例如 Docker) 能力,服務可以用不同的編程語言與數據庫等組件實現。(微服務使用不同的數據庫) —— WIKI
微服務實際上是SOA的一個子集,微服務架構強調的一個重點是“業務需要徹底的組件化和服務化”,原有的單個業務系統會拆分為多個可以獨立開發、設計、運行的小應用。這些小應用之間通過服務完成交互和集成。
微服務的特點:
- 單一職責:微服務中每一個服務都對應唯一的業務能力,做到單一職責
- 微:微服務的服務拆分粒度很小,例如一個用戶管理就可以作為一個服務。每個服務雖小,但“五臟俱全”。
- 面向服務:面向服務是說每個服務都要對外暴露服務接口API。并不關心服務的技術實現,做到與平臺和語言無關,也不限定用什么技術實現,只要提供Rest的接口即可。
- 自治:自治是說服務間互相獨立,互不干擾
- 團隊獨立:每個服務都是一個獨立的開發團隊,人數不能過多。
- 技術獨立:因為是面向服務,提供Rest接口,使用什么技術沒有別人干涉
- 前后端分離:采用前后端分離開發,提供統一Rest接口,后端不用再為PC、移動段開發不同接口
- 數據庫分離:每個服務都使用自己的數據源
- 部署獨立,服務間雖然有調用,但要做到服務重啟不影響其它服務。有利于持續集成和持續交付。每個服務都是獨立的組件,可復用,可替換,降低耦合,易維護
微服務的設計原則
Spring Cloud架構格式
微服務架構的常見問題:
- 這么多小服務,如何管理他們?(服務注冊與發現 -> 注冊中心[服務注冊 發現 剔除])
- 這么多小服務,他們之間如何通訊?(rest\rpc)
- 這么多小服務,客戶端怎么訪問他們?(服務網關)
- 這么多小服務,一旦出現問題了,應該如何自處理?(服務容錯)
- 這么多小服務,一旦出現問題了,應該如何排錯?(鏈路追蹤)
總結
系統架構實際上是由于現有架構滿足不了需求從而對原有架構進行演變過來的,所以在選擇的時候一定要選擇適合的。
微服務架構是SOA架構的子集,只不過微服務定義了服務劃分的粒度、通信方式。
一、微服務架構常見概念
1.1 服務注冊與發現
一般會通過注冊中心來實現服務的注冊與發現,服務注冊中心一般有下面3個功能:
- 服務注冊:服務實例將自身服務信息注冊到注冊中心。
- 服務發現:服務實例通過注冊中心,獲取到注冊到其中的服務實例的信息,通過這些信息去請求它們提供的服務。
- 服務剔除:服務注冊中心將出問題的服務自動剔除到可用列表之外,使其不會被調用到。
1.2 服務調用
在微服務架構中,通常存在多個服務之間的遠程調用的需求。目前主流的遠程調用技術有基于
HTTP的RESTful接口以及基于TCP的RPC協議。
REST:這是一種HTTP調用的格式,更標準,更通用,無論哪種語言都支持http協議
RPC:一種進程間通信方式。允許像調用本地服務一樣調用遠程服務。RPC框架的主要目標就是讓遠程服務調用更簡單、透明。RPC框架負責屏蔽底層的傳輸方式、序列化方式和通信細節。開發人員在使用的時候只需要了解誰在什么位置提供了什么樣的遠程服務接口即可,并不需要關心底層通信細節和調用過程。
區別:
REST | RPC | |
---|---|---|
通訊協議 | HTTP | 一般使用TCP |
性能 | 略低 | 較高 |
靈活度 | 高 | 低 |
應用 | 一般應用于微服務架構 | 一般應用于SOA架構 |
1.3 服務網關
隨著微服務的不斷增多,不同的微服務一般會有不同的網絡地址,而外部客戶端可能需要調用多個服務的接口才能完成一個業務需求,如果讓客戶端直接與各個微服務通信可能出現:
- 客戶端需要調用不同的url地址,增加難度
- 在一定的場景下,存在跨域請求的問題
- 每個微服務都需要進行單獨的身份認證
針對這些問題,API網關順勢而生。
API網關直面意思是將所有API調用統一接入到API網關層,由網關層統一接入和輸出。一個網關的基本功能有:統一接入、安全防護、協議適配、流量管控、長短鏈接支持、容錯能力。有了網關之后,各個API服務提供團隊可以專注于自己的的業務邏輯處理,而API網關更專注于安全、流量、路由等問題。
1.4 服務容錯
在微服務當中,一個請求經常會涉及到調用幾個服務,如果其中某個服務不可用,沒有做服務容錯的話,極有可能會造成一連串的服務不可用,這就是雪崩效應。
我們沒法預防雪崩效應的發生,只能盡可能去做好容錯。服務容錯的三個核心思想是:
- 不被外界環境影響(觀察服務的外界環境,例如服務器CPU、內存,保證外界環境是良好的,不會影響應用內部)
- 不被上游請求壓垮
- 不被下游響應拖垮
1.5 鏈路追蹤
隨著微服務架構的流行,服務按照不同的維度進行拆分,一次請求往往需要涉及到多個服務。互聯 網應用構建在不同的軟件模塊集上,這些軟件模塊,有可能是由不同的團隊開發、可能使用不同的編程 語言來實現、有可能布在了幾千臺服務器,橫跨多個不同的數據中心。因此,就需要對一次請求涉及的多個服務鏈路進行日志記錄,性能監控即鏈路追蹤。
二、微服務架構的常見解決方案
2.1 ServiceComb
Apache ServiceComb,前身是華為云的微服務引擎 CSE (Cloud Service Engine) 云服務,是全球首個Apache微服務頂級項目。它提供了一站式的微服務開源解決方案,致力于幫助企業、用戶和開發者將企業應用輕松微服務化上云,并實現對微服務應用的高效運維管理。
2.2 SpringCloud
Spring Cloud是一系列框架的集合。它利用Spring Boot的開發便利性巧妙地簡化了分布式系統基 礎設施的開發,如服務發現注冊、配置中心、消息總線、負載均衡、斷路器、數據監控等,都可以用 Spring Boot的開發風格做到一鍵啟動和部署。
Spring Cloud并沒有重復制造輪子,它只是將目前各家公司開發的比較成熟、經得起實際考驗的服 務框架組合起來,通過Spring Boot風格進行再封裝屏蔽掉了復雜的配置和實現原理,最終給開發者留 出了一套簡單易懂、易部署和易維護的分布式系統開發工具包。
注:上面的組件都是可以任意組合的。
其中SpringCloud Netflix已經停止開發,所以我們只能使用SpringCloud Alibaba了。
2.2.1 SpringCloud Alibaba
Spring Cloud Alibaba 致力于提供微服務開發的一站式解決方案。此項目包含開發分布式應用微服務的必需組件,方便開發者通過 Spring Cloud 編程模型輕松使用這些組件來開發分布式應用服務。
依托 Spring Cloud Alibaba,您只需要添加一些注解和少量配置,就可以將 Spring Cloud 應用接入阿里微服務解決方案,通過阿里中間件來迅速搭建分布式應用系統。
主要功能
服務限流降級:默認支持 WebServlet、WebFlux, OpenFeign、RestTemplate、Spring Cloud Gateway, Zuul, Dubbo 和 RocketMQ限流降級功能的接入,可以在運行時通過控制臺實時修改限流降級規則,還支持查看限流降級 Metrics 監控。
服務注冊與發現:適配Spring Cloud 服務注冊與發現標準,默認集成了 Ribbon 的支持。
分布式配置管理:支持分布式系統中的外部化配置,配置更改時自動刷新。 消息驅動能力:基于 Spring Cloud Stream為微服務應用構建消息驅動能力。
分布式事務:使用 @GlobalTransactional 注解,高效并且對業務零侵入地解決分布式事務問題。
阿里云對象存儲:阿里云提供的海量、安全、低成本、高可靠的云存儲服務。支持在任何應用、任何時間、任何地點存儲和訪問任意類型的數據。
分布式任務調度:提供秒級、精準、高可靠、高可用的定時(基于 Cron
表達式)任務調度服務。同時提供分布式的任務執行模型,如網格任務。網格任務支持海量子任務均勻分配到所有 Worker(schedulerx-client)上執行。
阿里云短信服務:覆蓋全球的短信服務,友好、高效、智能的互聯化通訊能力,幫助企業迅速搭建客戶觸達通道。
組件
Sentinel:把流量作為切入點,從流量控制、熔斷降級、系統負載保護等多個維度保護服務的穩定性。
Nacos:一個更易于構建云原生應用的動態服務發現、配置管理和服務管理平臺。
RocketMQ:一款開源的分布式消息系統,基于高可用分布式集群技術,提供低延時的、高可靠的消息發布與訂閱服務。
Dubbo:Apache Dubbo? 是一款高性能 Java RPC 框架。
Seata:阿里巴巴開源產品,一個易于使用的高性能微服務分布式事務解決方案。
Alibaba Cloud ACM:一款在分布式架構環境中對應用配置進行集中管理和推送的應用配置中心產品。
Alibaba Cloud OSS: 阿里云對象存儲服務(Object Storage Service,簡稱 OSS),是阿里云提供的海量、安全、低成本、高可靠的云存儲服務。您可以在任何應用、任何時間、任何地點存儲和訪問任意類型的數據。
Alibaba Cloud SchedulerX: 阿里中間件團隊開發的一款分布式任務調度產品,提供秒級、精準、高可靠、高可用的定時(基于 Cron 表達式)任務調度服務。
Alibaba Cloud SMS: 覆蓋全球的短信服務,友好、高效、智能的互聯化通訊能力,幫助企業迅速搭建客戶觸達通道。
三、服務注冊與發現 Nacos Discovery
3.1 簡介
服務注冊:在服務治理框架中,都會構建一個注冊中心,每個服務單元向注冊中心登記自己提供服務的詳細信息。并在注冊中心形成一張服務的清單,服務注冊中心需要以心跳的方式去監測清單中的服務是否可用,如果不可用,需要在服務清單中剔除不可用的服務。
服務發現:服務調用方向服務注冊中心咨詢服務,并獲取所有服務的實例清單,實現對具體服務實例的訪問。
常見的注冊中心
Zookeeper
zookeeper是一個分布式服務框架,是Apache Hadoop 的一個子項目,它主要是用來解決分布式應用中經常遇到的一些數據管理問題,如:統一命名服務、狀態同步服務、集群管理、分布式應用配置項的管理等。
Eureka
Eureka是Springcloud Netflix中的重要組件,主要作用就是做服務注冊和發現。
Consul
Consul是基于GO語言開發的開源工具,主要面向分布式,服務化的系統提供服務注冊、服務發現和配置管理的功能。Consul的功能都很實用,其中包括:服務注冊/發現、健康檢查、Key/Value 存儲、多數據中心和分布式一致性保證等特性。Consul本身只是一個二進制的可執行文件,所以安裝和部署都非常簡單,只需要從官網下載后,在執行對應的啟動腳本即可。
Nacos
Nacos是一個更易于構建云原生應用的動態服務發現、配置管理和服務管理平臺。它是 Spring Cloud Alibaba 組件之一,負責服務注冊發現和服務配置,可以這樣認為nacos=eureka+config。
3.2 Nacos 實戰
3.2.1 安裝
docker pull nacos/nacos-server
docker run --env MODE=standalone --name nacos -d -p 8848:8848 nacos/nacos-server
瀏覽器訪問 http://localhost:8848/nacos 就可以訪問服務,默認密碼為nacos/nacos。
3.2.2 將微服務注冊到nacos
1、在pom.xml中添加依賴
<!--nacos客戶端-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
2、在application.yml中添加nacos服務的地址
spring:
cloud:
nacos:
discovery:
server-addr: localhost:8848
3、啟動微服務,查看是否注冊到nacos
3.2.3 示例:訂單微服務調用商品微服務
1、在訂單微服務的主啟動類上添加@EnableDiscoveryClient
注解
2、將RestTemplate注入容器
@EnableDiscoveryClient
@EntityScan("cn.x5456.common")
@SpringBootApplication
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class);
}
@Bean
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
3、創建OrderController
@RestController
@Slf4j
public class OrderController {
@Autowired
private RestTemplate restTemplate;
// 我們可以通過它獲取到注冊到注冊中心的所有服務
@Autowired
private DiscoveryClient discoveryClient;
@Autowired
private OrderDao orderDao;
//下單
@RequestMapping("/order/prod/{pid}")
public Order order(@PathVariable("pid") Integer pid) {
log.info("接收到{}號商品的下單請求,接下來調用商品微服務查詢此商品信息", pid);
// 通過discoveryClient獲取服務的實例信息
List<ServiceInstance> instances = discoveryClient.getInstances("service-product");
ServiceInstance instance = instances.get(0);
//調用商品微服務,查詢商品信息
Product product = restTemplate.getForObject("http://" + instance.getHost() + ":" + instance.getPort() + "/product/" + pid, Product.class);
log.info("查詢到{}號商品的信息,內容是:{}", pid, JSON.toJSONString(product));
//下單(創建訂單)
Order order = new Order();
order.setUid(1);
order.setUsername("測試用戶");
order.setPid(pid);
order.setPname(product.getPname());
order.setPprice(product.getPprice());
order.setNumber(1);
orderDao.save(order);
return order;
}
}
4、啟動訂單和商品微服務,訪問 http://127.0.0.1:8091/order/prod/1
注:一般情況下,我們都會將一個微服務部署好多份,目的就是為了可以對請求進行負載,像我們上面那樣寫,每次調用都會使用同一個服務。
3.2.4 配置服務調用采用負載均衡
負載均衡就是將負載(工作任務,訪問請求)進行分攤到多個操作單元(服務器,組件)上進行執行。
根據負載均衡發生位置的不同,一般分為服務端負載均衡和客戶端負載均衡。
服務端負載均衡指的是發生在服務提供者一方,比如常見的nginx負載均衡。
而客戶端負載均衡指的是發生在服務請求的一方,也就是在發送請求之前已經選好了由哪個實例處理請求。
我們在微服務調用關系中一般會選擇客戶端負載均衡,也就是在服務調用的一方來決定服務由哪個提供者執行。
使用Netflix Ribbon 實現
1、再啟動一個商品微服務
2、在注入的RestTemplate上加上@LoadBalanced
注解
@EnableDiscoveryClient // 這個注解可以刪除
@EntityScan("cn.x5456.common")
@SpringBootApplication
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class);
}
@Bean
@LoadBalanced // 添加這個注解
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
3、修改order
方法
// 可以刪掉
@Autowired
private DiscoveryClient discoveryClient;
//下單--自定義負載均衡
@RequestMapping("/order/prod/{pid}")
public Order order(@PathVariable("pid") Integer pid) {
log.info("接收到{}號商品的下單請求,接下來調用商品微服務查詢此商品信息", pid);
Product product = restTemplate.getForObject("http://service-product/product/" + pid, Product.class);
log.info("查詢到{}號商品的信息,內容是:{}", pid, JSON.toJSONString(product));
//下單(創建訂單)
Order order = new Order();
order.setUid(1);
order.setUsername("測試用戶");
order.setPid(pid);
order.setPname(product.getPname());
order.setPprice(product.getPprice());
order.setNumber(1);
orderDao.save(order);
log.info("創建訂單成功,訂單信息為{}", JSON.toJSONString(order));
return order;
}
4、多訪問幾遍 http://127.0.0.1:8091/order/prod/1 ,查看日志。
Ribbon支持的負載均衡策略
Ribbon內置了多種負載均衡策略,內部負載均衡的頂級接口為 com.netflix.loadbalancer.IRule,具體的負載策略如下表所示:
策略名 | 策略聲明 | 策略描述 | 實現說明 |
---|---|---|---|
BestAvailableRule | public class BestAvailableRule extends ClientConfigEnabledRoundRobinRule | 選擇一個最小的并發請求的server | 逐個考察Server,如果Server被tripped了,則忽略,在選擇其中ActiveRequestsCount最小的server |
AvailabilityFilteringRule | public class AvailabilityFilteringRule extends PredicateBasedRule | 過濾掉那些因為一直連接失敗的被標記為circuit tripped的后端server,并過濾掉那些高并發的的后端server(active connections 超過配置的閾值) | 使用一個AvailabilityPredicate來包含過濾server的邏輯,其實就就是檢查status里記錄的各個server的運行狀態 |
WeightedResponseTimeRule | public class WeightedResponseTimeRule extends RoundRobinRule | 根據響應時間分配一個weight,響應時間越長,weight越小,被選中的可能性越低。 | 一個后臺線程定期的從status里面讀取評價響應時間,為每個server計算一個weight。Weight的計算也比較簡單responsetime 減去每個server自己平均的responsetime是server的權重。當剛開始運行,沒有形成status時,使用roubine策略選擇server。 |
RetryRule | public class RetryRule extends AbstractLoadBalancerRule | 對選定的負載均衡策略機上重試機制。 | 在一個配置時間段內當選擇server不成功,則一直嘗試使用subRule的方式選擇一個可用的server |
RoundRobinRule | public class RoundRobinRule extends AbstractLoadBalancerRule | roundRobin方式輪詢選擇server | 輪詢index,選擇index對應位置的server |
RandomRule | public class RandomRule extends AbstractLoadBalancerRule | 隨機選擇一個server | 在index上隨機,選擇index對應位置的server |
ZoneAvoidanceRule | public class ZoneAvoidanceRule extends PredicateBasedRule | 復合判斷server所在區域的性能和server的可用性選擇server | 使用ZoneAvoidancePredicate和AvailabilityPredicate來判斷是否選擇某個server,前一個判斷判定一個zone的運行性能是否可用,剔除不可用的zone(的所有server),AvailabilityPredicate用于過濾掉連接數過多的Server。 |
我們可以通過修改配置來調整Ribbon的負載均衡策略,具體代碼如下:
service-product: # 調用的提供者的名稱
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
四、服務調用 Open Feign
Feign是Spring Cloud提供的一個聲明式的偽Http客戶端,它使得調用遠程服務就像調用本地服務一樣簡單,只需要創建一個接口并添加一個注解即可。
Nacos很好的兼容了Feign,Feign默認集成了 Ribbon,所以在Nacos下使用Fegin默認就實現了負載均衡的效果。
4.1 使用
1、在order微服務中引入Feign的依賴
<!--fegin-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2、在主啟動類上添加@EnableFeignClients
注解,由于Feign會自動采用Ribbon做負載,所以就可以不用RestTemplate了。
@EnableFeignClients
@EntityScan("cn.x5456.common")
@SpringBootApplication
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class);
}
// @Bean
// @LoadBalanced
// public RestTemplate getRestTemplate() {
// return new RestTemplate();
// }
}
3、創建一個接口
//value用于指定調用nacos下哪個微服務
@FeignClient(value = "service-product")
public interface ProductClient {
//@FeignClient的value + @RequestMapping的value值 其實就是完成的請求地址 "http://service-product/product/" + pid
//指定請求的URI部分
@RequestMapping("/product/{pid}")
Product findByPid(@PathVariable("pid") Integer pid);
}
4、修改order
方法,使用Feign進行調用
@Autowired
private ProductClient productClient;
//下單--fegin
@RequestMapping("/order/prod/{pid}")
public Order order(@PathVariable("pid") Integer pid) {
log.info("接收到{}號商品的下單請求,接下來調用商品微服務查詢此商品信息", pid);
//調用商品微服務,查詢商品信息
Product product = productClient.findByPid(pid);
log.info("查詢到{}號商品的信息,內容是:{}", pid, JSON.toJSONString(product));
//下單(創建訂單)
Order order = new Order();
order.setUid(1);
order.setUsername("測試用戶");
order.setPid(pid);
order.setPname(product.getPname());
order.setPprice(product.getPprice());
order.setNumber(1);
orderDao.save(order);
log.info("創建訂單成功,訂單信息為{}", JSON.toJSONString(order));
return order;
}
五、服務容錯 Sentinel
5.1 服務雪崩效應
在微服務架構中,我們將業務拆分成一個個的服務,服務與服務之間可以相互調用,但是由于網絡原因或者自身的原因,服務并不能保證服務的100%可用,如果單個服務出現問題,調用這個服務就會出現網絡延遲,此時若有大量的網絡涌入,會形成任務堆積,最終導致服務癱瘓。
由于服務與服務之間的依賴性,故障會傳播,會對整個微服務系統造成災難性的嚴重后果,這就是服務故障的“雪崩效應” 。
雪崩發生的原因多種多樣,有不合理的容量設計,或者是高并發下某一個方法響應變慢,亦或是某臺機器的資源耗盡。我們無法完全杜絕雪崩源頭的發生,只有做好足夠的容錯,保證在一個服務發生問題,不會影響到其它服務的正常運行。也就是"雪落而不雪崩"。
5.2 常見的容錯方案
容錯說白了就是保護自己不被豬隊友拖垮的一些措施,下面介紹常見的服務容錯思路和組件。
常見的容錯思路
1、隔離
它是指將系統按照一定的原則劃分為若干個服務模塊,各個模塊之間相對獨立,無強依賴。當有故障發生時,能將問題和影響隔離在某個模塊內部,而不擴散風險,不波及其它模塊,不影響整體的系統服務。常見的隔離方式有:線程池隔離和信號量隔離。
2、超時
在上游服務調用下游服務的時候,設置一個最大響應時間,如果超過這個時間,下游未作出反應,就斷開請求,釋放掉線程。
3、限流
限流就是限制系統的輸入和輸出流量已達到保護系統的目的。為了保證系統的穩固運行,一旦達到的需要限制的閾值,就需要限制流量并采取少量措施以完成限制流量的目的。
4、熔斷
在互聯網系統中,當下游服務因訪問壓力過大而響應變慢或失敗,上游服務為了保護系統整 體的可用性,可以暫時切斷對下游服務的調用。這種犧牲局部,保全整體的措施就叫做熔斷。
服務熔斷一般有三種狀態:
- 熔斷關閉狀態(Closed) :服務沒有故障時,熔斷器所處的狀態,對調用方的調用不做任何限制
- 熔斷開啟狀態(Open) :后續對該服務接口的調用不再經過網絡,直接執行本地的fallback方法
- 半熔斷狀態(Half-Open):嘗試恢復服務調用,允許有限的流量調用該服務,并監控調用成功率。如果成功率達到預期,則說明服務已恢復,進入熔斷關閉狀態;如果成功率仍舊很低,則重新進入熔斷關閉狀態。
5、降級
降級其實就是為服務提供一個托底方案,一旦服務無法正常調用,就使用托底方案。
常見的容錯組件
Hystrix:
Hystrix是由Netflix開源的一個延遲和容錯庫,用于隔離訪問遠程系統、服務或者第三方庫,防止級聯失敗,從而提升系統的可用性與容錯性。
Resilience4J:
Resilicence4J一款非常輕量、簡單,并且文檔非常清晰、豐富的熔斷工具,這也是Hystrix官方推薦的替代產品。不僅如此,Resilicence4j還原生支持Spring Boot 1.x/2.x,而且監控也支持和 prometheus等多款主流產品進行整合。
Sentinel:
Sentinel 是阿里巴巴開源的一款斷路器實現,本身在阿里內部已經被大規模采用,非常穩定。
Sentinel | Hystrix | resilience4j | |
---|---|---|---|
隔離策略 | 信號量隔離(并發線程數限流) | 線程池隔離/信號量隔離 | 信號量隔離 |
熔斷降級策略 | 基于響應時間、異常比率、異常數等 | 異常比率模式、超時熔斷 | 基于異常比率、響應時間 |
實時統計實現 | 滑動窗口(LeapArray) | 滑動窗口(基于 RxJava) | Ring Bit Buffer |
動態規則配置 | 支持多種配置源 | 支持多種數據源 | 有限支持 |
擴展性 | 豐富的 SPI 擴展接口 | 插件的形式 | 接口的形式 |
基于注解的支持 | 支持 | 支持 | 支持 |
限流 | 基于 QPS,支持基于調用關系的限流 | 有限的支持 | Rate Limiter |
集群流量控制 | 支持 | 不支持 | 不支持 |
流量整形 | 支持預熱模式、勻速排隊模式等多種復雜場景 | 不支持 | 簡單的 Rate Limiter 模式 |
系統自適應保護 | 支持 | 不支持 | 不支持 |
控制臺 | 提供開箱即用的控制臺,可配置規則、查看秒級監控、機器發現等 | 簡單的監控查看 | 不提供控制臺,可對接其它監控系統 |
多語言支持 | Java / C++ | Java | Java |
開源社區狀態 | 活躍 | 停止維護 | 較活躍 |
5.3 Sentinel簡介&安裝
Sentinel (分布式系統的流量防衛兵) 是阿里開源的一套用于服務容錯的綜合性解決方案。它以流量為切入點,從流量控制、熔斷降級、系統負載保護等多個維度來保護服務的穩定性。
Sentinel 具有以下特征:
豐富的應用場景:Sentinel 承接了阿里巴巴近 10 年的雙十一大促流量的核心場景,例如秒殺(即突發流量控制在系統容量可以承受的范圍)、消息削峰填谷、集群流量控制、實時熔斷下游不可用應用等。
完備的實時監控:Sentinel 提供了實時的監控功能。通過控制臺可以看到接入應用的單臺機器秒級數據,甚至 500 臺以下規模的集群的匯總運行情況。
廣泛的開源生態:Sentinel 提供開箱即用的與其它開源框架/庫的整合模塊,例如與 Spring Cloud、Dubbo、gRPC 的整合。只需要引入相應的依賴并進行簡單的配置即可快速地接入Sentinel。
完善的 SPI 擴展點:Sentinel 提供簡單易用、完善的 SPI 擴展接口。您可以通過實現擴展接口來快速地定制邏輯。例如定制規則管理、適配動態數據源等。
Sentinel 分為兩個部分:
核心庫(Java 客戶端)不依賴任何框架/庫,能夠運行于所有 Java 運行時環境,同時對 Dubbo / Spring Cloud 等框架也有較好的支持。
控制臺(Dashboard)基于 Spring Boot 開發,打包后可以直接運行,不需要額外的 Tomcat 等應用容器。
安裝Sentinel控制臺
見項目的docker文件夾中。
安裝完成后,訪問http://127.0.0.1:8099/,默認密碼為sentinel/sentinel
Sentinel的控制臺其實就是一個SpringBoot編寫的程序。我們需要將我們的微服務程序注冊到控制臺上,即在微服務中指定控制臺的地址,并且還要開啟一個跟控制臺傳遞數據的端口,控制臺也可以通過此端口調用微服務中的監控程序獲取微服務的各種信息。
將order微服務注冊到Sentinel控制臺 & 簡單使用
1、引入依賴
<!--sentinel-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
2、編寫OrderController2
@RestController
@Slf4j
public class OrderController2 {
@RequestMapping("/order/message1")
public String message1() {
return "message1";
}
@RequestMapping("/order/message2")
public String message2() {
return "message2";
}
}
3、在application.yml添加配置
spring:
cloud:
sentinel:
transport:
port: 9999 #跟控制臺交流的端口,隨意指定一個未使用的端口即可
dashboard: localhost:8099 # 指定控制臺服務的地址
4、啟動訂單微服務,訪問http://127.0.0.1:8091/order/message1
,刷新控制臺頁面
5、為/order/message1
接口添加一個流控規則
6、快速訪問http://127.0.0.1:8091/order/message1
5.4 Sentinel的基本概念&功能
基本概念
資源:資源是 Sentinel 的關鍵概念。它可以是 Java 應用程序中的任何內容,可以是一個服務,也可以是一個方法,甚至可以是一段代碼。換句話說資源就是Sentinel要保護的東西。
注:上面那個demo中
message1
方法就可以認為是一個資源
規則:規則作用在資源之上,定義以什么樣的方式保護資源,主要包括流量控制規則、熔斷降級規則以及系統保護規則。
注:上面那個demo中就是為message1資源設置了一種流控規則,限制了進入message1的流量
功能(可以聯系5.2閱讀)
流量控制:
流量控制在網絡傳輸中是一個常用的概念,它用于調整網絡包的數據。任意時間到來的請求往往是隨機不可控的,而系統的處理能力是有限的。我們需要根據系統的處理能力對流量進行控制。
熔斷降級:
當檢測到調用鏈路中某個資源出現不穩定的表現,例如請求響應時間長或異常比例升高的時候,則對這個資源的調用進行限制,讓請求快速失敗,避免影響到其它的資源而導致級聯故障。
Sentinel 對這個問題采取了兩種手段:
-
通過并發線程數進行限制:
- Sentinel 通過限制資源并發線程的數量,來減少不穩定資源對其它資源的影響。當某個資源出現不穩定的情況下,例如響應時間變長,對資源的直接影響就是會造成線程數的逐步堆積。當線程數在特定資源上堆積到一定的數量之后,對該資源的新請求就會被拒絕。堆積的線程完成任務后才開始繼續接收請求。
-
通過響應時間對資源進行降級:
- 除了對并發線程數進行控制以外,Sentinel 還可以通過響應時間來快速降級不穩定的資源。當依賴的資源出現響應時間過長后,所有對該資源的訪問都會被直接拒絕,直到過了指定的時間窗口之后才重新恢復。
注:Sentinel 和 Hystrix 的區別?
兩者的原則是一致的,都是當一個資源出現問題時,讓其快速失敗,不要波及到其它服務。
但是在限制的手段上,確采取了完全不一樣的方法:
Hystrix 采用的是線程池隔離的方式,優點是做到了資源之間的隔離,缺點是增加了線程切換的成本。
Sentinel 采用的是通過并發線程的數量和響應時間來對資源做限制。(信號量隔離)
系統負載保護
Sentinel 同時提供系統維度的自適應保護能力。當系統負載較高的時候,如果還持續讓請求進入可能會導致系統崩潰,無法響應。在集群環境下,會把本應這臺機器承載的流量轉發到其它的機器上去。如果這個時候其它的機器也處在一個邊緣狀態的時候,Sentinel 提供了對應的保護機制,讓系統的入口流量和系統的負載達到一個平衡,保證系統在能力范圍之內處理最多的請求。
注:系統的負載能力,與其所在的服務器內存和CPU等外界環境有關。
5.5 Sentinel規則
5.5.1 流控規則
流量控制,其原理是監控應用流量的QPS(每秒查詢率) 或并發線程數等指標,當達到指定的閾值時對流量進行控制,以避免被瞬時的流量高峰沖垮,從而保障應用的高可用性。
資源名:唯一名稱,默認是請求路徑,可自定義
針對來源;指定對哪個微服務進行限流,默認指default,意思是不區分來源,全部限制
閾值類型/單機閾值:
- QPS(每秒請求數量):當調用該接口的QPS達到閾值的時候,進行限流
- 線程數:當調用該接口的線程數達到閾值的時候,進行限流
是否集群:暫不需要集群
配置好后,瘋狂請求/order/message1接口時,就會出現下面的情況:
3種流控模式
1、直接流控模式
直接流控模式是最簡單的模式,當指定的接口達到限流條件時開啟限流。就是我們上面演示的例子。
2、關聯流控模式
關聯流控模式指的是,當指定接口關聯的接口達到限流條件時,開啟對指定接口開啟限流。
當/order/message2
接口的OPS大于1的時候,/order/message1
就會被限流。
3、鏈路流控模式
鏈路流控模式指的是,當從某個接口過來的資源達到限流條件時,開啟限流。它的功能有點類似于針對來源配置項,區別在于:針對來源是針對上級微服務,而鏈路流控是針對上級接口,也就是說它的粒度更細。
1)編寫OrderService
@Service
public class OrderServiceImpl {
// 定義資源 value是資源的名稱
@SentinelResource("message")
public void message() {
System.out.println("message");
}
}
2)修改OrderController2中的2個方法,調用service層的message方法。
@RestController
@Slf4j
public class OrderController2 {
@Autowired
private OrderServiceImpl orderService;
@RequestMapping("/order/message1")
public String message1() {
orderService.message();
return "message1";
}
@RequestMapping("/order/message2")
public String message2() {
orderService.message();
return "message2";
}
}
3)禁止收斂URL的入口 context
從1.6.3 版本開始,Sentinel Web filter默認收斂所有URL的入口context,因此鏈路限流不生效。
1.7.0 版本開始(對應SCA的2.1.1.RELEASE),官方在CommonFilter 引入了 WEB_CONTEXT_UNIFY 參數,用于控制是否收斂context。將其配置為 false 即可根據不同的URL 進行鏈路限流。
SCA 2.1.1.RELEASE之后的版本,可以通過配置spring.cloud.sentinel.web-context-unify=false即可關閉收斂我們當前使用的版本是SpringCloud Alibaba 2.1.0.RELEASE,無法實現鏈路限流。
目前官方還未發布SCA 2.1.2.RELEASE,所以我們只能使用2.1.1.RELEASE,需要寫代碼的形式實現
下面的修改,測試完畢之后要改回來;生產環境最好還是等2.1.2.RELEASE版本發布,配合Cloud Hoxton使用。
(1)將SpringCloud Alibaba的版本調整為2.1.1.RELEASE
<spring-cloud-alibaba.version>2.1.1.RELEASE</spring-cloud-alibaba.version>
(2)配置文件中關閉sentinel的CommonFilter實例化
spring:
cloud:
sentinel:
filter:
enabled: false
(3)手動注入CommonFilter的實例
@Configuration
public class FilterContextConfig {
@Bean
public FilterRegistrationBean sentinelFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new CommonFilter());
registration.addUrlPatterns("/*");
// 入口資源關閉聚合
registration.addInitParameter(CommonFilter.WEB_CONTEXT_UNIFY, "false");
registration.setName("sentinelFilter");
registration.setOrder(1);
return registration;
}
}
4)重啟order微服務,訪問http://127.0.0.1:8091/order/message1
5)配置鏈路限流
6)狂點刷新
3種流控效果
快速失敗(默認):直接失敗,拋出異常,不做任何額外的處理,是最簡單的效果
Warm Up:它從開始閾值到最大QPS閾值會有一個緩沖階段,一開始的閾值是最大QPS閾值的1/3,然后慢慢增長,直到最大閾值,適用于將突然增大的流量轉換為緩步增長的場景。
排隊等待:讓請求以均勻的速度通過,單機閾值為每秒通過數量,其余的排隊等待; 它還會讓設置一個超時時間,當請求超過超時間時間還未處理,則會被丟棄。
5.5.2 降級規則
降級規則就是設置當滿足什么條件的時候,對服務進行降級。Sentinel提供了三個衡量條件:
平均響應時間(RT)
當資源的平均響應時間超過閾值(以 ms 為單位)之后,資源進入準降級狀態。如果接下來 1s 內持續進入 5 個請求,它們的 RT都持續超過這個閾值,那么在接下的時間窗口(以 s 為單位))之內,就會對這個方法進行服務降級。
注:注意 Sentinel 默認統計的 RT 上限是 4900 ms,超出此閾值的都會算作 4900 ms,若需要變更此上限可以通過啟動配置項 -Dcsp.sentinel.statistic.max.rt=xxx 來配置。
異常比例
當資源的每秒異常總數占通過量的比值超過閾值之后,資源進入降級狀態,即在接下的時間窗口(以 s 為單位)之內,對這個方法的調用都會自動地返回。異常比率的閾值范圍是[0.0, 1.0]。
異常數
當資源近 1 分鐘的異常數目超過閾值之后會進行服務降級。注意由于統計時間窗口是分鐘級別的,若時間窗口小于 60s,則結束熔斷狀態后仍可能再進入熔斷狀態。
5.5.3 熱點規則
熱點參數流控規則是一種更細粒度的流控規則,它允許將規則具體到參數上。
1、在OrderController2中新增一個方法,記得添加@SentinelResource
注解
@RequestMapping("/order/message3")
@SentinelResource("message3")
public String message3(String name, Integer age) {
return "message3" + name + age;
}
2、配置熱點規則
單機閥值:每秒訪問量進行限流
統計窗口時長:超過多長時間,恢復訪問
5.5.4 授權規則
很多時候,我們需要根據調用來源來判斷該次請求是否允許放行,例如知網會根據請求的refer來判斷是爬蟲還是正常的用戶訪問,從而返回不同的頁面,這時候可以使用 Sentinel 的來源訪問控制的功能。來源訪問控制根據資源的請求來源(origin)限制資源是否通過:
- 若配置白名單,則只有請求來源位于白名單內時才可通過;
- 若配置黑名單,則請求來源位于黑名單時不通過,其余的請求通過。
1、OrderController中新增一個方法
@RequestMapping("/order/message4")
public String message4() {
return "message4";
}
2、實現RequestOriginParser,定義解析規則
@Component
public class RequestOriginParserDefinition implements RequestOriginParser {
//定義區分來源: 本質作用是通過request域獲取到來源標識
//app pc
//然后 交給流控應用 位置進行匹配
@Override
public String parseOrigin(HttpServletRequest request) {
return request.getParameter("serviceName");
}
}
3、重啟系統,訪問 http://127.0.0.1:8091/order/message4
4、授權
5、當訪問 http://127.0.0.1:8091/order/message4?serviceName=pc 可以正常訪問,訪問其他的會失敗。
5.5.5 系統規則(一般運維配置)
系統保護規則是從應用級別的入口流量進行控制,從單臺機器的總體 Load、RT、入口 QPS 、CPU 使用率和線程數五個維度監控應用數據,讓系統盡可能跑在最大吞吐量的同時保證系統整體的穩定性。
系統保護規則是應用整體維度的,而不是資源維度的,并且僅對入口流量 (進入應用的流量)生效。
- Load(僅對 Linux/Unix-like 機器生效):當系統 load1(1min閾值)超過閾值,且系統當前的并發線程數超過系統容量時才會觸發系統保護。系統容量由系統的 maxQps * minRt 計算得出。設定參考值一般 是 CPU cores * 2.5。
- RT:當單臺機器上所有入口流量的平均 RT 達到閾值即觸發系統保護,單位是毫秒。
- 線程數:當單臺機器上所有入口流量的并發線程數達到閾值即觸發系統保護。
- 入口 QPS:當單臺機器上所有入口流量的 QPS 達到閾值即觸發系統保護。
- CPU使用率:當單臺機器上所有入口流量的 CPU使用率達到閾值即觸發系統保護。
擴展:自定義異常返回
問題:流控規則和降級規則返回的異常頁面是一樣的,我們怎么來區分到底是什么原因導致的呢?
//自定義異常返回頁面
@Component
public class ExceptionHandlerPage implements UrlBlockHandler {
// BlockException是五種規則異常的父類
@Override
public void blocked(HttpServletRequest request, HttpServletResponse response, BlockException e) throws IOException {
response.setContentType("application/json;charset=utf-8");
ResponseData responseData = null;
//BlockException 異常接口,包含Sentinel的五個異常
// FlowException 限流異常
// DegradeException 降級異常
// ParamFlowException 參數限流異常
// AuthorityException 授權異常
// SystemBlockException 系統負載異常
if (e instanceof FlowException) {
responseData = new ResponseData(-1, "接口被限流了...");
} else if (e instanceof DegradeException) {
responseData = new ResponseData(-2, "接口被降級了...");
}
response.getWriter().write(JSON.toJSONString(responseData));
}
}
@Data
@AllArgsConstructor//全參構造
@NoArgsConstructor
//無參構造
class ResponseData {
private int code;
private String message;
}
5.6 @SentinelResource的使用
在定義了資源點之后,我們可以通過Dashboard來設置限流和降級策略來對資源點進行保護。同時還能通過@SentinelResource來指定出現異常時的處理策略。
屬性 | 作用 | 是否必須 |
---|---|---|
value | 資源名稱 | 是 |
entryType | entry類型,標記流量的方向,取值IN/OUT,默認是OUT | 否 |
blockHandler | 處理BlockException的函數名稱。函數要求: 1. 必須是 public 2.返回類型與原方法一致 3. 參數類型需要和原方法相匹配,并在最后加 BlockException 類型的參數。 4. 默認需和原方法在同一個類中。若希望使用其他類的函數,可配置 blockHandlerClass ,并指定blockHandlerClass里面的方法。 |
否 |
blockHandlerClass | 存放blockHandler的類。對應的處理函數必須static修飾,否則無法解析,其他要求:同blockHandler。 | 否 |
fallback | 用于在拋出異常的時候提供fallback處理邏輯。fallback函數可以針對所有類型的異常(除了 exceptionsToIgnore 里面排除掉的異常類型)進行處理。函數要求: 1. 返回類型與原方法一致 2. 參數類型需要和原方法相匹配,Sentinel 1.6開始,也可在方法最后加 Throwable 類型的參數。 3.默認需和原方法在同一個類中。若希望使用其他類的函數,可配置 fallbackClass ,并指定fallbackClass里面的方法。 |
否 |
fallbackClass【1.6】 | 存放fallback的類。對應的處理函數必須static修飾,否則無法解析,其他要求:同fallback。 | 否 |
defaultFallback【1.6】 | 用于通用的 fallback 邏輯。默認fallback函數可以針對所有類型的異常(除了 exceptionsToIgnore 里面排除掉的異常類型)進行處理。若同時配置了 fallback 和 defaultFallback,以fallback為準。函數要求: 1. 返回類型與原方法一致 2. 方法參數列表為空,或者有一個 Throwable 類型的參數。 3. 默認需要和原方法在同一個類中。若希望使用其他類的函數,可配置 fallbackClass ,并指定 fallbackClass 里面的方法。 |
否 |
exceptionsToIgnore【1.6】 | 指定排除掉哪些異常。排除的異常不會計入異常統計,也不會進入fallback邏輯,而是原樣拋出。 | 否 |
exceptionsToTrace | 需要trace的異常 | Throwable |
5.6.1 示例
1、修改OrderServiceImpl中message方法上的注解,添加幾個參數
@Slf4j
@Service
public class OrderServiceImpl {
//定義一個資源
//定義當資源內部發生異常的時候的處理邏輯
//blockHandler 定義當資源內部發生了BlockException應該進入的方法[捕獲的是Sentinel定義的異常]
//fallback 定義當資源內部發生了Throwable應該進入的方法
@SentinelResource(
value = "message",
blockHandler = "blockHandler",
fallback = "fallback"
)
public String message() {
return "message";
}
//blockHandler
//要求:
//1 當前方法的返回值和參數要跟原方法一致
//2 但是允許在參數列表的最后加入一個參數BlockException, 用來接收原方法中發生的異常
public String blockHandler(BlockException e) {
//自定義異常處理邏輯
log.error("觸發了BlockException,內容為{}", e);
return "BlockException";
}
//fallback
//要求:
//1 當前方法的返回值和參數要跟原方法一致
//2 但是允許在參數列表的最后加入一個參數BlockException, 用來接收原方法中發生的異常
public String fallback(Throwable e) {
//自定義異常處理邏輯
log.error("觸發了Throwable,內容為{}", e);
return "Throwable";
}
}
2、修改OrderController2中的message1()方法
@RequestMapping("/order/message1")
public String message1() {
return orderService.message();
}
3、去控制臺添加流控規則
4、快速訪問 http://127.0.0.1:8091/order/message1
5.6.2 將限流和降級方法外置到單獨的類中
@Slf4j
@Service
public class OrderServiceImpl {
//定義一個資源
//定義當資源內部發生異常的時候的處理邏輯
//blockHandler 定義當資源內部發生了BlockException應該進入的方法[捕獲的是Sentinel定義的異常]
//fallback 定義當資源內部發生了Throwable應該進入的方法
@SentinelResource(
value = "message",
blockHandlerClass = OrderServiceImpl3BlockHandler.class,
blockHandler = "blockHandler",
fallbackClass = OrderServiceImpl3Fallback.class,
fallback = "fallback"
)
public String message() {
return "message";
}
public static class OrderServiceImpl3BlockHandler {
//blockHandler
//要求:
//1 當前方法的返回值和參數要跟原方法一致
//2 但是允許在參數列表的最后加入一個參數BlockException, 用來接收原方法中發生的異常
public static String blockHandler(BlockException e) {
//自定義異常處理邏輯
log.error("觸發了BlockException,內容為{}", e);
return "BlockException";
}
}
public static class OrderServiceImpl3Fallback {
//fallback
//要求:
//1 當前方法的返回值和參數要跟原方法一致
//2 但是允許在參數列表的最后加入一個參數BlockException, 用來接收原方法中發生的異常
public static String fallback(Throwable e) {
//自定義異常處理邏輯
log.error("觸發了Throwable,內容為{}", e);
return "Throwable";
}
}
}
注:類中的方法必須是static的
5.7 Sentinel規則持久化
Sentinel規則默認是存放在內存中,極不穩定,所以需要將其持久化。
本地文件數據源會定時輪詢文件的變更,讀取規則。這樣我們既可以在應用本地直接修改文件來更新規則,也可以通過 Sentinel 控制臺推送規則。以本地文件數據源為例,推送過程如下圖所示:
首先 Sentinel 控制臺通過 API 將規則推送至客戶端并更新到內存中,接著注冊的寫數據源會將新的規則保存到本地的文件中。
1、編寫處理類
參見cn.x5456.order.config.FilePersistence
2、添加配置
在resources下創建配置目錄 META-INF/services,然后添加文件
com.alibaba.csp.sentinel.init.InitFunc,文件內容為配置類的全路徑
5.8 Feign整合Sentinel
1、開啟Feign對Sentinel的支持
# 開啟feign對sentinel的支持
feign:
sentinel:
enabled: true
2、創建容錯類
//這是一個容錯類
//它要求實現Feign所在接口,并實現里面的方法
//當feign調用出現問題的時候,就會進入到當前類中同名方法中
@Component
public class ProductServiceFallback implements ProductClient {
@Override
public Product findByPid(Integer pid) {
Product product = new Product();
product.setPid(-100);
product.setPname("商品微服務調用出現異常了,已經進入到了容錯方法中");
return product;
}
}
3、修改ProductClient上的注解
//value用于指定調用nacos下哪個微服務
@FeignClient(value = "service-product", fallback = ProductServiceFallback.class)
public interface ProductClient {
//@FeignClient的value + @RequestMapping的value值 其實就是完成的請求地址 "http://service-product/product/" + pid
//指定請求的URI部分
@RequestMapping("/product/{pid}")
Product findByPid(@PathVariable("pid") Integer pid);
}
4、修改OrderController的order方法
//下單--fegin
@RequestMapping("/order/prod/{pid}")
public Order order(@PathVariable("pid") Integer pid) {
log.info("接收到{}號商品的下單請求,接下來調用商品微服務查詢此商品信息", pid);
//調用商品微服務,查詢商品信息
Product product = productClient.findByPid(pid);
if (product.getPid() == -100) {
Order order = new Order();
order.setOid(-100L);
order.setPname("下單失敗");
return order;
}
log.info("查詢到{}號商品的信息,內容是:{}", pid, JSON.toJSONString(product));
//下單(創建訂單)
Order order = new Order();
order.setUid(1);
order.setUsername("測試用戶");
order.setPid(pid);
order.setPname(product.getPname());
order.setPprice(product.getPprice());
order.setNumber(1);
orderDao.save(order);
log.info("創建訂單成功,訂單信息為{}", JSON.toJSONString(order));
return order;
}
5、啟動服務,訪問 http://127.0.0.1:8091/order/prod/1,由于商品微服務未開,所以會報錯
6、采用這種方式,異常信息被隱藏了,所以可以采用另外一種方式
1)新建ProductServiceFallbackFactory類
//這是容錯類,他要求我們要是實現一個FallbackFactory<要為哪個接口產生容錯類>
@Slf4j
@Service
public class ProductServiceFallbackFactory implements FallbackFactory<ProductClient> {
//Throwable 這就是fegin在調用過程中產生異常
@Override
public ProductClient create(Throwable throwable) {
return new ProductClient() {
@Override
public Product findByPid(Integer pid) {
log.error("{}",throwable);
Product product = new Product();
product.setPid(-100);
product.setPname("商品微服務調用出現異常了,已經進入到了容錯方法中");
return product;
}
};
}
}
2)修改ProductClient上的注解
//value用于指定調用nacos下哪個微服務
@FeignClient(value = "service-product", fallbackFactory = ProductServiceFallbackFactory.class)
public interface ProductClient {
//@FeignClient的value + @RequestMapping的value值 其實就是完成的請求地址 "http://service-product/product/" + pid
//指定請求的URI部分
@RequestMapping("/product/{pid}")
Product findByPid(@PathVariable("pid") Integer pid);
}
注:fallback和fallbackFactory只能使用其中一種方式