前言:學習本篇博客是有一些前提基礎的
1、熟悉gateway網關使用
2、熟悉nginx使用
3、熟悉sentinel的應用,會涉及網關規則持久化改造
看不懂的童鞋們可以補一下微服務gateway網關和Sentinel相關知識
秒殺鏈路兜底方案之限流&降級實戰
- 一、秒殺場景介紹
- 1.1 秒殺場景的特點
- 1.2 流量消峰
- 1.3 兜底方案
- 二、限流實戰
- 2.1 nginx限流(https://nginx.org/en/docs)
- 2.2 網關限流
- 2.2.1 網關接入sentinel控制臺
- 2.2.2 Sentinel規則持久化配置
- 2.3 應用層限流
- 三、降級實戰
- 3.1 服務降級的策略
- 3.2 應用層降級實戰
- 四、拒絕服務
一、秒殺場景介紹
1.1 秒殺場景的特點
- 秒殺具有瞬時高并發的特點,秒殺請求在時間上高度集中于某一特定的時間點(秒殺開始那一秒),這樣一來,就會導致一個特別高 的流量峰值,它對資源的消耗是瞬時的,
- 但是對秒殺這個場景來說,最終能夠搶到商品的人數是固定的,也就是說 100 人和 10000 人發起請求的結果都是一樣的,并發度越高,無效請求也越多,
- 但是從業務上來說,秒殺活動是希望更多的人來參與的,也就是開始之前希望有更多的人來刷頁面,但是真正開始下單時,秒殺請求 并不是越多越好,
1.2 流量消峰
服務器的處理資源是恒定的,你用或者不用它的處理能力都是一樣的,所以出現峰值的話,很容易導致忙到處理不過來,閑的時候卻又沒有什么要處理,
流量削峰,一是可以讓服務端處理變得更加平穩,二是可以節省服務器的資源成本,針對秒殺這一場景,削峰從本質 上來說就是更多地延緩用戶請求的發出,以便減少和過濾掉一些無效請求,它遵從“請求數要盡量少”的原則,流量削峰的比較常見的 思路:排隊、答題、分層過濾,
1.3 兜底方案
對于很多秒殺系統而言,在諸如雙十一這樣的大流量的迅猛沖擊下,都曾經或多或少發生過宕機的情況,當一個系統面臨持續的大流 量時,它其實很難單靠自身調整來恢復狀態,你必須等待流量自然下降或者人為地把流量切走才行,這無疑會嚴重影響用戶的購物體驗, 我們可以在系統達到不可用狀態之前就做好流量限制,防止最壞情況的發生,針對秒殺系統,在遇到大流量時,更多考慮的是運行階段如何保障系統的穩定運行,常用的手段:限流,降級,拒絕服務,
二、限流實戰
限流相對降級是一種更極端的保存措施,限流就是當系統容量達到瓶頸時,我們需要通過限制一部分流量來保護系統,并做到既可以 人工執行開關,也支持自動化保護的措施,
限流既可以是在客戶端限流,也可以是在服務端限流,限流的實作方式既要支持 URL 以及方法級別的限流,也要支持基于 QPS 和線 程的限流,
- 客戶端限流
好處:可以限制請求的發出,通過減少發出無用請求從而減少對系統的消耗,
缺點:當客戶端比較分散時,沒法設定合理的限流閾值:如果閾值設的太小,會導致服務端沒有達到瓶頸時客戶端已經被限制;而如 果設的太大,則起不到限制的作用, - 服務端限流
好處:可以根據服務端的性能設定合理的閾值
缺點:被限制的請求都是無效的請求,處理這些無效的請求本身也會消耗服務器資源,
在限流的實作手段上來講,基于 QPS 和執行緒數的限流應用最多,最大 QPS 很容易通過壓測提前獲取,例如我們的系統最高支持 1w QPS 時,可以設定 8000 來進行限流保護,執行緒數限流在客戶端比較有效,例如在遠程呼叫時我們設定連接池的執行緒數,超出這個并發線 程請求,就將執行緒進行排隊或者直接超時丟棄,
限流必然會導致一部分用戶請求失敗,因此在系統處理這種例外時一定要設定超時時間,防止因被限流的請求不能 fast fail(快速失 敗)而拖垮系統,
限流的方案
- 前端限流
- 接入層nginx限流
- 網關限流
- 應用層限流
2.1 nginx限流(https://nginx.org/en/docs)
# window下nginx強制關閉命令
taskkill /fi "imagename eq nginx.EXE" /f
# 啟動nginx
start nginx.exe
# 重新加載配置
nginx.exe ‐s reload
limit_conn_zone&limit_conn
ngx_http_limit_conn_module 可以對于一些服務器流量例外、負載過大,甚至是大流量的惡意攻擊訪問等,進行并發數 的限制;該模塊可以根據定義的鍵來限制每個鍵值的連接數,只有那些正在被處理的請求(這些請求的頭資訊已被完全 讀入)所在的連接才會被計數,
# 限制連接數
limit_conn_zone $binary_remote_addr zone=addr:10m;
server {
location /download/ {
# 指定每個給定鍵值的最大同時連接數,同一IP同一時間只允許有1個連接
limit_conn addr 1;
}
客戶端的IP地址作為鍵, binary_remote_addr變數的長度是固定的4位元組,存盤狀態在32位平臺中占用32位元組或64位元組,在64位平臺中占用64位元組, 1M共享空間可以保存3.2萬個32位的狀態,1.6萬個64位的狀態, 如果共享記憶體空間被耗盡,服務器將會對后續所有的請求回傳 503 (Service Temporarily Unavailable) 錯誤,
缺陷: 前端做LVS或反向代理,會出現大量的503錯誤,需要設定白名單(對某些ip不做限制) 測驗: http://localhost/pms/productInfo/29

limit_req_zone&limit_req
通過ngx_http_limit_req_module 模塊可以通過定義的鍵值來限制請求處理的頻率,特別的,可以限制來自單個IP地址 的請求處理頻率, 限制的方法如同漏斗,每秒固定處理請求數,推遲過多請求,
http {
# 限制請求數,大小為10m, 平均處理的頻率不能超過每秒1次
limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
...
server {
...
location /search/ {
# 允許超出頻率限制的請求數為5,默認會被延遲處理,如果不希望延遲處理,可以使用nodelay引數
limit_req zone=one burst=5 nodelay;
}
區域名稱為one,大小為10m,平均處理的請求頻率不能超過每秒一次,鍵值是客戶端IP, 使用$binary_remote_addr變數,可以將每條狀態記錄的大小減少到64個位元組,這樣1M的記憶體可以保存大約1萬6千個64位元組的記錄 如果限制域的存盤空間耗盡了,對于后續所有請求,服務器都會回傳503(Service Temporarily Unavailable)錯誤 速度可以設定為每秒處理請求數和每分鐘處理請求數,其值必須是整數,所以如果你需要每秒處理少于1個的請求,2秒處理一個請 求,可以使用30r/m
測驗:


利用Lua限流
https://github.com/openresty/lua-resty-limit-traffic
2.2 網關限流
spring cloud gateway接入sentinel實作限流的原理:

2.2.1 網關接入sentinel控制臺
建議sentinel 控制臺和微服務sentinel版本一一對應,否則可能出現兼容性問題導致規則配置失效
引入依賴
<!‐‐添加Sentinel的依賴‐‐>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring‐cloud‐starter‐alibaba‐sentinel</artifactId>
</dependency>
<!‐‐ gateway接入sentinel ‐‐>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring‐cloud‐alibaba‐sentinel‐gateway</artifactId>
</dependency>
接入sentinel控制臺,修改application.yml配置
spring:
application:
name: tulingmall‐gateway
main:
allow‐bean‐definition‐overriding: true
cloud:
sentinel:
transport:
dashboard: 127.0.0.1:8000
啟動sentinel控制臺
java ‐Dserver.port=8000 ‐Dsentinel.nacos.config.serverAddr=tl.nacos.com:8848 ‐jar sentinel‐dashboard‐1.7.1.jar
網關接入控制臺后界面展示:

Sentinel1.7.1版本,gateway網關規則不生效的問題根本原因: SlotChain中沒有添加GatewayFlowSlot ,默認生效的是HotParamSlotChainBuilder

解決思路: 使用GatewaySlotChainBuilder,將GatewayFlowSlot加入到SlotChain 可以利用SPI實作:在當前微服務添加GatewaySlotChainBuilder的spi檔案

Sentinel1.8.0 中SlotChain處理策略,統一在DefaultSlotChainBuilder中處理了

2.2.2 Sentinel規則持久化配置

引入依賴
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel‐datasource‐nacos</artifactId>
</dependency>
application.yml添加datasource配置
spring:
cloud:
sentinel:
transport:
# 添加sentinel的控制臺地址
dashboard: 127.0.0.1:8000
datasource:
gateway‐flow‐rules:
nacos:
server‐addr: 127.0.0.1:8848
dataId: ${spring.application.name}‐gateway‐flow‐rules
groupId: SENTINEL_GROUP
data‐type: json
rule‐type: gw‐flow
gateway‐api‐rules:
nacos:
server‐addr: 127.0.0.1:8848
dataId: ${spring.application.name}‐gateway‐api‐rules
groupId: SENTINEL_GROUP
data‐type: json
rule‐type: gw‐api‐group
啟動持久化改造后的sentinel dashboard
指定埠和nacos配置中心地址
java ‐Dserver.port=8000 ‐Dsentinel.nacos.config.serverAddr=tl.nacos.com:8848 ‐jar tuling‐sentinel‐dashboard.jar
注意:網關規則改造的坑
- 網關規則物體轉換
RuleEntity‐‐‐》Rule 利用RuleEntity#toRule
#網關規則物體
ApiDefinitionEntity‐‐‐》ApiDefinition 利用ApiDefinitionEntity#toApiDefinition
GatewayFlowRuleEntity‐‐‐‐‐>GatewayFlowRule 利用GatewayFlowRuleEntity#toGatewayFlowRule
- json決議丟失資料
json決議ApiDefinition型別出現資料丟失的現象

排查原因:ApiDefinition的屬性Set<ApiPredicateItem> predicateItems中元素 是介面型別,JSON決議丟失數 據

解決方案:重寫物體類ApiDefinition2,再轉換為ApiDefinition
//GatewayApiRuleNacosProvider.java
@Override
public List<ApiDefinitionEntity> getRules(String appName,String ip,Integer port)throws Exception{
String rules=configService.getConfig(appName+NacosConfigUtil.GATEWAY_API_DATA_ID_POSTFIX,
NacosConfigUtil.GROUP_ID,NacosConfigUtil.READ_TIMEOUT);
if(StringUtil.isEmpty(rules)){
return new ArrayList<>();
}
// 注意 ApiDefinition的屬性Set<ApiPredicateItem> predicateItems中元素 是介面型別,JSON決議丟失資料
// 重寫物體類ApiDefinition2,再轉換為ApiDefinition
List<ApiDefinition2> list=JSON.parseArray(rules,ApiDefinition2.class);
return list.stream().map(rule ‐>
ApiDefinitionEntity.fromApiDefinition(appName,ip,port,rule.toApiDefinition()))
.collect(Collectors.toList());
}
public class ApiDefinition2 {
private String apiName;
private Set<ApiPathPredicateItem> predicateItems;
public ApiDefinition2() {
}
public String getApiName() {
return apiName;
}
public void setApiName(String apiName) {
this.apiName = apiName;
}
public Set<ApiPathPredicateItem> getPredicateItems() {
return predicateItems;
}
public void setPredicateItems(Set<ApiPathPredicateItem> predicateItems) {
this.predicateItems = predicateItems;
}
@Override
public String toString() {
return "ApiDefinition2{" + "apiName='" + apiName + '\'' + ", predicateItems=" + predicateItems + '}';
}
public ApiDefinition toApiDefinition() {
ApiDefinition apiDefinition = new ApiDefinition();
apiDefinition.setApiName(apiName);
Set<ApiPredicateItem> apiPredicateItems = new LinkedHashSet<>();
apiDefinition.setPredicateItems(apiPredicateItems);
if (predicateItems != null) {
for (ApiPathPredicateItem predicateItem : predicateItems) {
apiPredicateItems.add(predicateItem);
}
}
return apiDefinition;
}
}
從 1.6.0 版本開始,Sentinel 提供了 Spring Cloud Gateway 的適配模塊,可以提供兩種資源維度的限流:
- route 維度:即在 Spring 組態檔中配置的路由條目,資源名為對應的 routeId
- 自定義 API 維度:用戶可以利用 Sentinel 提供的 API 來自定義一些 API 分組
route維度限流
配置流控規則

測驗:http://localhost:8888/pms/productInfo/29

API維度限流
配置流控規則

2.3 應用層限流
場景: 商品詳情介面
系統第一次上線啟動,或者系統在 redis 故障的情況下重新啟動,這時在高并發的場景下就會出現所有的流量 都會打到 mysql(原始資料庫) 上去,導致 mysql 崩潰,因此需要通過快取預熱的方案,提前給 redis 灌入部分資料后再提供服務,
jemeter測驗: 模擬2秒內查詢商品id為1-5000的商品資訊 【/pms/productInfo/${__counter(,)}】

壓測直接訪問DB的介面: 吞吐量:20-60
public PmsProductParam getProductInfo1(Long id){
PmsProductParam productInfo=portalProductDao.getProductInfo(id);
if(null==productInfo){
return null;
}
checkFlash(id,productInfo);
return productInfo;
}

壓測訪問快取的介面:
public PmsProductParam getProductInfo2(Long id){
PmsProductParam productInfo=null;
// 查詢本地快取
productInfo=cache.get(RedisKeyPrefixConst.PRODUCT_DETAIL_CACHE+id);
if(null!=productInfo){
return productInfo;
}
// 查詢redis快取
productInfo=redisOpsUtil.get(RedisKeyPrefixConst.PRODUCT_DETAIL_CACHE+id,PmsProductParam.class);
if(productInfo!=null){
//設定本地快取
cache.setLocalCache(RedisKeyPrefixConst.PRODUCT_DETAIL_CACHE+id,productInfo);
return productInfo;
}
// 查詢DB
productInfo=portalProductDao.getProductInfo(id);
if(null==productInfo){
return null;
}
checkFlash(id,productInfo);
// 設定redis快取
redisOpsUtil.set(RedisKeyPrefixConst.PRODUCT_DETAIL_CACHE+id,productInfo,3600,TimeUnit.SECONDS);
// 設定本地緩
cache.setLocalCache(RedisKeyPrefixConst.PRODUCT_DETAIL_CACHE+id,productInfo);
return productInfo;
}
第一次訪問快取擊穿的吞吐量: 20-60
之后吞吐量: 1000-2800

思考: 在沒有事先進行快取預熱的情況下,如何避免更多的請求直接訪問到資料庫?
當對資料庫訪問達到閾值,可以對商品詳情請求限流
配置流控規則

思考:排隊等待可以應用于什么場景?
勻速排隊(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER)方式會嚴格控制請求通過的間隔時間,也即是讓請 求以均勻的速度通過,對應的是漏桶演算法,
該方式的作用如下圖所示:

這種方式主要用于處理間隔性突發的流量,例如訊息佇列,想象一下這樣的場景,在某一秒有大量的請求到來,而接下 來的幾秒則處于空閑狀態,我們希望系統能夠在接下來的空閑期間逐漸處理這些請求,而不是在第一秒直接拒絕多余的 請求,
注意:勻速排隊模式暫時不支持 QPS > 1000 的場景,
場景: 對秒殺介面進行流控


熱點引數限流
何為熱點?熱點即經常訪問的資料,商家不定期做一些“商品秒殺”、“商品推廣”活動,導致“營銷活動”、“商品 詳情”、“交易下單”等鏈路應用出現 快取熱點訪問 的情況:
- 活動時間、活動型別、活動商品之類的資訊不可預期,導致 快取熱點訪問 情況不可提前預知;
- 快取熱點訪問 出現期間,應用層少數 熱點訪問 key 產生大量快取訪問請求,沖擊分布式快取系統,大量占據 內網帶寬,最終影回應用層系統穩定性;
很多時候我們希望統計某個熱點資料中訪問頻次最高的 Top K 資料,并對其訪問進行限制,比如:
- 商品 ID 為引數,統計一段時間內最常購買的商品 ID 并進行限制
- 用戶 ID 為引數,針對一段時間內頻繁訪問的用戶 ID 進行限制
熱點引數限流會統計傳入引數中的熱點引數,并根據配置的限流閾值與模式,對包含熱點引數的資源呼叫進行限流,熱點引數限流可以看做是一種特殊的流量控制,僅對包含熱點引數的資源呼叫生效,

注意:
1. 熱點規則需要使用@SentinelResource(“resourceName”)注解,否則不生效
2. 引數必須是7種基本資料型別才會生效

配置熱點引數限流規則

測驗: http://localhost:8866/pms/productInfo/26

思考: 如何快速且準確的發現熱點訪問key ?
熱點探測功能設計思路

三、降級實戰
降級就是當系統的容量達到一定程度時,限制或者關閉系統的某些非核心功能,從而把有限的資源保留給更核心的業務,
比如降級方案可以這樣設計:當秒殺流量達到 5w/s 時,把成交記錄的獲取從展示 20 條降級到只展示 5 條,“從 20 改到 5”這個操作由一個開關來實作,也就是設定一個能夠從開關系統動態獲取的系統引數,
降級的核心目標是犧牲次要的功能和用戶體驗來保證核心業務流程的穩定,是一個不得已而為之的舉措,例如在雙 11 零點時,如果優惠券系統扛不住,可能會臨時降級商品詳情的優惠資訊展示,把有限的系統資源用在保障交易系統正 確展示優惠資訊上,即保障用戶真正下單時的價格是正確的,
3.1 服務降級的策略

3.2 應用層降級實戰
場景: 秒殺下單 /order/miaosha/generateOrder

如果會員服務出現問題,會影響整個下單鏈路,
模擬查詢會員地址資訊出現網路問題和業務例外
@ApiOperation("顯示識訓地址詳情")
@RequestMapping(value = "/{id}", method = RequestMethod.GET)
@ResponseBody
public CommonResult<UmsMemberReceiveAddress> getItem(@PathVariable Long id,@RequestHeader("memberId") long memberId){
if(memberId==
){
try{
//模擬網路問題
Thread.sleep(2000);
}catch(InterruptedException e){
e.printStackTrace();
}
}
if(memberId==4){
//模擬業務例外
throw new IllegalArgumentException("非法引數例外");
}
UmsMemberReceiveAddress address=memberReceiveAddressService.getItem(id,memberId);
return CommonResult.success(address);
}
測驗: memberId為3的用戶壓測

Sentinel熔斷降級
OpenFeign整合Sentinel
組態檔打開 Sentinel 對 Feign 的支持:feign.sentinel.enabled=true
feign:
sentinel:
enabled: true
feign介面配置fallbackFactory
@FeignClient(name = "tulingmall‐member", path = "/member",
fallbackFactory = UmsMemberFeginFallbackFactory.class)
public interface UmsMemberFeignApi {
UmsMemberFeginFallbackFactory中撰寫降級邏輯
@Component
public class UmsMemberFeginFallbackFactory implements FallbackFactory<UmsMemberFeignApi> {
@Override
public UmsMemberFeignApi create(Throwable throwable) {
return new UmsMemberFeignApi() {
@Override
public CommonResult<UmsMemberReceiveAddress> getItem(Long id) {
//TODO 業務降級
UmsMemberReceiveAddress defaultAddress = new UmsMemberReceiveAddress();
defaultAddress.setName("默認地址");
defaultAddress.setId(‐ 1L);
defaultAddress.setDefaultStatus(0);
defaultAddress.setPostCode("‐1");
defaultAddress.setProvince("默認省份");
defaultAddress.setCity("默認city");
defaultAddress.setRegion("默認region");
defaultAddress.setDetailAddress("默認詳情地址");
defaultAddress.setMemberId(‐ 1L);
defaultAddress.setPhoneNumber("199xxxxxx");
return CommonResult.success(defaultAddress);
}
@Override
public CommonResult<String> updateUmsMember(UmsMember umsMember) {
return null;
}
@Override
public CommonResult<PortalMemberInfo> getMemberById() {
return null;
}
@Override
public CommonResult<List<UmsMemberReceiveAddress>> list() {
return null;
}
};
}
}
會員識訓地址介面配置基于回應時間的降級規則

測驗:

會員識訓地址介面配置基于例外數的降級規則

測驗:

四、拒絕服務
拒絕服務可以說是一種不得已的兜底方案,用以防止最壞情況發生,防止因把服務器壓跨而長時間徹底無法提供服務,當系統負載達 到一定閾值時,例如 CPU 使用率達到 90% 或者系統 load 值達到 2*CPU 核數時,系統直接拒絕所有請求,這種方式是最暴力但也最有 效的系統保護方式,
例如秒殺系統,我們可以在以下環節設計過載保護:
- 在最前端的 Nginx 上設定過載保護,當機器負載達到某個值時直接拒絕 HTTP 請求并回傳 503 錯誤碼, 阿里針對nginx開發的過載保護擴展插件sysguard:https://github.com/alibaba/nginx-http-sysguard
- 在 Java 層同樣也可以設計過載保護, 比如Sentinel提供了系統規則限流
Sentinel系統規則限流
Sentinel 系統自適應限流從整體維度對應用入口流量進行控制,結合應用的 Load、CPU 使用率、總體平均 RT、入口 QPS 和并發執行緒數等幾個維度的監控指標,通過自適應的流控策略,讓系統的入口流量和系統的負載達到一個平衡,讓 系統盡可能跑在最大吞吐量的同時保證系統整體的穩定性,
- Load 自適應(僅對 Linux/Unix-like 機器生效):系統的 load1 作為啟發指標,進行自適應系統保護,當系統 load1 超過設定的啟發值,且系統當前的并發執行緒數超過估算的系統容量時才會觸發系統保護(BBR 階段),系統 容量由系統的 maxQps * minRt 估算得出,設定參考值一般是 CPU cores * 2.5,
- CPU usage(1.5.0+ 版本):當系統 CPU 使用率超過閾值即觸發系統保護(取值范圍 0.0-1.0),比較靈敏,
- 平均 RT:當單臺機器上所有入口流量的平均 RT 達到閾值即觸發系統保護,單位是毫秒,
- 并發執行緒數:當單臺機器上所有入口流量的并發執行緒數達到閾值即觸發系統保護,
- 入口 QPS:當單臺機器上所有入口流量的 QPS 達到閾值即觸發系統保護,
系統規則持久化yml配置
system‐rules:
nacos:
server‐addr: tl.nacos.com:8848
dataId: ${spring.application.name}‐system‐rules
groupId: SENTINEL_GROUP
data‐type: json
rule‐type: system


轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/310540.html
標籤:其他
