分布式知识全景图:Java开发者的进阶必修课

AI编程·六月创作之星博客挑战赛 10w+人浏览 1.6k人参与

你有没有过这样的体验——跟朋友聊天,对方突然冒出一句"我们系统是分布式的",你一边点头一边心里嘀咕:分布式到底是啥?面试官问"你们怎么保证分布式事务一致性",你脑子里闪过无数个关键词(2PC、TCC、Seata、RocketMQ……)却一个也说不清楚?

别慌,你不是一个人。大多数 Java 开发者的日常是和单机系统打交道的——一个 Spring Boot 应用连一个 MySQL,世界和平。但一旦系统用户量上来、数据量飙升,单机就成了瓶颈。分布式不是"高大上"的炫技,而是业务增长的必然结果。

本文是一份分布式知识全景图——不要求你有任何分布式经验,从零开始帮你梳理:作为 Java 开发,到底需要学哪些分布式知识、它们之间的关系是什么、面试中最常被问到的点又是什么。读完本文,你至少能自信地画出一张分布式知识图谱,跟面试官聊上半小时不卡壳。


一、开篇:到底什么是"分布式"?

1.1 从你熟悉的单体聊起

想象你开了一家奶茶店。一开始,你一个人干所有事:点单、做茶、收钱、擦桌子。这就是单体架构——所有功能打包在一个进程里。

生意好了,你雇了三个人:小张点单、小王做茶、小李收钱。三个人各管一摊,通过喊话协调("大杯珍珠奶茶一杯!")。这就是分布式:一个系统由多个独立运行的节点组成,节点之间通过网络通信来协作完成任务。

// 单体:一个方法搞定一切
public String orderMilktea(String flavor) {
    // 收钱、做茶、打包……全在这里
}

// 分布式:多个服务协作
public String orderMilktea(String flavor) {
    paymentService.pay(amount);        // 收钱(远程调用)
    kitchenService.make(flavor);       // 做茶(远程调用)
    deliveryService.assign(riderId);   // 配送(远程调用)
}

1.2 什么时候必须"分布式"?

不是所有系统都需要分布式,以下几个信号出现时再考虑:

  1. 单机扛不住:QPS 上万,一台服务器的 CPU/内存被你写满了
  2. 数据太多:MySQL 单表上亿行,查询从 10ms 变成了 10 秒
  3. 不能挂:老板说"服务挂了影响公司声誉",要求 99.99% 可用
  4. 团队变大:几十号工程师改同一个项目,改一行代码得等半天 Code Review

黄金法则:能用单机解决的问题别上分布式。分布式引入了网络延迟、数据一致性和运维复杂度——这些是实打实的成本。

1.3 分布式 ≠ 微服务

很多人把这俩词混着用,但它们不是一回事:

  • 分布式:系统部署在多台机器上,节点间通过网络协作(物理视角)
  • 微服务:按业务领域拆分成独立服务,每个服务有自己的数据库(架构视角)
  • 微服务一定是分布式的,分布式不一定是微服务。一台机器上跑三个 Tomcat 连同一个数据库,也是分布式,但不是微服务。

好了,概念说清楚了。接下来我们从最核心的理论开始——理论之所以重要,是因为它能帮你在面对各种"看起来不一样"的问题时,快速找到本质。


二、理论基础:CAP 和 BASE——读懂这两个词,面试就赢了一半

2.1 CAP 定理:为什么分布式系统不能"全都要"?

CAP 是分布式领域最有名的定理,也是面试最高频的考点之一。它说的是:一个分布式系统最多只能同时满足以下三点中的两点

字母全称含义一句话理解
CConsistency所有节点在同一时刻看到的数据完全一致"我写进去,你马上读到"
AAvailability每个非故障节点都能在合理时间内返回合理响应"你有请求,我就有响应"
PPartition Tolerance网络分区(节点之间断联)时,系统仍能正常运作"网断了,系统不能崩"
为什么 P 是"必须选"的?

因为网络分区不是你"选不选"的问题——网络延迟、网线松动、交换机故障都可能造成事实上的分区。对于分布式系统,P 是无法回避的现实。所以真正的选择题是:当网络出问题时,你选 C 还是选 A?

P(无法割舍)+ C(一致性优先)→ 银行转账场景,宁可暂时不可用也不能少钱
P(无法割舍)+ A(可用性优先)→ 微博热搜,先让你看到(可能不是最新的),比转圈圈强

2.2 实践中常见的 CAP 选择

产品倾向理由
ZooKeeperCP分布式协调场景,一致性优先
EurekaAP服务注册中心,挂了节点其他节点继续服务
NacosCP/AP 可切换灵活,满足不同场景
Redis ClusterAP缓存场景,可用性优先
MySQL 主从集群AP(异步复制时)从库数据可能落后主库

面试技巧:当被问到 CAP 时,不要只背定义。补一句"在互联网场景中,通常选择 AP,因为保证可用性才能让业务不中断,最终一致性由后续机制补上"——这证明你不仅懂理论,还知道实践中的取舍。

2.3 BASE 理论:CAP 的现实妥协方案

既然 CAP 说"不能全都要",那现实中怎么妥协?答案就是 BASE 理论

字母含义大白话
BA (Basically Available)基本可用系统出问题时允许损失部分功能,但核心功能必须能用
S (Soft State)软状态允许系统中的数据存在中间状态(数据同步有延迟)
E (Eventually Consistent)最终一致性不要求数据实时一致,但保证经过一段时间后,所有副本都一致

BASE 是 AP 方案的升级版——它承认"做不到强一致",但提出了一套务实的标准。

CAP 是"这件事我做不到",BASE 是"做不到你要求的,但我可以做到这些"。


理论部分结束。接下来我们进入"看得见摸得着"的部分——分布式通信。毕竟分布式系统的各个节点要协作,首先得能"说上话"。


三、分布式通信:一个请求如何在万千服务器之间旅行?

3.1 HTTP vs RPC:你天天用 HTTP,为什么还要学 RPC?

很多初学者会困惑:我发个 HTTP 请求不就完了,还要 RPC 干嘛?

维度HTTP/RESTRPC(如 Dubbo、gRPC)
设计理念面向资源(GET /users/1)面向方法(userService.getUser(1))
序列化JSON/XML 文本格式Protobuf/Hessian 二进制格式
性能一般(文本+HTTP 头开销大)高(二进制+Tcp 长连接)
跨语言天然支持需要多语言协议支持(gRPC 支持好)
适用场景对外 API、前后端通信、跨系统对接内部微服务间高频调用

打个比方:HTTP 像寄明信片——格式标准,谁都能看懂,但写不了太多内容;RPC 像打电话——不需要每次自报家门,速度快,但两个人得说同一种语言。

3.2 常见的 RPC 框架选型

框架特点建议
Dubbo阿里出品,国内占有率最高,Java 生态完善纯 Java 微服务首选
gRPCGoogle 出品,基于 HTTP/2 + Protobuf多语言混编团队首选
FeignSpring Cloud 体系,声明式 HTTP 调用Spring Cloud 项目标配
ThriftFacebook 出品,性能出色较少新项目使用
// Dubbo 风格:像调用本地方法一样调用远程服务
@DubboReference
private UserService userService;

User user = userService.getUserById(1L);

// Feign 风格:声明式 HTTP 调用
@FeignClient(name = "user-service")
public interface UserClient {
    @GetMapping("/users/{id}")
    User getUser(@PathVariable Long id);
}

3.3 消息队列:异步通信的"救星"

以上讲的是同步通信(调了就得等返回),但分布式系统中还有一大类场景是异步的——你发个命令过去,不急着等结果。这就是消息队列的用武之地。

消息队列的三大核心作用:

作用说明举例
解耦生产者和消费者互不依赖下单系统和发短信系统通过 MQ 通信,任何一个挂了都不影响对方
削峰把瞬时高峰请求暂存,后端按能力消费秒杀场景,10 万请求先放 MQ,后端慢慢处理
异步耗时操作不用阻塞主流程注册成功 → 发 MQ → 后台异步发邮件、初始化账户、送优惠券
// 没有 MQ:同步处理,用户得等着
public void register(User user) {
    userMapper.insert(user);  // 1. 入库
    emailService.send(user);  // 2. 发邮件(慢!)
    couponService.grant(user);// 3. 送券(慢!)
    // 用户等了 3 秒才看到"注册成功"
}

// 有 MQ:异步处理,秒回
public void register(User user) {
    userMapper.insert(user);     // 1. 入库
    mq.send(new UserRegEvent()); // 2. 发条消息(毫秒级)
    // 用户立刻看到"注册成功"
}
// 邮件服务、优惠券服务自己去消费这条消息

3.4 主流消息队列对比

消息队列吞吐量特点适用场景
RocketMQ阿里出品,事务消息功能强大电商、金融,需要事务消息
Kafka极高分布式流处理平台大数据、日志收集、实时流处理
RabbitMQ基于 AMQP 协议,功能完善中小规模业务场景

国内 Java 生态中 RocketMQ 和 Kafka 最常见。简单场景 RocketMQ 上手快,大数据场景 Kafka 是事实标准。


通信讲完了——同步的 RPC 和异步的 MQ。但通信只是手段,分布式真正的难点在于:数据分散在多个节点上,怎么保证不出错? 下一章我们直面分布式中最让人头疼的问题。


四、分布式事务:跨服务的"一致性"难题

4.1 为什么分布式事务这么难?

单机事务靠数据库的 ACID 保证,一个 @Transactional 就搞定。但分布式场景下:

用户下单接口:
  → 订单服务:创建订单(成功 ✓)
  → 库存服务:扣减库存(失败 ✗——库存不足!)
  → 现在怎么办?订单创建了但库存没扣,数据不一致了!

这就是分布式事务的困境:数据库事务管不了跨服务的操作。我们需要一套跨服务协调一致性的机制。

4.2 从刚性到柔性:分布式事务解决方案图谱

越往左,一致性越强但性能越差;越往右,性能越好但一致性越弱。实用主义的选择是走中间偏右——90% 的场景用最终一致就够了。

4.3 主流方案逐一解析

(1)2PC(两阶段提交):最经典的刚性方案
阶段一(投票):协调者问所有参与者——"你们能提交吗?"
阶段二(提交/回滚):所有人都说能 → 一起提交;有人说不能 → 一起回滚

优点:原理简单,强一致性。 缺点:同步阻塞——整个过程中参与者都在等,性能差。协调者单点故障——协调者挂了,参与者不知道该提交还是回滚,资源一直锁着。

(2)TCC(Try-Confirm-Cancel):把事务逻辑写到代码里

TCC 不依赖数据库事务,而是要求开发者实现三个方法:

public interface OrderService {
    // Try:预留资源(冻结库存)
    boolean tryCreateOrder(Order order);

    // Confirm:确认执行(真正扣减)
    void confirmCreateOrder(Order order);

    // Cancel:回滚(解冻库存)
    void cancelCreateOrder(Order order);
}
阶段做什么举例(下单减库存)
Try预留资源,检查条件冻结 1 件库存(还没真扣)
Confirm使用预留的资源,执行业务真正扣减掉那 1 件库存
Cancel释放预留的资源解冻那 1 件库存

优点:不依赖数据库事务,性能好。 缺点:业务侵入性强,三个方法都得自己写,Confirm 和 Cancel 必须幂等。

(3)Seata:阿里开源的"懒人方案"

Seata 提供了四种模式,覆盖不同场景:

模式原理侵入性性能
AT自动记录 undo log,回滚时反向补偿低(加个注解)
TCC需要自定义 Try/Confirm/Cancel
Saga长事务拆成多个本地事务,失败时调补偿接口
XA数据库级别的 2PC
// AT 模式:一行注解搞定(原理是自动生成反向 SQL)
@GlobalTransactional
public void createOrder(Order order) {
    orderService.create(order);     // 本地事务 1
    storageService.deduct(orderId); // 远程调用 2
    // 任何一个失败,Seata 自动回滚已提交的操作
}

面试时怎么说:"我们项目用 Seata AT 模式解决分布式事务,侵入性低。但如果对性能要求高且能接受业务代码复杂度,考虑 TCC 或 RocketMQ 事务消息。"

(4)可靠消息最终一致:互联网公司的主流实践

大厂最常用的方案其实不是 Seata,而是基于 MQ 的消息最终一致:

本地事务 + 消息表(同一个数据库)→ 定时任务扫描未发送的消息 → 投递到 MQ
                                                     ↓
                                              消费者处理 + 手动 ACK

RocketMQ 的事务消息封装了这个过程:

// 发送半消息(此时消费者不可见)
// 执行本地事务
// 根据本地事务结果,提交或回滚半消息
// 只有提交后消费者才能消费

分布式事务的核心选择逻辑:优先考虑能否用最终一致性,刚性事务是最后的选择。记住一句话:能用异步解决的就别用同步协调。


五、分布式锁:多节点下的"排队问题"

单机里你用一个 synchronized 或 ReentrantLock 就能搞定并发,但换成多台服务器呢?

// 单机:好使
synchronized(this) {
    // 只有当前 JVM 里的线程会排队
    deductStock();
}

// 分布式:synchronized 完全没用!
// 三台服务器各有各的锁,谁也管不了谁

5.1 分布式锁的常见实现

方案原理优点缺点
RedisSET NX EX 命令性能极高,实现简单单节点可能丢锁(主从切换时)
ZooKeeper临时顺序节点可靠性高,节点挂了自动释放性能低于 Redis
Redisson基于 Redis 的 Java 客户端看门狗自动续期、可重入、读写锁依赖 Redis
// Redisson:几行代码搞定分布式锁
RLock lock = redissonClient.getLock("order:lock:" + orderId);
try {
    lock.lock();  // 阻塞式加锁,自带看门狗自动续期
    // 业务逻辑
} finally {
    lock.unlock();
}

5.2 Redis 分布式锁的"坑"

面试最爱问的一个点:Redis 做分布式锁有什么问题?

  • 锁过期问题:业务还没执行完,锁自动过期了 → Redisson 看门狗解决(自动续期)
  • 主从切换丢锁:主节点刚写入锁就挂了,从节点晋升后没有锁数据 → RedLock 算法(向多个独立 Redis 实例加锁,超过半数成功才算获取锁)
  • 集群脑裂:网络分区导致多个客户端认为自己持有了锁 → 用 fencing token 机制(每次加锁带一个递增的 token,写入共享存储时校验 token)

实用建议:大多数场景用 Redisson 就够了。只有涉及金额、库存扣减等强一致场景才考虑 RedLock 或 ZooKeeper。


六、分布式 ID:雪花算法和它的小伙伴们

单机里你靠 MySQL 自增 ID 过日子,分库分表之后呢?各库的自增 ID 会冲突,你必须换方案。

6.1 分布式 ID 的核心要求

要求说明
全局唯一最基本的,不能重
趋势递增MySQL InnoDB 用递增 ID 做聚簇索引,随机 ID 会导致页分裂
高性能生成 ID 不能成为瓶颈
高可用ID 生成服务不能挂

6.2 主流方案一览

方案原理优点缺点
UUID随机生成最简单,本地生成字符串太长,无序,不适合做主键
数据库号段一次申请一个号段(如 1-1000),用完再申请趋势递增,简单可靠有数据库依赖
雪花算法64bit:时间戳 + 机器 ID + 序列号趋势递增,高性能,不依赖外部依赖机器时钟(时钟回拨会出问题)
美团 Leaf号段模式 + 雪花算法双实现高可用,生产验证需要独立部署
百度 UidGenerator基于雪花算法的增强版解决时钟回拨问题依赖数据库 WORKER_ID 表

6.3 雪花算法(Snowflake)详解

最常用的分布式 ID 方案,64 bit 长整型,结构如下:

// 用 Hutool 一行搞定
long id = IdUtil.getSnowflake(workerId, datacenterId).nextId();

时钟回拨问题:如果服务器时间被人往回拨了,可能生成重复 ID。解决方案:美团 Leaf 用"时钟回拨检测 + 等待策略";百度 UidGenerator 用数据库 WORKER_ID 表来弱依赖时钟。


七、分布式缓存:Redis 集群与缓存一致性

缓存是分布式系统性能优化的第一利器。但"缓存和数据库双写一致性"也是面试中翻车率最高的话题之一。

7.1 缓存架构演进

单机版:
  应用 → Redis(单机) → MySQL

Redis 主从 + 哨兵:
  应用 → Redis 主(写)→ Redis 从(读)
           哨兵监控,主挂自动切换

Redis Cluster:
  应用 → 16384 个 Slot 分散在多个 Redis 节点
         每个节点负责一部分 Slot

7.2 缓存三大经典问题

问题现象解决方案
缓存穿透查一个不存在的数据,绕过缓存直接打 DB布隆过滤器、缓存空对象(短过期时间)
缓存击穿热点数据过期瞬间,大量请求同时打 DB互斥锁(只让一个请求去加载)、逻辑过期
缓存雪崩大量缓存同时过期或 Redis 宕机过期时间加随机值、Redis 集群 + 多级缓存

7.3 数据库与缓存一致性

这是面试的重灾区。常见方案演进:

先删缓存再更新数据库:不行,A 删缓存 → B 读缓存 miss 读旧值写入缓存 → A 更新 DB → 缓存里是旧数据。

先更新数据库再删缓存:大多数场景够用了。但仍存在极低概率的不一致(读操作在写操作之间刚好读到旧值并写入缓存)。

延迟双删:先删缓存 → 更新 DB → 等一会(如 500ms)再删一次缓存。简单有效,大多数场景够用。

订阅 Binlog + 异步更新(Canal + MQ):监听 MySQL Binlog,数据变了自动更新缓存。阿里开源的 Canal 是这个方案的典型组件。

// 最常用的 Cache-Aside 模式:先查缓存,miss 后查 DB 并回写缓存
public User getUser(Long userId) {
    // 1. 查缓存
    String key = "user:" + userId;
    User user = (User) redisTemplate.opsForValue().get(key);
    if (user != null) return user;

    // 2. 缓存 miss,查 DB
    user = userMapper.selectById(userId);

    // 3. 回写缓存 + 设置随机过期时间(防雪崩)
    if (user != null) {
        redisTemplate.opsForValue().set(key, user,
            30 + ThreadLocalRandom.current().nextInt(10), TimeUnit.MINUTES);
    }
    return user;
}

八、分布式配置中心与服务治理

8.1 配置中心:告别"改配置就重启"

单机时代你把配置写在 application.yml 里,改了就重启。分布式环境下一百台服务器,你怎么改配置?

配置中心特点
Nacos阿里开源,配置 + 服务发现二合一,国内首选
Apollo携程开源,配置管理功能更完善
Spring Cloud Config配合 Git 使用,简单但不如前两者

Nacos 配置热更新的核心流程:

Nacos Server ← 你修改配置
    │
    ▼  长轮询(Long Polling)
应用实例 1 ──→ 检测到变更 ──→ 刷新 @RefreshScope 标注的 Bean
应用实例 2 ──→ 检测到变更 ──→ 刷新 @RefreshScope 标注的 Bean
应用实例 3 ──→ 检测到变更 ──→ 刷新 @RefreshScope 标注的 Bean

8.2 服务注册与发现:谁在线上、谁下线了?

分布式环境下服务器随时可能增减,不能让调用方去记 IP 地址。

8.3 负载均衡:请求来了,发给谁?

策略原理场景
轮询一人一次轮流服务实例配置相同
加权轮询性能好的多承担服务器配置不同
最小连接数谁最闲发给谁长连接场景
一致性哈希同用户请求到同一台机器有状态服务、缓存亲和
随机纯随机简单场景
// Ribbon 负载均衡(Spring Cloud 使用)
@LoadBalanced
@Bean
public RestTemplate restTemplate() {
    return new RestTemplate();
}

// 调用时只要写服务名,Ribbon 自动选一个实例
String result = restTemplate.getForObject(
    "http://user-service/users/1", String.class);

九、分布式链路追踪与监控

9.1 为什么需要链路追踪?

单体时代看日志就够了。分布式时代一个请求可能经过 10 个服务——哪个环节出问题了?

用户请求 → Gateway(5ms) → 订单服务(30ms) → 库存服务(200ms!) → 支付服务(10ms)
                                                       ↑
                                            就是这个环节慢了!

9.2 主流方案

方案说明
SkyWalkingApache 开源,Java Agent 自动埋点,无需改代码
JaegerUber 开源,兼容 OpenTracing 标准
ZipkinTwitter 开源,Spring Cloud Sleuth 默认集成
SLF4J MDC最轻量:在日志中自动打印 TraceId

最务实的建议:在 Spring Boot 项目中引入 SkyWalking Agent,改一行启动参数即可实现自动链路追踪。配合 logback 的 MDC 在所有日志里打印 TraceId,排障效率提升 10 倍。


十、分布式知识体系速查表

作为半程总结,我把需要掌握的核心知识点整理成一张速查表:

知识领域必会了解面试频率
CAP & BASE 理论★★★★★
RPC 原理(Dubbo/Feign)★★★★
消息队列(削峰/解耦/异步)★★★★
分布式事务(TCC/Seata/MQ)3PC/Saga★★★★★
分布式锁(Redis/ZK)RedLock★★★★
分布式 ID(雪花算法)Leaf/UidGenerator★★★
缓存一致性Canal★★★★★
服务注册与发现★★★
负载均衡策略★★★
配置中心(Nacos)★★
链路追踪(SkyWalking)✓ 会用即可★★
共识算法(Paxos/Raft)✓ 了解思想★★★
分布式调度(XXL-JOB)★★
限流熔断(Sentinel)★★★

十一、面试常考高频题

11.1 "你们项目的分布式事务是怎么做的?"

参考答案框架:

我们的场景是下单减库存。支付成功后,需要同时创建订单记录和扣减库存,这两个操作分别在订单服务和库存服务上。

我们评估了多种方案:2PC 太重、TCC 侵入性高。最终采用 RocketMQ 事务消息——先发半消息,然后执行本地事务(创建订单),根据本地事务结果决定提交或回滚半消息。库存服务消费这条消息后扣库存,消费失败则重试。

关键保障措施:消费者做了幂等处理(数据库唯一索引 + 状态机),避免重复消费导致多扣库存。

11.2 "如何保证缓存和数据库的一致性?"

采用 Cache-Aside + 延迟双删。读的时候先查缓存,miss 了查 DB 并回写缓存。写的时候先更新数据库,再删缓存,然后等几百毫秒再删一次(延迟双删兜底极端情况)。

如果一致性要求非常高,会用 Canal 监听 Binlog 异步更新缓存——这样即使删除失败,后续的 Binlog 事件会触发补偿。

11.3 "Redis 分布式锁和 ZooKeeper 分布式锁选哪个?"

大多数场景选 Redis(Redisson),性能好、API 简单。Redisson 的看门狗机制解决了锁过期问题。

但如果是对一致性要求极严格的场景(比如金融扣款),会选 ZooKeeper——因为 ZK 的临时顺序节点机制天然能保证:客户端挂了锁自动释放,避免了 Redis 主从切换丢锁的风险。

折中方案是 RedLock,但实现复杂,小团队不推荐。

11.4 "CAP 理论和你实际工作有什么联系?"

CAP 不是纯理论,它在日常架构决策中起指导作用。比如我们选注册中心时,Eureka 是 AP、ZooKeeper 是 CP——我们的服务注册场景能容忍短暂的数据不一致但不能容忍服务不可用,所以选 AP 的 Eureka。

再比如订单表,强一致是必须的(钱不能算错),所以用 CP;而用户头像缓存,短暂不一致完全可以接受,走 AP。


十二、结语:分布式不是什么"黑魔法"

刚接触分布式时,你可能会被各种术语吓到:CAP、Paxos、TCC、Seata、ZAB、Raft……它们听起来像是另一个世界的东西。

但当你把它们一个个拆开看,会发现分布式无非是在解决几个基础问题:

  1. 怎么通信(RPC / 消息队列)
  2. 数据怎么保持一致(分布式事务 / 最终一致)
  3. 怎么"排队"(分布式锁)
  4. 怎么"取号"(分布式 ID)
  5. 怎么找到彼此(服务发现 / 配置中心)
  6. 怎么监控排查(链路追踪)

把这张图记在心里,学任何分布式相关的技术时问自己:"它在解决哪一类问题?"——这就是从"学知识"到"建体系"的关键一步。

分布式知识学习的最快路径不是看书,而是在你自己的项目里引入一个中间件——把 Seata 集成到你的下单流程里,把 Redisson 用到你的秒杀系统里。踩坑两次,比看书十小时更管用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值