虚拟化技术性能总结:Zones, KVM, Xen

[译]虚拟化技术性能总结:Zones, KVM, Xen

翻译源地址: http://dtrace.org/blogs/brendan/2013/01/11/virtualization-performance-zones-kvm-xen/ 
作者:Brendan Gregg,原文标题:Virtualization Performance: Zones, KVM, Xen 
译者:陈晓炜。麻烦转载请注明作者、译者及原始出处,并不吝多多指教!

先说一下,中国香港的 Fengqi.Asia 或大陆的 风起云 所使用的后台技术即是Joyent相关技术,应该也是中国现在唯一使用Joyent技术的公有云(如果有错请不吝指教)。

------------------------------------------------------------

在Joyent我们在两种不同的虚拟化技术( Zones 和 KVM )上运行一个高性能公有云。我们也曾经用过 Xen ,但后来逐步淘汰了它,取而代之,在 SmartOS 上使用KVM。我的任务是让他们运行得更快,所以会用到 DTrace 来分析内核、应用程序和上述的虚拟化技术。这篇文章我会用四种方式来总结一下它们的性能:特点、方框图、内部情况、测试结果。

AttributeZonesXenKVM
CPU Performancehighhigh (with CPU support)high (with CPU support)
CPU Allocationflexible (FSS + “bursting”)fixed to VCPU limitfixed to VCPU limit
I/O Throughputhigh (no intrinsic overhead)low or medium (with paravirt)low or medium (with paravirt)
I/O Latencylow (no intrinsic overhead)some (I/O proxy overhead)some (I/O proxy overhead)
Memory Access Overheadnonesome (EPT/NPT or shadow page tables)some (EPT/NPT or shadow page tables)
Memory Lossnonesome (extra kernels; page tables)some (extra kernels; page tables)
Memory Allocationflexible (unused guest memory used for file system cache)fixed (and possible double-caching)fixed (and possible double-caching)
Resource Controlsmany (depends on OS)some (depends on hypervisor)most (OS + hypervisor)
Observability: from the hosthighest (see everything)low (resource usage, hypervisor statistics)medium (resource usage, hypervisor statistics, OS inspection of hypervisor)
Observability: from the guestmedium (see everything permitted, incl. some physical resource stats)low (guest only)low (guest only)
Hypervisor Complexitylow (OS partitions)high (complex hypervisor)medium
Different OS Guestsusually no (sometimes possible with syscall translation)yesyes

(上表就不翻译了,看英文更合适)

配置调优等不同可能会导致上表的一些变化,从而细节可能会有不同。不过至少可以把上表当成一个属性清单来做分析确认,这样的话,当你在考虑其他技术,比如VMware时,可以参考一下此清单。其实Wikipedia上也提供了一个常用的 属性对比表

表中的三列代表三种不同的类型:: 操作系统虚拟化 (Zones)、 硬件虚拟化 的 Type 1  (Xen) and  Type 2  (KVM) 。 

它们虚拟化后表现出来的性能是我们最关心的。一般来说,Joyent使用高速的服务器级别的硬件,10 GbE 网络,所用文件系统使用 ZFS ,用 DTrace 做系统分析,尽量使用 Zones 虚拟化。我们使用自己移植的 KVM to illumos ,并在Zones内部运行KVM实例,提供额外的资源控制,和增强的安全措施(“double-hulled virtualization”双层虚拟化).

有很多属性的细节我都想讨论。这篇文章我会谈谈 I/O Path(网络、磁盘)和它的负载情况等。

I/O Path(I/O 路径)

对于传统的Unix和Zones,它们的 I/O 有什么不同呢?

性能完全一样——没有额外开销。Zones分隔OS的方式如同chroot在文件系统里隔离进程一样。在软件栈里没有必要提供额外的一层来让Zones工作。

现在来看看 Xen 和 KVM(简化版):

GK 是 Guest Kernel,在 Xen 里的 domU 运行 guest OS。有些箭头是指 控制路径(control-path ) ,组件相互通信,同步或异步,以传输更多的数据。 数据路径(data-path) 在某些场景下可能会在共享内存或环形缓冲区执行。 配置的方法可以有好几种。比如,Xen 可以使用Isolated Driver Domains(IDD)或stub-domains,在隔离区里运行 I/O Proxy。

使用 Xen ,hypervisor 为 domains 执行 CPU 调度,每个 domain 对线程调度都有自己 OS 内核。Hypervisor 支持不同的 CPU 调度类,包括 Borrowed Virtual Time (BVT)、Simple Earliest Deadline First (SEDF) 和 Credit-Based。Domain 使用 OS 内核调度器,以及 domain 提供的任何正规的调度类及策略。

多个调度器的额外开销会影响性能。多个调度器可能会在交互中产生复杂的问题,在错误的情景下增加 CPU 延迟。调试它们会非常困难,特别是当 Xen hypervisor 用尽常见的 OS 性能工具资源(用 xentrace )。

通过 I/O proxy 进程(一般是 qemu)发送 I/O 会带来上下文交换(context-switching)和更多开销。我们需要做大量的工作来尽可能减少开销,包括共享内存传输、缓冲、I/O 合并(coalescing)和半虚拟化(paravirtualization)驱动程序等。

使用 KVM ,hypervisor 是一个内核模块(kvm),由 OS 调度器来调度。Hypervisor 可以使用常见的 OS 内核调度器类、策略和优先级等进行调优。KVM 的 I/O 路径要比 Xen 少几步。(最初的 Qumranet KVM 论文 说相比Xen,KVM需要五步,当时的描述里没考虑半虚拟化)

使用 Zones ,就没有上述类似的比较了。它的 I/O 路径(对高速网络很敏感)没有这些多余的步骤。这在Solaris社区(Zones是Solaris的技术)和 FreeBSD 社区(Zones基于 FreeBSD jails 版)应是耳熟能详了。Linux社区也在学习它们并开发属于自己的版本: Linux Containers 。Glauber Costa 在 Linuxcon 2012 大会的演讲里介绍了它,题目是“ The failure of Operating Systems, and how we can fix it ”,并列出了一些用户案例,这些用户之前是用KVM。很多用户案例其实都是可以用Containers技术,并不需要用KVM。

有时你(或我们的客户)的确是需要用到硬件虚拟化技术,因为他们的应用程序依赖于某个特定版本的Linux内核或Windows。针对这种情况,我们提供KVM给客户(我们淘汰了Xen)。

内部情况(Internals)

让我们深入研究看看它们如何工作的(经常使用DTrace)

Network I/O, Zones

下面的两个 stack traces 显示了一个网络包如何通过 global zone(host,即裸机安装)传输到一个zone(guest)中:

Global Zone:                            Zone:
  mac`mac_tx+0xda                         mac`mac_tx+0xda
  dld`str_mdata_fastpath_put+0x53         dld`str_mdata_fastpath_put+0x53
  ip`ip_xmit+0x82d                        ip`ip_xmit+0x82d ip`ire_send_wire_v4+0x3e9 ip`ire_send_wire_v4+0x3e9 ip`conn_ip_output+0x190 ip`conn_ip_output+0x190 ip`tcp_send_data+0x59 ip`tcp_send_data+0x59 ip`tcp_output+0x58c ip`tcp_output+0x58c ip`squeue_enter+0x426 ip`squeue_enter+0x426 ip`tcp_sendmsg+0x14f ip`tcp_sendmsg+0x14f sockfs`so_sendmsg+0x26b sockfs`so_sendmsg+0x26b sockfs`socket_sendmsg+0x48 sockfs`socket_sendmsg+0x48 sockfs`socket_vop_write+0x6c sockfs`socket_vop_write+0x6c genunix`fop_write+0x8b genunix`fop_write+0x8b genunix`write+0x250 genunix`write+0x250 genunix`write32+0x1e genunix`write32+0x1e unix`_sys_sysenter_post_swapgs+0x14 unix`_sys_sysenter_post_swapgs+0x14

我用了一些方法以及很多时间,反复检查我没有弄错,因为上面它们两个是完全一样的。的确,右边显示的栈的结果和左边路径一样。

你也可以配置Zones,让它又一些开销,象一般的系统一样。比如,启动为网络I/O设置的防火墙,或者使用lofs挂载一个文件系统而不用直接挂载。这些都是可选的操作,对于某些用户案例也许会值得耗费一些额外的性能开销。

Network I/O, KVM

显示网络I/O的完整代码路径非常复杂。

第一部分是guest进程写到它自己的驱动里。这种情况,我演示一个带有  DTrace-for-Linux 的 Linux  Fedora guest,并且跟踪 paravirt 驱动:

guest# dtrace -n 'fbt:virtio_net:start_xmit:entry { @[stack(100)] = count(); }'
dtrace: description 'fbt:virtio_net:start_xmit:entry ' matched 1 probe
^C
[...]
  kernel`start_xmit+0x1
 kernel`dev_hard_start_xmit+0x322  kernel`sch_direct_xmit+0xef  kernel`dev_queue_xmit+0x184  kernel`eth_header+0x3a  kernel`neigh_resolve_output+0x11e  kernel`nf_hook_slow+0x75  kernel`ip_finish_output  kernel`ip_finish_output+0x17e  kernel`ip_output+0x98  kernel`__ip_local_out+0xa4  kernel`ip_local_out+0x29  kernel`ip_queue_xmit+0x14f  kernel`tcp_transmit_skb+0x3e4  kernel`__kmalloc_node_track_caller+0x185  kernel`sk_stream_alloc_skb+0x41  kernel`tcp_write_xmit+0xf7  kernel`__alloc_skb+0x8c  kernel`__tcp_push_pending_frames+0x26  kernel`tcp_sendmsg+0x895  kernel`inet_sendmsg+0x64  kernel`sock_aio_write+0x13a  kernel`do_sync_write+0xd2  kernel`security_file_permission+0x2c  kernel`rw_verify_area+0x61  kernel`vfs_write+0x16d  kernel`sys_write+0x4a  kernel`sys_rt_sigprocmask+0x84  kernel`system_call_fastpath+0x16 2015

上面便是 Linux 3.2.6 网络传输的路径。

控制部分由 KVM 传给 qemu I/O proxy,然后用常见的方式(native driver)在host OS上传输。这里是 SmartOS 栈的情况:

host# dtrace -n 'fbt::igb_tx:entry { @[stack()] = count(); }'
dtrace: description 'fbt::igb_tx:entry ' matched 1 probe
^C
[...]
  igb`igb_tx_ring_send+0x33
 mac`mac_hwring_tx+0x1d  mac`mac_tx_send+0x5dc  mac`mac_tx_single_ring_mode+0x6e  mac`mac_tx+0xda  dld`str_mdata_fastpath_put+0x53  ip`ip_xmit+0x82d  ip`ire_send_wire_v4+0x3e9  ip`conn_ip_output+0x190  ip`tcp_send_data+0x59  ip`tcp_output+0x58c  ip`squeue_enter+0x426  ip`tcp_sendmsg+0x14f  sockfs`so_sendmsg+0x26b  sockfs`socket_sendmsg+0x48  sockfs`socket_vop_write+0x6c  genunix`fop_write+0x8b  genunix`write+0x250  genunix`write32+0x1e  unix`_sys_sysenter_post_swapgs+0x149 1195

上述两个栈开始都非常得复杂。然后有一些相关的会运行在 Linux kernel 和illumos kernel 中,它们会更加复杂。基本上,paravirt 代码路径让这两个 kernel stack 得以“紧密接触”。

当 Joyent 的 Robert Mustacchi 深入研究了上述的代码路径后,他画了如下非常形象的 ASCII 图以帮助理解:

/*
 *                  GUEST                        #       QEMU
 * #####################################################################
 *                                               #
 *    +----------+                               #
 *    |  start_  | (1) # * | xmit() | # * +----------+ # * || # * || +-----------+ # * ||------>|free_old_ | (2) # * ||------>|xmit_skbs()| # * || +-----------+ # * \/ (3) # * +---------+ +-------------+ + - #--- PIO write to VNIC * | xmit_ |------->|virtqueue_add| | # PCI config space (6) * | skb() |------->|_buf_gfp() | | # * +---------+ +-------------+ | # * || | # +- VM exit * || +- iff interrupts | # | KVM driver exit (7) * \/ | unmasked (4) | # | * +---------+ | +-----------+(5) | # | +---------+ * |virtqueue|----*---->|vp_notify()|-----*---#-*->| handle | (8) * |_kick() |----*---->| |-----*---#-*->|PIO write| * +---------+ +-----------+ # +---------+ * || # || * || (13) # || * **-----+ iff avail ring # \/ (9) * || capacity < 20 # +-----------------+ * || else return # |virtio_net_handle| * || # |tx_timer() | * \/ (14) # +-----------------+ * +----------+ # || * |netif_stop| # || (10) * |_queue() | # || +---------+ * +----------+ # ||-->|qemu_mod_| * || # ||-->|timer() | * || (15) (16) # || +---------+ * +----------------+ +----------+ # || * |virtqueue_enable|---->|unmask | # || (11) * |_cb_delayed() |---->|interrupts| # || +------------+ * +----------------+ +----------+ # |+->|virtio_ | * || || # +-->|queue_set_ | * || (18) || (17) # |notification| * || +-return +-------------------+ # +------------+ * || | iff ---->|check if the number| # | * **--+ is false |of unprocessed used| # | disable host * || |ring entries is > | # +- interrupts * || |3/4s of the avail | # (12) * \/ (19) |ring index - the | # * +-----------+ |last freed used | # * |free_old_ | |ring index | # * |xmit_skbs()| +-------------------+ # * +-----------+ # * || # * || (20) # * **-----+ iff avail ring # * || capacity is # * || now > 20 # * \/ # * +-----------+ # * |netif_start| (21) # * |_queue() | # * +-----------+ # * || # * || # * \/ (22) (23) # * +------------+ +----------+ # * |virtqueue_ |----->|mask | # * |disable_cb()|----->|interrupts| # * +------------+ +----------+ # * # * # */   Figure II: Guest / Host Packet TX Part 1

我引用上图只是让你大概感觉里面发生了些什么。而且上面只是一部分(Part 1)。

简单来说,它(KVM)使用在共享内存中的 ring buffer 传输数据,一个提醒机制会告知何时可以准备传输数据。当万事如计划开始工作时,性能就非常不错。它没有裸金属那么快(或者如Zones那么快),但是也没那么差。我会在这篇后面引用一些数字来佐证。

CPU 开销和降低的网络性能是需要注意的。此外因为上面介绍过的复杂性,使得分析和性能调查都比较困难。如果使用 Zones,那么只用研究和调优一个内核 TCP/IP 栈(kernel TCP/IP stack)。因为其复杂性,一个就够啦!如果用 KVM,那么有两个不同的内核 TCP/IP 栈,加上 KVM 和 paravirt。调研性能会耗时十倍于Zones。因为太长,所以一般都被禁止。这是为什么在我的表格里,我要引入“可观察性(Observability)”这个概念作为一个重要的特性。因为越难看到的,就越难去调优。

Network I/O, Xen

Guest 传输和 I/O proxy 传输一样,里面的情况更加复杂。不能使用OS里可观察或调试的工具对hypervisor进行检查,因为它直接运行在裸金属级别。有一个工具叫 xentrace,也许很有用,因为在 Xen 调度器中编写的很多事件类型都使用静态探针(static probes)。(但它不是如 DTrace 那样实时观测且可编程,而且需要我再去学另外一个 Tracer 工具)

/proc, Zones

当 I/O 路径默认是0额外开销时,操作系统虚拟化还有一些开销,通常是因为管理或观测需要,且它们并不在 CPU 或 I/O 的 “热门路径” 中。

比如,在同一个系统中用 prstat(1M) , top(1) 之类的读 /proc,一个 Zone 是看不到其他 guest 系统的 /proc 信息。下面是 usr/src/uts/common/fs/proc/prvnops.c 的片段:

static int
pr_readdir_procdir(prnode_t *pnp, uio_t *uiop, int *eofp)
{
[...]
        /*
         * Loop until user's request is satisfied or until all processes
         * have been examined.
         */
        while ((error = gfs_readdir_pred(&gstate, uiop, &n)) == 0) {
                uint_t pid;
                int pslot;
                proc_t *p;

                /*
                 * Find next entry.  Skip processes not visible where
                 * this /proc was mounted.
                 */
                mutex_enter(&pidlock);
                while (n < v.v_proc &&
                    ((p = pid_entry(n)) == NULL || p->p_stat == SIDL ||
                    (zoneid != GLOBAL_ZONEID && p->p_zone->zone_id != zoneid) ||
                    secpolicy_basic_procinfo(CRED(), p, curproc) != 0))
                        n++;
[...]

从上看出,完整的进程信息列表被扫描,但只有本地的 Zone 进程信息被返回。听上去有点低效,难道不能在 proc_t 上加个链表,这样 Zone 进程就能直接得到了?当然可以,但让我们用数字来说话。

下面是使用  prstat(1M) 命令从 Zone 里读 /proc ,读取时间用DTrace来测算:

# dtrace -n 'fbt::pr_readdir_procdir:entry /execname == "prstat"/ {
    self->ts = timestamp; } fbt::pr_readdir_procdir:return /self->ts/ { @["ns"] = avg(timestamp - self->ts); self->ts = 0; }'
dtrace: description 'fbt::pr_readdir_procdir:entry ' matched 2 probes ^C ns 544584

平均而言,需要 544 us (微秒)

现在在另一个 Zone 里有额外的 1000 个进程(代表12个典型的额外的 guest ):

# dtrace -n 'fbt::pr_readdir_procdir:entry /execname == "prstat"/ {
   self->ts = timestamp; } fbt::pr_readdir_procdir:return /self->ts/ { @["ns"] = avg(timestamp - self->ts); self->ts = 0; }'
dtrace: description 'fbt::pr_readdir_procdir:entry ' matched 2 probes ^C ns 594254

这样增加了 50 微秒。对于一个 /proc 读操作,算不上是热路径(hot path)。如果这样的话,那 50 微秒的时间不算短,我们可以再来看看。(我还检查了 pidlockglobal,现在没什么问题,DTrace也检查过)

网络吞吐量结果

一般来说,我都会再三检查数字是否有问题,再分享性能测试结果。但我现在没有足够的时间(因为这篇本来想写的简短点)。我会分享一些我几个月前的数据,那时我仔细地做了测试,有详细的性能结果: Active Benchmarking 。

这儿有一系列的网络吞吐量和用 iperf 测试 IOPS 的结果,用来测试缺省安装 1 Gbyte SmartOS Zones 和 CentOS KVM 实例的不同表现(没有测试 Xen)。客户机和服务器都在同一个数据中心内,但是没有在同一个物理主机,也就是说用到了所有的网络栈。

我可以明确这些测试并没有为我们的云做最优化的配置,实际上是个最低配置minimum config  (1 Gbyte instances)。如果这是个市场宣传的行为,那么我很可能将测试结果调试到对我们最有利。也就是说,对于 SmartOS 内核,会做大量的工作,可以线性驱动多个 10 GbE 端口,需要客户机生成大量的负载来测试。

对于这些结果,视负载、平台内核型号、调优情况不同,每个人所得最终结果可能不尽相同。如果你要使用它们,请仔细思考如何应用它们到何种程度。如果你的负载是受限于 CPU 或文件系统,那么你最好不要测试它们的性能,可考虑使用网络的测试结果。

在 服务器端 一个典型的调用:

iperf -s -l 128k

在 客户端 :

iperf -c server -l 128k -P 4 -i 1 -t 30

线程计数 (-P) 根据调查情况有所不同。最终结果(每个测试时间平均超过30秒)如下。

吞吐量

搜索最高的 Gbits/sec:

sourcedestthreadsresultsuspected limiter
SmartOS 1 GBSmartOS 1 GB12.75 Gbits/secclient iperf @80% CPU, and network latency
SmartOS 1 GBSmartOS 1 GB23.32 Gbits/secdest iperf up to 19% LAT, and network latency
SmartOS 1 GBSmartOS 1 GB44.54 Gbits/secclient iperf over 10% LAT, hitting CPU caps
SmartOS 1 GBSmartOS 1 GB81.96 Gbits/secclient iperf LAT, hitting CPU caps
KVM CentOS 1 GBKVM CentOS 1 GB1400 Mbits/secnetwork/KVM latency (dest 60% of the 1 VCPU)
KVM CentOS 1 GBKVM CentOS 1 GB2394 Mbits/secnetwork/KVM latency (dest 60% of the 1 VCPU)
KVM CentOS 1 GBKVM CentOS 1 GB4388 Mbits/secnetwork/KVM latency (dest 60% of the 1 VCPU)
KVM CentOS 1 GBKVM CentOS 1 GB8389 Mbits/secnetwork/KVM latency (dest 70% of the 1 VCPU)

Zones 峰值性能是 4个线程,4.54 Gbits/sec。更多的线程会达到 1 Gbyte (小型实例)的 CPU 瓶颈,那么 CPU 调度延迟会引发 TCP 故障。更大的 SmartOS 实例有更高的 CPU 瓶颈,能够获得更好的性能。

对于 KVM 测试,我们使用默认的 CentOS 实例。我知道有更新的 Linux 内核,对 网络栈的调优也会更好,不过我们可以之后再去提高相应的吞吐量。我能达到的最高值是对于 1 VCPU KVM Linux 可达到 900 Mbits/sec(这是我们用大量的 DTrace 分析后,对 KVM 从  110 Mbits/sec 调优后达到的)。即使达到了 900 Mbits/sec 的速度,还是只能到 Zones 的 1/5。

请注意“suspected limiter”列(可能的限制因素)。这对于确认实际测试结果很关键,来源于 Active Benchmarking 。这意味着我对于 每个结果 都做了性能分析(包括为了节省空间没有列在这里的)。如果你有疑问,你可以花上一整天来进行所有的测试并分析每一个结果(使用 DTrace)。

IOPS

寻找那些最高的 packets/sec:

sourcedestthreadsresultsuspected limiter
SmartOS 1 GBSmartOS 1 GB114000 packets/secclient/dest thread count (each thread about 18% CPU total)
SmartOS 1 GBSmartOS 1 GB223000 packets/secclient/dest thread count
SmartOS 1 GBSmartOS 1 GB436000 packets/secclient/dest thread count
SmartOS 1 GBSmartOS 1 GB860000 packets/secclient/dest thread count
SmartOS 1 GBSmartOS 1 GB1678000 packets/secboth client & dest CPU cap
KVM Centos 1 GBKVM Centos 1 GB11180 packets/secnetwork/KVM latency, thread count (client thread about 10% CPU)
KVM Centos 1 GBKVM Centos 1 GB22300 packets/secnetwork/KVM latency, thread count
KVM Centos 1 GBKVM Centos 1 GB44400 packets/secnetwork/KVM latency, thread count
KVM Centos 1 GBKVM Centos 1 GB87900 packets/secnetwork/KVM latency, thread count (threads now using about 30% CPU each; plenty idle)
KVM Centos 1 GBKVM Centos 1 GB1613500 packets/secnetwork/KVM latency, thread count (~50% idle on both)
KVM Centos 1 GBKVM Centos 1 GB3218000 packets/secCPU (dest >90% of the 1 VCPU)

以上情况下,Zones 比 KVM 快4倍。同理,受限因素是云 CPU,我只测试了小型的 1 Gbyte 的服务器。更大的服务器有更高的 CPU 配额,那么所有的结果会扩展得到的更高的值。

结论

在这篇文章里,我总结了三种虚拟化技术(Zones, Xen, KVM)的性能特征,进一步调研了它们的 I/O 路径。Zones 没有额外开销,但是 Xen 和 KVM 都有。这些开销可能会限制网络吞吐量到原有的1/4。

缺省情况下,我们鼓励用户部署 Zones,为了性能、可观察性、简单(可调式性)。这意味着要为 SmartOS (我们基于 illumos 的操作系统,托管 Zones)编译用户的程序。万一用户必须要用 Linux 或者 Windows,或其之上应用程序,那么可以使用硬件虚拟化(KVM)。

还有更多的性能特征可以考虑,这里我没有探讨,只是简要地给了一些表格,包括 CPU如何分配,VCPU如何工作,内存分配如何工作,文件系统如何缓存等等。这些在后续的日志中会继续讨论。

这篇文章本来想过多地讨论 DTrace,但是它对于高性能计算是一个必备的工具,无法不提及。我们用它来提升 Zones 和 KVM 的整体性能、跟踪延迟异常、解释基准测试结果、研究多租户的影响,并提高应用程序和操作系统的性能。

转载于:https://www.cnblogs.com/zengkefu/p/5563545.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值