# springcloud **Repository Path**: verlet/springcloud ## Basic Information - **Project Name**: springcloud - **Description**: Springcloud 2020.xx环境 - **Primary Language**: Java - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 2 - **Created**: 2020-07-04 - **Last Updated**: 2024-05-13 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ## Springcloud 2023.xx 环境搭建 ### 一. 注册中心和配置中心 Nacos - 服务发现和服务健康监测: 支持基于 DNS 和基于 RPC 的服务发现, 支持对服务的实时的健康检查, 阻止向不健康的主机或服务实例发送请求; - 动态配置服务: 动态配置服务可以让您以中心化、外部化和动态化的方式管理所有环境的应用配置和服务配置; - 动态DNS服务: 动态 DNS 服务支持权重路由, 让您更容易地实现中间层负载均衡、更灵活的路由策略、流量控制以及数据中心内网的简单 DNS 解析服务; - 服务及其元数据管理: 支持从微服务平台建设的视角管理数据中心的所有服务及元数据. ### 二. 服务网关 Gateway - 基于 Spring Framework 5, Project Reactor 和 Spring Boot 2.0 进行构建; - 动态路由:能够匹配任何请求属性; - 可以对路由指定 Predicate(断言)和 Filter(过滤器); - 集成 CircuitBreaker的断路器功能; - 集成 Spring Cloud 服务发现功能; - 易于编写的 Predicate(断言)和 Filter(过滤器); - 请求限流功能; - 支持路径重写。 > * Route(路由): 路由是构建网关的基本模块, 它由ID, 目标URI, 一系列的断言和过滤器组成, 如果断言为 true 则匹配该路由; > * Predicate(断言): 指的是 Java 8 的 Function Predicate. 输入类型是Spring框架中的 ServerWebExchange. 这使开发人员可以匹配 HTTP 请求中的所有内容, 例如请求头或请求参数, **如果请求与断言相匹配,则进行路由**; > * Filter(过滤器): 指的是Spring框架中 GatewayFilter 的实例, 使用过滤器, **可以在请求被路由前后对请求进行修改**。 ##### 自定义过滤器 - 自定义GatewayFilter有两种实现方式 1.直接实现GatewayFilter接口, 并在RouteLocatorBuilder加上配置 ```java @Bean public RouteLocator customerRouteLocator(RouteLocatorBuilder builder) { // GatewayFilter 需要配置上加入到路由 return builder.routes().route("customer_filter_router", r -> r.order(0).path("/authFilter/**") .filters(f -> f.filter(new AuthorizeGatewayFilter(stringRedisTemplate)) .addResponseHeader("X-Response-Default-Foo", "Default-Bar")) .uri("lb://web")).build(); } ``` 2.继承AbstractGatewayFilterFactory类, 并在配置文件配置 ```yaml - id: authorize_filter_router uri: lb://web order: -2 predicates: - Path=/authorizeFilter/** filters: - name: Authorize args: withParams: true ``` - 实现GlobalFilter接口 不需要配置文件中配置, 系统初始化时加载, 作用在所有的路由上,最终通过GatewayFilterAdapter包装成GatewayFilterChain可识别的过滤器 ##### 查看路由配置 添加Spring Boot Actuator依赖并将 gateway 端点暴露 端点都在路径 /actuator/gateway/, 例如routes全路径/actuator/gateway/routes | ID | HTTP Method | Description | | ------------- | --------------- | -------------------- | | globalfilters | GET | 展示所有的全局过滤器 | | routefilters | GET | 展示所有的过滤器工厂(GatewayFilterFactory)| | refresh | POST[无消息体] | 清空路由缓存 | | routes | GET | 展示路由列表 | | routes/{id} | GET | 展示指定id的路由信息 | | routes/{id} | POST[消息体如下] | 新增一个路由 | | routes/{id} | DELETE[无消息体] | 删除一个路由 | 其中,要想动态添加路由配置,只需发送POST请求,消息体如下: ```json { "predicates": [ { "name": "Path", "args": { "_genkey_0": "/test" } } ], "filters": [ { "name": "AddRequestHeader", "args": { "_genkey_0": "X-Request-Foo", "_genkey_1": "Bar" } } ], "uri": "https://www.google.com", "order": 0 } ``` ##### Filters顺序 1. 自定义GlobalFilter实现了Ordered接口或者使用@Order注解,那么order就是给定的值,否则没有order。 ```java // FilteringWebHandler private static List loadFilters(List filters) { return filters.stream().map(filter -> { GatewayFilterAdapter gatewayFilter = new GatewayFilterAdapter(filter); if (filter instanceof Ordered) { int order = ((Ordered) filter).getOrder(); return new OrderedGatewayFilter(gatewayFilter, order); } return gatewayFilter; }).collect(Collectors.toList()); } ``` 2. 自定义GatewayFilter实现了Ordered接口或者使用@Order注解,那么order就是给定的值,否则根据定义的顺序(从1开始)。 ```java // RouteDefinitionRouteLocator#loadGatewayFilters(String id, List filterDefinitions) ArrayList ordered = new ArrayList<>(filters.size()); for (int i = 0; i < filters.size(); i++) { GatewayFilter gatewayFilter = filters.get(i); if (gatewayFilter instanceof Ordered) { ordered.add(gatewayFilter); } else { ordered.add(new OrderedGatewayFilter(gatewayFilter, i + 1)); } } ``` Gateway 把他们结合起来做一个排序,对于没有order的filter,order默认为Ordered.LOWEST_PRECEDENCE(2147483647) ```java public Mono handle(ServerWebExchange exchange) { Route route = exchange.getRequiredAttribute(GATEWAY_ROUTE_ATTR); List gatewayFilters = route.getFilters(); List combined = new ArrayList<>(this.globalFilters); combined.addAll(gatewayFilters); // TODO: needed or cached? AnnotationAwareOrderComparator.sort(combined); if (logger.isDebugEnabled()) { logger.debug("Sorted gatewayFilterFactories: " + combined); } return new DefaultGatewayFilterChain(combined).filter(exchange); } ``` Gateway的Filters 排序顺序如下: ![Filters排序顺序](img/filters1.png) Gateway的Filters 执行顺序如下: 如果这个Filter是Pre Filter,那么执行顺序和排序顺序相同,如果这个Filter是Post Filter 这执行顺序和排序顺序相反。 ![Filters执行顺序](img/filters2.png) 可以看到GatewayMetricsFilter既是Pre Filter也是Post Filter。 由上面可得: - 执行某个Route的时候,GlobalFilter和GatewayFilte 结合起来排序: - 没有给order的GlobalFilter则保持order为null去排序 - 没有给order的GatewayFilte的order定义的顺序从1开始 - 对于Pre Filter,执行顺序同排序顺序;对于Post Filter,执行顺序与排序顺序相反 - 如果自定义GlobalFilter,一般情况: - 自定义Global Pre Filter 要在GlobalFilter之前执行 - 自定义的Global Post Filter要在GatewayFilte之后执行或者NettyWriteResponseFilter之后执行 - 如果你要自定义GatewayFilte,一般情况: - 自定义Pre Filter要在ForwardPathFilter和RouteToRequestUrlFilter之间,而且不需要实现ordered接口或添加@Order注解 - 自定义的Post Filter比较少见,放在Routing Filter或者NettyWriteResponseFilter之后执行 ##### 内置Gateway过滤器工厂 详细介绍 https://blog.csdn.net/ooyhao/article/details/102810320 ### 三. 服务调用 OpenFeign ##### 配置Feign 添加 @EnableFeignClients 注解 ```yaml feign: # 开启sentinel熔断 sentinel: enabled: true compression: request: # 是否对请求进行GZIP压缩 enabled: false # 指定压缩的请求数据类型 mime-types: text/xml,application/xml,application/json # 超过该大小的请求会被压缩 min-request-size: 2048 response: # 是否对响应进行GZIP压缩 enabled: false ``` ##### FeignClient打印请求日志 1. 配置FeignConfiguration ```java @Configuration public class FeignConfiguration { @Bean Logger.Level feignLoggerLevel() { //这里记录所有,根据实际情况选择合适的日志level return Logger.Level.FULL; } } ``` 2. 指定配置类 @FeignClient==> configuration 配置类 3. 修改日志级别 yml文件添加Feign接口日志级别为debug ```yaml logging: level: com.verlet.web.fegin.NacosFeignService: debug ``` ### 四. 熔断与限流 Sentinel - 丰富的应用场景: 承接了阿里巴巴近 10 年的双十一大促流量的核心场景, 例如秒杀,可以实时熔断下游不可用应用; - 完备的实时监控: 同时提供实时的监控功能。可以在控制台中看到接入应用的单台机器秒级数据, 甚至 500 台以下规模的集群的汇总运行情况; - 广泛的开源生态: 提供开箱即用的与其它开源框架/库的整合模块, 例如与 Spring Cloud、Dubbo、gRPC 的整合; - 完善的 SPI 扩展点: 提供简单易用、完善的 SPI 扩展点. 您可以通过实现扩展点,快速的定制逻辑. ##### 限流功能 Sentinel Starter 默认为所有的 HTTP 服务提供了限流埋点, 没有指定使用默认的限流, 返回 429 Too Many Requests ###### 限流规则 > Sentinel目前的规则是存在客户端应用内存中的,重启之后设置的规则消失 使用配置文件配置流控规则, 使用Nacos配置中心(推荐)时是推模式, 会自动更新规则 - resource:资源名,即限流规则的作用对象 - count: 限流阈值 - grade: 限流阈值类型(0表示线程数, 1表示QPS), 默认QPS - limitApp: 流控针对的调用来源,若为 default 则不区分调用来源(针对上级微服务) - strategy: 调用关系限流策略(0表示直接, 1表示关联, 2表示链路), 默认直接 * 直接(默认):接口达到限流条件时,开启限流 * 关联:当关联的资源达到限流条件时,开启限流(适合做应用让步,保证关联的资源正常) * 链路:当从某个接口过来的资源达到限流条件时,开启限流(针对上级接口和limi tApp相似,但是更加细粒度) - controlBehavior: 流量控制效果(0表示快速失败, 1表示Warm Up, 2表示排队等待), 默认快速失败 * 快速失败:直接失败,抛出限流异常,不做任何额外的处理 * Warm Up: 从开始阈值到最大QPS阈值会有一个缓冲阶段,一开始的阈值是最大QPS阈值的1/3,慢慢增大,直到最大阈值,适用于将突然增大的流量转换为缓步增长的场景 * 排队等待:让请求以匀速通过,单机阈值为每秒通过数量,其余的排队等待;设置一个超时时间,当请求超过超时时间还未处理,则丢弃 - clusterMode: 是否集群 - clusterConfig: 集群配置, 参考com.alibaba.csp.sentinel.slots.block.flow.ClusterFlowConfig ```json [ { "resource": "byResource", "controlBehavior": 0, "count": 1, "grade": 1, "limitApp": "default", "strategy": 0 } ] ``` ##### 熔断降级功能 Sentinel 支持对服务间调用进行保护,对故障应用进行熔断操作. 1. 服务请求熔断 ```java @SentinelResource 自定义处理异常, 没有指定使用Sentinel默认异常处理 blockHandler 五种规则处理方法 blockHandlerClass 处理类(默认当前类),注意对应的函数必需为 static 函数 defaultFallback 同时配置fallback,此方法会失效 fallback 发生Throwable执行的方法 fallbackClass 发生Throwable执行类(默认当前类),注意对应的函数必需为 static 函数 exceptionsToIgnore 忽略某些异常 ``` fallback对应的异常是Throwable,blockHandler对应的BlockException,范围fallback > blockHandler 2. RestTemplate 熔断 使用@SentinelRestTemplate来包装下RestTemplate实例 ```java @Bean @LoadBalanced @SentinelRestTemplate public RestTemplate restTemplate() { return new RestTemplate(); } ``` 3. @FeignClient 熔断 feign启用sentinel熔断,并fallback或者fallbackFactory 参数配置熔断处理 ```yaml feign: sentinel: enabled: true ``` ###### 降级规则 Sentinel提供降级规则有两个: 1. 平均响应时间(RT):当资源的平均响应时间超过阀值(单位ms)之后,资源进入准降级状态,如果接下来1S内持续进去5个请求,他们的RT都持续超过这个阀值,那么在接下来的窗口(单位s)之内,就会对这个方法进行服务降级。 > 注意Sentinel默认统计的RT上限是4900ms,超出此阀值的都会算作4900ms,若需要变更此上限可以通过启动配置项 -Dscp.sentinel.statistic.max.rt=xxx 来配置 2. 异常比例:当资源的每秒异常总数占通过量的比例值超过阀值之后,资源进入降级状态,即在接下来的时间窗口(单位s)之内,对这个方法的调用都会自动地返回。 ##### 热点规则 热点参数流控规则是一种更加细粒度的流控规则,他允许将规则具体到参数上。 注意点,需要指定要资源,不是使用默认的路径资源。 1. 参数索引:需要限流的参数索引 2. 限流阀值:QPS 限流的阀值 3. 参数例外项:可以更加细粒度根据参数值来限流 ##### 授权规则 根据资源的请求来源(流控应用)限制资源是否通过。 1. 配置白名单:只有请求来源于白名单内时才可通过 2. 配置黑名单:请求来源位于黑名单时才通过,其余的请求通过 流控应用,是来源标识,Sentinel提供了**RequestOriginParser**接口来处理来源。 只要Sentinel保护的接口资源被访问,就会调用此实现类去解析访问来源。 ```java @Component public class RequestOriginParserDefinition implements RequestOriginParser { @Override public String parseOrigin(HttpServletRequest httpServletRequest) { // 返回来源标识 return httpServletRequest.getParameter("serviceName"); } } ``` ##### 系统规则 系统保护规则是从应用级别的入口流量进行控制,从单台机器的总体Load,RT,入口QPS,CPU使用率和线程数五个监控维度,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。 系统保护规则是引用整体维度的,而不是资源维度的,并且仅对入口流量(进入应用的流量)生效。 - Load(仅对Linux/Unix-like生效):当系统load1(平均一分钟)超过阀值,且系统当前的并发现场数超过系统容量时才会触发系统保护。系统容量有系统的maxQps * minRt计算的。设定参考值一般是:CPU cores*2.5。 - RT:当单台机器上所有入口流量的平均RT达到阀值即触发系统保护,单位是毫秒。 - 线程数:当单台机器上所有入口流量的并发线程数达到阀值即触发系统保护。 - 入口QPS:当单台机器上所有入口流量的QPS达到阀值即触发系统保护。 - CPU使用率:当单台机器上所有入口流量的CPU使用率达到阀值即触发系统保护。 ##### Sentinel全局异常配置 实现Sentinel提供的**BlockExceptionHandler**接口 其中BlockException有五个异常的子类: - AuthorityException - DegradeException - FlowException - ParamFlowException - SystemBlockException ##### 规则配置类 ```java // 规则类型 public enum RuleType { // 控流规则 FLOW("flow", FlowRule.class), // 降级规则 DEGRADE("degrade", DegradeRule.class), // 热点参数限流规则 PARAM_FLOW("param-flow", ParamFlowRule.class), // 系统规则 SYSTEM("system", SystemRule.class), // 权限规则 AUTHORITY("authority", AuthorityRule.class), // 网关控流规则 GW_FLOW("gw-flow", "com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule"), // 网关API控流规则 GW_API_GROUP("gw-api-group", "com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition"); } ``` ![规则配置类图](img/Rule.png) - FlowRule 控流规则配置类 - DegradeRule 降级规则配置类 - ParamFlowRule 热点参数限流规则配置类 - SystemRule 系统规则配置类 - AuthorityRule 权限规则配置类 ##### 使用Resilience4j 详细使用地址 https://blog.csdn.net/zhxdick/article/details/106566509?utm_medium=distribute.pc_relevant.none-task-blog-baidujs-1 ##### Sentinel dashboard 配置 docker 安装 Sentinel dashboard 服务 ```shell docker run --name=sentinel-dashboard -d -p 8858:8858 -d bladex/sentinel-dashboard ``` 默认用户密码 sentinel 添加Sentinel dashboard地址 ```yaml spring: cloud: sentinel: transport: port: 8720 dashboard: localhost:8858 ``` ### 五. 服务链路追踪 Spring Cloud Sleuth SpringCloud Sleuth主要功能就是在分布式系统中提供追踪解决方案。它大量借用了Google Dapper的设计, 先来了解一下Sleuth中的术语和相关概念。 - Trace:由一组Trace Id相同的Span串联形成一个树状结构。为了实现请求跟踪,当请求到达分布式系统的入口端点时,只需要服务跟踪框架为该请求创建一个唯一的标识(即TraceId),同时在分布式系统内部流转的时候,框架始终保持传递该唯一值,直到整个请求的返回。那么我们就可以使用该唯一标识将所有的请求串联起来,形成一条完整的请求链路。 - Span:代表了一组基本的工作单元。为了统计各处理单元的延迟,当请求到达各个服务组件的时候,也通过一个唯一标识(SpanId)来标记它的开始、具体过程和结束。通过SpanId的开始和结束时间戳,就能统计该span的调用时间,除此之外,我们还可以获取如事件的名称。请求信息等元数据。 - Annotation:用它记录一段时间内的事件,内部使用的重要注释: - cs(Client Send)客户端发出请求,开始一个请求的生命 - sr(Server Received)服务端接受到请求开始进行处理, sr - cs = 网络延迟(服务调用的时间) - ss(Server Send)服务端处理完毕准备发送到客户端, ss - sr = 服务器上的请求处理时间 - cr(Client Reveived)客户端接受到服务端的响应,请求结束。 cr - sr = 请求的总时间 ##### Sleuth 使用 添加 sleuth 依赖 ```xml org.springframework.cloud spring-cloud-starter-sleuth ``` 日志输出 [gateway,4591194b5502684d,4591194b5502684d,true] 可以看出 4591194b5502684d是TraceId,4591194b5502684d是SpanId,依次调用有个一个全局的TraceId,将调用链路串起来。 可以通过 Zipkin 将日志聚合,并进行可视化展示和全文检索。 ##### Zipkin分布式追踪 Zipkin 是 Twitter 的一个开源项目,它基于Google Dapper实现,它致力于收集服务的定时数据,以解决微服务架构中的延迟问题,包括数据的收集、存储、查找和展现。 Zipkin 的基础架构,它主要由 4 个核心组件构成: - Collector:收集器组件,它主要用于处理从外部系统发送过来的跟踪信息,将这些信息转换为Zipkin内部处理的 Span 格式,以支持后续的存储、分析、展示等功能。 - Storage:存储组件,它主要对处理收集器接收到的跟踪信息,默认会将这些信息存储在内存中,我们也可以修改此存储策略,通过使用其他存储组件将跟踪信息存储到数据库中。 - RESTful API: API 组件,它主要用来提供外部访问接口,比如给客户端展示跟踪信息,或是外接系统访问以实现监控等。 - Web UI: UI 组件, 基于API组件实现的上层应用,通过UI组件用户可以方便而有直观地查询和分析跟踪信息。 ###### Zipkin配置 添加 Zipkin依赖 ```xml org.springframework.cloud spring-cloud-starter-zipkin ``` 并添加配置文件 ```yaml spring: zipkin: baseUrl: http://127.0.0.1:9411 # 让 nacos 把它当成一个URL,而不要当成服务名 discoveryClientEnabled: false sleuth: sampler: # 采样百分比 probability: 1.0 ``` ###### ZipKin数据持久化到MySql 首先我们需要创 ZipKin 指定的表,不过这些不需要我们主动的编写,我们可以通关官方文档拿到执行语句:[zipkin.sql](https://github.com/openzipkin/zipkin/blob/master/zipkin-storage/mysql-v1/src/main/resources/mysql.sql) 接着在启动 Zipkin 的时候,加上链接数据库的命令就可以了。 ```shell java -jar zipkin-server-2.21.6-exec.jar --STORAGE_TYPE=mysql --MYSQL_HOST=127.0.0.1 \ --MYSQL_TCP_PORT=3306 --MYSQL_DB=zipkin --MYSQL_USER=root --MYSQL_PASS=root ``` ###### ZipKin数据持久化到Elasticsearch 在启动 Zipkin 的时候,添加 Elasticsearch 连接信息即可。 ```shell java -jar zipkin-server-2.21.6-exec.jar --STORAGE_TYPE=elasticsearch --ESHOST=localhost:9200 ``` ### 六. 安全保护 Spring Cloud Security ### 七. 监控中心 Spring Boot Admin ### 八. 分布式事务解决 Seata