百度網(wǎng)站的設(shè)計(jì)風(fēng)格上海外貿(mào)seo
從本文開始,筆者將總結(jié) spring cloud 相關(guān)內(nèi)容的教程
版本選擇
為了適應(yīng) java8,筆者選擇了下面的版本,后續(xù)會(huì)出 java17的以SpringBoot3.0.X為主的教程
SpringBoot 版本?2.6.5
SpringCloud 版本?2021.0.1
SpringCloudAlibaba 版本?2021.0.1.0
SpringCloudAlibaba github 版本說明截圖
SpringCloud 官網(wǎng):https://spring.io/projects/spring-cloud
Spring Cloud Alibaba?官網(wǎng):https://sca.aliyun.com/zh-cn/
目錄
1、環(huán)境準(zhǔn)備
2、項(xiàng)目創(chuàng)建
3、測(cè)試限流
4、改進(jìn)限流返回
5、項(xiàng)目代碼
1、環(huán)境準(zhǔn)備
本文講解Spring Cloud Gateway 使用 Redis 限流,注冊(cè)中心使用 Nacos
Macos 官網(wǎng):https://nacos.io/zh-cn/index.html
Nacos 安裝這里不做過多介紹,不了解的朋友可以參考
Nacos 單機(jī)安裝:https://blog.csdn.net/wsjzzcbq/article/details/123916233
Nacos 集群安裝:https://blog.csdn.net/wsjzzcbq/article/details/123956116
筆者使用 docker 開啟 nacos 和 redis
Spring Cloud Alibaba??2021.0.1.0 版本對(duì)應(yīng)的nacos版本是 1.4.2
筆者使用的 Naocs 版本是 2.0.0-bugfix,redis 版本是 7.0.6
2、項(xiàng)目創(chuàng)建
新建 maven 聚合項(xiàng)目 cloud-learn
最外層父工程?cloud-learn 的 pom.xml
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.wsjzzcbq</groupId><artifactId>cloud-learn</artifactId><version>1.0-SNAPSHOT</version><modules><module>gateway-learn</module><module>consumer-learn</module></modules><packaging>pom</packaging><repositories><repository><id>naxus-aliyun</id><name>naxus-aliyun</name><url>https://maven.aliyun.com/repository/public</url><releases><enabled>true</enabled></releases><snapshots><enabled>false</enabled></snapshots></repository></repositories><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.6.5</version><relativePath/></parent><properties><spring-cloud.version>2021.0.1</spring-cloud.version><spring-cloud-alibaba.version>2021.0.1.0</spring-cloud-alibaba.version><alibaba-nacos-discovery.veriosn>2021.1</alibaba-nacos-discovery.veriosn><alibaba-nacos-config.version>2021.1</alibaba-nacos-config.version><spring-cloud-starter-bootstrap.version>3.1.1</spring-cloud-starter-bootstrap.version></properties><dependencyManagement><dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>${spring-cloud.version}</version><type>pom</type><scope>import</scope></dependency><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-alibaba-dependencies</artifactId><version>${spring-cloud-alibaba.version}</version><type>pom</type><scope>import</scope></dependency><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId><version>${alibaba-nacos-discovery.veriosn}</version></dependency><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId><version>${alibaba-nacos-config.version}</version></dependency><!--spring-cloud-dependencies 2020.0.0 版本不在默認(rèn)加載bootstrap文件,如果需要加載bootstrap文件需要手動(dòng)添加依賴--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-bootstrap</artifactId><version>${spring-cloud-starter-bootstrap.version}</version></dependency><dependency><groupId>com.alibaba.fastjson2</groupId><artifactId>fastjson2</artifactId><version>2.0.40</version></dependency></dependencies></dependencyManagement><dependencies><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency></dependencies></project>
然后創(chuàng)建2個(gè)子工程?consumer-learn 和 gateway-learn
gateway-learn 中配置路由轉(zhuǎn)發(fā)到?consumer-learn
consumer-learn 工程 pom.xml
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>cloud-learn</artifactId><groupId>com.wsjzzcbq</groupId><version>1.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>consumer-learn</artifactId><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency><!--feign負(fù)載均衡--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-loadbalancer</artifactId></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>
</project>
consumer-learn 工程 啟動(dòng)類
package com.wsjzzcbq;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;/*** ConsumerApplication** @author wsjz* @date 2023/09/17*/
@EnableFeignClients
@SpringBootApplication
public class ConsumerApplication {public static void main(String[] args) {SpringApplication.run(ConsumerApplication.class, args);}
}
consumer-learn 工程 配置文件
spring.application.name=consumer-learn
server.port=8081
spring.cloud.nacos.discovery.username=nacos
spring.cloud.nacos.discovery.password=nacos
spring.cloud.nacos.discovery.server-addr=192.168.31.152:8848
spring.cloud.nacos.discovery.namespace=public
logging.level.com.alibaba.cloudlearnconsumer.feign.ProducerService=DEBUG
consumer-learn 工程 controller
package com.wsjzzcbq.controller;import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;/*** ConsumerController** @author wsjz* @date 2023/09/17*/
@RestController
public class ConsumerController {@RequestMapping("/name")public String user() {return "寶劍鋒從磨礪出,梅花香自苦寒來";}
}
gateway-learn
gateway-learn 工程 pom.xml
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>cloud-learn</artifactId><groupId>com.wsjzzcbq</groupId><version>1.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>gateway-learn</artifactId><dependencies><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><!-- gateway負(fù)載均衡需要下面依賴,不添加會(huì)報(bào)錯(cuò)503--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-loadbalancer</artifactId></dependency><!--redis使用較高版本,5以上版本,不要使用windows版redis,低版本redis不支持lua中的命令--><!--https://blog.csdn.net/wxxiangge/article/details/95024214/--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis-reactive</artifactId></dependency><dependency><groupId>com.alibaba.fastjson2</groupId><artifactId>fastjson2</artifactId></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>
gateway-learn 工程 啟動(dòng)類
package com.wsjzzcbq;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;/*** GatewayApplication** @author wsjz* @date 2023/09/17*/
@SpringBootApplication
public class GatewayApplication {public static void main(String[] args) {SpringApplication.run(GatewayApplication.class, args);}
}
限流需要實(shí)現(xiàn)?KeyResolver 接口的?resolve 方法
在?resolve 方法中返回限流的維度,如請(qǐng)求路徑、ip地址、請(qǐng)求參數(shù)等
筆者這里限流維度是請(qǐng)求路徑
package com.wsjzzcbq.limit;import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;/*** ApiKeyResolver** @author wsjz* @date 2023/09/17*/
@Component
public class ApiKeyResolver implements KeyResolver {@Overridepublic Mono<String> resolve(ServerWebExchange exchange) {System.out.println("API限流: " + exchange.getRequest().getPath().value());return Mono.just(exchange.getRequest().getPath().value());}
}
gateway-learn 工程 配置文件
server:port: 9000
spring:application:name: gateway-learnredis:host: 192.168.31.152password: 123456timeout: 5000database: 0cloud:nacos:discovery:server-addr: http://192.168.31.152:8848gateway:routes:- id: consumer-learnuri: lb://consumer-learnpredicates:- Path=/cloudlearn/consumer/**filters:- name: RequestRateLimiterargs:key-resolver: "#{@apiKeyResolver}"redis-rate-limiter.replenishRate: 1 #生成令牌速率:個(gè)/秒redis-rate-limiter.burstCapacity: 2 #令牌桶容量redis-rate-limiter.requestedTokens: 1 #每次消費(fèi)的Token數(shù)量,默認(rèn)是 1- StripPrefix=2
配置說明:
RequestRateLimiter 是 gateway 提供的限流過濾器
#{@apiKeyResolver} 是筆者實(shí)現(xiàn)的?ApiKeyResolver
redis-rate-limiter.replenishRate?生成令牌的速率每秒幾個(gè)
redis-rate-limiter.burstCapacity 令牌桶容量
redis-rate-limiter.requestedTokens 每次消費(fèi)的令牌數(shù)量
當(dāng)請(qǐng)求到網(wǎng)關(guān)以?/cloudlearn/consumer/? 為開頭前綴時(shí),會(huì)路由到?consumer-learn 服務(wù)上
創(chuàng)建完成的項(xiàng)目結(jié)構(gòu)
3、測(cè)試限流
分別啟動(dòng)?consumer-learn 和 gateway-learn
登錄 Nacos 控制臺(tái)查看
啟動(dòng)成功后,可在Naocs 控制臺(tái)查看注冊(cè)服務(wù)信息
瀏覽器訪問測(cè)試限流:http://localhost:9000/cloudlearn/consumer/name
運(yùn)行效果
可以看到當(dāng)1秒鐘內(nèi)請(qǐng)求超過2次時(shí)會(huì)被限流
4、改進(jìn)限流返回
上面代碼實(shí)現(xiàn)了限流,但限流觸發(fā)后返回的是429,不利于前端處理,這里我們可以在默認(rèn)的限流過濾器基礎(chǔ)上進(jìn)行改進(jìn),自定義限流時(shí)的返回
新建?NewRequestRateLimiterGatewayFilterFactory 類
繼承默認(rèn)的?RequestRateLimiterGatewayFilterFactory
package com.wsjzzcbq.filter;import com.alibaba.fastjson2.JSONObject;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.RequestRateLimiterGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.cloud.gateway.filter.ratelimit.RateLimiter;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.gateway.support.HttpStatusHolder;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.Map;/*** NewRequestRateLimiterGatewayFilterFactory** @author wsjz* @date 2023/09/17*/
@Component
public class NewRequestRateLimiterGatewayFilterFactory extends RequestRateLimiterGatewayFilterFactory {private final RateLimiter defaultRateLimiter;private final KeyResolver defaultKeyResolver;private boolean denyEmptyKey = true;private String emptyKeyStatusCode = HttpStatus.FORBIDDEN.name();public NewRequestRateLimiterGatewayFilterFactory(RateLimiter defaultRateLimiter, KeyResolver defaultKeyResolver) {super(defaultRateLimiter, defaultKeyResolver);this.defaultRateLimiter = defaultRateLimiter;this.defaultKeyResolver = defaultKeyResolver;}@Overridepublic GatewayFilter apply(Config config) {System.out.println("過濾限流");KeyResolver resolver = (KeyResolver)this.getOrDefault(config.getKeyResolver(), this.defaultKeyResolver);RateLimiter<Object> limiter = (RateLimiter)this.getOrDefault(config.getRateLimiter(), this.defaultRateLimiter);boolean denyEmpty = (Boolean)this.getOrDefault(config.getDenyEmptyKey(), this.denyEmptyKey);HttpStatusHolder emptyKeyStatus = HttpStatusHolder.parse((String)this.getOrDefault(config.getEmptyKeyStatus(), this.emptyKeyStatusCode));return (exchange, chain) -> {return resolver.resolve(exchange).defaultIfEmpty("____EMPTY_KEY__").flatMap((key) -> {if ("____EMPTY_KEY__".equals(key)) {if (denyEmpty) {ServerWebExchangeUtils.setResponseStatus(exchange, emptyKeyStatus);return exchange.getResponse().setComplete();} else {return chain.filter(exchange);}} else {String routeId = config.getRouteId();if (routeId == null) {Route route = (Route)exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);routeId = route.getId();}return limiter.isAllowed(routeId, key).flatMap((response) -> {Iterator var4 = response.getHeaders().entrySet().iterator();while(var4.hasNext()) {Map.Entry<String, String> header = (Map.Entry)var4.next();exchange.getResponse().getHeaders().add((String)header.getKey(), (String)header.getValue());}if (response.isAllowed()) {return chain.filter(exchange);} else {ServerHttpResponse httpResponse = exchange.getResponse();httpResponse.getHeaders().set("Content-Type", "application/json");JSONObject json = new JSONObject();json.put("code", 0);json.put("msg", "當(dāng)前請(qǐng)求人數(shù)較多,請(qǐng)稍后再訪問");DataBuffer dataBuffer = httpResponse.bufferFactory().wrap(json.toJSONString().getBytes(StandardCharsets.UTF_8));return httpResponse.writeWith(Mono.just(dataBuffer));}});}});};}private <T> T getOrDefault(T configValue, T defaultValue) {return configValue != null ? configValue : defaultValue;}
}
修改配置文件
配置我們自定義的限流過濾器
server:port: 9000
spring:application:name: gateway-learnredis:host: 192.168.31.152password: 123456timeout: 5000database: 0cloud:nacos:discovery:server-addr: http://192.168.31.152:8848gateway:routes:- id: consumer-learnuri: lb://consumer-learnpredicates:- Path=/cloudlearn/consumer/**filters:- name: NewRequestRateLimiterargs:key-resolver: "#{@apiKeyResolver}"redis-rate-limiter.replenishRate: 1 #生成令牌速率:個(gè)/秒redis-rate-limiter.burstCapacity: 2 #令牌桶容量redis-rate-limiter.requestedTokens: 1 #每次消費(fèi)的Token數(shù)量- StripPrefix=2
重新啟動(dòng)?gateway-learn
請(qǐng)求測(cè)試
5、項(xiàng)目代碼
碼云地址:https://gitee.com/wsjzzcbq/csdn-blog/tree/master/cloud-learn
至此完