sentinel 整合dubbo限流


sentinel 整合dubbo限流

            

                      

                                     

相关依赖

              

        <!-- 服务注册与发现 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

        <!-- dubbo服务调用 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-dubbo</artifactId>
        </dependency>

        <!-- sentinel dubbo限流 -->
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-apache-dubbo-adapter</artifactId>
        </dependency>

        <!-- 数据上传到sentinel控制台 -->
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-transport-simple-http</artifactId>
        </dependency>

              

                      

                                     

限流原理

      

在服务端、消费端添加filter实现限流

                 

# dubbo/org.apache.dubbo.rpc.Filter
sentinel.dubbo.provider.filter=com.alibaba.csp.sentinel.adapter.dubbo.SentinelDubboProviderFilter
sentinel.dubbo.consumer.filter=com.alibaba.csp.sentinel.adapter.dubbo.SentinelDubboConsumerFilter
dubbo.application.context.name.filter=com.alibaba.csp.sentinel.adapter.dubbo.DubboAppContextFilter

           

SentinelDubboProviderFilter:服务端限流过滤器

@Activate(
    group = {"provider"}
)
public class SentinelDubboProviderFilter extends BaseSentinelDubboFilter {
    public SentinelDubboProviderFilter() {
        RecordLog.info("Sentinel Apache Dubbo provider filter initialized", new Object[0]);
    }

    String getMethodName(Invoker invoker, Invocation invocation, String prefix) {
        return DubboUtils.getMethodResourceName(invoker, invocation, prefix);
    }

    String getInterfaceName(Invoker invoker, String prefix) {
        return DubboUtils.getInterfaceName(invoker, prefix);
    }

    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        String origin = DubboAdapterGlobalConfig.getOriginParser().parse(invoker, invocation);
        if (null == origin) {
            origin = "";
        }

        Entry interfaceEntry = null;
        Entry methodEntry = null;
        String prefix = DubboAdapterGlobalConfig.getDubboProviderResNamePrefixKey();
        String interfaceResourceName = this.getInterfaceName(invoker, prefix);
        String methodResourceName = this.getMethodName(invoker, invocation, prefix);

        Result var10;
        try {
            ContextUtil.enter(methodResourceName, origin);
            interfaceEntry = SphU.entry(interfaceResourceName, 2, EntryType.IN);
            methodEntry = SphU.entry(methodResourceName, 2, EntryType.IN, invocation.getArguments());
            Result result = invoker.invoke(invocation);
            if (result.hasException()) {
                Tracer.traceEntry(result.getException(), interfaceEntry);
                Tracer.traceEntry(result.getException(), methodEntry);
            }

            var10 = result;
            return var10;
        } catch (BlockException var15) {
            var10 = DubboAdapterGlobalConfig.getProviderFallback().handle(invoker, invocation, var15);
        } catch (RpcException var16) {
            Tracer.traceEntry(var16, interfaceEntry);
            Tracer.traceEntry(var16, methodEntry);
            throw var16;
        } finally {
            if (methodEntry != null) {
                methodEntry.exit(1, invocation.getArguments());
            }

            if (interfaceEntry != null) {
                interfaceEntry.exit();
            }

            ContextUtil.exit();
        }

        return var10;
    }
}

           

SentinelDubboConsumerFilter:消费端限流过滤器

@Activate(
    group = {"consumer"}
)
public class SentinelDubboConsumerFilter extends BaseSentinelDubboFilter {
    public SentinelDubboConsumerFilter() {
        RecordLog.info("Sentinel Apache Dubbo consumer filter initialized", new Object[0]);
    }

    String getMethodName(Invoker invoker, Invocation invocation, String prefix) {
        return DubboUtils.getMethodResourceName(invoker, invocation, prefix);
    }

    String getInterfaceName(Invoker invoker, String prefix) {
        return DubboUtils.getInterfaceName(invoker, prefix);
    }

    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        InvokeMode invokeMode = RpcUtils.getInvokeMode(invoker.getUrl(), invocation);
        return InvokeMode.SYNC == invokeMode ? this.syncInvoke(invoker, invocation) : this.asyncInvoke(invoker, invocation);
    }

    private Result syncInvoke(Invoker<?> invoker, Invocation invocation) {
        Entry interfaceEntry = null;
        Entry methodEntry = null;
        String prefix = DubboAdapterGlobalConfig.getDubboConsumerResNamePrefixKey();
        String interfaceResourceName = this.getInterfaceName(invoker, prefix);
        String methodResourceName = this.getMethodName(invoker, invocation, prefix);

        Result var9;
        try {
            interfaceEntry = SphU.entry(interfaceResourceName, 2, EntryType.OUT);
            methodEntry = SphU.entry(methodResourceName, 2, EntryType.OUT, invocation.getArguments());
            Result result = invoker.invoke(invocation);
            if (result.hasException()) {
                Tracer.traceEntry(result.getException(), interfaceEntry);
                Tracer.traceEntry(result.getException(), methodEntry);
            }

            var9 = result;
            return var9;
        } catch (BlockException var14) {
            var9 = DubboAdapterGlobalConfig.getConsumerFallback().handle(invoker, invocation, var14);
        } catch (RpcException var15) {
            Tracer.traceEntry(var15, interfaceEntry);
            Tracer.traceEntry(var15, methodEntry);
            throw var15;
        } finally {
            if (methodEntry != null) {
                methodEntry.exit(1, invocation.getArguments());
            }

            if (interfaceEntry != null) {
                interfaceEntry.exit();
            }

        }

        return var9;
    }

    private Result asyncInvoke(Invoker<?> invoker, Invocation invocation) {
        LinkedList<SentinelDubboConsumerFilter.EntryHolder> queue = new LinkedList();
        String prefix = DubboAdapterGlobalConfig.getDubboConsumerResNamePrefixKey();
        String interfaceResourceName = this.getInterfaceName(invoker, prefix);
        String methodResourceName = this.getMethodName(invoker, invocation, prefix);

        try {
            queue.push(new SentinelDubboConsumerFilter.EntryHolder(SphU.asyncEntry(interfaceResourceName, 2, EntryType.OUT), (Object[])null));
            queue.push(new SentinelDubboConsumerFilter.EntryHolder(SphU.asyncEntry(methodResourceName, 2, EntryType.OUT, 1, invocation.getArguments()), invocation.getArguments()));
            Result result = invoker.invoke(invocation);
            result.whenCompleteWithContext((r, throwable) -> {
                Throwable error = throwable;
                if (throwable == null) {
                    error = (Throwable)Optional.ofNullable(r).map(Result::getException).orElse((Object)null);
                }

                while(!queue.isEmpty()) {
                    SentinelDubboConsumerFilter.EntryHolder holder = (SentinelDubboConsumerFilter.EntryHolder)queue.pop();
                    Tracer.traceEntry(error, holder.entry);
                    this.exitEntry(holder);
                }

            });
            return result;
        } catch (BlockException var8) {
            while(!queue.isEmpty()) {
                this.exitEntry((SentinelDubboConsumerFilter.EntryHolder)queue.pop());
            }

            return DubboAdapterGlobalConfig.getConsumerFallback().handle(invoker, invocation, var8);
        }
    }

    private void exitEntry(SentinelDubboConsumerFilter.EntryHolder holder) {
        if (holder.params != null) {
            holder.entry.exit(1, holder.params);
        } else {
            holder.entry.exit();
        }

    }

    static class EntryHolder {
        private final Entry entry;
        private final Object[] params;

        public EntryHolder(Entry entry, Object[] params) {
            this.entry = entry;
            this.params = params;
        }
    }
}

          

BaseSeninelDubboFilter

public abstract class BaseSentinelDubboFilter implements Filter {
    public BaseSentinelDubboFilter() {
    }

    abstract String getMethodName(Invoker var1, Invocation var2, String var3);

    abstract String getInterfaceName(Invoker var1, String var2);
}

          

Filter

@SPI
public interface Filter {
    Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException;

    public interface Listener {
        void onResponse(Result appResponse, Invoker<?> invoker, Invocation invocation);

        void onError(Throwable t, Invoker<?> invoker, Invocation invocation);
    }
}

            

           

                                     

使用示例

     

**********

服务端

        

                                  

              

application.yml

spring:
  application:
    name: nacos-provider
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848

dubbo:
  #registry:
  # address: spring-cloud://localhost:8848
  protocol:
    name: dubbo
    port: 20880

                   

HelloService

public interface HelloService {

    String hello();
}

           

HelloServiceImpl

@DubboService
public class HelloServiceImpl implements HelloService {
 
    @Override
    public String hello() {
        return "hello";
    }
}

              

DemoApplication

@EnableDubbo  //开启dubbo
@SpringBootApplication
public class DemoApplication {
 
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
 
}

             

**********

消费端

     

                                  

        

application.yml

spring:
  application:
    name: nacos-consuemr
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848

dubbo:
  protocol:
    name: dubbo
    port: 20881

server:
  port: 8081

          

HelloService

public interface HelloService {

    String hello();
}

        

HelloController

@RestController
public class HelloController {
 
    @DubboReference
    private HelloService helloService;
 
    @RequestMapping("/hello")
    public String hello(){
        System.out.println(helloService.hello());
 
        return "success";
    }
}

          

DemoApplication

@EnableDubbo
@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

}

                

**********

本地限流配置

     

                                  

        

CustomFlowRule

public class CustomFlowRule implements InitFunc {

    @Override
    public void init() throws Exception {
        List<FlowRule> flowRules = new ArrayList<>();

        FlowRule flowRule = new FlowRule();
        flowRule.setResource("com.example.demo.service.HelloService");  //限制接口,对所有的接口方法限流(加总限流)
        //flowRule.setResource("com.example.demo.service.HelloService:hello()");
                   //单个接口方法限流
        //flowRule.setResource("com.example.demo.service.HelloService:hello(java.lang.String)");
                   //单个接口方法限流,方法有参数
        flowRule.setCount(1);
        flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        flowRule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT);
        flowRules.add(flowRule);

        FlowRuleManager.loadRules(flowRules);
    }
}

           

META-INF/services/com.alibaba.csp.sentinel.init.InitFunc

com.example.demo.rule.CustomFlowRule

          

jmeter 测试

         

           

         

         

2022-03-31 13:13:34.076  INFO 3296 --- [           main] o.a.d.config.bootstrap.DubboBootstrap    :  [DUBBO] DubboBootstrap has started., dubbo version: 2.7.8, current host: 192.168.5.12
2022-03-31 13:13:34.077  INFO 3296 --- [           main] com.example.demo.DemoApplication         : Started DemoApplication in 3.577 seconds (JVM running for 4.167)
2022-03-31 13:13:35.001  WARN 3296 --- [client.listener] a.c.d.m.r.DubboServiceMetadataRepository : Current application will subscribe all services(size:2) in registry, a lot of memory and CPU cycles may be used, thus it's strongly recommend you using the externalized property 'dubbo.cloud.subscribed-services' to specify the services
2022-03-31 13:13:39.904  INFO 3296 --- [nio-8081-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2022-03-31 13:13:39.904  INFO 3296 --- [nio-8081-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2022-03-31 13:13:39.920  INFO 3296 --- [nio-8081-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 16 ms
hello
2022-03-31 13:13:39.980 ERROR 3296 --- [nio-8081-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.RuntimeException: SentinelBlockException: FlowException] with root cause

java.lang.RuntimeException: SentinelBlockException: FlowException
	at com.alibaba.csp.sentinel.slots.block.BlockException.block(BlockException:0) ~[sentinel-core-1.8.0.jar:1.8.0]

2022-03-31 13:13:40.000 ERROR 3296 --- [nio-8081-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.RuntimeException: SentinelBlockException: FlowException] with root cause

java.lang.RuntimeException: SentinelBlockException: FlowException
	at com.alibaba.csp.sentinel.slots.block.BlockException.block(BlockException:0) ~[sentinel-core-1.8.0.jar:1.8.0]

2022-03-31 13:13:40.005 ERROR 3296 --- [nio-8081-exec-3] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.RuntimeException: SentinelBlockException: FlowException] with root cause

java.lang.RuntimeException: SentinelBlockException: FlowException
	at com.alibaba.csp.sentinel.slots.block.BlockException.block(BlockException:0) ~[sentinel-core-1.8.0.jar:1.8.0]

2022-03-31 13:13:40.010 ERROR 3296 --- [nio-8081-exec-4] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.RuntimeException: SentinelBlockException: FlowException] with root cause

java.lang.RuntimeException: SentinelBlockException: FlowException
	at com.alibaba.csp.sentinel.slots.block.BlockException.block(BlockException:0) ~[sentinel-core-1.8.0.jar:1.8.0]

2022-03-31 13:13:40.015 ERROR 3296 --- [nio-8081-exec-5] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.RuntimeException: SentinelBlockException: FlowException] with root cause

java.lang.RuntimeException: SentinelBlockException: FlowException
	at com.alibaba.csp.sentinel.slots.block.BlockException.block(BlockException:0) ~[sentinel-core-1.8.0.jar:1.8.0]

2022-03-31 13:13:40.019 ERROR 3296 --- [nio-8081-exec-6] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.RuntimeException: SentinelBlockException: FlowException] with root cause

java.lang.RuntimeException: SentinelBlockException: FlowException
	at com.alibaba.csp.sentinel.slots.block.BlockException.block(BlockException:0) ~[sentinel-core-1.8.0.jar:1.8.0]

2022-03-31 13:13:40.024 ERROR 3296 --- [nio-8081-exec-7] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.RuntimeException: SentinelBlockException: FlowException] with root cause

java.lang.RuntimeException: SentinelBlockException: FlowException
	at com.alibaba.csp.sentinel.slots.block.BlockException.block(BlockException:0) ~[sentinel-core-1.8.0.jar:1.8.0]

2022-03-31 13:13:40.028 ERROR 3296 --- [nio-8081-exec-8] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.RuntimeException: SentinelBlockException: FlowException] with root cause

java.lang.RuntimeException: SentinelBlockException: FlowException
	at com.alibaba.csp.sentinel.slots.block.BlockException.block(BlockException:0) ~[sentinel-core-1.8.0.jar:1.8.0]

2022-03-31 13:13:40.032 ERROR 3296 --- [nio-8081-exec-9] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.RuntimeException: SentinelBlockException: FlowException] with root cause

java.lang.RuntimeException: SentinelBlockException: FlowException
	at com.alibaba.csp.sentinel.slots.block.BlockException.block(BlockException:0) ~[sentinel-core-1.8.0.jar:1.8.0]

               

**********

控制台限流配置

    

启动sentinel dashboard

java -Dserver.port=8000 -Dcsp.sentinel.dashboard.server=localhost:8000 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar
 
参数说明:
-Dserver.port=8080:指定控制台启动端口
-Dcsp.sentinel.dashboard.server:指定控制台地址和端口
-Dproject.name=sentinel-dashboard:指定控制台项目名称

         

nacos-provider添加启动参数

-Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8000 -Dproject.name=nacos-provider

          

nacos-consumer添加启动参数

-Dserver.port=8081 -Dcsp.sentinel.dashboard.server=localhost:8000 -Dproject.name=nacos-consumer

           

sentinel控制台

         

         

             

jmeter 测试

         

         

           

           

 

2022-03-31 13:38:51.158  INFO 3815 --- [           main] com.example.demo.DemoApplication         : Started DemoApplication in 3.814 seconds (JVM running for 4.334)
2022-03-31 13:38:52.060  WARN 3815 --- [client.listener] a.c.d.m.r.DubboServiceMetadataRepository : Current application will subscribe all services(size:2) in registry, a lot of memory and CPU cycles may be used, thus it's strongly recommend you using the externalized property 'dubbo.cloud.subscribed-services' to specify the services
2022-03-31 13:38:58.567  INFO 3815 --- [erverWorker-4-1] o.a.d.r.t.netty4.NettyServerHandler      :  [DUBBO] The connection of /192.168.5.12:53422 -> /192.168.5.12:20881 is established., dubbo version: 2.7.8, current host: 192.168.5.12
2022-03-31 13:39:11.710  INFO 3815 --- [nio-8081-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2022-03-31 13:39:11.710  INFO 3815 --- [nio-8081-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2022-03-31 13:39:11.726  INFO 3815 --- [nio-8081-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 16 ms
hello
hello
2022-03-31 13:39:34.726 ERROR 3815 --- [nio-8081-exec-5] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.RuntimeException: SentinelBlockException: FlowException] with root cause

java.lang.RuntimeException: SentinelBlockException: FlowException
	at com.alibaba.csp.sentinel.slots.block.BlockException.block(BlockException:0) ~[sentinel-core-1.8.0.jar:1.8.0]

2022-03-31 13:39:34.732 ERROR 3815 --- [nio-8081-exec-6] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.RuntimeException: SentinelBlockException: FlowException] with root cause

java.lang.RuntimeException: SentinelBlockException: FlowException
	at com.alibaba.csp.sentinel.slots.block.BlockException.block(BlockException:0) ~[sentinel-core-1.8.0.jar:1.8.0]

2022-03-31 13:39:34.737 ERROR 3815 --- [nio-8081-exec-7] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.RuntimeException: SentinelBlockException: FlowException] with root cause

java.lang.RuntimeException: SentinelBlockException: FlowException
	at com.alibaba.csp.sentinel.slots.block.BlockException.block(BlockException:0) ~[sentinel-core-1.8.0.jar:1.8.0]

2022-03-31 13:39:34.742 ERROR 3815 --- [nio-8081-exec-8] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.RuntimeException: SentinelBlockException: FlowException] with root cause

java.lang.RuntimeException: SentinelBlockException: FlowException
	at com.alibaba.csp.sentinel.slots.block.BlockException.block(BlockException:0) ~[sentinel-core-1.8.0.jar:1.8.0]

2022-03-31 13:39:34.746 ERROR 3815 --- [nio-8081-exec-9] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.RuntimeException: SentinelBlockException: FlowException] with root cause

java.lang.RuntimeException: SentinelBlockException: FlowException
	at com.alibaba.csp.sentinel.slots.block.BlockException.block(BlockException:0) ~[sentinel-core-1.8.0.jar:1.8.0]

2022-03-31 13:39:34.751 ERROR 3815 --- [io-8081-exec-10] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.RuntimeException: SentinelBlockException: FlowException] with root cause

java.lang.RuntimeException: SentinelBlockException: FlowException
	at com.alibaba.csp.sentinel.slots.block.BlockException.block(BlockException:0) ~[sentinel-core-1.8.0.jar:1.8.0]

2022-03-31 13:39:34.755 ERROR 3815 --- [nio-8081-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.RuntimeException: SentinelBlockException: FlowException] with root cause

java.lang.RuntimeException: SentinelBlockException: FlowException
	at com.alibaba.csp.sentinel.slots.block.BlockException.block(BlockException:0) ~[sentinel-core-1.8.0.jar:1.8.0]

2022-03-31 13:39:34.759 ERROR 3815 --- [nio-8081-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.RuntimeException: SentinelBlockException: FlowException] with root cause

java.lang.RuntimeException: SentinelBlockException: FlowException
	at com.alibaba.csp.sentinel.slots.block.BlockException.block(BlockException:0) ~[sentinel-core-1.8.0.jar:1.8.0]

          

                  

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值