Redis7 主从复制

1、主从复制机制

在这里插入图片描述

通过info replication获取的主从信息:

  • master_link_status:up:表示从节点与主节点的连接状态,可以是up或down。
  • master_last_io_seconds_ago:4: 表示从节点最后一次与主节点通信的时间,单位是秒。
  • master_sync_in_progress:0: 表示从节点是否正在与主节点进行同步,可以是0或1。
  • slave_read_repl_offset:46567: 从节点读取的复制偏移量,即从节点已经接收了主节点发送的多少字节的命令。
  • slave_repl_offset:46567: 从节点复制的偏移量,即从节点实际执行了主节点发送的多少字节的命令,若与slave_read_repl_offset相差太大,则说明从机有很多数据还未处理。
  • slave_priority:100:表示从节点的优先级,用于在主节点故障时选择新的主节点,数值越小优先级越高。
  • slave_read_only:1:表示从节点是否只读,可以是0或1。
  • replica_announced:1:表示从节点是否向主节点报告自己的IP地址和端口号,可以是0或1。
  • connected_slaves:0: 当前节点连接的从节点数量。
  • master_failover_state: 当前是否有故障转移发生,no-failover或其他状态。
  • master_replid: 表示主节点的复制ID,用于标识一个复制流程。
  • master_replid2:表示主节点的第二个复制ID,用于在故障转移后进行部分重同步。
  • master_repl_offset:46567: 主节点复制的偏移量,即主节点已经发送了多少字节的命令给从节点。
  • second_repl_offset:40478: 从机在切换主机前原主机的复制偏移量master_repl_offset,用于故障切换时支持增量同步。
  • repl_backlog_active:1: 从机是否启用了复制积压缓冲区,可以是0或1。
  • repl_backlog_size:1048576: 配置复制积压缓冲区的大小默认1048576字节,即1M。
  • repl_backlog_first_byte_offset:39162: 从机复制积压缓冲区中第一个字节对应的偏移量。
  • repl_backlog_histlen:7406: 从机的复制积压缓冲区中实际存储的字节数。

主从复制系统使用三种主要机制工作:

  • 当Master、Slave连接良好时,Master通过向Slave发送命令流来保持Slave更新,以复制由于在Master对数据集产生的影响:客户端写入、Key过期或逐出、任何其他更改主数据集的操作。
  • 当Master、Slave由于网络问题或由于主从心跳检测超时等原因导致连接中断时,Slave将重新连接并尝试继续增量同步,尝试获取在断开连接期间丢失的命令流部分。
  • 当偏移量之后的数据已挤出挤压缓冲区或初次连接,则副本将要求全量同步。Master将创建其数据的快照并发送到Slave,然后在数据集更改时继续发送命令流

1.1 全量复制

​  Slave首次连接或执行过SLAVEOF no one则会向主服务器发送PSYNC ? -1(在2.8版本之前是SYNC),主动请求主服务器进行完整重同步。master收到后,会立即响应FULLRESYNC runid offset,从机保存Master的runid并将offset作为初始偏移量,之后Master会fork后台子线程执行bgsave命令并使用复制缓存区client-output-buffer(replication buffer)记录此后执行的写命令,bgsave执行完成后将生成的RDB文件发送给slave节点,然后master节点再将复制缓冲区内容以redis协议格式全部发送给slave节点。slave节点先删除旧数据,再将收到的RDB文件载入自己的内存,再加载所有收到缓冲区的内容,从而这样一次完整的数据同步。

若从机开启了AOF,则在全量复制阶段会触发重写机制

  若从节点开启了AOF,则触发bgrewriteaof,保证AOF文件更新至最新状态。如果主节点接收到多个并发的全量同步请求,则只会执行单个后台保存以服务于所有从机。

通过全量复制的过程可以看出,全量复制是非常重型的操作:

  • 主节点通过bgsave命令fork子进程进行RDB持久化,该过程是非常消耗CPU、内存(页表复制)、硬盘IO的;
  • 主节点通过网络将RDB文件发送给从节点,对主从节点的带宽都会带来很大的消耗
  • 从节点清空老数据、载入新RDB文件的过程是阻塞的,无法响应客户端的命令;且若执行bgrewriteaof,还将有额外的消耗。

在这里插入图片描述

1.2 增量复制

  如果从服务器已经复制过某个主服务器,那么从服务器在开始复制时将向主服务器发送PSYNC <rep_id> <offset>命令:其中rep_id是上次获取的Master的master_replid,而offset则是从服务器当前的复制偏移量+1。Master会通过这两个参数来判断应该对Slave执行哪种同步操作:Slave上报的master_replidMastermaster_replid1replid2其中一个是否相等,判断主从是否发生改变;offset+1来判断偏移量之后的数据是否还存在于积压缓冲区中。

当主机版本过低,收到psync如何处理?

  主服务器的版本低于Redis 2.8,识别不了PSYNC命令并返回-ERR回复,随后从服务器将向主服务器发送SYNC命令,并与主服务器执行全量同步。

1.2.1 复制偏移量

  主库和从库分别各自维护一个复制偏移量(info replication查看),在Mastermaster_repl_offset代表服务器的当前复制偏移量(总字节数),也代表Master通过命令流向从节点传递的字节数;slave_repl_offset代表从库完成同步的字节数。Slave会定期向Master报告已经处理了多少偏移量的数据,这样master就可以知道哪些Master已经同步了哪些命令。

​  如果MasterSlave之间的连接出现网络抖动,Slave会尝试增量同步,获取断开期间错过的命令流部分。否则,会请求进行全量同步,然后继续发送命令流同步。

1.2.2 积压缓冲区

在这里插入图片描述
​  Master中维护着一个复制积压缓冲区replication buffer,一个固定长度的FIFO队列,保存最近redis执行的命令和对应字节的复制偏移量offset,大小由配置参数repl-backlog-size指定(默认1MB),所有Slave共享此缓冲区,备份最近通过命令流同步给从库的数据(在主从命令传播阶段,Master不仅将写命令发送给Slave,还会备份一份到复制积压缓冲区)。当在命令传播阶段,主从断联恢复后,主库就会在repl_backlog_buffer中找到offset对应位置,并把之后的写命令写replication buffer同步给从库。

​  〖推荐大小〗:比如若网络中断的平均时间是60s,而主节点平均每秒产生的写命令字节数为100KB,则复制积压缓冲区的平均需求为6MB,保险起见可以设置为12MB,来保证绝大多数断线情况都可以使用部分复制。

client-output-buffer-limit slave复制缓冲区和repl-backlog-size积压缓冲区

​  复制缓冲区只在全量复制时使用,而复制积压缓冲区只在增量复制时使用。复制缓冲区是为了保证RDB文件传输期间的数据一致性,而复制积压缓冲区是为了保证网络断连期间的数据一致性。复制缓冲区是客户端输出缓冲区的一种,主节点会为每一个从节点分别分配复制缓冲区;而复制积压缓冲区则是一个主节点只有一个,无论它有多少个从节点。

1.2.3 Replication ID

  通过info replicationinfo server可以查看主从机的信息和runid。每个redis实例在启动时候,都会随机生成一个长度为40唯一随机字符串来标识当前redis节点复制idrepl_id。当主从复制在初次复制时,Slave接收来自Master的FULLRESYNC并将repl_id保存下来;当断线重连时,从节点会将这个repl_id发送给主节点,主节会判断能否进行部分复制。

注意: redis4.0之后,replication ID取代了runid,作为redis服务器的复制标识,连接到master的slave会在握手后继承其复制IDreplication ID而不再需要提供运行IDrunid

1.2.4 源码分析

  Masterreplication IDSlave通过PSYNC请求中的是否一致,如果复制 ID 更改,则此主服务器具有不同的复制历史,并且无法继续增量同步,必须全量同步。Master包含两个有效的复制IDmaster_replidmaster_replid2,但是对于master_replid2仅在特定偏移量second_replid_offset之前有效。

//replication.c:syncCommand->masterTryPartialResynchronization()
int masterTryPartialResynchronization(client *c, long long psync_offset) {
    long long psync_len;
    char *master_replid = c->argv[1]->ptr;
    char buf[128];
    int buflen;
    //满足这个条件,将进行全量同步
    if (strcasecmp(master_replid, server.replid) &&//当前server的ID不是master的ID
       //并且,主机旧的master不是从机的master 或 是同一个master但从机要求的偏移比自己接收的数据还大
       (strcasecmp(master_replid, server.replid2) || psync_offset > server.second_replid_offset)) {
        //携带的复制id为?,则意味着从机想强制进行全量同步
        /* Replid "?" is used by slaves that want to force a full resync. */
        if (master_replid[0] != '?') {
            if (strcasecmp(master_replid, server.replid) &&
                strcasecmp(master_replid, server.replid2))
            {
                serverLog(LL_NOTICE,"Partial resynchronization not accepted: "
                    "Replication ID mismatch (Replica asked for '%s', my "
                    "replication IDs are '%s' and '%s')",
                    master_replid, server.replid, server.replid2);
            } else {
                serverLog(LL_NOTICE,"Partial resynchronization not accepted: "
                    "Requested offset for second ID was %lld, but I can reply "
                    "up to %lld", psync_offset, server.second_replid_offset);
            }
        } else {
            serverLog(LL_NOTICE,"Full resync requested by replica %s",replicationGetSlaveName(c));
        }
        goto need_full_resync;
    }

    /* We still have the data our slave is asking for? */
    if (!server.repl_backlog || //主机是否开启了积压缓冲区
        psync_offset < server.repl_backlog->offset || //请求的offset小于了积压缓冲区起始的offset
        //或请求的offset大于积压缓冲区最新的offset,即超过已有数据范围将开启全量复制
        psync_offset > (server.repl_backlog->offset + server.repl_backlog->histlen)) {
        serverLog(LL_NOTICE,"Unable to partial resync with replica %s for lack of backlog" +
                  "(Replica request was: %lld).", replicationGetSlaveName(c), psync_offset);
        if (psync_offset > server.master_repl_offset) {
            serverLog(LL_WARNING,"Warning: replica %s tried to PSYNC with an offset that is " + 
                   "greater than the master replication offset.", replicationGetSlaveName(c));
        }
        goto need_full_resync;
    }

    //至此开始启动增量复制响应
    /* If we reached this point, we are able to perform a partial resync:
     * 1) Set client state to make it a slave.
     * 2) Inform the client we can continue with +CONTINUE
     * 3) Send the backlog data (from the offset to the end) to the slave. */
    c->flags |= CLIENT_SLAVE;//设置客户端状态使其成为slave
    c->replstate = SLAVE_STATE_ONLINE;//通知Slave:+CONTINUE
    c->repl_ack_time = server.unixtime;
    c->repl_start_cmd_stream_on_ack = 0;
    listAddNodeTail(server.slaves,c);
    /* We can't use the connection buffers since they are used to accumulate
     * new commands at this stage. But we are sure the socket send buffer is
     * empty so this write will never fail actually. */
    if (c->slave_capa & SLAVE_CAPA_PSYNC2) {
        buflen = snprintf(buf,sizeof(buf),"+CONTINUE %s\r\n", server.replid);
    } else {
        buflen = snprintf(buf,sizeof(buf),"+CONTINUE\r\n");
    }
    if (connWrite(c->conn,buf,buflen) != buflen) {
        freeClientAsync(c);
        return C_OK;
    }
    psync_len = addReplyReplicationBacklog(c,psync_offset);
    serverLog(LL_NOTICE,"Partial resynchronization request from %s accepted. " + 
              "Sending %lld bytes of backlog starting from offset %lld.",
              replicationGetSlaveName(c),psync_len, psync_offset);
    /* Note that we don't need to set the selected DB at server.slaveseldb
     * to -1 to force the master to emit SELECT, since the slave already
     * has this state from the previous connection with the master. */

    refreshGoodSlavesCount();

    /* Fire the replica change modules event. */
    moduleFireServerEvent(REDISMODULE_EVENT_REPLICA_CHANGE,
                          REDISMODULE_SUBEVENT_REPLICA_CHANGE_ONLINE,
                          NULL);

    return C_OK; /* The caller can return, no full resync needed. */

need_full_resync:
    /* We need a full resync for some reason... Note that we can't
     * reply to PSYNC right now if a full SYNC is needed. The reply
     * must include the master offset at the time the RDB file we transfer
     * is generated, so we need to delay the reply to that moment. */
    //如果需要完全同步,不能立即回复PSYNC请求,还必须包含RDB文件生成时的master偏移量
    return C_ERR;
}

1.3 命令传播阶段

  数据同步阶段完成后,主从节点进入命令传播阶段;在这个阶段主节点将自己执行的写命令通过复制流发送给从节点,从节点接收命令并执行,从而保证主从节点数据的一致性。在命令传播阶段,除了发送写命令,主从节点还维持着心跳机制PINGREPLCONF ACK

延迟与不一致: 命令传播是异步的过程,即主节点发送写命令后并不会等待从节点的回复;因此实际上主从节点之间很难保持实时的一致性,延迟在所难免。数据不一致的程度,与主从节点之间的网络状况、主节点写命令的执行频率、以及主节点中的repl-disable-tcp-nodelay配置等有关。

​  repl-disable-tcp-nodelay no:作用于命令传播阶段,控制Master是否禁止与Slave的TCP_NODELAY。默认no,TCP将主节点的数据立即发送给从节点,带宽增加但延迟变小。。为yes时,会使用Nagle算法对TCP包进行合并从而减少带宽,将发送频率降低,会使得Slave数据延迟增加和一致性变差。具体发送频率与Linux内核的配置有关,默认配置为40ms。一般来说,只有当应用对Redis数据不一致的容忍度较高,且主从节点之间网络状况不好时,才会设置为yes;多数情况使用默认值no。

2、配置及使用

2.1 Master配置

  • 开启Master防火墙端口: firewall-cmd --add-port=6379/tcp --permanent随后重启防火墙`firewall-cmd --reload``

  • ``repl-diskless-sync no:作用于全量复制阶段,控制主节点是否使用diskless`复制(无盘复制)。所谓diskless复制,是指在全量复制时,主节点不再先把数据写入RDB文件,而是直接写入slave的socket中,整个过程中不涉及硬盘;diskless复制在磁盘IO很慢而网速很快时更有优势。

  • repl-diskless-sync-delay 5:作用于全量复制阶段且diskless配置为yes时,当主节点使用diskless复制时,该配置决定主节点向从节点发送之前停顿的时间,单位秒,默认5s。之所以设置停顿时间,是基于以下两个考虑:①向slave的socket的传输一旦开始,新连接的slave只能等待当前数据传输结束,才能开始新的数据传输; ②多个从节点有较大的概率在短时间内建立主从复制。

  • client-output-buffer-limit slave 256MB 64MB 60:在全量复制阶段,限制主节点为每个从节点分配的复制缓冲区的大小:若复制缓冲区buffer大于256MB,或者60s内大于64M,则主节点会断开与该从节点的连接。全量复制期间主节点将bgsave生成RDB文件、主节点发往从节点RDB和从节点载入RDB期间执行的写命令存储在复制缓冲区中。如果主节点数据量大(Slave还没装载完rdb)或网络延迟高,可能导致缓冲区超限,引起全量复制和连接中断的循环

  • repl-backlog-size 1mb设置复制积压缓冲区的大小,一个环形缓冲区,主节点只有一个,所有从节点共享。复制积压缓冲区用来保存主节点最近的写命令,当主从连接断开后,重新建立连接,从节点可以通过复制积压缓冲区进行部分复制,而不需要进行全量同步。当复制积压缓冲区不足时,缓冲区发生覆写,导致重连的从机进行全量复制

  • repl-backlog-ttl 3600:当主节点没有从节点时,复制积压缓冲区保留的时间,这样当断开的从节点重新连进来时,可以进行部分复制;默认3600s。如果设置为0,则永远不会释放复制积压缓冲区。

  • repl-ping-slave-period 10REPLCONF ACK <offset>:当数据同步完成以后,在此主从维护着心跳检查来确认对方是否在线,每隔一段时间(默认10秒,repl-ping-slave-period)主节点向从节点发送PING命令判断从节点是否在线;而从节点每秒1次向主节点发送REPLCONF ACK <offset>,其中offset指从节点保存的复制偏移量,汇报自己复制偏移量并判断判断主节点是否在线,主节点会对比复制偏移量并向从节点发起增量同步,最终实现与主库数据相同。

  • repl-timeout:设置超时时间默认60秒,通过上两个参数判断。有两个作用:在全量复制过程中,若从节点在repl-timeout内未接收到主节点的数据,那么从节点会关闭连接并重新尝试复制;在主从节点正常通信时,若从节点在repl-timeout内未向主节点发送任何数据,那么主节点会认为从节点已经下线,并在INFO replication中显示该从节点的lag=-1

  • repl-disable-tcp-nodelay no:是否禁用TCP_NODELAY,控制主节点在命令传播阶段是否使用Nagle算法,以减少网络包的数量和带宽消耗,但是可能增加数据在从节点出现的延迟。

  • min-slaves-to-write 3min-slaves-max-lag 10:保证主节点在不安全的情况下不会执行写命令。规定了主节点在执行写命令时,最小从节点数目,及对应的最大延迟(Master接收REPLCONF ACK时间间隔),提高数据的可靠性和数据的一致性。

2.2 Slave配置

  • replicaof 192.168.1.1 6379:配置Master地址和端口,其中192.168.1.1 6379为主机IP地址(或主机名)和端口。也可调用 REPLICAOF/SLAVEOF ip port指定Master,slaveof no one表示恢复自己主机身份。
  • masterauth<password>:设置要向主机进行身份验证的从机,若主机配置了requirepass,那需要在从机中配置主机的密码来实现连接:运行中在命令行要使用redis cli并键入:`config set masterauth ``
  • slave-read-only yes:从节点是否只读;默认是只读的。由于从节点开启写操作容易导致主从节点的数据不一致,因此该配置尽量不要修改。
  • slave-serve-stale-data yes:控制从服务器在失去主服务器连接或复制进行中时是否响应旧数据,默认yes。如果对数据一致性要求很高则设置为no,从服务器会返回一个错误SYNC with master in progress给除了INFOSLAVEOF之外的所有请求。

3、主从复制问题

3.1 从机宕机恢复

​  在Redis4.0 之前psync有不足之处,当从库重启之后会丢失Master的runid,则从库将进行全量复制。在实际生产中时常需要对从库的维护并进行重启,所以应避免执行全量复制。因此redis 4.0优化了psync实现重启的情况下也能实现增量同步:新增两个复制ID,通过repl_id取代runid

  • master_replid: 每个redis启动时,都会为其生成40个字节的随机字符串,和runid没有直接关联,但生成规则相同。当变为从机后,master_replid会被Master的master_replid覆盖。
  • master_replid2:默认初始化为全0,默认初始化为全0,用于存储上次主实例的master_replid

​  redis通过shutdown save关闭,会调用rdbSaveInfoAuxFields函数,把当前repl-idrepl-offset保存到RDB文件中,当前的RDB存储的数据内容和复制信息可通过redis-check-rdb查看。当从机宕机重启后,redis加载RDB文件并处理其中的辅助字段AUX fields,把其中repl_idrepl_offset加载到实例并赋给master_replidmaster_repl_offset

当从库开启了AOF,则会失去部分复制机制

​  当从库开启了AOF持久化,redis加载顺序发生变化优先加载AOF文件,但是由于aof文件中没有复制信息,所以导致重启后从实例依旧使用全量复制。

3.2 数据过期

​  在单机版Redis中,存在两种删除策略:惰性删除,服务器不会主动删除数据,只有当客户端查询某个数据时,服务器判断该数据是否过期,如果过期则删除;定期删除,服务器执行定时任务删除过期数据,但是考虑到内存和CPU的折中(删除会释放内存,但是频繁的删除操作对CPU不友好),该删除的频率和执行时间都受到了限制。

​  在主从复制场景下不能依赖主从同步时间,为了主从节点的数据一致性,从节点不会主动删除数据,而是由主节点通过DEL控制从节点中过期数据的删除。由于主节点的惰性删除和定期删除策略,都不能保证主节点及时对过期数据执行删除操作,因此,当客户端通过Redis从节点读取数据时,很容易读取到已经过期的数据。

  Redis 3.2中可以解决数据过期问题,从节点在读取数据时,增加了对数据是否过期的判断:如果该数据已过期,则返回给客户端空值nil,避免了数据的脏读。Redis 3.2之后还引入了replica-ignore-maxmemory配置项(默认yes),从节点不会根据maxmemory设置进行内存淘汰,而是等待主节点发送DEL命令来删除过期的键,保证主从节点的数据一致性;若为no,则从节点会进行内存淘汰,可能导致主从节点的数据不一致。

3.3 主机宕机处理

  Redis4.0 之后,主机宕机重启之后,仍能与从机进行增量复制而非全量复制(前提是开启了RDB)。但实际上在主节点宕机的情况下,会利用哨兵自动进行故障转移处理,将其中的一个从节点升级为主节点,其他从节点从新的主节点进行复制。通过新增的两个复制ID和复制偏移量OFFSET来确保故障转移后增量复制的正常进行。

为什么需要两个复制 ID 和 OFFSET 呢?

​  当故障切换后,slave被提升为Master,会将前任主节点复制ID作为其次要复制IDmaster_replid2,并记录ID切换时的偏移量master_repl_offset+1并保存到second_repl_offset。这样,当其他slave将与新master同步时,将尝试使用旧主节点复制ID执行部分重新同步。但是当从机通过psync请求提供的master_repl_offset大于second_repl_offset,则从机将丢弃所有数据并做全量同步来确保数据的一致。

4、特点

  • 异步复制:使用异步复制,每次接收到写命令之后,先在内部写入数据,然后异步发送给slave服务器。且从Redis 2.8开始,从服务器通过config ack offset周期性的应答处理复制流中的数据量情况。
  • 负载均衡:在主从复制的基础上,配合读写分离,主写从读,分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。
  • 数据冗余,读写分离:使用从机取代Master进行数据备份持久化,实现数据热备份及持久化数据冗余处理,免除Master将数据写入磁盘的消耗。**注意:**若主机没有配置持久化,应该关闭其主机自动重启,因为可能直接跳过Redis Sentinel过程,致使副本数据也被销毁。
  • 故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;
  • 高可用基石:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。
  • 主从复制不阻塞slave服务器。当master服务器进行初始同步时,slave服务器返回的是以前旧版本的数据,可配置slave-serve-stale-datano,则Slave在同步过程收到的查询请求将返回错误(在删除旧数据集和加载新的数据集,在这个短暂时间内,从服务器仍会阻塞连接进来的请求)
Redis主从复制是一种常用的数据复制和高可用性方案,它通过将一个Redis实例(主节点)的数据复制到其他Redis实例(从节点)来实现数据的备份和读写分离。下面是Redis配置主从复制的步骤: 1. 配置主节点: - 打开主节点的配置文件redis.conf。 - 将配置项`bind`设置为主节点的IP地址。 - 将配置项`port`设置为主节点的端口号。 - 将配置项`daemonize`设置为yes,表示以守护进程方式运行。 - 将配置项`logfile`设置为日志文件路径。 - 将配置项`dir`设置为持久化文件的存储路径。 - 将配置项`appendonly`设置为yes,开启AOF持久化方式(可选)。 - 保存并关闭配置文件。 2. 启动主节点: - 打开终端,进入Redis安装目录。 - 执行命令`redis-server redis.conf`启动主节点。 3. 配置从节点: - 复制主节点的配置文件redis.conf到从节点。 - 打开从节点的配置文件redis.conf。 - 将配置项`bind`设置为从节点的IP地址。 - 将配置项`port`设置为从节点的端口号。 - 将配置项`daemonize`设置为yes,表示以守护进程方式运行。 - 将配置项`logfile`设置为日志文件路径。 - 将配置项`dir`设置为持久化文件的存储路径。 - 将配置项`appendonly`设置为yes,开启AOF持久化方式(可选)。 - 将配置项`replicaof`设置为主节点的IP地址和端口号,格式为`replicaof <masterip> <masterport>`。 - 保存并关闭配置文件。 4. 启动从节点: - 打开终端,进入Redis安装目录。 - 执行命令`redis-server redis.conf`启动从节点。 至此,Redis主从复制配置完成。主节点会将数据同步到从节点,从节点可以处理读请求,提高系统的读取性能和可用性。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值