NSQ 在 Golang 项目中的监控与管理方法

NSQ 在 Golang 项目中的监控与管理方法

关键词:NSQ、消息队列、Golang、监控指标、故障排查、动态扩缩容、云原生

摘要:本文以“快递中转站”为类比,用通俗易懂的语言讲解 NSQ 消息队列的核心组件与监控管理逻辑。结合 Golang 项目实战,详细介绍如何通过指标监控、健康检查、日志分析等方法保障 NSQ 稳定运行,并提供代码示例与工具推荐,帮助开发者快速掌握 NSQ 监控与管理的核心技巧。


背景介绍

目的和范围

在高并发的互联网系统中,消息队列是“流量缓冲池”和“业务解耦器”。NSQ(New Simple Queue)作为轻量级分布式消息队列,凭借其简单、高效、易扩展的特性,被广泛应用于 Golang 项目(如电商订单系统、实时数据同步、日志聚合等场景)。本文聚焦“如何监控与管理 NSQ”,覆盖指标采集、健康检查、故障排查、动态扩缩容等核心问题,适用于使用 NSQ 的 Golang 开发者与架构师。

预期读者

  • 对消息队列有基础认知的 Golang 开发者
  • 负责分布式系统运维的工程师
  • 希望优化系统稳定性的技术架构师

文档结构概述

本文从“快递中转站”的生活场景切入,先解释 NSQ 的核心组件(类比快递分拣中心、调度系统、监控大屏),再拆解监控与管理的五大核心方法(指标监控、健康检查、日志分析、动态扩缩容、故障恢复),最后通过 Golang 代码实战演示如何落地这些方法,并给出工具推荐与未来趋势。

术语表

术语解释(类比快递场景)
NSQD消息处理核心服务(快递分拣中心,负责接收、存储、转发消息)
NSQLookupd服务发现与元数据管理(快递调度系统,记录每个分拣中心的位置与状态)
NSQAdmin可视化管理界面(快递监控大屏,展示各分拣中心的实时数据)
Producer消息生产者(发件人,向分拣中心发送快递)
Consumer消息消费者(收件人,从分拣中心取走快递)
Topic消息主题(快递类型,如“生鲜”“文件”)
Channel主题的子队列(同类型快递的不同配送组,如“北京组”“上海组”)
Depth队列深度(分拣中心当前积压的快递数量)

核心概念与联系

故事引入:快递中转站的监控难题

假设你是“闪电快递”的运营主管,管理着全国 100 个分拣中心。最近遇到几个麻烦:

  1. 某分拣中心突然“爆仓”(积压 10 万件快递),但监控大屏没及时报警;
  2. 配送员(消费者)取件速度太慢,导致生鲜快递(实时消息)变质;
  3. 某天系统崩溃后,丢失了 500 件“紧急文件”(关键消息)。

为了解决这些问题,你需要:

  • 实时监控每个分拣中心的快递数量(队列深度)、取件速度(消费速率);
  • 检查分拣中心的运行状态(是否宕机、磁盘是否满);
  • 分析异常日志(如“配送员超时未取件”);
  • 根据业务量动态增减分拣中心(扩缩容);
  • 设计“快递丢失”的补救方案(消息重投)。

而 NSQ 的监控与管理,本质上就是解决类似“快递中转站”的问题——只不过这里的“快递”是“消息”,“分拣中心”是“NSQD 实例”。

核心概念解释(像给小学生讲故事一样)

1. NSQD:消息分拣中心
NSQD 是 NSQ 的核心服务,负责接收、存储、转发消息。就像快递分拣中心:

  • 接收发件人(Producer)的快递(消息);
  • 暂时存放在仓库(内存/磁盘);
  • 等待配送员(Consumer)取走。

2. NSQLookupd:快递调度系统
NSQLookupd 是“服务发现中心”,记录所有 NSQD 实例的位置(IP:端口)和状态(是否在线)。当发件人要发快递时,先问调度系统:“最近的分拣中心在哪里?”调度系统会告诉它可用的 NSQD 地址。

3. NSQAdmin:快递监控大屏
NSQAdmin 是可视化管理界面,能展示所有 Topic/Channel 的消息数量、消费速率、延迟等数据,还能手动触发消息重投或清空队列(类似监控大屏上的“爆仓预警”和“紧急清空”按钮)。

4. Topic/Channel:快递类型与分组

  • Topic 是消息的“类型”(如“订单消息”“日志消息”),类似快递的“生鲜”“文件”分类;
  • Channel 是 Topic 的“子队列”(如“订单消息-北京组”“订单消息-上海组”),类似同一类型快递的不同配送组。

核心概念之间的关系(用小学生能理解的比喻)

  • NSQD 与 NSQLookupd:分拣中心(NSQD)每天向调度系统(NSQLookupd)汇报“我还能收 1000 件快递”;发件人(Producer)要发快递时,先问调度系统“最近的分拣中心在哪?”,调度系统告诉它可用的 NSQD 地址。
  • NSQD 与 NSQAdmin:分拣中心(NSQD)的实时数据(如“当前有 500 件快递”)会被监控大屏(NSQAdmin)收集并展示,运营人员可以通过大屏手动调整分拣中心的参数(如“限制最多存 2000 件”)。
  • Topic 与 Channel:发件人(Producer)把“生鲜快递”(Topic)送到分拣中心后,分拣中心会把快递分给“北京配送组”(Channel1)和“上海配送组”(Channel2),每个配送组独立取件(消费者订阅不同 Channel)。

核心概念原理和架构的文本示意图

[Producer] → [NSQD1] → [Consumer Group1 (Channel A)]  
       │        │  
       ├→ [NSQD2] → [Consumer Group2 (Channel B)]  
       │  
       └→ [NSQLookupd](记录 NSQD1/NSQD2 的地址与状态)  
             │  
             └→ [NSQAdmin](监控 NSQD1/NSQD2、Topic、Channel 的实时数据)  

Mermaid 流程图(NSQ 消息流转与监控流程)

Producer
NSQLookupd: 找可用 NSQD
NSQD1
NSQD2
Channel A
Channel B
Consumer Group1
Consumer Group2
监控消费者状态

核心监控与管理方法:五大维度保障 NSQ 稳定

要让 NSQ 像“永不爆仓的快递中转站”,需要从 指标监控、健康检查、日志分析、动态扩缩容、故障恢复 五个维度入手。

一、指标监控:给 NSQ 装“电子秤”和“计时器”

指标监控是 NSQ 管理的“晴雨表”,通过采集关键数据(如队列深度、消费速率),可以提前发现“爆仓”或“配送员偷懒”的问题。

核心监控指标(类比快递场景)
指标名称含义(快递类比)阈值建议(示例)风险说明
Topic DepthTopic 总消息数(分拣中心总快递数)单 Topic ≤ 10 万超过阈值可能导致消息堆积超时
Channel DepthChannel 消息数(某配送组的快递数)单 Channel ≤ 1 万消费者处理慢,需扩容或优化
Messages In消息入队速率(每分钟收到的快递数)根据业务峰值调整突发流量可能压垮 NSQD
Messages Out消息出队速率(每分钟被取走的快递数)需 ≥ Messages In出队慢会导致积压
Requeue Count消息重投次数(被退回的快递数)每小时 ≤ 100 次重投过多可能是消费者故障
Timeout Count消息超时次数(超时未取的快递数)每小时 ≤ 50 次消费者处理时间过长
如何采集指标?(Golang 实战)

NSQ 提供了 HTTP API 暴露指标,Golang 可以通过 net/http 库调用这些接口。例如,获取 NSQD 实例的状态:

package main

import (
    "fmt"
    "net/http"
    "io/ioutil"
)

func getNSQDStats(nsqdAddr string) (string, error) {
    url := fmt.Sprintf("http://%s/stats?format=json", nsqdAddr)
    resp, err := http.Get(url)
    if err != nil {
        return "", fmt.Errorf("请求失败: %v", err)
    }
    defer resp.Body.Close()

    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return "", fmt.Errorf("读取响应失败: %v", err)
    }
    return string(body), nil
}

func main() {
    stats, err := getNSQDStats("127.0.0.1:4151") // NSQD 默认 HTTP 端口 4151
    if err != nil {
        fmt.Printf("获取指标失败: %v\n", err)
        return
    }
    fmt.Println("NSQD 实时指标:\n", stats)
}

运行后,会输出类似以下的 JSON 数据(关键指标已标注):

{
    "version": "1.2.1",
    "topics": [
        {
            "topic_name": "order_topic",
            "depth": 1234, // Topic 深度(总消息数)
            "channels": [
                {
                    "channel_name": "order_channel",
                    "depth": 456, // Channel 深度(当前积压消息数)
                    "messages_out": 1000, // 已消费消息数
                    "requeue_count": 5, // 重投次数
                    "timeout_count": 2 // 超时次数
                }
            ]
        }
    ]
}

二、健康检查:给 NSQ 做“全身检查”

健康检查是确保 NSQD/NSQLookupd 实例正常运行的“体检”,需要检查进程状态、网络连通性、资源使用情况(如磁盘、内存)。

健康检查项(类比快递分拣中心)
检查项检查方法(Golang 示例)异常处理建议
进程是否存活通过 ps 命令或 systemd 检查进程自动重启或切换备用实例
TCP 端口是否监听使用 net.Dial 测试端口连通性检查防火墙规则或 NSQD 配置
磁盘空间是否充足调用 syscall.Statfs 获取磁盘使用率清理过期消息或扩容磁盘
内存占用是否过高通过 /proc/meminforuntime.MemStats优化消息缓存策略或扩容实例
Golang 实现端口连通性检查
package main

import (
    "fmt"
    "net"
    "time"
)

// 检查 NSQD 的 TCP 端口是否可用(默认 4150 是 TCP 监听端口)
func checkNSQDHealth(nsqdAddr string, timeout time.Duration) bool {
    conn, err := net.DialTimeout("tcp", nsqdAddr, timeout)
    if err != nil {
        return false
    }
    defer conn.Close()
    return true
}

func main() {
    nsqdAddr := "127.0.0.1:4150"
    if checkNSQDHealth(nsqdAddr, 2*time.Second) {
        fmt.Println("NSQD 健康:端口可连接")
    } else {
        fmt.Println("NSQD 异常:端口不可连接")
    }
}

三、日志分析:从“快递丢件记录”中找规律

NSQ 的日志是排查故障的“黑匣子”,通过分析日志可以定位消息丢失、消费者超时等问题。

关键日志类型(类比快递问题记录)
日志类型示例内容问题定位方向
消息丢失日志ERROR: diskqueue: write failed磁盘故障或写入压力过大
消费者超时日志WARNING: client timeout消费者处理逻辑耗时过长
连接拒绝日志ERROR: accept error: too many open files文件句柄限制(需调整 ulimit
磁盘队列满日志INFO: diskqueue: data/order_topic: size 1000000消息积压超过内存限制,转存磁盘
Golang 实现日志监控(Tail 实时日志)

可以用 github.com/hpcloud/tail 库实时读取 NSQD 日志文件,触发告警:

package main

import (
    "fmt"
    "github.com/hpcloud/tail"
    "time"
)

func monitorNSQDLog(logPath string) {
    t, err := tail.TailFile(logPath, tail.Config{
        ReOpen:    true,    // 日志切割后重新打开
        Follow:    true,    // 实时跟踪
        MustExist: false,   // 文件不存在不报错
        Poll:      true,    // 轮询模式(兼容不同系统)
    })
    if err != nil {
        panic(err)
    }

    for line := range t.Lines {
        if line.Err != nil {
            fmt.Printf("日志读取错误: %v\n", line.Err)
            continue
        }
        // 检测 ERROR 日志
        if strings.Contains(line.Text, "ERROR") {
            fmt.Printf("【告警】NSQD 错误日志: %s\n", line.Text)
            // 这里可以调用邮件/Slack 告警接口
        }
    }
}

func main() {
    go monitorNSQDLog("/var/log/nsqd.log") // NSQD 默认日志路径(需根据实际配置调整)
    select {} // 阻塞主协程
}

四、动态扩缩容:根据“快递量”增减分拣中心

当业务量激增(如双 11 订单暴增),需要快速增加 NSQD 实例(扩容);当业务量下降,需要减少实例(缩容)以节省资源。

扩缩容策略(类比快递旺季应对)
触发条件操作步骤(Golang 自动化)注意事项
Topic Depth > 10 万(爆仓)1. 启动新 NSQD 实例;
2. 注册到 NSQLookupd;
3. 迁移部分消息到新实例
需保证消息有序性,避免重复消费
Messages In > 10 万/分钟(高负载)1. 检查现有 NSQD CPU/内存;
2. 按负载均衡策略分配新实例
需同步更新 Producer 的 NSQLookupd 配置
业务低峰期(如凌晨)1. 检查低负载 NSQD 实例;
2. 优雅下线(停止接收新消息,待消息处理完后关闭)
避免直接终止导致消息丢失
Golang 调用 NSQLookupd API 注册新 NSQD

NSQLookupd 提供 PUT /nsqd 接口注册 NSQD 实例,Golang 可以通过 HTTP 请求实现自动化扩容:

package main

import (
    "bytes"
    "fmt"
    "net/http"
)

func registerNSQD(lookupdAddr, nsqdAddr string) error {
    url := fmt.Sprintf("http://%s/nsqd?broadcast_address=%s&tcp_port=4150&http_port=4151", lookupdAddr, nsqdAddr)
    req, err := http.NewRequest("POST", url, bytes.NewBuffer(nil))
    if err != nil {
        return fmt.Errorf("创建请求失败: %v", err)
    }

    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        return fmt.Errorf("注册失败: %v", err)
    }
    defer resp.Body.Close()

    if resp.StatusCode != http.StatusOK {
        return fmt.Errorf("注册失败,状态码: %d", resp.StatusCode)
    }
    return nil
}

func main() {
    // 假设新增 NSQD 实例地址为 10.0.0.2:4150(TCP 端口)
    err := registerNSQD("127.0.0.1:4161", "10.0.0.2") // NSQLookupd 默认 HTTP 端口 4161
    if err != nil {
        fmt.Printf("注册 NSQD 失败: %v\n", err)
        return
    }
    fmt.Println("NSQD 注册成功,已加入集群")
}

五、故障恢复:“快递丢件”后的补救方案

即使监控到位,故障仍可能发生(如 NSQD 宕机、消费者崩溃)。需要设计“消息不丢失”的恢复机制。

常见故障与恢复方案
故障类型现象(快递类比)恢复方案(Golang 实现)
NSQD 宕机分拣中心停电,无法接收/转发快递1. 自动切换到备用 NSQD(通过 NSQLookupd 发现新实例);
2. 启用磁盘持久化(NSQD 重启后从磁盘恢复消息)
消费者处理超时配送员取件后迟迟不送,快递超时1. NSQ 自动重投消息(设置 max_timeout 参数);
2. 消费者记录失败消息到“死信队列”(DLQ)
消息重复消费同一件快递被两个配送员取走1. 消费者实现幂等性(如用 Redis 记录已处理消息 ID);
2. NSQ 设置 idempotent 标识
Golang 实现消息幂等消费(防重复)
package main

import (
    "github.com/nsqio/go-nsq"
    "github.com/go-redis/redis"
)

var redisClient *redis.Client

func initRedis() {
    redisClient = redis.NewClient(&redis.Options{
        Addr: "localhost:6379",
    })
}

// 消费者处理函数(带幂等校验)
func handleMessage(msg *nsq.Message) error {
    messageID := string(msg.ID)
    // 检查 Redis 是否已处理过该消息
    exists, err := redisClient.Exists(messageID).Result()
    if err != nil {
        return fmt.Errorf("Redis 查询失败: %v", err)
    }
    if exists == 1 {
        return nil // 已处理过,直接返回成功
    }

    // 处理消息(例如写入数据库)
    err = processMessage(msg.Body)
    if err != nil {
        return err // 处理失败,触发重投
    }

    // 记录消息 ID 到 Redis(设置 1 天过期)
    redisClient.Set(messageID, "1", 24*time.Hour)
    return nil
}

func main() {
    initRedis()
    config := nsq.NewConfig()
    consumer, err := nsq.NewConsumer("order_topic", "order_channel", config)
    if err != nil {
        panic(err)
    }
    consumer.AddHandler(nsq.HandlerFunc(handleMessage))
    err = consumer.ConnectToNSQLookupd("127.0.0.1:4161") // 连接 NSQLookupd
    if err != nil {
        panic(err)
    }
    select {} // 阻塞主协程
}

项目实战:Golang + Prometheus + Grafana 搭建 NSQ 监控平台

开发环境搭建

  1. 安装 NSQ:brew install nsq(Mac)或 apt-get install nsq(Linux);
  2. 安装 Prometheus:从 官网 下载并启动;
  3. 安装 Grafana:从 官网 下载并启动;
  4. 安装 NSQ Exporter(指标采集工具):go install github.com/nsqio/nsq/nsq_to_prometheus@latest

源代码:NSQ Exporter 集成 Prometheus

NSQ 官方提供了 nsq_to_prometheus Exporter,可将 NSQ 指标暴露给 Prometheus。Golang 项目中可以直接使用该工具,或自定义 Exporter(以下是简化版实现):

package main

import (
    "net/http"
    "github.com/prometheus/client_golang/prometheus"
    "github.com/prometheus/client_golang/prometheus/promhttp"
)

// 定义 Prometheus 指标
var (
    topicDepth = prometheus.NewGaugeVec(
        prometheus.GaugeOpts{
            Name: "nsq_topic_depth",
            Help: "当前 Topic 的消息深度",
        },
        []string{"topic"},
    )
    channelDepth = prometheus.NewGaugeVec(
        prometheus.GaugeOpts{
            Name: "nsq_channel_depth",
            Help: "当前 Channel 的消息深度",
        },
        []string{"topic", "channel"},
    )
)

func init() {
    prometheus.MustRegister(topicDepth)
    prometheus.MustRegister(channelDepth)
}

// 定时拉取 NSQ 指标并更新 Prometheus
func scrapeNSQMetrics(nsqdAddr string) {
    for {
        stats, _ := getNSQDStats(nsqdAddr) // 复用前文的 getNSQDStats 函数
        // 解析 JSON 并更新指标(伪代码,实际需用 json.Unmarshal 解析)
        for _, topic := range stats.Topics {
            topicDepth.WithLabelValues(topic.Name).Set(float64(topic.Depth))
            for _, channel := range topic.Channels {
                channelDepth.WithLabelValues(topic.Name, channel.Name).Set(float64(channel.Depth))
            }
        }
        time.Sleep(10 * time.Second) // 每 10 秒采集一次
    }
}

func main() {
    go scrapeNSQMetrics("127.0.0.1:4151") // 启动指标采集协程
    http.Handle("/metrics", promhttp.Handler())
    http.ListenAndServe(":9100", nil) // Prometheus 从 9100 端口拉取指标
}

Grafana 可视化配置

  1. 在 Grafana 中添加 Prometheus 数据源(URL: http://localhost:9090);
  2. 导入 NSQ 监控模板(Grafana 官网模板 ID: 11162);
  3. 配置告警规则(如“当 nsq_channel_depth > 1 万时触发告警”)。

最终效果:Grafana 大屏展示 Topic/Channel 深度、消费速率、重投次数等核心指标,支持实时告警。


实际应用场景

场景 1:电商大促的订单消息处理

  • 问题:双 11 期间,订单消息量激增(10 万条/分钟),可能导致 NSQ 队列积压;
  • 监控方案:
    • 监控 messages_in 速率,触发自动扩容(新增 NSQD 实例);
    • 监控 channel_depth,确保每个消费者组的消息数 ≤ 1 万;
    • 日志分析 requeue_count,定位消费慢的消费者(如数据库慢查询)。

场景 2:实时数据同步系统

  • 问题:用户修改个人信息后,需实时同步到 5 个业务系统(如订单、会员、积分),任何延迟都会导致数据不一致;
  • 监控方案:
    • 监控 message_out 延迟(消息从入队到被消费的时间),要求 ≤ 100ms;
    • 监控 timeout_count,避免消费者处理超时导致消息堆积;
    • 启用消息幂等校验(如用 Redis 记录消息 ID),防止重复同步。

工具和资源推荐

工具/资源用途链接
NSQ 官方文档学习 NSQ 配置与 APIhttps://nsq.io/
nsq_to_prometheusNSQ 指标导出到 Prometheushttps://github.com/nsqio/nsq
Grafana 监控模板快速搭建 NSQ 可视化大屏https://grafana.com/grafana/dashboards/11162
go-nsq 客户端库Golang 操作 NSQ 的官方 SDKhttps://github.com/nsqio/go-nsq
Prometheus 告警规则配置 NSQ 异常告警https://prometheus.io/docs/prometheus/latest/configuration/alerting_rules/

未来发展趋势与挑战

  • 云原生集成:NSQ 与 Kubernetes 的结合(如通过 Operator 实现自动扩缩容)是未来趋势,可解决“手动扩缩容效率低”的问题;
  • 智能监控:引入机器学习预测消息量峰值(如根据历史数据预测双 11 订单量),提前自动扩容;
  • 跨语言兼容性:支持更多客户端库(如 Rust、Python),但 Golang 凭借高并发优势仍会是主流;
  • 数据一致性增强:支持事务消息(如 RocketMQ 的事务特性),解决“消息发送与业务操作”的原子性问题。

总结:学到了什么?

核心概念回顾

  • NSQD 是消息分拣中心,NSQLookupd 是调度系统,NSQAdmin 是监控大屏;
  • Topic 是消息类型,Channel 是消息分组,Depth 是队列积压量。

概念关系回顾

  • 生产者通过 NSQLookupd 找到 NSQD 发送消息,消费者从 NSQD 的 Channel 取消息;
  • NSQAdmin 监控 NSQD 的指标(如 Depth),结合 Prometheus/Grafana 实现可视化告警;
  • 动态扩缩容和故障恢复是保障高可用的关键,需结合业务场景设计策略。

思考题:动动小脑筋

  1. 如果你的项目中,NSQ 的 channel_depth 突然从 100 激增到 10 万,你会如何排查?(提示:检查消费者是否宕机、消息生产速率是否突增)
  2. 如何用 Golang 实现“当 NSQD 磁盘空间不足时,自动清理 3 天前的旧消息”?(提示:调用 NSQD 的 DELETE /topic API 或通过磁盘操作)
  3. 在微服务架构中,NSQ 与 Kafka、RabbitMQ 相比有哪些优缺点?什么时候更适合用 NSQ?(提示:NSQ 轻量易部署,适合中小规模;Kafka 适合大数据量,RabbitMQ 适合复杂路由)

附录:常见问题与解答

Q1:NSQ 消息会丢失吗?如何保证不丢失?
A:默认情况下,NSQ 消息存储在内存(或磁盘队列),若 NSQD 宕机且未持久化到磁盘,可能丢失消息。解决方案:

  • 启用磁盘队列(-mem-queue-size 设置为较小值,强制消息落盘);
  • 消费者确认机制(FIN 命令确认消息已处理,未确认则重投);
  • 监控 timeout_countrequeue_count,及时发现未确认的消息。

Q2:NSQ 如何处理消息重复?
A:NSQ 不保证“恰好一次”(Exactly Once),但可以通过以下方式减少重复:

  • 消费者实现幂等性(如用 Redis 记录已处理消息 ID);
  • 设置合理的 max-in-flight(消费者同时处理的消息数),避免因处理超时导致重投;
  • 使用 idempotent 标识(部分客户端支持),告知 NSQ 消息可重复消费。

Q3:NSQAdmin 无法显示实时数据,可能是什么原因?
A:常见原因:

  • NSQD 未注册到 NSQLookupd(检查 nsqd -lookupd-tcp-address 配置);
  • NSQAdmin 配置错误(检查 nsqadmin -lookupd-http-address 是否指向正确的 NSQLookupd 地址);
  • 网络问题(NSQAdmin 与 NSQLookupd 之间无法通信)。

扩展阅读 & 参考资料

  1. 《NSQ 官方文档》:https://nsq.io/
  2. 《Prometheus 官方文档》:https://prometheus.io/docs/
  3. 《Golang 并发编程实战》(书籍):讲解如何利用 Golang 协程优化 NSQ 消费者性能。
  4. 《分布式消息队列设计模式》(论文):对比 NSQ、Kafka、RabbitMQ 的设计差异。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值