上篇文章:
目录
[1 授权](#1 授权)
[1.1 服务端(被调用方)获取来源](#1.1 服务端(被调用方)获取来源)
[1.2 客户端(调用方)设置来源](#1.2 客户端(调用方)设置来源)
[1.3 配置授权规则](#1.3 配置授权规则)
[2 自定义异常返回结果](#2 自定义异常返回结果)
[3 Sentinel规则管理及推送](#3 Sentinel规则管理及推送)
[3.1 原始模式](#3.1 原始模式)
[3.2 Pull模式](#3.2 Pull模式)
[3.3 Push模式](#3.3 Push模式)
[3.3.1 基于Nacos实现Push模式](#3.3.1 基于Nacos实现Push模式)
[3.3.2 Sentinel集成Nacos持久化](#3.3.2 Sentinel集成Nacos持久化)
1 授权
目前的微服务架构中,许多服务是可以通过ip+端口号直接访问的,这样就会导致系统的不安全,比如订单服务可能包含用户的个人隐私,如果可以随便访问订单服务就会导致数据泄露。
因此可以通过Sentinel添加授权规则,调用方设置请求来源,被调用方获取请求来源,然后通过Sentinel判断授权类型:黑名单和白名单,根据授权规则决定是否允许请求通过。

****白名单:****请求来源位于白名单内的调用者才允许调用被调用者的接口。
****黑名单:****请求来源位于黑名单内的调用者不允许调用被调用者的接口,其它允许。
现有Gateway调用order-service这样的调用链,Gateway网关相当于调用方,order-service相当于被调用方,实现授权就需要网关来设置请求头的来源,order-service获取请求头来源来判断是否在黑白名单内:
1.1 服务端(被调用方)获取来源
Sentinel通过RequestOriginParser的parseOrigin()方法获取请求头,并获取请求的来源,因此调用方可以获取约定好的请求头来判断来源:
java
@Component
public class HeaderOriginParser implements RequestOriginParser {
@Override
public String parseOrigin(HttpServletRequest httpServletRequest) {
// 1.获取请求头
String origin = httpServletRequest.getHeader("origin");
// 2.非空判断
if (!StringUtils.hasLength(origin)) {
origin = "default";
}
return origin;
}
}
1.2 客户端(调用方)设置来源
调用方是网关,设置来源可以通过过滤器,在请求头添加key:value=origin:gateway:
bash
spring:
cloud:
gateway:
default-filters:
- AddRequestHeader=origin,gateway
1.3 配置授权规则
资源名是指要进行权限判断的接口,即服务端(被调用方)。流控应用可以填写多个(用英文,分割),指客户端(调用方)。授权类型就是把调用方添加到白名单或者黑名单:

如果直接访问8080端口的order-service服务,就会失败:

如果通过gateway访问,由于gateway是白名单,因此权限判断通过,可以成功访问:

2 自定义异常返回结果
上面发现,无论是限流还是授权未通过,返回结果都是Blocked by Sentinel。这是由于Sentinel默认实现了BlockExceptionHandle接口:
java
public class DefaultBlockExceptionHandler implements BlockExceptionHandler {
public DefaultBlockExceptionHandler() {
}
public void handle(HttpServletRequest request, HttpServletResponse
response, BlockException e) throws Exception {
response.setStatus(429);
PrintWriter out = response.getWriter();
out.print("Blocked by Sentinel (flow limiting)");
out.flush();
out.close();
}
}
BlockException有5种实现子类,分别对应授权异常、服务降级异常、限流异常、热点参数限流异常、系统阻塞异常:

DefaultBlockExceptionHandler接口将所有类型的异常都定义返回消息Blocked by Sentinel (flow limiting),这不利于我们观察到异常的发生原因,因此需要自己实现BlockExceptionHandle接口来自定义异常返回结果:
java
@Component
public class SentinelExceptionHandler implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
response.setContentType("text/html; charset=utf-8");
String msg = "Blocked by Sentinel (flow limiting)";
int status = 429;
PrintWriter out = response.getWriter();
if (e instanceof FlowException) { //限流异常
msg = "触发限流, 请联系服务方进行调整";
} else if (e instanceof DegradeException) { //降级异常
msg = "触发降级异常";
} else if (e instanceof AuthorityException) { //授权异常
status = 401;
msg = "没有权限访问, 请联系服务方进行调整";
}
response.setStatus(status);
out.print(msg);
out.flush();
out.close();
}
}
分别设置流控规则、授权规则、熔断规则,观察异常返回结果:



3 Sentinel 规则管理及推送
Sentinel想要在生产环境中使用,还缺少一些重要的特性。目前Sentinel仅提供规则存储在内存中,一旦服务重启就得重新配置规则,因此需要对规则进行持久化,这就是规则管理及推送。
共分为3种模式,Sentinel默认采用原始模式:

3.1 原始模式
如果不做任何修改,Dashboard的推送规则方式是通过API将规则推送至客户端并直接更新到内存中,这里的客户端即微服务系统:

3.2 Pull模式
Pull模式是将规则持久化到一种数据源上(如本地文件、RDBMS、配置中心(Consul、Eureka)等),该数据源要求是可读/写的,然后客户端定期轮询从数据源拉取规则。使用时需要在客户端(微服务)注册数据源:将对应的读数据源注册至对应的RuleManager,将写数据源注册至transport的WritableDataSourceRegistry中。以本地文件数据源为例:

首先需要注册数据源:
java
public class FileDataSourceInit implements InitFunc {
@Override
public void init() throws Exception {
String ruleDir = "D:/javaee_study/springcloud_sentinel/sentinel/rules/orderService";
String flowRulePath = ruleDir + "/flow-rule.json";
mkdirIfNotExist(ruleDir);
createFileIfNotExist(flowRulePath);
// 注册一个可读数据源,用来定时读取本地的json文件,更新到规则内存中
// 流控规则
ReadableDataSource<String, List<FlowRule>> ds = new
FileRefreshableDataSource<>(
flowRulePath, source -> JSON.parseObject(
source, new TypeReference<List<FlowRule>>() {
})
);
// 将可读数据源注册至FlowRuleManager
// 这样当规则文件发生变化时,就会更新规则到内存
FlowRuleManager.register2Property(ds.getProperty());
WritableDataSource<List<FlowRule>> flowRuleWDS = new FileWritableDataSource<>(
flowRulePath,
this::encodeJson
);
// 将可写数据源注册至transport模块的WritableDataSourceRegistry中
// 这样收到控制台推送的规则时,Sentinel会先更新到内存,然后将规则写入到文件中
WritableDataSourceRegistry.registerFlowDataSource(flowRuleWDS);
}
private <T> String encodeJson(T t) {
return JSON.toJSONString(t);
}
//创建路径
private void mkdirIfNotExist(String filePath) throws IOException {
File file = new File(filePath);
if (!file.exists()) {
file.mkdirs();
}
}
//创建文件:本地文件作为数据源
private void createFileIfNotExist(String filePath) throws IOException {
File file = new File(filePath);
if (!file.exists()) {
file.createNewFile();
}
}
}
然后在项目中的resources目录下创建META-INF/services目录,并在目录下创建文件com.alibaba.csp.sentinel.init.InitFunc,文件内容是注册数据源的类,如下:


配置完成后重启服务,设置流控规则,流控规则会被持久化到配置的本地数据源:

文件内容如下:
{"clusterConfig":{"acquireRefuseStrategy":0,"clientOfflineTime":2000,"fallbackToLocalWhenFail":true,"resourceTimeout":2000,"resourceTimeoutStrategy":0,"sampleCount":10,"strategy":0,"thresholdType":0,"windowIntervalMs":1000},"clusterMode":false,"controlBehavior":0,"count":5.0,"grade":1,"limitApp":"default","maxQueueingTimeMs":500,"resource":"/order/{orderId}","strategy":0,"warmUpPeriodSec":10}
重启服务,流控规则不会消失说明成功持久化到本地。
3.3 Push模式
Pull模式是Sentinel客户端(微服务)从数据源拉取规则;而Push模式是数据源推送规则给Sentinel客户端(客户端监听数据源)。此时这里的数据源不再是本地文件等方式,因为本地文件并不能推送给服务。

注意:规则管理及推送的Pull模式或Push模式实现本质还是看数据源支持的是推模式还是拉模式,如果数据源支持推模式,比如Nacos配置中心、ZooKeeper,那么基于这些配置中心实现的就是Push模式。如果数据源支持拉模式,比如Eureka或本地文件(只能是服务拉取本地文件,本地文件系统压根无权访问服务端),那实现的就是Pull模式。
而Push模式的数据源通常是基于远程配置中心的,比如Nacos、ZooKeeper等等。配置中心的数据源将变化的规则及时推送给客户端,即推送规则的路径变为Sentinel Dashboard/Config Center Dashboard => Config Center => Sentinel数据源 => Sentinel客户端:

3.3.1 基于Nacos实现Push模式
首先需要引入Sentinel关于Nacos的依赖:
XML
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
然后把Nacos作为数据源:
java
public class NacosDataSourceInit implements InitFunc {
// nacos server ip
private static final String remoteAddress = "192.168.141.150:8848";
// nacos group
private static final String groupId = "SENTINEL_GROUP";
// nacos dataId
private static final String dataId = "orderservice-flow-rules";
@Override
public void init() throws Exception {
ReadableDataSource<String, List<FlowRule>> flowRuleDataSource = new
NacosDataSource<>(remoteAddress, groupId, dataId,
source -> JSON.parseObject(source, new
TypeReference<List<FlowRule>>() {
}));
FlowRuleManager.register2Property(flowRuleDataSource.getProperty());
}
}
接着类似Pull模式,也需要配置数据源的类的读取路径:


最后,在Nacos Dashboard也就是Nacos的控制台页面配置规则,比如流控规则(JSON格式可以从Pull模式的本地数据源文件中复制):

重启服务,观察Sentinel Dashboard:

这说明基于Nacos的数据源配置成功,Nacos配置中心可以把规则推送给Sentinel客户端(微服务)和Sentinel Dashboard。
但是反之,如果在Sentinel Dashboard修改流控规则,Nacos配置中心的规则无法同步,这需要开启Sentinel集成Nacos持久化,从而让Sentinel Dashboard和Nacos的通信是双向的。
3.3.2 Sentinel集成Nacos持久化
Sentinel想要集成Nacos实现持久化(Sentinel Dashboard和Nacos双向通信)需要修改Sentinel的源码。具体流程如下:
首先先从官网https://github.com/alibaba/Sentinel/releases下载Sentinel的源码:

然后用IDEA打开源码项目,修改sentinel-dashboard的pom文件,将sentinel-datasource-nacos依赖的<scope>test</scope>删除或注释:

接着将sentinel-dashboard/src/test/java/com/alibaba/csp/sentinel/dashboard/rule/nacos目录完整的复制到sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/rule目录下面:

在配置文件application.properties中添加自定义配置项sentinel.nacos.addr=192.168.141.150:8848,然后修改nacos包下面的NacosConfig类读取该配置项:

修改com.alibaba.csp.sentinel.dashboard.controller.v2.FlowControllerV2类中注入的对象为nacos包下面的对象:


修改前端页面src/main/webapp/resources/app/scripts/directives/sidebar/sidebar.html,这段代码原先是注释状态,取消注释即可:

修改前端js部分src/main/webapp/resources/app/scripts/controllers/identity.js,把第四行FlowControllerV1修改为FlowControllerV2:

还是该js文件,98行左右,修改/dashboard/flow/为/dashboard/v2/flow/(该js逻辑就是将Sentinel Dashboard的流控规则同步给Nacos):

修改完成后,重新编译打包部署Sentinel Dashboard,需要跳过单元测试:


上图包名是防止重名修改后的包。关闭原Sentinel Dashboard,启动新的Dashboard。
Sentinel客户端(order-service服务)也需要修改流控规则的数据源的配置项:
bash
spring:
application:
name: order-service
cloud:
nacos:
discovery:
server-addr: 192.168.141.150:8848
sentinel:
transport:
dashboard: 127.0.0.1:8100 #sentinel控制台地址
client-ip: 127.0.0.1 #连接本地Sentinel可以不配置,连接服务器Sentinel需要配置client-ip
web-context-unify: false #关闭context整合
datasource:
flow-rules: #流控规则
nacos:
server-addr: ${spring.cloud.nacos.discovery.server-addr}
dataId: ${spring.application.name}-flow-rules
groupId: SENTINEL_GROUP
rule-type: flow
然后重启服务,观察效果,记得注释resources目录下com.alibaba.csp.sentinel.init.InitFunc文件中的内容(这是Push模式基于Nacos的实现,但是只能Nacos同步Sentinel Dashboard,不能Sentinel Dashboard同步Nacos):

Sentinel Dashboard页面,多了选项流控规则V1,该选项修改的流控规则就会同步给Nacos,选项流控规则还是原来的选项,只能接受Nacos往Sentinel Dashboard单向同步。


可以发现,规则正确同步过去了。
下篇文章: