Starter
服务注册中心
- Eureka (Netflix)
- Zookeeper
- Consul
- Nacos (Alibaba)
服务配置
- Config
- Nacos (Alibaba)
服务总线
- Bus
- Nacos (Alibaba)
服务调用
- Ribbon (Netflix)
- LoadBalancer (SpringCloud)
- Feign
- OpenFeign (SpringCloud)
服务降级
- Hystrix (Netflix)
- Resilience4j
- Sentinel (Alibaba)
服务网关
- Zuul (Netflix)
- Zuul2 (Netflix)
- Gateway (SpringCloud)
服务注册&配置中心:Nacos (Spring Cloud Alibaba)
- 服务中心 (同类实现: Eureka,Zookeeper,Consual)
- 服务配置和总线 (同类实现:Config & Bus)
https://github.com/alibaba/nacos/releases https://nacos.io/zh-cn/docs/quick-start.html https://spring-cloud-alibaba-group.github.io/github-pages/hoxton/en-us/index.html
# 安装
unzip nacos-server-$version.zip 或者 tar -xvf nacos-server-$version.tar.gz
cd nacos/bin
# 打开
sh startup.sh -m standalone
# 关闭
sh shutdown.sh
Nacos Server
link:http://127.0.0.1:8848/nacos/ 默认账号和密码为:nacos/nacos
注意:Nacos Server 的数据源是用 Derby 还是 MySQL 完全是由其运行模式决定的
- standalone 的话仅会使用 Derby,即使在 application.properties 里边配置 MySQL 也照样无视
- cluster 模式会自动使用 MySQL,这时候如果没有 MySQL 的配置,是会报错的
- 不支持 MySQL 8.0 版本
Nacos 服务注册
parent pom
<properties> <spring.cloud.alibaba>2.2.1.RELEASE</spring.cloud.alibaba> </properties> <dependencyManagement> <dependencies> <!-- Spring Cloud Alibaba --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>${spring.cloud.alibaba}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
provider/consumer client pom
<!-- 服务发现 --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency>
provider
- application.yml
server: port: 7010 spring: application: name: nacos-provider cloud: nacos: discovery: server-addr: localhost:8848 # ${NACOS_SERVER:8848}
main
@SpringBootApplication @EnableDiscoveryClient public class NacosProviderApp { public static void main(String[] args){ SpringApplication.run(NacosProviderApp.class,args); } @RestController public class HelloController { @GetMapping("/hi") public Object hello(){ return "Hello";} } }
- application.yml
consumer
- application.yml
server: port: 7020 spring: application: name: nacos-consumer cloud: nacos: discovery: server-addr: localhost:8848 # ${NACOS_SERVER:8848}
main
@SpringBootApplication @EnableDiscoveryClient public class NacosConsumerApp { public static void main(String[] args){ SpringApplication.run(NacosConsumerApp.class,args); } @Bean @LoadBalanced // 使用serviceId访问则一定要加上,否则找不到服务;不加此注解,则可通过普通的ip:host的URL来访问服务 public RestTemplate restTemplate(){ return new RestTemplate(); } @RestController public class HelloController { private String remoteServiceId="nacos-provider"; @Autowired private RestTemplate restTemplate; @GetMapping("/") public Object index(){ return "Index"; } @GetMapping("/hi") public Object hello(){ String url = String.format("http://%s/hi",remoteServiceId); String result = restTemplate.getForObject(url,String.class); return "Say: "+result; } } }
- application.yml
Test
- provider: Get http://localhost:7010/hi
- consumer: Get http://localhost:7020/
- consumer: Get http://localhost:7020/hi
Nacos 服务配置
provider/consumer client pom
<!-- 服务配置 --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </dependency> <!-- spring cloud 2020,需加上这个才能读取bootstrap.yml,Nacos Config才能正常使用--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bootstrap</artifactId> </dependency>
bootstrap.yml (或者 bootstrap.properties) 优先级高于application.yml,nacos相关配置需配在此处
server: port: 7010 spring: application: name: nacos-provider cloud: nacos: discovery: server-addr: localhost:8848 # ${NACOS_SERVER:8848} | 注意:必须有端口号 config: server-addr: localhost:8848 file-extension: yaml # 支持properties(默认),yaml | 注意:nacos识别yaml,yml会报错 namespace: dear-v1 # 命名空间ID,不是命名空间名称 | 注意:只能配置一个命名空间 group: NACOS_GROUP # 默认DEFAULT_GROUP | 注意:只能配置一个GROUP # => 加载的dataId为: ${prefix}-${spring.profiles.active}.${file-extension} # prefix 默认为 spring.application.name # spring.profiles.active为空时 => 对应的连接符 - 也将不存在,dataId 的拼接格式变成 ${prefix}.${file-extension} ext-config: # 列表,优先级都低于上面正常group&namespace下的配置,列表的优先级是下标越大优先级越高 + data-id: logback-spring.yaml group: LOG_GROUP refresh: true + data-id: external.yaml group: EXTERNAL_GROUP refresh: true
application.yml (optional):
spring.profiles.active
可在运行时指定spring: profiles: active: dev
Controller
@RestController @RefreshScope // 实现配置自动更新 public class ConfigController{ @Value("${provider.name:UnKnow}") private String providerName; @Value("${welcome:UnKnow}") private String welcome; @GetMapping("/config") public Object config(){ return "ProviderName: "+providerName +"; Welcome: "+welcome; } }
Nocos 配置界面:
http://127.0.0.1:8848/nacos
,添加配置文件- namesapce: dear-v1
- group:
- EXTERNAL_GROUP
- external.yaml
welcome: External Welcome V1
- external.yaml
- LOG_GROUP
- logback-spring.yaml
logging: config: classpath:logback-spring-${spring.profiles.active}.xml path: logs package: com.cj.dear
- logback-spring.yaml
- NACOS_GROUP
- nacos-provider.yaml
provider: name: Default Nacos Provider
- nacos-provider-dev.yaml
provider: name: Dev Nacos Provider
- nacos-provider-prod.yaml
provider: name: Prod Nacos Provider
- nacos-provider.yaml
- EXTERNAL_GROUP
启动运行,访问
http://localhost:7010/config
- Run Configuration => program arguments
--spring.profiles.active=prod
- 使用命令行:
java -jar nacos-provider.jar --spring.profiles.active=dev
- Run Configuration => program arguments
链路追踪:Sleuth & Zipkin
Sleuth
- trace: 整个调用链路,一系列span组成一个树状结构 -- tranceID
- span: 每个最小的工作单元(一次微服务调用)-- spanID
Sleuth 帮助记录这些traceID,spanID Zipkin Twitter的一个开源项目,收集链路的跟踪数据,提供可插拔的数据存储方式,UI直观查看分析
Zipkin
https://zipkin.io/ https://github.com/openzipkin/openzipkin.github.io https://github.com/openzipkin/zipkin
核心组件:
- Collector 收集链路信息
- Storage 存储
- Restful API
- Web UI
分两端:
- 服务端
- Zipkin Server 下载 https://zipkin.io/pages/quickstart
- 启动
java -jar zipkin.jar
或使用dockerdocker run -d -p 9411:9411 openzipkin/zipkin
- visit
http://localhost:9411
- 客户端 发送链路信息给服务端
- HTTP报文方式发送
- 消息总线方式(如RabbitMQ)
Sample
pom.xml (所有微服务)
<!-- 链路追踪 Sleuth--> <!--<dependency>--> <!--<groupId>org.springframework.cloud</groupId>--> <!--<artifactId>spring-cloud-starter-sleuth</artifactId>--> <!--</dependency>--> <!-- Zipkin Client(dependence 已包含了sleuth相关依赖) 收集链路信息,发送给Zipkin Server --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zipkin</artifactId> </dependency>
application.xml (所有微服务)
spring: zipkin: base-url: http://localhost:9411 # Zipkin Server端地址 sender: type: web # 数据用HTTP方式传送给Zipkin Server端的方式 sleuth: sampler: probability: 0.5 # 数据采样比(0~1,默认0.1)
Test (多请求一些链接,然后去 http://localhost:9411 查看可视化界面)
优化:链路数据持久化(默认存储在内存)
https://github.com/openzipkin/zipkin/tree/master/zipkin-server
Sample: 持久化到MySQL
准备数据库表
重启 Zipkin Server, 启动时传递相关配置参数
- https://github.com/openzipkin/zipkin/tree/master/zipkin-server
- https://blog.csdn.net/qq_42714869/article/details/90052903
- https://blog.csdn.net/qq_42714869/article/details/90052903
java -jar zipkin.jar --STORAGE_TYPE=mysql --MYSQL_HOST=localhost --MYSQL_TCP_PORT=3306 --MYSQL_USER=cj --MYSQL_PASS=123 --MYSQL_DB=dear_zipkin
优化:链路数据传输方式(默认Http方式发送给Zipkin Server)
Sample: 使用消息中间件
准备RabbitMQ服务器 http://localhost:15672
修改Zipkin Server,从RabbitMQ中拉取信息
- https://github.com/openzipkin/zipkin/tree/master/zipkin-collector/rabbitmq
java -jar zipkin.jar --STORAGE_TYPE=mysql --MYSQL_HOST=localhost --MYSQL_TCP_PORT=3306 --MYSQL_USER=cj --MYSQL_PASS=123 --MYSQL_DB=dear_zipkin --RABBIT_ADDRESSES=localhost:5672 --RABBIT_USER=admin --RABBIT_PASSWORD=admin --RABBIT_VIRTUAL_HOST=my_vhost
- https://github.com/openzipkin/zipkin/tree/master/zipkin-collector/rabbitmq
修改微服务Zipkin client 相关配置,将采集信息发送RabbitMQ
服务调用:OpenFeign (Spring Cloud)
服务调用(同类实现:Ribbon,LoadBalancer,Feign)
https://docs.spring.io/spring-cloud-openfeign/docs/current/reference/html/
- 依赖注册中心
- 使用本地负载均衡器
- 常见算法:轮询,随机,固定IP,权重,Hash一致性,...
pom.xml
<!-- 服务发现 Nacos--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> <!-- 排除ribbon, 使用Springcloud loadbalancer (f否则启动报错)--> <exclusions> <exclusion> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-ribbon</artifactId> </exclusion> </exclusions> </dependency> <!-- Spring Cloud OpenFeign --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-loadbalancer</artifactId> </dependency>
application.yml
server: port: 7020 spring: application: name: nacos-consumer cloud: nacos: discovery: server-addr: localhost:8848 # ${NACOS_SERVER:8848} namespace: dear-v1 loadbalancer: ribbon: enabled: false # config feign -- optional feign: client: config: nacos-provider: # FeignClient名 或使用 default connectTimeout: 5000 readTimeout: 5000 loggerLevel: FULL logging: level: com.cj.dear.nacos: debug config: classpath:logback-spring-dev.xml path: logs
激活Feign
@EnableFeignClients
@SpringBootApplication @EnableDiscoveryClient @EnableFeignClients public class NacosConsumerApp { public static void main(String[] args){ SpringApplication.run(NacosConsumerApp.class,args); } }
FeignClient (注意:feign要求指明确定返回值类,才能正确解析方法返回值,即方法返回值不能使用Object!)
@FeignClient("nacos-provider") public interface ProviderFeignClient{ @GetMapping("/hi") String hello(); @GetMapping("/config") String config(); }
Usage
@RestController public class HelloController{ @Autowired private ProviderFeignClient providerFeignClient; @GetMapping("callProvider") public Object callProvider(@RequestParam(required = false, name="func",defaultValue = "hi")String func){ System.out.println("func:"+func); if(func.equals("hi")) return "Call "+func+" Result: "+providerFeignClient.hello(); return "Call "+func+" Result: "+providerFeignClient.config(); } }
Test (启动N个nacos-provider)
服务降级:Sentinel(Spring Cloud Alibaba)
服务降级(同类实现:Hystrix,Resilience4j)
https://github.com/alibaba/Sentinel/
https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D
高并发问题 由于请求积压,造成服务瘫痪,服务与服务之间存在依赖性,故障传播,连锁反应,造成整个系统崩溃 (雪崩)=》 处理方案:
- 服务隔离
- 线程池隔离(每个服务接口有自己独立的线程池,互不影响)
- 信号量隔离(计数器,最大阈值,超过则拒绝)
- 服务熔断,降级
- 服务限流
Sentinel 控制台
https://github.com/alibaba/Sentinel/wiki/%E6%8E%A7%E5%88%B6%E5%8F%B0
启动Sentinel控制台
java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar
- 启动时加入 JVM 参数
-Dcsp.sentinel.dashboard.server=consoleIp:port
指定控制台地址和端口。 - 若启动多个应用,则需要通过
-Dcsp.sentinel.api.port=xxxx
指定客户端监控 API 的端口(默认是 8719)
- 启动时加入 JVM 参数
visit: http://localhost:8080/
- username/password: sentinel/sentinel
Sample
pom.xml
<!-- Spring Cloud Aalibaba Sentinel --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency>
application.yml
spring: cloud: sentinel: transport: dashboard: localhost:8080 eager: true # 默认false,懒加载
java
@GetMapping("callProvider") @SentinelResource(value="callProvider", blockHandler="callProviderBlockHandler",fallback="callProviderFallbackHandler") public Object callProvider(@RequestParam(required = false, name="func",defaultValue = "hi")String func){ System.out.println("func:"+func); if(func.equals("hi")) return "Call "+func+" Result: "+providerFeignClient.hello(); return "Call "+func+" Result: "+providerFeignClient.config(); } // sentinel // 1. 限流熔断 blockHandler(捕获BlockException) 注意:参数(最后Optional: BlockException),返回值类型需与原方法一致 // 2. 异常降级 fallback(捕获其他异常) 注意:参数,返回值类型需与原方法一致(Optional: 可加Throwable) public Object callProviderBlockHandler(String func,BlockException ex){ return "CallProvider:"+func+"; BlockHandler"; } public Object callProviderFallbackHandler(String func){ return "CallProvider:"+func+"; FallBack"; }
- 注意:blockHandler和fallback都配置了,则被限流降级而抛出BlockException时只会进入blockHandler处理逻辑
启动服务
访问Sentinel控制台,配置限流规则,测试
限流规则持久化
一条限流规则的组成:
- resource : 资源名(限流规则的作用对象),唯一,默认为请求路径
- limitApp : 流控针对的调用来源(Sentinel可针对调用者进行限流,填写微服务名,默认default,即不区分调用来源)
- grade : 阈值类型
- 0: 线程数
- 1: QPS
- count : 单机限流阈值
- strategy : 流控模式
- 0: 直接,API达到限流条件时,直接限流
- 1: 关联,关联资源达到阈值时,限流自己
- 2: 链路,只记录指定链路上的流量(指定资源从入口资源进来的流量,达到阈值则进行限流,API级别的针对来源)
- controlBehavior : 流控效果
- 0: 快速失败,直接拒绝,抛异常
- 1: Warm up,根据codeFactor(冷加载因子,默认3)的值,从阈值/codeFactor,经过预热时长,才达到设置的QPS值
- 2: 排队等待,匀速排队,让请求匀速通过(阈值类型必须为QPS才可)
- clusterMode : 是否集群
Sample: 本地文件加载限流规则
resources/flowRule.json
[ { "resource": "callProvider", "limitApp": "default", "grade": 0, "count": 2, "strategy": 0, "controlBehavior": 0 } ]
resources/application.yml
spring: application: name: nacos-consumer cloud: sentinel: transport: dashboard: localhost:8080 eager: true # 默认false,懒加载 datasource: ds1: file: # 配置从本地文件读取限流规则 dataType: json ruleType: flow file: classpath:flowRule.json
启动服务
visit sentinel控制台查看规则 http://localhost:8080/
Sample: 从nacos加载限流规则
visit nacos控制台,配置
nacos-consumer-sentinel.json
pom.xml: 增加
sentinel-datasource-nacos
包<!-- Spring Cloud Aalibaba Sentinel --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <!-- Sentinel从nacos获取限流规则--> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> </dependency>
application.yml
spring: application: name: nacos-consumer cloud: sentinel: transport: dashboard: localhost:8080 eager: true # 默认false,懒加载 datasource: ds1: # file: # 配置从本地文件读取限流规则 # dataType: json # ruleType: flow # file: classpath:flowRule.json nacos: # 配置从nacos中获取 dataType: json ruleType: flow serverAddr: localhost:8848 namespace: dear-v1 dataId: nacos-consumer-sentinel.json groupId: DEFAULT_GROUP
启动服务
visit sentinel控制台查看规则 http://localhost:8080/
服务网关:Gateway(Spring Cloud)
服务网关(同类实现:Zuul1,Zuul2)
- Netflix Zuul1: 阻塞Servlet
- Netflix Zuul2: 基于Netty,非阻塞,支持长链接
- SpringCloud Gateway: 基于Netty,Project Reactor响应式非阻塞,支持长链接,使用WebFlux,Filter链方式
可应用解决问题:
- 统一微服务登录认证问题,权限控制,减少代码冗余
- 跨域问题
- 保护服务,限流,黑白名单等
- 统一日志
核心概念:
- 路由 routes: 一个route由id,uri,predicates,filters组成
- 断言 predicates : 定义匹配来自HttpRequest的任何信息(断言函数输入类型是Spring5.0的ServerWebExchange)
- 过滤器 filters : 两种类型 GatewayFilter / GlobalFilter
Sample
pom.xml
<!-- Spring Cloud Gateway | 注意:依赖WebFlux,所以不要引入spring-boot-starter-web,,不然会与SpringMVC冲突,启动报错--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <!--<dependency>--> <!--<groupId>org.springframework.boot</groupId>--> <!--<artifactId>spring-boot-starter-web</artifactId>--> <!--</dependency>--> <!-- Spring Cloud 2x 有这个问题 解决报错:Parameter 0 of method loadBalancerWebClientBuilderBeanPostProcessor in org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerBeanPostProcessorAutoConfiguration required a bean of type 'org.springframework.cloud.client.loadbalancer.reactive.DeferringLoadBalancerExchangeFilterFunction' that could not be found. --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-loadbalancer</artifactId> </dependency>
application.yml
spring: profiles: active: dev cloud: gateway: discovery: # 简化路由配置,自动根据serviceId进行路由转发 eg: http://localhost:81/nacos-consumer/hi 会自动转发到 http://nacos-consumer/hi locator: enabled: true # 开启根据serviceId自动转发|打开后,可使用serviceId,负载均衡定位访问服务 lower-case-service-id: true # 微服务名称以小写形式呈现 routes: + id: baidu uri: https://www.baidu.com filters: + SetPath=/?{args} predicates: + Path=/baidu/{args} + id: nacos-provider # 路由的ID,没有固定规则但要求唯一 uri: lb://nacos-provider/ # 匹配后提供服务的路由地址(lb:// 根据serviceId,负载均衡策略确定具体服务地址) filters: + StripPrefix=1 # 过滤器,不加时定位服务为:lb://nacos-provider/provider/**;设置去掉第一段,则变成lb://nacos-provider/** predicates: + Path=/provider/** # 断言,路径相匹配的进行路由
自定义GlobalFilter
@Component public class GatewayTokenFilter implements GlobalFilter { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { String token = exchange.getRequest().getQueryParams().getFirst("token"); if(StringUtils.isEmpty(token)){ ServerHttpResponse response = exchange.getResponse(); response.setStatusCode(HttpStatus.FORBIDDEN); String msg = "Gateway:Forbidden to visit"; DataBuffer df = response.bufferFactory().wrap(msg.getBytes()); return response.writeWith(Mono.just(df)); } return chain.filter(exchange); } }
Test
- http://localhost:81/baidu/dd =》 Gateway:Forbidden to visit
- http://localhost:81/baidu/dd?token=ee => 看到百度页面
结合Nacos
server: port: 81 spring: application: name: cloud-gateway cloud: nacos: discovery: server-addr: localhost:8848 namespace: dear-v1 config: server-addr: localhost:8848 file-extension: yaml namespace: dear-v1 group: CLOUD_GROUP ext-config: + data-id: logback-spring.yaml group: LOG_GROUP refresh: true + data-id: external.yaml group: EXTERNAL_GROUP refresh: true
main
@SpringBootApplication @EnableDiscoveryClient public class CloudGatewayApp { public static void main(String[] args){ SpringApplication.run(CloudGatewayApp.class,args); } @Value("${provider.name:UnKnow}") private String providerName; @RestController @RefreshScope public class HelloController{ @Value("${provider.name:UnKnow}") private String providerName; @GetMapping("/hi") // http://localhost:81/hi => Gateway: Hello | ProviderName: Default Cloud Gateway V5 dev public String Hello() { return "Gateway: Hello | ProviderName: "+providerName; } } }
Test (需启动 Nacos Server,nacos-provider工程)
- http://localhost:81/hi => Gateway: Hello | ProviderName: Default Cloud Gateway V5 dev
- http://localhost:81/provider/hi =》 Gateway:Forbidden to visit
- http://localhost:81/provider/hi?token=11 =》 Hello
- http://localhost:81/provider/config?token=22 =》 ProviderName: Dev Nacos Provider; Welcome: External Welcome V1
- http://localhost:81/nacos-consumer/hi?token=11 =》 Say: Hello
predicates 断言
RoutePredicateFactory
- datetime 请求时间校验: After,Before,Between
- Cookie 请求Cookie校验
- Header 请求头校验
- Host 请求Host校验
- Method 请求方法校验
- Path 请求路径校验
- Queryparam 请求参数校验
- RemoteAddr 请求远程地址校验
开启微服务名称转发
spring.cloud.gateway.discovery.locator.enable=true
spring:
cloud:
gateway:
discovery: # 简化路由配置,自动根据serviceId进行路由转发
locator:
enabled: true # 开启根据serviceId自动转发|打开后,可使用serviceId,负载均衡定位访问服务
lower-case-service-id: true # 微服务名称以小写形式呈现
开启后,无需配置routes,即可实现
visit http://localhost:81/nacos-consumer/hi
自动转发到 http://nacos-consumer/hi
Test: http://localhost:81/nacos-consumer/hi?token=11 =》 Say: Hello
过滤器
- 生命周期:
pre
post
- 类型:
GatewayFilter
局部过滤器,应用到单一路由,或一个分组路由上- Gateway中已内置了很多不同类型的局部过滤器,可直接使用 (实现类以
GatewawyFilterFactory
结尾)
- Gateway中已内置了很多不同类型的局部过滤器,可直接使用 (实现类以
GlobalFilter
全局过滤器,应用到所有路由上,eg 内置的一些全局过滤器- LoadBalancer 负载均衡相关过滤器: LoadBalancerClientFilter
- HttpClient http客户短相关:NettyRoutingFilter
- Websocket :WebsocketRoutingFilter
- ForwardPath 路径转发:ForwardPathFilter
- RouteToRequestUrl 路由Url相关:RouteToRequestUrlFilter
- WebClient :WebClientHttpRoutingFilter,WebClientWriteResponseFilter
自定义GlobalFilter: 实现GlobalFilter, Ordered(Optional,越小优先级越高)
网关限流
常见限流算法:
- 计数器
- 每个单位时间内 => 累计统计单位时间内请求数量 => 是否超过单位时间内最大的请求数量(阈值)=> 一个单位时间结束,重置0重新累计
- 缺点:单位时间内不平滑
- 漏桶算法
- 内部维护一个队列(桶容量)=> 按一定频率输出(rate)=> 超出桶容量的丢弃/拒绝
- 都按一定频率输出,自己可能积压太多请求,自身压力大,对后面微服务是种浪费
- 令牌算法(漏桶算法改进版,允许一定程度的突发调用)
- 内部维护桶(可负载最大容量)=> 按一定速率往桶里存放令牌(满了则不生成令牌) => 请求从桶里获取令牌成功则可进行下步操作 => 请求无法获取令牌则等待或失败
- 可快速转发为后面的微服务,不用积压太多请求,保护自己
Spring Cloud Gateway:
- 基于Filter:官方提供了基于令牌桶的限流支持
- 基于内置的局部过滤器工厂
RequestRateLimiterGatewayFilterFactory
实现(通过Redis和lu脚本结合的方式实现)
- 基于内置的局部过滤器工厂
- 基于Sentinel的限流
网关限流:基于Filter
pom.xml
<!-- 网关限流: 基于Filter 实现 --> <!-- 监控依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!-- Reids 相关依赖(基于reactive的redis依赖)) --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis-reactive</artifactId> </dependency>
application.yml 添加redis & 限流配置
spring: redis: host: localhost port: 6379 database: 1 password: 123456 cloud: gateway: routes: + id: nacos-provider uri: lb://nacos-provider/ filters: + name: RequestRateLimiter # 使用限流过滤器 args: key-resolver: '#{@queryParamKeyResolver}' # 限流Key解析器(即基于什么限流),使用SpEL表达式@xxx从Spring容器中获取name为xxx的Bean对象, 查看自定义的RateLimiteConfig中注入的Bean redis-rate-limiter.replenishRate: 1 # 向令牌桶中填充token的速率 redis-rate-limiter.burstCapacity: 3 # 令牌桶容量 redis-rate-limiter.requestedTokens: 1 # 一个请求消耗几个令牌 + StripPrefix=1 predicates: + Path=/provider/**
编写KeyResolver
@Configuration public class KeyResolverConfig { // 基于请求路径限流 @Bean @Primary public KeyResolver pathKeyResolver(){ return new KeyResolver(){ @Override public Mono<String> resolve(ServerWebExchange exchange) { return Mono.just(exchange.getRequest().getPath().toString()); } }; } // 基于请求参数限流 @Bean public KeyResolver queryParamKeyResolver(){ return exchange ->{ String token = exchange.getRequest().getQueryParams().getFirst("token"); return Mono.just(token!=null?token:exchange.getRequest().getPath().toString()); }; } }
Test
- Redis Cli: 选择数据库,打开监听
> select 1 > monitor
- vist:
- Redis Cli: 选择数据库,打开监听
网关限流:基于Sentinel
https://www.cnblogs.com/yinjihuan/p/10772558.html
https://github.com/alibaba/Sentinel/wiki/API-Gateway-Flow-Control
pom.xml
<!-- 网关限流: 基于Sentinel 实现 --> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-spring-cloud-gateway-adapter</artifactId> </dependency>
application.yml
spring: cloud: gateway: discovery: locator: enabled: true lower-case-service-id: true routes: + id: baidu uri: https://www.baidu.com filters: + SetPath=/?{args} predicates: + Path=/baidu/{args} + id: nacos-provider uri: lb://nacos-provider/ predicates: + Path=/provider/** filters: + StripPrefix=1
Configuration
@Configuration public class SentinelGatewayConfig { private final List<ViewResolver> viewResolvers; private final ServerCodecConfigurer serverCodecConfigurer; public SentinelGatewayConfig(ObjectProvider<List<ViewResolver>> viewResolversProvider, ServerCodecConfigurer serverCodecConfigurer) { this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList); this.serverCodecConfigurer = serverCodecConfigurer; } // 配置限流异常处理器 @Bean @Order(-1) public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() { // Register the block exception handler for Spring Cloud Gateway. return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer); } // 配置限流过滤器 @Bean @Order(Ordered.HIGHEST_PRECEDENCE) public GlobalFilter sentinelGatewayFilter() { // By default the order is HIGHEST_PRECEDENCE return new SentinelGatewayFilter(); } // Optinal: 配置一些初始化的限流规则 // Test: http://localhost:81/provider/hi?token=22 @PostConstruct public void initGatewayRules(){ Set<GatewayFlowRule> rules = new HashSet<>(); // 路由ID,单位时间限流阈值,单位时间 // rules.add(new GatewayFlowRule("nacos-provider").setCount(2).setIntervalSec(1)); // 限流分组名,单位时间限流阈值,单位时间 rules.add(new GatewayFlowRule("providerApis").setCount(2).setIntervalSec(1)); GatewayRuleManager.loadRules(rules); } // Optinal: 自定义限流分组 @PostConstruct public void initCustomizedApis(){ Set<ApiDefinition> definitions = new HashSet<>(); Set<ApiPredicateItem> predicateItems = new HashSet<>(); predicateItems.add(new ApiPathPredicateItem() .setPattern("/provider/**") .setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX) ); ApiDefinition def = new ApiDefinition("providerApis") .setPredicateItems(predicateItems); definitions.add(def); GatewayApiDefinitionManager.loadApiDefinitions(definitions); } // Optinal: 自定义限流处理器 @PostConstruct public void initBlockHandlers(){ BlockRequestHandler handler = new BlockRequestHandler() { @Override public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) { Map map = new HashMap(); map.put("code","001"); map.put("message","不好意思,限流了哦"); return ServerResponse.status(HttpStatus.OK) .contentType(MediaType.APPLICATION_JSON) .body(BodyInserters.fromValue(map)); } }; GatewayCallbackManager.setBlockHandler(handler); } }
网关高可用
Ngnix + 网关集群
Ngnix 配置
// gateway集群 upstream gateway{ server 127.0.0.1:81; server 127.0.0.1:82; } Server{ listen 80; server_name localhost; # 路由 location / { proxy_pass http://gateway; } }
启动两个网关实例,端口分别为81,82