Redis 知识点速览

Redis 能做什么?

1、集群、分布式架构的 session 共享问题。

通常在集群、分布式架构中使用如下方法,但是都存在一定的问题。

  • 存储在 cookie 中:不安全;
  • 存储在文件服务器或者数据库里:大量的 IO 效率问题;
  • session 复制:session 数据冗余、节点越多浪费越大;

而使用 Redis 作为缓存数据库,数据完全在服务器内存中,安全且速度快。

2、提高数据库性能。

传统方式提高数据库性能,需要破坏一定的业务逻辑。

  • 水平切分:数据的分割(避免一张表数据量过大的问题);
  • 垂直切分:字段的分割(避免一条数据过大的问题);
  • 读写分离:减少服务器压力。

使用 Redis 将数据库中的数据缓存在内存中,从而提高数据库性能,不需要破坏业务逻辑。

3、存放一些有时效性的数据,或者热门数据。如验证码。

四种软件架构:单体架构、分布式架构、微服务架构、Serverless架构

浅谈高性能数据库集群——读写分离

 

NoSql

NoSql 全称是 not only sql,扩展关系型数据库的能力。

NoSql 不适用的场景:需要事务支持、处理复杂的关系、需要即席查询(用户根据自己的需求,灵活的选择查询条件)。

Memchached 和 Redis(remote dictionary server)区别:

  1. M 不支持持久化(R 支持)
  2. M 支持简单的 k-v 模式(R 还支持 list、set、hash、zset 等)

mongoDB(最接近关系型数据库的非关系型数据库,微信小程序的云数据库用的就是这个)。

一分钟搞懂列式与行式数据库

 

Redis

Redis 五种数据结构:全部都是键值对形式,只是值有不同格式。

基本命令

  • select <dbid>:切换数据库(Redis 默认有16 个数据库,默认使用 0 号库)
  • keys *:查询当前库的所有 key
  • exists <key>:判断某个 key 是否存在(返回 0 表示不存在,1 表示存在)
  • type <key>:查看某个 key 的类型
  • del <key>:删除某个 key
  • expire <key> <seconds>:为 key 设置过期时间(单位秒)
  • ttl(Time-To-Live) <key>:查看 key 还有多少秒过期(-1 表示永不过期,-2 表示已过期)
  • dbsize:查看当点数据库的 key 的数量
  • flushdb:清空当前库

  • flushall:清空所有库

String 常用命令

  • get <key>:查询 key 对应的值
  • set <key> <value>:添加键值对
  • append <key> <value>:在 key 对应值的末尾追加 value
  • strlen:获得 key 对应值的长度
  • setnx <key> <value>:只有在 key 不存在时,设置 key 的值
  • incr <key>:将 key 对应的值增加 1(只能对数字操作)【原子操作】
  • decr <key>:将 key 对应的值减 1
  • incrby / decrby <key> <step>:将 key 对应的值增减 step
  • mset(multiple) <key1> <value1> <key2> <value2> ...:同时设置一个或多个键值对
  • mget <key1> <key2> <key3>:同时获取一个或多个 key 对应的值
  • msetnx  <key1> <value1> <key2> <value2> ...:当所有给定的 key 都不存在,同时设置一个或多个 key
  • getrange <key> <start> <end>:获得 key 对应值的范围(包前包后)
  • setrange <key> <start> <value> :用 value 覆盖 key 所对应值的范围内的值
  • setex <key> <seconds> <value>:设置键值的同时设置过期时间
  • getset <key> <value>:设置新值的同时获得旧值

List 常用命令

底层是双向链表,所以某些操分左右。

  • lpush/rpush <key> <value1> <value2>:从左边(插入 a、b、c,结果顺序为 cba)/右边插入一个或多个值
  • lpop/rpop <key>:从左边/右边弹出一个值(值在键在,值光键亡)
  • rpoplpush <key1> <key2>:从 key1 列表右边弹出一个值,插入到 key2 的右边
  • lrange <key> <start> <end>:按照索引下标获得元素(从左到右,-1 代表尾部)
  • lindex <key> <index>:按照索引下标获得元素(从左到右)
  • llen <key>:获得列表长度
  • linsert <key> before/after <value> <newvalue>:在 value 的前/后面插入 newvalue 
  • lrem <key> <n> <value>:删除 n 个值(正数表示从左往右删 n 个,负数表示从右往左删 n 个,0 表示全部删除)

Set 常用命令

Set 是 String 类型的无序集合,放入数据时会自动去重。底层实现是一个 value 为 null 的 hash 表。

  • sadd <key> <value1> <value2> ....:将一个或多个值加入到集合 key 当中,已经存在于集合的值会被忽略
  • smembers <key>:取出该集合的所有值
  • sismember <key> <value>:判断集合 key 是否含有该 value
  • scard <key>:返回该集合的元素个数
  • srem <key> <value1> <value2>:删除集合中的某个元素
  • spop <key>:随机从该集合中弹出一个值
  • srandmember <key> <n>:随机从集合中取出 n 个值(不会从集合中删除)
  • sinter <key1> <key2>:返回两个集合的交集元素
  • sunion <key1> <key2>:返回两个集合的并集元素
  • sdiff <key1> <key2>:返回两个集合的差集元素

Hash 常用命令

Hash 是键值对集合,特别适用于存储对象。

  • hset <key> <field> <value>:给 key 集合中的 field 键(user:10010:username 形式)赋值 value
  • hget <key> <field>: 从 key 集合中取出 value
  • hmset <key1> <field1> <value1> <field2> <value2> ...:批量设置键值对
  • hexists <key> <field>:查看 key 集合中给定 field 键是否存在
  • hkeys <key>:列出 key 集合所有的键
  • hvals <key>:列出 key 集合所有的值
  • hincrby <key> <field> <infrement>:为 key 集合中的 field 的值加上增量
  • hsetnx <key> <field> <value>:将 key 集合中的 field 设置为 value,当且仅当 field 不存在。

ZSet 常用命令

是一个有序的 Set,每个元素都需要关联一个 score,score 用于从低到高排序集合元素。集合元素是唯一的,评分是可以重复。

  • zadd <key> <sorce1> <value1> <score2> <value2>:将一个或多个值以及其 score 加入到 key 集合中(相同元素不同分数,将原始元素分数修改。相同分数不同元素,可以直接加入集合)
  • zrange <key> <start> <end> [WITHSCORES]:获取 key 集合中,下标在范围之间的元素(带 WITHSCORES 可以让 score 和值一起返回)
  • zrangebyscore <key> <min> <max> [withscores]:获取 key 集合所有 score 介于 min 和 max 之间(全包)的元素。
  • zrerangebyscore <key> <max> <min> [withscores]:同上,值次序为从大到小。
  • zincrby <key> <increment> <value>:为 key 集合中元素的 score 加上增量
  • zrem <key> <value>:删除 key 集合下指定值的元素
  • zcount <key> <min> <max>:统计 key 集合,分数区间内的元素个数
  • zrank <key> <value>:返回 key 集合中的 score,从 0 开始(score 可以是负数)

重要配置

  • bind ip:默认情况下,bind 127.0.0.1 开启,即只接受本机访问的请求。需要注意的是,如果开启了 protected-mode,那么在没有设定 bind ip 且没有设置密码的情况下,Redis 也只接收本机的请求。
  • log level:日志级别(debug、verbose、notice、warning),级别越高,信息越少。
  • maxclient:最大客户端连接数
  • maxmemory:redis 可以使用的内存量,一旦达到内存使用上限,redis 会使用移除规则移除内部数据。如果设置了不允许移除,将会直接报错。
  • Maxmemory-policy(移除规则)
    • volatile-lru:使用 LRU(Least Recently Used,最近最少使用)算法移除 key,只对设置了过期时间的键
    • allkeys-lru:对所有 key 使用 LRU 算法移除
    • volatile-random:在过期集合中移除随机的 key,只对设置了过期时间的 key(因此,过期的 key 并没有直接删除,只是不能使用)
    • allkeys-random:对所有 key 随机移除
    • volatile-ttl:移除 TTL 值最小的 key,即最近将要过期的 key
    • noeviction:不进行移除。针对写操作,只返回错误信息
  • Maxmemory-samples:设置样本数量,LRU 和最小 TTL 算法都非精确的,而是估算值,因此可以设置样本大小。(一般设置 3 到 7 的数字,数值越小样本越不准确)

Jedis

Jedis 是 Java 连接 Redis 的一项技术,需要引入相关 jar 包。

相关方法和名上述命令相同。

 

Redis 事务

了解 Redis 事务之前,有一个重要的前提需要牢记:Redis 是单线程的,所以 Redis 的命令都是原子性的

Redis 事务是一个单独的隔离操作:事务中所有的命令都会序列化、按顺序的执行。即事务在执行的过程中,不会被其他客户端发送的命令打断。

需要注意的是,对 Redis 事务的描述是:Redis 事务是一个单独的隔离操作。它只能保证事务中的一系列命令在执行的时候不会被打断,但是执行过程中如果出现错误,事务中的一系列指令并不会回滚。

因此 Redis 事务有如下三大特性:

  1. 单独的隔离操作:事务执行的过程中,不会被客户端发送来的请求打断。
  2. 没有隔离级别概念:事务提交前的指令不会被执行, 也就不存在事务内、事务外的沟通问题。
  3. 不保证原子性:同一事务中如果一条命令执行失败,其他的命令仍然会被执行,没有回滚。

事务指令

  • Multi:输入该命令表示开启事务,之后输入的命令会一起进入命令队列,但不会执行。
  • Exec:输入该命令后,命令队列中的命令会依次执行。
  • Discard:在组队过程中可以通过该命令放弃组队。

错误处理

1、组队中某个命令出现报告错误(编译时异常),执行时整个队列都会被取消。

2、执行阶段某个命令出现了错误,只有报错的命令不会被执行,其他的命令都会执行,不会回滚。

监视

在执行 multi 命令之前,先执行 watch <key1> <key2> .... 命令可以监视一个或多个 key。如果在事务执行之前,监视的 key 被其他客户端改动,则整个事务队列都会被取消。

使用 unwatch 命令取消对所有 key 的监视。若 Exec 或者 discard 命令已经执行,那么就不需要执行 unwatch 命令了。

乐观锁、悲观锁

悲观锁:某个请求操作数据时加锁,期间不允许其他请求操作(不允许访问,处于 block 状态)。

乐观锁:某个请求操作数据时,操作前和操作后的数据会对应两个版本号。期间别的请求来操作该数据,会因为版本号不一致而操作失败。

使用 watch 指令,可以让 Redis 事务实现乐观锁的效果。

【BAT面试题系列】面试官:你了解乐观锁和悲观锁吗?

redis——乐观锁

 

持久化

Redis 提供两种持久化方式:RDB 和 AOF。

恢复持久化数据流程:

  1. 关闭 Redis
  2. 将备份文件(rdb 文件、aof 文件)拷贝到 Redis 工作目录下
  3. 启动 Redis,备份数据会直接加载

如果二者同时开启,Redis 默认加载 aof 文件。

RDB

RDB 即 Redis Database,RDB 会在指定的时间间隔内将内存中的数据集快照写入磁盘,恢复时将快照文件直接读取到内存里。

优点:

  1. 只保存数据,节省磁盘空间
  2. 恢复时直接加载数据,速度快

缺点:

  1. 如果持久化时数据庞大,比较消耗性能
  2. 由于备份周期的存在,如果 Redis 宕机会丢失最后一次快照后的修改

备份流程

Redis 会单独创建(fork — — COW)一个子进程来进行持久化,先将数据写入到一个临时文件中,等持久化过程结束了,再用这个临时文件替换上次持久化好的文件。整个过程,主进程不进行任何 IO 操作,这就确保了极高的性能。

如果对于数据恢复的完整性不是非常敏感,那 RDB 方式要比 AOF 更加高效,缺点是最后一次持久化的数据可能丢失。

dump.rdb

在配置文件中,有如下两个配置:

dbfilename dump.rdb 

dir ./

前者表示保存的文件名(一般不改),后者表示 rdb 文件保存路径(默认存在启动 redis 时的目录下)。一般将后者改成一个固定的路径。

保存策略

配置文件中默认有以下三种自动保存策略:

save 900 1     #  after 900 sec (15 min) if at least 1 key changed  

save 300 10      # after 300 sec (5 min) if at least 10 keys changed

save 60 10000    #   after 60 sec if at least 10000 keys changed

除了自动保存,还可以使用手动保存:save 命令。save 命令是一个阻塞操作,会将内存中的数据持久化到硬盘。

而 bgsave 命令则采用自动保存时的保存方式,会 fork 出一个子线程来持久化数据。

相关配置

  • stop-writes-on-bgsave-error yes:当 Redis 无法写入磁盘时,直接关掉 Redis 写操作。
  • rdbcompression yes:进行 rdb 保存时,将文件进行压缩。
  • rdbchecksum yes:存储快照后,使用 CRC64 算法校验数据完整性(会有性能消耗,如果只是将 Redis 用作缓存,可以关闭该功能)。

RDB

AOF

AOF 即 Append-only File,是以日志的形式来记录每个写操作,将 Redis 执行过程中所有的写指令记录下来,只许追加文件但不可以改写文件。Redis 启动时会读取该文件,根据日志中记录的写指令重新构建数据。

优点:

  1. 备份机制更稳健,丢失数据概率更低
  2. 可读的日志文本,可以处理误操作

缺点:

  1. 比起 RDB 占用更多的磁盘空间
  2. 恢复本分速度慢
  3. 每次读写都同步,有一定的性能压力

相关配置

AOF 默认不开启,需要手动在配置文件中配置:

appendonly no   # 默认不开启 AOF

appendfilename "appendonly.aof"  # 保存文件名

aof 文件的保存路径和 rdb 文件一致(配置文件中 dir 指定的路径)。

appendonly.aof

appendonly.aof 文件中的数据格式如下:

*2
$6
SELECT
$1
0
*3
$3
set
$2
k1
$2
v1
*3
$3
set
$2
k2
$2
v2
*3
$3
set
$2
k3
$2
v3
*1
$8
FLUSHALL

上文代码示例中,最后一个操作是清空所有库,如果直接加载该文件,那么最后 Redis 中的数据还是会被清空。

因此出现这种情况,需要手动将 flushall 指令删除,然后再加载 aof 文件。

同步频率

  • appendfsync always:始终同步,每次 Redis 的写入都会被记入日志
  • appendfsync everysec:每秒同步,每秒记入日志一次
  • appendfsync no:不主动进行同步,让操作系统决定何时进行同步(一般来说是系统缓存被写满)

重写

AOF 采用文件追加方式,所以文件会变得越来越大。为了避免这种情况,新增重写机制。当 AOF 文件的大小超过设定的阈值,Redis 就会启动 AOF 文件的内容压缩,只保留可恢复数据的最小指令集。例如对同一个键的多次修改,只保留最后一次修改。

重写AOF提供两种方式:

  1. rewrite: 在主线程中重写AOF,会阻塞工作线程,在生产环境中很少使用,处于废弃状态;
  2. bgrewrite: 在子进程中重写AOF, 不会阻塞工作线程,能正常服务,此方法最常用

重写机制:当 AOF 文件持续增长而过大时,会 fork 出一条新的进程来将文件重写(和 RDB 一样,也是先写临时文件再替换),遍历新进程的内存中数据,每条记录对应一条 set 语句。
重写 aof 文件的操作,并没有读取旧的 aof 文件,而是将整个内存中的数据库内容用命令的方式重写一个新的 aof 文件。

重写时机:系统载入或上次重写完毕时,Redis 会记录此时 AOF 的大小,设为 base_size。若当前 Redis 的 aof 文件大小 >= base_size + base_size*auto-aof-rewrite-percentage(当且仅当 base_size >= auto-aof-rewrite-min-size)情况下,Redis 会对 aof 文件进行重写。

auto-aof-rewrite-percentage 100   # 默认重写百分比

auto-aof-rewrite-min-size 64mb    # 默认重写最小容量

AOF

AOF介绍

 

主从复制

主从复制就是主机更新数据后,根据配置和策略,自动同步到从机的 master/slave 机制。

主要用于读写分离(缓解 Redis 服务器读操作压力),容灾快速恢复。

配置

配置主从复制,配从不配主

准备两份 Redis 配置文件,将另一份配置文件的端口号修改为 6380。

分别启动两个 Redis 服务:

指令:

  • info replication:打印主从复制的相关信息
  • slaveof <ip> <port>:成为某个实例的从服务器

在主机写入数据后,会自动将数据复制到从机。

注意事项

  1. 主机可以读写,从机只能读。
  2. 从机宕机之后重新启动,需要重新执行 slaveof 指令(也可以在配置文件中永久配置),为从机配置主机,主机会将所有数据复制给从机。
  3. 主机宕机之后重新启动,从机会等待主机重新上线。期间从机无法写入数据。

复制原理

  1. 每次从机联通主机后(从机首次和主机建立连接),都会给主机发送 sync 指令。
  2. 主机会立刻进行存盘操作,发送 rdb 文件给从机。
  3. 从机收到 rdb 文件后,进行全盘加载。
  4. 之后每次主机的写操作,都会立刻发送个从机,从机执行相同的指令。

薪火相传

普通一主二从的主从复制模式,如果主机宕机,那么剩下的从机就无法工作,风险较大。

工作原理:将上一个 slave 作为下一个 slave 的 master,可以去中心化,降低风险。但是如果某个 slave 宕机,那么后面 slave 都无法备份。

若此时主机 1 宕机,只需要在从机 2 上执行 slaveof no one 命令,从机 2 就会变成主机开始工作。

薪火相传模式需要运维人工维护 Redis 服务器,成本较大。

哨兵模式

工作原理:在传统的主从机之外,引入哨兵机,哨兵机会通过心跳机制监视主从机。如果有一台哨兵机检测到主机出现宕机,会通知其他的哨兵机判断主机是否宕机。若半数以上的哨兵机认为主服务器宕机,则在哨兵机中选出一个 Leader,然后由 Leader 决定由哪个从机成为主机。

一主二从三哨兵模式配置:

  1. 在 Redis 工作目录下新建 sentinel.conf 文件
  2. 在配置文件中写入:sentinel monitor mymaster 127.0.0.1 6379 2
  3. mymaster 是为监控的主机起的别名,2 表示至少有 2 个哨兵机同意才会迁移主机。

使用 redis-sentinel /{REDIS_HOME}/sentinel.conf 命令即可启动哨兵。

故障恢复流程:

  1. 从下线主机的所有从机中挑选一个(依照选取规则),将其转为主机
  2. 挑选出新的主机后,哨兵向原主机的从机发送 slaveof 新主机的命令
  3. 当下线的主机重新上线时,哨兵会向其发送 slaveof 命令,让其成为新的从机

选取规则:

  1. 选择优先级靠前的(配置文件中默认 slave-priority 100)
  2. 选择偏移量最大的(获得原主机数据最多的)
  3. 选择 runid 最小的(每个 Redis 实例启动后都会随机生成一个 40 位的 runid)

redis集群选举机制

Redis 主从配置(Windows版)

 

集群

主从复制解决的是 Redis 读压力和容灾能力,但是并没有缓解 Redis 的内存压力。Redis 集群则是用来缓解 Redis 写压力和内存压力,集群和主从复制面向的是两个问题。

Redis 集群实现了对 Redis 的水平扩容,即启动 N 个 Redis 节点,将整个数据库分布存储在 N 个节点中,每个节点存储总数据的 1/N。

环境搭建流程

  1. 安装 ruby 环境
  2. 配置文件中修改配置
    1. cluster-enabled yes  # 打开集群模式
    2. cluster-config-file nodes-6379.conf   # 设定节点配置文件名
    3. cluster-node-timeout 15000 #  设定节点失联时间,超过该时间(毫秒),集群自动进行主从切换
  3. 启动 N 个 Redis 节点(工作目录下可以看到生成 node-xxx.conf 文件)
  4. 将 N 个 Redis 节点合成一个 Redis 集群(使用 create -replicas 1 <ip> <port> <ip> <port> .... 命令)
    1. 一个集群至少要有 3 个主节点。
    2. 选项 -replicas 1 表示为集群中的每个主节点创建一个从节点。
    3. 每个节点会被分配一个 slot(插槽:是一个范围,0 ~ 5104,5105 ~ 10213,一共 16384 个)

通过 cluster nodes 命令可以查看集群信息。

在任意节点进行操作时,集群会根据 key 计算出一个值(CRC16 算法),这个值会落在某一个 slot 内,然后该操作会被重定向到 slot 对应的节点执行。因此不在一个 slot 下的键值,不能使用 mget、mset 等多键操作。

可以通过 {} 定义组的概念,从而使 key 中 {} 内相同内容的键值对放到一个 slot 中去。例如:

set zhangsan{user} 174627292 

set lisi{user} 7262892  # zhangsan 和 lisi 都会被放到同一个 slot,即使计算出来 zhangsan 和 lisi 属于不同的 slot

一些命令:

  • cluster keyslot <key>:计算 key 应该被放置在哪个 slot 中
  • cluster countkeysinslot <slot>:返回 slot 中目前包含的键值对数量
  • cluster getkeysinslot <slot> <count>:返回 count 个 slot 中的键

故障恢复

类似于自动哨兵:

  1. 主节点下线,从节点自动升为主节点
  2. 原主节点恢复后,将成为新主节点的从节点
  3. 如果某一段 slot 的所有主从节点都宕机,Redis 集群将无法提供服务(16384 个 slot 都正常工作时才能对外提供服务,对应配置文件中的 cluster-require-full-coverage yes)。

Hash 一致性算法

用于解决集群扩展问题。

普通做法:使用 hash(key)% 2,可以将数据分到 2 个 Redis 节点。

如果新增一台服务器,之后的数据会使用 hash(key) % 3,将数据分配到 3 个节点。而已经存在于之前两个节点的数据,需要重新取出来使用 hash(key)% 3,再次分发。

带来的问题:

  1. 当缓存服务器数量发生变化时,会引起缓存雪崩,可能会引起整体系统压力过大而崩溃(大量缓存同一时间失效)。
  2. 当缓存服务器数量发生变化时,几乎所有缓存的位置都会发生改变,怎样才能尽量减少受影响的缓存呢?

Hash 一致性算法:通过 hash(ip)% 2^32,将 Redis 节点映射到 Hash 环上。之后需要将数据存入节点时,使用 hash(key) % 2^32 将 key 映射到 Hash 环上,然后顺时针查找,找到的第一台节点,就将数据存放在上面。

之后我们增加新的 Redis 节点,只需要两台 Redis 节点之间发生数据传输即可。

虚拟节点:在进行 hash(ip)% 2^32 计算时,有可能会两个 Redis 节点的映射位置很近,导致数据倾斜。这种情况下可以使用增加虚拟节点来解决问题。

白话解析:一致性哈希算法 consistent hashing

 

缓存穿透、雪崩、击穿

缓存穿透

描述:缓存中没有命中数据,进而去数据库中查询数据(例如黑客查询大量不存在的数据,致使数据库崩溃)。

使用 Redis 作为缓存无法避免缓存穿透,只能避免高频的缓存穿透。

解决方案:

  1. 用 null 值将请求的数据缓存(null 值可能会占用大量的空间)
  2. 布隆过滤器(有错误率,通过一定的错误率换取空间)。

布隆过滤器

布隆过滤器的底层是 bit 数组,数组的每个元素可以标识一个数据是否存在。要查询的数据,通过 hash 函数计算得到一个数组索引,然后在数组的对应的索引位置查看是否存在,存在则去数据库检索,不存在则直接返回。

布隆过滤器由于存在 hash 碰撞,会存在错误率。

因此布隆过滤器判断数据存在,其实可能不存在;判断数据不存在,数据肯定不存在(宁可错杀,也不放过)。

减少碰撞的方法:

  1. 增大数组长度
  2. 增加 hash 函数个数(使用多个索引位置标示一个数据,减少碰撞概率。需要参考数组长度

实现思路:

  1. 计算出需要的 hash 函数个数
  2. 计算出需要的 bit 数据长度
  3. 将需要标识的数据通过 hash 函数计算后初始化 bit 数组(数据预热)
  4. 使用布隆过滤器过滤数据。

关于如何计算需要的 hash 函数个数以及数组长度,一般使用下面的公式计算:

k 为 hash 函数个数,m 为布隆过滤器长度,n 为插入的元素个数,p 为误报率(如允许 1 % 的误报率)。

缓存雪崩

描述:缓存中的数据在某一刻突然失效,导致大量的请求发往数据库。

出现原因与解决方案:

  1. 缓存中数据的有效期一致:给数据加上一个随机有效期,防止数据统一失效。
  2. Redis 宕机:使用主从复制。

缓存击穿

描述缓存中的热门数据失效,导致短时间内大量的请求发往数据库(一般公司碰不到)。

缓存穿透和缓存雪崩的本质都是缓存击穿,是缓存击穿的两种特殊表现。

解决思路:分布式锁

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值