一 微服务架构
1.1 微服务
微服务其实是一种架构风格,我们在开发一个应用的时候这个应用应该是由一组小型服务组成,每个小型服务都运行在自己的进程内;小服务之间通过HTTP的方式进行互联互通。
1.2 微服务架构的常见问题
一旦采用微服务系统架构,就势必会遇到这样几个问题:
这么多小服务,如何管理他们?(服务治理 注册中心[服务注册 发现 剔除]) nacos
这么多小服务,他们之间如何通讯? httpclient("url",参数),springBoot restTemplate("url",参数) ,feign
这么多小服务,客户端怎么访问他们?(网关) gateway
这么多小服务,一旦出现问题了,应该如何自处理?(容错) sentinel
这么多小服务,一旦出现问题了,应该如何排错? (链路追踪) skywalking
1.3 springcloud alibaba
Spring Cloud Alibaba 致力于提供微服务开发的一站式解决方案。此项目包含开发微服务架构的必需组件,方便开发者,通过 Spring Cloud 编程模型轻松使用这些组件来开发微服务架构。 依托 Spring Cloud Alibaba,您只需要添加一些注解和少量配置,就可以将 Spring Cloud 应用接入阿里分布式应用解决方案,通过阿里中间件来迅速搭建分布式应用系统。
1.4 组件和版本关系
二 SpringCloudAlibaba组建
2.1工程结构
2.1.1 父pom文件
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<java.version>1.8</java.version>
<spring.cloud.version>Hoxton.SR8</spring.cloud.version>
<spring.boot.version>2.3.11.RELEASE</spring.boot.version>
<spring.cloud.alibaba.version>2.2.5.RELEASE</spring.cloud.alibaba.version>
</properties>
<dependencyManagement>
<!--Spring Cloud alibaba的版本管理, 通过dependency完成继承-->
<dependencies>
<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>
<!--SpringBoot的版本管理-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--Spring Cloud的版本管理-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
2.1.2 子项目引入starter-web
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.3.11.RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.3</version>
</dependency>
</dependencies>
2.1.3 引入restTemplate做跨服务的调用
@Configuration
public class RestConfig {
@Bean
public RestTemplate getRestTemplate() {
HttpComponentsClientHttpRequestFactory httpRequestFactory =
new HttpComponentsClientHttpRequestFactory();
httpRequestFactory.setConnectionRequestTimeout(3000);
httpRequestFactory.setConnectTimeout(2000);
httpRequestFactory.setReadTimeout(5000);
return new RestTemplate(httpRequestFactory);
}
}
@RestController
@RequestMapping(value = "order")
public class OrderController {
@Resource
private RestTemplate restTemplate;
@RequestMapping(value = "/addOrder")
@ResponseBody
public String order(){
String forObject = restTemplate.getForObject("http://localhost:8081/stock/reduce", String.class);
System.out.println(forObject);
return "订单成功";
}
}
如上代码所示我们用订单服务去调用远程的库存服务,实现下订单减库存的功能。这就是我们传通的SOA调用,这种调用方式的弊端有:
- 服务的ip ,端口号都是写死在代码里的,一旦更换服务就要重新修改代码,这样的结果就是导致不必要的版本变更。
- Soa调用负载均衡策略需要自己来规划,不能很好的做到服务的负载均衡,服务发现注册。
2.2 Nacos
从传统的soa架构出发,分析系统的弊端得到,我们怎么样才能更好的管理我们的服务呢?这个时候我们就需要引入一个组件nacos.nacos一个更易于构建云原生应用的动态服务发现(Nacos Discovery )、服务配置(Nacos Config)和服务管理平台。
2.2.1 nacos核心功能
服务注册:Nacos Client会通过发送REST请求的方式向Nacos Server注册自己的服务,提供自身的元数据,比如ip地址、端口等信息。Nacos Server接收到注册请求后,就会把这些元数据信息存储在一个双层的内存Map中。
服务心跳:在服务注册后,Nacos Client会维护一个定时心跳来持续通知Nacos Server,说明服务一直处于可用状态,防止被剔除。默认5s发送一次心跳。
服务同步:Nacos Server集群之间会互相同步服务实例,用来保证服务信息的一致性。 leader raft
服务发现:服务消费者(Nacos Client)在调用服务提供者的服务时,会发送一个REST请求给Nacos Server,获取上面注册的服务清单,并且缓存在Nacos Client本地,同时会在Nacos Client本地开启一个定时任务定时拉取服务端最新的注册表信息更新到本地缓存
服务健康检查:Nacos Server会开启一个定时任务用来检查注册服务实例的健康情况,对于超过15s没有收到客户端心跳的实例会将它的healthy属性置为false(客户端服务发现时不会发现),如果某个实例超过30秒没有收到心跳,直接剔除该实例(被剔除的实例如果恢复发送心跳则会重新注册)
2.2.2 架构演变
如上图所示引入nacos以后我们可以看到架构的变更,从传统的SOA调用变更了注册中心方式的服务发现与注册的策略。那么可以看出就可以解决上面那两个弊端。通过nacos对服务的ip地址和端口的统一管理。对大量的微服务进行管理,还可以实现负载均衡的功能。
2.2.1.1 部署单机版的nacos
1) github下载 https://github.com/alibaba/nacos/releases/tag/2.1.2
随便找一个nacos的版本,下载tar.gz文件。上传linux并解压。修改启动文件为单机版启动,启动nacos.
访问对应主机的 http://192.168.189.3:8848/nacos地址.
用户名密码都是nacos.
2.2.1.2 服务注册到nacos
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
spring:
application:
name: order-service
cloud:
nacos:
discovery:
server-addr: 192.168.189.3:8848
namespace: public
username: nacos
password: nacos
@RequestMapping(value = "/addOrder")
@ResponseBody
public String order(){
String forObject = restTemplate.getForObject("http://stock-service/stock/reduce", String.class);
System.out.println(forObject);
return "订单成功";
}
@Bean
@LoadBalanced
public RestTemplate getRestTemplate() {
HttpComponentsClientHttpRequestFactory httpRequestFactory =
new HttpComponentsClientHttpRequestFactory();
httpRequestFactory.setConnectionRequestTimeout(3000);
httpRequestFactory.setConnectTimeout(2000);
httpRequestFactory.setReadTimeout(5000);
return new RestTemplate(httpRequestFactory);
}
@LoadBalanced 如上通过引入pom的依赖就可以把服务注册进来了。至此我们也可以用nacos来管理我们的服务了。接下来修改调用的地址,并在httpclient上面添加负载均衡的注解. 这样我们就可以通过服务名来调用远程服务了。实现服务的注册发现。
2.2.1.3 nacos高可用集群
如图所示就是nacos的高可用集群。通过nginx做一层代理转发就可以构建出高可用的nginx集群了.
1)准备多份nacos
数据共享与持久化
修改conf\application.properties的配置,优先修改端口号.
其次修改外置数据源 要使用mysql5.7+.
打开数据源配置。
将conf\cluster.conf.example改为cluster.conf,添加节点配置
[root@localhost conf]# mv cluster.conf.example cluster.conf
[root@localhost conf]# ls -l
配置集群的主机.
创建mysql数据库,sql文件位置:conf\nacos\mysql.sql
创建nacos数据库运行mysql.sql文件。
如果出现内存不足:修改启动脚本(bin\startup.sh)的jvm参数,主要是启动方式,还有内存.
修改启动脚本, 分别启动nacos8849,nacos8850,nacos8851.集群模式。
依次启动各个nacos节点以后。就可以看到nacos的集群主机了。
安装nginx,配置反向代理
upstream nacoscluster {
server 127.0.0.1:8849;
server 127.0.0.1:8850;
server 127.0.0.1:8851;
}
server {
listen 8847;
server_name localhost;
location /nacos/{
proxy_pass http://nacoscluster/nacos/;
}
}
修改服务连接地址,指向nginx服务起
2.2.3 Nacos配置中心
Nacos 提供用于存储配置和其他元数据的 key/value 存储,为分布式系统中的外部化配置提供服务器端和客户端支持。使 用 Spring Cloud Alibaba Nacos Config,您可以在 Nacos Server 集中管理你 Spring Cloud 应用的外部属性配置。
- 维护性
- 时效性
- 安全性
Namespace:代表不同环境,如开发、测试、生产环境。
Group:代表某项目,如XX医疗项目、XX电商项目
DataId:每个项目下往往有若干个工程(微服务),每个配置集(DataId)是一个工程(微服务)的主配置文件
2.2.1.1 配置中心实践
1)引入依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring‐cloud‐starter‐alibaba‐nacos‐config</artifactId>
</dependency>
2)添加bootstrap.yml (spring cloud 只支持bootrap.yml文件)
spring:
application:
name: order-service
cloud:
nacos:
server-addr: 192.168.189.3:8848
username: nacos
password: nacos
config:
file-extension: yml
namespace: 87848116-5501-4666-986e-6315d7c5f5af
如上配置所示,如果不是properties文件那么就要配置文件拓展方式才行,配置了文件拓展方式就可以取拓展名为指定格式的拓展文件了。名称空间取nacos配置的名称空间的Md5编码。
3)Nacos中 data-id的配置
支持profile粒度的配置
springcloudstarteralibabanacosconfig 在加载配置的时候,不仅仅加载了以 dataid 为
${spring.application.name}.${file-extension:properties} 为前缀的基础配置,还加载了dataid为 ${spring.application.name}-${profile}.${file-extension:properties} 的基础配置。在日常开发中如果遇到多套环境下的不同配置,可以通过Spring 提供的 ${spring.profiles.active} 这个配置项来配置。
spring.profiles.active=dev
profile 的配置文件 大于 默认配置的文件。 并且形成互补
4)支持自定义 namespace 的配置
用于进行租户粒度的配置隔离。不同的命名空间下,可以存在相同的 Group 或 Data ID 的配置。Namespace 的常用场景之一是不同环境的配置的区分隔离,例如开发测试环境和生产环境的资源(如配置、服务)隔离等。在没有明确指定 ${spring.cloud.nacos.config.namespace} 配置的情况下, 默认使用的是 Nacos 上 Public 这个 namespace。如果需要使用自定义的命名空间,可以通过以下配置来实现:
1 spring.cloud.nacos.config.namespace=71bb9785‐231f‐4eca‐b4dc‐6be446e12ff8
5)支持自定义 Group 的配置
Group是组织配置的维度之一。通过一个有意义的字符串(如 Buy 或 Trade )对配置集进行分组,从而区分 Data ID 相同的配置集。当您在 Nacos 上创建一个配置时,如果未填写配置分组的名称,则配置分组的名称默认采用DEFAULT_GROUP 。配置分组的常见场景:不同的应用或组件使用了相同的配置类型,如 database_url 配置和MQ_topic 配置。
在没有明确指定 ${spring.cloud.nacos.config.group} 配置的情况下,默认是DEFAULT_GROUP 。如果需要自定义自己的 Group,可以通过以下配置来实现:
1 spring.cloud.nacos.config.group=DEVELOP_GROUP
6)支持自定义扩展的 Data Id 配置
Data ID 是组织划分配置的维度之一。Data ID 通常用于组织划分系统的配置集。一个系统或者应用可以包含多个配置
集,每个配置集都可以被一个有意义的名称标识。Data ID 通常采用类 Java 包(如 com.taobao.tc.refund.log.level)的命
名规则保证全局唯一性。此命名规则非强制。
通过自定义扩展的 Data Id 配置,既可以解决多个应用间配置共享的问题,又可以支持一个应用有多个配置文件。
# 自定义 Data Id 的配置
#不同工程的通用配置 支持共享的 DataId
spring.cloud.nacos.config.sharedConfigs[0].data‐id= common.yaml
spring.cloud.nacos.config.sharedConfigs[0].group=REFRESH_GROUP
spring.cloud.nacos.config.sharedConfigs[0].refresh=true
# config external configuration
# 支持一个应用多个 DataId 的配置 一定要加扩展名
spring.cloud.nacos.config.extensionConfigs[0].data‐id=ext‐config‐common01.properties
spring.cloud.nacos.config.extensionConfigs[0].group=REFRESH_GROUP
spring.cloud.nacos.config.extensionConfigs[0].refresh=true
spring.cloud.nacos.config.extensionConfigs[1].data‐id=ext‐config‐common02.properties
spring.cloud.nacos.config.extensionConfigs[1].group=REFRESH_GROUP
spring.cloud.nacos.config.extensionConfigs[1].refresh=true
7)配置的优先级
Spring Cloud Alibaba Nacos Config 目前提供了三种配置能力从 Nacos 拉取相关的配置。
A: 通过 spring.cloud.nacos.config.shared-configs 支持多个共享 Data Id 的配置
B: 通过 spring.cloud.nacos.config.ext-config[n].data-id 的方式支持多个扩展 Data Id 的配置
C: 通过内部相关规则(应用名、应用名+ Profile )自动生成相关的 Data Id 配置
当三种方式共同使用时,他们的一个优先级关系是:A < B < C
优先级从高到低:
1) nacosconfigproduct.yaml 精准配置
2) nacosconfig.yaml 同工程不同环境的通用配置
3) extconfig: 不同工程 扩展配置
4) shareddataids 不同工程通用配置
8)测试配置
@SpringBootApplication(scanBasePackages = {"worn.xiao.order"})
@EnableFeignClients
public class OrderApplication {
public static void main(String[] args) throws InterruptedException {
ConfigurableApplicationContext applicationContext = SpringApplication.run(OrderApplication.class, args);
while (true) {
//当动态配置刷新时,会更新到 Enviroment中,因此这里每隔一秒中从Enviroment中获取配置
String userName = applicationContext.getEnvironment().getProperty("common.name");
String userAge = applicationContext.getEnvironment().getProperty("common.age");
System.err.println("common name :" + userName + "; age: " + userAge);
TimeUnit.SECONDS.sleep(1);
}
}
}
9) @RefreshScope
@RestController
@RequestMapping(value = "order")
@RefreshScope
public class OrderController {
@Autowired
private StockService stockService;
@Autowired
private ProductServie productServie;
@Value("${user.name:哈哈}")
private String name;
@Value("${user.age:30}")
private String age;
@RequestMapping(value = "/addOrder")
@ResponseBody
public String order(){
String forObject =stockService.reduce();
Object product = productServie.getProduct(1L);
System.out.println(forObject);
return JSON.toJSONString(product);
}
@RequestMapping(value = "/get")
@ResponseBody
public String get(){
return "name:="+name+" age:="+age;
}
}
当我们使用获取配置文件的时候,发现并没有name和age并没有刷新。那么这个时候如果我们修改了
2.3 openFeign负载均衡
2.3.1 JAVA 项目中如何实现接口调用
1)Httpclient
HttpClient 是 Apache Jakarta Common 下的子项目,用来提供高效的、最新的、功能丰富的支持 Http 协议的客户端编程工具包,并且它支持 HTTP 协议最新版本和建议。
HttpClient相比传统 JDK 自带的 URLConnection,提升了易用性和灵活性,使客户端发送 HTTP 请求变得容易,提高了开发的效率。
2)Okhttp
一个处理网络请求的开源项目,是安卓端最火的轻量级框架,由 Square 公司贡献,用于替代HttpUrlConnection 和 Apache HttpClient。OkHttp 拥有简洁的 API、高效的性能,并支持多种协议(HTTP/2 和 SPDY)。
3)HttpURLConnection
HttpURLConnection 是 Java 的标准类,它继承自 URLConnection,可用于向指定网站发GET 请求、POST 请求。HttpURLConnection 使用比较复杂,不像 HttpClient 那样容易使用。
4)RestTemplate WebClient
RestTemplate 是 Spring 提供的用于访问 Rest 服务的客户端,RestTemplate 提供了多种便 捷访问远程 HTTP 服务的方法,能够大大提高客户端的编写效率。上面介绍的是最常见的几种调用接口的方法,我们下面要介绍的方法比上面的更简单、方便,它就是 Feign。
2.3.2 什么是Feign
Feign是Netflix开发的声明式、模板化的HTTP客户端,其灵感来自Retrofit、JAXRS-2.0以及 WebSocket。Feign可帮助我们更加便捷、优雅地调用HTTP API。Feign支持多种注解,例如Feign自带的注解或者JAX-RS注解等。
Spring Cloud openfeign对Feign进行了增强,使其支持Spring MVC注解,另外还整合了Ribbon和Nacos,从而使得Feign的使用更加方便
优势
Feign可以做到使用 HTTP 请求远程服务时就像调用本地方法一样的体验,开发者完全感不知到这是远程方法,更感知不到这是个HTTP 请求。它像 Dubbo 一样,consumer 直接调用接口方法调用 provider,而不需要通过常规的 Http Client 构造请求再解析返回数据。它解决了 让开发者调用远程接口就跟调用本地方法一样,无需关注与远程的交互细节,更无需关注分布式环境开发。
2.3.3 整合feignClient
1.导入feignClient依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2.服务调用方,编写feignClient文件,原则上springmvc接口怎么编写,FeignClient接口就怎么编写.path就是类文件映射。
@FeignClient(value = "stock-service",path = "/stock")
public interface StockService {
@RequestMapping(value = "/reduce")
public String reduce();
}
3.启动端启动 @EnableFeignClients
@SpringBootApplication(scanBasePackages = {"worn.xiao.order"})
@EnableFeignClients
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class,args);
}
}
4.替换原来的RestTemplate接口
@RestController
@RequestMapping(value = "order")
public class OrderController {
@Resource
private StockService stockService;
@RequestMapping(value = "/addOrder")
@ResponseBody
public String order(){
String forObject =stockService.reduce();
System.out.println(forObject);
return "订单成功";
}
}
2.3.4 feignClient日志配置
通过源码可以看到日志等级有 4 种,分别是:
NONE【性能最佳,适用于生产】:不记录任何日志(默认值)。
BASIC【适用于生产环境追踪问题】:仅记录请求方法、URL、响应状态代码以及
执行时间。
HEADERS:记录BASIC级别的基础上,记录请求和响应的header。
FULL【比较适用于开发及测试环境定位问题】:记录请求和响应的header、body
和元数据
@Configuration
public class FeignConfig {
@Bean
public Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
yml文件中设置springboot日志级别
logging:
level:
worn.xiao.order.feign: debug
2.3.5 调用超时时间配置
@Configuration
public class FeignConfig {
@Bean
public Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
@Bean
Request.Options options() {
return new Request.Options(2000, 7000);
}
}
2.4 sentinel 分布式系统流量防卫兵
2.4.1 服务不可用
在一个高度服务化的系统中,我们实现的一个业务逻辑通常会依赖多个服务, 如图所示
如果其中的下单服务不可用,就会出现所有的线程因为等待而被阻塞,从而造成整个服务链路不可用。进而导致整个系统服务雪崩。
导致服务不可用的原因
在服务提供者不可用的时候,会出现大量重试的情况:用户重试、代码逻辑重试,这些重试最终导致:进一步加大请求流量。所以归根结底导致雪崩效应的最根本原因是:大量请求线程同步等待造成的资源耗尽。当服务调用者使用同步调用时, 会产生大量的等待线程占用系统资源。一旦线程资源被耗尽,服务调用者提供的服务也将处于不可用状态, 于是服务雪崩效应产生了。
2.4.2 解决方案
1) 超时限制
在不做任何处理的情况下,服务提供者不可用会导致消费者请求线程强制等待,而造成系统资源耗尽。加入超时机制,一旦超时,就释放资源。由于释放资源速度较快,一定程度上可以抑制资源耗尽的问题。
服务限流
隔离
用户的请求将不再直接访问服务,而是通过线程池中的空闲线程来访问服务,如果线程池已满,则会进行降级处理,用户的请求不会被阻塞,至少可以看到一个执行结果(例如返回友好的提示信息),而不是无休止的等待或者看到系统崩溃。
隔离前:
隔离后
b)信号隔离:
信号隔离也可以用于限制并发访问,防止阻塞扩散, 与线程隔离最大不同在于执行依赖代码的线程依然是请求线程(该线程需要通过信号申请, 如果客户端是可信的且可以快速返回,可以使用信号隔离替换线程隔离,降低开销。信号量的大小可以动态调整, 线程池大小不可以。
服务熔断
远程服务不稳定或网络抖动时暂时关闭,就叫服务熔断。现实世界的断路器大家肯定都很了解,断路器实时监控电路的情况,如果发现电路电流异常,就会跳闸,从而防止电路被烧毁。
软件世界的断路器可以这样理解:实时监测应用,如果发现在一定时间内失败次数/失败率达到一定阈值,就“跳闸”,断路器打开——此时,请求直接返回,而不去调用原本调用的逻辑。跳闸一段时间后(例如10秒),断路器会进入半开状态,这是一个瞬间态,此时允许一次请求调用该调的逻辑,如果成功,则断路器关闭,应用正常调用;如果调用依然不成功,断路器继续回到打开状态,过段时间再进入半开状态尝试——通过”跳闸“,应用可以保护自己,而且避免浪费资源;而通过半开的设计,可实现应用的“自我修复“。所以,同样的道理,当依赖的服务有大量超时时,在让新的请求去访问根本没有意义,只会无畏的消耗现有资源。 比如我们设置了超时时间为1s,如果短时间内有大量请求在1s内都得不到响应,就意味着这个服务出现了异常,此时就没有必要再让其他的请求去访问这个依赖了,这个时候就应该使用断路器避免资源浪费。
服务降级
有服务熔断,必然要有服务降级。所谓降级,就是当某个服务熔断之后,服务将不再被调用,此时客户端可以自己准备一个本地的fallback(回退)回调,返回一个缺省值。 例如:(备用接口/缓存/mock数据) 。这样做,虽然服务水平下降,但好歹可用,比直接挂掉要强,当然这也要看适合的业务场景。
2.4.3 sentinel
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 是面向分布式服务架构的流量控制组件,主要以流量为切入点,从限流、流量整形、熔断降级、系统负载保护、热点防护等多个维度来帮助开发者保障微服务的稳定性。
丰富的应用场景: Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、实时熔断下游不可用应用等。
完备的实时监控: Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。
广泛的开源生态: Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud, Dubbo, gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。
完善的 SPI 扩展点: Sentinel 提供简单易用、完善的 SPI 扩展点。您可以通过实现扩展点,快速的定制逻辑。例如定制规则管理、适配数据源等。
资源
资源是 Sentinel 的关键概念。它可以是 Java 应用程序中的任何内容,例如,由应用程序提供的服务,或由应用程序调用的其它应用提供的服务,甚至可以是一段代码。在接下来的文档中,我们都会用资源来描述代码块。
只要通过 Sentinel API 定义的代码,就是资源,能够被 Sentinel 保护起来。大部分情况下,可以使用方法签名,URL,甚至服务名称作为资源名来标示资源。
规则
围绕资源的实时状态设定的规则,可以包括流量控制规则、熔断降级规则以及系统保护规则。所有规则可以动态实时调整。
2.4.4 Sentinel 的主要工作
对主流框架提供适配或者显示的 API,来定义需要保护的资源,并提供设施对资源进行实时统计和调用链路分析。
根据预设的规则,结合对资源的实时统计信息,对流量进行控制。同时,Sentinel 提供开放的接口,方便您定义及改变规则。
Sentinel 提供实时的监控系统,方便您快速了解目前系统的状态。
流量控制
流量控制在网络传输中是一个常用的概念,它用于调整网络包的发送数据。然而,从系统稳定性角度考虑,在处理请求的速度上,也有非常多的讲究。任意时间到来的请求往往是随机不可控的,而系统的处理能力是有限的。我们需要根据系统的处理能力对流量进行控制。Sentinel 作为一个调配器,可以根据需要把随机的请求调整成合适的形状,如下图所示:
流量控制设计理念
- 资源的调用关系,例如资源的调用链路,资源和资源之间的关系;
- 运行指标,例如 QPS、线程池、系统负载等;
- 控制的效果,例如直接限流、冷启动、排队等。
- Sentinel 的设计理念是让您自由选择控制的角度,并进行灵活组合,从而达到想要的效果。
熔断降级
除了流量控制以外,及时对调用链路中的不稳定因素进行熔断也是 Sentinel 的使命之一。由于调用关系的复杂性,如果调用链路中的某个资源出现了不稳定,可能会导致请求发生堆积,进而导致级联错误。
Sentinel 熔断方案
通过并发线程数进行限制
和资源池隔离的方法不同,Sentinel 通过限制资源并发线程的数量,来减少不稳定资源对其它资源的影响。这样不但没有线程切换的损耗,也不需要您预先分配线程池的大小。当某个资源出现不稳定的情况下,例如响应时间变长,对资源的直接影响就是会造成线程数的逐步堆积。当线程数在特定资源上堆积到一定的数量之后,对该资源的新请求就会被拒绝。堆积的线程完成任务后才开始继续接收请求。
针对慢调用和异常对资源进行降级
除了对并发线程数进行控制以外,Sentinel 还可以根据响应时间和异常等不稳定因素来快速对不稳定的调用进行熔断。当依赖的资源出现响应时间过长后,所有对该资源的访问都会被直接拒绝,直到过了指定的时间窗口之后才重新渐进式地恢复。
系统自适应保护
Sentinel 同时提供系统维度的自适应保护能力。防止雪崩,是系统防护中重要的一环。当系统负载较高的时候,如果还持续让请求进入,可能会导致系统崩溃,无法响应。在集群环境下,网络负载均衡会把本应这台机器承载的流量转发到其它的机器上去。如果这个时候其它的机器也处在一个边缘状态的时候,这个增加的流量就会导致这台机器也崩溃,最后导致整个集群不可用。
针对这个情况,Sentinel 提供了对应的保护机制,让系统的入口流量和系统的负载达到一个平衡,保证系统在能力范围之内处理最多的请求。
安装sentinel
https://github.com/alibaba/Sentinel/releases/tag/v1.8.0‘
nohup java -jar sentinel-dashboard-1.8.0.jar >log.file 2>&1 &
http://192.168.189.3:8080/#/login
Sentinel/sentinel
Spring Cloud Alibaba整合Sentinel
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring‐cloud‐starter‐alibaba‐sentinel</artifactId>
</dependency>
集成sentinel控制端:
server:
port: 8080
feign:
sentinel:
enabled: true
spring:
profiles:
active: dev
cloud:
sentinel:
transport:
dashboard: 192.168.189.3:8080
在sentinel控制台中设置流控规则
资源名: 接口的API
针对来源: 默认是default当多个微服务都调用这个资源时,可以配置微服务名来对指定的微服务设置阈值
阈值类型: 分为QPS和线程数 假设阈值为10
QPS类型: 只得是每秒访问接口的次数>10就进行限流
线程数: 为接受请求该资源分配的线程数>10就进行限流
2.4.5 流控模式
基于调用关系的流量控制。调用关系包括调用方、被调用方;一个方法可能会调用其它方法,形成一个调用链路的层次关系。
1)直接
资源调用达到设置的阈值后直接被流控抛出异常
2)关联
当两个资源之间具有资源争抢或者依赖关系的时候,这两个资源便具有了关联。比如对数据库同一个字段的读操作和写操作存在争抢,读的速度过高会影响写得速度,写的速度过高会影响读的速度。如果放任读写操作争抢资源,则争抢本 身带来的开销会降低整体的吞吐量。可使用关联限流来避免具有关联关系的资源之间过度的争抢,举例来说,read_db 和 write_db 这两个资源分别代表数据库读写,我们可以给read_db 设置限流规则来达到写优先的目的:设置 strategy 为RuleConstant.STRATEGY_RELATE 同时设置 refResource 为 write_db。这样当写库操作过于频繁时,读数据的请求会被限流。
链路
根据调用链路入口限流。 下面中记录了资源之间的调用链路,这些资源通过调用关系,相互之间构成一棵调用树。这棵树的根节点是一个名字为getUser 的虚拟节点,调用链的入口都是这个虚节点的子节点。
一棵典型的调用树如下图所示:
上图中来自入口 /order/test1 和 /order/test2的请求都调用到了资源 getUser,Sentinel 允许只根据某个入口的统计信息对资源限流。
测试会发现链路规则不生效
注意,高版本此功能直接使用不生效,如何解决?
spring.cloud.sentinel.web‐context‐unify: false
测试,此场景拦截不到BlockException,对应@SentinelResource指定的资源必须在@SentinelResource注解中指定blockHandler处理BlockException
总结: 为了解决链路规则引入ComonFilter的方式,除了此处问题,还会导致更多的问题,不建议使用ComonFilter的方式。 流控链路模式的问题等待官方后续修复,或者使用AHAS。
快速失败
(RuleConstant.CONTROL_BEHAVIOR_DEFAULT)方式是默认的流量控制方式,当QPS超过任意规则的阈值后,新的请求就会被立即拒绝,拒绝方式为抛出FlowException。这种方式适用于对系统处理能力确切已知的情况下,比如通过压测确定了系统的准确水位时。
Warm Up(激增流量)
Warm Up(RuleConstant.CONTROL_BEHAVIOR_WARM_UP)方式,即预热/冷启动方式。当系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过"冷启动",让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。
冷加载因子: codeFactor 默认是3,即请求 QPS 从 threshold / 3 开始,经预热时长逐渐升至设定的 QPS 阈值。通常冷启动的过程系统允许通过的 QPS 曲线如下图所
查看实时监控,可以看到通过QPS存在缓慢增加的过程
匀速排队(脉冲流量)
匀速排队(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER`)方式会严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法。
该方式的作用如下图所示:
这种方式主要用于处理间隔性突发的流量,例如消息队列。想象一下这样的场景,在某一秒有大量的请求到来,而接下来的几秒则处于空闲状态,我们希望系统能够在接下来的空闲期间逐渐处理这些请求,而不是在第一秒直接拒绝多余的请求。
注意:匀速排队模式暂时不支持 QPS > 1000 的场景。
查看实时监控,可以看到通过QPS为5,体现了匀速排队效果
2.4.6 降级规则
除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。我们需要对不稳定的弱依赖服务调用进行熔断降级,暂时切断不稳定调用,避免局部不稳定因素导致整体的雪崩。熔断降级作为保护自身的手段,通常在客户端(调用端)进行配置。
熔断降级规则说明
熔断降级规则(DegradeRule)包含下面几个重要的属性:
熔断策略
慢调用比例
慢调用比例 (SLOW_REQUEST_RATIO):选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间), 请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态 (HALFOPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。
异常比例
异常比例 (ERROR_RATIO):当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALFOPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0], 代表 0% 100%。
查看实时监控,可以看到断路器熔断效果
异常数
异常数 (ERROR_COUNT):当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALFOPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。
注意:异常降级仅针对业务异常,对 Sentinel 限流降级本身的异常(BlockException)不生效。
2.4.7 整合openFeign降级
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring‐cloud‐starter‐alibaba‐sentinel</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring‐cloud‐starter‐openfeign</artifactId>
</dependency>
yml文件中
feign:
sentinel:
enabled: true
@FeignClient(value = "stock-service",path = "/stock",fallback = StockServiceFalllBack.class)
public interface StockService {
@RequestMapping(value = "/reduce")
public String reduce();
}
@Component
public class StockServiceFalllBack implements StockService{
@Override
public String reduce() {
return "降级了";
}
}
1)热点参数限流
热点识别流控
何为热点?热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的数据,并对其访问进行限制。
热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。
注意:
1. 热点规则需要使用@SentinelResource("resourceName")注解,否则不生效
2. 参数必须是7种基本数据类型才会生效
@RequestMapping("/info/{id}")
@SentinelResource(value = "userinfo", blockHandlerClass = CommonBlockHandler.class,
blockHandler = "handleException2",fallbackClass = CommonFallback.class, fallback = "fallback")
public R info(@PathVariable("id") Integer id){
UserEntity user = userService.getById(id);
return R.ok().put("user", user);
}
单机阈值: 针对所有参数的值进行设置的一个公共的阈值
1假设当前 参数大部分的值都是热点流量, 单机阈值就是针对热点流量进行设置, 额外针对普通流量进行参数值流控
2假设当前 参数大部分的值都是普通流量, 单机阈值就是针对普通流量进行设置, 额外针对热点流量进行参数值流控
配置热点参数规则
注意: 资源名必须是@SentinelResource(value="资源名")中 配置的资源名,热点规则依赖于注解
测试:
http://localhost:8800/user/info/1 限流的阈值为3
http://localhost:8800/user/info/3 限流的阈值为1
3)系统规则
Sentinel 系统自适应限流从整体维度对应用入口流量进行控制,结合应用的 Load、CPU 使用率、总体平均 RT、入口QPS 和并发线程数等几个维度的监控指标,通过自适应的流控策略,让系统的入口流量和系统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。
Load 自适应(仅对 Linux/Unixlike 机器生效):系统的 load1 作为启发指标,进行自适应系统保护。当系统 load 超过设定的启发值,且系统当前的并发线程数超过估算的系统容量时才会触发系统保护(BBR 阶段)。系统容量由系统的 maxQps * minRt 估算得出。设定参考值一般是 CPU cores * 2.5。
CPU usage(1.5.0+ 版本):当系统 CPU 使用率超过阈值即触发系统保护(取值范围 0.01.0),比较灵敏。
平均 RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。
并发线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。
编写系统规则
授权控制规则
很多时候,我们需要根据调用来源来判断该次请求是否允许放行,这时候可以使用 Sentinel 的来源访问控制(黑白名单控制)的功能。来源访问控制根据资源的请求来源(origin)限制资源是否通过,若配置白名单则只有请求来源位于白名单内时才可通过;若配置黑名单则请求来源位于黑名单时不通过,其余的请求通过。 来源访问控制规则(AuthorityRule)非常简单,主要有以下配置项:
resource:资源名,即限流规则的作用对象。
limitApp:对应的黑名单/白名单,不同 origin 用 , 分隔,如 appA,appB。
strategy:限制模式,AUTHORITY_WHITE 为白名单模式,AUTHORITY_BLACK 为黑名单模式,默认为白名单模式。
2.5 seata 分布式事务
2.5.1 seata介绍
Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。AT模式是阿里首推的模式,阿里云上有商用版本的GTS(Global Transaction Service 全局事务服务)
2.5.2 seata工作原理
在 Seata 的架构中,一共有三个角色:
TC (Transaction Coordinator) - 事务协调者
维护全局和分支事务的状态,驱动全局事务提交或回滚。
TM (Transaction Manager) - 事务管理器
定义全局事务的范围:开始全局事务、提交或回滚全局事务。
RM (Resource Manager) - 资源管理器
管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
其中,TC 为单独部署的 Server 服务端,TM 和 RM 为嵌入到应用中的 Client 客户端。
- TM 请求 TC 开启一个全局事务。TC 会生成一个 XID 作为该全局事务的编号。XID,会在微服务的调用链路中传播,保证将多个微服务的子事务关联在一起。 当一进入事务方法中就会生成XID , global_table 就是存储的全局事务信息
- RM 请求 TC 将本地事务注册为全局事务的分支事务,通过全局事务的 XID 进行关联。 当运行数据库操作方法,branch_table 存储事务参与者
3 .TM 请求 TC 告诉 XID 对应的全局事务是进行提交还是回滚。
4.TC 驱动 RM 们将 XID 对应的自己的本地事务进行提交还是回滚。
AT 模式是一种无侵入的分布式事务解决方案。 阿里seata框架,实现了该模式。在 AT 模式下,用户只需关注自己的“业务 SQL”,用户的 “业务 SQL” 作为一阶段,Seata 框架 会自动生成事务的二阶段提交和回滚操作。
AT 模式如何做到对业务的无侵入 :
一阶段:
在一阶段,Seata 会拦截“业务 SQL”,首先解析 SQL 语义,找到“业务 SQL”要更新的业务数 据,在业务数据被更新前,将其保存成“before image”,然后执行“业务 SQL”更新业务数据, 在业务数据更新之后,再将其保存成“after image”,最后生成行锁。以上操作全部在一个数据库 事务内完成,这样保证了一阶段操作的原子性。
二阶段提交:二阶段如果是提交的话,因为“业务 SQL”在一阶段已经提交至数据库, 所以 Seata 框架只需将一阶段保存的快照数据和行锁删掉,完成数据清理即可。
二阶段回滚:
二阶段如果是回滚的话,Seata 就需要回滚一阶段已经执行的“业务 SQL”,还原业务数据。回滚方式便是用“before image”还原业务数据;但在还原前要首先要校验脏写,对比“数据库当前业务 数据”和 “after image”,如果两份数据完全一致就说明没有脏写,可以还原业务数据,如果不一致就说明有脏写,出现脏写就需要转人工处理。
2.5.3设计亮点
相比与其它分布式事务框架,Seata架构的亮点主要有几个:
1. 应用层基于SQL解析实现了自动补偿,从而最大程度的降低业务侵入性;
2. 将分布式事务中TC(事务协调者)独立部署,负责事务的注册、回滚;
3. 通过全局锁实现了写隔离与读隔离。
2.5.4 存在的问题
性能损耗
一条Update的SQL,则需要全局事务xid获取(与TC通讯)、before image(解析SQL,查询一次数据库)、after image(查询一次数据库)、insert undo log(写一次数据库)、before commit(与TC通讯,判断锁冲突),这些操作都需要一次远程通讯RPC,而且是同步的。另外undo log写入时blob字段的插入性能也是不高的。每条写SQL都会增加这么多开销,粗略估计会增加5倍响应时间。
性价比
为了进行自动补偿,需要对所有交易生成前后镜像并持久化,可是在实际业务场景下,这个是成功率有多高,或者说分布式事务失败需要回滚的有多少比率?按照二八原则预估,为了20%的交易回滚,需要将80%的成功交易的响应时间增加5倍,这样的代价相比于让应用开发一个补偿交易是否是值得?
全局锁
相比XA,Seata 虽然在一阶段成功后会释放数据库锁,但一阶段在commit前全局锁的判定也拉长了对数据锁的占有时间,这个开销 比XA的prepare低多少需要根据实际业务场景进行测试。全局锁的引入实现了隔离性,但带来的问题就是阻塞,降低并发性,尤其是热点 数据,这个问题会更加严重。
回滚锁释放时间
Seata在回滚时,需要先删除各节点的undo log,然后才能释放TC内存中的锁,所以如果第二阶段是回滚,释放锁的时间会更长。
死锁问题
Seata的引入全局锁会额外增加死锁的风险,但如果出现死锁,会不断进行重试,最后靠等待全局锁超时,这种方式并不优雅,也延长了对数据库锁的占有时间。
AT 模式的一阶段、二阶段提交和回滚均由 Seata 框架自动生成,用户只需编写“业务 SQL”,便能轻松接入分布式事务,AT 模式是一种对业务无任何侵入的分布式事务解决方案。
2.6 gateway 网关
2.6.1 为什么需要网关
这样的架构,会存在着诸多的问题:
每个业务都会需要鉴权、限流、权限校验、跨域等逻辑,如果每个业务都各自为战,自己造轮子实现一遍,会很蛋疼,完全可以抽出来,放到一个统一的地方去做。
如果业务量比较简单的话,这种方式前期不会有什么问题,但随着业务越来越复杂,比如淘宝、亚马逊打开一个页面可能会涉及到数百个微服务协同工作,如果每一个微服务都分配一个域名的话,一方面客户端代码会很难维护,涉及到数百个域名,另一方面是连接数的瓶颈,想象一下你打开一个APP,通过抓包发现涉及到了数百个远程调用,这在移动端下会显得非常低效。
后期如果需要对微服务进行重构的话,也会变的非常麻烦,需要客户端配合你一起进行改造,比如商品服务, 随着业务变的越来越复杂,后期需要进行拆分成多个微服务,这个时候对外提供的服务也需要拆分成多个,同时需要客户端配合你进行改造,非常蛋疼。上面的这些问题可以借助API网关来解决。
所谓的API网关,就是指系统的统一入口,它封装了应用程序的内部结构,为客户端提供统一服务,一些与业务本身功能无关的公共逻辑可以在这里实现,诸如认证、鉴权、监控、路由转发等等。添加上API网关之后,系统的架构图变成了如下所示:
我们也可以观察下,我们现在的整体架构图:
2.6.2 Spring cloud gateway
网关作为流量的入口,常用的功能包括路由转发,权限校验,限流等。
Spring Cloud Gateway 是Spring Cloud官方推出的第二代网关框架,定位于取代 Netflix Zuul1.0。相比 Zuul 来说,Spring Cloud Gateway 提供更优秀的性能,更强大的有功能。
Spring Cloud Gateway 是由 WebFlux + Netty + Reactor 实现的响应式的 API 网关。它不能在传统的 servlet 容器中工作,也不能构建成 war 包。
Spring Cloud Gateway 旨在为微服务架构提供一种简单且有效的 API 路由的管理方式,并基于 Filter 的方式提供网关的基本功能,例如说安全认证、监控、限流等等。
Spring Cloud Gateway 功能特征基于Spring Framework 5, Project Reactor 和 Spring Boot 2.0 进行构建;动态路由:能够匹配任何请求属性;支持路径重写;集成 Spring Cloud 服务发现功能(Nacos、Eruka);可集成流控降级功能可以对路由指定易于编写的 Predicate(断言)和 Filter(过滤器)
核心概念
路由
路由是网关中最基础的部分,路由信息包括一个ID、一个目的URI、一组断言工厂、一组Filter组成。如果断言为真,则说明请求的URL和配置的路由匹配。
断言
Java8中的断言函数,SpringCloud Gateway中的断言函数类型是Spring5.0框架中的ServerWebExchange。断言函数允许开发者去定义匹配Http request中的任何信息,比如请求头和参数等。
过滤器
SpringCloud Gateway中的filter分为Gateway FilIer和Global Filter。Filter可以对请求和响应进行处理
工作原理
执行流程大体如下
1. Gateway Client向Gateway Server发送请求
2. 请求首先会被HttpWebHandlerAdapter进行提取组装成网关上下文
3. 然后网关的上下文会传递到DispatcherHandler,它负责将请求分发给RoutePredicateHandlerMapping
4. RoutePredicateHandlerMapping负责路由查找,并根据路由断言判断路由是否可用
5. 如果过断言成功,由FilteringWebHandler创建过滤器链并调用
6. 请求会一次经过PreFilter--微服务--PostFilter的方法,最终返回响
2.6.3 自定义断言
自定义路由断言工厂需要继承 AbstractRoutePredicateFactory 类,重写 apply 方法的逻辑。在 apply 方法中可以通过exchange.getRequest() 拿到 ServerHttpRequest 对象,从而可以获取到请求的参数、请求方式、请求头等信息。
1.必须spring组件 bean
2.类必须加上RoutePredicateFactory作为结尾
3.必须继承AbstractRoutePredicateFactory
4.必须声明静态内部类,声明属性来接收 配置文件中对应的断言的信息
5.需要结合shortcutFieldOrder进行绑定
6.通过apply进行逻辑判断 true就是匹配成功 false匹配失败
@Component
@Slf4j
public class CheckAuthRoutePredicateFactory extends AbstractRoutePredicateFactory<CheckAuthRoutePredicateFactory.Config> {
@Override
public List<String> shortcutFieldOrder() {
return Collections.singletonList("name");
}
public CheckAuthRoutePredicateFactory() {
super(Config.class);
}
@Override
public Predicate<ServerWebExchange> apply(Config config) {
return new GatewayPredicate() {
@Override
public boolean test(ServerWebExchange serverWebExchange) {
log.info("checkAuthRoutePredicateFactory"+config.getName());
if(config.getName().endsWith("worn.xiao")){
return true;
}
return false;
}
};
}
public static class Config {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
Application.yml 配置
2.6.4 自定义过滤器
Gateway 内置了很多的过滤器工厂,我们通过一些过滤器工厂可以进行一些业务逻辑处理器,比如添加剔除响应头,添加去除参数等。自定义过滤器继承AbstractNameValueGatewayFilterFactory且我们的自定义名称必须要以GatewayFilterFactory结尾并交给spring管理。
@Slf4j
@Component
public class CheckAuthGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory {
@Override
public GatewayFilter apply(NameValueConfig config) {
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("CheckAuthGatewayFilterFactory config={}", JSONObject.toJSONString(config));
return chain.filter(exchange);
}};
}}
Application.yml 配置
2.6.5 全局过滤器
局部过滤器和全局过滤器区别:
局部:局部针对某个路由, 需要在路由中进行配置
全局:针对所有路由请求, 一旦定义就会投入使用
GlobalFilter 接口和 GatewayFilter 有一样的接口定义,只不过, GlobalFilter 会作用于所有路由。
@Component
@Slf4j
public class CustomFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("CustomFilter exchange={}",JSONObject.toJSONString(exchange.getRequest()));
return chain.filter(exchange);
}
}
2.6.6 网关允许跨域
@Configuration
public class CorsConfig {
@Bean
public CorsWebFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedMethod("*");
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
source.registerCorsConfiguration("/**", config);
return new CorsWebFilter(source);
}
}
2.6.7 gateway整合sentinel流控降级
网关作为内部系统外的一层屏障, 对内起到一定的保护作用, 限流便是其中之一. 网关层的限流可以简单地针对不同路由进行限流, 也可针对业务的接口进行限流,或者根据接口的特征分组限流。
<!--sentinel整合gateway 以前的版本adapter-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
</dependency>
<!--sentinel的依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
Sentinel 1.6.3 引入了网关流控控制台的支持,用户可以直接在 Sentinel 控制台上查看 API Gateway 实时的 route 和自定义 API 分组监控,管理网关规则和 API 分组配置。
从 1.6.0 版本开始,Sentinel 提供了 Spring Cloud Gateway 的适配模块,可以提供两种资源维度的限流:
route 维度:即在 Spring 配置文件中配置的路由条目,资源名为对应的 routeId自定义 API 维度:用户可以利用 Sentinel 提供的 API 来自定义一些 API 分组.
2.6.8 网关高可用
为了保证 Gateway 的高可用性,可以同时启动多个 Gateway 实例进行负载,在 Gateway 的上游使用 Nginx 或者 F5 进行负载转发以达到高可用。
2.7 skywalking
2.7.1 链路追踪介绍
对于一个大型的几十个、几百个微服务构成的微服务架构系统,通常会遇到下面一些问题,比如:
1. 如何串联整个调用链路,快速定位问题?
2. 如何缕清各个微服务之间的依赖关系?
3. 如何进行各个微服务接口的性能分折?
4. 如何跟踪整个业务流程的调用处理顺序?
skywalking是一个国产开源框架,2015年由吴晟开源 , 2017年加入Apache孵化器。skywalking是分布式系统的应用程序性能监视工具,专为微服务、云原生架构和基于容器(Docker、K8s、Mesos)架构而设计。它是一款优秀的APM(Application Performance Management)工具,包括了分布式追踪、性能指标分析、应用和服务依赖分析等。
Skywalking主要功能特性
1、多种监控手段,可以通过语言探针和service mesh获得监控的数据;
2、支持多种语言自动探针,包括 Java,.NET Core 和 Node.JS;
3、轻量高效,无需大数据平台和大量的服务器资源;
4、模块化,UI、存储、集群管理都有多种机制可选;
5、支持告警;
6、优秀的可视化解决方案;
2.7.2 SkyWalking 环境搭建部署
1 skywalking Agent(探针)
Agent 运行在各个服务实例中,负责采集服务实例的 Trace 、Metrics 等数据,然后通过 gRPC 方式上报给 SkyWalking 后端。
2、Skywalking oapservice
负责接收 Agent 发送的 Tracing 和Metric的数据信息,然后进行分析(Analysis Core) ,存储到外部存储器( Storage ),最终提供查询( Query )功能。
3、skywalking ui
负责提供web控制台,查看链路,查看各种指标,性能等等。
4、storage
负责数据的存储,支持多种存储类型。
2.7.2.1 skywalking 服务端安装
https://hub.docker.com/r/apache/skywalking-oap-server
https://hub.docker.com/r/apache/skywalking-ui
启动 SkyWalking Server
docker run --name skywalking-oap --restart always -d -p 11800:11800 -p 12800:12800 apache/skywalking-oap-server
启动 UI
docker run --name skywalking-ui --restart always -d -p 8080:8080 --link skywalking-oap:skywalking-oap -e SW_OAP_ADDRESS=skywalking-oap:12800 apache/skywalking-ui
http://192.168.189.3:8080/
2.7.2.2 SkyWalking中三个概念
服务(Service) :表示对请求提供相同行为的一系列或一组工作负载,在使用Agent时,可以定义服务的名字;
服务实例(Service Instance) :上述的一组工作负载中的每一个工作负载称为一个实例, 一个服务实例实际就是操作系统上的一个真实进程;
端点(Endpoint) :对于特定服务所接收的请求路径, 如HTTP的URI路径和gRPC服务的类名 + 方法签名;
2.7.3 接入SkyWalking
下载agent
Downloads | Apache SkyWalking
本地启动
-DSW_AGENT_COLLECTOR_BACKEND_SERVICES 可以指定远程地址, 但是-javaagent必须绑定你本机物理路径的skywalking-agent.jar
2.7.4 Skywalking跨多个微服务跟踪
Skywalking跨多个微服务跟踪,只需要每个微服务启动时添加javaagent参数即可。
测试: 启动微服务mall-gateway,mall-order,mall-user ,配置skywalking的jvm参数.
2.7.5 Skywalking持久化跟踪数据
4.1 基于mysql持久化:
1. 修改config目录下的application.yml,使用mysql作为持久化存储的仓库
2 修改mysql的连接配置,这里要注意如果是docker安装的,要关闭防火墙,保证docker容器网络和宿主机网络是通的,附带一些容器常用工具安装指令。
安装vim命令
apt-get install vim
安装ifconfig命令
apt-get install net-tools
安装ping命令
apt-get install iputils-ping
安装telnet 命令
apt-get install telnet
重启容器
自动创建了很多表,证明Skywalking的持久化已经实现了。
2.7.6自定义SkyWalking链路追踪
依赖
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>apm‐toolkit‐trace</artifactId>
<version>8.4.0</version>
</dependency>
@Trace将方法加入追踪链路
如果一个业务方法想在ui界面的跟踪链路上显示出来,只需要在业务方法上加上@Trace注解即可
加入@Tags或@Tag
我们还可以为追踪链路增加其他额外的信息,比如记录参数和返回信息。实现方式:在方法上增加@Tag或者@Tags。 @Tag 注解中 key = 方法名 ; value = returnedObj 返回值 arg[0] 参数
@Trace
@Tag(key = "list", value = "returnedObj")
public List<User> list() {
return userMapper.list();
}
@Trace
@Tags({@Tag(key = "param", value = "arg[0]"),
@Tag(key = "user", value = "returnedObj")})
public User getById(Integer id) {
return userMapper.getById(id);
}
2.7.7 Skywalking集成日志框架
<!-- apm-toolkit-logback-1.x -->
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>apm-toolkit-logback-1.x</artifactId>
<version>8.5.0</version>
</dependency>
添加logback-spring.xml文件,并配置 %tid 占位符
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径 -->
<!-- <property name="LOG_HOME" value="d:" /> -->
<!-- ch.qos.logback.core.ConsoleAppender 控制台输出 -->
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.TraceIdPatternLogbackLayout">
<pattern>[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %logger{36} - [%tid] - %msg%n</pattern>
</layout>
</encoder>
</appender>
<root level="info">
<appender-ref ref="stdout" />
</root>
</configuration>
<appender name="grpc‐log" class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.log.GRPCLogClientAppender">
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.mdc.TraceIdMDCPatternLogbackLayout">
<Pattern>%d{yyyy‐MM‐dd HH:mm:ss.SSS} [%X{tid}] [%thread] %‐5level %logger{36} ‐%msg%n</Pattern>
</layout>
</encoder>
</appender>
<root level="info">
<appender‐ref ref="grpc‐log" />
</root>
效果