代码:https://gitee.***/mindleader/spring-cloud-alibaba
docker-***pose.yml 启动windows docker docker-***pose up -d
services:
mysql1:
image: mysql:5.7
container_name: mysql-SEAT
environment:
MYSQL_ROOT_PASSWORD: 123456
ports:
- "3306:3306"
volumes:
- ./data/mysql:/var/lib/mysql
nacos:
image: nacos/nacos-server:v2.1.0
container_name: nacos-standalone
environment:
- PREFER_HOST_MODE=hostname
- MODE=standalone
- NACOS_AUTH_IDENTITY_KEY=serverIdentity
- NACOS_AUTH_IDENTITY_VALUE=security
- NACOS_AUTH_TOKEN=SecretKey012345678901234567890123456789012345678901234567890123456789
volumes:
- ./standalone-logs/:/home/nacos/logs
SpringCloud 依赖版本
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2021.0.5</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>***.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2021.0.5.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.6.13</version>
</dependency>
</dependencies>
</dependencyManagement>
Nacos
1、nacos 依赖
2、开启NacosDiscover
@EnableDiscoverClient
3、nacos 添加Nacos 服务地址
使用
List<ServiceInstance> instances = discoverClient.getInstance("service-product");
ServiceInstance serviceInstance = instances.get(0);
nacos config
引入依赖
Spring引用nacos 配置
dataid {service-name}-{profile}.{file-extension}
spring:
application:
name: service-order
cloud:
nacos:
config:
server-addr: localhost:8848 # Nacos的服务端地址
file-extension: yaml # 配置格式
# 如果需要,可以添加如下行
config:
import: "nacos:service-order-dev.yaml" # 指定Nacos配置文件 不指定报错不知道为什么
profiles:
active: dev # 开发环境
@RefreshScope 用于注解值的动态刷新
配置共享
Ribbon
@LoadBalance
service-product:#调用的提供者的名称
ribbon :
NFLoadBalancerRuleClassName: ***.***flix.loadbalancer.RandomRule
这里需要注意版本
引入依赖 但是不支持那么多种策略
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
在RestTemplate 或 WebClient上添加策略
feign
引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
@EnableFeignClients
定义 接口 @FeignClient(“service-product”)
Sentinal
流量控制、熔断降价、系统负载保护
dashboard
https://github.***/alibaba/Sentinel/wiki/Dashboard
java -Dserver.port=8089 -Dcsp.sentinel.dashboard.server=localhost:8089 -Dproject.name=sentinel-dashboard -Dnacos.ServerAddr= -jar sentinel-dashboard.jar
<dependency>
<groupId>***.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
配置监控端口
sentinel:
transport:
port: 9999
dashboard: localhost:8083
资源:要保护的东西
规则:定义什么方式保护资源
Sentinel 和 Hystrix 的区别
两者的原则是一致的,都是当一个资源出现问题时,让其快速失败,不要波及到其它服务但是在限制的手段上,确采取了完全不一样的方法:
Hystrix 采用的是线程池隔离的方式,优点是做到了资源之间的隔离,缺点是增加了线程切换的成本。
Sentinel 采用的是通过并发线程的数量和响应时间来对资源做限制。
雪崩发生的原因多种多样,有不合理的容量设计,或者是高并发下某一个方法响应变慢,亦或是某台机器的资源耗尽。我们无法完全杜绝雪崩源头的发生,只有做好足够的容错,保证在一个服务发生问题,不会影响到其它服务的正常运行。也就是"雪落而不雪崩
常见的容错思路
常见的容错思路有隔离、超时、限流、熔断、降级这几种,下面分别介绍一下。
隔离 线程池隔离
熔断状态: 关闭、开启、半熔断
流控模式:关联(让步)、链路(针对上级接口)
@SentinelResource(“message”) 指定资源
blockHandler 资源内部BlockException 处理逻辑。捕获Sentinel定义的异常
-
1 当前方法的返回值和参数要跟原方法一致
-
2但是允许在参数列表的最后加入一个参数 BlockException
fallback 定义资源内部发生Throwable
-
1 当前方法的返回值和参数要跟原方法一致
-
2但是允许在参数列表的最后加入一个参数 Throwable
流控效果:(链路)
@SentinelResource 设置资源
spring:
cloud:
sentinel:
web-context-unify: false
在这里插入图片描述
流控规则
-
资源名
-
针对来源(QPS、线程数)
降级规则
平均响应时间
RT 平均响应时间 (最大4900) -Dcsp.sentinel.statistic.max.rt=xx
时间窗口 降级多少s内
异常比列(每秒)
异常数(1min之内)
异常比列
异常数量
热点规则(根据参数、值)
参数索引 参数的序号
授权规则(区分应用来源)
黑名单
白名单
实现 RequestOriginParser接口 ,拦截请求获取应用
@***ponent
public class RequestOriginParserDefinition implements RequestOriginParser {
@Override
public String parseOrigin(HttpServletRequest httpServletRequest) {
String parameter = httpServletRequest.getParameter("serviceName");
return parameter;
}
}
系统规则
从单台机器的总体 Load、RT(ms)、入口 QPS、CPU 使用率和线程数五个维度监控应用数据,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。
自定义异常返回
实现接口BlockExceptionHandler
在这里插入图片描述
@***ponent
public class ExceptionHandlerPage implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws Exception {
httpServletResponse.setContentType("application/json;charset=utf-8");
ResponseData responseData = new ResponseData();
responseData.setCode(500);
if(e instanceof DegradeException){
responseData.setErrMsg("访问接口降级了");
}else if(e instanceof FlowException){
responseData.setErrMsg("接口限流了");
}
httpServletResponse.getWriter().write(JSONObject.toJSONString(responseData));
}
}
@Data
class ResponseData{
String errMsg;
Integer code;
}
规则持久化
fegin sentinel 整合
feign:
sentinel:
enabled: true
实现远程接口,定义错误回调类
@FeignClient(value = "service-product",fallback = ProductServiceImpl.class)
public interface ProductService {
@RequestMapping("/product/{pid}")
public Product product(@PathVariable("pid") Integer pid);
}
@***ponent
public class ProductServiceImpl implements ProductService{
@Override
public Product product(Integer pid) {
Product product = new Product();
product.setPid(1);
product.setPname("错误回调");
return product;
}
}
Spring Cloud Gateway服务网关
客户端需维护服务端各个地址
认证鉴权
跨域问题
系统统一入口 公共逻辑(认证、健全、监控、路由转发)
SpringBoot 2.0以上支持
依赖(不能引Starter-web)
配置
spring:
application:
name: api-gateway
cloud:
gateway:
routes:
- id: product_route
uri: http://localhost:8081
order: 1
predicates:
- Path=/product-serv/**
filters:
- StripPrefix=1
引入依赖(注意503可能没有引入负载均衡)
<dependency>
<groupId>***.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-loadbalancer</artifactId>
</dependency>
@EnableDiscoveryClient
配置nacos 和gaeway
spring:
application:
name: api-gateway
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
gateway:
discovery:
locator:
enabled: true
routes:
- id: order_route
uri: lb://service-product
order: 1
predicates:
- Path=/product-serv/**
- Pid=122 #自定义断言 RoutePredicateFactory
filters:
- StripPrefix=1
- Log= true #自定义过滤器 GatewayFilterFactory
order 越小优先级越高
predicate 返回真才路由
filter 修改请求和响应
断言
在这里插入图片描述
自定义断言工厂
AbstractRoutePredicateFactory<AgeRoutePredicateFactory.config>
定义类名应为配置开头 AgeRoutePredicateFactory,<> 定义参数
@***ponent
public class PidRoutePredicateFactory extends AbstractRoutePredicateFactory<PidRoutePredicateFactory.Config> {
public PidRoutePredicateFactory() {
super(Config.class);
}
@Override
public List<String> shortcutFieldOrder() {
return Collections.singletonList("pid");
}
@Override
public Predicate<ServerWebExchange> apply(Config config) {
return new Predicate<ServerWebExchange>() {
@Override
public boolean test(ServerWebExchange serverWebExchange) {
String pid = serverWebExchange.getRequest().getQueryParams().getFirst("pid");
if(StringUtils.isNotEmpty(pid)){
if(Integer.parseInt(pid)>config.getPid()){
return true;
}
}
return false;
}
};
}
@Data
@NoArgsConstructor
public static class Config {
private Integer pid;
}
}
过滤器
在请求和响应做一些操作
定义过滤工厂
@***ponent
public class LogGatewayFilterFactory extends AbstractGatewayFilterFactory<LogGatewayFilterFactory.Config> {
/**
* Status key.
*/
public static final String STATUS_KEY = "status";
/**
* The name of the header which contains http code of the proxied request.
*/
private String originalStatusHeaderName;
public LogGatewayFilterFactory() {
super(LogGatewayFilterFactory.Config.class);
}
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("handle");
}
@Override
public GatewayFilter apply(LogGatewayFilterFactory.Config config) {
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
if(config.getHandle()){
System.out.println("自定义过滤器");
}
return chain.filter(exchange);
}
};
}
public String getOriginalStatusHeaderName() {
return originalStatusHeaderName;
}
public void setOriginalStatusHeaderName(String originalStatusHeaderName) {
this.originalStatusHeaderName = originalStatusHeaderName;
}
@Data
@NoArgsConstructor
public static class Config {
// TODO: relaxed HttpStatus converter
private Boolean handle;
}
}
全局过滤器
全局过滤器 (鉴权)
@Slf4j
@***ponent
public class AuthenticationGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String token = exchange.getRequest().getHeaders().getFirst("token");
if(!StringUtils.equals(token, "123")){
log.error("认证失败");
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().set***plete();
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
}
网关限流(Gateway-Sentinel)
route维度
Api 限流
引入依赖
配置类 注入
SentinelGatewayFilter 和SentinelGatewayBlockException
package ***.grent;
import ***.alibaba.csp.sentinel.adapter.gateway.***mon.rule.GatewayFlowRule;
import ***.alibaba.csp.sentinel.adapter.gateway.***mon.rule.GatewayRuleManager;
import ***.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter;
import ***.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandler;
import ***.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
import ***.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.codec.ServerCode***onfigurer;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.reactive.result.view.ViewResolver;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import javax.annotation.PostConstruct;
import java.util.*;
@Configuration
public class GatewayConfiguration {
private final List<ViewResolver> viewResolvers;
private final ServerCode***onfigurer serverCode***onfigurer;
public GatewayConfiguration(ObjectProvider<List<ViewResolver>>
viewResolversProvider,
ServerCode***onfigurer serverCode***onfigurer) {
this.viewResolvers =
viewResolversProvider.getIfAvailable(Collections::emptyList);
this.serverCode***onfigurer = serverCode***onfigurer;
}
// 初始化一个限流的过滤器
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public GlobalFilter sentinelGatewayFilter() {
return new SentinelGatewayFilter();
}
// 配置初始化的限流参数
@PostConstruct
public void initGatewayRules() {
Set<GatewayFlowRule> rules = new HashSet<>();
rules.add(
new GatewayFlowRule("product_route") //资源名称,对应路由id
.setCount(1) // 限流阈值
.setIntervalSec(1) // 统计时间窗口,单位是秒,默认是 1 秒
);
GatewayRuleManager.loadRules(rules);
}
// 配置限流的异常处理器
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SentinelGatewayBlockExceptionHandler
sentinelGatewayBlockExceptionHandler() {
return new SentinelGatewayBlockExceptionHandler(viewResolvers,
serverCode***onfigurer);
}
// 自定义限流异常页面
@PostConstruct
public void initBlockHandlers() {
BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {
public Mono<ServerResponse> handleRequest(ServerWebExchange
serverWebExchange, Throwable throwable) {
Map map = new HashMap<>();
map.put("code", 0);
map.put("message", "接口被限流了");
return ServerResponse.status(HttpStatus.OK).
contentType(MediaType.APPLICATION_JSON_UTF8).
body(BodyInserters.fromObject(map));
}
};
GatewayCallbackManager.setBlockHandler(blockRequestHandler);
}
}
Api 分组
@PostConstruct
public void initGatewayRules(){
// Set<GatewayFlowRule> rules = new HashSet<>();
// rules.add(new GatewayFlowRule("product_route")
// .setCount(1)
// .setIntervalSec(1)
// );
// GatewayRuleManager.loadRules(rules);
Set<GatewayFlowRule> rules = new HashSet<>();
rules.add(new GatewayFlowRule("product_api1").setCount(1).setIntervalSec(1));
// rules.add(new GatewayFlowRule("product_api2").setCount(1).setIntervalSec(1));
GatewayRuleManager.loadRules(rules);
}
@PostConstruct
private void initCustomizedApis() {
Set<ApiDefinition> definitions = new HashSet<>();
ApiDefinition api1 = new ApiDefinition("product_api1")
.setPredicateItems(new HashSet<ApiPredicateItem>() {{
// 以/product-serv/product/api1 开头的请求
add(new ApiPathPredicateItem().setPattern("/product-serv/product/**").
setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
}});
// ApiDefinition api2 = new ApiDefinition("product_api2")
// .setPredicateItems(new HashSet<ApiPredicateItem>() {{
// add(new ApiPathPredicateItem().setPattern("/product_serv/product/api2/demo1"));
// }});
definitions.add(api1);
// definitions.add(api2);
GatewayApiDefinitionManager.loadApiDefinitions(definitions);
}
待处理问题
nacos 如何实现权限认证?
ribbon如何配置负载均衡策略 ?
接口限流同时设置BlockExceptionHandler 和blockException 为啥两个都会生效?
共享配置为啥不生效 sentinel为啥持久化到nacos不生效 ?