寫(xiě)在前面
源碼 。
本文一起看下spring cloud的sentinel組件的使用。
1:準(zhǔn)備
1.1:理論
對(duì)于一個(gè)系統(tǒng)來(lái)說(shuō),最重要的就是高可用
,那么如何實(shí)現(xiàn)高可用呢?你可能會(huì)說(shuō),集群部署不就可以了,但事實(shí)并非這么簡(jiǎn)單,假定一個(gè)不那么靠譜的開(kāi)發(fā),寫(xiě)了一個(gè)不那么靠譜的sql語(yǔ)句,上線之后,DB資源很快耗盡,那么所有需要查詢DB的服務(wù)都無(wú)法處理請(qǐng)求,進(jìn)而導(dǎo)致這些服務(wù)掛掉,而上游的服務(wù)因?yàn)闊o(wú)法快速得到響應(yīng),也會(huì)很快的掛掉,進(jìn)而整個(gè)系統(tǒng)都會(huì)無(wú)法對(duì)外提供服務(wù),這其實(shí)就是發(fā)生了非??膳碌?code>服務(wù)雪崩,這個(gè)過(guò)程可以簡(jiǎn)單參考下圖:
假定那個(gè)不那么靠譜的開(kāi)發(fā),是在服務(wù)D開(kāi)發(fā)了那個(gè)不那么靠譜的sql語(yǔ)句,則上線后的后果就是D導(dǎo)致B和C不可用,B和C的不可用又會(huì)導(dǎo)致A的不可用,最終整個(gè)系統(tǒng)不可用。所以想要真正的實(shí)現(xiàn)高可用,除了多節(jié)點(diǎn)的集群部署外,我們還需要預(yù)防服務(wù)雪崩,而本文要學(xué)習(xí)的sentinel正是這樣的一個(gè)組件,我們可以叫做容錯(cuò)組件
。
基本上,導(dǎo)致服務(wù)雪崩發(fā)生的原因在2個(gè)方面,第一個(gè)方面是高并發(fā)導(dǎo)致的請(qǐng)求量增大,第二方面就是應(yīng)用內(nèi)部的異常和錯(cuò)誤(就像那個(gè)不那么靠譜的sql)
。其實(shí)sentinel也正是從這兩方面來(lái)預(yù)防服務(wù)雪崩的,對(duì)于高并發(fā),sentinel可以從外部來(lái)限制并發(fā)量,對(duì)于應(yīng)用內(nèi)部異常和錯(cuò)誤,sentinel可以進(jìn)行降級(jí)和熔斷。我們也會(huì)從這方面來(lái)展開(kāi)sentinel的實(shí)戰(zhàn)環(huán)節(jié)。
在java中,萬(wàn)物皆對(duì)象,在Linux中,萬(wàn)物皆文件,而在sentinel中,萬(wàn)物皆資源,而這里的資源我們可以認(rèn)為就是接口地址,而對(duì)這些資源作用的過(guò)程是通過(guò)其內(nèi)部的一個(gè)責(zé)任鏈,可以參考下圖:
比如有用來(lái)收集數(shù)據(jù)的StaticSlot,構(gòu)建調(diào)用鏈的NodeSelectorSlot(形成一個(gè)樹(shù)狀的調(diào)用關(guān)系圖)
,如果業(yè)務(wù)有需要,我們也可以增加自定義的額slot到這個(gè)調(diào)用鏈上。
1.2:安裝sentinel
首先在這里 下載可運(yùn)行的jar包,接著如下操作:
$ java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.2.jar
INFO: Sentinel log output type is: file
INFO: Sentinel log charset is: utf-8
INFO: Sentinel log base directory is: C:\Users\dell9020\logs\csp\
INFO: Sentinel log name use pid is: false
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.0.5.RELEASE)
...
成功后訪問(wèn)http://localhost:8080/#/login
,進(jìn)入登陸頁(yè)面:
賬號(hào)sentinel/sentinel,成功后進(jìn)入如下頁(yè)面:
2:限流實(shí)戰(zhàn)
限流,或者叫流量整形,是sentinel從外部預(yù)防服務(wù)雪崩的重要手段。
2.1:基礎(chǔ)配置
這部分我們直接來(lái)使用sentiniel對(duì)我們項(xiàng)目做流量整形,首先,在custom和template模塊引入依賴:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
然后在custom和template模塊的application.yml中配置sentinel信息:
spring:
jpa:
...
cloud:
sentinel:
eager: true
transport:
port: 8719
dashboard: localhost:8080
接著使用@SentinelResource
注解標(biāo)記要進(jìn)行流量整形的接口,如下:
- custom模塊
dongshi.daddy.sentinel.controller.CouponCustomerController#requestCoupon
@PostMapping("requestCoupon")
@SentinelResource(value = "requestCoupon")
public Coupon requestCoupon(@Valid @RequestBody RequestCoupon request) {
...
}
@PostMapping("findCoupon")
@SentinelResource(value = "customer-findCoupon")
public List<CouponInfo> findCoupon(@Valid @RequestBody SearchCoupon request) {
return customerService.findCoupon(request);
}
- template模塊
// 讀取優(yōu)惠券
@GetMapping("/getTemplate")
@SentinelResource(value = "getTemplate")
public CouponTemplateInfo getTemplate(@RequestParam("id") Long id){
log.info("Load template, id={}", id);
/*try {
// 休眠二十秒模擬超時(shí)
TimeUnit.SECONDS.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
return couponTemplateService.loadTemplateInfo(id);
}
// 批量獲取
@GetMapping("/getBatch")
// 降級(jí)要執(zhí)行的方法
@SentinelResource(value = "getTemplateInBatch", blockHandler = "getTemplateInBatch_block")
public Map<Long, CouponTemplateInfo> getTemplateInBatch(@RequestParam("ids") Collection<Long> ids) {
log.info("getTemplateInBatch: {}", JSON.toJSONString(ids));
return couponTemplateService.getTemplateInfoMap(ids);
}
// 降級(jí)要執(zhí)行的方法
public Map<Long, CouponTemplateInfo> getTemplateInBatch_block(
Collection<Long> ids, BlockException exception) {
log.info("接口被限流");
return Maps.newHashMap();
}
接著我們啟動(dòng)服務(wù),如果一切正常的話,就可以看到custom和template這兩個(gè)模塊注冊(cè)到sentinel上了:
2.2:通過(guò)流控規(guī)則進(jìn)行限流
sentinel支持通過(guò)三種方式來(lái)進(jìn)行流量整形:
直接流控:
針對(duì)特定接口(sentinel稱為資源)限流
關(guān)聯(lián)流控:
當(dāng)某接口(sentinel稱為資源)達(dá)到限流條件時(shí)對(duì)關(guān)聯(lián)的接口(sentinel稱為資源)進(jìn)行限流
鏈路流控:
2.2.1:直接流控
我們對(duì)資源getTemplateInBatch
進(jìn)行流控,在sentinel選擇template對(duì)應(yīng)的微服務(wù),點(diǎn)擊子菜單流控規(guī)則
,然后點(diǎn)擊右上角 添加流控規(guī)則,如下:
為了測(cè)試我們使用jmeter來(lái)進(jìn)行測(cè)試,jmeter配置可以從這里 下載,配置如下:
通過(guò)按鈕 啟動(dòng),之后查看template模塊日志輸出如下:
2024-01-08 15:34:44.411 INFO 26504 --- [io-20006-exec-7] d.d.s.c.CouponTemplateController : getTemplateInBatch: [2,3]
Hibernate: select coupontemp0_.id as id1_0_, coupontemp0_.available as availabl2_0_, coupontemp0_.type as type3_0_, coupontemp0_.created_time as created_4_0_, coupontemp0_.description as descript5_0_, coupontemp0_.name as name6_0_, coupontemp0_.rule as rule7_0_, coupontemp0_.shop_id as shop_id8_0_ from coupon_template coupontemp0_ where coupontemp0_.id in (? , ?)
2024-01-08 15:34:44.904 INFO 26504 --- [io-20006-exec-5] d.d.s.c.CouponTemplateController : 接口被限流
可以看到第二次執(zhí)行了熔斷邏輯被限流了。
2.2.2:關(guān)聯(lián)流控
jmeter配置下載 。
我們來(lái)設(shè)置當(dāng)getTempalte接口qps超過(guò)1時(shí)限制getBatch接口,配置如下:
首先我們使用jmeter模擬qps 2不間斷訪問(wèn)getTemplate接口,如下:
啟動(dòng)后,這樣,肯定就會(huì)觸發(fā)限流規(guī)則了,這樣,我們先來(lái)啟動(dòng)jmeter,
之后訪問(wèn)getBatch接口,如下:
http://localhost:20006/template/getBatch?ids=2,3
查看后臺(tái)輸出:
然后我們來(lái)停止jmeter,再來(lái)請(qǐng)求接口,就會(huì)恢復(fù)正常了:
2.2.3:鏈路流控
jmeter配置下載 。
對(duì)于同一個(gè)資源訪問(wèn)可以來(lái)自于不同的鏈路,針對(duì)特定的鏈路進(jìn)行流控,就是鏈路流控了,如下圖,上方鏈路就是被流控的:
這里我們模擬從custom模塊訪問(wèn)template模塊的getBatch接口,為了測(cè)試,需要首先在custom模塊中增加如下接口:
// 批量獲取
@GetMapping("/getBatchFromCustomer")
public Map<Long, CouponTemplateInfo> getTemplateInBatch(@RequestParam("ids") Collection<Long> ids) {
log.info("getTemplateInBatch111: {}", JSON.toJSONString(ids));
return templateService.getTemplateInBatch(ids);
}
因?yàn)樾枰M(jìn)行限流的資源需要知道當(dāng)前的調(diào)用者是誰(shuí),才能知道是否需要觸發(fā),所以,首先需要在custom模塊添加openfeign的攔截器,在openfeign的請(qǐng)求中添加來(lái)源頭
:
@Configuration
public class OpenfeignSentinelInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
template.header("SentinelSource", "coupon-customer-serv");
}
}
接著在tempalte模塊中獲取這個(gè)來(lái)源的信息:
@Component
@Slf4j
public class SentinelOriginParser implements RequestOriginParser {
@Override
public String parseOrigin(HttpServletRequest request) {
log.info("request {}, header={}", request.getParameterMap(), request.getHeaderNames());
return request.getHeader("SentinelSource");
}
}
接著我們來(lái)配置sentinel針對(duì)來(lái)源coupon-customer-serv
進(jìn)行限流,如下:
接著開(kāi)啟jmeter,正常的話會(huì)看到被限流了:
為了測(cè)試來(lái)源不匹配的情況不限流,我們將sentinel的目標(biāo)資源名稱隨便改為其他的,如下:
此時(shí)再開(kāi)啟jmeter測(cè)試就正常了:
2.3:三種流控效果
sentinel支持三種流控效果,快速失敗,warm up,排隊(duì)等待,在上述的實(shí)戰(zhàn)環(huán)節(jié)我們使用就是快速失敗,這也是sentinel默認(rèn)的流控效果。
- 快速失敗
直接失敗。 - warm up
這是一種從低水位逐漸拉高的一種限流方式,比如如下的配置:
上圖設(shè)置在5秒內(nèi)拉高到10進(jìn)行限流,而非開(kāi)始就是10,開(kāi)始的限流值需要除以冷卻因子,這里冷卻因子是3,所以就是10/3=3,也就是當(dāng)qps到達(dá)3時(shí)開(kāi)始限流,然后在5秒內(nèi)將這個(gè)限流值拉高到最大值10,
使用的場(chǎng)景,如采用緩存+DB讀的場(chǎng)景,如果接口一段時(shí)間內(nèi)都處于很低的水位,導(dǎo)致大量的緩存都失效了,此時(shí)突然發(fā)生了突發(fā)流量(某明星出軌了,某漂亮country被原子彈攻擊了)
,此時(shí)緩存還沒(méi)有完全構(gòu)建起來(lái),為了避免突發(fā)流量全部打到DB,把DB打穿,就可以考慮使用warm up,在一段時(shí)間內(nèi)從低水位逐漸拉到高水位,同時(shí)在這段時(shí)間內(nèi)完成緩存的構(gòu)建工作。
- 排隊(duì)等待
被限流的請(qǐng)求在一個(gè)任務(wù)隊(duì)列中排隊(duì)等待,并按照超時(shí)等待時(shí)間從隊(duì)列中刪除任務(wù),如下500內(nèi)還沒(méi)有處理則移除任務(wù)的配置:
2:降級(jí)、熔斷實(shí)戰(zhàn)
jmeter配置 。
降級(jí)、熔斷,是sentinel從服務(wù)內(nèi)部預(yù)防服務(wù)雪崩的重要手段。這部分我們一起來(lái)看下如何使用sentinel來(lái)實(shí)現(xiàn)降級(jí)和熔斷。為了方便測(cè)試我們首先在template模塊添加一個(gè)新的接口并設(shè)置為sentinel的資源:
// 批量獲取
@GetMapping("/getBatchV1")
// 降級(jí)要執(zhí)行的方法
@SentinelResource(value = "getTemplateInBatchV1", fallback = "getTemplateInBatch_fallback")
public Map<Long, CouponTemplateInfo> getTemplateInBatchV1(@RequestParam("ids") Collection<Long> ids) {
log.info("getTemplateInBatch: {}", JSON.toJSONString(ids));
int i = 1 / 0;
return couponTemplateService.getTemplateInfoMap(ids);
}
public Map<Long, CouponTemplateInfo> getTemplateInBatch_fallback(Collection<Long> ids) {
log.info("接口被fallback");
return Maps.newHashMap();
}
這里注意指定了fallback = "getTemplateInBatch_fallback"
,則當(dāng)拋出了RuntimeException時(shí)就會(huì)進(jìn)入執(zhí)行降級(jí)邏輯,接著我們就需要在sentinel中配置熔斷邏輯了,如下:
降級(jí)熔斷的判斷過(guò)程可以參考下圖(注意時(shí)示例)
:
接著執(zhí)行jemter,日志輸出如下:
2024-01-10 14:01:47.813 INFO 13112 --- [io-20006-exec-2] d.d.s.c.CouponTemplateController : getTemplateInBatch: [2,3] #
2024-01-10 14:01:47.813 INFO 13112 --- [io-20006-exec-2] d.d.s.c.CouponTemplateController : 接口被fallback
2024-01-10 14:01:47.813 INFO 13112 --- [o-20006-exec-10] d.daddy.sentinel.SentinelOriginParser : request org.apache.catalina.util.ParameterMap@39d7b9e0, header=org.apache.tomcat.util.http.NamesEnumerator@7d50964c
2024-01-10 14:01:47.814 INFO 13112 --- [o-20006-exec-10] d.d.s.c.CouponTemplateController : 接口被fallback
2024-01-10 14:01:47.815 INFO 13112 --- [io-20006-exec-9] d.daddy.sentinel.SentinelOriginParser : request org.apache.catalina.util.ParameterMap@39d7b9e0, header=org.apache.tomcat.util.http.NamesEnumerator@1f47105b
2024-01-10 14:01:47.816 INFO 13112 --- [io-20006-exec-9] d.d.s.c.CouponTemplateController : 接口被fallback
....
2024-01-10 14:01:47.813 INFO 13112 --- [io-20006-exec-2] d.d.s.c.CouponTemplateController : getTemplateInBatch: [2,3] # 1s后半開(kāi)啟,后又重新進(jìn)入熔斷
2024-01-10 14:01:47.813 INFO 13112 --- [io-20006-exec-2] d.d.s.c.CouponTemplateController : 接口被fallback
2024-01-10 14:01:47.813 INFO 13112 --- [o-20006-exec-10] d.daddy.sentinel.SentinelOriginParser : request org.apache.catalina.util.ParameterMap@39d7b9e0, header=org.apache.tomcat.util.http.NamesEnumerator@7d50964c
2024-01-10 14:01:47.814 INFO 13112 --- [o-20006-exec-10] d.d.s.c.CouponTemplateController : 接口被fallback
2024-01-10 14:01:47.815 INFO 13112 --- [io-20006-exec-9] d.daddy.sentinel.SentinelOriginParser : request org.apache.catalina.util.ParameterMap@39d7b9e0, header=org.apache.tomcat.util.http.NamesEnumerator@1f47105b
2024-01-10 14:01:47.816 INFO 13112 --- [io-20006-exec-9] d.d.s.c.CouponTemplateController : 接口被fallback
...
可以看到降級(jí)后,滿足條件就會(huì)進(jìn)入熔斷。一段時(shí)間后會(huì)嘗試恢復(fù),這個(gè)狀態(tài)叫做半熔斷,完整的狀態(tài)轉(zhuǎn)換參考下圖:
4:接入nacos實(shí)現(xiàn)持久化
二次開(kāi)發(fā)后源碼 。
在前面的實(shí)戰(zhàn)中,不知道你發(fā)現(xiàn)沒(méi)有,如果是應(yīng)用重啟,則我們?cè)O(shè)置的規(guī)則就都會(huì)丟失,這是因?yàn)檫@些規(guī)則信息默認(rèn)是保存在內(nèi)存中,sentinel也支持將規(guī)則信息保存在nacos中,本部分就一起來(lái)看下如何實(shí)現(xiàn),想要實(shí)現(xiàn)這個(gè)操作,需要對(duì)sentinel的源碼做一下簡(jiǎn)單的二次開(kāi)發(fā)工作。我們開(kāi)始。
4.1:下載sentient源碼
你可以在這里 下載源碼,如下圖:
下載后導(dǎo)入到idea中,注意將idea中jdk相關(guān)的配置都修改為1.8,否則可能會(huì)無(wú)法正常編譯代碼,成功后如下圖:
接著就可以開(kāi)始二次開(kāi)發(fā)的工作了。
4.2:二次開(kāi)發(fā)
我們需要修改的模塊是sentinel-dashboard
,首先需要修改nacos依賴范圍,默認(rèn)是test的,即只在test階段生效,我們需要修改為編譯階段的,如下:
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
<!-- 將scope注釋掉,改為編譯期打包 -->
<!--<scope>test</scope>-->
</dependency>
接著我們需要將test包下com.alibaba.csp.sentinel.dashboard.rule.nacos
的四個(gè)nacos操作的相關(guān)文件,如下:
NacosConfig:初始化 Nacos Config 的連接;(該類(lèi)我們需要修改,設(shè)置自己環(huán)境的nacos地址)
NacosConfigUtil:約定了 Nacos 配置文件所屬的 Group 和文件命名后綴等常量字段;
FlowRuleNacosProvider:從 Nacos Config 上獲取限流規(guī)則;(從nacos獲取配置數(shù)據(jù))
FlowRuleNacosPublisher:將限流規(guī)則發(fā)布到 Nacos Config。(將配置寫(xiě)到nacos)
我們需要在main目錄中創(chuàng)建一樣的包路徑,并將這四個(gè)類(lèi)拷貝過(guò)去,如下圖:
首先我們修改NacosConfig
類(lèi)nacosConfigService方法,該方法負(fù)責(zé)創(chuàng)建操作nacos的ConfigService的bean,如下:
/*
負(fù)責(zé)更新以及讀取nacos的類(lèi)
*/
@Bean
public ConfigService nacosConfigService() throws Exception {
// 將Nacos的注冊(cè)地址引入進(jìn)來(lái)
Properties properties = new Properties();
properties.setProperty("serverAddr", "192.168.10.77:8868");
properties.setProperty("namespace", "dev");
// return ConfigFactory.createConfigService("localhost");
return ConfigFactory.createConfigService(properties);
}
然后修改FlowControllerV2中寫(xiě)入sentinel配置的pulisher和讀取配置的provider為nacos的,如下:
@Autowired
// @Qualifier("flowRuleDefaultProvider")
// 修改為nacos的數(shù)據(jù)provider提供者,這樣就能從nacos中獲取數(shù)據(jù)
@Qualifier("flowRuleNacosProvider")
private DynamicRuleProvider<List<FlowRuleEntity>> ruleProvider;
@Autowired
// @Qualifier("flowRuleDefaultPublisher")
// 修改為nacos的發(fā)布者,這樣就能將規(guī)則配置數(shù)據(jù)寫(xiě)到nacos中
@Qualifier("flowRuleNacosPublisher")
private DynamicRulePublisher<List<FlowRuleEntity>> rulePublisher;
我們來(lái)看下publisher向nacos寫(xiě)數(shù)據(jù)的源碼:
// com.alibaba.csp.sentinel.dashboard.rule.nacos.FlowRuleNacosPublisher#publish
@Override
public void publish(String app, List<FlowRuleEntity> rules) throws Exception {
AssertUtil.notEmpty(app, "app name cannot be empty");
if (rules == null) {
return;
}
// app 應(yīng)用名稱
// public static final String FLOW_DATA_ID_POSTFIX = "-flow-rules";
// public static final String GROUP_ID = "SENTINEL_GROUP";
// converter.convert(rules) 轉(zhuǎn)成字符串
configService.publishConfig(app + NacosConfigUtil.FLOW_DATA_ID_POSTFIX,
NacosConfigUtil.GROUP_ID, converter.convert(rules));
}
源碼中就可以看到nacos作為配置中心時(shí)的組是SENTINEL_GROUP
,dataId是${spring.application.name}-flow-rules
。最后我們修改頁(yè)面sidebar.html,增加如下內(nèi)容:
<li ui-sref-active="active">
<a ui-sref="dashboard.flow({app: entry.app})">
<i class="glyphicon glyphicon-filter"></i> 流控規(guī)則 極客時(shí)間改造</a>
</li>
最后需要改造我們的微服務(wù),以cusotm為例,首先增加依賴:
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
然后增加配置:
spring:
cloud:
sentinel:
datasource:
# 數(shù)據(jù)源的key,可以自由命名
geekbang-flow:
# 指定當(dāng)前數(shù)據(jù)源是nacos
nacos:
# 設(shè)置Nacos的連接地址、命名空間和Group ID
server-addr: localhost:8848
namespace: dev
groupId: SENTINEL_GROUP
# 設(shè)置Nacos中配置文件的命名規(guī)則
dataId: ${spring.application.name}-flow-rules
# 必填的重要字段,指定當(dāng)前規(guī)則類(lèi)型是"限流"
rule-type: flow
最后,打jar包,成功后會(huì)在target目錄看到j(luò)ar包:
最后啟動(dòng)項(xiàng)目:
java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar
啟動(dòng)custom模塊,登錄后就可以看到新加的菜單:
如果是該菜單增加配置,則會(huì)同步到nacos中,并且配置dataID是coupon-customer-serv-sentinel-flow-rules:
配置內(nèi)容是以文本的格式存儲(chǔ)的,但存儲(chǔ)的也是json:文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-782999.html
寫(xiě)在后面
參考文章列表
jmeter之簡(jiǎn)單使用 。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-782999.html
到了這里,關(guān)于spring cloud之集成sentinel的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!