ETCD 从入门到精通-深入浅出ETCD系列文章

第一模块:ETCD 基础概念与核心功能

1.1 什么是 ETCD?

ETCD 是一个开源的分布式键值存储系统(Key-Value Store),由 CoreOS 团队开发并捐赠给云原生计算基金会(CNCF)。它的设计目标是提供高可用性强一致性的分布式数据管理服务,常用于分布式系统的配置管理、服务发现、分布式锁等场景。

核心特点

  • 简单性:基于 HTTP/JSON 或 gRPC 的 API 设计。

  • 强一致性:使用 Raft 共识算法保证数据一致性。

  • 高可用性:支持多节点集群部署,容忍部分节点故障。

  • 持久化存储:数据默认持久化到磁盘,支持快照和恢复。


1.2 为什么需要 ETCD?

在分布式系统中,多个服务实例需要共享配置或协调状态。传统的关系型数据库难以满足高并发和低延迟的需求,而 ETCD 通过以下特性解决了这些问题:

  • 低延迟读写:基于内存的键值存储,读写性能优异(每秒数万次操作)。

  • 分布式协同:天然支持分布式场景下的数据同步。

  • 与 Kubernetes 深度集成:Kubernetes 使用 ETCD 作为其默认的元数据存储组件。

示例场景
假设有一个微服务集群,需要动态调整所有服务的日志级别。通过 ETCD 存储日志配置,服务实例监听 ETCD 中的配置变更,即可实现实时更新。


1.3 ETCD 的核心功能
  1. 键值存储(Key-Value Storage)
    ETCD 的核心功能是存储键值对,支持以下操作:

    • PUT:插入或更新键值。

    • GET:获取键值。

    • DELETE:删除键值。

    • WATCH:监听键值变化。

    示例:通过 etcdctl 命令行工具操作键值:

    # 写入键值
    etcdctl put /config/app1/log_level "debug"
    
    # 读取键值
    etcdctl get /config/app1/log_level
    # 输出:/config/app1/log_level debug
    
    # 监听键值变化(另开一个终端执行)
    etcdctl watch /config/app1/log_level

  2. 租约(Lease)
    通过租约机制实现键值的自动过期。租约需要定期续期,否则绑定的键值会被自动删除。

    示例:创建一个 10 秒的租约并绑定键值:

    # 创建租约(返回 Lease ID)
    etcdctl lease grant 10
    # 输出:lease 12345abcd granted with TTL(10s)
    
    # 将键值绑定到租约
    etcdctl put --lease=12345abcd /temp/session "active"
     
  3. 事务(Transaction)
    支持多操作的原子性提交,类似数据库事务。

    示例:条件更新(只有键 /resource/lock 不存在时才写入):

    etcdctl txn --interactive
    # 输入以下内容:
    compare:
    mod("/resource/lock") = 0  # 检查键的修改版本是否为 0(不存在)
    
    success requests:           # 条件成立时执行的操作
    put /resource/lock "owner1"
    
    failure requests:           # 条件不成立时执行的操作
    get /resource/lock

1.4 快速入门:单节点部署与操作
步骤 1:安装 ETCD

从 ETCD GitHub Release 下载二进制文件(以 Linux 为例):

wget https://github.com/etcd-io/etcd/releases/download/v3.5.0/etcd-v3.5.0-linux-amd64.tar.gz
tar xzvf etcd-v3.5.0-linux-amd64.tar.gz
cd etcd-v3.5.0-linux-amd64
步骤 2:启动单节点 ETCD
./etcd --listen-client-urls http://0.0.0.0:2379 --advertise-client-urls http://localhost:2379
步骤 3:使用 etcdctl 测试 
# 写入数据
./etcdctl put /test/key1 "Hello ETCD"

# 读取数据
./etcdctl get /test/key1
# 输出:/test/key1 Hello ETCD

# 监听数据变化(新终端执行)
./etcdctl watch /test/key1
1.5 实例:用代码操作 ETCD

以下是一个使用 Go 语言操作 ETCD 的简单示例:

package main

import (
    "context"
    "fmt"
    "time"
    "go.etcd.io/etcd/client/v3"
)

func main() {
    // 创建客户端
    cli, err := clientv3.New(clientv3.Config{
        Endpoints:   []string{"http://localhost:2379"},
        DialTimeout: 5 * time.Second,
    })
    if err != nil {
        panic(err)
    }
    defer cli.Close()

    // 写入键值
    ctx := context.Background()
    _, err = cli.Put(ctx, "/config/app1/version", "1.0.0")
    if err != nil {
        panic(err)
    }

    // 读取键值
    resp, err := cli.Get(ctx, "/config/app1/version")
    if err != nil {
        panic(err)
    }
    fmt.Printf("当前版本: %s\n", resp.Kvs[0].Value)

    // 监听键值变化
    watchChan := cli.Watch(ctx, "/config/app1/version")
    for resp := range watchChan {
        for _, ev := range resp.Events {
            fmt.Printf("事件类型: %s, 新值: %s\n", ev.Type, ev.Kv.Value)
        }
    }
}

第二模块:ETCD 集群架构与高可用性设计

2.1 ETCD 集群的核心架构

ETCD 采用多节点集群架构,每个节点通过 Raft 共识算法实现数据同步。集群中的节点分为以下角色:

  • Leader:负责接收客户端请求、管理日志复制和提交操作。

  • Follower:被动接收 Leader 的日志并同步数据。

  • Candidate:在 Leader 失效时参与选举的临时角色。

集群的最小规模
为保证高可用性,ETCD 集群至少需要 3 个节点(容忍 1 个节点故障)。5 节点集群可容忍 2 个节点故障。


2.2 Raft 共识算法详解

Raft 是 ETCD 实现强一致性的核心算法,其核心流程分为以下阶段:

  1. Leader 选举

    • 节点初始状态为 Follower。

    • 若 Follower 在选举超时(默认 1s)内未收到 Leader 的心跳,会变为 Candidate 并发起选举。

    • 获得超过半数节点投票的 Candidate 成为新 Leader。

    示例
    假设 3 节点集群(A、B、C)中 Leader A 宕机:

    • B 和 C 在 1s 后成为 Candidate。

    • B 可能获得 B 和 C 的投票成为新 Leader。

  2. 日志复制

    • Leader 将客户端请求封装为日志条目(Log Entry)。

    • Leader 将日志条目广播给所有 Followers。

    • 当超过半数节点确认后,日志条目被提交并应用到状态机。

  3. 脑裂与安全性

    • Raft 通过**任期(Term)日志索引(Index)**避免脑裂问题。

    • 每个操作必须由 Leader 发起,确保全局顺序一致性。


2.3 搭建一个 3 节点 ETCD 集群
环境准备
  • 3 台服务器:node1 (10.0.0.1), node2 (10.0.0.2), node3 (10.0.0.3)

  • 安装 ETCD 二进制文件(同第一模块步骤)。

节点配置

node1 启动命令

./etcd \
  --name node1 \
  --initial-advertise-peer-urls http://10.0.0.1:2380 \
  --listen-peer-urls http://0.0.0.0:2380 \
  --listen-client-urls http://0.0.0.0:2379 \
  --advertise-client-urls http://10.0.0.1:2379 \
  --initial-cluster-token my-etcd-cluster \
  --initial-cluster "node1=http://10.0.0.1:2380,node2=http://10.0.0.2:2380,node3=http://10.0.0.3:2380" \
  --initial-cluster-state new
 

node2 和 node3 的配置类似,需修改 --name 和 IP 地址。

验证集群状态

# 查看集群成员列表
etcdctl --endpoints=http://10.0.0.1:2379 member list

# 输出示例:
# 2d3d4c6e5f7a8b9c, started, node1, http://10.0.0.1:2380, http://10.0.0.1:2379
# 3e4f5a6b7c8d9e0f, started, node2, http://10.0.0.2:2380, http://10.0.0.2:2379
# 4a5b6c7d8e9f0a1b, started, node3, http://10.0.0.3:2380, http://10.0.0.3:2379

# 检查集群健康状态
etcdctl --endpoints=http://10.0.0.1:2379 endpoint health
# 输出:http://10.0.0.1:2379 is healthy
 

2.4 高可用性实践:故障恢复与数据迁移
场景 1:Leader 节点宕机
  • 自动恢复:Raft 算法会在 1s 内选举新 Leader。

  • 手动干预(可选):通过 etcdctl endpoint status 查看新 Leader。

场景 2:节点永久故障(如 node3 硬盘损坏)
  1. 移除故障节点

    etcdctl --endpoints=http://10.0.0.1:2379 member remove 4a5b6c7d8e9f0a1b
     
  2. 新增节点

    etcdctl --endpoints=http://10.0.0.1:2379 member add node4 --peer-urls=http://10.0.0.4:2380
     
  3. 启动新节点(node4):

    ./etcd \
      --name node4 \
      --initial-cluster "node1=http://10.0.0.1:2380,node2=http://10.0.0.2:2380,node4=http://10.0.0.4:2380" \
      --initial-cluster-state existing
     

2.5 安全配置:TLS 加密与访问控制
生成 TLS 证书(使用 cfssl)

# 安装 cfssl 工具
go install github.com/cloudflare/cfssl/cmd/cfssl@latest
go install github.com/cloudflare/cfssl/cmd/cfssljson@latest

# 生成 CA 证书
cfssl gencert -initca ca-csr.json | cfssljson -bare ca

# 生成服务器证书(包含所有节点 IP 和域名)
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=server server-csr.json | cfssljson -bare server
 
启动 ETCD 集群启用 TLS

./etcd \
  --name node1 \
  --client-cert-auth \
  --trusted-ca-file=ca.pem \
  --cert-file=server.pem \
  --key-file=server-key.pem \
  --peer-client-cert-auth \
  --peer-trusted-ca-file=ca.pem \
  --peer-cert-file=server.pem \
  --peer-key-file=server-key.pem
 
配置基于角色的访问控制(RBAC)
  1. 创建用户和角色

    etcdctl user add root --new-user-password=123456
    etcdctl role add admin
    etcdctl user grant-role root admin
    etcdctl auth enable
     
  2. 授权键值路径

    etcdctl role grant-permission admin readwrite /config/app1/*
     

2.6 性能调优与监控
关键性能参数
  • --heartbeat-interval:Leader 发送心跳的间隔(默认 100ms)。

  • --election-timeout:Follower 等待 Leader 心跳的超时时间(默认 1000ms)。

  • --snapshot-count:触发快照的日志条目数(默认 100,000)。

优化建议
在低延迟网络中可调整 --heartbeat-interval=50ms 和 --election-timeout=500ms

监控指标(集成 Prometheus)

ETCD 暴露的监控端点:http://localhost:2379/metrics

关键指标

  • etcd_server_leader_changes_seen_total:Leader 切换次数。

  • etcd_disk_wal_fsync_duration_seconds:WAL 日志同步延迟。

  • etcd_network_peer_round_trip_time_seconds:节点间网络延迟。


2.7 实例:模拟大规模写入与性能测试
使用 benchmark 工具测试

./etcdctl benchmark \
  --endpoints=http://10.0.0.1:2379,http://10.0.0.2:2379,http://10.0.0.3:2379 \
  --total=10000 \
  --conns=100 \
  --clients=1000 \
  put
 

输出结果示例

Summary:
  Total:        2.5 seconds
  Requests/sec: 4000
  Avg latency:  25ms
  Max latency:  150ms
 
性能瓶颈分析
  • 网络延迟:跨地域部署时需优化节点间带宽。

  • 磁盘 IO:使用 SSD 并禁用 atime 挂载选项。

  • 内存压力:监控 etcd_memory_usage_bytes 避免 OOM。 

    第三模块:ETCD 高级特性与实战场景

    3.1 数据版本控制与历史回溯

    ETCD 通过 多版本并发控制(MVCC) 实现数据的版本管理,每个键的修改都会生成新的修订版本号(Revision)。
    核心机制

    • 全局单调递增的 Revision:所有键值操作(包括删除)均会递增 Revision。

    • 历史版本保留:默认保留所有历史版本,可通过压缩(Compact)机制清理旧数据。

    示例:查看键的历史版本

    # 写入初始值
    etcdctl put /config/app1/feature_flag "v1"
    
    # 更新值
    etcdctl put /config/app1/feature_flag "v2"
    etcdctl put /config/app1/feature_flag "v3"
    
    # 查看所有历史版本(需指定 --rev 范围)
    etcdctl get /config/app1/feature_flag --rev=0 -w json
    # 输出:{"kvs":[{"key":"L2NvbmZpZy9hcHAxL2ZlYXR1cmVfZmxhZw==","create_revision":2,"mod_revision":2,"version":1,"value":"djE="}...]}
    
    # 回滚到指定版本(需重新写入)
    etcdctl get /config/app1/feature_flag --rev=2
    # 输出旧值:v1
     
    
    数据压缩与碎片整理

    # 压缩指定 Revision 之前的所有历史版本
    etcdctl compact 5
    
    # 触发碎片整理(释放存储空间)
    etcdctl defrag
     
    
    3.2 分布式锁与选主机制

    ETCD 提供原子性操作和租约机制,天然支持分布式锁和领导者选举。

    场景 1:实现分布式锁(Go 示例)

    func acquireLock(cli *clientv3.Client, lockKey string, timeout time.Duration) error {
        // 创建租约(TTL 为 10 秒)
        resp, err := cli.Grant(context.Background(), 10)
        if err != nil {
            return err
        }
        leaseID := resp.ID
    
        // 尝试获取锁(事务操作)
        txn := cli.Txn(context.Background())
        txn.If(clientv3.Compare(clientv3.CreateRevision(lockKey), "=", 0).
            Then(clientv3.OpPut(lockKey, "locked", clientv3.WithLease(leaseID))).
            Else(clientv3.OpGet(lockKey))
        txnResp, err := txn.Commit()
        if err != nil {
            return err
        }
    
        if !txnResp.Succeeded {
            return fmt.Errorf("锁已被占用")
        }
    
        // 自动续约(保持锁)
        keepAliveCh, err := cli.KeepAlive(context.Background(), leaseID)
        if err != nil {
            return err
        }
        go func() {
            for range keepAliveCh {
                // 维持租约活跃
            }
        }()
        return nil
    }
     
    
    场景 2:领导者选举

    # 使用 etcdctl 实现选主
    etcdctl elect my-election-key "node1" --ttl=60
    # 其他节点执行相同命令时会阻塞,直到当前 Leader 释放锁或超时
     
    
    3.3 ETCD 在 Kubernetes 中的深度应用

    Kubernetes 使用 ETCD 存储所有集群状态数据,包括:

    • Pod、Service、Deployment 等资源定义

    • 节点状态、ConfigMap、Secret

    • 事件(Events)和审计日志

    关键数据路径示例

    # 查看所有 Pod 信息
    etcdctl get /registry/pods --prefix
    
    # 查看某个 Deployment
    etcdctl get /registry/deployments/default/my-app
    
    # 直接修改资源(危险操作!需谨慎)
    etcdctl put /registry/configmaps/default/my-config '{"data":{"key":"value"}}'
     
    
    调优 Kubernetes 的 ETCD 性能
    1. 分离 Kubernetes 事件存储
      为事件数据分配独立的 ETCD 集群,避免影响核心元数据性能。

      # kube-apiserver 配置
      --etcd-servers-overrides=/events#http://etcd-events:2379
       
    2. 限制 List 操作的影响
      使用分页查询和资源版本控制,避免全量拉取数据。

      kubectl get pods --chunk-size=500
       

    3.4 大规模集群运维挑战与解决方案
    挑战 1:数据增长与存储优化
    • 问题:ETCD 默认存储所有历史版本,数据量可能爆炸式增长。

    • 解决方案

      • 定期执行压缩:etcdctl compact + etcdctl defrag

      • 启用自动压缩策略:

        etcd --auto-compaction-retention=24h  # 保留最近 24 小时的历史
         
    挑战 2:跨地域集群的脑裂风险
    • 问题:跨地域部署时网络分区可能导致脑裂。

    • 解决方案

      • 使用 Proxy 模式部署本地缓存节点。

      • 配置更长的 --election-timeout(如 5 秒)以适应高延迟网络。

    挑战 3:备份与灾难恢复

    全量备份与恢复

    # 创建快照
    etcdctl snapshot save snapshot.db
    
    # 恢复快照到新集群
    etcdctl snapshot restore snapshot.db \
      --initial-cluster-token=new-cluster \
      --initial-advertise-peer-urls=http://new-node:2380
     
    

    增量备份策略
    结合 WAL(Write-Ahead Log)日志和定期快照,实现实时备份。


    3.5 实战:构建高可用 CI/CD 系统的配置中心
    架构设计
    • ETCD 集群:存储流水线配置、任务状态、分布式锁。

    • 微服务:监听 /pipelines/<id>/status 的变化触发操作。

    • Worker 节点:通过选举机制竞争任务执行权。

    关键代码片段(监听配置变化)

    func watchPipelineChanges(cli *clientv3.Client) {
        watchChan := cli.Watch(context.Background(), "/pipelines/", clientv3.WithPrefix())
        for resp := range watchChan {
            for _, ev := range resp.Events {
                switch ev.Type {
                case clientv3.EventTypePut:
                    var pipeline Pipeline
                    json.Unmarshal(ev.Kv.Value, &pipeline)
                    if pipeline.Status == "pending" {
                        go triggerPipelineExecution(pipeline.ID)
                    }
                case clientv3.EventTypeDelete:
                    cleanupPipelineResources(string(ev.Kv.Key))
                }
            }
        }
    }
     
    
    性能验证

    # 模拟 1000 条流水线配置更新
    benchmark put --key-size=8 --val-size=1024 --total=1000 --endpoints=etcd-cluster:2379
    # 结果:平均写入延迟 < 50ms,满足实时性要求
     
    

    第四模块:ETCD 生产环境最佳实践与故障排查

    4.1 生产环境部署的黄金法则
    硬件与网络要求
    • 推荐配置

      • CPU:4 核以上(高并发场景建议 8 核)

      • 内存:16GB 起步(ETCD 依赖内存加速读写)

      • 磁盘:SSD,建议 IOPS > 5000,吞吐量 > 200MB/s

      • 网络:节点间延迟 < 10ms,带宽 >= 1Gbps

    • 禁止事项

      • 混合部署:避免与计算密集型服务(如数据库)共享节点

      • 使用 NFS 或网络存储:ETCD 需要低延迟本地磁盘

    配置模板(3 节点集群)

    # etcd.service 示例(Systemd 配置)
    [Unit]
    Description=ETCD Server
    After=network.target
    
    [Service]
    Type=notify
    ExecStart=/usr/local/bin/etcd \
      --name ${NODE_NAME} \
      --data-dir /var/lib/etcd \
      --listen-client-urls https://0.0.0.0:2379 \
      --advertise-client-urls https://${NODE_IP}:2379 \
      --listen-peer-urls https://0.0.0.0:2380 \
      --initial-advertise-peer-urls https://${NODE_IP}:2380 \
      --cert-file=/etc/etcd/ssl/server.pem \
      --key-file=/etc/etcd/ssl/server-key.pem \
      --client-cert-auth \
      --trusted-ca-file=/etc/etcd/ssl/ca.pem \
      --auto-compaction-retention=24h \
      --snapshot-count=10000 \
      --heartbeat-interval=100 \
      --election-timeout=1000
    
    Restart=always
    LimitNOFILE=65536
    
    [Install]
    WantedBy=multi-user.target
     
    
    4.2 监控与告警体系建设
    Prometheus 监控指标分类
    指标类型关键指标示例告警阈值建议
    节点健康etcd_server_has_leader== 0(持续 1 分钟)
    存储性能etcd_disk_wal_fsync_duration_secondsP99 > 500ms
    网络延迟etcd_network_peer_round_trip_time_seconds平均值 > 200ms
    内存压力process_resident_memory_bytes> 物理内存的 70%
    Grafana 仪表盘配置示例

    {
      "panels": [
        {
          "title": "Raft 提案速率",
          "type": "graph",
          "targets": [{
            "expr": "rate(etcd_server_proposals_committed_total[1m])",
            "legendFormat": "{{instance}}"
          }]
        },
        {
          "title": "存储容量",
          "type": "singlestat",
          "targets": [{
            "expr": "etcd_mvcc_db_total_size_in_bytes / 1e9",
            "format": "GB"
          }]
        }
      ]
    }
     
    
    4.3 常见故障场景与排查指南
    场景 1:客户端报错 "etcdserver: request timed out"
    • 可能原因

      1. 网络分区导致 Leader 不可达

      2. 磁盘 IO 饱和,WAL 写入阻塞

      3. 大量未提交提案堆积

    • 排查步骤

      # 检查 Leader 状态
      etcdctl endpoint status --cluster -w table
      
      # 查看磁盘 IO 情况
      iostat -x 1
      
      # 分析 pending 提案数量
      etcdctl --endpoints=$ENDPOINTS endpoint status | grep pending
       
    场景 2:集群频繁 Leader 切换
    • 诊断命令

      # 查看选举次数变化率
      watch -n 1 "etcdctl endpoint status | grep -E 'Leader|State'"
      
      # 检查网络丢包
      tcptraceroute -n $PEER_IP 2380
      
      # 分析 Raft 日志
      journalctl -u etcd | grep -i 'raft|leader'
       
    场景 3:数据库大小异常增长
    • 根因分析工具

      # 统计 key 空间占用(需 etcd v3.5+)
      etcdctl check perf --load=s --auto-compact=no
      
      # 输出示例:
      # KEY                 COUNT         SIZE
      /registry/pods       15234         2.1GB
      /registry/events     98432         4.7GB
       

    4.4 性能调优实战:百万级配置项管理
    优化策略
    1. Key 设计优化

      • 避免宽目录:/config/env/prod/app1/db 优于 /config/prod_app1_db

      • 使用压缩格式:存储 JSON 时启用 gzip(客户端侧处理)

    2. 批量操作模式

      // Go 批量写入示例
      ops := make([]clientv3.Op, 0)
      for i := 0; i < 1000; i++ {
          ops = append(ops, clientv3.OpPut(fmt.Sprintf("/keys/%d", i), "value"))
      }
      _, err := cli.Txn(ctx).Then(ops...).Commit()
       
    3. 客户端连接池配置

      # 客户端配置参数
      max-connections: 50
      keep-alive-time: 30s
      max-retries: 3
       
    压力测试结果对比
    优化措施写入 QPS平均延迟P99 延迟
    默认配置12,00045ms210ms
    批量写入 + 连接池58,00018ms95ms

    4.5 灾备方案设计:跨区域多活集群
    架构设计
               +-----------------+
               |   Global LB     |
               +-----------------+
                      |
          +------------+------------+
          |            |            |
    +------------+ +------------+ +------------+
    |  Region A  | |  Region B  | |  Region C  |
    | 3-node etcd| | 3-node etcd| | 3-node etcd|
    +------------+ +------------+ +------------+
    关键技术点
    1. 异步复制:使用 etcd 的 Learner 节点跨区域同步

      # 添加 Learner 节点
      etcdctl member add learner1 --peer-urls=http://region-b:2380 --learner
       
    2. 流量调度

      • 写操作定向到主区域

      • 读操作就近访问本地 Learner

    3. 冲突解决

      • 基于时间戳的 Last-Write-Win 策略

      • 业务层幂等设计

    容灾演练步骤
    1. 模拟主区域网络中断

    2. 观察 Global LB 自动切换写入区域

    3. 验证 Learner 数据最终一致性

    4. 恢复后执行数据一致性校验

      etcdctl make-mirror --dest-prefix=/backup /source-prefix
       

    4.6 真实案例:某电商大促期间的 ETCD 故障复盘
    背景
    • 现象:大促开始 5 分钟后 Kubernetes 控制平面瘫痪

    • 集群规模:5 节点 ETCD,存储 120 万 key

    根因分析
    1. 直接原因

      • 监控系统频繁全量拉取 /registry/events(每秒 200 次 List 操作)

      • 导致 ETCD CPU 飙升至 90%

    2. 深层问题

      • 未启用客户端查询限流

      • 事件存储未与主集群隔离

    解决方案
    1. 紧急措施

      # 临时限制 events 的 List 权限
      etcdctl role revoke-permission events read /registry/events/
       
    2. 长期优化

      • 部署独立的事件存储集群

      • 启用 APIServer 的 --watch-cache 和 --watch-cache-sizes

    改进后效果
    指标故障前改进后
    ETCD CPU90%35%
    List 操作 QPS2005

    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

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

    抵扣说明:

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

    余额充值