SpringCloud
SpringCloud
SpringCloudAlibaba
创建项目
创建Springboot父项目,只需要一个pom就可以了(AugJungle:生产项目单独部署)
POM:
1 |
|
pom添加SpringCloudAlibaba依赖
1 | <dependencyManagement> |
创建子模块后修改子模块中的pom里面的parent标签,修改为父项目中的值
1 |
|
开启Nacos Server
文档地址:https://nacos.io/zh-cn/docs/quick-start.html
Windows版Nacos Server直接下载地址:https://download.csdn.net/download/qq_35742333/12697057
下载后进入bin文件夹下运行 .\startup.cmd -m standalone
-m:以单机模式运行
注册Nacos Discovery
子项目导入依赖
1 | <dependency> |
maven出现问题:
Could not find artifact com.alibaba.cloud:spring-cloud-starter-alibaba-nacos-discovery:pom:unknown in aliyun (https://maven.aliyun.com/repository/public)
Non-resolvable parent POM for com.jungle:news:0.0.1-SNAPSHOT: Could not find artifact com.jungle:springclouddemo:pom:0.0.1-SNAPSHOT and 'parent.relativePath' points at no local POM @ line 5, column 13
问题解决:
父项目添加
不是必须
1 | <modules> |
必须
父项目记得配一个
子项目最好配一个
修改配置文件
在application.properties文件添加spring.profiles.active=dev
新建application-dev.yml文件,添加配置
1 | server: |
此时运行子项目,已经可以在Nacos Server上看见注册的子项目运行服务,访问网址:http://192.168.20.29:8848/nacos/index.html
可以看见已经注册的服务
DiscoveryClient
服务发现对象,可以用来进行手动服务注册
1 |
|
使用Ribbon
参考文档:https://cloud.spring.io/spring-cloud-netflix/reference/html/
参考文档-ribbon细节:https://cloud.spring.io/spring-cloud-netflix/multi/multi_spring-cloud-ribbon.html
概念
Ribbon是Netflix发布的云中间层服务开源项目,主要功能是提供客户端(消费者方)负载均衡算法。Ribbon客户端组件提供一系列完善的配置项,如,连接超时,重试等。简单的说,Ribbon是一个客户端负载均衡器,我们可以在配置文件中列出load Balancer后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器,我们也很容易使用Ribbon实现自定义的负载均衡算法。
Bean实例
The following table shows the beans that Spring Cloud Netflix provides by default for Ribbon:
Bean Type | Bean Name | Class Name | 作用 |
---|---|---|---|
IClientConfig |
ribbonClientConfig |
DefaultClientConfigImpl |
读取配置 |
IRule |
ribbonRule |
ZoneAvoidanceRule |
负载均衡规则,选择实例 |
IPing |
ribbonPing |
DummyPing |
筛选掉ping不通的实例 |
ServerList<Server> |
ribbonServerList |
ConfigurationBasedServerList |
交给Ribbon的实例列表 |
ServerListFilter<Server> |
ribbonServerListFilter |
ZonePreferenceServerListFilter |
过滤掉不符合条件的实例 |
ILoadBalancer |
ribbonLoadBalancer |
ZoneAwareLoadBalancer |
Ribbon的入口 |
ServerListUpdater |
ribbonServerListUpdater |
PollingServerListUpdater |
更新交给Ribbon的List的策略 |
开启RestTemplate的负载均衡
- 启动类上添加
@EnableDiscoveryClient
- 给
RestTemplate
实例添加@LoadBalanced
1 |
|
代码实现自定义配置
局部配置
1 |
|
上面意思是,针对custom服务调用,选择CustomConfiguration
类的算法合适
全局配置
1 |
上面意思是,针对所有服务调用,选择DefaultRibbonConfig
类的算法合适
注意
The CustomConfiguration
class must be a @Configuration
class, but take care that it is not in a @ComponentScan
for the main application context. Otherwise, it is shared by all the @RibbonClients
. If you use @ComponentScan
(or @SpringBootApplication
), you need to take steps to avoid it being included (for instance, you can put it in a separate, non-overlapping package or specify the packages to scan explicitly in the @ComponentScan
).
需要将配置类放到@SpringBootApplication
注解扫描不到的上级菜单中,避免冲突
实例
在
@SpringBootApplication
不可以扫描到的地方,定义一个Ribbon的配置类1
2
3
4
5
6
7
public class RibbonConfig {
public IRule getIRule(){
return new BestAvailableRule();
}
}在
@SpringBootApplication
可以扫描到,定义一个Ribbon配置类,用来指定配置的位置1
2
3
4@Configuration
@RibbonClient(name = "custom", configuration = CustomConfiguration.class)
public class TestConfiguration {
}
properteis属性配置自定义配置
Starting with version 1.2.0, Spring Cloud Netflix now supports customizing Ribbon clients by setting properties to be compatible with the Ribbon documentation.
This lets you change behavior at start up time in different environments.
The following list shows the supported properties>:
<clientName>.ribbon.NFLoadBalancerClassName
: Should implementILoadBalancer
<clientName>.ribbon.NFLoadBalancerRuleClassName
: Should implementIRule
<clientName>.ribbon.NFLoadBalancerPingClassName
: Should implementIPing
<clientName>.ribbon.NIWSServerListClassName
: Should implementServerList
<clientName>.ribbon.NIWSServerListFilterClassName
: Should implementServerListFilter
Classes defined in these properties have precedence over beans defined by using @RibbonClient(configuration=MyRibbonConfig.class) and the defaults provided by Spring Cloud Netflix. |
|
---|---|
To set the IRule
for a service name called users
, you could set the following properties:
application.yml
1 | users: |
See the Ribbon documentation for implementations provided by Ribbon.
与NacosServer联动
可以通过在nacos server网页上设置值,来动态调整Ribbon策略
修改Ribbon的配置类,调用自定义的配置类
1
2
3
4
5
6
7
8
public class RibbonConfig {
public IRule getIRule(){
return new WeightRule;
}
}增加配置类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41public class WeightRule extends AbstractLoadBalancerRule {
/**
* 服务发现参数对象,Nacos自带
*
* @date 2020/08/11
* @see NacosDiscoveryProperties
*/
private NacosDiscoveryProperties nacosDiscoveryProperties;
/**
* 配置初始化
*
* @param iClientConfig 我的客户端配置
* @return
* @author Jiangmanman
* @date 2020/08/11
*/
public void initWithNiwsConfig(IClientConfig iClientConfig) {
}
public Server choose(Object key) {
//1.拿到要访问的服务名
//1.1调用父类对象
ILoadBalancer loadBalancer = this.getLoadBalancer();
//1.2转换成真正的实现类
BaseLoadBalancer baseLoadBalancer = (BaseLoadBalancer)loadBalancer;
//1.3得到加载的服务名称
String serviceName = baseLoadBalancer.getName();
//2.通过服务名获得在网页端往 Nacos Service 设置的权重值的实例
//2.1通过Nacos参数对象获得服务名
NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
//2.1得到所有的健康的实例
Instance instance = namingService.selectOneHealthyInstance(serviceName);
return new NacosServer(instance);
}
}优化,可以限定上,仅调用相同集群下的instance(或者更加精确)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27//优化,通过服务名在Nacos上面获得相同集群下面的instance
//获得服务名下所有健康的实例
List<Instance> allInstances = namingService.getAllInstances(serviceName, true);
//获得当前集群名
String clusterName = nacosDiscoveryProperties.getClusterName();
//进行比对
List<Instance> clusterInstance = allInstances.stream().filter(instance1 -> {
return Objects.equals(clusterName, instance1);
}).collect(Collectors.toList());
//判断是否有相邻的集群节点
if (CollectionUtils.isEmpty(clusterInstance)) {
//维持原样
clusterInstance = allInstances;
}
//通过权重算法从集合中获取一个instance,分析前面的 selectOneHealthyInstance 方法,里面就有实现的过程
Instance weightInstance = Mybalancer.getWeightInstance(clusterInstance);
/**
*这个类是分析 selectOneHealthyInstance 方法的实现得来的
*/
public static class Mybalancer extends Balancer{
public static Instance getWeightInstance(List<Instance> hosts){
return getHostByRandomWeight(hosts);
}
}
注意:上述的优化只是手动实现,其实NamingService里面的selectOneHealthyInstance方法的重载里面有对这个的实现,直接调用即可
现在就可以通过Nacos Server 在网页中手动设置权重值,动态改变调用者,但是遇到No instances available
,原因是配置里面进行了分组造成的,将分组去掉后可以正常调用,原因不明,有时间在回头看
缓存的设置
Ribbon第一次调用比较慢,就是因为默认实现是懒加载的方式,调用后才会取拉取信息,可以通过配置来修改这种默认行为
Each Ribbon named client has a corresponding child application Context that Spring Cloud maintains. This application context is lazily loaded on the first request to the named client. This lazy loading behavior can be changed to instead eagerly load these child application contexts at startup, by specifying the names of the Ribbon clients, as shown in the following example:
application.yml
1 | ribbon: |
Feign
概念
Feign is a declarative web service client. It makes writing web service clients easier. To use Feign create an interface and annotate it. It has pluggable annotation support including Feign annotations and JAX-RS annotations. Feign also supports pluggable encoders and decoders. Spring Cloud adds support for Spring MVC annotations and for using the same HttpMessageConverters
used by default in Spring Web. Spring Cloud integrates Ribbon and Eureka, as well as Spring Cloud LoadBalancer to provide a load-balanced http client when using Feign.
主要解决客户端调用问题
使用方式
导入依赖
To include Feign in your project use the starter with group
org.springframework.cloud
and artifact idspring-cloud-starter-openfeign
. See the Spring Cloud Project page for details on setting up your build system with the current Spring Cloud Release Train.1
2
3
4
5<!--Spring Feign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>配置注解
@EnableFeignClients
,调用方必须配置,被调用方可以不配置1
2
3
4
5
6
7
8
9
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}编写接口,接口的配置和要调用的controller保持一直
1
2
3
4
5
6
7
//调用的方法有,这里也必须有,保证一致性
public interface UserClient {
CommonResult all();
}使用方式,此接口会放入容器中,所以
@Resource
注入即可注意:
- 使用注解
@FeignClient
的接口去调用其他服务一定要标准化,例如多个参数必须使用@RequestParam
来注明 - 在调用表单参数的方法时,例如
CommonResult all(User user);
,需要将对象转变成表单参数,加上@SpringQueryMap
注解
- 使用注解
开启日志(默认关闭)
代码实现
编写配置类
1
2
3
4
5
6
7
public class FooConfiguration {
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}全局配置需要在
@EnableFeignClients
注解上指定defaultConfiguration 属性,例如:@EnableFeignClients(defaultConfiguration = UserClient.class)
具备配置则是将
@FeignClient("service-user")
变成@FeignClient(name = "service-user",configuration = FeignConfig.class)
配置实现
开启整体的默认日志环境,例如上诉
UserClient
类所在的的包1
2
3logging:
level:
com.jungle.servicenews.client: debug开启日志,还可以配置其他feign的属性
1
2
3
4
5
6
7
8
9feign:
client:
config:
#也可以针对某个包进行配置
default:
connectTimeout: 5000
readTimeout: 5000
#日志级别:NONE(默认),BASIC,HEADERS,FULL
loggerLevel: FULL
连接优化
因为feign默认的是来一次就建立一次连接,可以配置类似连接池的东东
配置如下:
1 | feign: |
Sentinel
在github上搜索spring-cloud-alibaba
- 然后点击上面Wiki,进入的是每个模块下面大致的内容
- 点击下面的read.me的内容,可以进入到具体的某个模块,再点击上面的wiki便可以进入这个模块的具体内容
在spring官网上的参考资料说明如下所示
简介
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。 Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
使用方式
前置,通过actuator暴露端点信息,先导入依赖
1
2
3
4<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>然后修改配置文件,加入如下
1
2
3
4
5
6management:
endpoints:
web:
exposure:
#下面是暴漏所有端点
include: "*"测试暴漏端点是否成功,打开网页访问:IP+端口+/actuator查看所有暴漏的端点信息,引入sentinel依赖后,可以直接通过
IP:端口/actuator/sentinel
访问查看sentinel的信息引入依赖,如果要在您的项目中引入 Sentinel,使用 group ID 为
com.alibaba.cloud
和 artifact ID 为spring-cloud-starter-alibaba-sentinel
的 starter。1
2
3
4<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>下载centinel控制台,下载地址:https://download.csdn.net/download/qq_35742333/12705492,下载完成直接可以运行`java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar`,运行完成直接ip+端口访问,默认java -jar 启动后的端口为8080,默认账户密码一致,都为sentinel
服务与控制台建立通信
1
2
3
4
5
6
7cloud:
sentinel:
transport:
#控制台与服务之间通信的端口,不写默认也会给个8719
port: 8719
#控制台位置
dashboard: localhost:8080测试:服务至少有一次访问,控制台的网页才会有显示
对接口的调用如果出现返回数据为XML格式,因为sentinel中集成了com.fastxml.jackson.dataformat的jackson-dataformat-xml.xml优先级比JSON高,所以先返回XML
解决方式一,去除sentinel依赖包的com.fastxml.jackson-dataformat
1
2
3
4
5
6
7
8
9
10<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<exclusions>
<exclusion>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</exclusion>
</exclusions>
</dependency>解决方式二,Controller层增加返回格式指定
@GetMapping(value = "/all",produces = MediaType.APPLICATION_JSON_VALUE)
流量控制
控制模式
- 直接
- 针对资源自己直接进行流量控制
- 关联
- 监控关联资源的流量情况来对自身资源进行限制
- 链路
- 对资源内部调用的资源进行入口来源的限制
控制效果
- 快速失败
- 达到阈值之后直接抛出异常
- Warm Up
- 当浏览在一瞬间很大的时候,不会让流量立即到达阈值,而是经过一段时间慢慢的预热增长
- 排队通过
- 请求达到阈值进行
熔断降级
- 平均响应时间 (
DEGRADE_GRADE_RT
):当 1s 内持续进入 N 个请求,对应时刻的平均响应时间(秒级)均超过阈值(count
,以 ms 为单位),那么在接下的时间窗口(DegradeRule
中的timeWindow
,以 s 为单位)之内,对这个方法的调用都会自动地熔断(抛出DegradeException
)。注意 Sentinel 默认统计的 RT 上限是 4900 ms,超出此阈值的都会算作 4900 ms,若需要变更此上限可以通过启动配置项-Dcsp.sentinel.statistic.max.rt=xxx
来配置。 - 异常比例 (
DEGRADE_GRADE_EXCEPTION_RATIO
):当资源的每秒请求量 >= N(可配置),并且每秒异常总数占通过量的比值超过阈值(DegradeRule
中的count
)之后,资源进入降级状态,即在接下的时间窗口(DegradeRule
中的timeWindow
,以 s 为单位)之内,对这个方法的调用都会自动地返回。异常比率的阈值范围是[0.0, 1.0]
,代表 0% - 100%。 - 异常数 (
DEGRADE_GRADE_EXCEPTION_COUNT
):当资源近 1 分钟的异常数目超过阈值之后会进行熔断。注意由于统计时间窗口是分钟级别的,若timeWindow
小于 60s,则结束熔断状态后仍可能再进入熔断状态。 - 注意:设置异常比例时,时间窗口的时间应该大于或者等于60S,因为假如设置30比例,时间窗口设置为10S,那么因为默认的比例计算时间是60S统计一次,熔断降级后10S内又调用,则还是会读取到30S比例(写的什么毛线!)
热点参数限流
在需要进行限流的方法上加上注解@SentinelResource("myMethod")
注意:若需要配置例外项或者使用集群维度流控,则传入的参数只支持基本类型。
如果还是测试不好
配置文件将filter干掉
1 | cloud: |
PS:感觉热点参数限流这里测试没成功。。。QAQ
ps:没测试成功是必然的,因为你的参数没加@RequestParam
扩展接口
下表显示当应用的 ApplicationContext
中存在对应的Bean的类型时,会进行自动化设置:
存在Bean的类型 | 操作 | 作用 |
---|---|---|
UrlCleaner |
WebCallbackManager.setUrlCleaner(urlCleaner) |
资源清理(资源(比如将满足 /foo/:id 的 URL 都归到 /foo/* 资源下)) |
UrlBlockHandler |
WebCallbackManager.setUrlBlockHandler(urlBlockHandler) |
自定义限流处理逻辑 |
RequestOriginParser |
WebCallbackManager.setRequestOriginParser(requestOriginParser) |
设置来源信息 |
Spring Cloud Alibaba Sentinel 提供了这些配置选项:
配置项 | 含义 | 默认值 |
---|---|---|
spring.application.name or project.name |
Sentinel项目名 | |
spring.cloud.sentinel.enabled |
Sentinel自动化配置是否生效 | true |
spring.cloud.sentinel.eager |
是否提前触发 Sentinel 初始化 | false |
spring.cloud.sentinel.transport.port |
应用与Sentinel控制台交互的端口,应用本地会起一个该端口占用的HttpServer | 8719 |
spring.cloud.sentinel.transport.dashboard |
Sentinel 控制台地址 | |
spring.cloud.sentinel.transport.heartbeat-interval-ms |
应用与Sentinel控制台的心跳间隔时间 | |
spring.cloud.sentinel.transport.client-ip |
此配置的客户端IP将被注册到 Sentinel Server 端 | |
spring.cloud.sentinel.filter.order |
Servlet Filter的加载顺序。Starter内部会构造这个filter | Integer.MIN_VALUE |
spring.cloud.sentinel.filter.url-patterns |
数据类型是数组。表示Servlet Filter的url pattern集合 | /* |
spring.cloud.sentinel.filter.enabled |
Enable to instance CommonFilter | true |
spring.cloud.sentinel.metric.charset |
metric文件字符集 | UTF-8 |
spring.cloud.sentinel.metric.file-single-size |
Sentinel metric 单个文件的大小 | |
spring.cloud.sentinel.metric.file-total-count |
Sentinel metric 总文件数量 | |
spring.cloud.sentinel.log.dir |
Sentinel 日志文件所在的目录 | |
spring.cloud.sentinel.log.switch-pid |
Sentinel 日志文件名是否需要带上 pid | false |
spring.cloud.sentinel.servlet.block-page |
自定义的跳转 URL,当请求被限流时会自动跳转至设定好的 URL | |
spring.cloud.sentinel.flow.cold-factor |
WarmUp 模式中的 冷启动因子 | 3 |
spring.cloud.sentinel.zuul.order.pre |
SentinelZuulPreFilter 的 order | 10000 |
spring.cloud.sentinel.zuul.order.post |
SentinelZuulPostFilter 的 order | 1000 |
spring.cloud.sentinel.zuul.order.error |
SentinelZuulErrorFilter 的 order | -1 |
spring.cloud.sentinel.scg.fallback.mode |
Spring Cloud Gateway 流控处理逻辑 (选择 redirect or response ) |
|
spring.cloud.sentinel.scg.fallback.redirect |
Spring Cloud Gateway 响应模式为 ‘redirect’ 模式对应的重定向 URL | |
spring.cloud.sentinel.scg.fallback.response-body |
Spring Cloud Gateway 响应模式为 ‘response’ 模式对应的响应内容 | |
spring.cloud.sentinel.scg.fallback.response-status |
Spring Cloud Gateway 响应模式为 ‘response’ 模式对应的响应码 | 429 |
spring.cloud.sentinel.scg.fallback.content-type |
Spring Cloud Gateway 响应模式为 ‘response’ 模式对应的 content-type | application/json |
Note | 请注意。这些配置只有在 Servlet 环境下才会生效,RestTemplate 和 Feign 针对这些配置都无法生效 | ||||
---|---|---|---|---|---|
实例:
修改访问URL
1 | //可以修改访问的URL |
自定义流控返回(加了@SentinelResource注解后,这个就不会生效)
1 |
|
设置来源信息
和上一样,只改变实现接口RequestOriginParser,可以通过request.getParameter得到请求携带的参数
注解支持
@SentinelResource 注解
注意:注解方式埋点不支持 private 方法。
@SentinelResource
用于定义资源,并提供可选的异常处理和 fallback 配置项。 @SentinelResource
注解包含以下属性:
value
:资源名称,必需项(不能为空)entryType
:entry 类型,可选项(默认为EntryType.OUT
)blockHandler
/blockHandlerClass
:blockHandler
对应处理BlockException
的函数名称,可选项。blockHandler 函数访问范围需要是public
,返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为BlockException
。blockHandler 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定blockHandlerClass
为对应的类的Class
对象,注意对应的函数必需为 static 函数,否则无法解析。- fallback / fallbackClass:fallback 函数名称,可选项,用于在抛出异常的时候提供 fallback 处理逻辑。fallback 函数可以针对所有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。fallback 函数签名和位置要求:
- 返回值类型必须与原函数返回值类型一致;
- 方法参数列表需要和原函数一致,或者可以额外多一个
Throwable
类型的参数用于接收对应的异常。 - fallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定
fallbackClass
为对应的类的Class
对象,注意对应的函数必需为 static 函数,否则无法解析。
- defaultFallback(since 1.6.0):默认的 fallback 函数名称,可选项,通常用于通用的 fallback 逻辑(即可以用于很多服务或方法)。默认 fallback 函数可以针对所有类型的异常(除了 exceptionsToIgnore里面排除掉的异常类型)进行处理。若同时配置了 fallback 和 defaultFallback,则只有 fallback 会生效。defaultFallback 函数签名要求:
- 返回值类型必须与原函数返回值类型一致;
- 方法参数列表需要为空,或者可以额外多一个
Throwable
类型的参数用于接收对应的异常。 - defaultFallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定
fallbackClass
为对应的类的Class
对象,注意对应的函数必需为 static 函数,否则无法解析。
exceptionsToIgnore
(since 1.6.0):用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出。
注:1.6.0 之前的版本 fallback 函数只针对降级异常(
DegradeException
)进行处理,不能针对业务异常进行处理。
特别地,若 blockHandler 和 fallback 都进行了配置,则被限流降级而抛出 BlockException
时只会进入 blockHandler
处理逻辑。若未配置 blockHandler
、fallback
和 defaultFallback
,则被限流降级时会将 BlockException
直接抛出(若方法本身未定义 throws BlockException 则会被 JVM 包装一层 UndeclaredThrowableException
)。
示例:
1 | public class TestService { |
从 1.4.0 版本开始,注解方式定义资源支持自动统计业务异常,无需手动调用 Tracer.trace(ex)
来记录业务异常。Sentinel 1.4.0 以前的版本需要自行调用 Tracer.trace(ex)
来记录业务异常。
配置
Spring Cloud Alibaba
若您是通过 Spring Cloud Alibaba 接入的 Sentinel,则无需额外进行配置即可使用 @SentinelResource
注解。
Spring AOP
若您的应用使用了 Spring AOP(无论是 Spring Boot 还是传统 Spring 应用),您需要通过配置的方式将 SentinelResourceAspect
注册为一个 Spring Bean:
1 | @Configuration |
我们提供了 Spring AOP 的示例,可以参见 sentinel-demo-annotation-spring-aop。
AspectJ
若您的应用直接使用了 AspectJ,那么您需要在 aop.xml
文件中引入对应的 Aspect:
1 | <aspects> |
Feign的支持
Sentinel 适配了 Feign 组件。如果想使用,除了引入 spring-cloud-starter-alibaba-sentinel
的依赖外还需要 2 个步骤:
- 配置文件打开 Sentinel 对 Feign 的支持:
feign.sentinel.enabled=true
- 加入
spring-cloud-starter-openfeign
依赖使 Sentinel starter 中的自动化配置类生效:
1 | <dependency> |
这是一个 FeignClient
的简单使用示例:
1 | @FeignClient(name = "service-provider", fallback = EchoServiceFallback.class, configuration = FeignConfiguration.class) |
RestTemplate的支持
Spring Cloud Alibaba Sentinel 支持对 RestTemplate
的服务调用使用 Sentinel 进行保护,在构造 RestTemplate
bean的时候需要加上 @SentinelRestTemplate
注解。
1 | @Bean |
@SentinelRestTemplate
注解的属性支持限流(blockHandler
, blockHandlerClass
)和降级(fallback
, fallbackClass
)的处理。
其中 blockHandler
或 fallback
属性对应的方法必须是对应 blockHandlerClass
或 fallbackClass
属性中的静态方法。
该方法的参数跟返回值跟 org.springframework.http.client.ClientHttpRequestInterceptor#interceptor
方法一致,其中参数多出了一个 BlockException
参数用于获取 Sentinel 捕获的异常。
比如上述 @SentinelRestTemplate
注解中 ExceptionUtil
的 handleException
属性对应的方法声明如下:
1 | public class ExceptionUtil { |
Note | 应用启动的时候会检查 @SentinelRestTemplate 注解对应的限流或降级方法是否存在,如不存在会抛出异常 |
---|---|
@SentinelRestTemplate
注解的限流(blockHandler
, blockHandlerClass
)和降级(fallback
, fallbackClass
)属性不强制填写。
当使用 RestTemplate
调用被 Sentinel 熔断后,会返回 RestTemplate request block by sentinel
信息,或者也可以编写对应的方法自行处理返回信息。这里提供了 SentinelClientHttpResponse
用于构造返回信息。
Sentinel RestTemplate 限流的资源规则提供两种粒度:
httpmethod:schema://host:port/path
:协议、主机、端口和路径httpmethod:schema://host:port
:协议、主机和端口
Note | 以 https://www.taobao.com/test 这个 url 并使用 GET 方法为例。对应的资源名有两种粒度,分别是 GET:https://www.taobao.com 以及 GET:https://www.taobao.com/test |
---|---|
Spring Cloud Gateway
概念
To include Spring Cloud Gateway in your project, use the starter with a group ID of org.springframework.cloud
and an artifact ID of spring-cloud-starter-gateway
. See the Spring Cloud Project page for details on setting up your build system with the current Spring Cloud Release Train.
If you include the starter, but you do not want the gateway to be enabled, set spring.cloud.gateway.enabled=false
.
Spring Cloud Gateway is built on Spring Boot 2.x, Spring WebFlux, and Project Reactor. As a consequence, many of the familiar synchronous libraries (Spring Data and Spring Security, for example) and patterns you know may not apply when you use Spring Cloud Gateway. If you are unfamiliar with these projects, we suggest you begin by reading their documentation to familiarize yourself with some of the new concepts before working with Spring Cloud Gateway. | |
---|---|
Spring Cloud Gateway requires the Netty runtime provided by Spring Boot and Spring Webflux. It does not work in a traditional Servlet Container or when built as a WAR. | |
---|---|
大致就是如何使用,以及注意gateeay不是传统的springweb项目,所以不要导入乱七八糟的web包
使用方式
首先导包
因为网关也需要注册到Nacos里面去,所以需要导入Nacos的包
1
2
3
4<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>Nacos为啥不要写版本号,因为交给dependencyManagement标签来管理了,所以需要导入
1
2
3
4
5
6
7
8
9
10
11<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.1.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>导入网关自身的包spring.cloud.gateway,然后依赖于springboot以及自身受dependencyManagement里面的rg.springframework.cloud来管理自身的版本,所以这里直接给出完整的pom配置,Jiang慢慢应该懂得
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.jungle.gateway</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR6</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.1.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>将网关注册到Nacos Server里面,以及开启Gateway,配置如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
spring:
cloud:
gateway:
#开启网关的自动路由方式,也就是通过url的服务名称去找到对应的服务
discovery:
locator:
enabled: true
nacos:
discovery:
# #命令空间ID
namespace: d2f4bb90-17ac-4bc1-9e32-8734e722eb0c
# #集群
cluster-name: newMachine
# group: 1
# metadata:
# testKey1: testValue1
# testKey2: testValue2
server-addr: 127.0.0.1:8848
server:
port: 9001启动报错
org.springframework.cloud.gateway.config.GatewayAutoConfiguration$NettyConfiguration.gatewayHttpClient(GatewayAutoConfiguration.java:622)
,反正是依赖有问题,直接ctrl+左键点击gateway的依赖,查看这个吊毛到底要毛线依赖,发现依赖的是2.3.0的springboot版本,改了下,ok,然后通过IP:9001/服务名/news/testTemplate
测试成功
自定义配置
最有用的是谓词工厂,可以通过这个来指定什么请求什么时间段访问什么服务
1 | server: |
Filter的使用
局部配置可以在配置文件中使用,也就是在routes
下面增加
1 | filters: |
全局的写出来丢到容器中,或者在配置文件中gateway的下面增加default-filter
也可以
自定义Filter
!!!注意:拦截器弄好了千万记得加
@Component
注解放到容器中,这是我弄完Filter后得到的经验教训
可以模仿SpringCloudGateway的实现自己来写一个,例如在idea中使用ctrl+N搜索类
AddRequestHeaderGatewayFilterFactory
实现过程如下
可以看出
AddRequestHeaderGatewayFilterFactory
类继承了 extends AbstractAuthenticationGatewayFilterFactory抽象类,并且重写了一个apply方法.那么可以自建一个类,保持名字的一致性,就是自定义名+GatewayFilterFactory
这种格式,这种格式可以直接在配置中给每个服务局部配置1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51package com.jungle.gateway.demo.filter;
import org.apache.commons.lang.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AddRequestHeaderGatewayFilterFactory;
import org.springframework.cloud.gateway.support.GatewayToStringStyler;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* @author Jiangmanman
*/
public class AuthenticationGatewayFilterFactory extends AbstractAuthenticationGatewayFilterFactory{
public GatewayFilter apply(Config config) {
return new GatewayFilter() {
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
System.out.println("/******************进入认证过滤器**********************/");
System.out.println("/******************取出参数"+config+"**********************/");
String token = request.getHeaders().getFirst("token");
System.out.println(request.getHeaders());
if(StringUtils.isBlank(token)){
throw new RuntimeException("没有Token");
}
return chain.filter(exchange);
}
public String toString() {
return GatewayToStringStyler.filterToStringCreator(AuthenticationGatewayFilterFactory.this).append(config.getName()).toString();
}
};
}
}抽象类
AbstractAuthenticationGatewayFilterFactory
继承了AbstractGatewayFilterFactory这个接口,并且指定了由哪个类去读取参数,shortcutFieldOrder方法主要用来控制参数的排序1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57package com.jungle.gateway.demo.filter;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.AbstractNameValueGatewayFilterFactory;
import org.springframework.core.style.ToStringCreator;
import org.springframework.validation.annotation.Validated;
import javax.validation.constraints.NotEmpty;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* @author Jiangmanman
*/
public abstract class AbstractAuthenticationGatewayFilterFactory extends AbstractGatewayFilterFactory<AbstractAuthenticationGatewayFilterFactory.Config> {
public AbstractAuthenticationGatewayFilterFactory() {
super(AbstractAuthenticationGatewayFilterFactory.Config.class);
}
public List<String> shortcutFieldOrder() {
return Collections.singletonList("name");
}
public static class Config {
protected String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String toString() {
return "NameValueConfig{" +
"name='" + name + '\'' +
'}';
}
public String getValue() {
return this.name;
}
}
}
最后在在配置上写上服务对应的过滤器名即可,如下所示,增加的就是最后一行
1 | spring: |
上面的自定义Filter主要用来做服务的局部配置,对于服务的全局配置(拦截所有服务)代码实现如下
1 |
|
过滤器顺序
在spring官方网站7.1里就有实现方法,就是通过实现Ordered
接口再重写getOrder
方法,给一个值,越小的值,优先级越高,越大的值优先级越低
hen a request matches a route, the filtering web handler adds all instances of GlobalFilter
and all route-specific instances of GatewayFilter
to a filter chain. This combined filter chain is sorted by the org.springframework.core.Ordered
interface, which you can set by implementing the getOrder()
method.
As Spring Cloud Gateway distinguishes between “pre” and “post” phases for filter logic execution (see How it Works), the filter with the highest precedence is the first in the “pre”-phase and the last in the “post”-phase.
The following listing configures a filter chain:
Example 59. ExampleConfiguration.java
1 |
|
为了测试这个改变Filter拦截的顺序,我又把Filter提取出去了,不然怎么实现Ordered接口呢…,如下所示
1 | public class MyServiceFilter implements GatewayFilter, Ordered { |
1 |
|
filter在请求从服务返回后进行处理
在上面MyServiceFilter
类里的filter
方法
1 | return chain.filter(exchange).then(Mono.fromRunnable(()->{ |
Http超时配置
Http timeouts (response and connect) can be configured for all routes and overridden for each specific route.
Global timeouts
To configure Global http timeouts:
connect-timeout
must be specified in milliseconds.
response-timeout
must be specified as a java.time.Duration
global http timeouts example
1 | spring: |
Per-route timeouts
To configure per-route timeouts:
connect-timeout must be specified in milliseconds.
response-timeout must be specified in milliseconds.
per-route http timeouts configuration via configuration
1 | - id: per_route_timeouts |
跨域问题
在实际项目中,一般通过Nginx做反向代理,不做概述
You can configure the gateway to control CORS behavior. The “global” CORS configuration is a map of URL patterns to Spring Framework CorsConfiguration
. The following example configures CORS:
Example 73. application.yml
1 | spring: |
In the preceding example, CORS requests are allowed from requests that originate from docs.spring.io
for all GET requested paths.
To provide the same CORS configuration to requests that are not handled by some gateway route predicate, set the spring.cloud.gateway.globalcors.add-to-simple-url-handler-mapping
property to true
. This is useful when you try to support CORS preflight requests and your route predicate does not evalute to true
because the HTTP method is options
.
我是这样配置的
1 | spring: |
异常处理
因为网关不是传统的Web项目,所以不应该引入web的包
代码如下
1 | package com.jungle.gateway.demo.handler; |
服务间调用传参问题
Feign方式解决
1 | /** |
上面的可以加 @Component注解,直接放入容器中,也可以在配置文件中指定,就不要加注解了,如下所示
1 | feign: |
RestTemplate方式解决
定义
Intercepter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public class SecuringRequestInterceptor implements RequestInterceptor {
public void apply(RequestTemplate requestTemplate) {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder
.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
Enumeration<String> headerNames = request.getHeaderNames();
if (headerNames != null) {
while (headerNames.hasMoreElements()) {
String name = headerNames.nextElement();
String values = request.getHeader(name);
requestTemplate.header(name, values);
}
}
}
}在resttemplate的配置上加上
interceptor
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class BaseConfig {
public RestTemplate getRestTemplate(){
RestTemplate restTemplate = new RestTemplate();
//这里就是将interceptor放入resttemplate中
restTemplate.setInterceptors(Arrays.asList(new TokenRestTemplateInterceptor()));
return restTemplate;
}
}注意要使用resttemplate方式去访问接口,如下所示
1
CommonResult forObject = restTemplate.getForObject("http://(服务名||IP+端口)/user/all", CommonResult.class);
Zipkin
数据一般存在ElasticSearch
返回优化
在全局异常处理处返回ResponseEntity<主题类名,例如我喜欢用CommonResult>
new ResponseEntiry<ResultError(返回内容,HttpsStatus.NOT_FUND)>
项目文件
项目文件:链接: https://pan.baidu.com/s/1N4eMWDfjzQZwoWwusYwoZA 提取码: at59
将配置文件中的数据库连接去掉即可运行,加上Nacos Server可正常注册,加上Sentinel可正常进行服务流控和降级
参考链接
参考链接: