三、Redis高可用(主从复制和集群Cluster)

Redis高可用技术主要由主从复制Replicas、哨兵Sentinel和集群Cluster。现在哨兵已经淘汰,主要使用集群。
本文详细介绍了主从复制和集群,包括搭建步骤。

1 Redis高可用演变过程

1.1 单机版

核心技术:持久化

持久化是最简单的高可用方法(有时甚至不被归为高可用的手段),主要作用是数据备份,即将数据存储在硬盘,保证数据不会因进程退出而丢失。

1.2 主从复制Replicas

复制是高可用Redis的基础,哨兵和集群都是在复制基础上实现高可用的。复制主要实现了数据的多机备份,以及对于读操作的负载均衡和简单的故障恢复。缺陷是故障恢复无法自动化;写操作无法负载均衡;存储能力受到单机的限制。

1.3 哨兵Sentinel

在复制的基础上,哨兵实现了自动化的故障恢复。

缺陷是写操作无法负载均衡;存储能力受到单机的限制。

1.4 集群Cluster

基于主从复制技术。

通过集群,Redis解决了写操作无法负载均衡,以及存储能力受到单机限制的问题,实现了较为完善的高可用方案。

2 主从复制

2.1 优缺点

img

Redis采用主从(replication)部署结构,相较于单机而言最大的特点就是主从实例间数据保持一致(异步),并且提供数据持久化和备份策略。

优点

  • 高可靠性:一方面,采用双击主备架构,能够在主库出现故障时自动进行主备切换,从库提升为主库提供服务,保证服务平稳运行;另一方面,开启数据持久化功能和配置合理的备份策略,能有效的解决数据误操作和数据异常丢失的问题。
  • 读写分离策略:从节点可以扩展主库节点的读能力,有效应对大并发量的读操作。

缺点

  • 故障恢复复杂,如果没有Redis HA 系统(需要另开发),当主库节点出现故障时,需要手动将一个从节点晋升为主节点,并让其他从库节点去复制新主库节点。
  • 发生主从切换后需要通知客户端变更配置。
  • 主库的写能力受到单机的限制,可以考虑分片。
  • 主库的存储能力受到单机的限制,可以考虑Pika。

2.2 原理

Redis 中主从节点复制数据有全量复制和部分复制之分。

旧版本(低于2.8)全量复制功能的实现

img

全量复制使用 Snyc 命令来实现,其流程是:

从服务器向主服务器发送 Sync 命令。

主服务器在收到 Sync 命令之后,调用 Bgsave 命令生成最新的 RDB 文件,将这个文件同步给从服务器,这样从服务器载入这个 RDB 文件之后,状态就会和主服务器执行 Bgsave 命令时候的一致。

主服务器将保存在命令缓冲区中的写命令同步给从服务器,从服务器执行这些命令,这样从服务器的状态就跟主服务器当前状态一致了。

旧版本全量复制功能,其最大的问题是从服务器断线重连时,即便在从服务器上已经有一部分数据了,也需要进行全量复制,这样做的效率很低,于是新版本的 Redis 在这部分做了改进。

新版本(高于2.8)全量复制功能的实现

新版本 Redis 使用 Psync 命令来代替 Sync 命令,该命令既可以实现完整全同步也可以实现部分同步。

复制偏移量

执行复制的双方,主从服务器,分别会维护一个复制偏移量:

主服务器每次向从服务器同步了 N 字节数据之后,将修改自己的复制偏移量 +N。

从服务器每次从主服务器同步了 N 字节数据之后,将修改自己的复制偏移量 +N。

复制积压缓冲区

img

主服务器内部维护了一个固定长度的先进先出队列做为复制积压缓冲区,其默认大小为 1MB。

在主服务器进行命令传播时,不仅会将写命令同步到从服务器,还会将写命令写入复制积压缓冲区。

服务器运行 ID

每个 Redis 服务器,都有其运行 ID,运行 ID 由服务器在启动时自动生成。

主服务器会将自己的运行 ID 发送给从服务器,而从服务器会将主服务器的运行 ID 保存起来。

从服务器 Redis 断线重连之后进行同步时,就是根据运行 ID 来判断同步的进度:

如果从服务器上面保存的主服务器运行 ID 与当前主服务器运行 ID 一致,则认为这一次断线重连连接的是之前复制的主服务器,主服务器可以继续尝试部分同步操作。

否则,如果前后两次主服务器运行 ID 不相同,则认为是完成全同步流程。

Psync 命令流程

有了前面的准备,下面开始分析 Psync 命令的流程:

如果从服务器之前没有复制过任何主服务器,或者之前执行过 slaveof no one 命令,那么从服务器就会向主服务器发送 psync ? -1 命令,请求主服务器进行数据的全量同步。

否则,如果前面从服务器已经同步过部分数据,那么从服务器向主服务器发送 psync 命令,其中 runid 是上一次主服务器的运行 id,offset 是当前从服务器的复制偏移量。

img

前面两种情况主服务器收到 Psync 命令之后,会出现以下三种可能:

主服务器返回 +fullresync 回复,表示主服务器要求与从服务器进行完整的数据全量同步操作。

其中,runid 是当前主服务器运行 id,而 offset 是当前主服务器的复制偏移量。

如果主服务器应答 +continue,那么表示主服务器与从服务器进行部分数据同步操作,将从服务器缺失的数据同步过来即可。

如果主服务器应答 -err,那么表示主服务器版本低于 2.8,识别不了 Psync 命令,此时从服务器将向主服务器发送 Sync 命令,执行完整的全量数据同步。

2.3 搭建步骤

为了简单,在同一台机器上搭建一主一从的主从复制(下面是配置文件方式,也可以使用命令直接随时动态修改)。

主节点配置redis_7000.conf, 然后启动。

# 服务器端口号
port 7000
# 使得Redis可以跨网络访问 
bind 0.0.0.0 

从节点配置redis_8001.conf,然后启动

# 服务器端口号
port 8001
# 使得Redis可以跨网络访问 
bind 0.0.0.0 
# 配置主节点信息
replicaof 192.168.43.201 7000

这时分别连接这两个redis,可以发现

  • 连接到主节点(端口7000),可以正常操作,且数据几乎是实时同步到了从节点。
  • 从节点能查到主节点的数据,但不能写(默认只读)

3 哨兵

简单的主从复制有个问题,就是主节点挂了之后,无法从新选举新的节点作为主节点进行写操作,导致服务不可用。所以接下来介绍Sentinel(哨兵)功能的使用。哨兵是一个独立的进程,哨兵会实时监控master节点的状态,当master不可用时会从slave节点中选出一个作为新的master,并修改其他节点的配置指向到新的master。

该哨兵系统执行以下三个任务:

  • 监控(Monitoring):Sentinel 会不断地检查你的主服务器和从服务器是否运作正常。
  • 提醒(Notification):当被监控的某个 Redis 服务器出现问题时, Sentinel 可以通过 API 向管理员或者其他应用程序发送通知。
  • 自动故障迁移(Automatic failover): 当一个主服务器不能正常工作时, Sentinel 会开始一次自动故障迁移操作, 它会将失效主服务器的其中一个从服务器升级为新的主服务器, 并让失效主服务器的其他从服务器改为复制新的主服务器; 当客户端试图连接失效的主服务器时, 集群也会向客户端返回新主服务器的地址, 使得集群可以使用新主服务器代替失效服务器。

img

从Redis3.0推出集群后,不再建议使用哨兵,推荐使用Cluster集群。

4 集群Cluster

4.1 优缺点

Redis cluster集群是一个由多个主从节点群组成的分布式服务器群,它具有复制、高可用和分片特性。Redis cluster集群不需要sentinel哨兵也能完成节点移除和故障转移的功能。需要将每个节点设置成集群模式,这种集群模式没有中心节点,可水平扩展,据官方文档称可以线性扩展到1000节点。redis cluster集群的性能和高可用性均优于之前版本的哨兵模式,且集群配置非常简单。

优点

  • 无中心架构;
  • 数据按照 slot 存储分布在多个节点,节点间数据共享,可动态调整数据分布;
  • 可扩展性:可线性扩展到 1000 多个节点,节点可动态添加或删除;
  • 高可用性:部分节点不可用时,集群仍可用。通过增加 Slave 做 standby 数据副本,能够实现故障自动 failover,节点之间通过 gossip 协议交换状态信息,用投票机制完成 Slave 到 Master 的角色提升;
  • 降低运维成本,提高系统的扩展性和可用性。

缺点

  • Client 实现复杂,驱动要求实现 Smart Client,缓存 slots mapping 信息并及时更新,提高了开发难度,客户端的不成熟影响业务的稳定性。目前仅 JedisCluster 相对成熟。
  • 节点会因为某些原因发生阻塞(阻塞时间大于 clutser-node-timeout),被判断下线,这种 failover 是没有必要的。
  • 数据通过异步复制,不保证数据的强一致性。
  • 多个业务使用同一套集群时,无法根据统计区分冷热数据,资源隔离性较差,容易出现相互影响的情况。
  • Slave 在集群中充当“冷备”,不能缓解读压力,当然可以通过 SDK 的合理设计来提高 Slave 资源的利用率。
  • Key 批量操作限制,如使用 mset、mget 目前只支持具有相同 slot 值的 Key 执行批量操作。对于映射为不同 slot 值的 Key 由于 Keys 不支持跨 slot 查询,所以执行 mset、mget、sunion 等操作支持不友好。
  • Key 事务操作支持有限,只支持多 key 在同一节点上的事务操作,当多个 Key 分布于不同的节点上时无法使用事务功能。
  • Key 作为数据分区的最小粒度,不能将一个很大的键值对象如 hash、list 等映射到不同的节点。
  • 复制结构只支持一层,从节点只能复制主节点,不支持嵌套树状复制结构。
  • 避免产生 hot-key,导致主库节点成为系统的短板。
  • 避免产生 big-key,导致网卡撑爆、慢查询等。
  • 重试时间应该大于 cluster-node-time 时间。
  • Redis Cluster 不建议使用 pipeline和multi-keys 操作,减少 max redirect 产生的场景。

通常(也是推荐的最低配置),一个集群由6个节点组成(3主3从)。

整个集群中若任意1个节点故障,集群整体仍然可用。

由于3主节点(或者更多主节点),则如何将数据进行分片放到每个主节点,通常有Hash算法,但是Hash算法在集群收缩,节点数发生变化存在问题。因此Redis提出了Slot的概念(接近于一致性Hash算法)。

4.2 原理

4.2.1 数据分布

正如开篇中提到的,分布式数据库要解决的就是将整块数据,按照规则分配到多个缓存节点,解决的是单个缓存节点处理数量大的问题。

如果要将这些数据进行拆分,并且存放必须有一个算法。例如:

  • 哈希算法。简单易用,但无法支持节点变化(扩展和收缩)
  • 一致性哈希。一定程度解决了节点扩展和收缩(相邻节点仍会受到影响)。
  • 虚拟槽(Slot)。预先分配一个足够大的空间比如16384 个槽。哈希算法结果一定落在这些槽位中,每个集群节点记录本节点可受理的槽位。哈希结果与节点的对应关系,通过槽实现了解耦了。

Redis Cluster 则采用的是虚拟槽分区算法。其中提到了槽(Slot)的概念。这个槽是用来存放缓存信息的单位,在 Redis 中将存储空间分成了 16384 个槽,也就是说 Redis Cluster 槽的范围是 0 -16383(2^4 * 2^10)。

缓存信息通常是用 Key-Value 的方式来存放的,在存储信息的时候,集群会对 Key 进行 CRC16 校验并对 16384 取模(slot = CRC16(key)%16383)。

得到的结果就是 Key-Value 所放入的槽,从而实现自动分割数据到不同的节点上。然后再将这些槽分配到不同的缓存节点中保存。
在这里插入图片描述

如图 所示,假设有三个缓存节点分别是 1、2、3。Redis Cluster 将存放缓存数据的槽(Slot)分别放入这三个节点中:

缓存节点 1 存放的是(0-5000)Slot 的数据。缓存节点 2 存放的是(5001-10000)Slot 的数据。缓存节点 3 存放的是(10000-16383)Slot 的数据。

此时 Redis Client 需要根据一个 Key 获取对应的 Value 的数据,首先通过 CRC16(key)%16383 计算出 Slot 的值,假设计算的结果是 5002。

将这个数据传送给 Redis Cluster,集群接受到以后会到一个对照表中查找这个 Slot=5002 属于那个缓存节点。

发现属于“缓存节点 2”,于是顺着红线的方向调用缓存节点 2 中存放的 Key-Value 的内容并且返回给 Redis Client。

4.2.2 集群节点通信

img

在这个图中,每一个蓝色的圈都代表着一个redis的服务器节点。它们任何两个节点之间都是相互连通的。客户端可以与任何一个节点相连接,然后就可以访问集群中的任何一个节点。对其进行存取和其他操作。

如果说 Redis Cluster 的虚拟槽算法解决的是数据拆分和存放的问题,那么存放缓存数据的节点之间是如何通讯的,就是接下来我们要讨论的。

缓存节点中存放着缓存的数据,在 Redis Cluster 的分布式部署下,缓存节点会被分配到一台或者多台服务器上。
在这里插入图片描述

图 2:新上线的缓存节点 2 和缓存节点 1 进行通讯

缓存节点的数目也有可能根据缓存数据量和支持的并发进行扩展。如图 2 所示,假设 Redis Cluster 中存在“缓存节点 1”,此时由于业务扩展新增了“缓存节点 2”。

新加入的节点会通过 Gossip 协议向老节点,发出一个“Meet 消息”。收到消息以后“缓存节点 1”,会礼貌地回复一个“Pong 消息”。

此后“缓存节点 2”会定期发送给“缓存节点 1” 一个“Ping 消息”,同样的“缓存节点 1”每次都会回复“Pong 消息”。

上面这个例子说明了,在 Redis Cluster 中缓存节点之间是通过 Gossip 协议进行通讯的。

其实节点之间通讯的目的是为了维护节点之间的元数据信息。这个元数据就是每个节点包含哪些数据,是否出现故障。

节点之间通过 Gossip 协议不断相互交互这些信息,就好像一群人在一起八卦一样,没有多久每个节点就知道其他所有节点的情况了,这个情况就是节点的元数据。

4.2.3 Move重定向

Redis 的客户端无论访问集群中的哪个节点都可以路由到对应的节点上,因为每个节点都有一份 ClusterState,它记录了所有槽和节点的对应关系。

下面来看看 Redis 客户端是如何通过路由来调用缓存节点的:
在这里插入图片描述

图 7:MOVED 重定向请求

如图 7 所示,Redis 客户端通过 CRC16(key)%16383 计算出 Slot 的值,发现需要找“缓存节点 1”读/写数据,但是由于缓存数据迁移或者其他原因导致这个对应的 Slot 的数据被迁移到了“缓存节点 2”上面。

那么这个时候 Redis 客户端就无法从“缓存节点 1”中获取数据了。

但是由于“缓存节点 1”中保存了所有集群中缓存节点的信息,因此它知道这个 Slot 的数据在“缓存节点 2”中保存,因此向 Redis 客户端发送了一个 MOVED 的重定向请求。

这个请求告诉其应该访问的“缓存节点 2”的地址。Redis 客户端拿到这个地址,继续访问“缓存节点 2”并且拿到数据。

上面的例子说明了,数据 Slot 从“缓存节点 1”已经迁移到“缓存节点 2”了,那么客户端可以直接找“缓存节点 2”要数据。

那么如果两个缓存节点正在做节点的数据迁移,此时客户端请求会如何处理呢?
在这里插入图片描述

图 8:ASK 重定向请求

如图 8 所示,Redis 客户端向“缓存节点 1”发出请求,此时“缓存节点 1”正向“缓存节点 2”迁移数据,如果没有命中对应的 Slot,它会返回客户端一个 ASK 重定向请求并且告诉“缓存节点 2”的地址。

客户端向“缓存节点 2”发送 Asking 命令,询问需要的数据是否在“缓存节点 2”上,“缓存节点 2”接到消息以后返回数据是否存在的结果。

4.2.4 缓存节点扩展/收缩

作为分布式部署的缓存节点总会遇到缓存扩容和缓存故障的问题。这就会导致缓存节点的上线和下线的问题。

由于每个节点中保存着槽数据,因此当缓存节点数出现变动时,这些槽数据会根据对应的虚拟槽算法被迁移到其他的缓存节点上。
在这里插入图片描述

图 9:分布式缓存扩容

如图 9 所示,集群中本来存在“缓存节点 1”和“缓存节点 2”,此时“缓存节点 3”上线了并且加入到集群中。

此时根据虚拟槽的算法,“缓存节点 1”和“缓存节点 2”中对应槽的数据会应该新节点的加入被迁移到“缓存节点 3”上面。

针对节点扩容,新建立的节点需要运行在集群模式下,因此新建节点的配置最好与集群内其他节点配置保持一致。

新节点加入到集群的时候,作为孤儿节点是没有和其他节点进行通讯的。因此,其会采用 cluster meet 命令加入到集群中。

在集群中任意节点执行 cluster meet 命令让新节点加入进来。假设新节点是 192.168.1.1 5002,老节点是 192.168.1.1 5003,那么运行以下命令将新节点加入到集群中。

192.168.1.1 5003> cluster meet 192.168.1.1 5002

这个是由老节点发起的,有点老成员欢迎新成员加入的意思。新节点刚刚建立没有建立槽对应的数据,也就是说没有缓存任何数据。

如果这个节点是主节点,需要对其进行槽数据的扩容;如果这个节点是从节点,就需要同步主节点上的数据。总之就是要同步数据。
在这里插入图片描述

图 10:节点迁移槽数据的过程

如图 10 所示,由客户端发起节点之间的槽数据迁移,数据从源节点往目标节点迁移:

客户端对目标节点发起准备导入槽数据的命令,让目标节点准备好导入槽数据。这里使用 cluster setslot {slot} importing {sourceNodeId} 命令。之后对源节点发起送命令,让源节点准备迁出对应的槽数据。使用命令 cluster setslot {slot} importing {sourceNodeId}。此时源节点准备迁移数据了,在迁移之前把要迁移的数据获取出来。通过命令 cluster getkeysinslot {slot} {count}。Count 表示迁移的 Slot 的个数。然后在源节点上执行,migrate {targetIP} {targetPort} “” 0 {timeout} keys{keys} 命令,把获取的键通过流水线批量迁移到目标节点。重复 3 和 4 两步不断将数据迁移到目标节点。目标节点获取迁移的数据。完成数据迁移以后目标节点,通过 cluster setslot {slot} node {targetNodeId} 命令通知对应的槽被分配到目标节点,并且广播这个信息给全网的其他主节点,更新自身的槽节点对应表。

既然有缓存服务器的上线操作,那么也有下线的操作。下线操作正好和上线操作相反,将要下线缓存节点的槽数据分配到其他的缓存主节点中。

迁移的过程也与上线操作类似,不同的是下线的时候需要通知全网的其他节点忘记自己,此时通过命令 cluster forget{downNodeId} 通知其他的节点。

当节点收到 forget 命令以后会将这个下线节点放到仅用列表中,那么之后就不用再向这个节点发送 Gossip 的 Ping 消息了。

不过这个仅用表的超时时间是 60 秒,超过了这个时间,依旧还会对这个节点发起 Ping 消息。

不过可以使用 redis-trib.rb del-node{host:port} {donwNodeId} 命令帮助我们完成下线操作。

尤其是下线的节点是主节点的情况下,会安排对应的从节点接替主节点的位置。

4.3 搭建步骤

可以通过命令逐步搭建Cluster集群。

  • 对于Redis4.0及以下版本,通常使用redis自带的ruby脚本进行快速搭建集群。
  • 对于Redis5.0及以上版本,无需ruby,直接使用redis-cli --cluster命令配置集群(这里以此为例)

4.3.1 环境准备

部署一套Redis Cluster集群。每个数据中心有6个节点,组成三主三从的Cluster模式。

主机名IP地址对外服务端口Cluster内部通讯端口备注
yzredis122.224.173.47638116381默认master节点
yzredis222.224.173.48638116381默认slave节点
yzredis322.224.173.49638116381默认master节点
yzredis422.224.173.50638116381默认slave节点
yzredis522.224.173.51638116381默认master节点
yzredis622.224.173.52638116381默认slave节点

安装路径:

  • 为简单统一,6个机器均使用appgess用户操作。
  • 假设$APP_BASE的目录为/app/usr

4.3.2 安装redis

若已经安装可省略

$ tar xzf redis-5.0.8.tar.gz
$ cd redis-5.0.8
$ make
$ make test
$ make install

检查安装结果

$ redis-server -v
$ redis-cli -v

4.3.3 配置节点

以一台主机为例,其他节点均需要按本章步骤分别进行执行!

在目录$APP_BASE/redis/node_config 下,用vim编辑上步拷贝得到的redis.conf文件。修改内容如下

# 绑定本机IP地址(可指定具体的IP地址)
bind 0.0.0.0

# 保护模式:关闭
protected-mode no

# 指定端口
port 6381

#redis后台运行
daemonize    yes

# pidfile文件对应目录
pidfile  /var/run/redis_6381.pid

# 日志文件名
logfile "node_6381.log"

# 设置Redis可使用的最大内存
maxmemory  8GB

# aof持久化日志开启  有需要就开启,它会每次写操作都记录一条日志
appendonly  yes

# 开启集群  把注释#去掉
cluster-enabled  yes

# 集群的配置存放文件  配置文件首次启动会自动生成
cluster-config-file  cluster_auto_config_6381.conf

# 集群节点之间超时设置8秒
cluster-node-timeout  8000

注:集群中每台机器都需要配置(按实际环境调整)。

4.3.4 启动各节点

在每台主机启动redis

进入到目录中
$ cd  $APP_BASE/redis/node_config

$ redis-server redis.conf

查看redis启动状态:

$ ps -ef | grep redis-server

并用redis-cli客户端连接进行ping/PONG测试

输出的结果如下:
在这里插入图片描述

4.3.5 (可选)编写redis启动脚本,纳入操作系统自动启停

vim /etc/init.d/redis-6381.server

#!/bin/sh
#
# Simple Redis init.d script conceived to work on Linux systems
# as it does use of the /proc filesystem.
# chkconfig:   2345 90 10
# description:  Redis is a persistent key-value database
export PATH=$PATH:/usr/local/bin/
REDISSERVER= redisA
REDISPORT=6381
PIDFILE=/var/run/redis_$REDISPORT.pid
CONF="/app/usr/redis/ $REDISSERVER/redis.conf"
case "$1" in
    status)
    if [ ! -f $PIDFILE ]
    then
        echo "Redis stopped..."
    else
        ps -ef | grep redis-server | grep $REDISPORT
    fi
    ;;
    start)
        if [ -f $PIDFILE ]
        then
                echo "$PIDFILE exists, process is already running or crashed"
        else
                echo "Starting Redis server..."
                redis-server $CONF
        fi
        ;;
    stop)
        if [ ! -f $PIDFILE ]
        then
                echo "$PIDFILE does not exist, process is not running"
        else
                PID=$(cat $PIDFILE)
                echo "Stopping ..."
                ps -ef | grep redis-server | grep $REDISPORT | awk '{print $2}' | xargs kill
                while [ -x /proc/${PID} ]
                do
                    echo "Waiting for Redis to shutdown ..."
                    sleep 1
                done
                echo "Redis stopped"
        fi
        ;;
    restart|force-reload)
        ${0} stop
        ${0} start
        ;;
    *)
        echo "Please use start or stop as first argument"
        ;;
esac

然后执行:

$ chkconfig --add redis-6381.server
$ chkconfig redis-6381.server on

4.3.6 配置集群

本节在任意一台机器上执行即可,无需再每个节点上执行。

在任意一个节点上,执行:

$ redis-cli --cluster create \
    22.224.173.47:6381 \
    22.224.173.49:6381 \
    22.224.173.51:6381 \
    22.224.173.52:6381 \
    22.224.173.48:6381 \
    22.224.173.50:6381 \
--cluster-replicas 1

注1:–cluster-replicas 则指定了为Redis Cluster中的每个Master节点配备几个Slave节点数。在本环境一个master配一个slave节点,因此设置为1。

请仔细查看,并确认Cluster配置计划是否正确。如果正确,则在最后一行提示后,输入“yes”表示同意。

4.3.7 检查安装结果

查看cluster info。其cluster_state为OK,cluster_know_nodes为6
在这里插入图片描述

查看cluster nodes,可以看到六个节点,并且有三个分配了slots的为master节点
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值