Sentinel整合Dubbo限流实战

Sentinel整合Dubbo限流实战

建立provider项目

image-20190819104544783

添加jar依赖

<dependency>
  <artifactId>sentinel-api</artifactId>
  <groupId>com.lucas.sentinel</groupId>
  <version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
  <groupId>org.apache.dubbo</groupId>
  <artifactId>dubbo</artifactId>
  <version>2.7.2</version>
</dependency>
<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>slf4j-api</artifactId>
  <version>1.7.25</version>
</dependency>
<dependency>
  <groupId>org.apache.curator</groupId>
  <artifactId>curator-framework</artifactId>
  <version>4.0.0</version>
</dependency>
<dependency>
  <groupId>org.apache.curator</groupId>
  <artifactId>curator-recipes</artifactId>
  <version>4.0.0</version>
</dependency>

SentinelService

public interface SentinelService {
  String sayHello(String name);
}

SentinelServiceImpl

@Service
public class SentinelServiceImpl implements SentinelService{
  
  @Override
  public String sayHello(String name) {
    System.out.println("begin execute sayHello:"+name);
    return "Hello World:"+name+"->timer:"+LocalDateTime.now();
  }
}

ProviderConfig

@Configuration
@DubboComponentScan("com.lucas.sentinel")
public class ProviderConfig {

    @Bean
    public ApplicationConfig applicationConfig() {
        ApplicationConfig config = new ApplicationConfig();
        config.setName("sentinel-web");
        config.setOwner("Lucas");
        return config;
    }

    @Bean
    public RegistryConfig registryConfig() {
        RegistryConfig registryConfig = new RegistryConfig();
        registryConfig.setAddress("zookeeper://192.168.0.102:2181");
        return registryConfig;
    }

    @Bean
    public ProtocolConfig protocolConfig() {
        ProtocolConfig protocolConfig = new ProtocolConfig();
        protocolConfig.setName("dubbo");
        protocolConfig.setPort(20880);
        return protocolConfig;
    }
}

BootstrapApp

public class BootstrapApp {
    public static void main(String[] args) throws IOException {
        ApplicationContext applicationContext =
                new AnnotationConfigApplicationContext(ProviderConfig.class);
        ((AnnotationConfigApplicationContext) applicationContext).start();
        System.in.read();
    }
}

建立SpringBoot的Consumer项目

添加jar依赖

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter</artifactId>
</dependency>

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-test</artifactId>
  <scope>test</scope>
</dependency>

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
  <groupId>org.apache.dubbo</groupId>
  <artifactId>dubbo-spring-boot-starter</artifactId>
  <version>2.7.1</version>
</dependency>
<dependency>
  <groupId>org.apache.dubbo</groupId>
  <artifactId>dubbo</artifactId>
  <version>2.7.1</version>
</dependency>
<dependency>
  <groupId>com.lucas.sentinel</groupId>
  <artifactId>sentinel-api</artifactId>
  <version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
  <!-- zookeeper 须要使用3.5+版本 -> curator 4.0+ zookeeper 须要使用3.4.*版本 -> curator 2.0+ 采坑了-->
  <groupId>org.apache.curator</groupId>
  <artifactId>curator-recipes</artifactId>
  <version>4.0.0</version>
</dependency>

SentinelDubboController

@RestController
public class SentinelDubboController {

    @Reference
    SentinelService sentinelService;

    @GetMapping("/say")
    public String sayHello(){
        String result=sentinelService.sayHello("hello word");
        return result;
    }
}

application.properties

dubbo.registry.address=zookeeper://192.168.0.102:2181
dubbo.application.name=sentinel-web
dubbo.scan.base-packages=com.lucas.sentinel.demo.study

如今启动BoostrapApp,启动Consumer,用jmeter测试

image-20190819105614153

image-20190819105644332

添加sentinel限流支持

provider添加jar包依赖

<dependency>
  <groupId>com.alibaba.csp</groupId>
  <artifactId>sentinel-dubbo-adapter</artifactId>
  <version>1.6.2</version>
</dependency>

设置限流的基准

Service Provider 用于向外界提供服务,处理各个消费者的调用请求。为了保护 Provider 不被激增的流量拖垮影响稳定性,能够给 Provider 配置 QPS 模式的限流,这样当每秒的请求量超过设定的阈值时会自动拒绝多的请求。限流粒度能够是服务接口和服务方法两种粒度。若但愿整个服务接口的 QPS 不超过必定数值,则能够为对应服务接口资源(resourceName 为接口全限定名)配置 QPS 阈值;若但愿服务的某个方法的 QPS 不超过必定数值,则能够为对应服务方法资源(resourceName 为接口全限定名:方法签名)配置 QPS 阈值html

public class BootstrapApp {
    public static void main(String[] args) throws IOException {
        initFlowRule();//初始化限流规则
        ApplicationContext applicationContext =
                new AnnotationConfigApplicationContext(ProviderConfig.class);
        ((AnnotationConfigApplicationContext) applicationContext).start();
        System.in.read();
    }

    private static void initFlowRule() {
        FlowRule flowRule = new FlowRule();
        //针对具体的方法限流
        flowRule.setResource("com.lucas.sentinel.api.SentinelService:sayHello(java.lang.String) ");
        flowRule.setCount(10);//限流阈值 qps=10
        flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS);//限流阈值类型(QPS 或并发线程数)
        flowRule.setLimitApp("default");//流控针对的调用来源,若为 default 则不区分调用来源
        // 流量控制手段(直接拒绝、Warm Up、匀速排队)
        flowRule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT);
        FlowRuleManager.loadRules(Collections.singletonList(flowRule));
    }
}

启动时加入 JVM 参数 -Dcsp.sentinel.dashboard.server=localhost:8080 指定控制台地址和端口java

image-20190819113449456

这个是sentinel提供的一个web可视化页面,咱们下载这个sentinel-dashboard.jar,下载地址https://github.com/alibaba/Sentinel/releasesgit

image-20190819110634450

下载完成之后,直接用java -jar 启动,默认是8080端口github

java -jar sentinel-dashboard-1.6.3.jar

启动以后的浏览器进入http://localhost:8080web

image-20190819113414277

默认的用户密码为sentinel/sentinel算法

进入以后sentinel首页以下图spring

image-20190819111856025

由于尚未请求相应,没有数据shell

使用jemeter进行压测

100个qpsapache

image-20190819112140707

image-20190819114522244

由于咱们配置的限流阈值 qps=10json

image-20190819114621543

参数解释

LimitApp

不少场景下,根据调用方来限流也是很是重要的。好比有两个服务 A 和 B 都向 Service Provider 发起调用请求,咱们但愿只对来自服务 B 的请求进行限流,则能够设置限流规则的 limitApp 为服务 B 的名称。Sentinel Dubbo Adapter 会自动解析 Dubbo 消费者(调用方)的 application name 做为调用方名称(origin),在进行资源保护的时候都会带上调用方名称。若限流规则未配置调用方(default),则该限流规则对全部调用方生效。若限流规则配置了调用方则限流规则将仅对指定调用方生效。

注:Dubbo 默认通讯不携带对端 application name 信息,所以须要开发者在调用端手动将 applicationname 置入 attachment 中,provider 端进行相应的解析。Sentinel Dubbo Adapter 实现了一个 Filter 用于自动从 consumer 端向 provider 端透传 application name。若调用端未引入 Sentinel Dubbo Adapter,又但愿根据调用端限流,能够在调用端手动将 application name 置入 attachment 中,key 为dubboApplication

演示流程

  1. 修改provider中限流规则:flowRule.setLimitApp(“springboot-study”);

  2. 在consumer工程中,作以下处理。其中一个经过attachment传递了一个消费者的 application.name,另外一个没有传,经过jemeter工具进行测试

@GetMapping("/say")
    public String sayHello(){
        RpcContext.getContext().setAttachment("dubboApplication","springbootstudy");
        String result=sentinelService.sayHello("Lucas");
        return result;
    }
    @GetMapping("/say2")
    public String say2Hello(){
        String result=sentinelService.sayHello("Lucas");
        return result;
    }

/say的压测,限流生效

image-20190819115158588

/say2的压测,限流未生效

image-20190819115246776

ControlBehavior

当 QPS 超过某个阈值的时候,则采起措施进行流量控制。流量控制的手段包括如下几种:

直接拒绝Warm Up匀速排队

  • 直接拒绝(RuleConstant.CONTROL_BEHAVIOR_DEFAULT)方式是默认的流量控制方式,当QPS超过任意规则的阈值后,新的请求就会被当即拒绝,拒绝方式为抛出FlowException。这种方式适用于对系统处理能力确切已知的状况下,好比经过压测肯定了系统的准确水位时

  • Warm Up(RuleConstant.CONTROL_BEHAVIOR_WARM_UP)方式,即预热/冷启动方式,当系统长期处于低并发的状况下,流量忽然增长到qps的最高峰值,可能会形成系统的瞬间流量过大把系统压垮。因此warmup,至关于处理请求的数量是缓慢增长,通过一段时间之后,到达系统处理请求个数的最大值

  • 匀速排队(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER)方式会严格控制请求经过的间隔时间,也便是让请求以均匀的速度经过,对应的是漏桶算法它的原理是,以固定的间隔时间让请求经过。当请求过来的时候,若是当前请求距离上个经过的请求经过的时间间隔不小于预设值,则让当前请求经过;不然,计算当前请求的预期经过时间,若是该请求的预期经过时间小于规则预设的 timeout 时间,则该请求会等待直到预设时间到来经过;反之,则立刻抛出阻塞异常。

能够设置一个最长排队等待时间: flowRule.setMaxQueueingTimeMs(5 * 1000); // 最长排队等待时间:5s这种方式主要用于处理间隔性突发的流量,例如消息队列。想象一下这样的场景,在某一秒有大量的请求到来,而接下来的几秒则处于空闲状态,咱们但愿系统可以在接下来的空闲期间逐渐处理这些请求,而不是在第一秒直接拒绝多余的请求。

如何实现分布式限流

在前面的全部案例中,咱们只是基于Sentinel的基本使用和单机限流的使用,假若有这样一个场景,咱们如今把provider部署了10个集群,但愿调用这个服务的api的总的qps是100,意味着每一台机器的qps是10,理想状况下总的qps就是100.可是实际上因为负载均衡策略的流量分发并非很是均匀的,就会致使总的qps不足100时,就被限了。在这个场景中,仅仅依靠单机来实现整体流量的控制是有问题的。因此最好是能实现集群分布式限流。

架构图

要想使用集群流控功能,咱们须要在应用端配置动态规则源,并经过 Sentinel 控制台实时进行推送。以下图所示:

081911590257041

搭建token-server

image-20190819124250904

Jar包依赖

<dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-cluster-server-default</artifactId>
            <version>1.6.3</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-transport-simple-http</artifactId>
            <version>1.6.3</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-datasource-nacos</artifactId>
            <version>1.6.3</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.25</version>
        </dependency>

ClusterServer

public class ClusterServer {
    public static void main(String[] args) throws Exception {
        ClusterTokenServer tokenServer = new SentinelDefaultTokenServer();
        ClusterServerConfigManager.loadGlobalTransportConfig(new
                ServerTransportConfig().
                setIdleSeconds(600).setPort(9999));
        ClusterServerConfigManager.loadServerNamespaceSet(Collections.singleton("App-Lucas"));
                tokenServer.start();
    }
}

DataSourceInitFunc

public class DataSourceInitFunc implements InitFunc {
    //nacos 远程服务host
    private final String remoteAddress = "192.168.0.102";
    //nacos groupId
    private final String groupId = "SENTINEL_GROUP";
    //namespace不一样,限流规则也不一样
    private static final String FLOW_POSTFIX = "-flow-rules";

    @Override
    public void init() throws Exception {
        ClusterFlowRuleManager.setPropertySupplier(namespace -> {
            ReadableDataSource<String, List<FlowRule>> rds =
                    new NacosDataSource<>(remoteAddress, groupId,
                            namespace + FLOW_POSTFIX,
                            source -> JSON.parseObject(source, new
                                    TypeReference<List<FlowRule>>() {
                                    }));
            return rds.getProperty();
        });
    }
}

resource目录添加扩展点

/META-INF/services/com.alibaba.csp.sentinel.init.InitFunc = 自定义扩展点

添加log4j.properties文件

启动Sentinel dashboard

java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.6.3.jar

启动nacos以及增长配置

  1. 启动nacos服务: nohup sh startup.sh -m standalone &

  2. 增长限流配置

    [
        {
            "resource":"com.lucas.sentinel.api.SentinelService:sayHello(java.lang.String)",//限流资源
            "grade":1,//限流模式:qps
            "count":10,//限流总阈值
            "clusterMode":true,
            "clusterConfig":{
                "flowId":111111,//全局惟一ID
                "thresholdType":1,//阈值模式,全局阈值
                "fallbackToLocalWhenFail":true //clinet链接失败或者通讯失败时,是否退化到本地限流模式
            }
        }
    ]
  3. image-20190819124842161

配置jvm参数

配置以下jvm启动参数,链接到sentinel dashboard

-Dproject.name=App-Lucas -Dcsp.sentinel.dashboard.server=192.168.0.102:8080 -Dcsp.sentinel.log.use.pid=true

  • 服务启动以后,在 u s e r . h o m e user.home /logs/csp/ 能够找到sentinel-record.log.pid*.date文件,若是看到日志文件中获取到了远程服务的信息,说明token-server启动成功了

Dubbo接入分布式限流

jar包依赖

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <artifactId>sentinel-api</artifactId>
            <groupId>com.lucas.sentinel</groupId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo</artifactId>
            <version>2.7.2</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.25</version>
        </dependency>
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-framework</artifactId>
            <version>4.0.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-recipes</artifactId>
            <version>4.0.0</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-dubbo-adapter</artifactId>
            <version>1.6.3</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-transport-simple-http</artifactId>
            <version>1.6.3</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-dubbo-adapter</artifactId>
            <version>1.6.3</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-cluster-client-default</artifactId>
            <version>1.6.3</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-transport-simple-http</artifactId>
            <version>1.6.3</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-datasource-nacos</artifactId>
            <version>1.6.3</version>
        </dependency>

增长扩展点

扩展点须要在resources/META-INF/services/增长扩展的配置

com.alibaba.csp.sentinel.init.InitFunc = 自定义扩展点

public class DataSourceInitFunc implements InitFunc {
    private static final String CLUSTER_SERVER_HOST = "localhost";//token-server的ip
    private static final int CLUSTER_SERVER_PORT = 8999;//token-server 端口
    private static final int REQUEST_TIME_OUT = 200000; //请求超时时间
    private static final String APP_NAME = "App-Lucas";
    private static final String REMOTE_ADDRESS = "192.168.0.102"; //nacos服务的ip
    private static final String GROUP_ID = "SENTINEL_GROUP";//group id
    private static final String FLOW_POSTFIX = "-flow-rules";//限流规则后缀

    @Override
    public void init() throws Exception {
        loadClusterClientConfig();
        registerClusterFlowRuleProperty();
    }

    //经过硬编码的方式,配置链接到token-server服务的地址,{这种在实际使用过程当中不建议,后续能够基于动态配置源改造}
    public static void loadClusterClientConfig() {
        ClusterClientAssignConfig assignConfig = new
                ClusterClientAssignConfig();
        assignConfig.setServerHost(CLUSTER_SERVER_HOST);
        assignConfig.setServerPort(CLUSTER_SERVER_PORT);
        ClusterClientConfigManager.applyNewAssignConfig(assignConfig);
        ClusterClientConfig clientConfig = new ClusterClientConfig();
        clientConfig.setRequestTimeout(REQUEST_TIME_OUT); //token-client请求token-server获取令牌的超时时间
        ClusterClientConfigManager.applyNewConfig(clientConfig);
    }

    /** * 注册动态规则Property * 当client与Server链接中断,退化为本地限流时须要用到的该规则 * 该配置为必选项,客户端会从nacos上加载限流规则,请求tokenserver时,会戴上要check的规则id * {这里的动态数据源,咱们稍后会专门讲到} */
    private static void registerClusterFlowRuleProperty() {
        // 使用 Nacos 数据源做为配置中心,须要在 REMOTE_ADDRESS 上启动一个 Nacos 的服务
        ReadableDataSource<String, List<FlowRule>> ds = new
                NacosDataSource<List<FlowRule>>(REMOTE_ADDRESS, GROUP_ID, APP_NAME + FLOW_POSTFIX,
                source -> JSON.parseObject(source, new
                        TypeReference<List<FlowRule>>() {
                        }));
        // 为集群客户端注册动态规则源
        FlowRuleManager.register2Property(ds.getProperty());
    }
}

配置jvm参数

这里的project-name要包含在token-server中配置的namespace中,token server 会根据客户端对应的 namespace(默认为 project.name 定义的应用名)下的链接数来

计算总的阈值

-Dproject.name=App-Lucas -Dcsp.sentinel.dashboard.server=192.168.0.102:8080 -Dcsp.sentinel.log.use.pid=true

  • 服务启动以后,在$user.home​$/logs/csp/ 能够找到sentinel-record.log.pid*.date文件,若是看到日

志文件中获取到了token-server的信息,说明链接成功了

演示集群限流

所谓集群限流,就是多个服务节点使用同一个限流规则。从而对多个节点的总流量进行限制,添加一个sentinel-server。同时运行两个程序

image-20190819135143652

-Dserver.port=8082 -Dproject.name=App-Lucas -Dcsp.sentinel.dashboard.server=192.168.0.102:8080 -Dcsp.sentinel.log.use.pid=true -Ddubbo.protocol.port=20881
-Dserver.port=8081 -Dproject.name=App-Lucas -Dcsp.sentinel.dashboard.server=192.168.0.102:8080 -Dcsp.sentinel.log.use.pid=true

压测

使用jemeter建立1000个线程,进行压测,而后关注sentinel dashboard的变化。

image-20190819135351799

image-20190819135410759

咱们改一下nacos中心的配置,qps改成20,在压测一次

image-20190819135524846

image-20190819135549915

image-20190819135657336

图上出现了21,多是统计的偏差。

工程代码地址