參考鏈接:
建立Springboot父專案,只需要一個pom就可以了(AugJungle:生產專案單獨部署)
POM:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.jungle.springclouddemo</groupId>
<artifactId>springclouddemo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springclouddemo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
pom新增SpringCloudAlibaba依賴
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.1.1.BUILD-SNAPSHOT</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
#出現問題再引入SpringCloud的包吧,個人感覺不用引入,but寫這個的專案時引入了。。。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR6</version>
<type>pom</type>
<scope>import</scope>
</dependency>
建立子模組後修改子模組中的pom裏面的parent標籤,修改爲父專案中的值
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.jungle.springclouddemo</groupId>
<artifactId>springclouddemo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.jungle</groupId>
<artifactId>news</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>news</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
</dependencies>
<build>
</build>
</project>
文件地址:https://nacos.io/zh-cn/docs/quick-start.html
Windows版Nacos Server直接下載地址:https://download.csdn.net/download/qq_35742333/12697057
下載後進入bin資料夾下執行.\startup.cmd -m standalone
-m:以單機模式執行
注:部分圖片可能無法下載,可以存取我的個人主頁
[外連圖片轉存失敗,源站可能有防盜鏈機制 機製,建議將圖片儲存下來直接上傳(img-X0ExcvZf-1597236593840)(https://s3.ap-northeast-1.wasabisys.com/img.tw511.com/202008/20200810174100vegunxjesst.png)]
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
maven出現問題:
Could not find artifact com.alibaba.cloud:spring-cloud-starter-alibaba-nacos-discovery:pom:unknown in aliyun (https://maven.aliyun.com/repository/public)
Non-resolvable parent POM for com.jungle:news:0.0.1-SNAPSHOT: Could not find artifact com.jungle:springclouddemo:pom:0.0.1-SNAPSHOT and 'parent.relativePath' points at no local POM @ line 5, column 13
問題解決:
父專案新增
不是必須
<modules>
<module>news</module>
</modules>
必須
父專案記得配一個pom
子專案最好配一個jar
在application.properties檔案新增spring.profiles.active=dev
新建application-dev.yml檔案,新增設定
server:
port: 18081
spring:
cloud:
nacos:
discovery:
#命令空間ID
namespace: d2f4bb90-17ac-4bc1-9e32-8734e722eb0c
#叢集
cluster-name: newMachine
#分組
group: 1
#數據源,可以用來自定義一些數據,供服務進行呼叫,例如兩個服務版本不一致,用來讓不同版本的呼叫不同的服務
metadata:
testKey1: testValue1
testKey2: testValue2
#Nacos註冊服務的IP及埠號
server-addr: 127.0.0.1:8848
application:
name: service-news
此時執行子專案,已經可以在Nacos Server上看見註冊的子專案執行服務
服務發現物件,可以用來進行手動服務註冊
@Resource
private DiscoveryClient discoveryClient;
List<ServiceInstance> instances = discoveryClient.getInstances("service-news");
System.out.println("instances.size():"+instances.size());
ServiceInstance serviceInstance = instances.get(new Random().nextInt(instances.size()));
URI uri = serviceInstance.getUri();
String str = this.restTemplate.getForObject(uri +"/user/list",String.class);
參考文件:https://cloud.spring.io/spring-cloud-netflix/reference/html/
參考文件-ribbon細節:https://cloud.spring.io/spring-cloud-netflix/multi/multi_spring-cloud-ribbon.html
Ribbon是Netflix發佈的雲中間層服務開源專案,主要功能是提供用戶端(消費者方)負載均衡演算法。Ribbon用戶端元件提供一系列完善的設定項,如,連線超時,重試等。簡單的說,Ribbon是一個用戶端負載均衡器,我們可以在組態檔中列出load Balancer後面所有的機器,Ribbon會自動的幫助你基於某種規則(如簡單輪詢,隨機連線等)去連線這些機器,我們也很容易使用Ribbon實現自定義的負載均衡演算法。
The following table shows the beans that Spring Cloud Netflix provides by default for Ribbon:
Bean Type | Bean Name | Class Name | 作用 |
---|---|---|---|
IClientConfig |
ribbonClientConfig |
DefaultClientConfigImpl |
讀取設定 |
IRule |
ribbonRule |
ZoneAvoidanceRule |
負載均衡規則,選擇範例 |
IPing |
ribbonPing |
DummyPing |
篩選掉ping不通的範例 |
ServerList<Server> |
ribbonServerList |
ConfigurationBasedServerList |
交給Ribbon的範例列表 |
ServerListFilter<Server> |
ribbonServerListFilter |
ZonePreferenceServerListFilter |
過濾掉不符合條件的範例 |
ILoadBalancer |
ribbonLoadBalancer |
ZoneAwareLoadBalancer |
Ribbon的入口 |
ServerListUpdater |
ribbonServerListUpdater |
PollingServerListUpdater |
更新交給Ribbon的List的策略 |
@EnableDiscoveryClient
RestTemplate
範例新增 @LoadBalanced
@SpringBootApplication
@EnableDiscoveryClient
public class NacosConsumerApplication {
@LoadBalanced
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
@Configuration
@RibbonClient(name = "custom", configuration = CustomConfiguration.class)
public class TestConfiguration {
}
上面意思是,針對custom服務呼叫,選擇CustomConfiguration
類的演算法合適
@RibbonClients(defaultConfiguration = DefaultRibbonConfig.class)
上面意思是,針對所有服務呼叫,選擇DefaultRibbonConfig
類的演算法合適
The CustomConfiguration
class must be a @Configuration
class, but take care that it is not in a @ComponentScan
for the main application context. Otherwise, it is shared by all the @RibbonClients
. If you use @ComponentScan
(or @SpringBootApplication
), you need to take steps to avoid it being included (for instance, you can put it in a separate, non-overlapping package or specify the packages to scan explicitly in the @ComponentScan
).
需要將設定類放到@SpringBootApplication
註解掃描不到的上級選單中,避免衝突
在@SpringBootApplication
不可以掃描到的地方,定義一個Ribbon的設定類
@Configuration
public class RibbonConfig {
@Bean
public IRule getIRule(){
return new BestAvailableRule();
}
}
在@SpringBootApplication
可以掃描到,定義一個Ribbon設定類,用來指定設定的位置
@Configuration
@RibbonClient(name = "custom", configuration = CustomConfiguration.class)
public class TestConfiguration {
}
Starting with version 1.2.0, Spring Cloud Netflix now supports customizing Ribbon clients by setting properties to be compatible with the Ribbon documentation.
This lets you change behavior at start up time in different environments.
The following list shows the supported properties>:
<clientName>.ribbon.NFLoadBalancerClassName
: Should implement ILoadBalancer
<clientName>.ribbon.NFLoadBalancerRuleClassName
: Should implement IRule
<clientName>.ribbon.NFLoadBalancerPingClassName
: Should implement IPing
<clientName>.ribbon.NIWSServerListClassName
: Should implement ServerList
<clientName>.ribbon.NIWSServerListFilterClassName
: Should implement ServerListFilter
Classes defined in these properties have precedence over beans defined by using @RibbonClient(configuration=MyRibbonConfig.class) and the defaults provided by Spring Cloud Netflix. |
|
---|---|
To set the IRule
for a service name called users
, you could set the following properties:
application.yml
users:
ribbon:
NIWSServerListClassName: com.netflix.loadbalancer.ConfigurationBasedServerList
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule
See the Ribbon documentation for implementations provided by Ribbon.
可以通過在nacos server網頁上設定值,來動態調整Ribbon策略
修改Ribbon的設定類,呼叫自定義的設定類
@Configuration
public class RibbonConfig {
@Bean
public IRule getIRule(){
return new WeightRule;
}
}
增加設定類
public class WeightRule extends AbstractLoadBalancerRule {
/**
* 服務發現參數物件,Nacos自帶
*
* @date 2020/08/11
* @see NacosDiscoveryProperties
*/
@Resource
private NacosDiscoveryProperties nacosDiscoveryProperties;
/**
* 設定初始化
*
* @param iClientConfig 我的用戶端設定
* @return
* @author Jiangmanman
* @date 2020/08/11
*/
@Override
public void initWithNiwsConfig(IClientConfig iClientConfig) {
}
@SneakyThrows
@Override
public Server choose(Object key) {
//1.拿到要存取的服務名
//1.1呼叫父類別物件
ILoadBalancer loadBalancer = this.getLoadBalancer();
//1.2轉換成真正的實現類
BaseLoadBalancer baseLoadBalancer = (BaseLoadBalancer)loadBalancer;
//1.3得到載入的服務名稱
String serviceName = baseLoadBalancer.getName();
//2.通過服務名獲得在網頁端往 Nacos Service 設定的權重值的範例
//2.1通過Nacos參數物件獲得服務名
NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
//2.1得到所有的健康的範例
Instance instance = namingService.selectOneHealthyInstance(serviceName);
return new NacosServer(instance);
}
}
優化,可以限定上,僅呼叫相同叢集下的instance(或者更加精確)
//優化,通過服務名在Nacos上面獲得相同叢集下面 下麪的instance
//獲得服務名下所有健康的範例
List<Instance> allInstances = namingService.getAllInstances(serviceName, true);
//獲得當前叢集名
String clusterName = nacosDiscoveryProperties.getClusterName();
//進行比對
List<Instance> clusterInstance = allInstances.stream().filter(instance1 -> {
return Objects.equals(clusterName, instance1);
}).collect(Collectors.toList());
//判斷是否有相鄰的叢集節點
if (CollectionUtils.isEmpty(clusterInstance)) {
//維持原樣
clusterInstance = allInstances;
}
//通過權重演算法從集閤中獲取一個instance,分析前面的 selectOneHealthyInstance 方法,裏面就有實現的過程
Instance weightInstance = Mybalancer.getWeightInstance(clusterInstance);
/**
*這個類是分析 selectOneHealthyInstance 方法的實現得來的
*/
public static class Mybalancer extends Balancer{
public static Instance getWeightInstance(List<Instance> hosts){
return getHostByRandomWeight(hosts);
}
}
注意:上述的優化只是手動實現,其實NamingService裏面的selectOneHealthyInstance方法的過載裏面有對這個的實現,直接呼叫即可
現在就可以通過Nacos Server 在網頁中手動設定權重值,動態改變呼叫者,但是遇到No instances available
,原因是設定裏面進行了分組造成的,將分組去掉後可以正常呼叫,原因不明,有時間在回頭看
Ribbon第一次呼叫比較慢,就是因爲預設實現是懶載入的方式,呼叫後纔會取拉取資訊,可以通過設定來修改這種預設行爲
Each Ribbon named client has a corresponding child application Context that Spring Cloud maintains. This application context is lazily loaded on the first request to the named client. This lazy loading behavior can be changed to instead eagerly load these child application contexts at startup, by specifying the names of the Ribbon clients, as shown in the following example:
application.yml
ribbon:
eager-load:
enabled: true
clients: client1, client2, client3
Feign is a declarative web service client. It makes writing web service clients easier. To use Feign create an interface and annotate it. It has pluggable annotation support including Feign annotations and JAX-RS annotations. Feign also supports pluggable encoders and decoders. Spring Cloud adds support for Spring MVC annotations and for using the same HttpMessageConverters
used by default in Spring Web. Spring Cloud integrates Ribbon and Eureka, as well as Spring Cloud LoadBalancer to provide a load-balanced http client when using Feign.
主要解決用戶端呼叫問題
匯入依賴
To include Feign in your project use the starter with group org.springframework.cloud
and artifact id spring-cloud-starter-openfeign
. See the Spring Cloud Project page for details on setting up your build system with the current Spring Cloud Release Train.
<!--Spring Feign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
設定註解@EnableFeignClients
,呼叫方必須設定,被呼叫方可以不設定
@SpringBootApplication
@EnableFeignClients
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
編寫介面,介面的設定和要呼叫的controller保持一直
@FeignClient("service-user")
//呼叫的方法有,這裏也必須有,保證一致性
@RequestMapping("/news")
public interface UserClient {
@GetMapping("/all")
CommonResult all();
}
注意:
@FeignClient
的介面去呼叫其他服務一定要標準化,例如多個參數必須使用@RequestParam
來註明CommonResult all(User user);
,需要將物件轉變成表單參數,加上@SpringQueryMap
註解編寫設定類
@Configuration
public class FooConfiguration {
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
全域性設定需要在@EnableFeignClients
註解上指定defaultConfiguration 屬性,例如:@EnableFeignClients(defaultConfiguration = UserClient.class)
具備設定則是將@FeignClient("service-user")
變成@FeignClient(name = "service-user",configuration = FeignConfig.class)
開啓整體的預設日誌環境,例如上訴UserClient
類所在的的包
logging:
level:
com.jungle.servicenews.client: debug
開啓日誌,還可以設定其他feign的屬性
feign:
client:
config:
#也可以針對某個包進行設定
default:
connectTimeout: 5000
readTimeout: 5000
#日誌級別:NONE(預設),BASIC,HEADERS,FULL
loggerLevel: FULL
因爲feign預設的是來一次就建立一次連線,可以設定類似連線池的東東
設定如下:
feign:
# client:
# config:
# #也可以針對某個包進行設定
# default:
# connectTimeout: 5000
# readTimeout: 5000
# #日誌級別:NONE(預設),BASIC,HEADERS,FULL
# loggerLevel: FULL
httpclient:
enabled: true
max-connections: 200
max-connections-per-route: 50
connection-timeout: 200000
在github上搜尋spring-cloud-alibaba
在spring官網上的參考資料說明如下所示
[外連圖片轉存失敗,源站可能有防盜鏈機制 機製,建議將圖片儲存下來直接上傳(img-KkE3ZUXp-1597236593842)(https://s3.ap-northeast-1.wasabisys.com/img.tw511.com/202008/20200811195853oxqm5p5exhf.png)]
隨着微服務的流行,服務和服務之間的穩定性變得越來越重要。 Sentinel 以流量爲切入點,從流量控制、熔斷降級、系統負載保護等多個維度保護服務的穩定性。
前置,通過actuator暴露端點資訊,先匯入依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
然後修改組態檔,加入如下
management:
endpoints:
web:
exposure:
#下面 下麪是暴漏所有端點
include: "*"
測試暴漏端點是否成功,開啓網頁存取:IP+埠+/actuator檢視所有暴漏的端點資訊,引入sentinel依賴後,可以直接通過IP:埠/actuator/sentinel
存取檢視sentinel的資訊
引入依賴,如果要在您的專案中引入 Sentinel,使用 group ID 爲 com.alibaba.cloud
和 artifact ID 爲 spring-cloud-starter-alibaba-sentinel
的 starter。
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
下載centinel控制檯,下載地址:https://download.csdn.net/download/qq_35742333/12705492,下載完成直接可以執行java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar
,執行完成直接ip+埠存取,預設java -jar 啓動後的埠爲8080,預設賬戶密碼一致,都爲sentinel
服務與控制檯建立通訊
cloud:
sentinel:
transport:
#控制檯與服務之間通訊的埠,不寫預設也會給個8719
port: 8719
#控制檯位置
dashboard: localhost:8080
測試:服務至少有一次存取,控制檯的網頁纔會有顯示
對介面的呼叫如果出現返回數據爲XML格式,因爲sentinel中整合了com.fastxml.jackson.dataformat的jackson-dataformat-xml.xml優先順序比JSON高,所以先返回XML
解決方式一,去除sentinel依賴包的com.fastxml.jackson-dataformat
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<exclusions>
<exclusion>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</exclusion>
</exclusions>
</dependency>
解決方式二,Controller層增加返回格式指定@GetMapping(value = "/all",produces = MediaType.APPLICATION_JSON_VALUE)
DEGRADE_GRADE_RT
):當 1s 內持續進入 N 個請求,對應時刻的平均響應時間(秒級)均超過閾值(count
,以 ms 爲單位),那麼在接下的時間視窗(DegradeRule
中的 timeWindow
,以 s 爲單位)之內,對這個方法的呼叫都會自動地熔斷(拋出 DegradeException
)。注意 Sentinel 預設統計的 RT 上限是 4900 ms,超出此閾值的都會算作 4900 ms,若需要變更此上限可以通過啓動設定項 -Dcsp.sentinel.statistic.max.rt=xxx
來設定。DEGRADE_GRADE_EXCEPTION_RATIO
):當資源的每秒請求量 >= N(可設定),並且每秒異常總數佔通過量的比值超過閾值(DegradeRule
中的 count
)之後,資源進入降級狀態,即在接下的時間視窗(DegradeRule
中的 timeWindow
,以 s 爲單位)之內,對這個方法的呼叫都會自動地返回。異常比率的閾值範圍是 [0.0, 1.0]
,代表 0% - 100%。DEGRADE_GRADE_EXCEPTION_COUNT
):當資源近 1 分鐘的異常數目超過閾值之後會進行熔斷。注意由於統計時間視窗是分鐘級別的,若 timeWindow
小於 60s,則結束熔斷狀態後仍可能再進入熔斷狀態。在需要進行限流的方法上加上註解@SentinelResource("myMethod")
注意:若需要設定例外項或者使用叢集維度流控,則傳入的參數只支援基本型別。
如果還是測試不好
組態檔將filter幹掉
cloud:
#sentinel:
#transport:
#控制檯與服務之間通訊的埠,不寫預設也會給個8719
# port: 8719
#控制檯位置
#dashboard: localhost:8080
filter:
enabled: false
PS:感覺熱點參數限流這裏測試沒成功。。。QAQ
下表顯示當應用的 ApplicationContext
中存在對應的Bean的型別時,會進行自動化設定:
存在Bean的型別 | 操作 | 作用 |
---|---|---|
UrlCleaner |
WebCallbackManager.setUrlCleaner(urlCleaner) |
資源清理(資源(比如將滿足 /foo/:id 的 URL 都歸到 /foo/* 資源下)) |
UrlBlockHandler |
WebCallbackManager.setUrlBlockHandler(urlBlockHandler) |
自定義限流處理邏輯 |
RequestOriginParser |
WebCallbackManager.setRequestOriginParser(requestOriginParser) |
設定來源資訊 |
Spring Cloud Alibaba Sentinel 提供了這些設定選項:
設定項 | 含義 | 預設值 |
---|---|---|
spring.application.name or project.name |
Sentinel專案名 | |
spring.cloud.sentinel.enabled |
Sentinel自動化設定是否生效 | true |
spring.cloud.sentinel.eager |
是否提前觸發 Sentinel 初始化 | false |
spring.cloud.sentinel.transport.port |
應用與Sentinel控制檯互動的埠,應用本地會起一個該埠佔用的HttpServer | 8719 |
spring.cloud.sentinel.transport.dashboard |
Sentinel 控制檯地址 | |
spring.cloud.sentinel.transport.heartbeat-interval-ms |
應用與Sentinel控制檯的心跳間隔時間 | |
spring.cloud.sentinel.transport.client-ip |
此設定的用戶端IP將被註冊到 Sentinel Server 端 | |
spring.cloud.sentinel.filter.order |
Servlet Filter的載入順序。Starter內部會構造這個filter | Integer.MIN_VALUE |
spring.cloud.sentinel.filter.url-patterns |
數據型別是陣列。表示Servlet Filter的url pattern集合 | /* |
spring.cloud.sentinel.filter.enabled |
Enable to instance CommonFilter | true |
spring.cloud.sentinel.metric.charset |
metric檔案字元集 | UTF-8 |
spring.cloud.sentinel.metric.file-single-size |
Sentinel metric 單個檔案的大小 | |
spring.cloud.sentinel.metric.file-total-count |
Sentinel metric 總檔案數量 | |
spring.cloud.sentinel.log.dir |
Sentinel 日誌檔案所在的目錄 | |
spring.cloud.sentinel.log.switch-pid |
Sentinel 日誌檔名是否需要帶上 pid | false |
spring.cloud.sentinel.servlet.block-page |
自定義的跳轉 URL,當請求被限流時會自動跳轉至設定好的 URL | |
spring.cloud.sentinel.flow.cold-factor |
WarmUp 模式中的 冷啓動因子 | 3 |
spring.cloud.sentinel.zuul.order.pre |
SentinelZuulPreFilter 的 order | 10000 |
spring.cloud.sentinel.zuul.order.post |
SentinelZuulPostFilter 的 order | 1000 |
spring.cloud.sentinel.zuul.order.error |
SentinelZuulErrorFilter 的 order | -1 |
spring.cloud.sentinel.scg.fallback.mode |
Spring Cloud Gateway 流控處理邏輯 (選擇 redirect or response ) |
|
spring.cloud.sentinel.scg.fallback.redirect |
Spring Cloud Gateway 響應模式爲 ‘redirect’ 模式對應的重定向 URL | |
spring.cloud.sentinel.scg.fallback.response-body |
Spring Cloud Gateway 響應模式爲 ‘response’ 模式對應的響應內容 | |
spring.cloud.sentinel.scg.fallback.response-status |
Spring Cloud Gateway 響應模式爲 ‘response’ 模式對應的響應碼 | 429 |
spring.cloud.sentinel.scg.fallback.content-type |
Spring Cloud Gateway 響應模式爲 ‘response’ 模式對應的 content-type | application/json |
Note | 請注意。這些設定只有在 Servlet 環境下纔會生效,RestTemplate 和 Feign 針對這些設定都無法生效 | ||||
---|---|---|---|---|---|
範例:
//可以修改存取的URL
@Component
public class MyUrlCleaner implements UrlCleaner {
@Override
public String clean(String s) {
System.out.println(s);
//修改來源的URL
return s+":admin";
}
}
@Component
public class MyUrlBlockHandler implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
response.setContentType("application/json;charset=utf8");
response.setCharacterEncoding("utf-8");
response.getWriter().write("{\"msg\":\"流控\"}");
response.getWriter().flush();
}
}
和上一樣,只改變實現介面RequestOriginParser,可以通過request.getParameter得到請求攜帶的參數
注意:註解方式埋點不支援 private 方法。
@SentinelResource
用於定義資源,並提供可選的例外處理和 fallback 設定項。 @SentinelResource
註解包含以下屬性:
value
:資源名稱,必需項(不能爲空)entryType
:entry 型別,可選項(預設爲 EntryType.OUT
)blockHandler
/ blockHandlerClass
: blockHandler
對應處理 BlockException
的函數名稱,可選項。blockHandler 函數存取範圍需要是 public
,返回型別需要與原方法相匹配,參數型別需要和原方法相匹配並且最後加一個額外的參數,型別爲 BlockException
。blockHandler 函數預設需要和原方法在同一個類中。若希望使用其他類的函數,則可以指定 blockHandlerClass
爲對應的類的 Class
物件,注意對應的函數必需爲 static 函數,否則無法解析。Throwable
型別的參數用於接收對應的異常。fallbackClass
爲對應的類的 Class
物件,注意對應的函數必需爲 static 函數,否則無法解析。Throwable
型別的參數用於接收對應的異常。fallbackClass
爲對應的類的 Class
物件,注意對應的函數必需爲 static 函數,否則無法解析。exceptionsToIgnore
(since 1.6.0):用於指定哪些異常被排除掉,不會計入異常統計中,也不會進入 fallback 邏輯中,而是會原樣拋出。注:1.6.0 之前的版本 fallback 函數只針對降級異常(
DegradeException
)進行處理,不能針對業務異常進行處理。
特別地,若 blockHandler 和 fallback 都進行了設定,則被限流降級而拋出 BlockException
時只會進入 blockHandler
處理邏輯。若未設定 blockHandler
、fallback
和 defaultFallback
,則被限流降級時會將 BlockException
直接拋出(若方法本身未定義 throws BlockException 則會被 JVM 包裝一層 UndeclaredThrowableException
)。
範例:
public class TestService {
// 對應的 `handleException` 函數需要位於 `ExceptionUtil` 類中,並且必須爲 static 函數.
@SentinelResource(value = "test", blockHandler = "handleException", blockHandlerClass = {ExceptionUtil.class})
public void test() {
System.out.println("Test");
}
// 原函數
@SentinelResource(value = "hello", blockHandler = "exceptionHandler", fallback = "helloFallback")
public String hello(long s) {
return String.format("Hello at %d", s);
}
// Fallback 函數,函數簽名與原函數一致或加一個 Throwable 型別的參數.
public String helloFallback(long s) {
return String.format("Halooooo %d", s);
}
// Block 例外處理函數,參數最後多一個 BlockException,其餘與原函數一致.
public String exceptionHandler(long s, BlockException ex) {
// Do some log here.
ex.printStackTrace();
return "Oops, error occurred at " + s;
}
}
從 1.4.0 版本開始,註解方式定義資源支援自動統計業務異常,無需手動呼叫 Tracer.trace(ex)
來記錄業務異常。Sentinel 1.4.0 以前的版本需要自行呼叫 Tracer.trace(ex)
來記錄業務異常。
若您是通過 Spring Cloud Alibaba 接入的 Sentinel,則無需額外進行設定即可使用 @SentinelResource
註解。
若您的應用使用了 Spring AOP(無論是 Spring Boot 還是傳統 Spring 應用),您需要通過設定的方式將 SentinelResourceAspect
註冊爲一個 Spring Bean:
@Configuration
public class SentinelAspectConfiguration {
@Bean
public SentinelResourceAspect sentinelResourceAspect() {
return new SentinelResourceAspect();
}
}
我們提供了 Spring AOP 的範例,可以參見 sentinel-demo-annotation-spring-aop。
若您的應用直接使用了 AspectJ,那麼您需要在 aop.xml
檔案中引入對應的 Aspect:
<aspects>
<aspect name="com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect"/>
</aspects>
Sentinel 適配了 Feign 元件。如果想使用,除了引入 spring-cloud-starter-alibaba-sentinel
的依賴外還需要 2 個步驟:
feign.sentinel.enabled=true
spring-cloud-starter-openfeign
依賴使 Sentinel starter 中的自動化設定類生效:<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
這是一個 FeignClient
的簡單使用範例:
@FeignClient(name = "service-provider", fallback = EchoServiceFallback.class, configuration = FeignConfiguration.class)
public interface EchoService {
@RequestMapping(value = "/echo/{str}", method = RequestMethod.GET)
String echo(@PathVariable("str") String str);
}
class FeignConfiguration {
@Bean
public EchoServiceFallback echoServiceFallback() {
return new EchoServiceFallback();
}
}
class EchoServiceFallback implements EchoService {
@Override
public String echo(@PathVariable("str") String str) {
return "echo fallback";
}
}
Spring Cloud Alibaba Sentinel 支援對 RestTemplate
的服務呼叫使用 Sentinel 進行保護,在構造 RestTemplate
bean的時候需要加上 @SentinelRestTemplate
註解。
@Bean
@SentinelRestTemplate(blockHandler = "handleException", blockHandlerClass = ExceptionUtil.class)
public RestTemplate restTemplate() {
return new RestTemplate();
}
@SentinelRestTemplate
註解的屬性支援限流(blockHandler
, blockHandlerClass
)和降級(fallback
, fallbackClass
)的處理。
其中 blockHandler
或 fallback
屬性對應的方法必須是對應 blockHandlerClass
或 fallbackClass
屬性中的靜態方法。
該方法的參數跟返回值跟 org.springframework.http.client.ClientHttpRequestInterceptor#interceptor
方法一致,其中參數多出了一個 BlockException
參數用於獲取 Sentinel 捕獲的異常。
比如上述 @SentinelRestTemplate
註解中 ExceptionUtil
的 handleException
屬性對應的方法宣告如下:
public class ExceptionUtil {
public static ClientHttpResponse handleException(HttpRequest request, byte[] body, ClientHttpRequestExecution execution, BlockException exception) {
...
}
}
Note | 應用啓動的時候會檢查 @SentinelRestTemplate 註解對應的限流或降級方法是否存在,如不存在會拋出異常 |
---|---|
@SentinelRestTemplate
註解的限流(blockHandler
, blockHandlerClass
)和降級(fallback
, fallbackClass
)屬性不強制填寫。
當使用 RestTemplate
呼叫被 Sentinel 熔斷後,會返回 RestTemplate request block by sentinel
資訊,或者也可以編寫對應的方法自行處理返回資訊。這裏提供了 SentinelClientHttpResponse
用於構造返回資訊。
Sentinel RestTemplate 限流的資源規則提供兩種粒度:
httpmethod:schema://host:port/path
:協定、主機、埠和路徑httpmethod:schema://host:port
:協定、主機和埠Note | 以 https://www.taobao.com/test 這個 url 並使用 GET 方法爲例。對應的資源名有兩種粒度,分別是 GET:https://www.taobao.com 以及 GET:https://www.taobao.com/test |
---|---|
To include Spring Cloud Gateway in your project, use the starter with a group ID of org.springframework.cloud
and an artifact ID of spring-cloud-starter-gateway
. See the Spring Cloud Project page for details on setting up your build system with the current Spring Cloud Release Train.
If you include the starter, but you do not want the gateway to be enabled, set spring.cloud.gateway.enabled=false
.
Spring Cloud Gateway is built on Spring Boot 2.x, Spring WebFlux, and Project Reactor. As a consequence, many of the familiar synchronous libraries (Spring Data and Spring Security, for example) and patterns you know may not apply when you use Spring Cloud Gateway. If you are unfamiliar with these projects, we suggest you begin by reading their documentation to familiarize yourself with some of the new concepts before working with Spring Cloud Gateway. | |
---|---|
Spring Cloud Gateway requires the Netty runtime provided by Spring Boot and Spring Webflux. It does not work in a traditional Servlet Container or when built as a WAR. | |
---|---|
大致就是如何使用,以及注意gateeay不是傳統的springweb專案,所以不要匯入亂七八糟的web包
首先導包
因爲閘道器也需要註冊到Nacos裏面去,所以需要匯入Nacos的包
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
Nacos爲啥不要寫版本號,因爲交給dependencyManagement標籤來管理了,所以需要匯入
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.1.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
匯入閘道器自身的包spring.cloud.gateway,然後依賴於springboot以及自身受dependencyManagement裏面的rg.springframework.cloud來管理自身的版本,所以這裏直接給出完整的pom設定,Jiang慢慢應該懂得
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.jungle.gateway</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR6</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.1.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
將網關注冊到Nacos Server裏面,以及開啓Gateway,設定如下
spring:
cloud:
gateway:
#開啓閘道器的自動路由方式,也就是通過url的服務名稱去找到對應的服務
discovery:
locator:
enabled: true
nacos:
discovery:
# #命令空間ID
namespace: d2f4bb90-17ac-4bc1-9e32-8734e722eb0c
# #叢集
cluster-name: newMachine
# group: 1
# metadata:
# testKey1: testValue1
# testKey2: testValue2
server-addr: 127.0.0.1:8848
server:
port: 9001
啓動報錯org.springframework.cloud.gateway.config.GatewayAutoConfiguration$NettyConfiguration.gatewayHttpClient(GatewayAutoConfiguration.java:622)
,反正是依賴有問題,直接ctrl+左鍵點選gateway的依賴,檢視這個吊毛到底要毛線依賴,發現依賴的是2.3.0的springboot版本,改了下,ok,然後通過IP:9001/服務名/news/testTemplate
測試成功
最有用的是謂詞工廠,可以通過這個來指定什麼請求什麼時間段存取什麼服務
server:
port: 9001
spring:
application:
name: spring-cloud-gateway
cloud:
gateway:
enabled: true
routes:
# 自定義唯一標識
- id: damkdamk54da1
# 呼叫自己服務可以使用:lb://服務名
# uri: http://baidu.com
uri: lb://service-news
predicates:
#謂詞工廠,例如這裏針對COOKIE名爲mycookie,值爲mycookieValue的到上面的uri中
#使用最多的是 - Path
- Path=/news/**
- id: httpbin_route
uri: https://httpbin.org
predicates:
- Path=/httpbin/**
nacos:
discovery:
# #命令空間ID
namespace: d2f4bb90-17ac-4bc1-9e32-8734e722eb0c
# #叢集
cluster-name: newMachine
# group: 1
# metadata:
# testKey1: testValue1
# testKey2: testValue2
server-addr: 127.0.0.1:8848
區域性設定可以在組態檔中使用,也就是在routes
下面 下麪增加
filters:
- RewritePath=/httpbin/(?<segment>.*), /$\{segment}
全域性的寫出來丟到容器中,或者在組態檔中gateway的下面 下麪增加default-filter
也可以
!!!注意:攔截器弄好了千萬記得加
@Component
註解放到容器中,這是我弄完Filter後得到的經驗教訓
可以模仿SpringCloudGateway的實現自己來寫一個,例如在idea中使用ctrl+N搜尋類AddRequestHeaderGatewayFilterFactory
實現過程如下
可以看出AddRequestHeaderGatewayFilterFactory
類繼承了 extends AbstractAuthenticationGatewayFilterFactory抽象類,並且重寫了一個apply方法.那麼可以自建一個類,保持名字的一致性,就是自定義名+GatewayFilterFactory
這種格式,這種格式可以直接在設定中給每個服務區域性設定
package com.jungle.gateway.demo.filter;
import org.apache.commons.lang.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AddRequestHeaderGatewayFilterFactory;
import org.springframework.cloud.gateway.support.GatewayToStringStyler;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* @author Jiangmanman
*/
@Component
public class AuthenticationGatewayFilterFactory extends AbstractAuthenticationGatewayFilterFactory{
@Override
public GatewayFilter apply(Config config) {
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
System.out.println("/******************進入認證過濾器**********************/");
System.out.println("/******************取出參數"+config+"**********************/");
String token = request.getHeaders().getFirst("token");
System.out.println(request.getHeaders());
if(StringUtils.isBlank(token)){
throw new RuntimeException("沒有Token");
}
return chain.filter(exchange);
}
@Override
public String toString() {
return GatewayToStringStyler.filterToStringCreator(AuthenticationGatewayFilterFactory.this).append(config.getName()).toString();
}
};
}
}
抽象類AbstractAuthenticationGatewayFilterFactory
繼承了AbstractGatewayFilterFactory這個介面,並且指定了由哪個類去讀取參數,shortcutFieldOrder方法主要用來控制參數的排序
package com.jungle.gateway.demo.filter;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.AbstractNameValueGatewayFilterFactory;
import org.springframework.core.style.ToStringCreator;
import org.springframework.validation.annotation.Validated;
import javax.validation.constraints.NotEmpty;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* @author Jiangmanman
*/
public abstract class AbstractAuthenticationGatewayFilterFactory extends AbstractGatewayFilterFactory<AbstractAuthenticationGatewayFilterFactory.Config> {
public AbstractAuthenticationGatewayFilterFactory() {
super(AbstractAuthenticationGatewayFilterFactory.Config.class);
}
@Override
public List<String> shortcutFieldOrder() {
return Collections.singletonList("name");
}
@Validated
public static class Config {
@NotEmpty
protected String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "NameValueConfig{" +
"name='" + name + '\'' +
'}';
}
public String getValue() {
return this.name;
}
}
}
最後在在設定上寫上服務對應的過濾器名即可,如下所示,增加的就是最後一行
spring:
application:
name: spring-cloud-gateway
cloud:
gateway:
enabled: true
routes:
# 自定義唯一標識
- id: damkdamk54da1
# 呼叫自己服務可以使用:lb://服務名
# uri: http://baidu.com
uri: lb://service-news
predicates:
#謂詞工廠,例如這裏針對COOKIE名爲mycookie,值爲mycookieValue的到上面的uri中
#使用最多的是 - Path
- Path=/news/**
filters:
- Authentication=test
上面的自定義Filter主要用來做服務的區域性設定,對於服務的全域性設定(攔截所有服務)程式碼實現如下
@Component
public class MyglobalFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("=========全域性攔截===========");
return chain.filter(exchange);
}
}
在spring官方網站7.1裡就有實現方法,就是通過實現Ordered
介面再重寫getOrder
方法,給一個值,越小的值,優先順序越高,越大的值優先順序越低
hen a request matches a route, the filtering web handler adds all instances of GlobalFilter
and all route-specific instances of GatewayFilter
to a filter chain. This combined filter chain is sorted by the org.springframework.core.Ordered
interface, which you can set by implementing the getOrder()
method.
As Spring Cloud Gateway distinguishes between 「pre」 and 「post」 phases for filter logic execution (see How it Works), the filter with the highest precedence is the first in the 「pre」-phase and the last in the 「post」-phase.
The following listing configures a filter chain:
Example 59. ExampleConfiguration.java
@Bean
public GlobalFilter customFilter() {
return new CustomGlobalFilter();
}
public class CustomGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("custom global filter");
return chain.filter(exchange);
}
@Override
public int getOrder() {
return -1;
}
}
爲了測試這個改變Filter攔截的順序,我又把Filter提取出去了,不然怎麼實現Ordered介面呢…,如下所示
public class MyServiceFilter implements GatewayFilter, Ordered {
private AbstractAuthenticationGatewayFilterFactory.Config config;
public MyServiceFilter() {
}
public MyServiceFilter(AbstractAuthenticationGatewayFilterFactory.Config config) {
this.config = config;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
System.out.println("/******************進入認證過濾器**********************/");
System.out.println("/******************取出參數"+config+"**********************/");
String token = request.getHeaders().getFirst("token");
System.out.println(request.getHeaders());
if(StringUtils.isBlank(token)){
throw new RuntimeException("沒有Token");
}
return chain.filter(exchange);
}
@Override
public String toString() {
return GatewayToStringStyler.filterToStringCreator(MyServiceFilter.this).append(config.getName()).toString();
}
@Override
public int getOrder() {
return 1;
}
}
@Component
public class AuthenticationGatewayFilterFactory extends AbstractAuthenticationGatewayFilterFactory{
@Override
public GatewayFilter apply(Config config) {
return new MyServiceFilter();
}
}
在上面MyServiceFilter
類裡的filter
方法
return chain.filter(exchange).then(Mono.fromRunnable(()->{
// 請求-》過濾器-》服務-》過濾器(現在在這裏)-》返回請求
//例如可以計算這次服務的響應時間
}));
Http timeouts (response and connect) can be configured for all routes and overridden for each specific route.
To configure Global http timeouts:
connect-timeout
must be specified in milliseconds.
response-timeout
must be specified as a java.time.Duration
global http timeouts example
spring:
cloud:
gateway:
httpclient:
connect-timeout: 1000
response-timeout: 5s
To configure per-route timeouts:
connect-timeout must be specified in milliseconds.
response-timeout must be specified in milliseconds.
per-route http timeouts configuration via configuration
- id: per_route_timeouts
uri: https://example.org
predicates:
- name: Path
args:
pattern: /delay/{timeout}
metadata:
response-timeout: 200
connect-timeout: 200
專案檔案:鏈接: https://pan.baidu.com/s/1N4eMWDfjzQZwoWwusYwoZA 提取碼: at59
將組態檔中的數據庫連線去掉即可執行,加上Nacos Server可正常註冊,加上Sentinel可正常進行服務流控和降級