手寫網(wǎng)關(guān)上高性能通用熔斷組件!
Sentinel能夠在高并發(fā)、大流量的場(chǎng)景下有效的對(duì)流量進(jìn)行管控,包括限流、熔斷、降級(jí)、系統(tǒng)負(fù)載保護(hù)等,為了讓小伙伴們更加清楚的了解Sentinel的功能和使用案例,今天就系統(tǒng)性的帶著大家以實(shí)戰(zhàn)的方式學(xué)習(xí)Sentinel的核心技術(shù)和配置規(guī)則。
冰河又偷偷給大家肝了一個(gè)高性能通用熔斷組件項(xiàng)目,這個(gè)項(xiàng)目經(jīng)過(guò)實(shí)際高并發(fā)大促場(chǎng)景壓測(cè),可直接引用這個(gè)熔斷組件的Jar包到實(shí)際項(xiàng)目中,通過(guò)注解快速實(shí)現(xiàn)業(yè)務(wù)項(xiàng)目接口、方法的熔斷。支持單一熔斷數(shù)據(jù)指標(biāo)的熔斷,支持不同熔斷指標(biāo)數(shù)據(jù)的任意組合熔斷。熔斷規(guī)則配置化,標(biāo)準(zhǔn)化,高并發(fā)大流量場(chǎng)景下可隨時(shí)更新熔斷規(guī)則,隨時(shí)解除熔斷,更新配置時(shí)及時(shí)生效。文末領(lǐng)券學(xué)習(xí)更實(shí)惠。值得一提的是,我們自己手寫的高性能網(wǎng)關(guān)項(xiàng)目會(huì)零侵入直接對(duì)接高性能通用熔斷組件。
學(xué)習(xí)鏈接:https://articles.zsxq.com/id_zuv6si9ztzb2.html
一、Sentinel核心功能
隨著微服務(wù)的流行,服務(wù)和服務(wù)之間的穩(wěn)定性變得越來(lái)越重要。Sentinel 以流量為切入點(diǎn),從流量控制、熔斷降級(jí)、系統(tǒng)負(fù)載保護(hù)等多個(gè)維度保護(hù)服務(wù)的穩(wěn)定性。所以,Sentinel的核心功能包括:流量控制、熔斷降級(jí)、系統(tǒng)負(fù)載保護(hù)。
1.1 流量控制
在高并發(fā)、大流量場(chǎng)景下,進(jìn)入系統(tǒng)的流量如果不加控制的話,系統(tǒng)就很有可能會(huì)被流量壓垮。所以,在流量正式進(jìn)入系統(tǒng)之前,需要對(duì)流量進(jìn)行控制,以便使流量均勻、可控的方式進(jìn)入系統(tǒng)。
Sentinel作為一個(gè)非常出色的容錯(cuò)組件,能夠?qū)⒉豢煽氐牧髁拷?jīng)過(guò)處理轉(zhuǎn)化成均勻、可控的流量。
1.2 熔斷降級(jí)
如果檢測(cè)到系統(tǒng)中的某個(gè)調(diào)用鏈路中某個(gè)節(jié)點(diǎn)出現(xiàn)故障,比如請(qǐng)求超時(shí)、服務(wù)宕機(jī)或者異常比超出一定閾值時(shí),就會(huì)對(duì)出現(xiàn)故障的節(jié)點(diǎn)的調(diào)用頻率進(jìn)行限制,甚至不調(diào)用出現(xiàn)故障的節(jié)點(diǎn),讓請(qǐng)求能夠快速失敗并返回,以最大程度避免影響到其他節(jié)點(diǎn)的服務(wù)而導(dǎo)致系統(tǒng)的級(jí)聯(lián)故障。
Sentinel主要通過(guò) 限制并發(fā)線程數(shù)和響應(yīng)時(shí)間 對(duì)資源的訪問(wèn)進(jìn)行降級(jí)。
1.限制并發(fā)線程數(shù)進(jìn)行降級(jí)
Sentinel可以通過(guò)限制服務(wù)節(jié)點(diǎn)的并發(fā)線程數(shù)量,來(lái)減少對(duì)其他服務(wù)節(jié)點(diǎn)的影響。例如,當(dāng)某個(gè)服務(wù)節(jié)點(diǎn)出現(xiàn)故障,響應(yīng)時(shí)間變長(zhǎng),或者直接宕機(jī)。
此時(shí),對(duì)服務(wù)的直接影響就是會(huì)造成請(qǐng)求線程數(shù)的不斷堆積。如果這些堆積的線程數(shù)達(dá)到一定的數(shù)量后,對(duì)當(dāng)前服務(wù)節(jié)點(diǎn)的后續(xù)請(qǐng)求就會(huì)被拒絕,等到堆積的線程完成任務(wù)后再開(kāi)始繼續(xù)接收新的請(qǐng)求。
2.通過(guò)響應(yīng)時(shí)間進(jìn)行降級(jí)
Sentinel除了可以通過(guò)限制并發(fā)線程數(shù)進(jìn)行降級(jí)外,也能夠通過(guò)響應(yīng)時(shí)間進(jìn)行降級(jí)。如果依賴的服務(wù)出現(xiàn)響應(yīng)時(shí)間過(guò)長(zhǎng)的情況,則所有對(duì)該服務(wù)的請(qǐng)求都會(huì)被拒絕,直到過(guò)了指定的時(shí)間窗口之后才能再次訪問(wèn)該服務(wù)。
1.3 系統(tǒng)負(fù)載保護(hù)
Sentinel提供了系統(tǒng)維度的自適應(yīng)保護(hù)能力。當(dāng)系統(tǒng)的壓力和負(fù)載比較高的時(shí)候,如果還持續(xù)讓大量的請(qǐng)求進(jìn)入系統(tǒng),此時(shí)就有可能將系統(tǒng)壓垮,進(jìn)而導(dǎo)致系統(tǒng)宕機(jī)。
Sentinel會(huì)在集群環(huán)境下,將本應(yīng)服務(wù)器A承載的流量轉(zhuǎn)發(fā)到其他服務(wù)器上,比如轉(zhuǎn)發(fā)到服務(wù)器B上。如果此時(shí)服務(wù)器B也處于高負(fù)載的狀態(tài),則Sentinel會(huì)提供相應(yīng)的保護(hù)機(jī)制,讓系統(tǒng)的入口流量和系統(tǒng)的整體負(fù)載達(dá)到平衡,讓系統(tǒng)整體可用,并且能夠最大限度的處理請(qǐng)求。
二、Sentinel核心規(guī)則
Sentinel的核心規(guī)則包括流控規(guī)則、熔斷規(guī)則、熱點(diǎn)規(guī)則、授權(quán)規(guī)則和系統(tǒng)規(guī)則,每種規(guī)則的配置方式不同。接下來(lái),就詳細(xì)介紹下Sentinel中的每種規(guī)則的作用與效果。
圖片
三、流控規(guī)則
Sentinel能夠?qū)α髁窟M(jìn)行控制,主要是監(jiān)控應(yīng)用的QPS流量或者并發(fā)線程數(shù)等指標(biāo),如果達(dá)到指定的閾值時(shí),就會(huì)被流量進(jìn)行控制,以避免服務(wù)被瞬時(shí)的高并發(fā)流量擊垮,保證服務(wù)的高可靠性。
3.1 簇點(diǎn)鏈路規(guī)則
(1)點(diǎn)擊簇點(diǎn)鏈路菜單,可以看到之前訪問(wèn)過(guò)的接口,如下所示。
圖片
(2)點(diǎn)擊右側(cè)的流控按鈕,會(huì)彈出新增流控規(guī)則的提示框,如下所示。
圖片
這里,每個(gè)配置項(xiàng)的說(shuō)明如下所示。
- 資源名:資源的唯一名稱,默認(rèn)就是請(qǐng)求的接口路徑,可以自行修改,但是要保證唯一。
- 針對(duì)來(lái)源:具體針對(duì)某個(gè)微服務(wù)進(jìn)行限流,默認(rèn)值為default,表示不區(qū)分來(lái)源,全部限流。
- 閾值類型:QPS表示通過(guò)QPS進(jìn)行限流,并發(fā)線程數(shù)表示通過(guò)并發(fā)線程數(shù)限流。
- 單機(jī)閾值:與閾值類型組合使用。如果閾值類型選擇的是QPS,表示當(dāng)調(diào)用接口的QPS達(dá)到閾值時(shí),進(jìn)行限流操作。如果閾值類型選擇的是并發(fā)線程數(shù),則表示當(dāng)調(diào)用接口的并發(fā)線程數(shù)達(dá)到閾值時(shí),進(jìn)行限流操作。
- 是否集群:選中則表示集群環(huán)境,不選中則表示非集群環(huán)境。
3.2 配置簡(jiǎn)單限流
這里,針對(duì)http://localhost:8080/order/test_sentinel接口進(jìn)行簡(jiǎn)單的配置,在新增流控規(guī)則里閾值類型選擇QPS,單機(jī)閾值輸入3,表示每秒鐘的請(qǐng)求量如果超過(guò)3,則會(huì)觸發(fā)Sentinel的限流操作。
圖片
點(diǎn)擊新增按鈕后,會(huì)為http://localhost:8080/order/test_sentinel接口新增一條限流規(guī)則,如下所示。
圖片
接下來(lái),在瀏覽器上快速刷新http://localhost:8080/order/test_sentinel接口,當(dāng)每秒鐘的刷新頻率超過(guò)3次時(shí),會(huì)出現(xiàn)如下所示的提示信息。
圖片
3.3 配置流控模式
點(diǎn)擊http://localhost:8080/order/test_sentinel接口流控規(guī)則后面的編輯按鈕,打開(kāi)編輯流控規(guī)則彈出框(如果是首次配置的話,就是新增流控規(guī)則彈出框),點(diǎn)擊高級(jí)選項(xiàng)配置。
圖片
會(huì)顯示出如下所示的界面。
圖片
可以看到,Sentinel主要提供了三種流控模式,分別為直接、關(guān)聯(lián)和鏈路。
- 直接:默認(rèn)的流控模式,當(dāng)接口達(dá)到限流條件時(shí),直接開(kāi)啟限流功能。
- 關(guān)聯(lián):當(dāng)關(guān)聯(lián)的資源達(dá)到限流條件時(shí),開(kāi)啟限流功能。
- 鏈路:當(dāng)從某個(gè)接口請(qǐng)求過(guò)來(lái)的資源達(dá)到限流條件時(shí),開(kāi)啟限流功能。
演示直接流控模式
Sentinel默認(rèn)就是使用的直接流控模式,我們之前在訂單微服務(wù)中集成的就是Sentinel的直接流控模式,在本文的流控規(guī)則-配置簡(jiǎn)單限流中,也是使用的直接流控模式,這里不再贅述。
演示關(guān)聯(lián)流控模式
(1)在訂單微服務(wù)的io.binghe.shop.order.controller.OrderController 類中新增 /test_sentinel2接口,如下所示。
@GetMapping(value = "/test_sentinel2")
public String testSentinel2(){
log.info("測(cè)試Sentinel2");
return "sentinel2";
}
(2)在瀏覽器上訪問(wèn)下http://localhost:8080/order/test_sentinel2接口,以便使Sentinel檢測(cè)到http://localhost:8080/order/test_sentinel2接口。
圖片
注意:如果想使用Sentinel對(duì)某個(gè)接口進(jìn)行限流和降級(jí)等操作,一定要先訪問(wèn)下接口,使Sentinel檢測(cè)出相應(yīng)的接口,這里一定要注意,在后續(xù)的文章中,不再單獨(dú)說(shuō)明。
(3)回到為http://localhost:8080/order/test_sentinel接口配置流控規(guī)則的頁(yè)面,如下所示。
圖片
(4)在流控模式中選擇關(guān)聯(lián),在關(guān)聯(lián)資源中輸入/test_sentinel2,如下所示。
圖片
點(diǎn)擊保存按鈕保存配置。
(5)打開(kāi)JMeter,對(duì)http://localhost:8080/order/test_sentinel2接口進(jìn)行測(cè)試,使其每秒鐘的訪問(wèn)次數(shù)大于3,JMeter的具體配置如下所示。
圖片
在線程組中配置每秒訪問(wèn)4次。
圖片
配置完畢后,使用JMeter開(kāi)始訪問(wèn)http://localhost:8080/order/test_sentinel2接口。
(6)在瀏覽器上刷新http://localhost:8080/order/test_sentinel接口,發(fā)現(xiàn)已經(jīng)觸發(fā)了Sentinel的限流功能。
圖片
至此,關(guān)聯(lián)流控模式演示完畢。
演示鏈路流控模式
(1)在訂單微服務(wù)的io.binghe.shop.order.service包中新增SentinelService接口,用于測(cè)試流控模式,源碼如下所示。
/**
* @author binghe
* @version 1.0.0
* @description 測(cè)試Sentinel
*/
public interface SentinelService {
/**
* 測(cè)試方法
*/
void sendMessage();
}
(2)在訂單微服務(wù)的io.binghe.shop.order.service.impl包中新增SentinelServiceImpl類,實(shí)現(xiàn)SentinelService接口,源碼如下所示。
/**
* @author binghe
* @version 1.0.0
* @description 測(cè)試類
*/
@Service
public class SentinelServiceImpl implements SentinelService {
@Override
@SentinelResource("sendMessage")
public void sendMessage() {
System.out.println("測(cè)試Sentinel的鏈路流控模式");
}
}
這里,我們?cè)趕endMessage()方法上使用了@SentinelResource注解, @SentinelResource注解會(huì)在后續(xù)文章中介紹,這里不再贅述。
(3)在訂單微服務(wù)的io.binghe.shop.order.controller包下新建SentinelController類,用于測(cè)試接口,源碼如下所示。
/**
* @author binghe
* @version 1.0.0
* @description 測(cè)試Sentinel
*/
@Slf4j
@RestController
public class SentinelController {
@Autowired
private SentinelService sentinelService;
@GetMapping(value = "/request_sentinel1")
public String requestSentinel1(){
log.info("測(cè)試Sentinel1");
sentinelService.sendMessage();
return "sentinel1";
}
@GetMapping(value = "/request_sentinel2")
public String requestSentinel2(){
log.info("測(cè)試Sentinel2");
sentinelService.sendMessage();
return "sentinel2";
}
}
(4)升級(jí)SpringCloud Alibaba的依賴版本到2.2.7.RELEASE,升級(jí)SpringCloud版本到Hoxton.SR12,并且加入SpringBoot的管理依賴。主要的修改的是shop-springcloud-alibaba父工程的pom.xml配置,修改后的配置文件如下所示。
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring-cloud.version>Hoxton.SR12</spring-cloud.version>
<spring-cloud-alibaba.version>2.2.7.RELEASE</spring-cloud-alibaba.version>
<spring.boot.version>2.3.12.RELEASE</spring.boot.version>
<logback.version>1.1.7</logback.version>
<slf4j.version>1.7.21</slf4j.version>
<common.logging>1.2</common.logging>
<fastjson.version>1.2.51</fastjson.version>
<mybatis.version>3.4.6</mybatis.version>
<mybatis.plus.version>3.4.1</mybatis.plus.version>
<mysql.jdbc.version>8.0.19</mysql.jdbc.version>
<druid.version>1.1.10</druid.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
(5)升級(jí)Nacos,將Nacos注冊(cè)中心由1.4.3版本升級(jí)為2.1.0版本,并進(jìn)入Nacos的bin目錄,輸入如下命令啟動(dòng)Nacos。
startup.cmd -m standalone
(6)在訂單微服務(wù)的application.yml文件中新增鏈路配置,如下所示。
spring:
cloud:
sentinel:
web-context-unify: false
(7)在瀏覽器中分別訪問(wèn)http://localhost:8080/order/request_sentinel1和http://localhost:8080/order/request_sentinel2,查看Sentinel中的簇點(diǎn)鏈路,如下所示。
圖片
(8)點(diǎn)擊sendMessage后面的流控按鈕,如下所示。
圖片
(9)在彈出的新增流控規(guī)則編輯框中閾值類型選擇QPS,單機(jī)閾值輸入3,在打開(kāi)的高級(jí)選項(xiàng)中的流控模式選擇鏈路,入口資源輸入/request_sentinel1,如下所示。
圖片
點(diǎn)擊新增按鈕保存配置,此時(shí),如果是通過(guò)http://localhost:8080/order/request_sentinel1接口調(diào)用io.binghe.shop.order.service.SentinelService#sendMessage()方法時(shí),如果調(diào)用的頻率每秒鐘超過(guò)3次,就會(huì)觸發(fā)Sentinel的限流操作,而通過(guò)http://localhost:8080/order/request_sentinel2接口調(diào)用io.binghe.shop.order.service.SentinelService#sendMessage()方法時(shí),則不受限制。
(10)在瀏覽器中不斷訪問(wèn)http://localhost:8080/order/request_sentinel1,使得每秒的訪問(wèn)次數(shù)超過(guò)3,則會(huì)觸發(fā)Sentinel的限流操作,如下所示。
圖片
訪問(wèn)http://localhost:8080/order/request_sentinel2接口則不會(huì)被限流。
至此,鏈路流控模式演示完畢。
附加說(shuō)明
在流控規(guī)則的高級(jí)選項(xiàng)中還有三個(gè)流控效果,如下所示。
圖片
接下來(lái),就對(duì)這三個(gè)選項(xiàng)進(jìn)行簡(jiǎn)單的說(shuō)明。
- 快速失敗:會(huì)直接失敗,拋出異常,期間不會(huì)做任何其他的處理操作。
圖片
- Warm Up:從開(kāi)始閾值到最大QPS閾值會(huì)有一個(gè)緩沖,可以設(shè)置一個(gè)預(yù)熱時(shí)長(zhǎng),這個(gè)選項(xiàng)比較適合突發(fā)瞬時(shí)高并發(fā)流量的場(chǎng)景,能夠?qū)⑼话l(fā)的高并發(fā)流量轉(zhuǎn)換為均勻、緩慢增長(zhǎng)的場(chǎng)景。
圖片
- 排隊(duì)等待:能夠使請(qǐng)求均勻的通過(guò),單機(jī)的閾值為每秒通過(guò)的請(qǐng)求數(shù)量,其余的請(qǐng)求會(huì)排隊(duì)等待。另外,還會(huì)設(shè)置一個(gè)超時(shí)時(shí)間,當(dāng)請(qǐng)求超過(guò)超時(shí)時(shí)間未處理時(shí),會(huì)被丟棄。
圖片
四、熔斷規(guī)則
降級(jí)規(guī)則一般情況下,指的是滿足某些條件時(shí),對(duì)服務(wù)進(jìn)行降級(jí)操作。
4.1 熔斷規(guī)則概述
Sentinel主要提供了三個(gè)熔斷策略,分別為:慢調(diào)用比例、異常比例和異常數(shù)。
圖片
4.2 演示基于慢調(diào)用比例熔斷
(1)首先在瀏覽器中訪問(wèn)http://localhost:8080/order/request_sentinel2,在Sentinel的簇點(diǎn)鏈路里找到/request_sentinel2
圖片
(2)點(diǎn)擊熔斷按鈕,進(jìn)入熔斷規(guī)則配置框,按照如下方式進(jìn)行配置。
圖片
上述配置表示最大響應(yīng)時(shí)長(zhǎng)為1ms,比例閾值達(dá)到0.1時(shí),會(huì)觸發(fā)熔斷操作,并且熔斷的時(shí)長(zhǎng)為2,最小請(qǐng)求數(shù)未5,統(tǒng)計(jì)的時(shí)長(zhǎng)為1000毫秒。
(3)點(diǎn)擊新增按鈕后,不斷在瀏覽器中刷新http://localhost:8080/order/request_sentinel2,會(huì)發(fā)現(xiàn)觸發(fā)了Sentinel的熔斷操作,如下所示。
4.3 演示基于異常比例熔斷
(1)在訂單微服務(wù)的io.binghe.shop.order.controller.SentinelController類中定義一個(gè)成員變量count,用來(lái)記錄訪問(wèn)計(jì)數(shù),同時(shí),新增一個(gè)requestSentinel4()方法用來(lái)測(cè)試基于異常比例的熔斷,如下所示。
private int count = 0;
@GetMapping(value = "/request_sentinel4")
@SentinelResource("request_sentinel4")
public String requestSentinel4(){
log.info("測(cè)試Sentinel4");
count++;
//模擬異常,比例為50%
if (count % 2 == 0){
throw new RuntimeException("演示基于異常比例熔斷");
}
return "sentinel4";
}
(2)首先在瀏覽器中訪問(wèn)http://localhost:8080/order/request_sentinel4,在Sentinel的簇點(diǎn)鏈路里找到/request_sentinel4。
(3)點(diǎn)擊熔斷按鈕,進(jìn)入熔斷規(guī)則配置框,按照如下方式進(jìn)行配置。
圖片
在io.binghe.shop.order.controller.SentinelController#requestSentinel4()方法中,設(shè)置的異常比例為50%,這里在Sentinel的比例閾值中設(shè)置的異常比例為0.3,也就是30%,所以,在訪問(wèn)http://localhost:8080/order/request_sentinel4接口時(shí),會(huì)觸發(fā)Sentinel的熔斷操作。
(4)點(diǎn)擊新增按鈕后,不斷在瀏覽器中刷新http://localhost:8080/order/request_sentinel4,會(huì)發(fā)現(xiàn)觸發(fā)了Sentinel的熔斷操作,如下所示。
圖片
4.4 演示基于異常數(shù)熔斷
(1)首先在瀏覽器中訪問(wèn)http://localhost:8080/order/request_sentinel4,在Sentinel的簇點(diǎn)鏈路里找到/request_sentinel4。
圖片
(2)點(diǎn)擊熔斷按鈕,進(jìn)入熔斷規(guī)則配置框,按照如下方式進(jìn)行配置。
圖片
上述配置表示,在1秒鐘內(nèi)最少請(qǐng)求2次,當(dāng)異常數(shù)大于1時(shí),會(huì)觸發(fā)熔斷操作,熔斷的時(shí)長(zhǎng)為5秒。
(3)點(diǎn)擊保存按鈕后,不斷在瀏覽器中刷新http://localhost:8080/order/request_sentinel4,會(huì)發(fā)現(xiàn)觸發(fā)了Sentinel的熔斷操作,如下所示。
圖片
五、熱點(diǎn)規(guī)則
Sentinel的熱點(diǎn)規(guī)則可以將流量規(guī)則控制到具體的參數(shù)上。
5.1 熱點(diǎn)規(guī)則概述
Sentinel的熱點(diǎn)規(guī)則可以根據(jù)具體的參數(shù)來(lái)控制流量規(guī)則,適用于根據(jù)不同參數(shù)進(jìn)行流量控制的場(chǎng)景。
5.2 演示熱點(diǎn)規(guī)則
(1)在訂單微服務(wù)的io.binghe.shop.order.controller.SentinelController類中新增requestSentinel3()方法,其中會(huì)帶有一個(gè)String類型的參數(shù)header和一個(gè)String類型的參數(shù)body,如下所示。
@GetMapping(value = "/request_sentinel3")
@SentinelResource("request_sentinel3")
public String requestSentinel3(String header, String body){
log.info("測(cè)試Sentinel3");
return "sentinel3";
}
(2)在瀏覽器中訪問(wèn)http://localhost:8080/order/request_sentinel3接口,在Sentinel的簇點(diǎn)鏈路中會(huì)顯示/request_sentinel3接口,如下所示。
圖片
(3)點(diǎn)擊熱點(diǎn)按鈕,如下所示。
圖片
(4)在彈出的熱點(diǎn)規(guī)則配置框中的參數(shù)索引中輸入0,單機(jī)閾值輸入1,統(tǒng)計(jì)窗口時(shí)長(zhǎng)輸入1,如下所示。
圖片
表示對(duì)requestSentinel3()方法的第一個(gè)參數(shù)header進(jìn)行限流,如果每秒鐘訪問(wèn)的次數(shù)超過(guò)1次,則觸發(fā)限流。
(5)保存配置后,在瀏覽器中不斷訪問(wèn)http://localhost:8080/order/request_sentinel3?header=header,當(dāng)每秒訪問(wèn)的頻率超過(guò)1次時(shí),會(huì)觸發(fā)Sentinel的限流操作,如下所示。
圖片
不斷訪問(wèn)http://localhost:8080/order/request_sentinel3?body=body,則不會(huì)觸發(fā)限流操作。
圖片
5.3 演示熱點(diǎn)高級(jí)選項(xiàng)規(guī)則
(1)在彈出的熱點(diǎn)規(guī)則配置框中打開(kāi)高級(jí)選項(xiàng),在參數(shù)類型中選擇java.lang.String,因?yàn)樵趨?shù)索引中輸入0,表示的是對(duì)header參數(shù)限流,而header參數(shù)是String類型的。在參數(shù)值里輸入header,也就是為參數(shù)名為header的參數(shù)賦值為字符串header。限流閾值為1,如下所示。
圖片
(2)點(diǎn)擊添加按鈕后如下所示。
圖片
(3)點(diǎn)擊保存按鈕,在瀏覽器不斷刷新http://localhost:8080/order/request_sentinel3?header=header,會(huì)觸發(fā)Sentinel的限流操作。
圖片
六、授權(quán)規(guī)則
授權(quán)規(guī)則能夠根據(jù)調(diào)用來(lái)源判斷還否允許執(zhí)行本次請(qǐng)求。
6.1 授權(quán)規(guī)則概述
在某些場(chǎng)景下,需要根據(jù)調(diào)用接口的來(lái)源判斷是否允許執(zhí)行本次請(qǐng)求。此時(shí)就可以使用Sentinel提供的授權(quán)規(guī)則來(lái)實(shí)現(xiàn),Sentinel的授權(quán)規(guī)則能夠根據(jù)請(qǐng)求的來(lái)源判斷是否允許本次請(qǐng)求通過(guò)。
在Sentinel的授權(quán)規(guī)則中,提供了 白名單與黑名單 兩種授權(quán)類型。
6.2 演示授權(quán)規(guī)則
(1)在訂單微服務(wù)shop-order中新建io.binghe.shop.order.parser包,并創(chuàng)建MyRequestOriginParser類,實(shí)現(xiàn)com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser接口,用來(lái)處理請(qǐng)求的來(lái)源。代碼如下所示。
/**
* @author binghe
* @version 1.0.0
* @description Sentinel授權(quán)規(guī)則,用來(lái)處理請(qǐng)求的來(lái)源
*/
@Component
public class MyRequestOriginParser implements RequestOriginParser {
@Override
public String parseOrigin(HttpServletRequest httpServletRequest) {
return httpServletRequest.getParameter("serverName");
}
}
(2)首先在瀏覽器中訪問(wèn)http://localhost:8080/order/request_sentinel4,在Sentinel的簇點(diǎn)鏈路里找到/request_sentinel4。
圖片
(3)點(diǎn)擊授權(quán)按鈕,進(jìn)入授權(quán)規(guī)則配置框,按照如下方式進(jìn)行配置。
圖片
其中,流控應(yīng)用填寫的是test,授權(quán)類型為黑名單。這里要結(jié)合新建的MyRequestOriginParser類進(jìn)行理解,MyRequestOriginParser類的parseOrigin()方法如下所示。
public String parseOrigin(HttpServletRequest httpServletRequest) {
return httpServletRequest.getParameter("serverName");
}
parseOrigin()方法中直接返回了從HttpServletRequest中獲取的serverName參數(shù),而在上圖中的流控應(yīng)用中輸出的是test,授權(quán)類型為黑名單。
所以,如果我們?cè)L問(wèn)http://localhost:8080/order/request_sentinel4?serverName=test的話,是處于黑名單的狀態(tài),無(wú)法訪問(wèn)。
(4)點(diǎn)擊新增按鈕后,不斷在瀏覽器中刷新http://localhost:8080/order/request_sentinel4?serverName=test,會(huì)發(fā)現(xiàn)無(wú)法訪問(wèn),被Sentinel限流了。
七、系統(tǒng)規(guī)則
系統(tǒng)保護(hù)規(guī)則是從應(yīng)用級(jí)別的入口流量進(jìn)行控制,從單臺(tái)機(jī)器的總體 Load、 RT、入口 QPS 、 CPU使用率和線程數(shù)五個(gè)維度監(jiān)控應(yīng)用數(shù)據(jù),讓系統(tǒng)盡可能跑在最大吞吐量的同時(shí)保證系統(tǒng)整體的穩(wěn)定性。
7.1 系統(tǒng)規(guī)則概述
系統(tǒng)保護(hù)規(guī)則是應(yīng)用整體維度的,而不是資源維度的,并且僅對(duì)入口流量 (進(jìn)入應(yīng)用的流量) 生效。
- Load(僅對(duì) Linux/Unix-like 機(jī)器生效):當(dāng)系統(tǒng) load1 超過(guò)閾值,且系統(tǒng)當(dāng)前的并發(fā)線程數(shù)超過(guò)系統(tǒng)容量時(shí)才會(huì)觸發(fā)系統(tǒng)保護(hù)。系統(tǒng)容量由系統(tǒng)的 maxQps * minRt 計(jì)算得出。設(shè)定參考值一般是 CPU cores * 2.5。
- RT:當(dāng)單臺(tái)機(jī)器上所有入口流量的平均 RT 達(dá)到閾值即觸發(fā)系統(tǒng)保護(hù),單位是毫秒。
- 線程數(shù):當(dāng)單臺(tái)機(jī)器上所有入口流量的并發(fā)線程數(shù)達(dá)到閾值即觸發(fā)系統(tǒng)保護(hù)。
- 入口 QPS:當(dāng)單臺(tái)機(jī)器上所有入口流量的 QPS 達(dá)到閾值即觸發(fā)系統(tǒng)保護(hù)。
- CPU使用率:當(dāng)單臺(tái)機(jī)器上所有入口流量的 CPU使用率達(dá)到閾值即觸發(fā)系統(tǒng)保護(hù)
7.2 演示系統(tǒng)規(guī)則
(1)在訂單微服務(wù)中新建io.binghe.shop.order.handler包,并創(chuàng)建MyUrlBlockHandler類,實(shí)現(xiàn)com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler接口,用來(lái)捕獲系統(tǒng)級(jí)Sentinel異常,代碼如下所示。
/**
* @author binghe
* @version 1.0.0
* @description 處理Sentinel系統(tǒng)規(guī)則,返回自定義異常
*/
@Component
public class MyUrlBlockHandler implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
String msg = null;
if (e instanceof FlowException) {
msg = "限流了";
} else if (e instanceof DegradeException) {
msg = "降級(jí)了";
} else if (e instanceof ParamFlowException) {
msg = "熱點(diǎn)參數(shù)限流";
} else if (e instanceof SystemBlockException) {
msg = "系統(tǒng)規(guī)則(負(fù)載/...不滿足要求)";
} else if (e instanceof AuthorityException) {
msg = "授權(quán)規(guī)則不通過(guò)";
}
// http狀態(tài)碼
response.setStatus(500);
response.setCharacterEncoding("utf-8");
response.setHeader("Content-Type", "application/json;charset=utf-8");
response.setContentType("application/json;charset=utf-8");
JSONObject jsonObject = new JSONObject();
jsonObject.put("code", 500);
jsonObject.put("codeMsg", msg);
response.getWriter().write(jsonObject.toJSONString());
}
}
(2)在訂單微服務(wù)的io.binghe.shop.order.controller.SentinelController類中新增requestSentinel5()方法,如下所示。
@GetMapping(value = "/request_sentinel5")
@SentinelResource("request_sentinel5")
public String requestSentinel5(){
log.info("測(cè)試Sentinel5");
return "sentinel5";
}
(3)首先在瀏覽器中訪問(wèn)http://localhost:8080/order/request_sentinel5,在Sentinel的簇點(diǎn)鏈路里找到/request_sentinel5。
圖片
(4)點(diǎn)擊流控按鈕,進(jìn)入流控規(guī)則配置框,按照如下方式進(jìn)行配置。
圖片
(5)在瀏覽器中不斷刷新http://localhost:8080/order/request_sentinel5,會(huì)顯示如下信息。
圖片
返回的原始數(shù)據(jù)如下所示。
{"code":500,"codeMsg":"限流了"}
說(shuō)明觸發(fā)了系統(tǒng)規(guī)則,捕獲到了Sentinel全局異常。
八、@SentinelResource注解
使用Sentinel時(shí),可以使用@SentinelResource注解來(lái)指定異常處理策略。
8.1 @SentinelResource注解概述
在Sentinel中,指定發(fā)生異常時(shí)的處理策略非常簡(jiǎn)單,只需要使用@SentinelResource注解即可,@SentinelResource注解的源碼如下所示。
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface SentinelResource {
//資源名稱
String value() default "";
//entry類型,標(biāo)記流量的方向,取值IN/OUT,默認(rèn)是OUT
EntryType entryType() default EntryType.OUT;
int resourceType() default 0;
//處理BlockException的函數(shù)名稱,函數(shù)要求:
//1. 必須是 public
//2.返回類型 參數(shù)與原方法一致
//3. 默認(rèn)需和原方法在同一個(gè)類中。若希望使用其他類的函數(shù),可配置
//blockHandlerClass ,并指定blockHandlerClass里面的方法。
String blockHandler() default "";
//存放blockHandler的類,對(duì)應(yīng)的處理函數(shù)必須static修飾。
Class<?>[] blockHandlerClass() default {};
//用于在拋出異常的時(shí)候提供fallback處理邏輯。fallback函數(shù)可以針對(duì)所
//有類型的異常(除了 exceptionsToIgnore 里面排除掉的異常類型)進(jìn)行處理。函數(shù)要求:
//1. 返回類型與原方法一致
//2. 參數(shù)類型需要和原方法相匹配
//3. 默認(rèn)需和原方法在同一個(gè)類中。若希望使用其他類的函數(shù),可配置fallbackClass ,并指定fallbackClass里面的方法。
String fallback() default "";
//存放fallback的類。對(duì)應(yīng)的處理函數(shù)必須static修飾。
String defaultFallback() default "";
//用于通用的 fallback 邏輯。默認(rèn)fallback函數(shù)可以針對(duì)所有類型的異常進(jìn)
//行處理。若同時(shí)配置了 fallback 和 defaultFallback,以fallback為準(zhǔn)。函數(shù)要求:
//1. 返回類型與原方法一致
//2. 方法參數(shù)列表為空,或者有一個(gè) Throwable 類型的參數(shù)。
//3. 默認(rèn)需要和原方法在同一個(gè)類中。若希望使用其他類的函數(shù),可配置fallbackClass ,并指定 fallbackClass 里面的方法。
Class<?>[] fallbackClass() default {};
//指定排除掉哪些異常。排除的異常不會(huì)計(jì)入異常統(tǒng)計(jì),也不會(huì)進(jìn)入fallback邏輯,而是原樣拋出。
Class<? extends Throwable>[] exceptionsToTrace() default {Throwable.class};
//需要trace的異常
Class<? extends Throwable>[] exceptionsToIgnore() default {};
}
8.2 演示@SentinelResource注解
1.定義限流和降級(jí)后的處理方法
(1)在訂單微服務(wù)的io.binghe.shop.order.service.SentinelService接口中新增sendMessage2()方法,如下所示。
String sendMessage2();
(2)在訂單微服務(wù)的io.binghe.shop.order.service.impl.SentinelServiceImpl方法中,實(shí)現(xiàn)sendMessage2()方法,并且定義一個(gè)成員變量count,用來(lái)記錄請(qǐng)求sendMessage2()方法的次數(shù),同時(shí)定義25%的異常率。在sendMessage2()方法上使用@SentinelResource指定了資源的名稱、發(fā)生BlockException時(shí)進(jìn)入的方法和發(fā)生異常時(shí)進(jìn)入的方法,代碼如下所示。
private int count = 0;
@Override
@SentinelResource(
value = "sendMessage2",
blockHandler = "blockHandler",
fallback = "fallback")
public String sendMessage2() {
count ++;
//25%的異常率
if (count % 4 == 0){
throw new RuntimeException("25%的異常率");
}
return "sendMessage2";
}
public String blockHandler(BlockException e){
log.error("限流了:{}", e);
return "限流了";
}
public String fallback(Throwable e){
log.error("異常了:{}", e);
return "異常了";
}
(3)在訂單微服務(wù)的io.binghe.shop.order.controller.SentinelController類中新增requestSentinel6()方法,在方法中調(diào)用io.binghe.shop.order.service.SentinelService接口中的sendMessage2()方法,如下所示。
@GetMapping(value = "/request_sentinel6")
public String requestSentinel6(){
log.info("測(cè)試Sentinel6");
return sentinelService.sendMessage2();
}
(4)首先在瀏覽器中訪問(wèn)http://localhost:8080/order/request_sentinel6,在Sentinel的簇點(diǎn)鏈路里找到/request_sentinel6。
(5)點(diǎn)擊流控按鈕進(jìn)入流控規(guī)則頁(yè)面,按照下圖方式進(jìn)行配置。
圖片
(6)點(diǎn)擊新增按鈕后在瀏覽器中刷新http://localhost:8080/order/request_sentinel6,當(dāng)刷新的頻率超過(guò)每秒2次時(shí),瀏覽器會(huì)顯示如下信息。
圖片
當(dāng)刷新的次數(shù)是4的倍數(shù)時(shí),瀏覽器會(huì)顯示如下信息。
圖片
2.在外部類中指定限流和異常調(diào)用的方法
(1)在訂單微服務(wù)的io.binghe.shop.order.handler包下新建MyBlockHandlerClass類,用于定義被Sentinel限流時(shí)的方法,源碼如下所示。
/**
* @author binghe
* @version 1.0.0
* @description 定義被Sentinel限流時(shí)調(diào)用的方法
*/
@Slf4j
public class MyBlockHandlerClass {
public static String blockHandler(BlockException e){
log.error("限流了:{}", e);
return "限流了";
}
}
(2)在訂單微服務(wù)的io.binghe.shop.order.handler包下新建MyFallbackClass類,用于定義拋出異常時(shí)調(diào)用的方法,源碼如下所示。
/**
* @author binghe
* @version 1.0.0
* @description 定義異常時(shí)調(diào)用的方法
*/
@Slf4j
public class MyFallbackClass {
public static String fallback(Throwable e){
log.error("異常了:{}", e);
return "異常了";
}
}
(3)修改io.binghe.shop.order.service.impl.SentinelServiceImpl#sendMessage2()方法上的注解,修改后的代碼如下所示。
@Override
@SentinelResource(
value = "sendMessage2",
blockHandlerClass = MyBlockHandlerClass.class,
blockHandler = "blockHandler",
fallbackClass = MyFallbackClass.class,
fallback = "fallback")
public String sendMessage2() {
count ++;
System.out.println(count);
//25%的異常率
if (count % 4 == 0){
throw new RuntimeException("25%的異常率");
}
return "sendMessage2";
}
(4)首先在瀏覽器中訪問(wèn)http://localhost:8080/order/request_sentinel6,在Sentinel的簇點(diǎn)鏈路里找到/request_sentinel6。
圖片
(5)點(diǎn)擊流控按鈕進(jìn)入流控規(guī)則頁(yè)面,按照下圖方式進(jìn)行配置。
圖片
(6)點(diǎn)擊新增按鈕后在瀏覽器中刷新http://localhost:8080/order/request_sentinel6,當(dāng)刷新的頻率超過(guò)每秒2次時(shí),瀏覽器會(huì)顯示如下信息。
圖片
當(dāng)刷新的次數(shù)是4的倍數(shù)時(shí),瀏覽器會(huì)顯示如下信息。
圖片
九、Sentinel持久化
Sentinel中可以自定義配置的持久化來(lái)將Sentinel的配置規(guī)則持久化到服務(wù)器磁盤,使得重啟應(yīng)用或者Sentinel后,Sentinel的配置規(guī)則不丟失。
9.1 Sentinel持久化概述
細(xì)心的小伙伴會(huì)發(fā)現(xiàn),我們之前配置的Sentinel規(guī)則在程序重啟或者Sentinel重啟后就會(huì)消失不見(jiàn),此時(shí)就需要我們重新配置。如果這發(fā)生在高并發(fā)、大流量的場(chǎng)景下是不可接受的。那有沒(méi)有什么辦法讓程序或Sentinel重啟后配置不丟失呢?其實(shí),Sentinel中可以自定義配置的持久化來(lái)解決這個(gè)問(wèn)題。
9.2 實(shí)現(xiàn)Sentinel的持久化
(1)在訂單微服務(wù)shop-order中新建io.binghe.shop.order.persistence包,并創(chuàng)建SentinelPersistenceRule類,實(shí)現(xiàn)com.alibaba.csp.sentinel.init.InitFunc接口,并在SentinelPersistenceRule類中獲取應(yīng)用的名稱,覆寫init()方法,源碼如下所示。
/**
* @author binghe
* @version 1.0.0
* @description Sentinel規(guī)則持久化
*/
public class SentinelPersistenceRule implements InitFunc {
//實(shí)際可以從外部配置讀取
private String appcationName = "server-order";
@Override
public void init() throws Exception {
String ruleDir = System.getProperty("user.home") + "/sentinel-rules/" + appcationName;
String flowRulePath = ruleDir + "/flow-rule.json";
String degradeRulePath = ruleDir + "/degrade-rule.json";
String systemRulePath = ruleDir + "/system-rule.json";
String authorityRulePath = ruleDir + "/authority-rule.json";
String paramFlowRulePath = ruleDir + "/param-flow-rule.json";
this.mkdirIfNotExits(ruleDir);
this.createFileIfNotExits(flowRulePath);
this.createFileIfNotExits(degradeRulePath);
this.createFileIfNotExits(systemRulePath);
this.createFileIfNotExits(authorityRulePath);
this.createFileIfNotExits(paramFlowRulePath);
// 流控規(guī)則
ReadableDataSource<String, List<FlowRule>> flowRuleRDS = new FileRefreshableDataSource<>(
flowRulePath,
flowRuleListParser
);
FlowRuleManager.register2Property(flowRuleRDS.getProperty());
WritableDataSource<List<FlowRule>> flowRuleWDS = new FileWritableDataSource<>(
flowRulePath,
this::encodeJson
);
WritableDataSourceRegistry.registerFlowDataSource(flowRuleWDS);
// 降級(jí)規(guī)則
ReadableDataSource<String, List<DegradeRule>> degradeRuleRDS = new FileRefreshableDataSource<>(
degradeRulePath,
degradeRuleListParser
);
DegradeRuleManager.register2Property(degradeRuleRDS.getProperty());
WritableDataSource<List<DegradeRule>> degradeRuleWDS = new FileWritableDataSource<>(
degradeRulePath,
this::encodeJson
);
WritableDataSourceRegistry.registerDegradeDataSource(degradeRuleWDS);
// 系統(tǒng)規(guī)則
ReadableDataSource<String, List<SystemRule>> systemRuleRDS = new FileRefreshableDataSource<>(
systemRulePath,
systemRuleListParser
);
SystemRuleManager.register2Property(systemRuleRDS.getProperty());
WritableDataSource<List<SystemRule>> systemRuleWDS = new FileWritableDataSource<>(
systemRulePath,
this::encodeJson
);
WritableDataSourceRegistry.registerSystemDataSource(systemRuleWDS);
// 授權(quán)規(guī)則
ReadableDataSource<String, List<AuthorityRule>> authorityRuleRDS = new FileRefreshableDataSource<>(
authorityRulePath,
authorityRuleListParser
);
AuthorityRuleManager.register2Property(authorityRuleRDS.getProperty());
WritableDataSource<List<AuthorityRule>> authorityRuleWDS = new FileWritableDataSource<>(
authorityRulePath,
this::encodeJson
);
WritableDataSourceRegistry.registerAuthorityDataSource(authorityRuleWDS);
// 熱點(diǎn)參數(shù)規(guī)則
ReadableDataSource<String, List<ParamFlowRule>> paramFlowRuleRDS = new FileRefreshableDataSource<>(
paramFlowRulePath,
paramFlowRuleListParser
);
ParamFlowRuleManager.register2Property(paramFlowRuleRDS.getProperty());
WritableDataSource<List<ParamFlowRule>> paramFlowRuleWDS = new FileWritableDataSource<>(
paramFlowRulePath,
this::encodeJson
);
ModifyParamFlowRulesCommandHandler.setWritableDataSource(paramFlowRuleWDS);
}
private Converter<String, List<FlowRule>> flowRuleListParser = source -> JSON.parseObject(
source,
new TypeReference<List<FlowRule>>() {
}
);
private Converter<String, List<DegradeRule>> degradeRuleListParser = source -> JSON.parseObject(
source,
new TypeReference<List<DegradeRule>>() {
}
);
private Converter<String, List<SystemRule>> systemRuleListParser = source -> JSON.parseObject(
source,
new TypeReference<List<SystemRule>>() {
}
);
private Converter<String, List<AuthorityRule>> authorityRuleListParser = source -> JSON.parseObject(
source,
new TypeReference<List<AuthorityRule>>() {
}
);
private Converter<String, List<ParamFlowRule>> paramFlowRuleListParser = source -> JSON.parseObject(
source,
new TypeReference<List<ParamFlowRule>>() {
}
);
private void mkdirIfNotExits(String filePath) throws IOException {
File file = new File(filePath);
if (!file.exists()) {
file.mkdirs();
}
}
private void createFileIfNotExits(String filePath) throws IOException {
File file = new File(filePath);
if (!file.exists()) {
file.createNewFile();
}
}
private <T> String encodeJson(T t) {
return JSON.toJSONString(t);
}
}
(2)在訂單微服務(wù)的resources目錄下新建META-INF目錄,并在META-INF目錄下新建services目錄,在services目錄下新建名稱為com.alibaba.csp.sentinel.init.InitFunc的文件,如下所示。
(3)在com.alibaba.csp.sentinel.init.InitFunc文件中添加io.binghe.shop.order.persistence.SentinelPersistenceRule類的全類名,如下所示。
io.binghe.shop.order.persistence.SentinelPersistenceRule
(4)首先在瀏覽器中訪問(wèn)http://localhost:8080/order/request_sentinel6,在Sentinel的簇點(diǎn)鏈路里找到/request_sentinel6。
圖片
(5)點(diǎn)擊流控按鈕進(jìn)入流控規(guī)則頁(yè)面,按照下圖方式進(jìn)行配置。
圖片
(6)點(diǎn)擊新增按鈕,此時(shí)打開(kāi)電腦的user.home目錄,我電腦的目錄為C:\Users\binghe,可以發(fā)現(xiàn)C:\Users\binghe目錄中多了一個(gè)sentinel-rules目錄。
圖片
(7)打開(kāi)sentinel-rules目錄,發(fā)現(xiàn)里面存在一個(gè)server-order目錄,如下所示。
(8)打開(kāi)server-order目錄后,會(huì)發(fā)現(xiàn)生成了Sentinel的配置文件,并持久化到了磁盤上,如下所示。
(9)打開(kāi)flow-rule.json文件,內(nèi)容如下所示。
[
{
"clusterConfig": {
"acquireRefuseStrategy": 0,
"clientOfflineTime": 2000,
"fallbackToLocalWhenFail": true,
"resourceTimeout": 2000,
"resourceTimeoutStrategy": 0,
"sampleCount": 10,
"strategy": 0,
"thresholdType": 0,
"windowIntervalMs": 1000
},
"clusterMode": false,
"controlBehavior": 0,
"count": 2,
"grade": 1,
"limitApp": "default",
"maxQueueingTimeMs": 500,
"resource": "/request_sentinel6",
"strategy": 0,
"warmUpPeriodSec": 10
}
]
可以看到,flow-rule.json文件中持久化了對(duì)于/request_sentinel6接口的配置。
至此,我們完成了Sentinel規(guī)則的持久化。
十、寫在最后
在冰河的知識(shí)星球除了目前正在熱更的高性能網(wǎng)關(guān)外,還有其他7個(gè)項(xiàng)目,像高性能熔斷組件、分布式IM即時(shí)通訊系統(tǒng)、Sekill分布式秒殺系統(tǒng)、手寫RPC、簡(jiǎn)易商城系統(tǒng)等等,這些項(xiàng)目的需求、方案、架構(gòu)、落地等均來(lái)自互聯(lián)網(wǎng)真實(shí)業(yè)務(wù)場(chǎng)景,讓你真正學(xué)到互聯(lián)網(wǎng)大廠的業(yè)務(wù)與技術(shù)落地方案,并將其有效轉(zhuǎn)化為自己的知識(shí)儲(chǔ)備。