SpringCloud
面向服务(SOA)
面向服务就是将应用程序的不同功能单元(称之为服务)进行拆分,并将这些服务通过接口等联系起来(各服务之间松耦合),想要使用哪个功能,直接进行调用,不会将功能与整个项目紧紧绑定。
微服务
分布式:将业务逻辑进行横向扩展,通过负载均衡将请求分发到不同的服务端上
微服务:微服务一定是分布式,将项目拆分成多个子项目,项目之间使用负载均衡,这些微服务之间是松耦合的,每个微服务都可被独立部署,且都仅关注完成一件任务
SpringCloud Alibaba
组件
-
注册中心:Nacos;
-
负载均衡:Netflix Ribbon;
-
熔断器:Sentinel;
-
声明式服务调用组件:Feign(最初属 Netflix 公司,后来移交给 OpenFeign 组织);
-
网关:Spring Cloud Gateway。
Nacos注册中心
注册中心是微服务架构中的纽带,它记录了服务和服务地址的映射关系。在分布式架构中,服务会注册到这里,当服务需要调用其它服务时,就到这里找到服务的地址并进行调用。注册中心本质上是为了解耦服务提供者和服务消费者。
主要功能:
-
服务注册:服务实例将自身服务信息注册到注册中心
-
服务发现:服务实例通过注册中心,获取到注册到其中的服务实例的信息,通过这些信息去请求它们提供的服务
-
服务剔除:服务注册中心将出问题的服务自动剔除到可用列表之外,使其不会被调用到
Nacos基于AP原则构建的
CAP原则:在一个分布式系统中,Consistency(一致性),Availability(可用性),Partition tolerance(分区容错性)三者不可兼得
Nacos启动
1.在nacos包下的bin目录下启动cmd
2.输入startup.cmd -m standalone启动nacos
3.浏览器输入localhost:8848/nacos,用户名密码默认为nacos
服务器注册到注册中心
1.在pom文件中修改版本,导入依赖
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.12.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent>
<properties> <java.version>1.8</java.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <spring-cloud-alibaba.version>2.2.7.RELEASE</spring-cloud-alibaba.version> <spring.cloud.version>Hoxton.SR12</spring.cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.83</version> </dependency> <!-- 引入 Spring Cloud Alibaba Nacos Discovery 相关依赖,将 Nacos 作为注册中 心,并实现对其的自动配置 --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> </dependencies> <dependencyManagement> <dependencies> <!-- Spring Cloud 版本管理器 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring.cloud.version}</version> <type>pom</type> <!-- 在以前的springboot单体应用中, 使用<parent>标签来引入sprinboot依 赖,但是一个pom.xml只能有一个<parent>标签,如果有多个<parent>标签需要引入怎么办呢?那么这时 候就可以用<scope>import</scope> 来解决; import 可以引入多个<parent>依赖;--> <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> </dependencies> </dependencyManagement>
2.配置文件
#服务的端口号 server.port=8081 server.tomcat.uri-encoding=utf-8 #springcloud相关配置 #服务名称,应用名会注册到nacos中 spring.application.name=productor #注册中心地址 spring.cloud.nacos.discovery.server-addr=localhost:8848 #注册到注册中心的服务名称 spring.cloud.nacos.discovery.service=${spring.application.name}
3.启动类上添加@EnableDiscoveryClient注解
4.启动服务程序
5.启动后可在nacos页面的服务列表看到注册的productor
生产者消费者案例
生产者(productor)
端口号为8081
@RestController @RequestMapping("/user") public class UserController { // 单独创建一个类用于获取配置文件中的信息 @Resource private ConstantProperties constantProperties; /** * 返回用户信息:端口号,用户名 * @param username * @return */ @GetMapping("/info") public Result<?> getUserInfo(String username) { JSONObject obj = new JSONObject(); obj.put("username",username); obj.put("port",constantProperties.getPort()); obj.put("expireTime",constantProperties.getExpireTime()); return new Result<>().success("获取用户信息成功").put(obj); } }
其中的ConstantProperties类
@Component @Data public class ConstantProperties { // 获取到配置文件中的属性值 @Value("${server.port}") private Integer port; }
消费者(consumer)
端口号为7071
@RestController @RequestMapping("/user") public class UserController { // 远程访问 @Resource private RestTemplate restTemplate; @Resource private DiscoveryClient discoveryClient; /** * 远程调用productor服务的“/user/info”接口,获取返回值 * @param username * @return */ @GetMapping("/info") public Result<?> getUserInfo(String username) { // 从nacos注册中心获取指定服务名称的服务列表 List<ServiceInstance> list = discoveryClient.getInstances("productor"); // 从服务列表中获取一个服务 ServiceInstance instance = list.get(0); URI uri = instance.getUri(); String url = uri + "/user/info?username=" + username; // 向服务提供者发起请求(即生产者) Result result = restTemplate.getForObject(url,Result.class); return result; } }
其中的RestTemplate类
@Configuration public class RestTemplateConfig { @Bean public RestTemplate restTemplate() { return new RestTemplate(); } }
测试
当访问http://localhost:7071/user/info?username=zhangsan时,会优先进入消费者服务器,在消费者服务器中进行地址拼接后,发起远程访问,访问到生产者服务器,返回值
配置中心
在单体架构的时候我们可以将配置写在配置文件中,但有⼀个缺点就是每次修改配置都需要重启服务才能生效
Nacos除了可以做注册中心,也可以做统一配置管理(配置中心),Nacos提供了一种统一配置管理方案,可以集中管理所有实例的配置
注意:如果配置中心的配置需要在项目启动时进行初始化,则RefreshScope虽然能实时更新配置项,但不能使其生效,需要项目重启后生效
在配置中心进行配置
1.在配置管理的配置列表下,创建配置
-
Data ID
如:productor-dev.properties
${prefix}-${spring.profiles.active}.${file-extension}
prefix 默认为 spring.application.name 的值
spring.profiles.active 即为当前环境对应的 profile,例如开发环境为dev,测试环境为uat,生产环境为prod等
file-exetension 为配置内容的数据格式,支持 properties 、 yaml 和 yml 类型
-
Group:分组,默认为DEFAULT_GROUP
-
配置格式:目前,Springboot中能识别的配置格式只支持YAML和Properties两个格式
-
配置内容:根据自己需要配置的内容进行配置
2.将项目启动时需要进行初始化的配置项放在application.properties中,将改动相对频繁,能够实时生效的配置项可以配置在配置中心
application.properties
server.port=8081 server.tomcat.uri-encoding=utf-8 #springcloud相关配置 #应用名会注册到nacos中 spring.application.name=productor #注册中心地址 spring.cloud.nacos.discovery.server-addr=localhost:8848 #注册到注册中心的服务名称 spring.cloud.nacos.discovery.service=${spring.application.name}
配置中心
#自定义配置 #过期时间 system.expireTime=3000
3.添加依赖
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </dependency>
4.再新建bootstrap.properties配置文件,用于拉取配置中心相应的配置
#bootstrap.properties加载优先级比application.properties高 spring.application.name=productor spring.cloud.nacos.server-addr=localhost:8848 #配置文件的后缀 spring.cloud.nacos.config.file-extension=properties #指定开发环境信息 spring.profiles.active=dev #拼接后组成productor-dev.properties与配置中心Data ID对应 #自定义可扩展的配置文件,可以添加多个文件,以数组下标区分(即在配置中心需要拉取多个配置文件) spring.cloud.nacos.config.extension-configs[0].data-id=productor-test.properties spring.cloud.nacos.config.extension-configs[0].refresh=true
5.在需要获取配置文件中配置项值的类上添加@RefreshScope注解
@RefreshScope// 实时刷新配置中心修改的配置项 @Component @Data public class ConstantProperties { @Value("${server.port}") private Integer port; // 获取到配置文件中的属性值 @Value("${system.expireTime}") private Long expireTime; }
配置方式
1.在nacos配置中心配置Data ID,格式:前缀(默认${spring.application.name})-环境名(默认${spring.profiles.active}).后缀名(默认${spring.cloud.nacos.config.file-extension})
在bootstrap.properties中需要配置:${spring.application.name}、${spring.profiles.active}、${spring.cloud.nacos.config.file-extension}
#bootstrap.properties加载优先级比application.properties高 spring.application.name=productor spring.cloud.nacos.server-addr=localhost:8848 #配置文件的后缀 spring.cloud.nacos.config.file-extension=properties #指定开发环境信息 spring.profiles.active=dev #拼接后组成productor-dev.properties与配置中心Data ID对应 #自定义可扩展的配置文件,可以添加多个文件,以数组下标区分(即在配置中心需要拉取多个配置文件) spring.cloud.nacos.config.extension-configs[0].data-id=productor-test.properties spring.cloud.nacos.config.extension-configs[0].refresh=true
2.在nacos配置中心使用Data ID和group一起定义配置文件,需要在配置中心的配置文件中手动指定group(任意定义group名称)
在bootstrap.properties中不用配置${spring.profiles.active},需要配置${spring.cloud.nacos.config-group}
#bootstrap.properties加载优先级比application.properties高 spring.application.name=productor spring.cloud.nacos.server-addr=localhost:8848 #配置文件的后缀 spring.cloud.nacos.config.file-extension=properties #指定开发环境信息 spring.cloud.nacos.config.group=dev #拼接后组成productor-dev.properties与配置中心Data ID对应 #自定义可扩展的配置文件,可以添加多个文件,以数组下标区分(即在配置中心需要拉取多个配置文件) #下标值越大,优先级越高,会发生覆盖;默认配置的优先级高于可覆盖配置的优先级 spring.cloud.nacos.config.extension-configs[0].data-id=${spring.application.name}.${spring.cloud.nacos.config.file-extension} spring.cloud.nacos.config.extension-configs[0].refresh=true spring.cloud.nacos.config.extension-configs[0].group=test
Ribbon
负载均衡器
负载均衡是我们处理高并发、缓解网络压力和进行服务器扩容的重要手段之一,但是一般情况下我们所说的负载均衡通常都是指服务器端负载均衡
案例
1.在消费者服务端RestTemplateConfig类上添加@LoadBalanced注解
@Configuration public class RestTemplateConfig { @Bean // 负载均衡注解,默认负载均衡策略是轮询策略 @LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); } }
2.消费者服务端controller方法
@RestController @RequestMapping("/user") public class UserController { // 远程访问 @Resource private RestTemplate restTemplate; @GetMapping("/add") public Result<?> addUser(String username) { JSONObject param = new JSONObject(); param.put("username",username); // url中使用服务名来指定要访问的服务 // 服务名称会被LoadBalanced注解拦截,根据服务名从nacos获取服务列表,再根据负载均衡策略选择服务器进行处理 Result result = restTemplate.postForObject("http://productor/user/add", param, Result.class); return result; } }
3.在生产者服务端使用maven,clean后进行package
4.将jar包复制到文件夹中进行部署,使用cmd,第一次部署命令为java -jar springcloud_primary-0.0.1-SNAPSHOT.jar(包名),第二次部署命令为java -jar springcloud_primary-0.0.1-SNAPSHOT.jar(包名) --server.port=8082(指定端口号,防止重复)
5.此时再去访问时,第一次端口号为8081,第二次为8082,第三次为8081,......(轮询策略)
常用负载均衡策略
策略配置方式
文件配置
#等号后为策略对应类的全类名 productor.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.WeightedResponseTimeRule
代码配置
// 创建如下类 @Configuration public class MyRule { @Bean public IRule iRule() { return new WeightedResponseTimeRule(); } }
// 启动类上添加,其中name为服务提供者的名称,configuration为定义的策略类 @RibbonClient(name = "productor", configuration = MyRule.class)
Ribbon饥饿加载
Ribbon负载均衡策略默认在第一次请求发送时进行初始化
饥饿加载:在项目启动时进行初始化
#开启 Ribbon 的饥饿加载模式 ribbon.eager-load.enabled=true #指定需要饥饿加载的服务名,也就是你需要调用的服务,若有多个则用逗号隔开 ribbon.eager-load.clients=productor
Feign
Feign是一个声明式的REST客户端,简化了微服务之间的调用,类似controller调用service
Feign集成了Ribbon和Hystrix,其中Ribbon(默认为轮询策略):利用负载均衡策略选定目标机器;Hystrix:根据熔断器的开启状态,决定是否发起此次调用
案例
1.在消费者服务器中添加依赖
<!-- feign组件所需依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
2.在消费者启动类上添加注解@EnableFeignClients注解,开启远程调用
3.在消费者service接口上添加@FeignClient注解,value属性:远程调用的服务名称,到注册中心找到服务器
4.定义远程访问接口方法(即在service层发送远程调用请求到生产者服务器),返回数据类型要和服务提供者接口的数据类型一致;方法上添加Mapping注解,请求方式要和服务提供者的请求方式一致
如果是get请求,则需要在接口方法的参数上添加@RequestParam注解;如果是post请求,参数上的@RequestParam注解可以省略
@FeignClient(value = "productor") public interface UserService { @GetMapping("/user/info") Result<?> getUserInfo(@RequestParam("username") String username); }
日志配置
1.自定义日志配置类
日志级别:
-
NONE:不输出日志
-
BASIC:只输出请求方法的 URL 和响应的状态码以及接口执行的时间
-
HEADERS:将 BASIC 信息和请求头信息输出
-
FULL:输出完整的请求信息
@Configuration public class FeignConfig { @Bean public Logger.Level feignLevel() { return Logger.Level.FULL; } }
2.在service层的@FeignClient注解中添加属性configuration = FeignConfig.class
@FeignClient(value = "productor", configuration = FeignConfig.class)
3.配置文件中配置日志级别
logging.level.com.springcloud=debug
策略配置方式
与Ribbon配置方式相同,区别是不是在启动类上添加注解,而是在service层添加注解
1.创建配置类
@Configuration public class MyRule { @Bean public IRule iRule() { return new WeightedResponseTimeRule(); } }
2.在service接口上的@FeignClient注解中添加configuration属性
@FeignClient(value = "productor", configuration = MyRule.class)
当需要同时使用日志配置和策略配置时,可将两个配置写在一个配置类中国,此时configuration就只有一个属性
降级处理
Feign先在UserService接口方法中远程调用服务提供者,如果服务提供者响应出现异常,则调用该接口方法的实现类方法
1.创建service接口的实现类
@Service public class UserServiceImpl implements UserService { @Override public Result<?> getUserInfo(String username) { return new Result<>().error("获取用户信息失败"); } }
2.在service层@FeignClient注解中添加fallback属性,指定哪个类来做降级处理
@FeignClient(value = "productor", configuration = MyRule.class, fallback = UserServiceImpl.class)
3.在配置文件中开启熔断
feign.hystrix.enabled=true
Sentinel熔断器
雪崩效应: 一个微服务中可能会调用多个微服务接口才能完成, 形成复杂的链式调用. 当其中一个服务接口出现异常访问, 会导致整个线程堵塞. 越来越多的用户请求就会有越来越多的线程堵塞,资源无法释放,导致当前微服务的服务器资源耗尽, 当前服务挂掉, 从而引起雪崩效应
熔断的意义:是为了起到保护作用,如果某个目标服务调用比较慢或者大量的超时,这个时候如果触发熔断机制,则可以保证后续的请求不会继续发送到目标服务上,而是直接返回降级的逻辑并且快速释放资源。如果目标服务的情况恢复了,那么熔断机制又会动态进行关闭
启动
1.在sentinel-dashboard-1.8.6.jar的目录下启动cmd
2.输入命令java -jar sentinel-dashboard-1.8.6.jar(默认启动是8080,保证8080端口不能被占用)
3.浏览器输入localhost:8080即可访问,默认用户名/密码:sentinel/sentinel
项目初始化
1.在消费者服务器中配置,引入依赖
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency>
2.配置文件中配置
# sentinel dashboard 地址 spring.cloud.sentinel.transport.dashboard=localhost:8080 # sentinel dashboard 内部通信端口,默认为 8719,如果被占用会自动 +1,直到找到为止 spring.cloud.sentinel.transport.port=8719
流控规则
1.在sentinel页面找到簇点链路后,找到对应的接口,点击流控操作
2.设置自定义的流控规则
注意:重启项目后,设置的流控规则会消失
熔断规则
1.在sentinel页面找到簇点链路后,找到对应的接口,点击熔断操作
2.设置自定义的流控规则
RT:响应时间:执行一个请求从开始到最后收到响应数据所花费的总体时间,即从客户端发起请求到收到服务器响应结果的时间。
慢调用:当调用的时间(响应的实际时间)>设置的RT的时,这个调用叫做慢调用。
比例阈值:慢调用次数 / 调用次数=慢调用比例,当慢调用比例大于比例阈值时,会触发熔断。
熔断时长:满足该熔断规则的条件后,系统停止调用该接口的时长。
统计时长:规则统计的时间范围。
自定义降级处理
整合Ribbon案例
1.在controller需要处理的接口方法上添加@SentinelResource注解,其中有value(必需),blockHandler,fallback属性
-
value:资源名,可以是接口的url,也可以自定义资源名
-
blockHandler:对应处理服务 BlockException 的函数名称
BlockException:
其包含五个子类,分别对应sentinel流控降级规则的授权规则、降级规则、限流规则、热点规则、系统规则,因此blockHandler是专门用来处理sentinel异常的
1.blockHandler 函数访问范围需要是 public
2.返回类型需要与原方法相匹配
3.参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为 BlockException
4.blockHandler 函数默认需要和原方法在同一个类中
-
fallback:用于在抛出异常的时候提供 fallback 处理逻辑。fallback 函数可以针对所有类型的异常进行处理
1.函数访问范围需要是 public
2.返回值类型必须与原函数返回值类型一致
3.方法参数列表需要和原函数一致,可以额外多一个 Throwable 类型的参数用于接收对应的异常
4.fallback 函数默认需要和原方法在同一个类中
@GetMapping("/info") @SentinelResource(value = "getUserInfo", blockHandler = "userInfoBlockHandler",fallback ="userInfoFallback") public Result<?> getUserInfo(String username) { String url = "http://productor/user/info?username=" + username; Result result = restTemplate.getForObject(url,Result.class); return result; }
2.编写blockHandler方法
public Result<?> userInfoBlockHandler(String username, BlockException e) { Result<?> result = new Result<>().error(); if (e instanceof FlowException) { result.setMsg("接口被限流了"); } else if (e instanceof DegradeException) { result.setMsg("服务被降级了"); } return result; }
3.编写fallback方法
public Result<?> userInfoFallback(String username) { return new Result<>().error("业务服务被降级了"); }
注意:fallback和blockHandler区别:
-
blockHandler是专门(只)处理sentinel流控降级规则抛出的BlockException。
-
而fallback默认处理所有的异常,其中包括BlockEcxeption(因为BlockException是Exception的子类),也就是说如果我们不配置blockHandler,其抛出BlockEcxeption也将会进入fallback方法中。
-
如果同时配置了blockHandler和fallback,出现BlockException时将进入blockHandler方法中处理。例如当接口发生异常时会调用fallback降级,如果异常数超出了sentinel配置的熔断规则,则会触发BlockException,此时会进入blockHandler方法进行处理。
整合Feign案例
1.创建userService接口的实现类,用来做降级处理
@Service public class UserServiceImpl implements UserService { @Override public Result<?> getUserInfo(String username) { return new Result<>().error("获取用户信息失败"); } }
2.在自定义的UserService的接口上的@FeignClient注解里添加处理这个接口的熔断类:fallback=UserServiceImpl.class
@FeignClient(value = "productor", configuration = MyRule.class, fallback = UserServiceImpl.class)
3.配置application文件,开启熔断组件
feign.sentinel.enabled=true
注意:Ribbon中fallback方法需要自定义,而Feign中fallback方法就是接口的实现类
Sentinel规则持久化
当重启应用,Sentinel规则将消失,所以需要将配置规则进行持久化
导入依赖
<dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> </dependency>
流量控制
1.在注册中心添加配置
[ { "resource": "getUserInfo", "limitApp": "default", "grade": 1, "count": 1, "strategy": 0, "controlBehavior": 0, "clusterMode": false } ]
-
resource:资源名,可以随意定义,资源名是限流规则的作用对象,比如请求资源getUser。
-
limitApp:流控针对的调用来源。默认就是default,代表不区分调用来源。
-
grade:限流阈值类型,QPS或线程数模式。0表示线程数,1表示QPS。默认为1,即QPS模式。
-
count:限流阈值。比如值为2表示1秒超过2个请求就限流。
-
strategy:流控模式:直接、链路、关联,默认直接。0表示直接,1表示关联,2表示链路。
-
controlBehavior:流控效果(直接拒绝/排队等待/慢启动模式),0表示快速失败,1表示WarmUp,2表示排队等待。
-
clusterMode:是否为集群。值为boolean类型。
2.在消费者服务器的配置类中配置
#nacos配置中心的dataID spring.cloud.sentinel.datasource.flow.nacos.data-id=user-flow-sentinel.json #nacos的注册地址 spring.cloud.sentinel.datasource.flow.nacos.server-addr=localhost:8848 #nacos配置中心的数据配置格式 spring.cloud.sentinel.datasource.flow.nacos.data-type=json #nacos分组 spring.cloud.sentinel.datasource.flow.nacos.group-id=DEFAULT_GROUP #sentinel规则类型 # flow:控流、degrade:熔断、param_flow:热点规则、system:系统规则、authority:授权规则 spring.cloud.sentinel.datasource.flow.nacos.rule-type=flow
3.再次发送请求到配置了持久化的服务器,就会自动开启规则
熔断控制
1.在注册中心添加配置
[ { "resource": "addUser", "grade": 0, "count": 1, "timeWindow": 10, "minRequestAmount": 5, "statIntervalMs": 100000, "slowRatioThreshold":0.1 } ]
-
resource:资源名,资源名是限流规则的作用对象,比如请求资源getUser。
-
grade:熔断策略,支持慢调用比例/异常比例/异常数策略。0:慢调用比例,1:异常比例,2:异常数。默认为0,慢调用比例。
-
count:慢调用比例模式下为慢调用临界RT(超出该值计为慢调用);异常比例/异常数模式下为对应的阈值。
-
timeWindow:熔断时长,单位为秒。
-
minRequestAmount:熔断触发的最小请求数,请求数小于该值时即使异常比率超出阈值也不会熔断。默认为5。
-
statIntervalMs:统计时长(单位为ms),如60*1000代表分钟级。默认为1000ms。
-
slowRatioThreshold:慢调用比例阈值,仅慢调用比例模式有效
2.在消费者服务器的配置类中配置
#nacos配置中心的dataID spring.cloud.sentinel.datasource.degrade.nacos.data-id=user-degrade-sentinel.json #nacos的注册地址 spring.cloud.sentinel.datasource.degrade.nacos.server-addr=localhost:8848 #nacos配置中心的数据配置格式 spring.cloud.sentinel.datasource.degrade.nacos.data-type=json #nacos分组 spring.cloud.sentinel.datasource.degrade.nacos.group-id=DEFAULT_GROUP #sentinel规则类型 spring.cloud.sentinel.datasource.degrade.nacos.rule-type=degrade
3.再次发送请求到配置了持久化的服务器,就会自动开启规则
注意:持久化与@SentinelResource注解无关,但如果需要在方法上添加@SentinelResource注解时,value的值必须与资源名相同
Gateway(API网关)
是一个处于应用程序或服务(提供 REST API 接口服务)之前的系统,用来管理授权、访问控制和流量限制等,这样 REST API 接口服务就被 API 网关保护起来,对所有的调用者透明。因此,隐藏在 API 网关后面的业务系统就可以专注于创建和管理服务,而不用去处理这些策略性的基础设施。
-
路由:是 Spring Cloud Gateway 中基础的组件,通常由一个 id 标识,目标 URI,以及一系列断言(Predicate)和过滤器组成。
-
断言(Predicate):是 Java 8 函数库的 Predicate 对象,用于匹配 HTTP 请求上数据信息,如请求头信息,请求体信息。如果对于某个请求的断言为 true,那么它所关联的路由就算匹配成功,然后将请求给这个路由处理。
-
过滤器:用于某一个路由的请求或者响应进行修改的组件,在 Spring Cloud Gateway 都要实现GatewayFilter 接口,并且需要由基于 GatewayFilterFactory 具体实现类构造。
-
客户端请求首先被 GatewayHandlerMapping 获取,然后根据断言匹配找到对应的路由。
-
找到路由后,走完所关联的一组请求过滤器的处理方法,请求到目标 URI 所对应的服务程序,获得服务响应。
-
网关收到响应后,通过关联的响应过滤器的处理方法后,同样由 GatewayHandlerMapping 返回响应给客户端。
配置路由网关
1.新建一个SpringBoot项目,注意不需要添加Spring Web
2.修改版本为2.3.12.RELEASE,删除无关文件,并添加如下依赖
<properties> <java.version>1.8</java.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF- 8 </project.reporting.outputEncoding> <spring-cloud-alibaba.version>2.2.7.RELEASE</spring-cloud-alibaba.version> <spring.cloud.version>Hoxton.SR12</spring.cloud.version> </properties> <dependencies> <!-- 引入 Spring Cloud Alibaba Nacos Discovery 相关依赖,将 Nacos 作为注册中 心,并实现对其的自动配置 --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <!--gateway路由网关依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> </dependencies> <dependencyManagement> <dependencies> <!-- Spring Cloud 版本管理器 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring.cloud.version}</version> <type>pom</type> <!-- 在以前的springboot单体应用中, 使用<parent>标签来引入sprinboot依 赖,但是一个pom.xml只能有一个<parent>标签,如果我有多个<parent>标签需要引入怎么办呢? 那么这时候就可以用 <scope>import</scope> 来解决; import 可以引入 多个<parent>依赖; --> <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> </dependencies> </dependencyManagement>
3.启动类添加@EnableDiscoveryClient注解
4.配置文件
server.port=6001 server.tomcat.uri-encoding=UTF-8 spring.application.name=gateway #注册中心 spring.cloud.nacos.server-addr=localhost:8848 spring.cloud.nacos.discovery.service=${spring.application.name} #设置路由id,没有固定规则,要求唯一,建设配合服务名 spring.cloud.gateway.routes[0].id=gateway-consumer #设置路由的uri,lb表示负载均衡,consumer是应用的服务名称(即在注册中心找到相应服务器的服务名称) spring.cloud.gateway.routes[0].uri=lb://consumer #断言 spring.cloud.gateway.routes[0].predicates[0]=Path=/gw/** #过滤器,StripPrefix=1表示将断言中url路径切分,获取最后一个路径转发 #简单理解:将断言中添加的路径前缀过滤掉,转发真实的url spring.cloud.gateway.routes[0].filters[0]=StripPrefix=1 #开启网关路由 spring.cloud.gateway.discovery.locator.enabled=true
5.访问路径变成 http://localhost:6001/gw/user/info?username=zhangsan
网关过滤器
引入依赖
<dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.83</version> </dependency>
全局过滤器
只要是通过网关访问访问服务器的都会被拦截
1.新建IpFilter类
/** * 全局过滤器: * 所有的请求都会被拦截 */ @Component public class IpFilter implements GlobalFilter, Ordered { private static List<String> ipBlackList; static { ipBlackList = new ArrayList<>(); ipBlackList.add("127.0.0.1"); ipBlackList.add("0:0:0:0:0:0:0:1"); } // IP黑名单过滤 @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpRequest request = exchange.getRequest(); ServerHttpResponse response = exchange.getResponse(); // 获取请求ip String ip = request.getRemoteAddress().getHostString(); if (ipBlackList.contains(ip)) { response.setStatusCode(HttpStatus.UNAUTHORIZED); JSONObject obj = new JSONObject(); obj.put("code",401); obj.put("msg","拒绝访问"); DataBuffer dataBuffer = response.bufferFactory().wrap(obj.toString().getBytes()); response.getHeaders().add("Content-Type","application/json;charset=UTF-8"); return response.writeWith(Mono.just(dataBuffer)); } return chain.filter(exchange); } // 设置过滤器的优先级,数值越小,优先级越高 @Override public int getOrder() { return 0; } }
局部过滤器
1.自定义gatewayFilter,重写filter方法
/** * 敏感词汇过滤 */ public class TermFilter implements GatewayFilter, Ordered { /** * 敏感词汇列表初始化 */ private static List<String> termList; static { termList = new ArrayList<>(); termList.add("zhangsan"); termList.add("lisi"); } /** * 从请求中获取参数值,判断参数值是否在敏感词汇名单中 * 存在则拒绝访问 * 不存在则放行 * * @param exchange * @param chain * @return */ @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpRequest request = exchange.getRequest(); ServerHttpResponse response = exchange.getResponse(); // 获取请求中的参数列表 MultiValueMap<String,String> queryParams = request.getQueryParams(); for (String key : queryParams.keySet()) { // 根据参数名获取参数值列表 List<String> list = queryParams.get(key); for (String p:list) { if (termList.contains(p)) { response.setStatusCode(HttpStatus.UNAUTHORIZED); JSONObject obj = new JSONObject(); obj.put("code",401); obj.put("msg","包含敏感词汇,拒绝访问"); DataBuffer buffer = response.bufferFactory().wrap(obj.toString().getBytes()); response.getHeaders().setContentType(MediaType.APPLICATION_JSON); return response.writeWith(Mono.just(buffer)); } } } return chain.filter(exchange); } @Override public int getOrder() { return 0; } }
2.创建gatewayFilterFactory(命名格式:前缀+GatewayFilterFactory),重写apply方法,在该方法中初始化自定义的gatewayFilter
@Component public class TermGatewayFilterFactory extends AbstractGatewayFilterFactory<Object> { @Override public GatewayFilter apply(Object config) { return new TermFilter(); } }
3.在application.properties中使用gatewayFilterFactory的名称前缀作为过滤器
spring.cloud.gateway.routes[0].filters[1]=Term
注意:当同时存在全局过滤器和局部过滤器时,需要在getOrder方法中设置优先级,如将全局过滤器的优先级设置为10
网关跨域配置
配置文件方式
#网关跨域 spring.cloud.gateway.globalcors.cors-configurations[/**].allow-credentials=true spring.cloud.gateway.globalcors.cors-configurations[/**].allowed-headers=* spring.cloud.gateway.globalcors.cors-configurations[/**].allowed-methods=* spring.cloud.gateway.globalcors.cors-configurations[/**].allowed-origins=*
代码方式
/** * 注意: * gateway使用的filter是响应式的CorsWebFilter,SpringBoot使用的是CorsFilter * gateway使用响应式的UrlBasedCorsConfigurationSource,SpringBoot使用的是普通的UrlBasedCorsConfigurationSource */ @Configuration public class CorsWebConfig { @Bean public CorsWebFilter corsWebFilter() { CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); config.addAllowedMethod("*"); config.addAllowedHeader("*"); config.addAllowedOrigin("*"); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**",config); return new CorsWebFilter(source); } }