微服务、SpringCloud、Eureka、Ribbon、Nacos、Feign、Gateway笔记

区分Spring、SpringMVC、SpringBoot、SpringCloud

Spring

  • 广义: 指的全家桶(桶里装的是Spring顶级项目)
  • 侠义: 指的是Spring FrameWork(IOC、DI、AOP)

SpringMVC

SpringWeb层框架,底层基于Servlet

SpringBoot

  • 核心: 起步依赖(原理: maven依赖传递)
自动装配
  1. SpringBoot项目启动时会加载spring-boot-autoconfigure包下的MTATA-INF下的spring.factories文件

    import + selector选择器

  2. spring.factories文件配置了自动装配类的路径

  3. 不是所有的自动装配类都生效,是否生效取决于类似的方法上的条件注解@ConditionalOnClass(.class)

  4. 条件注解是否生效又取决于pom文件是否导入了响应的starter

SpringCloud

微服务中服务治理一整套解决方案,全家桶(erueka注册中心ribbon负载均衡feign远程调用gateway网关)

认识微服务

单体架构

将业务的所有功能集中在一个项目中开发,打包成一个包部署

优点
  • 架构简单
  • 部署成本低
缺点
  • 耦合度高

分布式架构

根据业务功能对系统进行拆分,每个业务模块作为单独项目开发,称为一个服务

优点
  • 降低服务耦合
  • 有利于服务升级扩展

微服务

微服务是一种经过良好架构设计的分布式架构方案

微服务架构特征

  • 单一职责: 微服务拆分力度更小,每一个服务都对应唯一的业务能力,做到单一职责,避免重复业务开发
  • 面向服务: 微服务对外暴露业务接口
  • 自治: 团队独立、技术独立、数据独立、部署独立

微服务结构

  • 服务集群
  • 注册中心: 拉取或注册服务信息
  • 配置中心: 拉取配置信息
  • 服务网关: 请求路由负载均衡

常见微服务技术对比

  • Dubbo
    • 注册中心: zookeeper、Redis
    • 服务远程调用: Dubbo协议
    • 配置中心: 无
    • 服务网关: 无
    • 服务监控和保护: dubbo-admin,服务弱
  • SpringCloud
    • 注册中心: Eureka、Consul
    • 服务远程调用: Feign(http协议)
    • 配置中心: SpringCloudConfig
    • 服务网关: SpringCloudGateway、Zuul
    • 服务监控和保护: Hystix
  • SpringCloudAlibaba
    • 注册中心: Nacos、Eureka
    • 服务远程调用: Dubbo、Feign
    • 配置中心: SpringCloudConfig、Nacos
    • 服务网关: SpringCloudGateway、Zuul
    • 服务监控和保护: Sentinel

微服务常见使用组合

  • SpringCloud + Feign: 使用SpringCLoud技术栈、服务接口采用Resuful风格、服务调用采用Feign方式
  • SpringCLoudAlibaba + Feign: 使用SpringCloudAlibaba技术栈、服务接口采用Restful风格、服务调用采用Feign方式
  • SpringCloudAlibaba + Dubbo: 使用SpringCloudAlibaba技术栈、服务接口采用Dubbo协议标准、服务调用采用Dubbo方式
  • Dubbo原始模式: 基于Dubbo老旧技术体系、服务接口采用Dubbo协议标准、服务调用采用Dubbo方式

微服务技术

  • Dubbo: 侧重于远程调用rpc框架,注册中心可以采用zookeeperredis服务治理相关功能不全
  • SpringCloud: 微服务中服务治理一整套解决方案,全家桶(erueka注册中心ribbon负载均衡feign远程调用gateway网关)
  • SpringCloudAlibaba: 阿里针对微服务开源一整套解决方案`

SpringCloud

微服务中服务治理一整套解决方案,全家桶(erueka注册中心ribbon负载均衡feign远程调用gateway网关),SpringCloud依赖于SpringBoot

SpringCloud集合了各种微服务功能组件,并基于SpringBoot实现了这些组件的自动装配,从而提供了良好的开箱即用体验

服务拆分

注意事项
  • 单一职责: 不同微服务,不要重复开发相同业务
  • 数据独立: 不要访问其他微服务的数据库
  • 面向服务: 将自己的业务暴露为接口,供其他微服务调用

消费者与提供者

  • 消费者: 一次业务中,被其他微服务调用的服务(提供接口给其他微服务)
  • 提供者: 一次业务中,调用其他微服务的服务(调用其他微服务提供的接口)
  • 一个服务既可以是消费者、也可以是提供者

RestTemplate

可以发送HTTP请求

  • getForObject: 能从该url中获取一个对象
  • postForObject: 能把一个对象转换为json发送给指定url
  • exchange(url, HttpMethod, HttpEntity, Boolean): 发送一次Http请求
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);  // 表示这是一个application/x-www-form-urlencoded格式数据
MultiValueMap body = new LinkedMultiValueMap();

// 放入数据
multiValueMap.add();
...
multiValueMap.add();

HttpEntity httpEntity = new HttpEntity(body, headers);
ResponseEntity<Boolean> exchange = new restTemplate.exchange(url, HttpMethod.POST, httpEntity, Boolean.class);

Eureka注册中心

作用

  • 消费者如何获取服务提供者具体信息?
    • 服务提供者启动时向eureka注册自己的信息
    • eureka保存这些信息
    • 消费者根据服务名称向eureka拉取提供者的信息
  • 有多个服务提供者时,消费者该怎么选择?
    • 服务消费者利用负载均衡算法,从服务列表中挑选一个
  • 消费者怎么感知服务提供者健康状态?
    • 服务提供者会每隔30秒EurekaServer发送心跳请求,报告健康状态
    • eureka会更新记录服务列表信息,心跳不正常会被剔除
    • 消费者就可以拉取到最新的信息

Eureka架构中的微服务角色

  • EurekaServer: 服务端,注册中心
    • 记录服务信息
    • 心跳监控
  • EurekClient: 客户端
    • Provider: 服务提供者
      • 注册自己的信息到EurekaServer
      • 每隔30秒EurekaServer发送心跳
    • Consumer: 服务消费者
      • 根据服务名称从EurekaServer拉取服务列表
      • 基于服务列表做负载均衡,选中一个微服务后发起远程调用

搭建EurekaServer

  1. 创建项目,引入spring-cloud-starter-netflix-eureka-server的依赖
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
  1. 编写启动类,添加@EnableEurekaServer注解
  2. 添加application.yml文件,编写以下配置
server:
  port: 10086
spring:
  application:
    name: eurekaserver
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka/

Eureka服务注册

  1. 引入eureka-client依赖
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

  1. application.yml中配置eureka地址
spring:
  application:
    name: userservice
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka/

服务发现

  1. 引入依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
  1. 修改url路径,用服务名代替ip、端口
  2. 在启动类上添加负载均衡注解@LoadBalanced

Ribbon负载均衡

工作流程

  1. 发起带有服务的请求
  2. 拉取对应服务
  3. 返回该服务的列表
  4. 轮询到合适接口

负载均衡策略

Ribbon的负载均衡规则是一个叫做IRule的接口来定义的,每一个子接口都是一种规则

在这里插入图片描述

  • 方式1: 代码方式,在启动类中定义一个新的IRule
@Bean
public IRule randomRule() {
    return new RandomRule();
}
  • 方式2: 配置文件方式
userservice: 
  ribbon: 
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

Ribbon和Nginx的区别

  • Eureka是客户端负载均衡器: 消费者从Eureka拉取提供者的实例信息,经过Ribbon的负载均衡直接发起调用,消费者是知道调用了那个提供者的
  • Nginx是服务端负载均衡器: 负载均衡能力是在服务端完成的,消费者不知道调用了哪个提供者

饥饿加载

Ribbon默认采用懒加载,第一次访问的时候才会去创建LoadBalanceClient,请求时间会很长。

饥饿加载会在项目启动时就加载,降低第一次访问的耗时

ribbon: 
  eager-load: 
    enabled: true # 开启饥饿加载
    clients: userservice  # 指定对userservice这个服务饥饿加载

Nacos

Nacos注册中心

既可以作为注册中心、也可由作为配置中心

使用步骤
  1. 导入依赖
<!-- 父工程 -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-alibaba-dependencies</artifactId>
    <version>2.2.6.RELEASE</version>
    <type>pom</type>
    <scope>import</scope>
</dependency>

<!-- 子工程 -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    <version>2.2.5.RELEASE</version>
</dependency>
  1. 配置类
spring: 
  cloud:
      nacos:
        server-addr: localhost:8848
Nacos服务分级存储模型

服务 -> 集群 -> 实例

使用步骤(在注册中心的基础上)
  1. 修改配置,添加所处集群
# service1
spring: 
  cloud:
      nacos:
        discovery:
          cluster-name: HN  # 集群1 HN
# service2
spring: 
  cloud:
      nacos:
        discovery:
          cluster-name: HZ  # 集群2 HZ
  1. 测试
    此时还是轮询
  2. 如果需要优先同集群访问
userservice:
  ribbon:
    NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule # 负载均衡策略,优先选择同级群
根据权重负载均衡
  • 设置实例的权重(0~1),权重越小,访问的几率越小
  • 同级群内的多个实例,权重越高被访问的频率越高
  • 权重设置为0则完全不会被访问
环境隔离 - namespace

Nacos中服务存储和数据存储的最外层都是一个名为namespace的东西,用来做最外层隔离

namespace -> group -> service/data

  • namespace用来做环境隔离
  • 每隔namespace都有唯一id
  • 不同namespace下的服务不可见
使用方法
spring: 
  cloud:
    nacos:
      discovery:
        namespace: 2e32b562-a44c-4750-a2c6-e6539b06438d # 命名空间id
Nacos和Eureka区别
  • 共同点
    • 都支持服务注册和服务拉取
    • 都支持服务提供者心跳方式做健康检测
  • 不同点
    • Nacos支持服务端主动检测提供者状态,临时实例采用心跳模式,非临时实例采用主动检测模式a
    • 临时实例心跳不正常会被剔除,非临时实例不会被剔除
    • Nacos支持服务列表变更的消息推送模式,服务列表更新更及时
    • Nacos集群默认采用AP方式,当集群中存在非临时实例时,采用CP方式;Eureka采用AP方式
Nacos注册中心细节分析
  • 设置临时实例
spring:
  cloud:
    nacos:
      discovery:
        ephemeral: false  # 是否是临时实例

Nacos配置管理

Nacos统一配置管理步骤
  1. Nacos中添加配置文件
# DataId完整格式为${prefix}-${spring.profiles.active}.${file-extension}
# prefix默认为spring.application.name的值,也可以通过配置项spring.cloud.nacos.confg.prefix来配置
# spring.profiles.active为当前环境对应的profile
# file-extension为配置内容的数据格式,可以通过配置项spring.cloud.nacos.config.file.extension来配置,目前只支持properties和yum类型
  1. Nacos中拉取配置
  2. 引入nacos-config依赖
<dependency>
  <groupId>com.alibaba.cloud</groupId>
  <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
  1. 添加bootstrap.yaml(引入bootstrap.yml是在引用application.yml之前 )
spring:
application:
  name: userservice  # 服务名称
cloud:
  nacos:
    server-addr: localhost:8848 # nacos地址
    discovery:
      cluster-name: HN
    config:
      file-extension: yaml  # 文件后缀名
      prefix: aaa 
profiles:
  active: dev # 开发环境
实现热更新
  • 方式一: 在使用远程配置的类上加@RefreshScope注解
  • 方式二: 使用@ConfigurationProperties注解代替@Value注解(新建一个类,将远程配置的变量作为成员变量,使用getter方法取出)
应用场景
  • 应用级别配置与业务相关的配置
  • 系统级别配置例如端口号
  • 应用名称等不适合被nacos管理
多环境配置共享

配置了服务名和profiles.active时,可以同时读取到服务名.ymal服务名-profile.ymal

多环境配置的优先级

服务名-profile.ymal > 服务名称.ymal > 本地配置

Nacos集群

配置步骤
  1. 初始化数据库: 官方推荐的最佳实践是使用带有主从的高可用数据库集群(手头服务器不够,暂时不作此配置)
  2. 下载Nacos
  3. 配置Nacos
  4. 修改conf目录下cluster.conf.example名为cluster.conf
  5. cluster.conf添加内容
# 集群地址和端口,手头没有多于服务器,先放在本地,本地有虚拟机的可以使用192.168.126.1:端口号
127.0.0.1:8845
127.0.0.1:8846
127.0.0.1:8847
  1. 修改application.properties文件,添加数据库配置
spring.datasource.platform=mysql

db.num=1

db.url.0=jdbc:mysql://127.0.0.1:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
db.user.0=root
db.password.0=123
  1. 启动
  2. nginx反向代理,修改conf/nginx.conf文件
upstream nacos-cluster {
    server 127.0.0.1:8845;
    server 127.0.0.1:8846;
    server 127.0.0.1:8847;
}

server {
    listen       80;
    server_name  localhost;

    location /nacos {
        proxy_pass http://nacos-cluster;
    }
}
  1. 而后在浏览器访问:http://localhost/nacos即可。

Feign

Feign是一个声明式的http客户端,作用是帮我们发送http请求,底层可以理解为RestTemplate+Ribbon

使用步骤

  1. 引入依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
  1. 在启动类上添加注解
@EnableFeignClients(clients = UserClient.class)
// 如果Feign接口包路径和引导类包路径不相同,需要单独指定
@EnableFeignClients(basePackages = "com.example.feignapi")  // 目的是创建feign接口代理对象并放入spring容器
  1. 编写Feign的客户端
@FeignClient("userservice")
public interface UserClient {
    @GetMapping("/user/{id}")
    User findById(@PathVariable("id") Long id);
}

主要基于SpringMVC的注解来声明远程调用的信息,比如:

  • 服务名称: userservice
  • 请求方式: GET
  • 请求路径: /user/{id}
  • 请求参数: Long id
  • 返回值类型: User
  1. 添加Feign的超时时间
feign:
  client:
    config:
      default:
        #不设置connectTimeout会导致readTimeout设置不生效
        connectTimeout: 3000
        readTimeout: 6000 
  1. 替换原先http远程调用代码

自定义配置

feign日志
  • 基于配置文件修改feign的日志级别可以针对单个服务
feign: 
  client: 
    config: 
      userservice: # 针对某个微服务的配置
        loggerLevel: FULL # 日志级别
  • 针对所有服务
feign: 
  client: 
    config: 
      default: # 针对全局配置
        loggerLevel: FULL # 日志级别
  • 使用Java代码生成日志
# 声明一个类
public class DefaultFeignConfiguration {
    @Bean
    public Logger.Level.BASIC;  // 日志级别为BASIC
}
  • 如果要全局生效将其放到启动类的@EnableFeignClients这个注解中
@EnableFeignClients(defaultConfiguration = DefaultFeignConfiguration.class)
  • 如果是局部生效将其放到对应的@FeignClient注解中
@FeignClient(value = "userservice", configuration = DefaultFeignConfiguration.class)
日志级别
  • NONE: 不记录任何日志信息,这是默认值
  • BASIC: 仅记录请求的方法,URL以及响应码和执行时间
  • HEADERS: 在BASIC的基础上,额外记录了请求和响应的头信息
  • FULL: 记录所有请求和响应的明细,包括头信息、请求体、元数据
自定义Feign的配置

在这里插入图片描述

Feign的性能优化

Feign底层发起http请求,依赖于其他的框架

Feign底层的客户端实现
  • URLConnection: 默认实现,不支持连接池
  • Apache HttpClient: 支持连接池
  • OKHttp: 支持连接池
Feign添加HttpClient支持步骤
  1. 引入依赖
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-httpclient</artifactId>
</dependency>
  1. 配置连接池
feign: 
  client: 
    config: 
      default:  # 全局配置
        loggerLevel: BASIC  # 日志级别,BASIC
  httpclient: 
    enabled: true # 开启feign对HttpClient的支持
    max-connections: 200  # 最大的连接数
    max-connections-per-route: 50 # 每个路径的最大连接数
Feign的最佳实践
  • 方式一(继承): 给消费者的FeignClient和提供者的Controller定义同意的父接口作为标准

在这里插入图片描述

  • 方式二(抽取): 将FeignClient抽取为独立模块,并且把接口有关的POJO、默认的Feign配置都放到这个模块中,提供给所有消费者使用

在这里插入图片描述

方式2的实现步骤
  1. 创建一个module,命名为feign-api,引入feign的starter依赖
  2. 将order-service(发起Http远程调用的服务)中编写的Client实体类日志配置类都复制到feign-api
  3. 在order-service中引入feign-api的依赖
  4. 修改order-service中的所有与上述三个组件有关的import部分,改成导入feign-api中的包
  5. 重启测试
  • 当定义的FeignClient不在SpringBootApplication的扫描包范围时,这些FeignClient无法启动,此时有两种解决方案:
    • 指定FeignClient所在包
    @EnableFeignClients(basePackages = "com.example.feign.clients")
    
    • 指定FeignClient字节码
    @EnableFeignClients(clients = {UserClient.class})
    

Gateway网关

核心功能

  • 身份认证和权限校验
  • 服务路由和负载均衡
  • 请求限流

SpringCloud中的网关实现有两种

  • SpringCloudGateway: 基于Spring5中提供的WebFlux,属于响应式编程的实现,具备更好的性能
  • zuul: 基于Servlet实现,属于阻塞式编程

Gateway使用步骤

  1. 创建gateway服务,引入依赖
<!-- 网关依赖 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

<!-- nacos服务发现依赖-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
  1. 编写SpringBoot启动类
  2. 编写路由配置及nacos地址
server: 
  port: 10010 # 网关端口
spring: 
  application: 
    name: gateway # 服务名称
  cloud: 
    nacos: 
      server-addr: localhost: 8848  # nacos地址
    gateway: 
      routes: # 网关路由配置
        - id: user-service # 路由id,自定义,只要唯一即可
          # uri: http://127.0.0.1:8081  # 路由的目标地址 http就是固定地址,不建议这样写
          uri: lb://userservice # 路由的目标地址 lb就是负载均衡,后面跟服务名称
          predicates: # 路由断言,也就是判断请求是否符合路由规则的条件
            - Path=/user/** # 这个是按照路径匹配,只要以/user/开头就符合要求
          filters:
              StripPrefix=1 # 去除1个前缀
      default-filters: # 默认过滤器
        - AddRequestHeader=authorization,2	# 所有的请求头都带有该信息
路由断言工厂Route Predicate Factory

我们在配置文件中写的断言规则只是字符串,这些字符串会被Predicate Factory读取并处理,转变为路由判断的条件

网关路由可以配置的内容包括:

  • 路由id: 路由的唯一标识
  • uri: 路由目的地,支持lbhttp两种
  • predicates: 路由断言,判断请求是否符合要求,符合则转发到路由目的地
  • fileters: 路由过滤器,处理请求或响应
Spring提供的11种基本的Predicate工厂

在这里插入图片描述

路由过滤器GatewayFilter

GatewayFilter是网关种提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理

Spring提供了31种GatewayFilter工厂

在这里插入图片描述

  • 默认过滤器,对所有服务生效: defaultFilters
全局过滤器GlobalFilter

全局过滤器的作用也是处理一切进入网关的请求和微服务响应,与GatewayFilter的作用一样

和GatewayFilter的区别

GatewayFilter通过配置定义,处理逻辑是固定的,GlobalFilter的逻辑是自己写代码实现的

实现步骤
  1. 自定义类
  2. 实现GlobalFilter接口
  3. 添加@Order注解(也可以实现Ordered接口)
@Order(-1)  // 数值越小优先级越高
@Component
public class AuthorizeFilter implements GlobalFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 1. 获取请求参数
        MultiValueMap<String, String> params = exchange.getRequest().getQueryParams();

        // 2. 获取authorization参数
        String authorication = params.getFirst("authorization");

        // 3. 校验
        if (java.util.Objects.equals("admin", authorication)) {
            // 放行
            return chain.filter(exchange);
        }

        // 4. 拦截
        // 4.1 禁止访问,设置状态码
        exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);

        // 4.2 结束处理
        return exchange.getResponse().setComplete();
    }
}
过滤器执行顺序

请求进入网关会碰到三类过滤器: 当前路由的过滤器、DefaultFilter、GlobalFilter
请求路由后,会将当前路由过滤器和DefaultFilter、GlobalFilter,合并到一个过滤器链(集合)种,排序后依次执行每个过滤器

在这里插入图片描述

  • 每一个过滤器都必须指定一个int类型的order值,order值越小,优先级越高,执行顺序越靠前
  • GloableFilter通过实现Ordered接口,或者添加@Order注解来指定order值,由我们自己决定
  • 路由过滤器和defaultFilter的order由Spring指定默认是按照声明顺序从1递增
  • 当过滤器的order值一样时,会按照defaultFilter > 路由过滤器 > GloableFilter的顺序执行

跨域问题

域: 协议、域名、端口
三样有任意一样不一样就是跨域

  • 跨域: 值浏览器针对jsjs从一个域加载到浏览器,但是发起ajax异步请求请求另一个域的后台,就叫跨域,浏览器默认不允许,有同源策略,为了安全,如果跨域,会报错
跨域问题解决
  1. 修改网关配置
spring: 
  cloud: 
    gateway: 
      globalcors: # 全局的跨域处理
        add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
        corsConfigurations:
          '[/**]':
            allowedOrigins: # 允许哪些网站的跨域请求
              - "http://localhost:8090"
            allowedMethods: # 允许的跨域ajax的请求方式
              - "GET"
              - "POST"
              - "DELETE"
              - "PUT"
              - "OPTIONS"
            allowedHeaders: "*" # 允许在请求中携带的头信息
            allowCredentials: true # 是否允许携带cookie
            maxAge: 360000 # 这次跨域检测的有效期
      
  1. 通过Nginx

让orgin原来的域和异步请求要访问的域保持一致,不算跨域,然后让Nginx做代理

  1. 后端处理
  2. 前端处理
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值