Sentinel

目录​​​​​​​

一、服务限流的作用及实现

二、算法

1.计数器算法

2.滑动窗口算法

3.令牌桶限流算法

4.漏桶限流算法

三、服务熔断与降级

1.触发情况

2.服务熔断

四、分布式限流框架Sentinel

1.介绍

2.Sentinel的特性

3.Sentinel的组成

4.Sentinel Dashboard的部署

五、Sentinel的基本应用

1.Sentinel实现限流

2.资源的定义方式

3.Sentinel资源保护规则

4.基于并发数和QPS的流控规则

5.QPS流量控制行为

6.调用关系流量策略

7.Sentinel实现服务熔断

六、Spring Cloud集成Sentinel

1.Sentinel接入Spring Cloud

2.基于Sentinel Dashboard来实现流控配置

3.自定义URL限流异常

七、Sentinel集成Nacos实现动态流控规则

八、Sentinel Dashboard集成Nacos实现规则同步

九、Sentinel热点限流

1.热点参数限流的使用

2.@SentinelResource热点参数限流

3.热点参数规则说明


一、服务限流的作用及实现

限流的主要目的是通过限制并发访问数或者限制一个时间窗口内允许处理的请求数来保护系统,一旦达到限制数量则对当前请求进行处理采取对应的拒绝策略,比如跳转到错误页面拒绝请求、进入排队系统、降级等。从本质上来说,限流的主要作用是损失一部分用户的可用性,为大部分用户提供稳定可靠的服务。

二、算法

要实现限流,最重要的就是限流的算法,以下为常用的算法

1.计数器算法

在指定周期内累加访问次数,当访问次数达到设定的阈值,触发限流策略,当进入下一个周期时进行访问次数的清零。

缺点:这种方法存在一个临界问题,比如第一周期的最后几秒,和第二周期的开始几秒访问量突然高出阈值,这种情况下由于单个周期内未超出阈值,所以解决不了。

2.滑动窗口算法

为了解决计数器算法的临界问题,引入了滑动窗口算法。滑动窗口算法的原理是在固定窗口中分割出多个小时间窗口,分别在每个小时间窗口内记录访问次数,然后根据时间将窗口往前滑动并删除过期的小时间窗口。最终只需统计滑动固定窗口范围内的所有小时间窗口总的计数即可。

Sentinel就是采用滑动窗口算法来实现限流的

3.令牌桶限流算法

对于每一个请求,都需要从令牌桶中获取一个令牌,如果没有获得令牌,则需要触发限流策略。系统会以一个恒定的速度往固定容量的令牌桶中放入令牌,假设令牌生成速度是每秒10个,也就等同于QPS=10,此时在请求获得令牌时会有三种情况:

  • 请求速度大于令牌生成速度:那么令牌会很快取完,后续在进来的请求会被限流
  • 请求速度等于令牌生成速度:此时流量处于平稳状态
  • 请求速度小于令牌生成速度:说明此时系统的并发数并不高,请求能被正常处理,并且令牌桶会被填满,这样令牌桶能处理突发流量,这就是令牌桶的特性。

4.漏桶限流算法

在漏桶算法内部维护一个容器,这个容器会以恒定的速度出水,不管上面的水流速度多快,漏桶滴水的流出速度始终保持不变。消息中间件就使用了漏桶限流的思想,不管生产者的请求量多大,消息的处理能力取决宇消费者。当请求速度大于漏桶流出水滴的速度,就会触发限流

三、服务熔断与降级

1.触发情况

在微服务架构中,由于服务拆分粒度较细,会出现请求链路较长的情况。用户发起一个请求,需要调用多个微服务才能完成。如果某个服务因为网络延迟或者请求超时等原因不可用时,就会导致当前请求阻塞。如果高并发场景,可能出现请求堆积从而导致出现雪崩效应

2.服务熔断

服务熔断是指当某个服务提供者不可用时,为了防止整个系统出现雪崩效应,暂时将出现故障的服务隔离出来,断绝与外部接口的联系,当触发熔断后,后续一段时间该服务调用者的请求会直接失败,直到服务恢复正常。

服务降级需要有一个参考指标,一般有如下几个方案:

  • 平均响应时间:比如1s内持续进入5个请求,对应时刻的平均响应时间均超过阈值,会触发熔断
  • 异常比例:当某个方法每秒调用所获得的异常总数的比例超过设定的阈值时,会触发熔断
  • 异常数量:某个方法在指定时间窗口内获得的异常总数超过阈值会触发熔断

四、分布式限流框架Sentinel

1.介绍

Sentinel是面向分布式服务架构的轻量级流量控制组件,主要以流量为切入点,从限流、减流整形、服务降级、系统负载保护等多个维度来帮助我们保障服务的稳定性。

2.Sentinel的特性

  • 丰富的应用场景:几乎涵盖所有的应用场景,例如秒杀、消息削峰填谷、集群流量控制等
  • 实时监控:开发者可以在控制台中看到介入应用的单台机器秒级数据,甚至500台以下规模的集群汇总情况
  • 开源生态支持:Sentinel提供开箱即用的与其他开源框架的整合,例如与Spring Cloud、Dubbo的整合,开发者只需要引入相应的依赖并进行简单的配置即可进入Sentinel
  • SPI扩展点:开发者可以通过扩展点来定制化限流规则,动态数据源适配等需求

3.Sentinel的组成

Sentinel分为两个部分:

  • 核心库(Java客户端):不依赖任何框架,能够运行于所有Java运行环境,同时对Dubbo、Spring Cloud等框架也有较好的支持
  • 控制台(Dashboard):基于Spring Boot开发,打包后可以直接运行,不需要额外的Tomcat等容器

4.Sentinel Dashboard的部署

Sentinel提供一个轻量级的开源控制台,它支持机器发现,以及健康情况管理、监控(单机和集群)、规则管理和推送的功能

Sentinel Dashboard安装步骤如下:

  • 在GitHub中Sentinel的源码仓库中,下载源码通过mvn clean package自己构建
  • 通过以下命令启动控制台

java -Dserver.port=7777 -Dcsp.sentinel.dashboard.server=localhost:7777

-Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar

注释:

-Dserver.port:指定Sentinel控制台的访问端口,默认是8080

-Dcsp.sentinel.dashboard.server:指定Sentinel Dashboard控制台的IP地址和端口,这里设置的目的是把自己的限流数据暴露到监控平台

-Dproject.name:设置项目名称

五、Sentinel的基本应用

使用Sentinel的核心库实现限流,主要分为以下几个步骤:

  • 定义资源:需要通过限流保护的元素,比如一个方法
  • 定义限流规则
  • 检验规则是否生效

1.Sentinel实现限流

  • 引入Sentinel核心库:sentinel-core
  • 定义一个普通的业务方法
private static void doSomething(){
    try(Entry entry=SphU.entry("doSomething")) {
     //业务逻辑
 } catch(BlockException ex){
   //处理被流控的逻辑
 }
}

注释:在doSomething方法中,通过使用Sentinel中的SphU.entry("doSomething")定义一个资源来实现流控的逻辑,它表示当请求进入doSomething方法时,需要进行限流判断。如果抛出BlockException异常,则表示触发了限流。

  • 针对该保护的资源定义限流规则
private static void initFlowRules(){
    List<FlowRule> rules=new ArrayList<>();
    FlowRule rule=new FlowRule();
    rule.setResource("doSomething");
    rule.setGrade("RuleConstant.FLOW_GRADE_QPS");
    rule.setCount(20);
    rules.add(rule);
    FlowRuleManager.loadRules(rules);
}

注释:针对资源doSomething,通过initFlowRules设置限流规则,其中参数的含义如下:

Grade:限流阈值类型,QPS模式(1)或并发线程数模式(0)

count:限流阈值

resource:设置需要保护的资源,这个资源的名称必须和SphU.entry中使用的名称保持一致。

上述代码的含义是,针对doSomething方法,每秒最多允许通过20个请求,也就是QPS为 20

  • 检验规则是否生效,可以写main方法进行验证。

2.资源的定义方式

  • 方式一:通过抛出异常的方式来定义一个资源
 try(Entry entry=SphU.entry("resourceName")) {
     //被保护的业务逻辑
 } catch(BlockException e){
   //被限流
 }

注释:当资源被限流之后,会抛出BlockException异常,这时我们需要捕获该异常进行限流后的逻辑。

其中,resourceName可以定义方法名称、接口名称或者其他唯一标识。

  • 方式二:通过返回布尔值的方式来定义资源
if(Sph0.entry("resourceName")){
    try{
       //被保护的业务
 }finally{
      Sph0.exit();
 }
}else{
    //资源访问被限制
}

注释:在这种方式中需要注意资源使用完之后要调用Sph0.exit(),否则会导致调用链记录异常,抛出ErrorEntryFreeException异常

  • 方式三:使用@SentinelResource注解的方式来定义资源
@SentinelResource(value="resourceName",blockHandler="blockHandlerForUser")
public User getUserById(String id){
     //业务逻辑
}

public user blockHandlerForUser(String id,BlockException e){
    //被限流后的处理方式
}

注释:需要注意的是,blockHandler所配置的值blockHandlerForUser会在触发限流之后调用,这个方法的定义必须和原始方法getUserById的返回值、参数保持一致,而且需要增加BlockException参数

3.Sentinel资源保护规则

Sentinel支持多种保护规则:流量控制规则、熔断降级规则、系统保护规则、来源访问控制规则、热点参数规则

限流规则的定义,先通过FlowRule来定义限流规则,然后通过FlowRuleManager.loadRules来加载规则列表,完整的代码如下:

private void initFlowRules(){
    List<FlowRule> rules=new ArrayList<>();
    FlowRule rule=new FlowRule();
    rule.setResource("doSomething");
    rule.setCount(20);
    rule.setGrade("RuleConstant.FLOW_GRADE_QPS");
    rule.setLimitApp("default");
    rule.setStrategy(RuleConstant.STRATEGY_CHAIN);
    rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT);
    rule.setClusterMode(false);
    rules.add(rule);
    FlowRuleManager.loadRules(rules);
}

FlowRule部分属性的含义说明如下:

  • limitApp:是否需要针对调用来源进行限流,默认是default,即不区分调用来源。
  • strategy:调用关系限流策略--直接、链路、关联。
  • controlBehavior:流控行为,包括直接拒绝、排队等等、慢自动模式,默认是直接拒绝。
  • clusterMode:是否是集群限流,默认为否。

4.基于并发数和QPS的流控规则

Sentinel流量控制统计有两种类型,通过grade属性来控制:

  • 并发线程数(FLOW_GRADE_THREAD)
  • QPS(FLOW_GRADE_QPS)

并发线程数:统计当前请求的上下文线程数量,如果超出阈值,新的请求就会被拒绝

QPS:表示每秒的查询数,当QPS达到限流的阈值,就会触发限流策略

5.QPS流量控制行为

当QPS超过阈值时,就会触发流量控制行为,这种行为是通过controlBehavior来设置的,它包含:

  • 直接拒绝(RuleConstant.CONTROL_BEHAVIOR_DEFAULT):直接拒绝是默认的流量控制方式,也就是请求流量超出阈值时,直接抛出一个FlowException
  • Warm Up(RuleConstant.CONTROL_BEHAVIOR_WARM_UP):是一种冷启动(预热)方式。当流量突然增大时,让请求处理的数量逐步递增,并在一个预期之后达到允许处理请求的最大值
  • 匀速排队(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER):严格控制请求通过的间隔时间,也就是让请求以匀速通过,相当于漏桶限流算法
  • 冷启动+匀速排队(RuleConstant.CONTROL_BEHAVIOR_WARM_UP_RATE_LIMITER)

6.调用关系流量策略

调用关系包括调用方和被调用方,一个方法又可能会调用其他方法,形成一个调用链。所谓的调用关系流量策略就是根据不同的调用维度来触发流量控制。

  • 根据调用方限流:就是根据请求来源进行流量控制,我们需要设置limitApp属性来设置来源信息,它有三个选项:
  1. default:表示不区分调用者,也就是任务访问调用者的请求都会进行限流统计;
  2. {some_origin_name}:设置特定的调用者,只有这个调用者的请求才会进行流量统计和控制;
  3. other:表示针对除{some_origin_name}外的其他调用者进行流量控制

备注:由于同一个资源可以配置多条规则,如果多个规则的limitApp不一样,那么规则的生效顺序为:{some_origin_name}--other--default

  • 根据调用链路入口限流:

一个被限流保护的方法,可能来自不同的调用链路。可以设置某个调用链路的调用才会统计请求次数,它在一定程度上有点类似调用方限流

  • 具有关系的资源流量控制(关联流量控制):

当两个资源之间存在依赖关系或者资源争抢时,我们就说这两个资源存在关联。关联流量控制就是限制其中一个资源的执行流量。

7.Sentinel实现服务熔断

Sentinel实现服务熔断操作的配置和限流类似,不同之处在于限流采用的是FlowRule,而熔断中采用的是DegradeRule,配置代码如下:

private static void initDegradeRule(){
    List<DegradeRule> rules=new ArrayList<>();
    DegradeRule degradeRule=new DegradeRule();
    degradeRule.setResource("KEY");
    degradeRule.setCount(10);
    degradeRule.setGrade(RuleConstant.DEGRADE_GRADE_RT);
    degradeRule.setTimeWindow(10);
    degradeRule.setMinRequestAmount(5);
    degradeRule.setRtSlowRequestAmount(5);
    rules.add(degradeRule);
}

 属性说明如下:

  • grade:熔断策略,支持秒级RT、秒级异常比例、分钟级异常数。默认是秒级RT
  • timeWindow:熔断降级的时间窗口,单位为s。也就是触发熔断降级之后多长时间内自动恢复
  • rtSlowRequestAmount:在RT模式下,1s内持续多少个请求的平均RT超出阈值后触发熔断,默认是5
  • minRequestAmount:触发的异常熔断最小请求数,请求数小于该值时即使异常比例超出阈值也不会触发熔断,默认值是5

Sentinel提供三种熔断策略,对于不同策略,参数的含义也不同:

  • 平均响应时间(RuleConstant.DEGRADE_GRADE_RT):如果1s内持续进入5个请求,对应的平均响应时间都超过了阈值(count,单位为ms),那么在接下来的时间窗口(timeWindow,单位是s)内,对这个方法的调用都会自动熔断,抛出DegradeException异常。
  • 异常比例(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO):如果每秒资源数>=minRequestAmount(默认值为5),并且每秒的异常总数占总提供量的比例超过阈值count,则资源进入降级状态。同样在接下来的timeWindow内,对这个方法的调用会自动熔断。
  • 异常数(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT):当资源最近一分钟的异常数目超过阈值之后,会触发熔断。需要注意的是,如果timeWindow小于60s,则结束熔断状态后仍然可能再进入熔断状态。

六、Spring Cloud集成Sentinel

1.Sentinel接入Spring Cloud

  • 创建一个基于Spring Boot的项目,并集成Greenwich.SR2版本的Spring Cloud依赖
  • 添加Sentinel依赖包(spring-cloud-starter-alibaba-sentinel)
  • 创建一个REST接口,并通过@SentinelResource配置限流保护资源
@RestController
public class HelloController{
  @SentinelResource(value="hello",blockHandler="blockHandlerForUser")
  @GetMapping("/say")
  public User hello(){
     return "hello,Mic";
 }

  public user blockHandlerForUser(BlockException e){
    return "被限流了";
 }

}

在上述代码中,配置限流资源有几种情况:

1.Sentinel starter 在默认情况下会为所有的HTTP服务提供限流埋点,所以如果只想对HTTP服务进行限流,那么只需要添加依赖即可,不需要修改任何代码。

2.如果想要对特定的方法进行限流或者降级,则需要通过@SentinelResource注解来实现限流资源的定义

3.可以通过SphU.entry()方法来配置资源

  • 手动配置流控规则,可以借助Sentinel 的InitFunc SPI扩展接口来实现,只需要实现自己的InitFunc接口,并在init方法中编写规则加载的逻辑即可
public class FlowRuleInitFunc implements InitFunc{
    @Override
    public void init()throws Exception {
       List<FlowRule> rules=new ArrayList<>();
       FlowRule rule=new FlowRule();
       rule.setCount(1);
       rule.setResource("hello");
       rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
       rule.setLimitApp("default");
       rules.add(rule);
       FlowRuleManager.loadRules(rules);
 }
}

注释:SPI是扩展点机制,如果需要被Sentinel加载,那么还要在resource目录下创建META-INF/services/com.alibaba/csp.sentinel.init.InitFunc文件,文件内容就是自定义扩展点的全路径。 例:com.gupaoedu.book.springcloud.sentinel.springcloudsentinelsample.FlowRuleInitFunc

按照上述配置好之后,在初次访问任意资源的时候,Sentinel就会自动加载hello资源的流控规则。

  • 启动服务后,访问http://localhost:8080/say方法,当访问频率超过设定阈值时,就会触发限流

备注:流控规则也可以通过Sentinel Dashboard来进行配置

2.基于Sentinel Dashboard来实现流控配置

基于Sentinel Dashboard来配置流控规则,可以实现流控规则的动态配置,步骤如下:

  • 启动Sentinel Dashboard
  • 在application.yml中增加如下配置

spring:

   application:

       name:spring-cloud-sentinel-sample

   cloud:

       sentinel:

           transport:

              dashboard: 192.168.216.128:7777

注释:spring.cloud.sentinel.transport.dashboard指向的是Sentinel Dashboard的服务器地址,可以实现流控数据的监控和流控规则的分发

  • 提供一个REST接口,不添加任何埋点,就会对所有的HTTP请求进行限流
  • 启动后,由于没有配置流控规则,所以不存在限流行为

至此Spring Cloud集成Sentinel就配置完了,接下来,进入Sentinel Dashboard来实现流控规则的配置:

  • 访问http://192.168.216.128:7777 进入Sentinel Dashboard
  • 进入spring.application.name 对应的菜单,访问“簇点链路”,在该列表下可以看到上步骤REST接口里的资源名称
  • 针对上文资源名称,点击操作列的流控按钮设置流控规则
  • 在这里新增规则中的所有配置信息,实际就是FlowRule中对应的属性配置

3.自定义URL限流异常

  • 实际开发中,可以通过自定义限流异常 来修改触发限流之后的返回结果形式,实现UrlBlockHandler接口,并且重写blocked方法
  • 还有一种场景,当触发限流后直接跳转到一个降级页面,可以通过下面这个配置来实现

spring.cloud.sentinel.servlet.block-page={url}

七、Sentinel集成Nacos实现动态流控规则

触发情况:基于Sentinel Dashboard所配置的流控规则,都保存在内存中,一旦应用重启,这些规则都会被清除。为了解决这个问题,Sentinel提供了动态数据源支持,例如集成Nacos

  • 添加Nacos数据源的依赖包(sentinel-datasource-nacos)
  • 创建一个REST接口,用于测试
  • 在application.yml中添加数据源配置,如下

spring:

   application:

       name:spring-cloud-sentinel-dynamic

   cloud:

       sentinel:

           transport:

              dashboard:  192.168.216.128:7777

           datasource:

               -   nacos:

                       server-addr:192.168.216.128:8848

                       data-id:${spring.application.name}-sentinel

                       group-id:DEFAULT_GROUP

                       data-type: json

                       rule-type:  flow

注释:配置说明如下

datasource:目前支持redis、apollo、zk、file、nacos,选择什么类型的数据源就配置相应的key即可

data-id:可以设置成${spring.application.name},方便区分不同应用的配置

rule-type:表示数据源中规则属于哪种类型,如flow、degrade、param-flow、gw-flow等

data-type:指配置项的内容格式

  • 登录Nacos控制台,创建流控配置规则
  • 最后,登录Sentinel Dashboard,找到执行项目名称菜单下的“流控规则”,就可以看到在Nacos上所配置的流控规则已经被加载了
  • 当我们在Nacos的控制台上修改流控规则后,可以同步在Sentinel Dashboard上看到流控规则的变化

备注:通过FlowRuleManager.loadRules(List rules)手动加载流控规则,可以自动同步到Sentinel Dashboard上,在Nacos配置也可以自动同步到Sentinel Dashboard上。但是标准开发要求最好操控Sentinel Dashboard配置流控规则,禁忌在Nacos上直接操作

八、Sentinel Dashboard集成Nacos实现规则同步

在Sentinel Dashboard配置流控规则自动同步到Nacos上

  • 在GitHub中下载Sentinel Dashboard1.7.1的源码
  • 使用IDEA工具打开${Sentinel_home}/sentinel-dashboard工程
  • 在pom.xml中把sentinel-datasource-nacos依赖的<scope>注释掉
  • 修改resources/app/scripts/directives/sidebar/sidebar.html文件,将dashboard.flowV1改成dashboard.flow,也就是去掉V1(修改后会调用FlowControllerV2中的接口)
  • 在com.alibaba.csp.sentinel.dashboard.ruls中创建一个nacos包,并创建一个类用来加载外部化配置
  • 创建一个Nacos配置类NacosConfiguration
  • 创建一个常量类NacosConstants,分别表示默认的GROUP_ID和DTA_ID的后缀
  • 实现动态从Nacos配置中心获取流控规则
  • 创建一个流控规则发布类,在Sentinel Dashboard上修改完配置后,需要调用该发布方法将数据持久化到Nacos中

九、Sentinel热点限流

热点数据表示经常访问的数据,在有些场景中我们希望针对这些访问频次非常高的数据进行限流,比如针对一段时间内频繁访问的用户IP。Sentinel会根据请求的参数来判断哪些是热点参数,然后通过热点参数限流规则,将QPS超过设定阈值的请求阻塞。

1.热点参数限流的使用

  • 引入热点参数限流依赖包 sentinel-parameter-flow-control
  • 创建一个REST接口,并定义限流埋点,此处针对参数id配置热点限流规则
@RestController
public class ParamRuleController {
    
    private String resourceName="sayHello";

    @GetMapping("/hello")
    public String sayHello(@pathParam("id")String id,@PathParam("name")String name){
         Entry entry=null;
         try{
            entry=SphU.entry(resourceName,EntryType.IN,1,id);
            return "access success"; 
       }catch(BlockException e){
          e.printStackTrace();
          return "block";
       }finally{
           if(entry!=null) {
        entry.exit(); 
   } 
  }
 }
}

 注释:针对不同的热点参数,需要通过SphU.entry(resourceName,EntryType.IN,1,id)方法设置,其最后一个参数是一个数组,有多个热点参数时就按照次序依次传入,该配置表示后续会针对该参数进行热点限流

  • 针对上述资源sayHello设置热点参数限流规则,通过ParamFlowRuleManager.loadRules方法加载热点参数规则
@PostConstruct
public void initParamRule(){
   ParamFlowRule rule=new ParamFlowRule(resourceName);
   rule.setParamIdx(0);
   rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
   rule.setCount(1);
   ParamFlowRuleManager.loadRules(Collections.singletonList(rule));
}

2.@SentinelResource热点参数限流

如果是通过@SentinelResource注解来定义资源的,当注解所配置的方法上有参数时,Sentinel会把这些参数传入Sphu.entry(res,args)。会把这个参数作为热点参数进行限流

3.热点参数规则说明

热点参数规则是通过ParamFlowRule来配置的,它的大部分属性和FlowRule类似,下面针对ParamFlowRule特定的属性进行简单说明:

  • durationInSec:统计窗口时间长度,单位为秒
  • maxQueueingTimeMS:最长排队等待时长,只有当流控行为controlBehavior设置为匀速排队模式时生效
  • paramIdx:热点参数的索引,属于必填项,它对应的是SphU.entry(xxx,args)中的参数索引位置
  • paramFlowItemList:针对指定参数值单独设置限流阈值,不受count阈值的限制
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值