numa_NUMA_学习笔记

NUMA 笔记

基础概念

socket、core、processor 的概念

在 NUMA 架构下,CPU 的概念从大到小依次是:socketcoreprocessor

socket(CPU插槽)

将多个CPU核封装在一起,该封装称为socket。即我们平常肉眼所见的CPU插槽所对应的物理CPU封装芯片。

工具:

命令lscpu输出结果中的 Socket(s)字段,可以看到socket的个数。
路径 /proc/cpuinfo中的 physical id 字段,表示的就是一个CPU插槽的标识号。
路径 /sys/devices/system/cpu/cpu${processor_index}/topology/physical_package_id,表示某个逻辑核${processor_index}所在在的CPU插槽的标识号。其中${processor_index}是逻辑核序号。

  • core(CPU核)

socket中的每个物理CPU核被称为core

工具:

命令lscpu输出结果中的 Core(s) per socket字段,可以看到一个socket的上core的个数。

  • processor(逻辑核)

为了进一步提升core的处理能力,Intel 引入了 HT(Hyper-Threading,超线程)技术。
一个core打开HT之后,在 OS 看来就是两个核。
由于该核只是逻辑上的概念,所以也被称为logical processor,简称为processor

工具:

命令lscpu输出结果中的CPU(s)字段,可以看到逻辑核的个数。
路径 /proc/cpuinfo中的 processor 字段,表示的就是一个逻辑核的序号。该序号是从零开始的,并且是连续的。
路径 /proc/cpuinfo中的 core id 字段,表示的就是一个逻辑核的标识号。注意该标识号不一定连续,而且与硬件相关。
路径 /sys/devices/system/cpu/cpu${processor_index}/topology/core_id,表示某个逻辑核${processor_index}的标识号。其中${processor_index}是逻辑核序号。

SMP(Symmetric Multi-Processing) & UMA(Uniform Memory Access)

SMP 将多个CPU插槽与一个集中的存储器相连。
在 SMP 下,所有CPU插槽都可以访问同一个系统物理存储器,SMP系统只运行操作系统的一个拷贝。
SMP系统有时也被称为一致存储器访问(UMA)结构体系,因为处理器只能为内存的每个数据保持或共享唯一一个数值。

SMP 拓扑结构图
在这里插入图片描述
问题:

随着CPU插槽的增加,共享内存可能会导致内存访问冲突越来越厉害,且如果内存访问达到瓶颈的时候,性能就不能随之增加。
实验证明,SMP 服务器 CPU 利用率最好的情况是 2 至 4 个CPU插槽

NUMA(Non-Uniform Memory Access)

NUMA 中内存被平均分配在了各个CPU插槽上。
CPU插槽与直连的内存块形成一个 NUMA node。

NUMA 拓扑结构图
在这里插入图片描述

NUMA node

CPU插槽和直连的内存块合起来,称为一个 NUMA node。

NUMA distance

distance 为各个 NUMA node 之间调用资源的开销。
distance 为资源调度优化提供数据支持。

工具:

使用 numactl --hardware 可以打印 distance。

socket 间的连接和内存访问

  1. inter-connect

socket 之间的连接,称为 inter-connect。

  1. local access

CPU 访问本 socket 的内存时,称为 local access。
由于同一 socket 内 CPU 和内存的物理距离最短,local access 有最短的响应时间。

注意:
尽量保证 local access,其效率是最高的。

  1. remote access

CPU 访问其他 socket 的内存时,称为 remote access。
由于需要通过 inter-connect 通道访问,remote access 响应时间相对变慢。

注意:
remote access 比 local access 慢是由于电路板物理结构所决定的。
当需要跨 socket 访问的时候,就会相对变慢。

BIOS 层 和 OS 层 NUMA 的相互关系

  1. 在 BIOS 层的 NUMA 开启时,如果 OS 层的 NUMA 关闭,性能会下降。
  2. 在 BIOS 层的 NUMA 关闭时,无论 OS 层面的 NUMA 是否打开,都不会影响性能。

NUMA 与 SWAP 问题 (SWAP Insanity)

如果一个进程限制它只能使用自己的 NUMA node 的内存。
当自身 NUMA node 内存使用光之后,就不会去使用其他 NUMA node 的内存,而是开始使用 SWAP ,造成业务停滞。
甚至机器在没有设置 SWAP 的时候,可能会直接死机!

处理方法:

使用 numactl --interleave=all 可以取消 NUMA node 的限制。

NUMA 的性能调优建议

  1. 如果程序会占用大内存的,因为很有可能碰到 SWAP 问题
    1.1. 考虑从 BIOS 层 关闭 NUMA。
    1.2. 考虑从 OS 层 关闭 NUMA。
    1.3. 设置 NUMA 的 内存 分配策略 为 interleave=all。
    1.4. 设置 内核参数 vm.zone_reclaim_mode=0。
  2. 如果程序并不会占用大内存,而是要求更快的程序运行时间。
    2.1. 应该选择限制只访问本 NUMA node。

工具应用

BIOS 层 的 NUMA 设置

查看 BIOS 层是否开启 NUMA

    grep -i numa /var/log/dmesg
    >   1. 如果为 "No NUMA configuration found"
    >       则说明 NUMA 为 disable。
    >
    >   2. 如果不是 "No NUMA configuration found"
    >       则说明 NUMA 为 enable。

修改 BIOS interleave

注意,由于 BIOS 种类繁多,请以实际情况为准。

参数路径:

    BIOS: interleave

设定值:

    Disable             # interleave 关闭,开启 NUMA。
    Enable              # interleave 开启,关闭 NUMA。

OS 层 的 NUMA 设置

linux 的 NUMA 设置

  1. 查询 当前内核启动参数
    cat /porc/cmdline
  1. 修改 内核启动参数

2.1. 直接修改启动参数

    vim /etc/grub.conf
    >   修改 kernel 或 linuxe 或 linuxefi 行
    >      添加 `numa=off`,关闭 NUMA。
    >      去除 `numa=off`,开启 NUMA。
    >
    >   例子:
    >      linuxefi /vmlinuz-3.10.0-123.el7.x86_64 ... numa=off

2.1. 修改默认启动参数

    # 修改默认启动参数
    vim /etc/default/grub
    >   修改 GRUB_CMDLINE_LINUX 行
    >      添加 `numa=off`,关闭 NUMA。
    >      去除 `numa=off`,开启 NUMA。
    >
    >   例子:
    >   GRUB_CMDLINE_LINUX="... numa=off"

    # 重新生成配置文件:
    grub2-mkconfig -o /etc/grub.conf

安装 numactl 工具

    yum install numactl -y

查看 CPU 信息


    # NUMA 关闭时:
    lscpu
    >   Architecture:          x86_64
    >   CPU op-mode(s):        32-bit, 64-bit
    >   Byte Order:            Little Endian
    >   CPU(s):                24               # 主板上`逻辑核`个数
    >   On-line CPU(s) list:   0-23
    >   Thread(s) per core:    1                # 每个`CPU核`的 线程个数
    >   Core(s) per socket:    12               # 每个`CPU插槽`的`CPU核`数
    >   Socket(s):             2                # 主板上`CPU插槽`个数
    >   NUMA node(s):          1                # 主板上`NUMA node`个数
    >   Vendor ID:             GenuineIntel
    >   CPU family:            6
    >   Model:                 62
    >   Model name:            Intel(R) Xeon(R) CPU E5-2697 v2 @ 2.70GHz
    >   Stepping:              4
    >   CPU MHz:               1346.625
    >   BogoMIPS:              5406.12
    >   Virtualization:        VT-x
    >   L1d cache:             32K
    >   L1i cache:             32K
    >   L2 cache:              256K
    >   L3 cache:              30720K
    >   NUMA node0 CPU(s):     0-23             # node 0 上`逻辑核`序号

    # NUMA 开启时:
    lscpu
    >   Architecture:          x86_64
    >   CPU op-mode(s):        32-bit, 64-bit
    >   Byte Order:            Little Endian
    >   CPU(s):                24               # 主板上`逻辑核`个数
    >   On-line CPU(s) list:   0-23
    >   Thread(s) per core:    1                # 每个`CPU核`的 线程个数
    >   Core(s) per socket:    12               # 每个`CPU插槽`的`CPU核`数
    >   Socket(s):             2                # 主板上`CPU插槽`个数
    >   NUMA node(s):          2                # 主板上`NUMA node`个数
    >   Vendor ID:             GenuineIntel
    >   CPU family:            6
    >   Model:                 62
    >   Model name:            Intel(R) Xeon(R) CPU E5-2697 v2 @ 2.70GHz
    >   Stepping:              4
    >   CPU MHz:               1364.871
    >   BogoMIPS:              5405.73
    >   Virtualization:        VT-x
    >   L1d cache:             32K
    >   L1i cache:             32K
    >   L2 cache:              256K
    >   L3 cache:              30720K
    >   NUMA node0 CPU(s):     0-11             # node 0 上`逻辑核`序号
    >   NUMA node1 CPU(s):     12-23            # node 1 上`逻辑核`序号

注意:
Thread(s) per core为1。表示没有开启超线程Thread(s) per core大于1。表示开启了超线程
NUMA 开启后,NUMA node个数增多,但是因为逻辑核总数固定不变,所以每一个NUMA node上的逻辑核反而减少。

查看 NUMA node 信息


    # NUMA 关闭时:
    numactl --hardware
    >   available: 1 nodes (0)      # node 列表
    >   node 0 cpus: 0 1 .. 23      # node0 `逻辑核`列表
    >   node 0 size: 65523 MB       # node0 总内存大小
    >   node 0 free: 51796 MB       # node0 空闲内存大小
    >   node distances:             # node 之间 distances 大小
    >   node   0                    # distances 会由矩阵来表示
    >     0:  10

    # NUMA 开启时:
    numactl --hardware
    >   available: 2 nodes (0-1)    # node 列表
    >   node 0 cpus: 0 1 .. 11      # node0 `逻辑核`列表
    >   node 0 size: 32755 MB       # node0 总内存大小
    >   node 0 free: 30836 MB       # node0 空闲内存大小
    >   node 1 cpus: 12 13 .. 23    # node1 `逻辑核`列表
    >   node 1 size: 32768 MB       # node1 总内存大小
    >   node 1 free: 31827 MB       # node1 空闲内存大小
    >   node distances:             # distances 会由矩阵来表示
    >   node   0   1
    >     0:  10  20                # local access(distance = 10)
    >     1:  20  10                # romote access(distance = 20)

注意:
当 NUMA 开启后,查看node distances字段,可见 romote access(20) 是 local access(10) 的 2 倍。

查看 NUMA 绑定信息

    # NUMA 关闭时:
    numactl --show
    >   policy: default
    >   preferred node: current
    >   physcpubind: 0 1 .. 23      # `逻辑核`绑定
    >   cpubind: 0                  # CPU 绑定
    >   nodebind: 0                 # node 绑定
    >   membind: 0                  # 内存绑定

    # NUMA 开启时:
    numactl --show
    >   policy: default
    >   preferred node: current
    >   physcpubind: 0 1 .. 23      # `逻辑核`绑定
    >   cpubind: 0 1                # CPU 绑定
    >   nodebind: 0 1               # node 绑定
    >   membind: 0 1                # 内存绑定

查看 /porc/cpuinfo

使用以下的指令可以查看 CPU 的信息。

    cat /proc/cpuinfo
Linux描述
processor逻辑核序号
physical idCPU插槽识号
core id逻辑核标识号

注意:

  1. 无论 是否开启 NUMA 所观察到的cpuinfo都是一样的。
  2. core id并不一定是连续的。该数字与硬件相关。
  3. 需要同时考虑physical idcore id,才能唯一决定出一个逻辑核

例子:

cpuinfo processorcpuinfo physical idcpuinfo core id
000
101
202
303
404
505
608
709
8010
9011
10012
11013
1210
1311
1412
1513
1614
1715
1818
1919
20110
21111
22112
23113

查看 NUMA/cpu 相关的文件系统

在 Linux 中。查看文件系统,可以观察基础的 NUMA/cpu 信息。
查看文件系统/sys/devices/system/cpu/,可以得知逻辑核的信息。
查看文件系统/sys/devices/system/node/,可以得知NUMA的信息。

相关的文件系统目录树如下:

    /
    +-> sys/
        +-> devices/
            +-> system/
                +-> cpu/                    # <== `逻辑核`
                |   +-> cpu0/
                |   |   +-> node0           # 软链接
                |   |   +-> topology/
                |   |       +-> core_id
                |   |       +-> physical_package_id
                |   |
                |   +-> cpu1/
                |   ...
                |   +-> cpu#/
                |
                +-> node/                   # <== `NUMA node`
                    +-> node0/
                    |   +-> cpu0            # 软链接
                    |   +-> cpu1            # 软链接
                    |   ...
                    |   +-> cpu#            # 软链接
                    |   +-> numastat
                    |
                    +-> node1/
                    ...
                    +-> node#/
  1. NUMA 关闭时
    # 主板上所有的`逻辑核`
    ll /sys/devices/system/cpu  | grep cpu
    >   drwxr-xr-x. 9 root root    0 Jan 25 11:41 cpu0
    >   drwxr-xr-x. 9 root root    0 Jan 25 11:41 cpu1
    >   drwxr-xr-x. 9 root root    0 Jan 25 11:41 cpu2
    >   ...
    >   drwxr-xr-x. 9 root root    0 Jan 25 11:41 cpu23

    # 某一个`逻辑核` 所在的 NUMA node id
    ll /sys/devices/system/cpu/cpu23 | grep node
    >   lrwxrwxrwx. 1 root root    0 Jan 30 13:35 node0 -> ../../node/node0

    # 某一个`逻辑核` 的`逻辑核`标识号
    cat  /sys/devices/system/cpu/cpu23/topology/core_id
    >   13

    # 某一个`逻辑核` 所在的`CPU插槽`标识号
    cat  /sys/devices/system/cpu/cpu23/topology/physical_package_id
    >   1

    # 主板上所有的`NUMA node`
    ll /sys/devices/system/node/ | grep node
    >   rwxr-xr-x. 4 root root    0 Jan 25 11:41 node0

    # `NUMA node0`下的 `逻辑核` 列表
    ll /sys/devices/system/node/node0 | grep "cpu"
    >   lrwxrwxrwx. 1 root root    0 Jan 25 13:14 cpu0 -> ../../cpu/cpu0
    >   lrwxrwxrwx. 1 root root    0 Jan 25 13:14 cpu1 -> ../../cpu/cpu1
    >   lrwxrwxrwx. 1 root root    0 Jan 25 13:14 cpu2 -> ../../cpu/cpu2
    >   ...
    >   lrwxrwxrwx. 1 root root    0 Jan 25 13:14 cpu23 -> ../../cpu/cpu23
  1. NUMA 开启时:
    # 主板上所有的`逻辑核`
    ll /sys/devices/system/cpu  | grep cpu
    >   drwxr-xr-x. 9 root root    0 Jan 30 10:11 cpu0
    >   drwxr-xr-x. 9 root root    0 Jan 30 10:11 cpu1
    >   drwxr-xr-x. 9 root root    0 Jan 30 10:11 cpu2
    >   ...
    >   drwxr-xr-x. 9 root root    0 Jan 30 10:11 cpu23

    # 某一个`逻辑核` 所在的 NUMA node id
    ll /sys/devices/system/cpu/cpu23 | grep node
    >   lrwxrwxrwx. 1 root root    0 Jan 30 13:35 node1 -> ../../node/node1

    # 某一个`逻辑核` 的`逻辑核`标识号
    cat  /sys/devices/system/cpu/cpu23/topology/core_id
    >   13

    # 某一个`逻辑核` 所在的`CPU插槽`标识号
    cat  /sys/devices/system/cpu/cpu23/topology/physical_package_id
    >   1

    # 主板上所有的`NUMA node`
    ll /sys/devices/system/node/ | grep node
    >   drwxr-xr-x. 4 root root    0 Jan 30 10:11 node0
    >   drwxr-xr-x. 4 root root    0 Jan 30 10:11 node1

    # `NUMA node0`下的 `逻辑核` 列表
    ll /sys/devices/system/node/node0 | grep "cpu"
    >   lrwxrwxrwx. 1 root root    0 Jan 30 11:17 cpu0 -> ../../cpu/cpu0
    >   lrwxrwxrwx. 1 root root    0 Jan 30 11:17 cpu2 -> ../../cpu/cpu2
    >   lrwxrwxrwx. 1 root root    0 Jan 30 11:17 cpu3 -> ../../cpu/cpu3
    >   ...
    >   lrwxrwxrwx. 1 root root    0 Jan 30 11:17 cpu11 -> ../../cpu/cpu11

    # `NUMA node1`下的 `逻辑核` 列表
    ll /sys/devices/system/node/node1 | grep "cpu"
    >   lrwxrwxrwx. 1 root root    0 Jan 30 11:19 cpu12 -> ../../cpu/cpu12
    >   lrwxrwxrwx. 1 root root    0 Jan 30 11:19 cpu13 -> ../../cpu/cpu13
    >   lrwxrwxrwx. 1 root root    0 Jan 30 11:19 cpu14 -> ../../cpu/cpu14
    >   ...
    >   lrwxrwxrwx. 1 root root    0 Jan 30 11:19 cpu23 -> ../../cpu/cpu23

查看 NUMA 统计信息

/sys/devices/system/node/文件夹中记录系统中的所有内存节点的相关详细信息。
numastat 等同于 cat /sys/devices/system/node/node[0…n]/numastat。

项目备注
numa_hit打算在本节点上分配内存,最后从本节点分配的次数
num_miss打算在本节点分配内存,最后却从其他节点分配的次数
num_foregin打算在其他节点分配内存,最后却从这个节点分配的次数
interleave_hit采用interleave策略最后从本节点分配的次数
local_node本节点上的进程,在本节点上分配的次数
other_node其他节点进程,在本节点上分配的次数
    # NUMA 关闭时:
    numastat
    >                              node0
    >   numa_hit                 5571187
    >   numa_miss                      0
    >   numa_foreign                   0
    >   interleave_hit             53465
    >   local_node               5571187
    >   other_node                     0

    # NUMA 开启时:
    numastat
    >                              node0           node1
    >   numa_hit                 1628912         1134271
    >   numa_miss                      0               0
    >   numa_foreign                   0               0
    >   interleave_hit             19496           19299
    >   local_node               1624846         1112228
    >   other_node                  4066           22043

注意:

如果发现 numa_miss 数值比较高时,说明需要对分配策略进行调整。
例如将指定进程关联绑定到指定的CPU上,从而提高内存命中率。

numactl 中的 nodes 和 cpus 的表示方法

在后续的 NUMA 的 CPU 分配策略NUMA 的 内存 分配策略 中需要用到以下方法,表示 node 和 cpu 的集合。

  1. numactl 中的 nodes 的 表示方法
项目备注
all表示所有的 node 的集合
same表示与之前设置一样
N,N,NN-NN,N-NN-N,N-N表示 node 的集合
+N,N,N+N-N+N,N-N表示相对于进程当前的设置的 node 的集合
!N-N表示 非 N-N 的 node 的集合
!+N-N表示 非 +N-N 的 node 的集合
  1. numactl 中的 cpus 的 表示方法
项目备注
all表示所有的 cpu 的集合
N,N,NN-NN,N-NN-N,N-N表示 cpu 的集合
+N,N,N+N-N+N,N-N表示相对于进程当前的设置的 cpu 的集合
!N-N表示 非 N-N 的 cpu 的集合
!+N-N表示 非 +N-N 的 cpu 的集合

NUMA 的 CPU 分配策略

注意:

每个进程(或线程)都会从父进程继承 NUMA CPU 分配策略,并分配有一个优先 node。

选项:

–all, -a

重置所有的 CPU 分配策略。进程可以使用所有可能的核。

–cpunodebind=nodes, -N nodes

规定进程在指定的 NUMA node 上运行。
注意一个 NUMA node 可能由多个逻辑核组成。

–physcpubind=cpus, -C cpus

规定进程在指定的逻辑核上运行。

NUMA 的 内存 分配策略

选项:

–localalloc, -l

规定进程从当前 node 上请求分配内存。

–membind=nodes, -m nodes

规定进程只能从指定的 nodes 上请求分配内存。

–preferred=node

指定了 一个 推荐的 node 来获取内存,如果失败,则尝试别的 node。

–interleave=nodes, -i nodes

规定进程从指定的 nodes 上,以 round robin 算法 交织地 请求分配内存。

NUMA 的 分配策略例子

    # Unset default cpuset awareness.
    numactl --all

    # Run ${process} on node 0 with memory allocated on node 0 and 1.
    numactl --cpunodebind=0 --membind=0,1 ${process} ${process_arguments}

    # Run ${process} on cpus 0-4 and 8-12 of the current cpuset.
    numactl --physcpubind=+0-4,8-12 ${process} ${process_arguments}

    # Run ${process} with its memory interleaved on all CPUs
    numactl --interleave=all ${process} ${process_arguments}

    # Run process as above, but with an option (-l) that would be confused with a numactl option.
    numactl --cpunodebind=0 --membind=0,1 -- ${process} -l

    # Run ${network-server} on the node of network device eth0 with its memory also in the same node.
    numactl --cpunodebind=netdev:eth0 --membind=netdev:eth0 ${network-server}

    # Set preferred node 1 and show the resulting state.
    numactl --preferred=1 numactl --show

    # Interleave all of the sysv shared memory region specified by /tmp/shmkey over all nodes.
    numactl --interleave=all --shm /tmp/shmkey

    # Place a tmpfs file on 2 nodes.
    numactl --membind=2 dd if=/dev/zero of=/dev/shm/A bs=1M count=1024
    numactl --membind=3 dd if=/dev/zero of=/dev/shm/A seek=1024 bs=1M count=1024

    # Reset the policy for the shared memory file file to the default localalloc policy.
    numactl --localalloc /dev/shm/file

vm 内核参数

overcommit_memory

Memory Overcommit 表示:在内存申请的时候,承诺给进程的内存大小,可以超过了实际可用的内存。

因为 UNIX/Linux 的算法中,物理内存页的分配发生在使用的瞬间,而不是在申请的时候。
而且往往实际使用到的内存比申请的少,所以 Linux 允许 memory overcommit。

一旦出现内存不足(Out-Of-Memory),Linux 有两种方法处理:

  1. 使用 OOM killer,不断挑选进程出来杀死,直到腾出内存。
  2. 通过设置内核参数 vm.panic_on_oom,自动重启系统。

因为以上的 OOM 机制,都有可能造成业务中断。
所以自从 Linux 2.6 之后,可以通过内核参数 vm.overcommit_memory 禁止 memory overcommit。

参数路径:

    /proc/sys/vm/overcommit_memory

设定值:

0

(默认值)
表示内核将检查是否有足够的可用内存供应用进程使用。

  • 如果有足够的可用内存,内存申请允许;
  • 否则,内存申请失败,并把错误返回给应用进程。

1

表示内核允许分配所有的物理内存,而不管当前的内存状态如何。

2

表示内核不允许分配超过所有物理内存和交换空间总和的内存

例子:

    echo 0 > /proc/sys/vm/overcommit_memory
    # 写入到 `/etc/sysctl.conf`
    sysctl -w vm.overcommit_memory=0

zone_reclaim_mode

内存回收策略

参数路径:

    /proc/sys/vm/zone_reclaim_mode

设定值:

0

(默认值)
当某个节点可用内存不足时,系统会从其他节点分配内存。

1

当某个节点可用内存不足时,系统会从本地节点回收 cache 内存

例子:

    # 多数时候 cache 对性能很重要,所以 0 是一个更好的选择。
    echo 0 > /proc/sys/vm/zone_reclaim_mode
    # 写入到 `/etc/sysctl.conf`
    sysctl -w vm.zone_reclaim_mode=0

实例分析

mongodb 在 NUMA 的问题

mongodb日志显示如下:

WARNING: You are running on a NUMA machine.
We suggest launching mongod like this to avoid performance problems:
numactl –interleave=all mongod [other options]

解决方案:

  1. 修改 NUAM 内存分配策略为 交织分配

在原启动命令前面加 numactl –interleave=all

    numactl --interleave=all ${MONGODB_HOME}/bin/mongod --config conf/mongodb.conf

2.修改内核内存回收策略为 从其他节点分配内存

    echo 0 > /proc/sys/vm/zone_reclaim_mode ;
    echo "vm.zone_reclaim_mode = 0" >> /etc/sysctl.conf;

极端解决方案:

  1. 从 BIOS 层关闭 NUMA。
  2. 从 OS 层关闭 NUMA。

参考资料

NUMA的取舍与优化设置
DPDK内存大页在NUMA架构重分配的问题
DPDK优化点和NUMA架构
直接内存访问 DMA
Node Interleaving: Enable or Disable?
SMP、NUMA、MPP体系结构介绍
SWAP的罪与罚
玩转cpu-topology

  • 12
    点赞
  • 86
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值