隨著公司的業務發展,有幸經歷了從單體應用遷移到分布式應用,又從分布式應用開始準備搭建微服務應用,以下是公司從零開始搭建微服務的過程,記錄并分享出來,希望對大家有所幫助,我們先使用Spring Cloud GateWay作為網關,由于目前還沒有服務發現組件,例如eurka,所以需要通過配置文件的方式配置Ribbon作負載均衡。所以以下重點講解Spring Cloud GateWay和Ribbon的搭配使用。
網關的由來
微服務提出后,單體應用被拆分成多個服務,為了對外提供統一入口,解耦客戶端與內部服務。
單體架構到微服務架構演變
網關的作用
網關能做統一的路由轉發、熔斷、限流、安全認證、日志監控等。
網關的作用
網關zuul與Spring Cloud Gateway對比
zuul與Spring Cloud Gateway對比
Spring Cloud Gateway核心概念
網關核心概念
1.路由(route) 路由是網關最基礎的部分,路由信息由一個ID、一個目的URL、一組斷言工廠和一組Filter組成。如果斷言為真,則說明請求URL和配置的路由匹配。
2.斷言(predicates) JAVA8中的斷言函數,Spring Cloud Gateway中的斷言函數輸入類型是Spring5.0框架中的ServerWebExchange。Spring Cloud Gateway中的斷言函數允許開發者去定義匹配來自Http Request中的任何信息,比如請求頭和參數等。
3.過濾器(filter) 一個標準的Spring webFilter,Spring Cloud Gateway中的Filter分為兩種類型,分別是Gateway Filter和Global Filter。過濾器Filter可以對請求和相應進行處理。
Spring Cloud Gateway工作原理
網關工作原理
Spring Cloud Gateway核心處理流程如上圖所示,Gateway的客戶端向Spring Cloud Gateway發送請求,請求首先被HttpWebHandlerAdapter進行提取組裝成網關上下文,然后網關的上下文會傳遞到DispatcherHandler。DispatcherHandler是所有請求的分發處理器,DispatcherHandler主要負責分發請求對應的處理器。比如請求分發到對應的RoutePredicateHandlerMApping(路由斷言處理映射器)。路由斷言處理映射器主要作用用于路由查找,以及找到路由后返回對應的FilterWebHandler。FilterWebHandler主要負責組裝Filter鏈并調用Filter執行一系列的Filter處理,然后再把請求轉到后端對應的代理服務處理,處理完畢之后將Response返回到Gateway客戶端。
路由斷言Factories整理
- After 路由斷言 Factory:在該日期時間之后發生的請求都將被匹配。
- Before 路由斷言 Factory:在該日期時間之前發生的請求都將被匹配。
- Between 路由斷言 Factory:在datetime1和datetime2之間的請求將被匹配。
- Cookie 路由斷言 Factory:Cookie 路由斷言 Factory有兩個參數,cookie名稱和正則表達式。請求包含以cookie名稱且正則表達式為真的將會被匹配。
- Header 路由斷言 Factory:Header 路由斷言 Factory有兩個參數,header名稱和正則表達式。請求包含以header名稱且正則表達式為真的將會被匹配。
- Host 路由斷言 Factory:Host 路由斷言 Factory包括一個參數:host name列表。使用Ant路徑匹配規則,.作為分隔符。
- Method 路由斷言 Factory:Method 路由斷言 Factory只包含一個參數: 需要匹配的HTTP請求方式。
- Path 路由斷言 Factory:Path 路由斷言 Factory 有2個參數: 一個Spring PathMatcher表達式列表和可選。
- Query 路由斷言 Factory:Query 路由斷言 Factory 有2個參數: 必選項 param 和可選項 regexp。
- RemoteAddr 路由斷言 Factory:RemoteAddr 路由斷言 Factory的參數為 一個CIDR符號(IPv4或IPv6)字符串的列表,最小值為1,例如192.168.0.1/16(其中192.168.0.1是IP地址并且16是子網掩碼)。
GatewayFilter Factories整理
- AddRequestHeader GatewayFilter Factory:對于所有匹配的請求,這將向下游請求的頭中添加header。
- AddRequestParameter GatewayFilter Factory:對于所有匹配的請求,這將向下游請求添加查詢字符串。
- AddResponseHeader GatewayFilter Factory:對于所有匹配的請求,這會將頭添加到下游響應的header中。
- Hystrix GatewayFilter Factory:Hystrix 是Netflix開源的斷路器組件。Hystrix GatewayFilter允許你向網關路由引入斷路器,保護你的服務不受級聯故障的影響。
- FallbackHeaders GatewayFilter Factory:FallbackHeaders允許在轉發到外部應用程序中的FallbackUri的請求的header中添加Hystrix異常詳細信息。
- PrefixPath GatewayFilter Factory:這將給所有匹配請求的路徑加前綴。
- PreserveHostHeader GatewayFilter Factory:該filter沒有參數。設置了該Filter后,GatewayFilter將不使用由HTTP客戶端確定的host header ,而是發送原始host header 。
- RequestRateLimiter GatewayFilter Factory:RequestRateLimiter使用RateLimiter實現是否允許繼續執行當前請求。如果不允許繼續執行,則返回HTTP 429 - Too Many Requests (默認情況下)。
- redis RateLimiter:令牌桶的填充速率。
- RedirectTo GatewayFilter Factory:該過濾器有一個 status 和一個 url參數。status是300類重定向HTTP代碼,如301。該URL應為有效的URL,這將是 Location header的值。
- RemoveRequestHeader GatewayFilter Factory:有一個name參數. 這是要刪除的header的名稱。
- RemoveResponseHeader GatewayFilter Factory:有一個name參數. 這是要刪除的header的名稱。
- RewritePath GatewayFilter Factory:包含一個 regexp正則表達式參數和一個 replacement 參數. 通過使用Java正則表達式靈活地重寫請求路徑。
- RewriteResponseHeader GatewayFilter Factory:包含 name, regexp和 replacement 參數.。通過使用Java正則表達式靈活地重寫響應頭的值。
- SetPath GatewayFilter Factory:它提供了一種通過允許路徑的模板化segments來操作請求路徑的簡單方法。使用Spring Framework中的URI模板,允許多個匹配segments。
- SetStatus GatewayFilter Factory:SetStatus GatewayFilter Factory 包括唯一的 status參數.必須是一個可用的Spring HttpStatus。
- StripPrefix GatewayFilter Factory:parts參數指示在將請求發送到下游之前,要從請求中去除的路徑中的節數。
- Retry GatewayFilter Factory:Retry GatewayFilter Factory包括 retries, statuses, methods和 series 參數。
- RequestSize GatewayFilter Factory:當請求大小大于允許的限制時,RequestSize GatewayFilter Factory可以限制請求不到達下游服務。過濾器以RequestSize作為參數,這是定義請求的允許大小限制(以字節為單位)。
Ribbon的LoadBalancer的主要組件
Ribboon主要組件
- IPing:客戶端用于快速檢查服務器當時是否處于活動狀態(心跳檢測)
- IRule:負載均衡策略,用于確定從服務器列表返回哪個服務器
- ServerList:可以響應客戶端的特定服務的服務器列表
- ServerListFilter:可以動態獲得的具有所需特征的候選服務器列表的過濾器
- ServerListUpdater:用于執行動態服務器列表更新
IRule
- RoundRobinRule:系統默認的規則,通過簡單輪詢服務列表來選擇服務器。
- AvailabilityFilteringRule:該規則會忽略一下服務器。無法連接的服務器。默認情況下,3次連接失敗,服務器會被置為短路的狀態,狀態持續為30秒;再次連接失敗,短路的狀態持續時間將會以幾何數增加。可以通過修改connectionFailureCountThreshold屬性,配置連接失敗的次數。并發數過高的服務器。可以修改ActiveConnectionsLimit屬性來設置最高并發數。
- WeightedResponseTimeRule: 為每個服務器賦予一個權重值,服務器的響應時間越長,權重就越小,隨機選擇服務器,權重值有可能會決定服務器的選擇。
- ZoneAvoidanceRule: 該規則以區域、可用服務器為基礎進行服務器選擇。使用Zone對服務器進行分類。
- BestAvailableRule: 忽略短路的服務器,并選擇并發數較低的服務器。
- RandomeRule: 隨機選擇可用的服務器。
- RetryRule: 含有重試的選擇邏輯。
IPing
檢查實例是否存活。如何ping。實現類:
- NoOpPing: 不進行Ping。
- DummyPing:默認實現,標記存活的服務器。
- NIWSDiscoveryPing: 假設服務器存活。
- PingUrl: 一種健康檢查的ping。
ServerList
獲取服務器列表。
- DiscoveryEnabledNIWSServerList: 從Eureka 客戶端獲取服務器列表。
- DomainExtractingServerList: 基于domain獲取服務列表。
- ConfigurationBasedServerList: 從配置中獲取服務器列表
ServerListFilter
在獲取的服務器列表中進行獲取。
- ZoneAffinityServerListFilter: 根據區域親緣關系過濾服務器。在使用這個過濾器時,需要開啟CommonClientConfig#EnableZoneAffinity或者
CommonClientConfigKey#EnableZoneExclusivity=true。開啟后,同一個區域之外的服務器將被過濾。默認情況下,區域親和力和排他性是關閉的,并且不會過濾任何內容。 - ZonePreferenceServerListFilter: 主動首選本地區域的過濾器。
- ServerListSubSetFilter: 服務器列表過濾器,將負載均衡器使用的服務器數量限制為所有服務器的子集。
ServerListUpdater
更新服務器列表。
- EurekaNotificationServerListUpdater: 利用Eureka的時間監聽器觸發LB緩存更新。
- PollingServerListUpdater: 默認的策略動態更新服務器列表。
IClientConfig
- IClientConfig的實現類為DefaultClientConfigImpl。DefaultClientConfigImpl是默認的客戶端配置,可以從Archaius ConfigurationManager加載屬性。
ILoadBalancer
LoadBalancer的組成:
- 一個基于特定條件可能進行存儲的服務器列表。
- 一個類:通過IRule實現并定義LoadBalancing策略。
- 該類定義并實現一種機制,用戶確定列表中節點/服務器的實用性/可用性。
LoadBalancer的實現類:
- BaseLoadBalancer: 基本的實現,ping確定存活的服務器列表。
- DynamicServerListLoadBalancer: 動態獲取的服務器列表。
- ZoneAwareLoadBalancer: LoadBalancer將計算并檢查所有可用區域的區域統計信息。如果任何區域的“平均活動請求數”已達到配置的閾值,
則該區域將從活動服務器列表中刪除。如果多個區域已達到閾值,則將刪除每臺服務器上最活躍請求的區域。一旦刪除了最壞的區域,將在其余區域中選擇一個區域,其概率與其實例數成正比。服務器將從具有指定規則的選定區域返回。每個區域相關的負載平衡決策都是在最新統計信息的幫助下實時做出的。
代碼實踐
pom.xml增加SpringCloud Gateway和Ribbon依賴
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.3.RELEASE</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
</dependencies>
代碼方式配置網關
@SpringBootApplication
@RestController
public class DemoGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(DemoGatewayApplication.class, args);
}
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route(r -> r
.path("/refund/**")
.filters(f -> f.addRequestHeader("Hello", "World"))
.uri("http://manage-test.payplatform.speiyou.cn/")
).build();
}
}
yml方式配置網關
spring:
application:
name: gateway-service
cloud:
gateway:
routes:
- id: merchant
uri: lb://merchant-load-balanced-service
predicates:
- Path=/merchant/**
- Method=POST
- id: split
uri: lb://split-load-balanced-service
predicates:
- Path=/split/**
filters:
- RewritePath=/split, /ledger-split #重寫url
- id: cashier
uri: lb://cashier-load-balanced-service
predicates:
- Path=/cashier/**
filters:
- StripPrefix=1 #將cashier過濾掉
#ribbon全局配置
ribbon:
ConnectTimeout: 1000 #服務請求連接超時時間(毫秒)
ReadTimeout: 3000 #服務請求處理超時時間(毫秒)
OkToRetryOnAllOperations: true #對超時請求啟用重試機制
MaxAutoRetriesNextServer: 1 #切換重試實例的最大個數
MaxAutoRetries: 1 # 切換實例后重試最大次數
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule #負載均衡算法
NFLoadBalancerPingClassName: com.talpay.gateway.config.HealthCheck #健康檢查
NFLoadBalancerPingInterval: 20 #設置健康檢查間隔,單位秒,默認30秒
merchant-load-balanced-service:
ribbon:
listOfServers: 192.168.xxx.xxx:8080
split-load-balanced-service:
ribbon:
listOfServers: 192.168.xxx.xxx:8081
cashier-load-balanced-service:
ribbon:
listOfServers: 192.168.xxx.xxx:8080
Ribbon服務健康檢查
@Slf4j
@Component
public class HealthCheck implements IPing{
@Autowired
private RestTemplate restTemplate;
@Value("${dingtalk.url}")
private String dingtalkURL; //釘釘報警url
@Override
public boolean isAlive(Server server) {
String url = "http://"+ server.getId()+ "/actuator/health";
try {
ResponseEntity<String> heath = restTemplate.getForEntity(url, String.class);
if (heath.getStatusCode() == HttpStatus.OK) {
log.info("ping " + url + " success ");
return true;
}
log.info("ping " + url + " error and response is " + heath.getBody());
return false;
} catch (Exception e) {
log.error("ping " + url + " failed");
DingRebotSendUtil.send(dingtalkURL,new TextMessage("網關|ping:" + url + " failed"));
return false;
}
}
}
自定義GlobalFilter
@Component
public class LogGlobalFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest serverHttpRequest= exchange.getRequest();
String url = serverHttpRequest.getURI().toString();
System.out.println("url ------>: " + url);//打印每次請求的url
return chain.filter(exchange);
}
}
測試結果:
第一次觸發
第二次觸發
以上為真實測試數據,第一次觸發鏈接到生產環境,第二次觸發鏈接到仿真環境。