Nginx实战02-中级篇

一. 阅读前提

本文建立在前一篇的 [Nginx实战01-入门篇] 之上, 有兴趣可以花5分钟的时间看一下入门篇
https://blog.csdn.net/weixin_43273174/article/details/105844831

  • 本文的受众对象
    • 阅读过 [Nginx实战01-入门篇]
    • 运维工程师
    • 架构师
    • 对Nginx技术有浓厚兴趣的小伙伴
  • 内容概要
    • 实战中对于Nginx负载均衡及反向代理的应用
      • 服务集群
    • 以案例的形式介绍一些实用的转发策略
      • HTTP/HTTPS
      • TCP
      • URL转发
      • URL重写
      • 静态资源转发(前端页面部署)
  • 那么闲话不多说,开始我们的中级篇

二. 实战中对于Nginx负载均衡及反向代理的应用

1. 服务集群,(本地集群)

上一篇文章中说到: 负载均衡与反向代理

  • 负载均衡
    • 为了降低后台服务器的压力,我们需要将压力分摊到更多的服务器中,我将这种做法称作负载均衡
  • 反向代理
    • 暴露一个公网的ip,请求通过公网入口转发到内网端口的实现
接下来我会用一个实例来简单的演示负载均衡的实现
  • 材料准备
    • 一台linux虚拟机
    • 一个springboot应用(什么应用都可以),接下来简称这个springboot应用为sb.jar将这个下面附上源码
package com.gralves.loadbalance.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.PostConstruct;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.ConcurrentLinkedQueue;

/**
 * @author 老周啊啊
 * @date 2020年4月30日14:28:53
 */
@Slf4j
@RestController
public class LoadBalanceTests {

    private static final ConcurrentLinkedQueue<String> TICKETS = new ConcurrentLinkedQueue<>();

    /*设置队列长度*/
    /*可通过--ticketAmount参数指定队列长度,即token数量*/
    @Value("${ticketAmount:200}")
    private Integer ticketAmount;

    /*初始化队列长度并加入票据*/
    @PostConstruct
    public void init() {
        for (int i = 0; i < ticketAmount; i++) {
            TICKETS.add(UUID.randomUUID().toString());
        }
    }

    /**
     * 队列中弹出一个UUID票据,为空则返回异常
     *
     * @return 票据/Exception
     * @author 老周啊啊
     * @date 2020年4月30日14:15:56
     */
    @GetMapping("takeoutTicket")
    public String takeoutTicket() throws Exception {
        String ticket = TICKETS.poll();
        if (Objects.nonNull(ticket)) {
            return ticket;
        }
        throw new Exception("队列为空");
    }
}

该代码示例以一个队列的形式取令牌,由上述代码,可以看到,队列中默认的令牌数量为200,所以当取出令牌数超过200时,则跑出异常,以为这请求失败

  • 首先以单进程的方式调用接口,获取令牌
# 启动sb.jar,并指定启动端口为8081
java -jar sb.jar --server.port=8081 --ticketAmount=200
使用jmeter进行接口请求测试,请求linux服务器的ip:8081/takeoutTicket
  • 当请求数200时,通过下图可以看到,当请求取出令牌数等于队列长度时,请求正常
    image
  • 当请求数+1,则返回失败,我们以队列的长度模拟服务器的负载能力,则认为单台服务器的负载为200
    image

在实际的生产环境中,单台服务器的负载能力始终是有限的,所以我们通常会通过增加机器的操作来增加服务的负载能力

配置Nginx负载均衡
  • 首先附上配置,然后再进行解释
http {                                                                        
    include       mime.types;                                                 
    default_type  application/octet-stream;                                   
                                                                              
    #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
    #                  '$status $body_bytes_sent "$http_referer" '            
    #                  '"$http_user_agent" "$http_x_forwarded_for"';          
                                                                              
    #access_log  logs/access.log  main;                                       
                                                                              
    sendfile        on;                                                       
                                                                              
    keepalive_timeout  65;                                                    
                                                                              
    #新增一个负载均衡器,负载均衡器命名方式可以自定义,此处叫localProxy,需要与下面server中proxy_pass的配置对应
    upstream localProxy {     
        # 配置两台服务器,第一个为前面启动的8081端口的Java进程所在的服务器ip:端口,由于我们模拟的是伪分布式,所以ip都是127.0.0.1
        server 127.0.0.1:8081;                                                
        server 127.0.0.1:8080;                                                
    }                                                                         
    
    # 请求转发代码块                                                                  
    server {        
        # 监听8888端口
        listen       8888;
        # 进入改代码块的域名要求为localhost,如果有多个server代码块监听相同端口,则server_name要求不同,
        # 这样才能进入不同的转发逻辑
        server_name  localhost;                                               
        
        # uri 匹配模式为 "/",表示所有的请求都进入下面的转发代码块
        location / {                                             
            # 所有的请求都会进入localProxy负载均衡器
            proxy_pass http://localProxy;                                     
            proxy_redirect default;                                           
        }                                                                     
    }                                                                        
}                                                                             
  • 完成以上配置后,检查nginx配置并重载配置
# 检查
nginx -t
# 配置重载
nginx -s reload
  • 上述配置可见,对外暴露一个8888端口,请求将分发到localProxy的负载均衡器并由两个java进程处理请求
    • 设置两台java应用的负载能力都是200
# 启动sb.jar,并指定启动端口为8081,设置负载为200
java -jar sb.jar --server.port=8081 --ticketAmount=200
# 启动sb.jar,并指定启动端口为8080,设置负载为200
java -jar sb.jar --server.port=8080 --ticketAmount=200
  • 使用Jmeter进行测试,设置并发为400,下图可见,添加负载均衡器后,该接口的负载能力增加到了400,那么负载均衡的目的已经达到了
    image

  • 至此已经完成了使用Nginx进行简单的负载均衡

负载均衡器的进阶应用-分发策略
  • 轮询策略
    • 负载均衡器对于后端服务器的分发策略默认为轮询,所有请求都按照时间顺序分配到不同的服务上,如果服务Down掉,可以自动剔除,配置参考
upstream  proxy-server {
       server    localhost:8081;
       server    localhost:8080;
}
  • 权重策略
    • 指定每个服务的权重比例,weight和访问比率成正比,通常用于后端服务机器性能不统一,将性能好的分配权重高来发挥服务器最大性能,如下配置后8080服务的访问比率会是8081服务的二倍。
upstream  proxy-server {
       server    localhost:8081 weight=1;
       server    localhost:8080 weight=2;
}
  • iphash策略
    • 每个请求都根据访问ip的hash结果分配,经过这样的处理,每个访客固定访问一个后端服务,如下配置(ip_hash可以和weight配合使用).
upstream  proxy-server {
       iphash;
       server    localhost:8081 weight=1;
       server    localhost:8080 weight=2;
}
  • 最少连接策略
    • 将请求分配到连接数最少的服务上。
upstream  proxy-server {
       least_conn;
       server    localhost:8081 weight=1;
       server    localhost:8080 weight=2;
}
  • fair策略
    • 按后端服务器的响应时间来分配请求,响应时间短的优先分配。
upstream  proxy-server { 
       server    localhost:8081 weight=1;
       server    localhost:8080 weight=2;
       fair;
}

三. 以案例的形式介绍一些实用的转发策略

在开始介绍转发策略之前,先简单的介绍一下Nginx的代码结构

  • nginx配置文件结构
    • 主要便于后续代码展示,不至于造成看不懂代码配置该放在什么地方的尴尬窘境
...              #全局块 
events {         #events块
   ...
} 

# TCP代码块
stream { 
    # stream 全局块
    server { 
        # server代码块
    } 
}
# http块
http      
{
    ...   #http全局块
    server        #server块
    { 
        ...       #server全局块
        location [PATTERN]   #location块
        {
            ...
        } 
    }
    server
    {
      ...
    }
    ...     #http全局块
}
  • Nginx全局变量介绍
$args               : #这个变量等于请求行中的参数,同$query_string
$content_length     : 请求头中的Content-length字段。
$content_type       : 请求头中的Content-Type字段。
$document_root      : 当前请求在root指令中指定的值。
$host               : 请求主机头字段,否则为服务器名称。
$http_user_agent    : 客户端agent信息
$http_cookie        : 客户端cookie信息
$limit_rate         : 这个变量可以限制连接速率。
$request_method     : 客户端请求的动作,通常为GET或POST。
$remote_addr        : 客户端的IP地址。
$remote_port        : 客户端的端口。
$remote_user        : 已经经过Auth Basic Module验证的用户名。
$request_filename   : 当前请求的文件路径,由root或alias指令与URI请求生成。
$scheme             : HTTP方法(如http,https)。
$server_protocol    : 请求使用的协议,通常是HTTP/1.0或HTTP/1.1。
$server_addr        : 服务器地址,在完成一次系统调用后可以确定这个值。
$server_name        : 服务器名称。
$server_port        : 请求到达服务器的端口号。
$request_uri        : 包含请求参数的原始URI,不包含主机名,如:”/foo/bar.php?arg=baz”。
$uri                : 不带请求参数的当前URI,$uri不包含主机名,如”/foo/bar.html”。
$document_uri       : 与$uri相同。
if判断指令

语法为if(condition){…},对给定的条件condition进行判断。如果为真,大括号内的rewrite指令将被执行,if条件(conditon)可以是如下任何内容:

  • 当表达式只是一个变量时,如果值为空或任何以0开头的字符串都会当做false
  • 直接比较变量和内容时,使用=或!=
  • 正则表达式匹配,*不区分大小写的匹配,!~区分大小写的不匹配
- -f和!-f用来判断是否存在文件
- -d和!-d用来判断是否存在目录
- -e和!-e用来判断是否存在文件或目录
- -x和!-x用来判断文件是否可执行 
关于if指令的一些操作示例,一下代码中
//如果UA包含"MSIE",rewrite请求到/msid/目录下
if ($http_user_agent ~ MSIE) {
    rewrite ^(.*)$ /msie/$1 break;
} 
//如果cookie匹配正则,设置变量$id等于正则引用部分
if ($http_cookie ~* "id=([^;]+)(?:;|$)") {
    set $id $1;
 } 
//如果提交方法为POST,则返回状态405(Method not allowed)。return不能返回301,302
if ($request_method = POST) {
    return 405;
} 
//限速,$slow可以通过 set 指令设置
if ($slow) {
    limit_rate 10k;
} 
//如果请求的文件名不存在,则反向代理到localhost 。这里的break也是停止rewrite检查
if (!-f $request_filename){
    break;
    proxy_pass  http://127.0.0.1; 
} 
//如果query string中包含"post=140",永久重定向到example.com
if ($args ~ post=140){
    rewrite ^ http://example.com/ permanent;
} 
//防盗链
location ~* \.(gif|jpg|png|swf|flv)$ {
    valid_referers none blocked oss.hdyl.net.cn
    if ($invalid_referer) {
        return 404;
    } 
}
常用正则
.     :  匹配除换行符以外的任意字符
?     :  重复0次或1次
+     :  重复1次或更多次
*     :  重复0次或更多次
\d    :  匹配数字
^     :  匹配字符串的开始
$     :  匹配字符串的介绍
{n}   :  重复n次
{n,}  :  重复n次或更多次
[c]   :  匹配单个字符c
[a-z] :  匹配a-z小写字母的任意一个

转发与重写

  • 什么是转发(forward)
请求转发时客户端和浏览器只发出一次请求,Servlet、HTML、JSP或其它信息资源,由第二个信息资源响应该请求,在请求对象request中,保存的对象对于一个每个信息资源是共享的。
  • 什么是重写(rewrite)
请求重写是两次HTTP请求,服务器端在响应第一次请求的时候,让浏览器再向另外一个URL发出请求,从而达到转发的目的。
  • 两者之间的区别
两者之间最明显的区别在于转发时会丢失request的所有信息,重写属于页面级别的重定向,相当于是告诉浏览器,客户端重新发起一次请求
举个例子

小明找小红借钱,小红没钱,然后小红找小王借钱,整个过程结束后,小明只发起了一次请求,这就是转发

小明找小红借钱,小红没钱,然后小红让小明去找小王借钱,整个过程中,小明将发起两次请求,这个过程就是重写(重定向)

1. 转发

1.1 转发语法
proxy_pass  <ip>:<port>;
转发关键字    ip : 端口
1.1.1 转发关键字使用域
# 1. server代码块
server {
    ...
    proxy_pass  <ip>:<port>;
}
# 2. location代码块
location ... {
    ...
    proxy_pass  <ip>:<port>;
}
# 3. if代码块
if(true){
    ...
    proxy_pass  <ip>:<port>;
}

以下演示案例皆在[location]代码块下进行演示, [if]代码块可在[location]代码块中嵌套执行

1.2 全匹配模式
# 匹配 $uri = /goods/add, 
# 例如访问 https://domain.com/goods/add,则请求将进入该代码块
location = /goods/add {
    # 转发请求到 127.0.0.1:8080
    # 127.0.0.1 为接受请求的服务ip,端口为服务的端口,实际按照真实的ip:port进行配置
    proxy_pass  http://127.0.0.1:8080;
}
1.3 正则匹配模式
正则匹配语法
location <match pattern> <regex>
关键字      匹配模式     正则表达式
match pattern 匹配模式
  • = 表示精确匹配
  • ^~ 表示uri以某个常规字符串开头,不是正则匹配
  • ~ 表示区分大小写的正则匹配;
  • ~* 表示不区分大小写的正则匹配
  • / 通用匹配, 如果没有其它匹配,任何请求都会匹配到
  • 优先级
= > 完整路径 > ^~ > ~,~* > 前缀匹配 > /
1.3.1 前缀匹配

使用前缀匹配模式的注意事项

  • 若配置location ^~ /goods , 表示匹配/goods** , 则满足一下条件的请求会进入相应的location

    • /goods/add
    • /goodsStock
    • /goodsStock/increase
    • 只要前缀满足/goods即可
  • 若配置location ^~ /goods/ , 表示匹配/goods/** , 则满足一下条件的请求会进入相应的location

    • /goods/add
    • /goods/delete
    • 需要前缀满足/goods/
# 匹配 $uri 前缀为 /goods, 
# 例如访问 https://domain.com/goods/add,则请求将进入该代码块
location ^~ /goods {
    # 转发请求到 127.0.0.1:8080
    # 127.0.0.1 为接受请求的服务ip,端口为服务的端口,实际按照真实的ip:port进行配置
    proxy_pass  http://127.0.0.1:8080;
}
1.3.2 正则匹配示例

location ~* \.(gif|jpg|jpeg)$ {
    # 匹配所有以 gif,jpg或jpeg 结尾的请求 
    proxy_pass  http://127.0.0.1:8081;
}


location ^~ /\w+/test/ {
    # 匹配所有/*/test/**为前缀的请求
    proxy_pass  http://127.0.0.1:8082;
}

1.4 RestFul匹配

RestFul匹配的使用在if代码块中进行演示,便于大家理解

# 匹配根目录,所有的请求都会进入次代码块
location / {
    # 当请求uri匹配前缀==/goods/add**==,并且请求方式为POST请求,则进行请求转发
    # 若请求uri匹配前缀==/goods/add**==,但请求方式不为POST,则不进行请求转发,继续进行后续的匹配逻辑,直至找到何时的匹配规则
    if ($uri ~ POST-/goods/add){
        proxy_pass http://127.0.0.1:8080; 
    }
}

2. 重写

2.1 重写语法
rewrite     <regex>     <replacement>     [flag];
重写关键字  正则表达式      替换字符串       转发标记
2.2 rewrite关键字使用域
# 1. server代码块
server {
    ...
    rewrite     <regex>     <replacement>     [flag];
}
# 2. location代码块
location ... {
    ...
    rewrite     <regex>     <replacement>     [flag];
}
# 3. if代码块
if(true){
    ...
    rewrite     <regex>     <replacement>     [flag];
}
转发标记详解
last ---> 本条规则匹配完成后,继续向下匹配新的location URI规则, 浏览器地址栏URL地址不变
break ---> 本条规则匹配完成即终止,不再匹配后面的任何规则, 浏览器地址栏URL地址不变
redirect ---> 返回302临时重定向,浏览器地址会显示跳转后的URL地址
permanent ---> 返回301永久重定向,浏览器地址栏会显示跳转后的URL地址 
重写示例

对形如/images/ef/uh7b3/test.png的请求,重写到/data?file=test.png,于是匹配到location /data,先看/data/images/test.png文件存不存在,如果存在则正常响应,如果不存在则重写tryfiles到新的image404 location,直接返回404状态码。

http {
    # 定义image日志格式
    log_format imagelog '[$time_local] ' $image_file ' ' $image_type ' ' $body_bytes_sent ' ' $status;
    # 开启重写日志
    rewrite_log on;

    server {
        root /home/www;

        location / {
                # 重写规则信息
                error_log logs/rewrite.log notice; 
                # 注意这里要用‘’单引号引起来,避免{}
                rewrite '^/images/([a-z]{2})/([a-z0-9]{5})/(.*)\.(png|jpg|gif)$' /data?file=$3.$4;
                # 注意不能在上面这条规则后面加上“last”参数,否则下面的set指令不会执行
                set $image_file $3;
                set $image_type $4;
        }

        location /data {
                # 指定针对图片的日志格式,来分析图片类型和大小
                access_log logs/images.log mian;
                root /data/images;
                # 应用前面定义的变量。判断首先文件在不在,不在再判断目录在不在,如果还不在就跳转到最后一个url里
                try_files /$arg_file /image404.html;
        }
        location = /image404.html {
                # 图片不存在返回特定的信息
                return 404 "image not found\n";
        }
}

3. HTTP/HTTPS

前置知识

  • HTTP请求默认解析端口为80
  • HTTPS请求默认解析端口为443
问题
  • 假设现在有一个域名aaa.bbb.ccc
    • 要求使用这个域名既可以使用http协议又可以使用https协议
      • http://aaa.bbb.ccc
      • https://aaa.bbb.ccc
  • 下面来实现以下这个需求
  • 前提
    • 一个域名
    • 解析域名到指定的Nginx服务器ip
    • SSL证书并解析域名
    • 下载SSL证书Nginx版
代码实现与注释详解
server {
        # 开放80 端口监听
        listen       80;
        # 开放443 端口监听
        listen       443 ssl;
        # 虚拟主机,匹配合法host进入相应server逻辑代码块
        server_name  aaa.bbb.ccc;
        # 设置字符集
        charset utf-8;
        
        # 配置Nginx访问日志,最后一级目录目录必须真实存在,否则检查配置的时候会报错
        access_log  /data/nginx/logs/80/access.log;
        # 配置Nginx异常日志,最后一级目录目录必须真实存在,否则检查配置的时候会报错
        error_log   /data/nginx/logs/80/error.log;

        # 配置SSL证书文件
        ssl_certificate   /etc/nginx/ssl/domain.pem;
        # 配置SSL秘钥文件
        ssl_certificate_key  /etc/nginx/ssl/domain.key;
        client_max_body_size  50m;

        location / {
            proxy_pass http://local_proxy;
            proxy_redirect default;    
        } 
    }

4. TCP

基于长连接的转发使用stream代码块实现

  • 常用的使用场景
    • 连接内网数据库
    • 资源反向代理

长连接的转发比较简单,直接附上配置解析

stream {
    # 配置虚拟服务器节点
    server {
       # 监听13306端口,即对外暴露13306端口
       listen 13306;
       # 连接超时时间
       proxy_connect_timeout 10s;
       # 代理超时时间
       proxy_timeout 300s;
       # 转发ip:端口
       # 此处演示的是转发阿里云RDS VPC网络连接地址
       proxy_pass xxxx.rds.aliyuncs.com:3306;
    }
}
  • 配置完成后重载配置
nginx -t
nginx -s reload
  • 之后通过nginx所在的ip:13306端口即可连接RDS的内网地址,实现公网转发内网的需求

5. 静态资源转发

本节内容建立在以上4节内容的基础之上,如有疑问,请先阅读上面的四个小节

转发示例
  • 设定页面在/usr/local/static/h5/目录下,页面入口均为为index.html,目录结构如下
- /usr/local/static/h5/
    // 淘宝活动页
    - taobaoActivity
        - css
        - favicon.ico
        - js
        - index.html
    // 官网页,vue工程,需要配置路由
    - portal
        - css
        - favicon.ico
        - js
        - index.html
示例
 server {
        # 监听8909端口
        listen       8909;
        # 设置domain
        server_name  domain.com;

        charset utf-8;

        client_max_body_size  50m;
        
        # 当uri匹配前缀/h5/taobaoActivity/时,请求将转发至Nginx本机的/usr/local/static/h5/taobaoActivity/目录
        # 若请求 https://domain.com/h5/taobaoActivity/index.html
        # 则该请求的内容为目录配置中的淘宝活动页
        location /h5/taobaoActivity/ {
            root   /usr/local/static/;
            index  index_real.html index.htm;
        } 
        
        # 当uri匹配前缀/h5/portal/时,请求将转发至Nginx本机的/usr/local/static/h5/portal/目录
        # 若请求 https://domain.com/h5/portal
        # 则该请求的内容为目录配置中的官网页
        location /h5/portal/ {
            alias  /usr/local/static/h5/portal/;
            index  index.html index.htm;
            # try_files取代了一部分rewrite的功能
            # try_files执行的最终逻辑为:
            # 1.将index.html拼接在$uri之后,则$uri=/h5/portal/index.html
            # 2.当$uri=/h5/portal/index.html,在alias的别名真实路径,最终的请求将转发至/usr/local/static/h5/portal/index.html,即上述文件目录中的官网页路径
            try_files $uri $uri/ index.html;
        } 
    }

补充:alias与root的区别
  • root和alias都可以定义在location模块中,都是用来指定请求资源的真实路径
location /i/ {
  root /data/w3;
}
  • 请求 http://foofish.net/i/top.gif 这个地址时,那么在服务器里面对应的真正的资源是 /data/w3/i/top.gif文件
  • 注意:真实的路径是root指定的值加上location指定的值.

image

  • alias指定的路径是location的别名,不管location的值怎么写,资源的真实路径都是alias指定的路径
location /i/ {
  alias /data/w3/;
}
  • 同样请求 http://foofish.net/i/top.gif 时,在服务器查找的资源路径是: /data/w3/top.gif
    image

  • 1、 alias 只能作用在location中,而root可以存在server、http和location中。

  • 2、alias 后面必须要用 “/” 结束,否则会找不到文件,而 root 则对 ”/” 可有可无。

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
nginx是一种高性能的Web服务器和反向代理服务器软件,可以在Linux、Windows、UNIX等操作系统上运行。它以其稳定性、高并发性和低内存消耗而受到广泛关注和使用。 nginx 1.18是nginx的一个版本,其中包含了一系列的新特性和改进。这个版本引入了新的HTTP/2服务器推送功能,提供了更好的性能和可扩展性。同时,该版本还增加了对TLS 1.3的支持,加强了传输层安全性。此外,nginx 1.18还改进了负载均衡算法,提高了对后端服务器的请求分发效率。总之,nginx 1.18在性能、安全性和功能上都有所提升,是一个值得使用的版本。 nginx-mod-stream是一个nginx模块,用于处理TCP/UDP流量。它提供了一系列的功能,如四层(网络层)和七层(应用层)的负载均衡、流量分片、数据重定向等。通过使用nginx-mod-stream,我们可以在一个单独的nginx服务器上同时处理HTTP和TCP/UDP流量,增加了服务器的灵活性和可扩展性。 通过将nginx 1.18和nginx-mod-stream结合使用,我们可以构建一个强大的、高性能的网络架构。nginx 1.18提供了优秀的HTTP服务和反向代理能力,而nginx-mod-stream则提供了处理TCP/UDP流量的功能。这样可以让我们的应用程序更加灵活,在一个服务器上同时处理不同类型的流量,提高服务器的利用率和性能。因此,nginx 1.18和nginx-mod-stream是两个重要的组件,能够帮助我们构建高效的网络架构。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值