跳至主要內容

Sentinel

wangdx大约 24 分钟

Sentinel 简介

流量防

  • 服务调用中面对大规模的并发请求时,如果没有进行合理的请求规划,那么就会出现服务资源调用量过大而导致服务响应慢而出现假死状态,甚至会出现服务宕机而导致的雪崩效应。所以在一个合理的微服务架构设计之中,就必须进行有效的流量防护控制,利用该组件可以在高并发访问量出现的情况下实现有效的流控管理,并且基于一定的规则对大规模请求进行调整,从而实现对资源的保护

Sentinel 组件功能

  • Sentinel 是由阿里巴巴提供的一款面向分布式服务架构的高可用流量防护开源组件,也。主要以流量为切入点实现限流、流量整是 SpringCloudAlibaba 之中的重要组成单元。形、熔断降级、系统负载保护、热点防护等功能,从而帮助开发者保障微服务运行的稳

Sentine

  • Sentinel 组件诞生于 2012 年并目于 2018 年实现开源,最初的功能主要实现了入口流量控制,而后在近十年的时间里承担了阿里双十一大促流量的应用场景,使突发流量(例如:秒杀)可以控制在系统承受的范围之内。Sentinel 组件支持广泛,可以方便的与 aernocos ouobe ghrc 进行数全,为一便一眼名管理,在 s 为了便于服务管理,在 Sentinel 组件内部提

Sentinel 与 Hystrix

  • Sentinel 与 Hystrix 在实现原则上是相同的,当检测到调用链路中某个资源出现不稳定的表现(例如:请求响应时间长或异常比例升高的时候),则对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联故障,但是 Hystrix 是通过线程池隔离的方式实现的,虽然可以有效的实现资源之间的彻底隔离,但是也会增加线程切换的成本,同时过多的线程池也会导致线程数量增多,而 Sentinel 并没有采用这样的线程池隔离策略,而是针对于微服务访问中可能存在的问题采取了不同的实现手段:
    • 通过并发线程数进行限制:Sentinel 组件中通过限制资源并发线程的数量来减少不稳定资源对其它资源的影响,当某一个资源造成线程数量堆积过多时,会由 Sentinel 自动拒绝新的请求,当堆积线程处理完成后才开始继续接收新的请求;
    • 服务慢调用和异常出现时对资源进行降级:Sentinel 可以根据响应时间和异常等不稳定的因素来实现当资源调用出现响应时间过长时,会自动拒绝对该资源后续的访问请求,直到对资源调用的熔断,过了指定的时间窗口之后才会重新尝试进行资源调用的恢复。

Sentinel 控制台

1、
https://github.com/alibaba/Sentinel

2、
192.168.190.160	sentinel-server

3、
vi /etc/sysconfig/network-scripts/ifcfg-ens33
vi /etc/hostname
vi /etc/hosts

4、
wget https://github.com/alibaba/Sentinel/releases/download/1.8.1/sentinel-dashboard-1.8.1.jar

5、
https://github.com/alibaba/Sentinel/wiki/控制台

6、
java -Dserver.port=8888 -Dcsp.sentinel.dashboard.server=localhost:8888 -Dproject.name=sentinel-dashboard -Dsentinel.dashboard.auth.username=muyan -Dsentinel.dashboard.auth.password=yootk -jar /usr/local/src/sentinel-dashboard-1.8.1.jar


7、
java -Dserver.port=8888 -Dcsp.sentinel.dashboard.server=localhost:8888 -Dproject.name=sentinel-dashboard -Dsentinel.dashboard.auth.username=muyan -Dsentinel.dashboard.auth.password=yootk -jar /usr/local/src/sentinel-dashboard-1.8.1.jar > /usr/local/src/sentinel.log 2>&1 &

8、
netstat -nptl

9、
firewall-cmd --zone=public --add-port=8888/tcp --permanent
firewall-cmd --zone=public --add-port=8719/tcp --permanent
firewall-cmd --reload

Sentinel 资源监控

Sentinel 限流保护

  • 在 SpringCloudAlibaba 套件中直接支持 Sentinel 服务的接入配置,开发者只需要导入所需要的依赖库,而后通过 application.yml 配置文件定义 Sentinel 相关服务地址,就可以自动扫描项目中的所有 REST 接口资源,并在微服务启动后实现自动的服务注册,下面通过具体的操作步骤实现服务资源与 Sentinel 组件的整合。
1、
// https://mvnrepository.com/artifact/com.alibaba.cloud/spring-cloud-starter-alibaba-sentinel
implementation group: 'com.alibaba.cloud', name: 'spring-cloud-starter-alibaba-sentinel', version: '2021.1'


2、
project(":provider-dept-8001") {    // 部门微服务
    dependencies {
        implementation(project(":common-api")) // 导入公共的子模块
        implementation(libraries.'mybatis-plus-boot-starter')
        implementation(libraries.'mysql-connector-java')
        implementation(libraries.'druid')
        implementation(libraries.'springfox-boot-starter')
        implementation('org.springframework.boot:spring-boot-starter-security')
        implementation('com.alibaba.cloud:spring-cloud-starter-alibaba-sentinel')
        // 以下的依赖库为Nacos注册中心所需要的依赖配置
        implementation('com.alibaba.cloud:spring-cloud-starter-alibaba-nacos-discovery') {
            exclude group: 'com.alibaba.nacos', module: 'nacos-client' // 移除旧版本的Nacos依赖
        }
        implementation('com.alibaba.cloud:spring-cloud-starter-alibaba-nacos-config') {
            exclude group: 'com.alibaba.nacos', module: 'nacos-client' // 移除旧版本的Nacos依赖
        }
        implementation(libraries.'nacos-client') // 引入与当前的Nacos匹配的依赖库
    }
}

3、
spring:
  application: # 配置应用信息
    name: dept.provider # 是微服务的名称
  cloud: # Cloud配置
    sentinel: # 监控配置
      transport: # 传输配置
        port: 8719 # Sentinel组件启用之后默认会启动一个8719端口
        dashboard: sentinel-server:8888 # 控制台地址

实时监控数据

Sentinel 实时监控数据

  • Sentinel 可以实现所有服务资源的性能监控,同时为了便于这些监控服务数据的获取又提供了一系列的 REST 接口,利用这些接口可以方便的实现其它应用平台的对接,下面将为读者列出几个 REST 接口进行说明。
1、
sentinel-server:8719/version

2、
sentinel-server:8719/clusterNode

3、
sentinel-server:8719/cnode?id=get

4、
sentinel-server:8719/cnode?id=e

5、
sentinel-server:8719/tree

6、
1630201669734	1630202669734

sentinel-server:8719/metric?id=dept&maxLines=10&startTime=1630201669734&endTime=1630202669734

Sentinel 限流保护

Sentinel 限流规则

  • 微服务接入到 Sentinel 组件之后,所有被调用过的方法都可以通过 Sentinel 控制台提供在该界面中除了可以显示出资源的访问数据之外,也提供有的“簇点链路”进行查看各种流量控制操作:流控,降级、热点、授权,开发者可以直接通过此界面并根据自身的需要设置资源保护规则,而一旦触发了此规则之后,就会自动的进行服务降级,并返回默认的失败数据。

自定义限流错误页

1、
package com.yootk.provider.action;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/errors/*") // 父路径
public class BlockAction {
    @RequestMapping("block_handler")
    public Object globalBlockHandler() {
        Map<String, Object> result = new HashMap<>(); // 保存错误信息
        result.put("status", HttpServletResponse.SC_BAD_REQUEST); // 设置状态码
        result.put("message", "Blocked by Sentinel (flow limiting)");
        return result;
    }
}


2、
spring:
  application: # 配置应用信息
    name: dept.provider # 是微服务的名称
  cloud: # Cloud配置
    sentinel: # 监控配置
      transport: # 传输配置
        port: 8719 # Sentinel组件启用之后默认会启动一个8719端口
        dashboard: sentinel-server:8888 # 控制台地址
      block-page: /errors/block_handler # 阻断页

Fallback 失败回退

1、
package com.yootk.provider.config;

import com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@EnableAspectJAutoProxy(exposeProxy = true, proxyTargetClass = true) // CGLIB代理
public class SentinelAOPConfig { // Sentinel配置
    // 所有的Fallback的处理操作全部都是基于切面的形式负责完成的
    @Bean
    public SentinelResourceAspect getSentinelResourceAspect() {
        return new SentinelResourceAspect();
    }
}


2、
package com.yootk.provider.action;

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.yootk.common.dto.DeptDTO;
import com.yootk.service.IDeptService;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;

@RestController
@RequestMapping("/provider/dept/*") // 微服务提供者父路径
@Slf4j // 使用一个注解
public class DeptAction {
    @Autowired
    private IDeptService deptService;

    @SentinelResource(value = "/dept_get",fallback = "getFallback")
    @ApiOperation(value="部门查询", notes = "根据部门编号查询部门详细信息")
    @GetMapping("get/{id}")
    public Object get(@PathVariable("id") long id) {
        this.printRequestHeaders("get");
        return this.deptService.get(id);
    }
    public Object getFallback(@PathVariable("id") long id) {
        DeptDTO dto = new DeptDTO();
        dto.setDeptno(id);
        dto.setDname("【Fallback】部门名称");
        dto.setLoc("【Fallback】部门位置");
        return dto;
    }
    @ApiOperation(value="部门增加", notes = "增加新的部门信息")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "deptDTO", required = true,
                    dataType = "DeptDTO", value = "部门传输对象实例")
    })
    @PostMapping("add")
    @SentinelResource(value = "/dept_add", fallback = "addFallback")
    public Object add(@RequestBody  DeptDTO deptDTO) {    // 后面会修改参数模式为JSON
        this.printRequestHeaders("add");
        return this.deptService.add(deptDTO);
    }
    public Object addFallback(@RequestBody  DeptDTO deptDTO) {
        return false;
    }
    @ApiOperation(value="部门列表", notes = "查询部门的完整信息")
    @GetMapping("list")
    @SentinelResource(value = "/dept_list", fallback = "listFallback")
    public Object list() {
        this.printRequestHeaders("list");
        return this.deptService.list();
    }
    public Object listFallback() {
        return new ArrayList<>();
    }
    @ApiOperation(value="部门分页查询", notes = "根据指定的数据库参数实现部门数据的分页加载")
    @ApiImplicitParams({
            @ApiImplicitParam(name="cp", value = "当前所在页", required = true, dataType = "int"),
            @ApiImplicitParam(name="ls", value = "每页显示的数据行数", required = true, dataType = "int"),
            @ApiImplicitParam(name="col", value = "模糊查询列", required = true, dataType = "String"),
            @ApiImplicitParam(name="kw", value = "模糊查询关键字", required = true, dataType = "String")
    })
    @GetMapping("split")
    @SentinelResource(value = "/dept_split", fallback = "splitFallback")
    public Object split(int cp, int ls, String col, String kw) {
        this.printRequestHeaders("split");
        return this.deptService.split(cp, ls, col, kw);
    }
    public Object splitFallback() {
        return new HashMap<>();
    }
    private void printRequestHeaders(String restName) {    // 实现所有请求头信息的输出
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        Enumeration<String> headerEnums = request.getHeaderNames();
        while (headerEnums.hasMoreElements()) {
            String headerName = headerEnums.nextElement();
            log.info("【{}】头信息:{} = {}", restName, headerName, request.getHeader(headerName));
        }
    }
}

BlockHandler

1、
package com.yootk.provider.action.block;

import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.yootk.common.dto.DeptDTO;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

public class DeptBlockHandler { // 限流信息
    public static Object addBlockHandler(DeptDTO deptDTO, BlockException e) {
        Map<String, Object> map = new HashMap<>();
        map.put("rule", e.getRule()); // 获取失败的信息
        map.put("message", e.getMessage()); // 异常信息
        map.put("result", false); // 本次的执行结果
        return map;
    }

    public static Object getBlockHandler(long id, BlockException e) {
        Map<String, Object> map = new HashMap<>();
        map.put("rule", e.getRule()); // 获取失败的信息
        map.put("message", e.getMessage()); // 异常信息
        DeptDTO dept = new DeptDTO();
        dept.setDeptno(id);
        dept.setDname("【Block】部门名称");
        dept.setLoc("【Block】部门位置");
        map.put("result", dept); // 本次的执行结果
        return map;
    }

    public static Object listBlockHandler(BlockException e) {
        Map<String, Object> map = new HashMap<>();
        map.put("rule", e.getRule()); // 获取失败的信息
        map.put("message", e.getMessage()); // 异常信息
        map.put("result", new ArrayList<>()); // 本次的执行结果
        return map;
    }

    public static Object splitBlockHandler(int cp, int ls, String col, String kw, BlockException e) {
        Map<String, Object> map = new HashMap<>();
        map.put("rule", e.getRule()); // 获取失败的信息
        map.put("message", e.getMessage()); // 异常信息
        map.put("result", new HashMap<>()); // 本次的执行结果
        return map;
    }
}



2、
package com.yootk.provider.action;

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.yootk.common.dto.DeptDTO;
import com.yootk.provider.action.block.DeptBlockHandler;
import com.yootk.service.IDeptService;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;

@RestController
@RequestMapping("/provider/dept/*") // 微服务提供者父路径
@Slf4j // 使用一个注解
public class DeptAction {
    @Autowired
    private IDeptService deptService;

    @SentinelResource(value = "/dept_get", blockHandlerClass = DeptBlockHandler.class, blockHandler = "getBlockHandler")
    @ApiOperation(value = "部门查询", notes = "根据部门编号查询部门详细信息")
    @GetMapping("get/{id}")
    public Object get(@PathVariable("id") long id) {
        this.printRequestHeaders("get");
        return this.deptService.get(id);
    }

    @ApiOperation(value = "部门增加", notes = "增加新的部门信息")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "deptDTO", required = true,
                    dataType = "DeptDTO", value = "部门传输对象实例")
    })
    @PostMapping("add")
    @SentinelResource(value = "/dept_add", blockHandlerClass = DeptBlockHandler.class, blockHandler = "addBlockHandler")
    public Object add(@RequestBody DeptDTO deptDTO) {    // 后面会修改参数模式为JSON
        this.printRequestHeaders("add");
        return this.deptService.add(deptDTO);
    }

    @ApiOperation(value = "部门列表", notes = "查询部门的完整信息")
    @GetMapping("list")
    @SentinelResource(value = "/dept_add", blockHandlerClass = DeptBlockHandler.class, blockHandler = "listBlockHandler")
    public Object list() {
        this.printRequestHeaders("list");
        return this.deptService.list();
    }

    @ApiOperation(value = "部门分页查询", notes = "根据指定的数据库参数实现部门数据的分页加载")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "cp", value = "当前所在页", required = true, dataType = "int"),
            @ApiImplicitParam(name = "ls", value = "每页显示的数据行数", required = true, dataType = "int"),
            @ApiImplicitParam(name = "col", value = "模糊查询列", required = true, dataType = "String"),
            @ApiImplicitParam(name = "kw", value = "模糊查询关键字", required = true, dataType = "String")
    })
    @GetMapping("split")
    @SentinelResource(value = "/dept_add", blockHandlerClass = DeptBlockHandler.class, blockHandler = "splitBlockHandler")
    public Object split(int cp, int ls, String col, String kw) {
        this.printRequestHeaders("split");
        return this.deptService.split(cp, ls, col, kw);
    }

    private void printRequestHeaders(String restName) {    // 实现所有请求头信息的输出
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        Enumeration<String> headerEnums = request.getHeaderNames();
        while (headerEnums.hasMoreElements()) {
            String headerName = headerEnums.nextElement();
            log.info("【{}】头信息:{} = {}", restName, headerName, request.getHeader(headerName));
        }
    }
}


4、
免费视频下载:www.yootk.com
找到顶部的资源:里面根据指引获取网盘地址。

热点规则 授权规则

1、
package com.yootk.provider.config;

import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
@Component
public class SentinelRequestOriginParser implements RequestOriginParser { // 请求解析
    @Override
    public String parseOrigin(HttpServletRequest request) {
        String serviceName = request.getParameter("serviceName"); // 接收请求参数
        if (serviceName == null || "".equals(serviceName)) {    // 参数的内容是空
            serviceName = request.getHeader("serviceName"); // 通过头信息传递参数
        }
        if (StringUtils.isEmpty(serviceName)) {
            return request.getRemoteAddr(); // 根据IP地址处理
        }
        return serviceName;
    }
}


2、
package com.yootk.provider.config;

import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
@Component
@Slf4j
public class SentinelRequestOriginParser implements RequestOriginParser { // 请求解析
    @Override
    public String parseOrigin(HttpServletRequest request) {
        String serviceName = request.getParameter("serviceName"); // 接收请求参数
        if (serviceName == null || "".equals(serviceName)) {    // 参数的内容是空
            serviceName = request.getHeader("serviceName"); // 通过头信息传递参数
        }
        log.info("授权规则的信息,serviceName = " + serviceName);
        if (StringUtils.isEmpty(serviceName)) {
            return request.getRemoteAddr(); // 根据IP地址处理
        }
        return serviceName;
    }
}


3、

package com.yootk.service.config;

import feign.Logger;
import feign.RequestInterceptor;
import org.springframework.context.annotation.Bean;

public class FeignConfig { // 定义Feign配置类
    @Bean
    public Logger.Level level() {
        return Logger.Level.FULL; // 输出完全的日志信息
    }
    @Bean
    public RequestInterceptor getFeignRequestInterceptor() {    // 请求拦截器
        return (template -> template.header("serviceName", "pc"));
    }
}

BlockExceptionHandler

1、
package com.yootk.provider.action.block;

import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityException;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException;
import com.alibaba.csp.sentinel.slots.system.SystemBlockException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.stereotype.Component;
import org.springframework.util.MimeTypeUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;

@Component
public class SentinelBlockExceptionHandler implements BlockExceptionHandler {
    @Override
    public void handle(HttpServletRequest request,
                       HttpServletResponse response, BlockException e) throws Exception {
        Map<String, Object> errors = new HashMap<>(); // 数据的保存
        errors.put("type", e.getClass().getName()); // 异常的类型
        // 之所以这样编写是为了帮助大家加深关于Sentinel组件内部提供的异常类型
        if (e instanceof FlowException) {
            errors.put("message", "服务限流");
        } else if (e instanceof DegradeException) {
            errors.put("message", "服务降级");
        } else if (e instanceof ParamFlowException) {
            errors.put("message", "热点参数限流");
        } else if (e instanceof SystemBlockException) {
            errors.put("message", "系统拦截");
        } else if (e instanceof AuthorityException) {
            errors.put("message", "授权拦截");
        } else {
            errors.put("message", "其他异常");
        }
        errors.put("path", request.getRequestURI()); // 产生异常的路径
        response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); // 状态码
        response.setCharacterEncoding("UTF-8"); // 设置响应编码
        response.setHeader("Content-Type", MimeTypeUtils.APPLICATION_JSON_VALUE); // JSON直接响应
        new ObjectMapper().writeValue(response.getWriter(), errors); // Jackson组件的输出
    }
}


2、
    // @SentinelResource(value = "/dept_get", fallback = "getFallback", blockHandlerClass = DeptBlockHandler.class, blockHandler = "getBlockHandler")
    @ApiOperation(value = "部门查询", notes = "根据部门编号查询部门详细信息")
    @GetMapping("get/{id}")
    public Object get(@PathVariable("id") long id) {
        if (id % 2 == 0) {   // 查询ID为偶数
            throw new RuntimeException("查询iD不能为偶数!");
        }
        this.printRequestHeaders("get");
        return this.deptService.get(id);
    }

集群流控

1、
project(":sentinel-token-server") {
    dependencies { // 配置模块所需要的依赖库
        implementation("org.springframework.boot:spring-boot-starter-web") // SpringBoot依赖
        implementation('com.alibaba.cloud:spring-cloud-starter-alibaba-sentinel') {
            exclude group: 'com.alibaba.csp', module: 'sentinel-cluster-client-default'
        }
    }
}

2、
package com.yootk.sentinel;

import com.alibaba.csp.sentinel.cluster.server.ClusterTokenServer;
import com.alibaba.csp.sentinel.cluster.server.SentinelDefaultTokenServer;
import com.alibaba.csp.sentinel.cluster.server.config.ClusterServerConfigManager;
import com.alibaba.csp.sentinel.cluster.server.config.ServerTransportConfig;

public class StartTokenServerApplication {
    // -Dcsp.sentinel.dashboard.server=sentinel-server:8888 -Dcsp.sentinel.api.port=8719
    // -Dproject.name=sentinel-token-server -Dcsp.sentinel.log.use.pid=true
    static {    // 使用系统属性代替启动参数
        System.setProperty("csp.sentinel.dashboard.server", "sentinel-server:8888");            // 控制台地址
        System.setProperty("csp.sentinel.api.port", "8719");    // sentinel端口
        System.setProperty("project.name", "token-server");        // 服务名称
        System.setProperty("csp.sentinel.log.use.pid", "true");    // 设置pid(可选)
    }

    public static void main(String[] args) throws Exception {
        ClusterTokenServer tokenServer = new SentinelDefaultTokenServer(); // 实例化Token集群管理
        ClusterServerConfigManager.loadGlobalTransportConfig(new ServerTransportConfig().setIdleSeconds(600).setPort(10217));
        tokenServer.start();// 启动服务器
    }
}


3、
127.0.0.1	sentinel-token-server

Sentinel 实现分析

1、
package com.yootk.provider.config;

import com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@EnableAspectJAutoProxy(exposeProxy = true, proxyTargetClass = true) // CGLIB代理
public class SentinelAOPConfig { // Sentinel配置
    // 所有的Fallback的处理操作全部都是基于切面的形式负责完成的
    @Bean
    public SentinelResourceAspect getSentinelResourceAspect() {
        return new SentinelResourceAspect();
    }
}


2、

package com.alibaba.csp.sentinel.annotation.aspectj;
import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.EntryType;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import java.lang.reflect.Method;
@Aspect // 现在定义的是一个AOP切面
public class SentinelResourceAspect extends AbstractSentinelAspectSupport {
// 定义AOP的切入点,使用了一个SpEL表达式,只要存在一个SentinelResource注解的操作
    @Pointcut("@annotation(com.alibaba.csp.sentinel.annotation.SentinelResource)")
    public void sentinelResourceAnnotationPointcut() {}
// 所有被Sentinel操作的组件的代码都是在代理结构之中进行的控制。
    @Around("sentinelResourceAnnotationPointcut()")// 采用了环绕通知
    public Object invokeResourceWithSentinel(ProceedingJoinPoint pjp) throws Throwable {
        Method originMethod = resolveMethod(pjp); // 方法的解析
        // 通过指定的方法(当前正在调用的微服务中的处理方法)获取Annotation
        SentinelResource annotation = originMethod.getAnnotation(SentinelResource.class);
        if (annotation == null) { // 没有发现对应的Annotation
            throw new IllegalStateException("Wrong state for SentinelResource annotation");
        }
// 获取资源的名称,如果没有在注解之中配置名称就使用方法定义,而如果有则使用自定义名称
        String resourceName = getResourceName(annotation.value(), originMethod);
        EntryType entryType = annotation.entryType();// 实体类型
        int resourceType = annotation.resourceType();// 资源类型
        Entry entry = null; // 实体,只是进行了一个简单的定义
        try { // 以下的处理操作才是Sentinel内部最为核心的实现原理
            entry = SphU.entry(resourceName, resourceType, entryType, pjp.getArgs());
            Object result = pjp.proceed();// 调用真实操作方法
            return result;
        } catch (BlockException ex) {  // 阻断异常,违反了限流规则
            return handleBlockException(pjp, annotation, ex);
        } catch (Throwable ex) { // 异常处理操作
            Class<? extends Throwable>[] exceptionsToIgnore = annotation.exceptionsToIgnore();
            // The ignore list will be checked first.
            if (exceptionsToIgnore.length > 0 && exceptionBelongsTo(ex, exceptionsToIgnore)) {
                throw ex;
            }
            if (exceptionBelongsTo(ex, annotation.exceptionsToTrace())) {
                traceException(ex);
                return handleFallback(pjp, annotation, ex);
            }
            // No fallback function can handle the exception, so throw it out.
            throw ex;
        } finally {
            if (entry != null) {
                entry.exit(1, pjp.getArgs());// 结束实体调用
             }
        }
    }
}

3、
package com.yootk.test;

import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

public class TestEntry {
    public static void main(String[] args) { // 主方法直接测试
        initFlowRules(); // 初始化规则
        while (true) {  // 循环调用
            Entry entry = null;
            try {
                entry = SphU.entry("YootkMessage"); // 创建规则实体
                System.out.println("【业务处理】www.yootk.com");
            } catch (BlockException e) {
                e.printStackTrace();
            } finally {
                if (entry != null) {
                    entry.exit();
                }
            }
        }
    }
    public static void initFlowRules() {    // 配置基础的流控原则
        // 在一个资源上可以同时配置若干个不同的限流访问规则,那么这一点在程序里面就是List集合
        List<FlowRule> rules = new ArrayList<>();
        FlowRule rule = new FlowRule(); // 定义一个具体的限流规则
        rule.setResource("YootkMessage"); // 定义资源名称
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS); // 根据QPS进行限流
        rule.setCount(10); // 每秒允许有10个请求访问
        rules.add(rule); // 保存当前的规则
        FlowRuleManager.loadRules(rules); // 加载规则
    }
}

ResourceWrapper

1、
package com.alibaba.csp.sentinel.annotation.aspectj;
public abstract class AbstractSentinelAspectSupport {
    protected String getResourceName(String resourceName, /*@NonNull*/ Method method) {
        if (StringUtil.isNotBlank(resourceName)) { // 判断名称是否为空
            return resourceName; // 直接返回一个具体的名称
        }
        return MethodUtil.resolveMethodName(method); // 根据方法来解析名称
    }
    protected Method resolveMethod(ProceedingJoinPoint joinPoint) { // AOP处理解析
        MethodSignature signature = (MethodSignature)joinPoint.getSignature();
        Class<?> targetClass = joinPoint.getTarget().getClass();
        Method method = getDeclaredMethodFor(targetClass, signature.getName(),
            signature.getMethod().getParameterTypes());
        if (method == null) {
            throw new IllegalStateException("Cannot resolve target method: " + signature.getMethod().getName());
        }
        return method;
    }
}


2、
public static String resolveMethodName(Method method) {
    if (method == null) { // 没有传入Method对象实例
        throw new IllegalArgumentException("Null method");
    }
    String methodName = methodNameMap.get(method); // 获取方法的名称
    if (methodName == null) { // 方法名称为空
        synchronized (LOCK) { // 解析的时候需要同步处理
            methodName = methodNameMap.get(method); // 继续进行解析处理
            if (methodName == null) {
                StringBuilder sb = new StringBuilder();
                String className = method.getDeclaringClass().getName();// Class名称
                String name = method.getName(); // 方法名称
                Class<?>[] params = method.getParameterTypes();// 参数的信息
                sb.append(className).append(":").append(name);
                sb.append("(");
                int paramPos = 0;
                for (Class<?> clazz : params) {
                    sb.append(clazz.getCanonicalName());
                    if (++paramPos < params.length) {
                        sb.append(",");
                    }
                }
                sb.append(")");
                methodName = sb.toString();
                methodNameMap.put(method, methodName); // 名称信息保存在了Map集合之中
            }
        }
    }
    return methodName;
}

ProcessorSlot

1、
package com.alibaba.csp.sentinel;
public class CtSph implements Sph {
    private static final Object[] OBJECTS0 = new Object[0];
// 这个类会维护有一个Map集合,这个Map集合对应的KEY类型是ResourceWrapper(描述的是资源名称)
// 而Map集合中的VALUE是一个ProcessorSlotChain抽象类的实例
    private static volatile Map<ResourceWrapper, ProcessorSlotChain> chainMap
        = new HashMap<ResourceWrapper, ProcessorSlotChain>();
    private static final Object LOCK = new Object();
}


2、
package com.alibaba.csp.sentinel.slotchain;
public abstract class ProcessorSlotChain extends AbstractLinkedProcessorSlot<Object> {
    /**
     * 追加一个处理类到SlotChain头部节点.
     * @param protocolProcessor processor to be added.
     */
    public abstract void addFirst(AbstractLinkedProcessorSlot<?> protocolProcessor);
    /**
     * 追加一个处理类到SlotChain尾部节点.
     * @param protocolProcessor processor to be added.
     */
    public abstract void addLast(AbstractLinkedProcessorSlot<?> protocolProcessor);
}


3、
ProcessorSlot<Object> lookProcessChain(ResourceWrapper resourceWrapper) {
    ProcessorSlotChain chain = chainMap.get(resourceWrapper);
    if (chain == null) {
        synchronized (LOCK) {
            chain = chainMap.get(resourceWrapper);
            if (chain == null) {
                // Entry size limit.
                if (chainMap.size() >= Constants.MAX_SLOT_CHAIN_SIZE) {
                    return null;
                }
                chain = SlotChainProvider.newSlotChain();
                Map<ResourceWrapper, ProcessorSlotChain> newMap = new HashMap<ResourceWrapper, ProcessorSlotChain>( chainMap.size() + 1);
                newMap.putAll(chainMap);
                newMap.put(resourceWrapper, chain);
                chainMap = newMap;
            }
        }
    }
    return chain;
}


4、
public static ProcessorSlotChain newSlotChain() {
    if (slotChainBuilder != null) {
        return slotChainBuilder.build();
    }
    // Resolve the slot chain builder SPI.
    slotChainBuilder = SpiLoader.loadFirstInstanceOrDefault(SlotChainBuilder.class, DefaultSlotChainBuilder.class);
    if (slotChainBuilder == null) {
        // Should not go through here.
        RecordLog.warn("[SlotChainProvider] Wrong state when resolving slot chain builder, using default");
        slotChainBuilder = new DefaultSlotChainBuilder();
    } else {
        RecordLog.info("[SlotChainProvider] Global slot chain builder resolved: "
            + slotChainBuilder.getClass().getCanonicalName());
    }
    return slotChainBuilder.build();
}


5、
package com.alibaba.csp.sentinel.slotchain;
public interface SlotChainBuilder {
    ProcessorSlotChain build();
}
package com.alibaba.csp.sentinel.slots;
public class DefaultSlotChainBuilder implements SlotChainBuilder {
    @Override
    public ProcessorSlotChain build() {
        ProcessorSlotChain chain = new DefaultProcessorSlotChain();
        chain.addLast(new NodeSelectorSlot());
        chain.addLast(new ClusterBuilderSlot());
        chain.addLast(new LogSlot());
        chain.addLast(new StatisticSlot());
        chain.addLast(new AuthoritySlot());
        chain.addLast(new SystemSlot());
        chain.addLast(new FlowSlot());
        chain.addLast(new DegradeSlot());
        return chain;
    }
}

Node

1、
package com.alibaba.csp.sentinel.slots.statistic;
public class StatisticSlot extends AbstractLinkedProcessorSlot<DefaultNode> {
    @Override
    public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, boolean prioritized, Object... args) throws Throwable {
        try {
            fireEntry(context, resourceWrapper, node, count, prioritized, args); // 检测
            // Request passed, add thread count and pass count.
            node.increaseThreadNum();// 访问线程数量增长
            node.addPassRequest(count); // 通过的请求数量
            if (context.getCurEntry().getOriginNode() != null) {
                context.getCurEntry().getOriginNode().increaseThreadNum();// 当前节点上一节点统计
                context.getCurEntry().getOriginNode().addPassRequest(count);
            }
            if (resourceWrapper.getEntryType() == EntryType.IN) { // 统计的处理类型
                // Add count for global inbound entry node for global statistics.
                Constants.ENTRY_NODE.increaseThreadNum();
                Constants.ENTRY_NODE.addPassRequest(count);
            }
            // Handle pass event with registered entry callback handlers.
            for (ProcessorSlotEntryCallback<DefaultNode> handler :
StatisticSlotCallbackRegistry.getEntryCallbacks()) {
                handler.onPass(context, resourceWrapper, node, count, args);
            }
        } catch (PriorityWaitException ex) {
            node.increaseThreadNum();
            if (context.getCurEntry().getOriginNode() != null) {
                // Add count for origin node.
                context.getCurEntry().getOriginNode().increaseThreadNum();
            }
            if (resourceWrapper.getEntryType() == EntryType.IN) { // 进口统计
                // Add count for global inbound entry node for global statistics.
                Constants.ENTRY_NODE.increaseThreadNum();
            }
            // Handle pass event with registered entry callback handlers.
            for (ProcessorSlotEntryCallback<DefaultNode> handler :
StatisticSlotCallbackRegistry.getEntryCallbacks()) {
                handler.onPass(context, resourceWrapper, node, count, args);
            }
        } catch (BlockException e) {
            context.getCurEntry().setError(e);
            node.increaseBlockQps(count);
            if (context.getCurEntry().getOriginNode() != null) {
                context.getCurEntry().getOriginNode().increaseBlockQps(count);
            }
            if (resourceWrapper.getEntryType() == EntryType.IN) {
                // Add count for global inbound entry node for global statistics.
                Constants.ENTRY_NODE.increaseBlockQps(count);
            }
            // Handle block event with registered entry callback handlers.
            for (ProcessorSlotEntryCallback<DefaultNode> handler : StatisticSlotCallbackRegistry.getEntryCallbacks()) {
                handler.onBlocked(e, context, resourceWrapper, node, count, args);
            }
            throw e;
        } catch (Throwable e) {
            // Unexpected error, set error to current entry.
            context.getCurEntry().setError(e);
            // This should not happen.
            node.increaseExceptionQps(count);
            if (context.getCurEntry().getOriginNode() != null) {
                context.getCurEntry().getOriginNode().increaseExceptionQps(count);
            }
            if (resourceWrapper.getEntryType() == EntryType.IN) {
                Constants.ENTRY_NODE.increaseExceptionQps(count);
            }
            throw e;
        }
    }
    @Override
    public void exit(Context context, ResourceWrapper resourceWrapper, int count,
Object... args) {
        DefaultNode node = (DefaultNode)context.getCurNode();// 获取当前节点
        if (context.getCurEntry().getError() == null) {
            // 计算RT时间 (max RT is statisticMaxRt from SentinelConfig).
            long rt = TimeUtil.currentTimeMillis() - context.getCurEntry().getCreateTime();
            int maxStatisticRt = SentinelConfig.statisticMaxRt();
            if (rt > maxStatisticRt) {
                rt = maxStatisticRt;
            }
            node.addRtAndSuccess(rt, count); //  RT记录
            if (context.getCurEntry().getOriginNode() != null) {
                context.getCurEntry().getOriginNode().addRtAndSuccess(rt, count);
            }
            node.decreaseThreadNum();// 完成了,减少线程数量
            if (context.getCurEntry().getOriginNode() != null) {
                context.getCurEntry().getOriginNode().decreaseThreadNum();
            }
            if (resourceWrapper.getEntryType() == EntryType.IN) {
                Constants.ENTRY_NODE.addRtAndSuccess(rt, count);
                Constants.ENTRY_NODE.decreaseThreadNum();
            }
        } else {
            // Error may happen.
        }
        Collection<ProcessorSlotExitCallback> exitCallbacks =
StatisticSlotCallbackRegistry.getExitCallbacks();
        for (ProcessorSlotExitCallback handler : exitCallbacks) {
            handler.onExit(context, resourceWrapper, count, args);
        }
        fireExit(context, resourceWrapper, count);
    }
}

Context

1、
package com.alibaba.csp.sentinel.context;
public class Context {
    private final String name;
    private DefaultNode entranceNode; // 进行数据统计的时候都会有DefaultNode对象实例
    private Entry curEntry; // 当前的实体对象
    private String origin = ""; // 源
    private final boolean async; // 异步处理标记
}


2、
package com.alibaba.csp.sentinel.context;
public class ContextUtil {
    // 每一次用户的访问Context都会保存在ThreadLocal集合之中
    private static ThreadLocal<Context> contextHolder = new ThreadLocal<>();
    // 对当前的Context进行访问统计的集合
    private static volatile Map<String, DefaultNode> contextNameNodeMap = new HashMap<>();
    private static final ReentrantLock LOCK = new ReentrantLock(); // 独占锁(互斥锁)
    private static final Context NULL_CONTEXT = new NullContext();
    static {
        initDefaultContext();// 初始化默认的Context
    }
    private static void initDefaultContext() {
        String defaultContextName = Constants.CONTEXT_DEFAULT_NAME; // 获取默认名称
        EntranceNode node = new EntranceNode(new StringResourceWrapper(defaultContextName, EntryType.IN), null); // 创建Node接口对象,同时定义ResourceWraper对象实例
        Constants.ROOT.addChild(node); // 节点追加
        contextNameNodeMap.put(defaultContextName, node); // Map集合保存Node
    }
    static void resetContextMap() {
        if (contextNameNodeMap != null) {
            RecordLog.warn("Context map cleared and reset to initial state");
            contextNameNodeMap.clear();
            initDefaultContext();
        }
    }
    public static Context enter(String name, String origin) {
        if (Constants.CONTEXT_DEFAULT_NAME.equals(name)) {
            throw new ContextNameDefineException(
                "The " + Constants.CONTEXT_DEFAULT_NAME + " can't be permit to defined!");
        }
        return trueEnter(name, origin);
    }
    protected static Context trueEnter(String name, String origin) { // 核心的处理
        Context context = contextHolder.get();// 获取当前的Context对象
        if (context == null) { // 没有存在Context
            Map<String, DefaultNode> localCacheNameMap = contextNameNodeMap; // 通过Map集合获取
            DefaultNode node = localCacheNameMap.get(name); // 获取初始化的时候保存的Node
            if (node == null) { // 节点为空(无法进行数据的存储)
                if (localCacheNameMap.size() > Constants.MAX_CONTEXT_NAME_SIZE) {
                    setNullContext();// 设置空的Context
                    return NULL_CONTEXT;
                } else {
                    try {
                        LOCK.lock();// 独占锁,只允许单线程访问
                        node = contextNameNodeMap.get(name); // 获取节点对象实例
                        if (node == null) { // 节点为空
                            if (contextNameNodeMap.size() > Constants.MAX_CONTEXT_NAME_SIZE) {
                                setNullContext();// 空上下文处理
                                return NULL_CONTEXT;
                            } else {
                                node = new EntranceNode(new StringResourceWrapper(
name, EntryType.IN), null); // 初始化节点
                                Constants.ROOT.addChild(node); // 追加节点
                                Map<String, DefaultNode> newMap = new HashMap<>(
contextNameNodeMap.size() + 1);
                                newMap.putAll(contextNameNodeMap); // 保存节点的Map集合
                                newMap.put(name, node);
                                contextNameNodeMap = newMap;
                            }
                        }
                    } finally {
                        LOCK.unlock();// 解锁
                    }
                }
            }
            context = new Context(node, name); // 每一个Context保存有节点对象
            context.setOrigin(origin); // 设置操作源
            contextHolder.set(context); // 保存在当前线程之中(每一次的请求)
        }
        return context;
    }
}

Sentinel 规则持久化

1、
package com.alibaba.csp.sentinel.datasource;
import com.alibaba.csp.sentinel.property.SentinelProperty;
public interface ReadableDataSource<S, T> {
    T loadConfig() throws Exception; // 读取配置项
    S readSource() throws Exception; // 读取原生配置的数据项
    SentinelProperty<T> getProperty();// 获取Sentinel配置属性
    void close() throws Exception; // 关闭读取数据源
}


2、
package com.alibaba.csp.sentinel.property;
public interface SentinelProperty<T> {
    void addListener(PropertyListener<T> listener); // 配置属性的监听器
    void removeListener(PropertyListener<T> listener); // 移除属性监听器
    boolean updateValue(T newValue); // 更新属性内容
}


3、
tar xzvf /var/ftp/nacos-server-2.0.2.tar.gz -C /usr/local/

4、
/usr/local/nacos/bin/startup.sh -m standalone

5、
firewall-cmd --zone=public --add-port=8848/tcp --permanent
firewall-cmd --zone=public --add-port=9848/tcp --permanent
firewall-cmd --zone=public --add-port=7848/tcp --permanent
firewall-cmd --zone=public --add-port=9849/tcp --permanent
firewall-cmd --reload

6、
sentinel-nacos-server:8848/nacos

流控规则持久化

1、
51586a27-b10d-4165-9cd2-38f1464c780d

2、
[
  {
    "resource": "/provider/dept/list",
    "limitApp": "default",
    "grade": 1,
    "count": 1,
    "strategy": 0,
    "controlBehavior": 0,
    "clusterMode": false
  }
]


3、
// https://mvnrepository.com/artifact/com.alibaba.csp/sentinel-datasource-nacos
implementation group: 'com.alibaba.csp', name: 'sentinel-datasource-nacos', version: '1.8.2'


4、
ext.versions = [                // 定义全部的依赖库版本号
    springboot           : '2.2.5.RELEASE',      // SpringBoot版本号
    springcloud          : 'Hoxton.SR3', // SpringCloud版本号
    alibabacloud         : '2.2.1.RELEASE', // SpringCloudAlibaba版本号
    lombok               : '1.18.20', // Lombok版本号
    junit                : '5.6.3', // 配置JUnit测试工具的版本编号
    junitPlatformLauncher: '1.6.3',  // JUnit测试工具运行平台版本编号
    mybatisPlus          : '3.4.3', // MyBatisPlus的版本号
    mysql                : '8.0.25', // MySQL数据库驱动版本
    druid                : '1.2.6', // Druid版本号
    swagger              : '3.0.0', // Swagger版本号
    nacos                : '2.0.2', // Nacos版本号
    httpclient           : '4.5.13', // HttpClient版本号
    feignHttpclient      : '11.6', // FeignHttpClient版本号
    sentinel             : '1.8.2', // Sentinel版本号
]
ext.libraries = [            // 依赖库引入配置
     'spring-boot-gradle-plugin'        :
             "org.springframework.boot:spring-boot-gradle-plugin:${versions.springboot}",
     'spring-cloud-dependencies'        :
             "org.springframework.cloud:spring-cloud-dependencies:${versions.springcloud}",
     'spring-cloud-alibaba-dependencies':
             "com.alibaba.cloud:spring-cloud-alibaba-dependencies:${versions.alibabacloud}",
     // 以下的配置为与项目用例测试有关的依赖
     'junit-jupiter-api'                :
             "org.junit.jupiter:junit-jupiter-api:${versions.junit}",
     'junit-vintage-engine'             :
             "org.junit.vintage:junit-vintage-engine:${versions.junit}",
     'junit-jupiter-engine'             :
             "org.junit.jupiter:junit-jupiter-engine:${versions.junit}",
     'junit-platform-launcher'          :
             "org.junit.platform:junit-platform-launcher:${versions.junitPlatformLauncher}",
     'junit-platform-engine'            :
             "org.junit.platform:junit-platform-engine:${versions.junitPlatformLauncher}",
     'junit-jupiter-params'             :
             "org.junit.jupiter:junit-jupiter-params:${versions.junit}",
     'junit-bom'                        : "org.junit:junit-bom:${versions.junit}",
     'junit-platform-commons'           :
             "org.junit.platform:junit-platform-commons:${versions.junitPlatformLauncher}",
     // 以下的配置为Lombok组件有关的依赖
     'lombok'                           : "org.projectlombok:lombok:${versions.lombok}",
     // 以下的配置为数据库开发有关的依赖
     'mybatis-plus-boot-starter'        : "com.baomidou:mybatis-plus-boot-starter:${versions.mybatisPlus}",
     'mysql-connector-java'             : "mysql:mysql-connector-java:${versions.mysql}",
     'druid'                            : "com.alibaba:druid:${versions.druid}",
     // 以下的配置为Swagger有关的依赖库
    'springfox-boot-starter'            : "io.springfox:springfox-boot-starter:${versions.swagger}",
     // 以下的配置为Nacos有关的依赖库
    'nacos-client'                      : "com.alibaba.nacos:nacos-client:${versions.nacos}",
    // 以下的配置为Feign与HttpClient有关的依赖库
    'httpclient'                        : "org.apache.httpcomponents:httpclient:${versions.httpclient}",
    'feign-httpclient'                  : "io.github.openfeign:feign-httpclient:${versions.feignHttpclient}",
    // 以下的配置为Sentinel有关的组件依赖
    'sentinel-datasource-nacos'         : "com.alibaba.csp:sentinel-datasource-nacos:${versions.sentinel}"
]


5、
project(":provider-dept-8001") {    // 部门微服务
    dependencies {
        implementation(project(":common-api")) // 导入公共的子模块
        implementation(libraries.'mybatis-plus-boot-starter')
        implementation(libraries.'mysql-connector-java')
        implementation(libraries.'druid')
        implementation(libraries.'springfox-boot-starter')
        implementation(libraries.'sentinel-datasource-nacos')
        implementation('org.springframework.boot:spring-boot-starter-security')
        implementation('com.alibaba.cloud:spring-cloud-starter-alibaba-sentinel')
        // 以下的依赖库为Nacos注册中心所需要的依赖配置
        implementation('com.alibaba.cloud:spring-cloud-starter-alibaba-nacos-discovery') {
            exclude group: 'com.alibaba.nacos', module: 'nacos-client' // 移除旧版本的Nacos依赖
        }
        implementation('com.alibaba.cloud:spring-cloud-starter-alibaba-nacos-config') {
            exclude group: 'com.alibaba.nacos', module: 'nacos-client' // 移除旧版本的Nacos依赖
        }
        implementation(libraries.'nacos-client') // 引入与当前的Nacos匹配的依赖库
    }
}


6、
spring:
  application: # 配置应用信息
    name: dept.provider # 是微服务的名称
  cloud: # Cloud配置
    sentinel: # 监控配置
      transport: # 传输配置
        port: 8719 # Sentinel组件启用之后默认会启动一个8719端口
        dashboard: sentinel-server:8888 # 控制台地址
      block-page: /errors/block_handler # 阻断页
      eager: true # 饥饿加载
      datasource:  # 数据源配置
        flow-datasource: # 流控的数据源
          nacos: # 当前的存储介质为Nacos
            server-addr: sentinel-nacos-server:8848 # SentinelNacos地址
            namespace: 51586a27-b10d-4165-9cd2-38f1464c780d # 命名空间ID
            group-id: SENTINEL_GROUP # 一般建议大写
            data-id: ${spring.application.name}-flow-rules # 配置项的名称
            data-type: json # 配置的文件结构
            rule_type: flow # 流控规则

7、
[2a540420-51d6-4ea3-b6f7-0e1bcfb78190_config-0]receive server push request,request=ConfigChangeNotifyRequest,requestId=1
[2a540420-51d6-4ea3-b6f7-0e1bcfb78190_config-0] [server-push] config changed. dataId=dept.provider-flow-rules, group=SENTINEL_GROUP,tenant=51586a27-b10d-4165-9cd2-38f1464c780d
[2a540420-51d6-4ea3-b6f7-0e1bcfb78190_config-0]ack server push request,request=ConfigChangeNotifyRequest,requestId=1
[config_rpc_client] [notify-listener] time cost=0ms in ClientWorker, dataId=dept.provider-flow-rules, group=SENTINEL_GROUP, md5=a648b66d966d505aec4490ba3e11be12, listener=com.alibaba.csp.sentinel.datasource.nacos.NacosDataSource$1@2f530d28
[config_rpc_client] [notify-ok] dataId=dept.provider-flow-rules, group=SENTINEL_GROUP, md5=a648b66d966d505aec4490ba3e11be12, listener=com.alibaba.csp.sentinel.datasource.nacos.NacosDataSource$1@2f530d28 ,cost=1 millis.

限流规则解析

1、
[
  {
    "resource": "/provider/dept/list",
    "limitApp": "default",
    "grade": 1,
    "count": 2,
    "strategy": 0,
    "controlBehavior": 0,
    "clusterMode": false
  }
]


2、
[
  {
    "qps": 1
  }
]

3、
spring:
  application: # 配置应用信息
    name: dept.provider # 是微服务的名称
  cloud: # Cloud配置
    sentinel: # 监控配置
      transport: # 传输配置
        port: 8719 # Sentinel组件启用之后默认会启动一个8719端口
        dashboard: sentinel-server:8888 # 控制台地址
      block-page: /errors/block_handler # 阻断页
      eager: true # 饥饿加载
      datasource:  # 数据源配置
        system-datasource: # 系统规则数据源
          nacos: # 当前的存储介质为Nacos
            server-addr: sentinel-nacos-server:8848 # SentinelNacos地址
            namespace: 51586a27-b10d-4165-9cd2-38f1464c780d # 命名空间ID
            group-id: SENTINEL_GROUP # 一般建议大写
            data-id: ${spring.application.name}-system-rules # 配置项的名称
            data-type: json # 配置的文件结构
            rule_type: system # 流控规则
        flow-datasource: # 流控的数据源
          nacos: # 当前的存储介质为Nacos
            server-addr: sentinel-nacos-server:8848 # SentinelNacos地址
            namespace: 51586a27-b10d-4165-9cd2-38f1464c780d # 命名空间ID
            group-id: SENTINEL_GROUP # 一般建议大写
            data-id: ${spring.application.name}-flow-rules # 配置项的名称
            data-type: json # 配置的文件结构
            rule_type: flow # 流控规则

SentinelDashboard 改造

1、
# 添加与Nacos数据源有关的连接配置项(此处不能够使用具有认证支持的Nacos)
nacos.address=sentinel-nacos-server:8848
nacos.namespace=51586a27-b10d-4165-9cd2-38f1464c780d
nacos.clusterName=SentinelCluster

2、
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-datasource-nacos</artifactId>
           <!-- <scope>test</scope>-->
        </dependency>

3、

/*
 * Copyright 1999-2018 Alibaba Group Holding Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.alibaba.csp.sentinel.dashboard.rule.nacos;

import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity;
import com.alibaba.csp.sentinel.datasource.Converter;
import com.alibaba.fastjson.JSON;
import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.api.config.ConfigFactory;
import com.alibaba.nacos.api.config.ConfigService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.List;
import java.util.Properties;

/**
 * @author Eric Zhao
 * @since 1.4.0
 */
@Configuration
public class NacosConfig {
    @Value("${nacos.address}") // application.yml中配置的属性内容
    private String address;
    @Value("${nacos.namespace}") // application.yml中配置的属性内容
    private String namespace;
    @Value("${nacos.clusterName}") // application.yml中配置的属性内容
    private String clusterName;
    @Bean
    public Converter<List<FlowRuleEntity>, String> flowRuleEntityEncoder() {
        return JSON::toJSONString;
    }

    @Bean
    public Converter<String, List<FlowRuleEntity>> flowRuleEntityDecoder() {
        return s -> JSON.parseArray(s, FlowRuleEntity.class);
    }

    @Bean
    public ConfigService nacosConfigService() throws Exception { // 进行服务的配置
        Properties properties = new Properties(); // 定义Nacos的配置属性
        properties.put(PropertyKeyConst.SERVER_ADDR, this.address);
        properties.put(PropertyKeyConst.NAMESPACE, this.namespace);
        properties.put(PropertyKeyConst.CLUSTER_NAME, this.clusterName);
        return ConfigFactory.createConfigService(properties);
    }
}

4、
Sentinel\sentinel-dashboard\src\main\webapp\resources\app\scripts\directives\sidebar\sidebar.html

5、
          <li ui-sref-active="active" ng-if="entry.appType==0">
            <a ui-sref="dashboard.flow({app: entry.app})">
              <i class="glyphicon glyphicon-filter"></i>&nbsp;&nbsp;流控规则 V1</a>
          </li>

6、
mvn clean package -DskipTests

7、
Sentinel\sentinel-dashboard\target\sentinel-dashboard.jar

8、
java -Dserver.port=8888 -Dcsp.sentinel.dashboard.server=localhost:8888 -Dproject.name=sentinel-dashboard -Dsentinel.dashboard.auth.username=muyan -Dsentinel.dashboard.auth.password=yootk -jar sentinel-dashboard.jar
1、
nacos.core.auth.enabled=false

2、
127.0.0.1	sentinel-nacos-server

3、
java -Dserver.port=8888 -Dcsp.sentinel.dashboard.server=localhost:8888 -Dproject.name=sentinel-dashboard -Dsentinel.dashboard.auth.username=muyan -Dsentinel.dashboard.auth.password=yootk -jar sentinel-dashboard.jar

demo


上次编辑于: