Spring?Cloud?Gateway實現(xiàn)分布式限流和熔斷降級的示例代碼
一、限流
思考:為啥需要限流?
在一個流量特別大的業(yè)務場景中,如果不進行限流,會造成系統(tǒng)宕機,當大批量的請求到達后端服務時,會造成資源耗盡【CPU、內(nèi)存、線程、網(wǎng)絡帶寬、數(shù)據(jù)庫連接等是有限的】,進而拖垮系統(tǒng)。
1.常見限流算法
- 漏桶算法
- 令牌桶算法
1.1漏桶算法(不推薦)
1.1.1.原理
將請求緩存到一個隊列中,然后以固定的速度處理,從而達到限流的目的
1.1.2.實現(xiàn)
將請求裝到一個桶中,桶的容量為固定的一個值,當桶裝滿之后,就會將請求丟棄掉,桶底部有一個洞,以固定的速率流出。
1.1.3.舉例
桶的容量為1W,有10W并發(fā)請求,最多只能將1W請求放入桶中,其余請求全部丟棄,以固定的速度處理請求
1.1.4.缺點
處理突發(fā)流量效率低(處理請求的速度不變,效率很低)
1.2.令牌桶算法(推薦)
1.2.1.原理
將請求放在一個緩沖隊列中,拿到令牌后才能進行處理
1.2.2.實現(xiàn)
裝令牌的桶大小固定,當令牌裝滿后,則不能將令牌放入其中;每次請求都會到桶中拿取一個令牌才能放行,沒有令牌時即丟棄請求/繼續(xù)放入緩存隊列中等待
1.2.3.舉例
桶的容量為10w個,生產(chǎn)1w個/s,有10W的并發(fā)請求,以每秒10W個/s速度處理,隨著桶中的令牌很快用完,速度又慢慢降下來啦,而生產(chǎn)令牌的速度趨于一致1w個/s
1.2.4.缺點
處理突發(fā)流量提供了系統(tǒng)性能,但是對系統(tǒng)造成了一定的壓力,桶的大小不合理,甚至會壓垮系統(tǒng)(處理1億的并發(fā)請求,將桶的大小設置為1,這個系統(tǒng)一下就涼涼啦)
2.網(wǎng)關限流(Spring Cloud Gateway + Redis實戰(zhàn))
2.1.pom.xml配置
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis-reactive</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> </dependency>
2.2.yaml配置
spring: application: name: laokou-gateway cloud: gateway: routes: - id: LAOKOU-SSO-DEMO uri: lb://laokou-sso-demo predicates: - Path=/sso/** filters: - StripPrefix=1 - name: RequestRateLimiter #請求數(shù)限流,名字不能亂打 args: key-resolver: "#{@ipKeyResolver}" redis-rate-limiter.replenishRate: 1 #生成令牌速率-設為1方便測試 redis-rate-limiter.burstCapacity: 1 #令牌桶容量-設置1方便測試 redis: database: 0 cluster: nodes: x.x.x.x:7003,x.x.x.x:7004,x.x.x.x:7005,x.x.x.x:7003,x.x.x.x:7004,x.x.x.x:7005 password: laokou #密碼 timeout: 6000ms #連接超時時長(毫秒) jedis: pool: max-active: -1 #連接池最大連接數(shù)(使用負值表示無極限) max-wait: -1ms #連接池最大阻塞等待時間(使用負值表示沒有限制) max-idle: 10 #連接池最大空閑連接 min-idle: 5 #連接池最小空間連接
2.3.創(chuàng)建bean
@Configuration public class RequestRateLimiterConfig { @Bean(value = "ipKeyResolver") public KeyResolver ipKeyResolver(RemoteAddressResolver remoteAddressResolver) { return exchange -> Mono.just(remoteAddressResolver.resolve(exchange).getAddress().getHostAddress()); } @Bean public RemoteAddressResolver remoteAddressResolver() { // 遠程地址解析器 return XForwardedRemoteAddressResolver.trustAll(); } }
3.測試限流(編寫java并發(fā)測試)
@Slf4j public class HttpUtil { public static void apiConcurrent(String url,Map<String,String> params) { Integer count = 200; //創(chuàng)建線程池 ThreadPoolExecutor pool = new ThreadPoolExecutor(5, 200, 0L, TimeUnit.SECONDS, new SynchronousQueue<>()); //同步工具 CountDownLatch latch = new CountDownLatch(count); Map<String,String> dataMap = new HashMap<>(1); dataMap.put("authorize","XXXXXXX"); for (int i = 0; i < count; i++) { pool.execute(() -> { try { //訪問網(wǎng)關的API接口 HttpUtil.doGet("http://localhost:1234/sso/laokou-demo/user",dataMap); } catch (IOException e) { e.printStackTrace(); }finally { latch.countDown(); } }); } try { latch.await(); } catch (InterruptedException e) { e.printStackTrace(); } } public static String doGet(String url, Map<String, String> params) throws IOException { //創(chuàng)建HttpClient對象 CloseableHttpClient httpClient = HttpClients.createDefault(); String resultString = ""; CloseableHttpResponse response = null; try { //創(chuàng)建uri URIBuilder builder = new URIBuilder(url); if (!params.isEmpty()) { for (Map.Entry<String, String> entry : params.entrySet()) { builder.addParameter(entry.getKey(), entry.getValue()); } } URI uri = builder.build(); //創(chuàng)建http GET請求 HttpGet httpGet = new HttpGet(uri); List<NameValuePair> paramList = new ArrayList<>(); RequestBuilder requestBuilder = RequestBuilder.get().setUri(new URI(url)); requestBuilder.setEntity(new UrlEncodedFormEntity(paramList, Consts.UTF_8)); httpGet.setHeader(new BasicHeader("Content-Type", "application/json;charset=UTF-8")); httpGet.setHeader(new BasicHeader("Accept", "*/*;charset=utf-8")); //執(zhí)行請求 response = httpClient.execute(httpGet); //判斷返回狀態(tài)是否是200 if (response.getStatusLine().getStatusCode() == 200) { resultString = EntityUtils.toString(response.getEntity(), "UTF-8"); } } catch (Exception e) { log.info("調(diào)用失敗:{}",e); } finally { if (response != null) { response.close(); } httpClient.close(); } log.info("打?。簕}",resultString); return resultString; } }
說明這個網(wǎng)關限流配置是沒有問題的
4.源碼查看
Spring Cloud Gateway RequestRateLimiter GatewayFilter Factory文檔地址
工廠 RequestRateLimiter GatewayFilter
使用一個RateLimiter
實現(xiàn)來判斷當前請求是否被允許繼續(xù)。如果不允許,HTTP 429 - Too Many Requests
則返回默認狀態(tài)。
4.1.查看 RequestRateLimiterGatewayFilterFactory
@Override public GatewayFilter apply(Config config) { KeyResolver resolver = getOrDefault(config.keyResolver, defaultKeyResolver); RateLimiter<Object> limiter = getOrDefault(config.rateLimiter, defaultRateLimiter); boolean denyEmpty = getOrDefault(config.denyEmptyKey, this.denyEmptyKey); HttpStatusHolder emptyKeyStatus = HttpStatusHolder .parse(getOrDefault(config.emptyKeyStatus, this.emptyKeyStatusCode)); return (exchange, chain) -> resolver.resolve(exchange).defaultIfEmpty(EMPTY_KEY).flatMap(key -> { if (EMPTY_KEY.equals(key)) { if (denyEmpty) { setResponseStatus(exchange, emptyKeyStatus); return exchange.getResponse().setComplete(); } return chain.filter(exchange); } String routeId = config.getRouteId(); if (routeId == null) { Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR); routeId = route.getId(); } // 執(zhí)行限流 return limiter.isAllowed(routeId, key).flatMap(response -> { for (Map.Entry<String, String> header : response.getHeaders().entrySet()) { exchange.getResponse().getHeaders().add(header.getKey(), header.getValue()); } if (response.isAllowed()) { return chain.filter(exchange); } setResponseStatus(exchange, config.getStatusCode()); return exchange.getResponse().setComplete(); }); }); }
4.2.查看 RedisRateLimiter
@Override @SuppressWarnings("unchecked") public Mono<Response> isAllowed(String routeId, String id) { if (!this.initialized.get()) { throw new IllegalStateException("RedisRateLimiter is not initialized"); } // 這里如何加載配置?請思考 Config routeConfig = loadConfiguration(routeId); // 令牌桶每秒產(chǎn)生令牌數(shù)量 int replenishRate = routeConfig.getReplenishRate(); // 令牌桶容量 int burstCapacity = routeConfig.getBurstCapacity(); // 請求消耗的令牌數(shù) int requestedTokens = routeConfig.getRequestedTokens(); try { // 鍵 List<String> keys = getKeys(id); // 參數(shù) List<String> scriptArgs = Arrays.asList(replenishRate + "", burstCapacity + "", "", requestedTokens + ""); // 調(diào)用lua腳本 Flux<List<Long>> flux = this.redisTemplate.execute(this.script, keys, scriptArgs); return flux.onErrorResume(throwable -> { if (log.isDebugEnabled()) { log.debug("Error calling rate limiter lua", throwable); } return Flux.just(Arrays.asList(1L, -1L)); }).reduce(new ArrayList<Long>(), (longs, l) -> { longs.addAll(l); return longs; }).map(results -> { // 判斷是否等于1,1表示允許通過,0表示不允許通過 boolean allowed = results.get(0) == 1L; Long tokensLeft = results.get(1); Response response = new Response(allowed, getHeaders(routeConfig, tokensLeft)); if (log.isDebugEnabled()) { log.debug("response: " + response); } return response; }); } catch (Exception e) { log.error("Error determining if user allowed from redis", e); } return Mono.just(new Response(true, getHeaders(routeConfig, -1L))); } static List<String> getKeys(String id) { String prefix = "request_rate_limiter.{" + id; String tokenKey = prefix + "}.tokens"; String timestampKey = prefix + "}.timestamp"; return Arrays.asList(tokenKey, timestampKey); }
思考:redis限流配置是如何加載?
其實就是監(jiān)聽動態(tài)路由的事件并把配置存起來
4.3.重點來了,令牌桶 /META-INF/scripts/request_rate_limiter.lua 腳本剖析
-- User Request Rate Limiter filter -- See https://stripe.com/blog/rate-limiters -- See https://gist.github.com/ptarjan/e38f45f2dfe601419ca3af937fff574d#file-1-check_request_rate_limiter-rb-L11-L34 -- 令牌桶算法工作原理 -- 1.系統(tǒng)以恒定速率往桶里面放入令牌 -- 2.請求需要被處理,則需要從桶里面獲取一個令牌 -- 3.如果桶里面沒有令牌可獲取,則可以選擇等待或直接拒絕并返回 -- 令牌桶算法工作流程 -- 1.計算填滿令牌桶所需要的時間(填充時間 = 桶容量 / 速率) -- 2.設置存儲數(shù)據(jù)的TTL(過期時間),為填充時間的兩倍(存儲時間 = 填充時間 * 2) -- 3.從Redis獲取當前令牌的剩余數(shù)量和上一次調(diào)用的時間戳 -- 4.計算距離上一次調(diào)用的時間間隔(時間間隔 = 當前時間 - 上一次調(diào)用時間) -- 5.計算填充的令牌數(shù)量(填充令牌數(shù)量 = 時間間隔 * 速率)【前提:桶容量是固定的,不存在無限制的填充】 -- 6.判斷是否有足夠多的令牌滿足請求【 (填充令牌數(shù)量 + 剩余令牌數(shù)量) >= 請求數(shù)量 && (填充令牌數(shù)量 + 剩余令牌數(shù)量) <= 桶容量 】 -- 7.如果請求被允許,則從桶里面取出相應數(shù)據(jù)的令牌 -- 8.如果TTL為正,則更新Redis鍵中的令牌和時間戳 -- 9.返回兩個兩個參數(shù)(allowed_num:請求被允許標志。1允許,0不允許)、(new_tokens:填充令牌后剩余的令牌數(shù)據(jù)) -- 隨機寫入 redis.replicate_commands() -- 令牌桶Key -> 存儲當前可用令牌的數(shù)量(剩余令牌數(shù)量) local tokens_key = KEYS[1] -- 時間戳Key -> 存儲上次令牌刷新的時間戳 local timestamp_key = KEYS[2] -- 令牌填充速率 local rate = tonumber(ARGV[1]) -- 令牌桶容量 local capacity = tonumber(ARGV[2]) -- 當前時間 local now = tonumber(ARGV[3]) -- 請求數(shù)量 local requested = tonumber(ARGV[4]) -- 填滿令牌桶所需要的時間 local fill_time = capacity / rate -- 設置key的過期時間(填滿令牌桶所需時間的2倍) local ttl = math.floor(fill_time * 2) -- 判斷當前時間,為空則從redis獲取 if now == nil then now = redis.call('TIME')[1] end -- 獲取當前令牌的剩余數(shù)量 local last_tokens = tonumber(redis.call("get", tokens_key)) if last_tokens == nil then last_tokens = capacity end -- 獲取上一次調(diào)用的時間戳 local last_refreshed = tonumber(redis.call('get', timestamp_key)) if last_refreshed == nil then last_refreshed = 0 end -- 計算距離上一次調(diào)用的時間間隔 local delta = math.max(0, now - last_refreshed) -- 當前的令牌數(shù)量(剩余 + 填充 <= 桶容量) local now_tokens = math.min(capacity, last_refreshed + (rate * delta)) -- 判斷是否有足夠多的令牌滿足請求 local allowed = now_tokens >= requested -- 定義當前令牌的剩余數(shù)量 local new_tokens = now_tokens -- 定義被允許標志 local allowed_num = 0 if allowed then new_tokens = now_tokens - requested -- 允許訪問 allowed_num = 1 end -- ttl > 0,將當前令牌的剩余數(shù)量和當前時間戳存入redis if ttl > 0 then redis.call('setex', tokens_key, ttl, new_tokens) redis.call('setex', timestamp_key, ttl, now) end -- 返回參數(shù) return { allowed_num, new_tokens }
4.4.查看 GatewayRedisAutoConfiguration 腳本初始化
@Bean @SuppressWarnings("unchecked") public RedisScript redisRequestRateLimiterScript() { DefaultRedisScript redisScript = new DefaultRedisScript<>(); redisScript.setScriptSource( // 根據(jù)指定路徑獲取lua腳本來初始化配置 new ResourceScriptSource(new ClassPathResource("META-INF/scripts/request_rate_limiter.lua"))); redisScript.setResultType(List.class); return redisScript; } @Bean @ConditionalOnMissingBean public RedisRateLimiter redisRateLimiter(ReactiveStringRedisTemplate redisTemplate, @Qualifier(RedisRateLimiter.REDIS_SCRIPT_NAME) RedisScript<List<Long>> redisScript, ConfigurationService configurationService) { return new RedisRateLimiter(redisTemplate, redisScript, configurationService); }
思考:請求限流過濾器是如何開啟?
1.通過yaml配置開啟
spring: cloud: gateway: server: webflux: filter: request-rate-limiter: enabled: true
2.GatewayAutoConfiguration自動注入bean
@Bean @ConditionalOnBean({ RateLimiter.class, KeyResolver.class }) @ConditionalOnEnabledFilter public RequestRateLimiterGatewayFilterFactory requestRateLimiterGatewayFilterFactory(RateLimiter rateLimiter, KeyResolver resolver) { return new RequestRateLimiterGatewayFilterFactory(rateLimiter, resolver); }
重點來了,真正加載這個bean的是 @ConditionalOnEnabledFilter
注解進行判斷
@Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.TYPE, ElementType.METHOD }) @Documented @Conditional(OnEnabledFilter.class) public @interface ConditionalOnEnabledFilter { // 這里value是用來指定滿足條件的某些類,換一句話說,就是這些類都加載或注入到ioc容器,這個注解修飾的自動裝配類才會生效 Class<? extends GatewayFilterFactory<?>> value() default OnEnabledFilter.DefaultValue.class; }
我們繼續(xù)跟進代碼,查看@Conditional(OnEnabledFilter.class)
眾所周知,@Conditional
可以用來加載滿足條件的bean,所以,我們分析一下OnEnabledFilter
public class OnEnabledFilter extends OnEnabledComponent<GatewayFilterFactory<?>> {}
我分析它的父類,這里有你想要的答案!
public abstract class OnEnabledComponent<T> extends SpringBootCondition implements ConfigurationCondition { private static final String PREFIX = "spring.cloud.gateway.server.webflux."; private static final String SUFFIX = ".enabled"; private ConditionOutcome determineOutcome(Class<? extends T> componentClass, PropertyResolver resolver) { // 拼接完整名稱 // 例如 => spring.cloud.gateway.server.webflux.request-rate-limiter.enabled String key = PREFIX + normalizeComponentName(componentClass) + SUFFIX; ConditionMessage.Builder messageBuilder = forCondition(annotationClass().getName(), componentClass.getName()); if ("false".equalsIgnoreCase(resolver.getProperty(key))) { // 不滿足條件不加載bean return ConditionOutcome.noMatch(messageBuilder.because("bean is not available")); } // 滿足條件加載bean return ConditionOutcome.match(); } }
5.優(yōu)化限流響應[使用全限定類名直接覆蓋類]
小伙伴們,有沒有發(fā)現(xiàn),這個這個響應體封裝的不太好,因此,我們來自定義吧,我們直接覆蓋類,代碼修改如下
@Getter @ConfigurationProperties("spring.cloud.gateway.server.webflux.filter.request-rate-limiter") public class RequestRateLimiterGatewayFilterFactory extends AbstractGatewayFilterFactory<RequestRateLimiterGatewayFilterFactory.Config> { private static final String EMPTY_KEY = "____EMPTY_KEY__"; private final RateLimiter<?> defaultRateLimiter; private final KeyResolver defaultKeyResolver; /** * Switch to deny requests if the Key Resolver returns an empty key, defaults to true. */ @Setter private boolean denyEmptyKey = true; /** HttpStatus to return when denyEmptyKey is true, defaults to FORBIDDEN. */ @Setter private String emptyKeyStatusCode = HttpStatus.FORBIDDEN.name(); public RequestRateLimiterGatewayFilterFactory(RateLimiter<?> defaultRateLimiter, KeyResolver defaultKeyResolver) { super(Config.class); this.defaultRateLimiter = defaultRateLimiter; this.defaultKeyResolver = defaultKeyResolver; } @Override public GatewayFilter apply(Config config) { KeyResolver resolver = getOrDefault(config.keyResolver, defaultKeyResolver); RateLimiter<?> limiter = getOrDefault(config.rateLimiter, defaultRateLimiter); boolean denyEmpty = getOrDefault(config.denyEmptyKey, this.denyEmptyKey); HttpStatusHolder emptyKeyStatus = HttpStatusHolder .parse(getOrDefault(config.emptyKeyStatus, this.emptyKeyStatusCode)); return (exchange, chain) -> resolver.resolve(exchange).defaultIfEmpty(EMPTY_KEY).flatMap(key -> { if (EMPTY_KEY.equals(key)) { if (denyEmpty) { setResponseStatus(exchange, emptyKeyStatus); return exchange.getResponse().setComplete(); } return chain.filter(exchange); } String routeId = config.getRouteId(); if (routeId == null) { Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR); Assert.notNull(route, "Route is null"); routeId = route.getId(); } return limiter.isAllowed(routeId, key).flatMap(response -> { for (Map.Entry<String, String> header : response.getHeaders().entrySet()) { exchange.getResponse().getHeaders().add(header.getKey(), header.getValue()); } if (response.isAllowed()) { return chain.filter(exchange); } // 主要修改這行 return responseOk(exchange, Result.fail("Too_Many_Requests", "請求太頻繁")); }); }); } private Mono<Void> responseOk(ServerWebExchange exchange, Object data) { return responseOk(exchange, JacksonUtils.toJsonStr(data), MediaType.APPLICATION_JSON); } private Mono<Void> responseOk(ServerWebExchange exchange, String str, MediaType contentType) { DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(str.getBytes(StandardCharsets.UTF_8)); ServerHttpResponse response = exchange.getResponse(); response.setStatusCode(HttpStatus.OK); response.getHeaders().setContentType(contentType); response.getHeaders().setContentLength(str.getBytes(StandardCharsets.UTF_8).length); return response.writeWith(Flux.just(buffer)); } private <T> T getOrDefault(T configValue, T defaultValue) { return (configValue != null) ? configValue : defaultValue; } public static class Config implements HasRouteId { @Getter private KeyResolver keyResolver; @Getter private RateLimiter<?> rateLimiter; @Getter private HttpStatus statusCode = HttpStatus.TOO_MANY_REQUESTS; @Getter private Boolean denyEmptyKey; @Getter private String emptyKeyStatus; private String routeId; public Config setKeyResolver(KeyResolver keyResolver) { this.keyResolver = keyResolver; return this; } public Config setRateLimiter(RateLimiter<?> rateLimiter) { this.rateLimiter = rateLimiter; return this; } public Config setStatusCode(HttpStatus statusCode) { this.statusCode = statusCode; return this; } public Config setDenyEmptyKey(Boolean denyEmptyKey) { this.denyEmptyKey = denyEmptyKey; return this; } public Config setEmptyKeyStatus(String emptyKeyStatus) { this.emptyKeyStatus = emptyKeyStatus; return this; } @Override public void setRouteId(String routeId) { this.routeId = routeId; } @Override public String getRouteId() { return this.routeId; } } }
二、熔斷降級
思考:為什么需要熔斷降級?
當某個服務發(fā)生故障時(超時,響應慢,宕機),上游服務無法及時獲取響應,進而也導致故障,出現(xiàn)服務雪崩【服務雪崩是指故障像滾雪球一樣沿著調(diào)用鏈向上游擴展,進而導致整個系統(tǒng)癱瘓】
熔斷降級的目標就是在故障發(fā)生時,快速隔離問題服務【快速失敗,防止資源耗盡】,保護系統(tǒng)資源不被耗盡,防止故障擴散,保護核心業(yè)務可用性。
1.技術選型
1.1.熔斷降級框架選型對比表
對比維度 | Hystrix (Netflix) | Sentinel (Alibaba) | Resilience4j |
---|---|---|---|
當前狀態(tài) | ? 停止更新 (維護模式) | ? 持續(xù)更新 | ? 持續(xù)更新 |
熔斷機制 | 滑動窗口計數(shù) | 響應時間/異常比例/QPS | 錯誤率/響應時間閾值 |
流量控制 | ? 僅基礎隔離 | ? QPS/并發(fā)數(shù)/熱點參數(shù)/集群流控 | ? RateLimiter |
隔離策略 | 線程池(開銷大)/信號量 | 并發(fā)線程數(shù)(無線程池開銷) | 信號量/Bulkhead |
降級能力 | Fallback 方法 | Fallback + 系統(tǒng)規(guī)則自適應 | Fallback + 自定義組合策略 |
實時監(jiān)控 | ? Hystrix Dashboard | ? 原生控制臺(可視化動態(tài)規(guī)則) | ? 需整合 Prometheus/Grafana |
動態(tài)配置 | ? 依賴 Archaius | ? 控制臺實時推送 | ? 需編碼實現(xiàn)(如Spring Cloud Config) |
生態(tài)集成 | ? Spring Cloud Netflix | ? Spring Cloud Alibaba/多語言網(wǎng)關 | ? Spring Boot/響應式編程 |
性能開銷 | 高(線程池隔離) | 低(無額外線程) | 極低(純函數(shù)式) |
適用場景 | 遺留系統(tǒng)維護 | 高并發(fā)控制/秒殺/熱點防護 | 云原生/輕量級微服務 |
推薦指數(shù) | ?? (不推薦新項目) | ????? (Java高并發(fā)首選) | ????? (云原生/響應式首選) |
1.2選型決策指南
需求場景 | 推薦方案 | 原因 |
---|---|---|
電商秒殺/API高頻調(diào)用管控 | ? Sentinel | 精細流量控制+熱點防護+實時看板 |
Kubernetes云原生微服務 | ? Resilience4j | 輕量化+無縫集成Prometheus+響應式支持 |
Spring Cloud Netflix舊系統(tǒng) | ?? Hystrix | 兼容現(xiàn)存代碼(短期過渡) |
多語言混合架構(gòu)(如Go+Java) | ? Sentinel | 通過Sidecar代理支持非Java服務 |
響應式編程(WebFlux) | ? Resilience4j | 原生Reactive API支持 |
2.Resilience4j使用
Resilience4j
可以看作是 Hystrix
的替代品,Resilience4j支持 熔斷器
和單機限流
Resilience4j 是一個專為函數(shù)式編程設計的輕量級容錯庫。Resilience4j 提供高階函數(shù)(裝飾器),可通過斷路器、速率限制器、重試或隔離功能增強任何函數(shù)式接口、lambda 表達式或方法引用。您可以在任何函數(shù)式接口、lambda 表達式或方法引用上堆疊多個裝飾器。這樣做的好處是,您可以只選擇所需的裝飾器,而無需考慮其他因素。
2.1.網(wǎng)關熔斷降級(Spring Cloud Gateway + Resilience4j實戰(zhàn))
2.1.1.pom依賴
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-circuitbreaker-reactor-resilience4j</artifactId> </dependency>
2.1.2.yaml配置
spring: application: name: laokou-gateway cloud: gateway: server: webflux: routes: - id: LAOKOU-SSO-DEMO uri: lb://laokou-sso-demo predicates: - Path=/sso/** filters: - name: CircuitBreaker args: name: default fallbackUri: "forward:/fallback" filter: circuit-breaker: enabled: true
2.1.3.CircuitBreakerConfig配置
/** * @author laokou */ @Configuration public class CircuitBreakerConfig { @Bean public RouterFunction<ServerResponse> routerFunction() { return RouterFunctions.route( RequestPredicates.path("/fallback").and(RequestPredicates.accept(MediaType.TEXT_PLAIN)), (request) -> ServerResponse.status(HttpStatus.SC_OK) .contentType(MediaType.APPLICATION_JSON) .body(BodyInserters.fromValue(Result.fail("Service_Unavailable", "服務正在維護")))); } @Bean public Customizer<ReactiveResilience4JCircuitBreakerFactory> reactiveResilience4JCircuitBreakerFactoryCustomizer() { return factory -> factory.configureDefault(id -> new Resilience4JConfigBuilder(id) // 3秒后超時時間 .timeLimiterConfig(TimeLimiterConfig.custom().timeoutDuration(Duration.ofSeconds(3)).build()) .circuitBreakerConfig(io.github.resilience4j.circuitbreaker.CircuitBreakerConfig.ofDefaults()) .build()); } }
到此這篇關于Spring Cloud Gateway實現(xiàn)分布式限流和熔斷降級的文章就介紹到這了,更多相關Spring Cloud Gateway分布式限流和熔斷降級內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
SpringBoot使用郵箱發(fā)送驗證碼實現(xiàn)注冊功能
這篇文章主要為大家詳細介紹了SpringBoot使用郵箱發(fā)送驗證碼實現(xiàn)注冊功能實例,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2020-02-02Java中CyclicBarrier和CountDownLatch的用法與區(qū)別
CyclicBarrier和CountDownLatch這兩個工具都是在java.util.concurrent包下,并且平時很多場景都會使用到。本文將會對兩者進行分析,記錄他們的用法和區(qū)別,感興趣的可以了解一下2021-08-08Spring Boot容器加載時執(zhí)行特定操作(推薦)
這篇文章主要介紹了Spring Boot容器加載時執(zhí)行特定操作及spring內(nèi)置的事件,需要的朋友可以參考下2018-01-01SpringBoot使用JavaMailSender實現(xiàn)發(fā)送郵件
JavaMailSender是Spring Framework中的一個接口,用于發(fā)送電子郵件,本文主要為大家詳細介紹了SpringBoot如何使用JavaMailSender實現(xiàn)發(fā)送郵件,需要的可以參考下2023-12-12