Redis 黑马

Redis 黑马

基础篇

SQL 与NoSQL
NoSQL: 文档型 KV型。图数据库 / 列。
SQL–>>结构化 / 关联的 /SQL查询 /事务ACID/磁盘存储/垂直方式/数据结构固定 相关业务对数据安全性一致性要求较高
NOSQL—>非结构化/无关联/非SQL/ BASE /内存/水平方式/数据结构不固定 对一致性 安全性要求不高 对性能要求

Redis 特征
键值型。 单线程。每个命令具备原子性。
低延迟 速度快 基于内存。IO多路复用。良好的编码。【内存是最重要的】
数据持久化。
主从集群 分片集群。
支持多语言客户端

Redis 安装 Linux
6.2.6 安装。
开机自启。命令行AUTH 指定密码。
RDM

Redis 常见数据结构
String Hash
List Set SortedSet
GEO BitMap HyperLogLog

通配符查看key
help @hash 。 help @generic   help del 
exists。 expire 设置有效期。TTL 查看有效期。 -1 永久有效。

String 类型。
字符串类型 的最大空间 不能超过512M
常见命令---- SET GET。MSET。MGET。
INRE incrby 增加指定步长。 incrbyfloat 必须指定步长 setnx set 不存在的否则不执行。
setex 添加一个 string 指定有效期。
字符串 。 int 。 float 。

Key 的层级格式
层级结构:项目名----业务名—类型—id

Hash 类型
常见命令:HSET HGET HMSET HMGET
MGETALL HKEYS。HVALUES HINCREBY HSETNX

List 类型
底层可以看作 是双向链表。
有序。元素可以重复 插入和删除快 查询速度一般
常见命令:
LPUSH LPOP RPUSH RPOP
LRANGE key start end
BLPOP. BRPOP 没有元素指定等待时间。

Set 类型
无序 元素不重复 查找快 支持交集 并集差集 。
单个集合sadd srem scard sismember smembers
多个集合之间 sinter sdiff sunion。

SortedSort 可排序Set 【TreeSet】
可排序 元素不重复。查询速度快。
每一个元素都带一个Score 属性 底层实现是SkipList 和Hash 表。
ZADD。ZREM ZSCORE。 ZRANK ZRANGE。 ZCARD。 ZCOUNT。 ZINCREBY
ZRANGE key min max
ZRANGEBYSCORE key min max
ZDIFF ZINTER ZUNION

Redis 客户端
Jedis :线程不安全。多线程下 需要基于线程池使用。
Lettuce :基于Netty 实现 支持异步同步和响应式编程的方式 线程安全 支持Redis 的哨兵模式集群模式 管道模式。Spring 默认
Redisson:基于Redis 实现分布式可伸缩的Java数据结构集合。
后续:Jedis。SpringDataRedis 。

Jedis
Jedis连接
Jedis 连接池。 JedisPoolConfig

SpringDataJedis
改变序列化的方式。

--------StringRedisTemplate
减少序列化所携带的数据
Object Mapper 是Spring 中默认使用的JSON 序列化工具。

两种 序列化实践方案:
自定义Redis Template
使用StringRedisTemplate

实战篇

企业实战。
黑马点评Redis
短信登录:redis 共享Session的应用
商户查询缓存
达人探店 排行榜
优惠券秒杀
好友关注
附近商户。GeoHash
UV统计
用户签到

短信登录
1. 发送短信验证码
2. 短信验证码登陆注册
3. 校验登陆状态

RandomUtil.randomString(10)
登陆校验 拦截器:
所有Controller 都不再需要系统校验,由拦截器来做。
用户信息 保存到ThreadLocal
隐藏用户敏感信息。

Session 共享的问题。
替代Session的东西:
数据共享。内存存储 KV 结构
--------Redis 满足 这些要求 Redis 替代Session
Hash 结构方便修改 内存占用更少 String需要保存到JSON格式

拦截器 优化:
登陆拦截器的优化–>> 拆成两个。
一个拦截一切 路径
获取token 查询Redis 的用户 保存到ThreadLocal 刷新Token 有效期 放行
一个 查询ThreadLocal 用户
不存在则拦截
存在则 继续。

商户查询缓存

Redis缓存
缓存更新策略:
内存淘汰。
超时剔除。
主动更新。
主动更新:
cache aside pattern 由缓存调用者在更新数据库的同时更新缓存
read/write through 缓存和数据库 整合为一个服务 调用者调用服务 无需关心缓存一致性
write behind caching pattern。 调用者只操作缓存 由其他线程 异步的将缓存数据持久化到 数据库保持最终一致。
采用第一种。---------

采用删除缓存的方案。
保证缓存和数据库操作的同时成功或失败。
– 单体系统 一个事务。
–分布式系统TCC 分布式事务。

先操作缓存还是先操作数据库?
先删缓存 再操作数据库 或者反过来都行。
线程安全 问题—
– 采用。先操作数据库 再删除缓存。
发生线程安全问题概率低。

缓存穿透 解决思路
缓存 空对象
布隆过滤器。 概率统计 存在不一定存在。
内存占用少 没有多余的Key。 实现复杂 存在误判可能。

增加Id 的复杂度,避免被猜测规律
做好基础格式校验
加强用户权限校验
做好热点参数限流

缓存雪崩
解决方案:
给不同的key的TTL设置随机值
利用Redis集群提高服务的可用性
给缓存服务添加降级限流策略
给业务添加多级缓存。

Redis 缓存击穿
热点Key问题
互斥锁:
逻辑过期:不设置TTL。加一个字段 过期时间。 互斥锁。 新建线程 查询缓存写缓存。

CAP 一致性 可用性的差别
String 中的setnx 可以做到互斥。 分布式锁的一个实现。
Jmeter 做并发测试 –
基于互斥锁的实现----

基于逻辑过期的控制----

缓存重建 用 线程池实现。
逻辑上讲 已经过期 实则永久存在。

缓存工具封装

优惠券秒杀

全局唯一ID
唯一性 递增性。安全性
高可用。高性能
数据库的自增 规律性 太明显。
1+31 时间戳 +32 符号位 + 时间戳+ 序列号
基于Redis的 ID生成器。
位运算 + 或运算 填充。
其他算法:
snowflake 算法
UUID
数据库自增—专门做自增的表

秒杀下单

超卖问题
加锁 解决:
悲观锁:
乐观锁:
版本号法
CAS 法 查库存。🌟
改进 库存大于0.

一人一单
对 id toString 再intern 放入常量池中 加锁 synchronized
setnx +ex 同时发生 。避免 setnx 后宕机 不能释放锁造成死锁。
原子性。

del 删除锁
误删除锁:谁创建 谁释放。 线程 标识:线程ID。

分布式锁 获取锁时存入线程标识 释放锁是 校验标识 UUID 集群环境
----JVM线程ID冲突情况存在 UUID +线程ID.

线程 开始:尝试获取锁--->>>>
判断结果-->>>. nil 获取失败 。 获取成功。 执行业务释放锁。
setnx ex。

判断锁 和释放锁 是原子性的。
Lua 脚本解决 多条命令 原子性的问题。 判断 锁标识 和释放锁的动作 是两个动作 要解决其原子性问题。

基于Redis 的分布式锁
利用Set nx ex 获取锁 并设置过期时间 保存线程标识。
释放锁时 先判断线程标识是否与自己一致,一致则删除锁
特性
利用setnx 满足互斥性
利用setex 保证故障时依然能释放 避免死锁。提高安全性。
利用Redis 集群 保证高可用 和高并发的特性

基于Redis 的分布式 锁的优化

  1. 不可重入
  2. 不可重试
  3. 超时释放
  4. 主从一致性。

分布式锁Redisson 介绍。
Redisson 可重入锁 原理:
锁重试 和WatchDog 机制。
尝试获取锁-- 是。 否–>>>判断是否等待超时

判断ttl是否为null–> 是
是 leaseTime=-1 返回True
否 开启WatchDog 更新时间。
————>>TTL 否–>>
判断剩余等待时间是否>0 否 返回false
是— 订阅并释放锁的信号。 ---->>>> 判断等待是否超时—超时返回false

Redisson 分布式锁原理:
可重入:利用Hash 结构记录线程id 和重入次数。
可重试:利用信号量 和PubSub功能实现等待唤醒 获取锁失败的重试机制。
超时续约:利用WatchDog 每隔一段时间releaseTime /3 重置超时时间。

主从一致性问题
multiLock
可以做主从 也可以 不做主从。

  1. 不可重入Reids 分布式锁
    2) 可重入的Redis 分布式锁
    3)Redisson 的multiLock

Redis 优化秒杀
异步下单。 基于阻塞队列。
1.先利用Redis 完成库存余量 一人一单判断 完成抢单 业务。
2.再将下单业务放入阻塞队列 利用独立线程异步下单。
存在问题:
内存限制问题
数据安全问题。

Redis消息队列实现异步秒杀
Redis 实现消息队列的三种方式:
List 结构
PubSub 基本的点对点消息模型
Stream 比较完善的消息队列模型。

List: RPUSH 和LPOP
实现阻塞:BRPUSH 和BLPOP 。
PubSub:
多消费者。
没有消费者在线 数据会丢失。

Stream的消息队列 Redis5.0+ 新的数据类型。
消息可回溯 永久保存
可以阻塞读取
有漏读的风险

Stream消费者组 ConsumerGroup 。
XREADGROUP 。
1. 消息可回溯
2. 可以多个消费者争抢消息 加快消费速度
3. 可以阻塞读取
4. 没有消息漏读的风险
5. 有消息确认机制 保证一个消息 至少被消费一次。
基于Stream 实现异步秒杀

04 达人探店。
发笔记
点赞
点赞排行榜
05 好友关注
06 附近商铺
07 用户签到
08 UV统计

Hyperloglog~

高级篇

分布式缓存:
	Redis 集群。
		数据丢失。 并发能力 故障恢复  存储能力
  • Redis 持久化
  • Redis 主从
  • Redis 哨兵
  • Redis 分片集群。

Redis 持久化—>>
RDB:
save :前台save。 bgsave:后台save
rdb 底层原理:
fork 主进程得到子进程 子进程 共享主进程底层数据。 完成fork后读取内存数据并写入RDB文件。
fork 采取的是copy-on-write 机制。
基本流程:
fork 主进程 得到一个 子进程。共享内存空间。
子进程 读取内存数据并写入新的RDB文件
新的RDB文件替换旧的文件。
执行时机: save 60 1000
默认服务停止时。
60s内至少执行1000 次修改则触发RDB
缺点:
RDB执行间隔时间长。两次RDB写入数据之间 有丢失的风险。
fork 子进程 压缩 写出RDB 文件都比较耗时。

AOF持久化:
频率always everysec no
bg rewrite aof :AOF重写 最少的命令达到相同的效果。
AOF文件比上次增长超过百分比。 auto-aof-rewrite-percentage。100
AOF体积最小多大以上才触发重写。auto-aof-rewrite-min-size 64mb

Redis 主从:
master 如何判断 slave 是不是第一次来同步数据?
Replication Id:replid。 数据集的标记。id一致说明时同一数据集。 每一个master 都有唯一的replid salve则会继承master节点的replid
offset :slave 完成同步时会记录当前同步的offset。
全量 同步:
1.slave 节点请求 增量同步。
2.master 节点判断replid 发现不一致 拒绝增量同步
3.master 将完整内存数据生成RDB 发送RDB到slave
4.slave清空本地数据 加载master的RDB
5.master将RDB期间的命令记录在repl_baklog 并持续将log中的命令发送给slave
增量同步:
slave重启后执行增量同步。

全量同步 增量同步区别:
	全量:
		master将完整内存数据生成RDB 发送RDB到slave 后续命令则记录在repl_backlog 逐个发送给slave
	增量同步:
		slave提交自己的offset到master master 获取repl_baklog中 从offset 之后的命令给slave
执行全量同步的时机:
	1.slave第一次链接master 节点时。
	2.slave 节点断开时间太久 repl_baklog 中的offset已经被覆盖时

增量同步执行时机:
	slave 节点断开后 又恢复 并且在repl_backlog 中能找到offset时。				

Redis 哨兵:
Sentinel 哨兵 会不断检查您的master和slave 是否按预期工作。
故障恢复:
master 故障,slave会升为master。
通知:
Sentinel 充当 Redis 客户端的服务发现的来源。当集群发生 故障转移时。会讲最新信息推送给Redis客户端,
Sentinel 是基于心跳机制检测服务状态。
主观下线:某sentinel 节点发现某个实例未在规定时间内响应,认为该实例主观下线。
客观下线:若超过指定数量的sentinel 都认为该实例主观下线,则该实例客观下线。quorum值最好超过Sentinel实例数量的一半。

Sentinel 的作用:
监控 故障转移。通知
Sentinel 如何判断一个Redis 实例是否健康?
1.每隔 1s发送一次PING 命令,如果超过一定时间没有相向则认为是主观下线。
2.如果大多数sentinel 都认为实例是主观下线 则判断服务下线。
故障转移的步骤有哪些?
1.首先选定一个slave 作为新的master 执行slaveof no one
2.然后让所有节点都执行 slave of 新的master
3.修改故障节点配置 添加slaveof 新master

Spring 的lettuce —>>. 保证自动切换。

Redis 分片集群: 分布式缓存。
散列插槽
16384 个插槽。
数据和插槽绑定 而非和节点绑定。
如何判断某个key在哪个实例。
1.将16384个插槽分配到不同的实例
2.根据key的有效部分 计算哈希值 对16384取余
3.余数作为插槽 寻找插槽所在的实例即可。
如何将同一类数据固定保存在同一个Redis 实例中?
将 一类数据使用相同的有效部分。 例如key都以{typeId}为前缀。

集群伸缩
故障转移。
	cluster failover ~。
	自动 故障 转移 // 手动故障转移。
	
分片集群。
	Redis Template 访问分片集群。
多级缓存
浏览器 客户端 缓存
Nginx 本地缓存
Redis 缓存
tomcat进程缓存。
数据库~。

JVM 进程缓存
caffeine: 本地缓存。
缓存驱逐策略:
基于容量
基于时间
基于引用。基于软引用 弱引用。 性能较差。
实现进程缓存。

Lua 语法·
多级缓存:
OpenResty:
基于Nginx 的高性能Web 平台 Nginx 定制。
可以当作Nginx 使用。

#### 缓存同步:
	
	设置有效期
	同步双写
	异步通知。
总结 多级缓存:

####Redis 的最佳实践:
Redis键值设计
批处理优化
服务端优化
集群最佳实践。

Redis 的键值设计:
基本格式: [业务名称]:[数据名]:[id]
长度不超过44 字节 embstr 编码6.0+ 低版本是 39字节。
不包含特殊字符。
拒绝BigKey ~
推荐。单个key的value< 10kb
对于集合key。建议元素数量小于1000
危害:
网络阻塞
数据倾斜
Redis 阻塞
CPU压力
键值最佳实践 总结:
Key:固定格式。 足够简短 不超过44字节
不包含特殊字符。
value:
合理拆分数据。 拒绝BigKey
选择合适的数据结构 不能什么都用String?
Hash 结构的entry 数量 不要超过1000
设置合理的超时时间。

	### 批处理优化:
		pipeline :
			不要一次 在批处理中 传输太多命令 否则单次命令占用带宽过多,会导致网络阻塞。
			mset。 hmset 。
		批处理方案:
			原生的M操作。  Pipeline 批处理
			批处理时不建议一次携带太多命令。 Pipeline多个命令之间不具备原子性。
		集群批处理:
			需要落在一个插槽中。
			1)串行slot  
				同一个slot 分到一组。
			2)并行slot. 推荐。。。。
				并行执行。
			3)hash_tag
				有效部分 hash_tag---->>>.  会产生倾斜。
				集群处理:建议RedisTemplate。
服务端的优化:
	持久化配置
	慢查询
		执行耗时超过某个阈值的命令。
		
	命令及安全配置
		1. Redis 一定要设置密码
		2. 禁止线上使用命令:keys。flushall。 flushdb config set 可以利用rename-command 禁用
		3. bind 限制网卡 禁止外网网卡访问
		4. 	开启防火墙
		5. 不用Root账户启用Redis
		6. 尽量不使用默认端口。
	
	内存配置
		数据内存 。✨✨✨
		进程内存 。
		缓冲区内存。 ✨✨✨。
	内存缓冲区:
		复制缓冲区:
		AOF缓冲区:
		客户端缓冲区:✨✨✨ 输入和输出缓冲区。 输入1G且不能设置。  输出可以设置。

集群的最佳实践。
集群模式下lua 和事务 问题?
尽量不要用集群。主从可以达到要求 尽量不用集群。

原理篇

数据结构:
网络模型:
通信协议:
内存策略:
数据结构

动态字符串SDS
IntSet
Dict
ZipList
QuickList
SkipList
RedisObject
五种数据结构

####SDS:
simple dynamic string.
SDS 是个结构体。
优点:
获取字符串长度的时间复杂度为O(1) SDS 中属性。
支持动态扩容
减少内存分配的次数
二进制安全。 不是以结束标识为结尾 而是有记录长度的大小。
len。alloc。 flags。+ 内容。
扩容。 小于1M 大于1M。 内存预分配。

IntSet:

底层基于整数数组。
IntSet 编码 格式。 数组脚标从 0开始 为了寻址方便。 数值超出范围,intset 会自动升级。为合适大小。
倒序 依次将数组中元素 拷贝到扩容后的正确位置。
底层是一个有序的 元素唯一的数组。
特点:
1.Redis 会确保intset 中的元素 唯一 youxu
2. 具备类型升级机制。 可以节省内存空间
3. 底层采用二分查找的方式来查询。

Dict

三部分组成 DictHashTable。 DictEntry。Dict
向Dict中添加键值对时,Redis先根据 key计算出Hash 值h。然后利用h&sizemask 来计算 元素应该存储到数组中的哪个索引位置。

dict 中ht 是存储数据的 其他都是功能性的rehash。
dict 渐进式 rehash。
负载因子。>=1 并且服务器没有执行BGSAVE 或者BGREWRITEAOF 等后天进程。
loadFactor >5.
总结:
1.类似Java的HashTable 结构 底层是数组+ 链表 来解决哈希冲突。
2.Dict包含两个Hash表 ht[0] 平常用,ht[1]用来rehash
Dict的伸缩:
1. load factor>5 或 load factor>1 并且没有子进程任务时 Dict 扩容。
2. Load factor< 0.1 时 Dict 收缩。
3. 扩容大小为第一个大于等于used+1 的 2^n
4. 收缩大小为第一个大于等于used+1 的 2^n
5. Dict 采取渐进式rehash 每次访问Dict时执行一次rehash 一次只迁移一个角标上的数据
6. rehash 时ht[0] 只减不增 新增操作只在ht[1] 执行 其他操作在两个哈希表。

ZipList

压缩列表。
特殊的双端链表。 任何一段进行压入 弹出操作。 连续内存块。
entry~。

ZipList 连锁更新问题
ZipList 总结:
	1. 压缩列表 可以看作一种连续内存空间的双向链表。
	2. 列表的节点之间不是通过指针链接 而是记录上一节点 和本节点长度来寻址。内存占用较低。
	3. 如果列表数据过多 导致链表过长 可能影响查询性能。
	4. 增删较大数据时。 有可能发生连续更新的问题。
QuickList

ZipList 必须申请 连续的内存空间 申请内存效率很低。

缓解这个问题,限制ZipList 的长度和entry 的大小。
多个ZipList 建立联系。
链表中的节点是ZipList。
总结:
1.QuickList 是节点为ZipList的双端链表
2.节点采用ZipList解决了传统链表内存占用的问题
3.控制了ZipList的大小 解决连续内存空间申请效率的问题。
4.中间节点可以压缩,进一步节省了内存。
兼具 链表 和ZipList 的优势。

SkipList

中间随机查询。跳表。 升序排列存储。
总结:
1.跳表本身是一个双向链表。 每个节点 都包含score 和ele值。
2.节点按照score 值排序 score值一样则按照ele 字典排序
3.每个节点 都可以包含多层指针 层数是1–32 随机数。
4.不同层指针到下一个节点的跨度不同 层级越大 跨度越大。
5.增删改查 效率与红黑树基本一致 实现却更简单。

RedisObject

五种数据结构 会被封装为Redis Object。
编码方式---->>>>
STRING---- int. embstr. raw
LIST====. LinkedList和ZipList 。3.2-- QuickList 3.2+
SET。intset HT
ZSET。ZipList HT SkipList
HASH。ZipList HT

数据结构
String 格式
尽量少于44 字节。
List

LinkedList---->>>> ZipList---->>> QuickList

Set

Hash. ✨ ✨ ✨ ✨ ✨
底层是Dict
当存储的数据都是整数时。:IntSet。

Zset

score+ member。
键值存储。键必须 唯一。可以排序。
----- SkipList。Dict
max-entry 为0 或初始化元素> 64字节采用SkipList 否则采用。
ZipList
元素数量不多时HT 和SkipList的优势并不明显,因此Zset还会采用ZipList 结构节省内存,不过需要同时满足两个条件:
1. 元素数量小于zset_max_ziplist_entries。默认128 ,
2. 每个元素都小于zset_ziplist_value 字节 默认64.
zipList本身没有排序功能 而且没有键值对的概念 因此需要zset 通过编码实现。
Ziplist是连续内存,因此score和element 时是紧挨在一起的两个Entry element在前,score在后
score 越小越接近队首,score 越大越接近队尾 按照score 生效排列。

Hash

ZipList,
Hash HT

Redis 网络模型。

用户空间和内核空间
阻塞IO 和非阻塞IO
IO多路复用
利用单个线程来 同时监听多个FD 充分利用CPU资源。
select — 挨个问
poll。 –
只会通知 用户进程有FD 就绪 但不确定 是哪个FD 需要用户进程逐个遍历FD来确认。
epoll
会在通知用户进程FD就绪时 把已经就绪的FD写入用户空间。

poll:
pollfd 既包含了值 也包含了类型。
做了一些优化 但是性能提升不明显。

epoll :
红黑树 和链表。
拷贝到用户空间是 就绪的FD。
减少了 拷贝的数量和次数
放到红黑树上
select模式存在的三大问题:
每次监听的FD最大不超过1024。
每次Select 都需要把所有要监听的FD都拷贝到内核空间。
每次都要遍历所有FD 来判断就绪状态。
Poll模式问题:
Poll模式利用链表解决了select 中监听FD上限的问题。但依然要遍历所有的FD 如果监听较多,性能会下降。
Epoll模式如何解决问题:
基于epoll 实例中的红黑树 保存要监听的FD 理论上 无上限。而且增删改查效率都非常高 性能不会监听的FD数量增多而下降。
每个FD只需要执行一次epoll_ctl 添加到红黑树。以后每一次epoll_wait无需传递任何参数 无需重复拷贝FD到内核空间

Epoll的 LT 和ET
LT 通知多次。
ET 只通知一次。

事件通知机制:
ET模式避免了 LT模式可能出现的惊群现象。
ET模式 最好结合非阻塞IO读取FD数据。相比LT复杂一些。

信号驱动IO

与内核建立SIGIO的信号关联。并设置回调,当内核 FD就绪时,会发出SIGIO 信号通知用户,期间用户应用可以执行其他业务 无需 阻塞等待。

缺点:
大量IO操作时 信号较多,SIGIO处理函数 不能及时处理 可能导致信号队列溢出。
而且内核空间的频繁信号交互性能也较低。

异步IO:
做好并发访问的限流。

同步 OR 异步:
关键看。 数据在内核空间与用户空间的拷贝过程。数据读写的IO操作。是同步还是异步。

Redis 网络模型

Redis 核心业务 命令处理。 是单线程。
整个Redis 是多线程。

纯内存操作。 多线程会导致过多的上下文切换,带来不必要的开销。 性能瓶颈是 网络延迟 而非 执行速度。 多线程并不会带来性能提升。

IO多路复用 + 事件派发 机制。

Redis 通信协议:

RESP 协议:

Redis 的内存策略:
内存回收 过期Key
内存淘汰策略。

过期策略:
dict *dict
dict *expires
redisDb 的结构体。

Redis 如何知道 一个key 是否过期?
1.利用两个Dict 分别记录key-value 和key-ttl 对。
是不是到期立刻删除?:
惰性删除。
周期删除。
定期抽样 删除。
SLOW 模式
FAST模式。

过期Key的记录方式:
在RedisDb 中 通过一个Dict 记录每个key的TTL时间。
过期删除策略:
惰性删除。每次查找key 时判断是否过期。 如果过期则删除。
定时清理:定期抽样 部分key 判断是否 过期,如果过期则删除。
定时清理策略:
SLOW模式 执行频率默认是10 每次不超过25ms
FAST 模式 执行频率不固定,但两次间隔不低于2ms 每次耗时不低于1ms。

淘汰策略

LRU 最少最近使用
LFU。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值