1. 現有項目注冊中心替換alibaba組件Nacos
1.springcloud 、springboot、springcloud alibaba版本對應
spring cloud alibaba文檔:https://github.com/alibaba/spring-cloud-alibaba/wiki
版本對應:https://github.com/alibaba/spring-cloud-alibaba/wiki/版本說明
注意:
- 0.9版本之后,畢業版本的groudId 為 com.alibaba.com
- 0.9版本之前,化器版本groudId 為org.springframework.cloud(不推薦使用)
2. 依賴修改
目前mango系統各組件版本
- springboot版本為1.5
- springcloud alibaba使用畢業版版本1.5.0RELEASE ,不要使用孵化器版本
- nacos service 阿里文檔中推薦1.1.1版本,實際上使用最新的穩定版本也可以使用,測試驗證使用1.2版本可以正常使用
<!--pom文件依賴新增一下內容,刪除其他注冊中心的相關的依賴(例如:Eureka) 和其他配置中心的依賴(例如:Springcloud config)-->
<dependencyManagement>
<!--只是對版本進行管理,不會實際引入jar -->
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Edgware.SR6</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>1.5.1.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!--mango系統替換后需要指定springCloud-common包版本號-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-commons</artifactId>
<version>1.3.5.RELEASE</version>
</dependency>
<!--因為nacos config是通過web獲取配置,需要導入springboot-web依賴-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--nacos 服務發現-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<exclusions>
<exclusion>
<artifactId>spring-cloud-commons</artifactId>
<groupId>org.springframework.cloud</groupId>
</exclusion>
<exclusion>
<artifactId>fastjson</artifactId>
<groupId>com.alibaba</groupId>
</exclusion>
</exclusions>
</dependency>
<!--nacos 配置管理-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<exclusions>
<exclusion>
<artifactId>spring-cloud-commons</artifactId>
<groupId>org.springframework.cloud</groupId>
</exclusion>
</exclusions>
</dependency>
<!--sentinel依賴,注意網關根據不同的實現方式對應着不同的sentinel依賴-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>spring-boot-starter-ahas-sentinel-client</artifactId>
<version>1.7.2</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring‐cloud‐starter‐openfeign</artifactId>
</dependency>
</dependencies>
網關不同實現方式sentinle對應的依賴:
<!--zuul 1.x網關接入, Mango即是zuul 1.X-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>spring-cloud-zuul-starter-ahas-sentinel</artifactId>
<version>1.1.8</version>
</dependency>
<!--springcloudGateway網關接入-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>spring-cloud-gateway-starter-ahas-sentinel</artifactId>
<version>1.1.8</version>
</dependency>
3. 配置文件修改
其他詳細的配置可參考nacos-配置管理中的介紹
#bootstrap
spring:
application:
name: gateway
cloud:
sentinel:
transport:
#sentinel 控制面板
dashboard: 127.0.0.1:8083
nacos:
config:
# nacos service地址
server-addr: 127.0.0.1:8848
namespace: public
group: DEFAULT_GROUP
data-id: gateway.yml
file-extension: yaml
refresh:
enables: true # 默認為fasle ,設為true,開啟動態刷新
#application
server:
port: 56020 #啟動端口 命令行注入
spring:
application:
name: gateway
cloud:
nacos:
discovery:
# nacos service地址
server‐addr: 127.0.0.1:8848
4. 代碼修改
- 啟動類新增服務發現注解
@EnableDiscoveryClient
@EnableFeignClients
- 生產者聲明
@FeignClient(name = "uaa")
,mango項目中,生產者遠程代理在common中,注意修改common的依賴
2. nacos介紹
Nacos是阿里的一個開源產品,它是針對微服務架構中的服務發現、配置管理、服務治理的綜合型解決方案。
nacos文檔: https://nacos.io/en-us/docs/what-is-nacos.html
2.1 nacos-配置管理
nacos-config文檔:https://github.com/alibaba/spring-cloud-alibaba/wiki/Nacos-config
2.1.1 配置中心的使用場景
微服務架構,系統拆分為一個個服務節點,配置文件也被分割,分散中就包含了很多相同的配置,造成冗余。
配置中心就是將配置從應用里剝離出來, 進行統一管理。
配置中心的服務流程:
1. 用戶在配置中心更新配置信息。
2. 服務A和服務B及時得到配置更新通知,從配置中心獲取配置
2.1.2 常見的配置中心
nacos 讀寫性能最高,且和springcloud condig相比,nacos帶圖形化界面配置。
2.1.3 nacos 快速部署
-
下載地址:https://github.com/alibaba/nacos/releases,下載源碼自己編譯/下載編譯好的安裝包
-
安裝包啟動方式:bin目錄下執行 sh startup.sh -m standalone
standalone 代表單機模式運行,非集群模式
sh startup ‐m cluster 標識集群啟動
-
啟動成功, http://127.0.0.1:8848/nacos可打開nacos控制台,默認賬號密碼都為 nacos
-
OPEN API 配置管理測試 :https://nacos.io/en-us/docs/open-api.html
- 發布配置:
curl -X POST "http://127.0.0.1:8848/nacos/v1/cs/configs dataId=nacos.cfg.dataId&group=test&content=HelloWorld"
- 獲取配置:
curl -X GET "http://127.0.0.1:8848/nacos/v1/cs/configs?dataId=nacos.cfg.dataId&group=test"
- 發布配置:
2.1.4 nacos 外部mysql數據庫支持
單機模式nacos使用的是嵌入式數據庫實現數據儲存,可以配置mysql作為外部存儲(目前只支持使用mysql)。
-
新建數據庫nacos_config
-
執行nacos下conf/nacos-mysql.sql 文件
-
修改nacos下conf/application.properties,增加支持mysql數據源配置,增加MySQL數據源賬號密碼
-
增加后記得重啟一下nacos
spring.datasource.platform=mysql db.num=1 db.url.0=jdbc:mysql://localhost:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true db.user= db.password=
2.1.5 nacos 配置管理應用
對於nacos配置管理,通過namespace、group、Data ID 可以定位到一個配置集(也可以理解為一個配置文件)。
- namespace:設計是 nacos 基於此做多環境以及多租戶(多個用戶共同使用nacos)數據(配置和服務)隔離的。
一個租戶情況下:有多套不同環境,可以使用同一套nacos集群,通過建多個不同的nacespace來做配置/服務的隔離
多個租戶情況下:不同租戶分配不同的賬號密碼,創建自己的namespace,不同租戶下namespace互不可見(nacos還沒有實現多租戶功能)
nacos 服務隔離,應該是同一namespace下才可以調用其他服務,注冊中心雖然是一個,但是注冊表不同
服務獲取配置集需要配置的內容:
- nacos server 地址 ,必須配置
- namespace:可以不指定,默認是public,若指定填寫的是namespace的id而不是名稱,id在nacos管理頁面查看
- group:可以不指定,默認是DEFAUT_GROUP
- dataId:必須指定
nacos配置管理提供了配置clone,歷史版本、回滾、訂閱者查詢等核心管理能力,通過圖形化界面很好操作。
監聽查詢:
Nacos提供配置訂閱者即監聽者查詢能力,同時提供客戶端當前配置的MD5校驗值,以便幫助用戶更好的檢查配 置變更是否推送到 Client 端。
2.1.6. nacos登錄管理:
除了使用默認的nacos賬號密碼,可以通過直接在數據庫中增加賬號密碼,密碼為BCrypt加密方法
也可以在nacos service中管理登錄功能,
spring.security.enabled=false management.security=false security.basic.enabled=false nacos.security.ignore.urls=/**
2.1.7 nacos集成分布式系統
通過 Spring cloud原生方式快捷的獲取配置內容:
1. 在bootstrap.yml基礎配置
Spring:
cloud:
nacos:
config:
enabled: true # 可不指定,默認true,false時關閉了nacos-config配置
server-addr: 127.0.0.1:8848 #必須指定
namespace: c67e4a97‐a698‐4d6d‐9bb1‐cfac5f5b51c4 # 可不指定,默認為public
ext-configs[0]: #配置單個配置集的時候,可省略
group: DEFAULT_GROUP # 可不指定,默認為DEFAULT_GROUP
file-extension: yaml # 默認是properties
data-id: service.yaml # 可不指定,默認是${spring.application.name}和文件拓展名組合
# 指定data-id時,需要帶上文件拓展名,yaml和yml都可以,和file-extension兩者不干擾,也可以配置多個data-id,優先級根據ex-config[n]中n的數字大小決定,數字越大,優先級越高
refresh:
enables: true # 默認為false,設為fasle 關閉動態刷新
ext‐config[1]:
# Data Id 不在默認的組,不支持動態刷新
data‐id: ext‐config‐common02.properties # 可以配置公用的配置
group: GLOBALE_GROUP
在加載配置文件時,不僅會加載${spring.application.name}.${file-extension:properties}為前綴的基礎配置
還會加載${spring.application}0 - ${profile}.${file-extension:properties}的基礎信息
${spring.profiles.active} 當通過配置文件來指定時必須放在 bootstrap.properties 文件中。但是實際項目中,時通過啟動參數-Dspring.profiles.active=<profile>
來切換不同的配置
通過修改namespace、group、dataId可以獲取指定的配置集,還可以修改profile.active來指定配置集
2. 自定義共享DataId配置
spring:
cloud:
nacos:
config:
# 配置分享dataId必須帶上文件后綴名,refreable-dataids也是
# shared-dataids 中group為默認的DEFAULT_GROUP ,若所配的data-id不在默認組中獲取不到配置集
# 支持多個DataId配置,逗號隔開,優先級按照配置出現的先后順序,即后面的優先級要高於前面。
shared‐dataids: ext‐config‐common01.properties,ext‐config‐common02.properties
# 指定動態刷新的 配置集
refreshable‐dataids: ext‐config‐common01.properties
3. 配置的優先級
Spring Cloud Alibaba Nacos Config 目前提供了三種配置能力從 Nacos 拉取相關的配置。
- A: 通過
spring.cloud.nacos.config.shared-configs[n].data-id
支持多個共享 Data Id 的配置 - B: 通過
spring.cloud.nacos.config.extension-configs[n].data-id
的方式支持多個擴展 Data Id 的配置 - C: 通過內部相關規則(應用名、應用名+ Profile )自動生成相關的 Data Id 配置
當三種方式共同使用時,他們的一個優先級關系是:A < B < C
4. 實時獲取最新配置
// 注入配置文件上下文
@Autowired
private ConfigurableApplicationContext applicationContext;
@GetMapping(value = "/configs")
public String getConfigs(){
// 實時獲取配置
return applicationContext.getEnvironment().getProperty("common.name");
}
2.1.8 nacos集群部署
-
安裝三個以上nacos
-
在所有nacos conf目錄下將cluster.conf.example文件改為cluster.conf,將每行配置成 ip:port。(請配置3個或3個以上節點)
# ip:port 127.0.0.1:8848 127.0.0.1:8849 127.0.0.1:8850
-
單機的話還需修改conf下application.properties中server.port ,防止端口沖突,如果服務器有多個ip也要指定具體的ip地址,如:nacos.inetutils.ip-address=127.0.0.1
-
客戶端配置,spring.cloud.nacos.config.server-addr: 127.0.0.1:8848,127.0.0.1:8849,127.0.0.1:8850
關閉其中的注冊中心,其他注冊注冊中心會被成功選舉。
2.2服務發現
2.2.1 常見服務發現中心:
從長遠來看Nacos在以后的版本會 支持SpringCLoud+Kubernetes的組合,填補 2 者的鴻溝,在兩套體系下可以采用同一套服務發現和配置管理的解 決方案,這將大大的簡化使用和維護的成本。另外,Nacos 計划實現 Service Mesh,也是未來微服務發展的趨 勢。
2.2.2 代碼修改
增加依賴,增加服務發現注解, 如1-4,省略
2.2.3 服務管理
- 可以通過nacos service 可視化界面對服務進行管理, 可以看到已經注冊的服務,且可以進行服務上線下線管理,通過配置實例的權重,修改實例接收的流量,權重為0時,不接受流量,和下線效果一樣
- 也可以在服務詳情頁面,進行元數據的修改,key-value的結構
2.2.4 nacos disvocery 配置項信息詳情
配置項 | Key | 默認值 | 說明 |
---|---|---|---|
服務端地址 |
spring.cloud.nacos.discovery.server-addr |
無 |
Nacos Server 啟動監聽的ip地址和端口 |
服務名 |
spring.cloud.nacos.discovery.service |
${spring.application.name} |
給當前的服務命名 |
服務分組 |
spring.cloud.nacos.discovery.group |
DEFAULT_GROUP |
設置服務所處的分組 |
權重 |
spring.cloud.nacos.discovery.weight |
1 |
取值范圍 1 到 100,數值越大,權重越大 |
網卡名 |
spring.cloud.nacos.discovery.network-interface |
無 |
當IP未配置時,注冊的IP為此網卡所對應的IP地址,如果此項也未配置,則默認取第一塊網卡的地址 |
注冊的IP地址 |
spring.cloud.nacos.discovery.ip |
無 |
優先級最高 |
注冊的端口 |
spring.cloud.nacos.discovery.port |
-1 |
默認情況下不用配置,會自動探測 |
命名空間 |
spring.cloud.nacos.discovery.namespace |
無 |
常用場景之一是不同環境的注冊的區分隔離,例如開發測試環境和生產環境的資源(如配置、服務)隔離等。 |
AccessKey |
spring.cloud.nacos.discovery.access-key |
無 |
當要上阿里雲時,阿里雲上面的一個雲賬號名 |
SecretKey |
spring.cloud.nacos.discovery.secret-key |
無 |
當要上阿里雲時,阿里雲上面的一個雲賬號密碼 |
Metadata |
spring.cloud.nacos.discovery.metadata |
無 |
使用Map格式配置,用戶可以根據自己的需要自定義一些和服務相關的元數據信息 |
日志文件名 |
spring.cloud.nacos.discovery.log-name |
無 |
|
集群 |
spring.cloud.nacos.discovery.cluster-name |
DEFAULT |
配置成Nacos集群名稱 |
接入點 |
spring.cloud.nacos.discovery.enpoint |
UTF-8 |
地域的某個服務的入口域名,通過此域名可以動態地拿到服務端地址 |
是否集成Ribbon |
ribbon.nacos.enabled |
true |
一般都設置成true即可 |
是否開啟Nacos Watch |
spring.cloud.nacos.discovery.watch.enabled |
true |
可以設置成false來關閉 watch |
3. sentinel
流控制組件,包括流控制,並發限制,電路中斷和自適應系統保護,以確保微服務的可靠性。
sentinel包括兩個部分:
- 核心庫:不依賴任何框架
- 控制台:springboot應用,可直接運行,負責管理推送規則、監控、集群限流分配,機器發現等.
sentinel功能:
- 流控
- 斷路和並發
- 最大並發限制
過限制並發線程的數量(即信號隔離)來減少不穩定資源的影響,而不是使用線程池。(hystrix使用線程池來實現隔離,可做對比//TODO)
當資源的響應時間變長時,線程將開始被占用。當線程數累積到一定數量時,新的傳入請求將被拒絕。反之亦然,當資源恢復並變得穩定時,占用的線程也將被釋放,新請求將被接受。 - 斷路
根據不穩定資源的響應時間降級不穩定資源保證可靠性。當資源的響應時間太大時,將在指定的時間窗口中拒絕所有對該資源的訪問。
- 最大並發限制
3.1 定義資源
能通過sentinel Api定義的diamond,就是資源
可以是:服務,方法,某一段代碼
3.1.1 Api方式定義資源
文檔:https://github.com/alibaba/Sentinel/wiki/如何使用
// 舉例
@GetMapping(value="/echo")
public String echo(){
// 定義資源
try(Entry entry = Sphu.entry("echo")){
//定義被保護的邏輯
return "i am from port"+port;
}catch(BlockException e){
retrun "當前訪問被控制了";
}
}
3.1.2 使用注解定義資源
sentinle提供了@SentinelResource
注解的方式定義資源,更推薦使用。
主要屬性 | 作用 |
---|---|
value | 資源名稱,必需項(不能為空) |
blockHandler | 在原方法被限流/降級/系統保護的時候調用,可選項。必須與原方法必須處於同一個類中、訪問類型必須為public、返回類型需要與原方法相匹配、參數類型需要和原方法相匹配。若希望使用其他類的函數,則可以指定 blockHandlerClass 為對應的類的 Class 對象,注意對應的函數必需為 static 函數,否則無法解析。 |
-
定義配置類
@Configuration public class SentinelAspectConfiguration { @Bean public SentinelResourceAspect sentinelResourceAspect() { return new SentinelResourceAspect(); } }
-
使用注解定義資源
@GetMapping(value = "/echo") @SentinelResource(value = "echo",blockHandler = "echoBlockHandler") public String echo() { //調用規則 this.initFlowRules(); //定義被保護的邏輯 return "i am from port " + port; } // 1. 必須處於同一個類,2. 訪問類型必須為public,3. 返回值類型必須與原方法一致,4. 參數與類型必須與原方法相同,BlockException是特例 public String echoBlockHandler(BlockException e){ //處理被控制的邏輯 return "當前訪問被控制了"; } private void initFlowRules() { List<FlowRule> rules = new ArrayList<>(); FlowRule rule = new FlowRule(); //關聯域名 rule.setResource("echo"); //設定限流閾值類型 rule.setGrade(RuleConstant.FLOW_GRADE_QPS); //設置限流閾值 rule.setCount(2); //設置流控針對的調用來源,default為不區分來源 rule.setLimitApp("default"); rules.add(rule); FlowRuleManager.loadRules(rules); }
3.2 定義規則
規則就是保護服務穩定的准則,可以是流控規則、服務熔斷降級規則、系統保護規則、來源訪問控制規則、熱點參數規則。
sentinle的所有規則都在內存
中動態查詢修改,規則可以實時進行調整。
定義規則有兩種途徑:1.sentinel Api 2. sentinel控制台
3.2.1 Api規則定義實現

// 舉例
private void initFolwRules(){
// flowRule 流控規則,可以定義多個流控規則
List<FlowRule> rules = new ArrayList<>();
FlowRule rule = new FlowRule();
//關聯域名、資源名稱
rule.setResource("echo");
//設定限流閥值類型
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
//設置限流閾值
rule.setCount(2);
//設置流控針對的調用來源,default為不區分來源
rule.setLimitApp("default");
rules.add(rule);
FlowRuleManager.loadRules(rules);
}
3.2.2 資源和規則結合
return "當前訪問被控制了";
@GetMapping(value = "/echo")
public String echo() {
//調用規則
this.initFlowRules();
//定義資源
try (Entry entry = SphU.entry("echo")){
//定義被保護的邏輯
return "i am from port " + port;
} catch (BlockException e) {
//處理被控制的邏輯
return "當前訪問被控制了";
}
}
private void initFlowRules() {
List<FlowRule> rules = new ArrayList<>();
FlowRule rule = new FlowRule();
//關聯域名
rule.setResource("echo");
//設定限流閾值類型
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
//設置限流閾值
rule.setCount(2);
//設置流控針對的調用來源,default為不區分來源
rule.setLimitApp("default");
rules.add(rule);
FlowRuleManager.loadRules(rules);
}
3.3 sentinle控制台使用
訪問:https://github.com/alibaba/Sentinel/releases。進行對應版本下載,可下載jar包,也可下載exe文件
3.3.1 客戶端接入控制台
客戶端添加控制台配置后重啟服務,若未開啟自動心跳, 則服務資源后調用后,才能在控制台看到具體服務。sentinel是延遲加載的。
project:
name: 服務名
// application.yml中添加配置
sentinel:
dashboard:
server: sentinel控制台地址
3.4 sentinel各規則詳解
3.4.1流控- QPS(每秒查詢率)流量控制
sentinel實現流控的原理是監控應用流量的 QPS 或並發線程數等指標,當達到指定的閾值時對流量進行控制,以避免被瞬時的流量高峰沖垮,從而保障應用的高可用性。
3.4.1.1 不針對來源進行流控
針對來源選項值當為default,代表不針對來源。即對所有微服務都生效。
3.4.1.2 針對特定來源流控
對於一個系統而言,不同的請求來源,很有可能會設置不同的規則。
例如對於商品搜索結果頁面,針對PC端指定一個規則,針對小程序端指定一個規則。
-
添加依賴
-
聲明配置bean
@Component public class MyRequestOriginParser implements RequestOriginParser { @Override public String parseOrigin(HttpServletRequest httpServletRequest) { // 從請求路徑或者請求頭中獲取請求來源信息 // 此處以請求頭為例 String origin = httpServletRequest.getParameter("origin"); if (StringUtils.isBlank(origin)) { throw new IllegalArgumentException("origin must be exist"); } return origin; } }
-
添加配置類
@Configuration @ConditionalOnWebApplication( type = ConditionalOnWebApplication.Type.SERVLET ) @ConditionalOnClass({CommonFilter.class}) @ConditionalOnProperty( name = {"spring.cloud.sentinel.enabled"}, matchIfMissing = true ) //@EnableConfigurationProperties({SentinelProperties.class}) // 從sentinel源碼中copy出來 public class SentinelWebAutoConfiguration { //private static final Logger log = LoggerFactory.getLogger(com.alibaba.cloud.sentinel.SentinelWebAutoConfiguration.class); // @Autowired // private SentinelProperties properties; @Autowired private Optional<UrlCleaner> urlCleanerOptional; @Autowired private Optional<UrlBlockHandler> urlBlockHandlerOptional; @Autowired private Optional<RequestOriginParser> requestOriginParserOptional; public SentinelWebAutoConfiguration() { } @PostConstruct public void init() { this.urlBlockHandlerOptional.ifPresent(WebCallbackManager::setUrlBlockHandler); this.urlCleanerOptional.ifPresent(WebCallbackManager::setUrlCleaner); this.requestOriginParserOptional.ifPresent(WebCallbackManager::setRequestOriginParser); } @Bean @ConditionalOnProperty( name = {"spring.cloud.sentinel.filter.enabled"}, matchIfMissing = true ) public FilterRegistrationBean sentinelFilter() { FilterRegistrationBean<Filter> registration = new FilterRegistrationBean(); // com.alibaba.cloud.sentinel.SentinelProperties.Filter filterConfig = this.properties.getFilter(); // if (filterConfig.getUrlPatterns() == null || filterConfig.getUrlPatterns().isEmpty()) { // List<String> defaultPatterns = new ArrayList(); // defaultPatterns.add("/*"); // filterConfig.setUrlPatterns(defaultPatterns); // } // registration.addUrlPatterns((String[])filterConfig.getUrlPatterns().toArray(new String[0])); Filter filter = new CommonFilter(); registration.setFilter(filter); registration.setOrder(-2147483648); //registration.setOrder(filterConfig.getOrder()); registration.addInitParameter("HTTP_METHOD_SPECIFY", String.valueOf(false)); //log.info("[Sentinel Starter] register Sentinel CommonFilter with urlPatterns: {}.", filterConfig.getUrlPatterns()); return registration; } }
-
在控制台增加流控規則,指定來源

-
訪問資源增加origin=pc,才會被限流,不是pc時可以任意訪問。例如:
http://localhost:8083/info?origin=pc
-
3.4.2 流控-並發線程控制
並發線程數限流用於保護業務線程數不被耗盡。
例如,當下游服務出現響應時間延長,則會導致上游服務吞吐量下降和更多的線程數占用,極端情況下甚至導致線程池耗盡。
為了避免這種情況,業內比較常見的解決方案是線程隔離(hystrix)。給不同的業務邏輯分配不同的線程池。但是這種方案會造成大量的線程上下文切換,嚴重影響效率。
sentinle實現原理:統計請求上下文線程數、超出閾值,新的請求被拒絕,效果當前與信號隔離。
// 測試,在控制台增加並發線程控制規則,可以發現有一部分請求會被隔離掉,這是因為超過了線程的閾值。
public class MyThread implements Runnable {
@Override
public void run() {
RestTemplate restTemplate = new RestTemplate();
String forObject = restTemplate.getForObject("http://localhost:8083/info", String.class);
System.out.println(forObject);
}
}
public class ThreadTest {
public static void main(String[] args) throws InterruptedException {
//線程池
ExecutorService pool = Executors.newCachedThreadPool();
for(int i=0;i<100;i++){
MyThread myThread = new MyThread();
pool.execute(myThread);
}
}
}
3.4.3 流控-流控模式、流控效果
- QPS關聯模式-快速失敗:場景為兩個方法會產生性能上相互影響,如讀方法和寫方法。若寫方法優先級高,在定義讀方法流控規則時,將關聯資源定為寫方法,當寫方法操作超過閾值,會限制讀方法。
- QPS鏈路模式快速失敗:鏈路即從指定入口訪問的資源達到閾值則限流
流控效果:
-
快速失敗:達到限流規則,不允許進行訪問
-
warmUp:冷啟動/預熱,對流量的增加是逐步增加的,適用於處理突發性流量。
threshold (閾值)/coldFactor(冷加載因子,默認為3)=最初的閾值
,接着最初的閾值*預熱時長
來最終實現限流。 -
排隊等待/勻速器:讓請求勻速通過,適用處理間隔性突發的流量。
原理:如果當前請求距離上個通過的請求通過的時間間隔不小於預設值,則讓當前請求通過;
否則,計算當前請求的預期通過時間,如果該請求的預期通過時間小於規則預設的 timeout 時間,則該請求會等待直到預設時間到來通過(排隊等待處理);
若預期的通過時間超出最大排隊時長,則直接拒接這個請求。

3.4.4 熔斷降級
避免某個接口異常,造成請求堆積,導致系統雪崩。
1. 熔斷降級策略:
3.4.4.1 熔斷策略-平均響應時間:rt
當 1s 內持續進入 5 個請求,對應時刻的平均響應時間超過閾值(count
,以 ms 為單位),那么會在下一個時間窗口(DegradeRule
中的 timeWindow
,以 s 為單位)之內,對這個方法的調用都會自動地熔斷
3.4.4.2 熔斷策略-異常比例
當資源的每秒請求量 >= 5,並且每秒異常總數占通過量的比值超過閾值(每秒異常總數/通過量 > 閾值
)之后,資源進入降級狀態,即在下一個時間窗口,對這個方法的調用自動熔斷。異常比率的閾值范圍是 [0.0, 1.0]
,代表 0% - 100%
3.4.4.3 熔斷策略-異常數
當資源近 1 分鍾的異常數目超過閾值之后會進行熔斷。注意由於統計是分鍾級別的,若 timeWindow
小於 60s,則結束熔斷狀態后仍可能再進入熔斷狀態。
3.4.5 熱點參數限流
熱點就是經常被訪問的數據,也叫熱數據。
熱點參數限流會統計訪問時所帶的參數,根據定義的規則進行參數級別的限流。也是一種流量控制。
=sentinel熱點參數限流實現原理:Sentinel 利用 LRU 策略統計最近最常訪問的熱點參數,結合令牌桶算法來進行參數級別的流控。
舉例:對id為1的參數進行限流,那么在訪問時,生效的級別只會在id為1時,當id為其他值時不會進行限流控制。

3.4.5.1 熱點參數位限流

上述配置聲明:對於hot資源通過QPS限流模式對訪問路徑上第一個參數,每秒只能訪問一次。
3.4.5.1 熱點參數值限流

上述內容聲明:對於hot資源通過QPS限流模式對訪問路徑上第一個參數,每秒只能訪問一次。但 當參數值2,每秒能訪問兩次。當參數值3,每秒能訪問10000次。
3.4.6 系統自適應限流
文檔:https://github.com/alibaba/Sentinel/wiki/系統自適應限流
3.4.6.1 系統保護簡介
sentinel從 應用的load、cpu利用率、總體平均rt、入口QPS和並發線程幾個維度的監控指標,通過自適應的流控策略,讓系統的入口流量和系統的負載達到一個平衡。
系統保護目的:1. 保證系統不會拖垮 2.系統穩定的前提下,保證系統的吞吐量
以往的系統保護思路:根據硬指標,即load來做系統過載的保護,當系統負載高於閾值,就會禁止或減少流量的進入,load好轉即恢復流量的進入。
以往設計思路的弊端:
- load是一個結果,根據load 的情況來調節流量的通過率會有延遲性
- 恢復慢:當下游應用異常導致RT過高,從而load達到了一個很高的點,當下游應用恢復,這是load應然很高,通過率依然還在受限制
sentinel系統保護的思路:
- 根據系統能處理的請求和允許進來的請求做平衡,而不是通過一個間接的指標(load)做限流。
- 在sentinel系統保護的做法中,load是作用啟動自適應保護因子,而允許通過的流量由處理請求的能力,即請求的響應時間以及當前系統正在處理的請求速率來決定。
3.4.6.2 系統保護-規則
系統保護規則是從應用級別的入口流量做控制的,並且僅對入口流量生效(進入應用的流量EntryType.IN
,如web服務或Dubbo服務端接收的請求)。
從單台機器的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 達到閾值即觸發系統保護
3.4.6.3 系統保護原理
3.5 動態規則、規則持久化
Sentinel默認會把規則信息保存到內存中,當服務重啟之后,規則就會丟失。可以將規存儲在文件、數據庫或者配置中心當中。
3.5.1 規則推送模式
- pull(拉)模式:客戶端定期輪詢拉取規則,規則持久化,但實時性不能保證,拉取過於頻繁還有性能問題。
- push(推)模式:規則中心統計推送,客戶端通過注冊監聽器的方式監聽變化,可以使用Nacos、zookeeper等配置中心。這種方式有更好的實時性和以執行的保證。推薦使用
4. 負載
文檔:https://juejin.im/post/6844903608371118094
在衡量服務器的性能時,經常會涉及到幾個指標,load(機器負載)、cpu、mem、qps、rt等。每個指標都有其獨特的意義,很多時候在線上出現問題時,往往會伴隨着某些指標的異常。
負載(load)是linux機器的一個重要指標,直觀了反應了機器當前的狀態。
在UNIX系統中,系統負載是對當前CPU工作量的度量,被定義為特定時間間隔內運行隊列中的平均線程數。load average 表示機器一段時間內的平均load。這個值越低越好。負載過高會導致機器無法處理其他請求及操作,甚至導致死機。Linux的負載高,主要是由於CPU使用、內存使用、IO消耗三部分構成。任意一項使用過多,都將導致服務器負載的急劇攀升。
load值代表的是對應時間內的jobs的平均數量,比如load1就表示過去1分鍾內的jobs數量的平均值。
4.1 查看機器負載
在Linux機器上,有多個命令都可以查看機器的負載信息。其中包括uptime
、top
、w
等。
- uptime:能夠打印系統總共運行了多長時間和系統的平均負載。
➜ ~ uptime
13:29 up 23:41, 3 users, load averages: 1.74 1.87 1.97
現在時間、系統已經運行了多長時間、目前有多少登陸用戶、系統在過去的1分鍾、5分鍾和15分鍾內的平均負載。
-
w:能夠打印當前時間,系統啟動到現在的時間,登錄用戶的數目,系統在最近1分鍾、5分鍾和15分鍾的平均負載。然后是每個用戶的各項數據,項目顯示順序如下:登錄帳號、終端名稱、遠 程主機名、登錄時間、空閑時間、JCPU、PCPU、當前正在運行進程的命令行]。
-
top:是Linux下常用的性能分析工具,能夠實時顯示系統中各個進程的資源占用狀況,類似於Windows的任務管理器。
4.2 機器正常負載范圍
沒有一個明確的指標。
可以根據自己機器的實際情況,建立一個指標的基線(如近一個月的平均值),只要日常的load在基線上下范圍內不太大都可以接收,如果差距太多可能就要人為介入檢查了。
阮一峰對此的建議為:
當系統負荷持續大於0.7,你必須開始調查了,問題出在哪里,防止情況惡化。
當系統負荷持續大於1.0,你必須動手尋找解決辦法,把這個值降下來。
當系統負荷達到5.0,就表明你的系統有很嚴重的問題,長時間沒有響應,或者接近死機了。你不應該讓系統達到這個值。注:這里的指標是但cpu的,如果是多喝系統,需乘上cpu的數量
4.3 如何降低負荷
CPU使用、內存使用、IO消耗都可能導致負載高。如果是軟件問題,有可能由於Java中的某些線程被長時間占用、大量內存持續占用等導致。建議從以下幾個方面排查代碼問題:
- 是否有內存泄露導致頻繁GC
- 是否有死鎖發生
- 是否有大字段的讀寫
- 會不會是數據庫操作導致的,排查SQL語句問題
- 死循環
這里還有個建議,如果發現線上機器Load飆高,可以考慮先把堆棧內存dump下來后,進行重啟,暫時解決問題,然后再考慮回滾和排查問題。
4.4 排查思路
1、使用uptime查看當前load,發現load飆高。
➜ ~ uptime
13:29 up 23:41, 3 users, load averages: 10 10 10
復制代碼
2、使用top命令,查看占用CPU較高的進程ID。
➜ ~ top
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1893 admin 20 0 7127m 2.6g 38m S 181.7 32.6 10:20.26 java
復制代碼
發現PID為1893的進程占用CPU 181%。而且是一個Java進程,基本斷定是軟件問題。
3、使用 top
命令,查看具體是哪個線程占用率較高
➜ ~ top -Hp 1893
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
4519 admin 20 0 7127m 2.6g 38m R 18.6 32.6 0:40.11 java
復制代碼
4、使用printf
命令查看這個線程的16進制
➜ ~ printf %x 4519
11a7
復制代碼
5、使用jstack
命令查看當前線程正在執行的方法。(Java命令學習系列(二)——Jstack)
➜ ~ jstack 1893 |grep -A 200 11a7
"thread-5" #500 daemon prio=10 os_prio=0 tid=0x00007f632314a800 nid=0x11a2 runnable [0x000000005442a000]
java.lang.Thread.State: RUNNABLE
at sun.misc.URLClassPath$Loader.findResource(URLClassPath.java:684)
at sun.misc.URLClassPath.findResource(URLClassPath.java:188)
at java.net.URLClassLoader$2.run(URLClassLoader.java:569)
at java.net.URLClassLoader$2.run(URLClassLoader.java:567)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findResource(URLClassLoader.java:566)
at org.hibernate.validator.internal.xml.ValidationXmlParser.getInputStreamForPath(ValidationXmlParser.java:248)
at com.hollis.test.util.BeanValidator.validate(BeanValidator.java:30)
復制代碼
從上面的線程的棧日志中,可以發現,當前占用CPU較高的線程正在執行我代碼的com.hollis.test.util.BeanValidator.validate(BeanValidator.java:30)類。那么就可以去排查這個類是否用法有問題了。
6、還可以使用jstat(Java命令學習系列(四)——jstat)來查看GC情況,看看是否有頻繁FGC,然后再使用jmap(Java命令學習系列(三)——Jmap)來dump內存,查看是否存在內存泄露。