Redis知识梳理

Redis:Remote  Dictionary Service

Redis特点:

  1. 基于内存,速度快
  2. 支持丰富的数据类型
  3. 支持事务
  4. 可设置过期时间
  5. 可持久化数据(异步操作flush到硬盘上保存)
  6. value可达1GB
  7. 单进程单线程
  8. 读写分离: Master用来插入写,Slave用来检索读
  9. 受内存限制

Redis适用场景:

  • 会话缓存(购物车信息)
  • 全页缓存(FPC) 最小速度加载页面
  • 队列
  • 排行榜/计数器
  • 发布/订阅(聊天系统)

Redis的高并发:

  1. Redis是基于内存的,内存的读写速度非常快
  2. Redis是单线程的,省去了很多上下文切换的时间
  3. Redis使用了多路复用技术(epoll),可以处理并发连接


Redis基本数据结构:

数据结构指令
stringmget、setnx
list

lpush(异步队列放入事件)、lpop、blpop、
lindex、lrange、lindex、lrem、linsert、
lset、rpush

ltrim(定长链表,index可以是负数,-1表示倒数第一个元素,-2表示倒数第二个元素)

hash

hset、hget、hgetall、hexists、hkeys、hvals

hincrby:对单个子key进行计数

setsdiff、smembers(获取状态)、sinter、scard(点赞)、
srem(消除踩)、sadd
zset(SortedSet)

zadd(添加关注)、zscore、zrange(获取粉丝)、
zcount、zrank、zrevrank、zrem(取消关注)

zrangebysocre

对象的底层实现:滑动验证页面

string

int                     如果字符串对象保存的是整数值,且可以用long类型表示

embstr              如果字符串对象保存到是一个字符串值,且长度小于等于32字节

raw (SDS)    如果字符串对象保存到是一个字符串值,且长度大于32字节

list

ziplist               1. 保存的字符串对象的长度都小于64字节 & 2. 保存的元素数量小于512个

linkedlist           其他

hash

同HashMap,数组+链表

ziplist                1. 保存的字符串对象的长度都小于64字节 & 2. 保存的元素数量小于512个

dict  ht(hashtable)         其他

set

内部相当于一个特殊的字典,字典中所有的value都是一个值NULL。当集合中最后一个元素被移除之后,数据结构被自动删除,内存被回收

interset             1.所有的元素都是整数   &   2.元素数量不超过512个

dict  ht(hashtable)  其他

zset

ziplist(压缩列表)    1.元素成员的长度小于64字节  &  2.元素量小于128

skiplist+dict                其他

String扩容:String扩容都是加倍现有的空间,如果字符串长度超过1MB,扩容时一次只会多扩1MB。字符串最大长度是512MB

渐进式rehash策略:渐进式rehash会在rehash的同时,保留新旧两个hash结构,查询会同时查询两个hash结构,然后在后续的定时任务以及hash操作指令中,循序渐进地将旧hash的内容一点点迁移到新的hash结构中,当搬迁完成,会使用新的hash结构取而代之。

zset:一方面是一个set,保证内部value的唯一性,另一方面可以给每个value赋予一个score,代表这个value的排序权重

容器数据结构的通用规则:

  1. create if not exists
  2. drop if no elements

分布式锁:

Redis分布式锁不要用于较长时间的任务

有个稍微安全的方案是set指令的value参数设置一个随机数,释放锁时先匹配随机数是否一致,然后再删除key

setnx xxx true

expire xxx 5  //防止中间逻辑异常del没有被调用,产生死锁

(set xxx true ex 6 nx)

del xxx

锁冲突处理:直接抛出异常,通知用户稍后重试,sleep一会再重试,将请求转移到延时队列,过一会再试

分布式锁不安全场景:

假如在哨兵模式中 主节点获取到锁之后,数据没有同步到从节点主节点挂掉了,这样数据完整性不能保证,另一个客户端请求过来,就会一把锁被两个客户端持有,会导致数据一致性出问题。

同一把锁被两个客户端同时持有

Redlock的解决方式:

加锁时,它会向过半节点发送set指令,只要过半节点set成功,就认为加锁成功

释放锁使用del指令

延迟队列:

队列为空:使用sleep解决

阻塞读:在队列没有数据时,会立即进入休眠状态,一旦数据到来,立即醒过来。消息延迟几乎为零,用blpop/brpop替代lpop/rpop


Redis持久化:

redis持久化RDB和AOF - 乐晨的个人空间 - OSCHINA - 中文开源技术交流社区

一起看懂Redis两种持久化方式的原理 - SegmentFault 思否

  • RDB:最终结果  dump.rdb
  • AOF:记录过程
名称

RDB  快照(适合冷备)

save 阻塞执行

bgsave  fork子进程出来

在指定的时间间隔内生成数据集的时间点快照

AOF  日志追加  1.1引入(适合热备)

重构AOF文件:使用bgrewriteaof命令

过程

多进程Cow:

  1. 使用glibc函数fork子进程,父进程处理client的请求
  2. 子进程做数据持久化,不会修改现有内存的数据结构,只会对数据结构进行遍历读取,然后序列化到磁盘中,父进程需要对内存数据进行修改,修改的是被复制出来的页
  3. 当子进程完成对新 RDB 文件的写入时,Redis 用新 RDB 文件替换原来的 RDB 文件,并删除旧的 RDB 文件

AOF 的默认策略为每秒钟 fsync一次,将内核缓存刷到磁盘

  1. 子进程往临时文件写入以往命令,序列化到AOF日志中
  2. 父进程此时处理客户端来的请求,并把命令缓存起来(先执行命令再日志存盘)
  3. 当子进程完成临时文件之后,会通知父进程,父进程将缓存的命令写入到临时文件后,使用copy-on-write技术,用临时文件替代AOF文件
优点

只有一个备份文件,恢复大数据要快

RDB是内存数据二进制序列化形式,在存储上非常紧凑

就算出问题,最多丢失1秒内的更新数据
缺点

服务器故障丢失数据

子进程如果变大会耗时,造成客户端停止

AOF体积可能会很大,系统重启时会从AOF读命令,会很久

所以需要定期进行AOF重写,给AOF瘦身

Redis的主节点不会进行持久化操作,持久化操作主要在从节点进行,从节点是备份节点,没有来自客户端请求的压力

Redis4.0使用了混合持久化,将RDB文件的内容和增量的AOF日志文件存在一起。因此Redis重启时,可以先加载RDB内容,然后重放AOF日志,这样就可以代替AOF全量文件重放,重启效率会大幅提升


Jedis连接池:JedisPool
管理连接,缓存Jedis和redis server之间的连接

创建保存连接的容器

LinkedBlockingDeque<PooledObject<T>> idleObject; //存放连接对象
Map<IdentityWrapper<T>,PooledObject<T>> allObjects; //没有可用连接时创建连接

过程:Jedis使用JedisPoolConfig创建idleObject, startEvictor 自动回收空闲连接

JedisConfig config = new JedisConfig();
config.setMaxTotal(10);//最大连接数
config.setMaxIdle(1000);//最大空闲时间
config.setMaxWaitMillis(1000);//连接最大等待时间
config.setTestOnBorrow(true);//获取时是否校验连接
config.setTestOnReturn(false);//归还时是否校验连接

JedisPool pool = new JedisPool(config,......);
Jedis jedis = pool.getResource();
......
pool.returnResource(jedis);

获取一个连接

  • 从idleObject队列中获取连接;
  • 有空闲连接时直接返回,没有空闲连接时创建空闲连接
  • 创建失败后继续阻塞等待
  • 连接使用完毕,关闭连接

连接池会将归还的连接从allObjects中拿出来放入到idleObjects中,所以下一次从idleObjects直接获取。

jedisPool会在指定时间对连接池中空闲对象删除

  • 当关闭一个连接时,如果连接存在,将会把资源还给连接池
  • 如果idleObjects队列中连接的数据已经 >= 允许的最大连接数 就会直接销毁

将连接放入到idleObjects队列中,一旦放入,如果长时间不被使用将直接自动回收

为防止异常后jedis对象无法返回回收,用下列方式访问:

try(Jedis jedis = pool.getResource()){ //用完自动close
    doSomething(jedis);
}

  • Redis设置过期时间:expire key exptime
  • 查看过期时间:ttl  key    如果一个字符串已经设置了过期时间,然后你调用set方法修改它的值,它的过期时间将消失
  • 永久:persist key
  • 搜索键: keys *name

keys一次返回所有匹配的key,可以使用scan进行替换

Redis遍历所有key的两个命令 -- KEYS 和 SCAN_代码一天不写我浑森蓝廋的博客-CSDN博客_redis获取所以key

  • 随机产生key: randomkey

Redis写入Redis,移动数据:

  • 本地删除:   migrate
  • 本地不删除:move

  • jedis一次设置多个值:jedis.mset("name","...","age","...")
  • 字符串追加:Long setnx = jedis.setnx("name","..."); jedis.append("name","...")
  • 一次获取多个值:jedis.mget("name","age"); //会打散分组,然后依次对每个实例调用mget方法,最后汇总为一个
  • 删除:jedis.del(...);
  • 判断键是否存在:jedis.exists(...);
  • 清空子库:jedis.flushDB();
  • 清空所有子库:jedis.flushAll();
  • 查看Redis内部参数:info stats  /  info clients  / info memory / info replication

Redis过期策略:

  1. 定时删除:Redis会将每个设置了过期时间的key放入到一个独立字典中,以后会定时遍历这个字典删除过期key
  2. 惰性策略:在客户端访问这个key时,Redis对key的过期时间进行检查,如果过期就立即删除

Redis定时扫描策略:

  1. 从过期字典中随机选出20个key
  2. 删除这个20个key中已过期的key
  3. 如果过期key的比例超过1/4,就重复上面操作

如果有大量key过期,给过期时间设置一个随机范围,不要全部在同一个时间过期

Redis定时任务:

Redis定时任务会记录在最小堆数据结构中,最快要执行的任务排在堆的最上方,在每个循环周期里,Redis会对最小堆里面已经到时间点的任务进行处理,处理完毕后,将最快要执行的任务还需要的时间记录下来,这个时间就是select系统调用的timeout参数。


Redis的Pipeline批量操作:客户端提供的,提高效率,节省网络延迟损耗
pipeline会打包客户端指令,将数据打包发送给服务器,无需等待指令的返回

管道的本质:客户端对管道中指令列表改变读写顺序,大幅节省IO时间

redis在处理所有打包数据之前,必须缓存所有指令的处理结果

Pipeline pipelined = jedis.pipeline();
pipelined.set(...);

Redis通信协议RESP(Redis  Serialization Protocal

是一种直观的文本协议,优势在于实现过程简单,解析性能好

Redis协议将传输的数据结构分为5种最小单元类型,单元结束统一加上回车换行符号\r\n

  1. 单个字符以"+"符号开头      +hello world\r\n
  2. 多行字符串以”$“符号开头,后跟字符串长度      $11\r\nhello world\r\n
  3. 整数值以":"符号开头,后跟整数的字符串形式   :1024\r\n
  4. 错误消息以"-"符号开头     -WRONGTYPE XXXX
  5. 数组以"*"号开头,后跟数组的长度   *3\r\n:1\r\n:2\r\n:3\r\n
  6. NULL     $-1\r\n
  7. 空串      $0\r\n\r\n

客户端向服务器发送的指令只有一种格式,多行字符串数组封装发送


Redis排序:SortingParams

  • 数字排序
SortingParams sortingParams = new SortingParams();
sortingParams.desc();
  • 非数字排序
SortingParams sortingParams = new SortingParams();
sortingParams.alpha();
sortingParams.asc();
  • 结果分页
sortingParams.limit(0,2);

Redis分布式处理
    
不同key采用一致性hash分片,将不同key分配到不同Redis服务器上

JedisSharedInfo jedisSharedInfo1 = new JedisSharedInfo("","");
JedisSharedInfo jedisSharedInfo2 = new JedisSharedInfo("","");
List<JedisSharedInfo> list = new LinkedList<JedisSharedInfo>();
list.add(jedisSharedInfo1);
list.add(jedisSharedInfo2);
SharedJedisPool pool = new SharedJedisPool(config,list);
SharedJedis sharedJedis = pool.getResource();

Redis高可用方案:

  1. keepalived:虚拟IP,提供主从同一访问,部署复杂
  2. zookeeper
  3. sentinel(哨兵模式):自动进行故障恢复
  4. Redis Cluster(官方)
  5. Twemproxy:静态分布Redis方案,很难做到平滑扩缩容 
  6. Redis Cluster
  7. Codis

主从复制:

slaveof host port 让一个服务器成为另一个服务器的从服务器
  1. Slave向Master发送同步指令,Master进行快照发送给Slave
  2. Slave接收到快照后,清空内存表,重建内存表数据结构
  3. Master在发送快照中,接收到任何改变的命令,都会保存在发送缓存中,快照完成后,依次发送给Slave


增量复制:Redis同步的是指令流,主节点会将那些自己的状态产生的指令记录在buffer中,然后异步将buffer同步到从节点中,从节点一边执行一边反馈自己同步状态。buffer是有限的,buffer中可能会被后续指令覆盖掉

无盘复制:减少快照同步对系统的影响。生成快照是一个遍历过程,主节点会一边遍历内存,一边将序列化的内容发送到从节点。从节点收到内容存储到磁盘文件中,再进行一次性加载。

哨兵Sentinel:可以监听集群中的服务器,并在主服务器进入下线时,自动从服务器中选举出新的服务器

默认端口26379

  • discover_master
  • discover_slaves

Sentinel过程:Sentinel负责监控主从节点的健康,当主节点挂掉时,自动选择一个最优的从节点切换成主节点。客户端连接集群时,会首先连接Sentinel,通过Sentinel来查询主节点的地址,然后再连接主节点进行数据交互。当主节点发生故障时,客户端会重新向Sentinel要地址,Sentinel将最新的主节点告诉客户端

数据保护:哨兵无法保证消息完全不丢失,可以规定节点复制的延迟时间,如果超过时间则反馈该节点已断开,防止数据丢失

Codis:将众多小内存的Redis实例整合起来,是Redis集群方案之一

Codis上挂接的所有Redis实例构成一个Redis集群,当集群空间不足时,可以通过动态增加Redis实例来实现扩容需求

Codis实现原理:

Codis默认将所有key划分为1024个槽位,它首先将客户端传过来的key进行crc32运算计算hash值,再将hash后的整数值对1024这个整数进行取模得到一个余数,这个余数就是对应的key的槽位。

每个槽位都会唯一映射到后面的多个Redis实例之一,Codis会在内存中维护槽位和Redis实例的映射关系

针对多Codis实例,我们讲槽位关系存储在zookeeper中,Codis增加SLOTSSCAN指令,扫描出待迁移槽位的所有key,然后挨个迁移每个key到新的Redis节点。当Codis接收到位于正在迁移槽位中的key后,会立即强制对当前的单个key进行迁移,迁移完成后,再将请求转发到新的Redis实例中。

Codis会在系统比较空闲的时候观察每个Redis实例对应的slot数量,如果不平衡,就会进行自动迁移

hash = crc32(command.key)

slot_index = hash % 1024

redis = slots[slot_index].redis

redis.do(command)

Codis限制:

  1. 不再支持事务,事务只能在单个Redis实例中完成
  2. rename操作会很危险
  3. 为了支持扩容,单个key对应的value不宜过大(不超过1MB)
  4. 整体性能上要比单个Redis性能有所下降
  5. 增加了zookeeper运维的代价

Redis Cluster:

去中心化,每个节点负责的数据会不一样,多个节点相互连接组成一个对等的集群,它们之间通过一种特殊的二进制协议交互集群信息

当大多数节点认为某个节点失联时,集群才认为某节点需要进行主从切换容错

Redis Cluster将所有数据划分为16384个槽位,比Codis的1024个槽位更精细,每个节点负责其中一部分槽位

客户端跳转:

当客户端向一个错误的节点发送指令之后,该节点会发现指令的key所在的槽位并不归自己管理,它会向客户端发送一个特殊的跳转指令并携带目标操作的节点地址,告诉客户端去连接这个节点并获取数据

迁移:

从源节点获取内容  -->   存到目标节点  -->  从源节点删除内容

提供了redis-trib可以让运维人员手动调整槽位的分配情况

Redis Cluster限制:

  1. 不支持事务
  2. Cluster的mget方法比Redis慢很多,被拆分成多个get指令
  3. Cluster的rename方法不再是原子的,需要将数据从源节点转移到目标节点

Redis缓存与Mysql数据库一致性

读:读redis没有,则会从数据库读redis,并写回redis
写:(并发不高)写mysql成功,再写回redis缓存
       (并发高)写redis缓存,再写入mysql

Redis与MongDB的区别
事务、持久化、最大key、数据结构


Redis数据流处理流程:

Redis主函数redis.C文件中,主函数最终调用aeMain函数进入事件处理循环

void aeMain(asEventloop *eventloop){
    eventloop->stop=0;
    while(!eventloop->stop){
        if(eventloop->before!=NULL){
            eventloop->beforesleep(eventloop);//处理外部事件
        }
    aeProcessEvent(eventloop.AE_ALL_EVNET);
    //文件定时器事件ProcessTimeEvents
    //文件相关事件readQueryFromClient
    }

}

readOnlyFromClient过程:

processInputBuffer--->processCommand--->call

唤起后lookupCommand会去redisCommandTable中找命令

Redis的启动方式:

  1. 直接启动  ./redis-server &
  2. 指定配置文件启动  ./redis-server /etc/redis/6379.conf
  3. 使用redis启动服务配置开机自启动

Redis段页分离:

Redis内存回收策略

lru  最近最少使用ttl    删除存活时间最短的random   任意选择

volatile

过期数据集

allkeys

全体数据集

内存回收机制:

Redis可以使用jemalloc、tcmalloc管理内存

操作系统是以页为单位来回收内存的,这个页只要还有一个key在使用,则不会回收

虽然无法保证立即回收已经删除的key的内存,它会重新使用哪些尚未回收的空闲内存

近似LRU算法:

  1. 给每个key增加一个额外的小字段,这个字段长度是24个bit,也就是最后一次被访问的时间戳
  2. 随机采样出5个key,淘汰掉最旧的key,如果淘汰后内存还是超过maxmemory,就继续淘汰,指导内存低于maxmemory
  3. 如果是allkeys,就从所有key字典中随机采样;如果是volatile,就从带过期时间的key字典中随机采样

LFU:按最近访问频率进行淘汰

Redis3.0在算法中增加了淘汰池,淘汰池是一个数组,在每一次淘汰循环中,新的随机得出的key列表会和淘汰池中key列表进行融合,淘汰最旧的一个key之后,保留key列表待下一次循环

惰性删除:

unlink key 对删除操作进行懒处理,丢给后台线程来异步回收内存

flush异步化:flushdb async、flushall async,丢给后台线程异步回收

Redis位数介绍:redis位操作介绍 - 知乎

  • setbit:setbit kk  位数  值      set kk a
  • getbit:getbit kk offset
  • bitcount:bitcount kk  获取二进制中1个个数
  • bitfield:bitfield 指令1 指令2    一次操作多个位(最多只能处理64个连续的位,如果超过64位,就得使用多个子命令)

bitfiled有三个子命令:get,set,incrby

bitfield指令提供了溢出策略子指令 overflow,只影响接下来的下一个指令

  • 折返wrap
  • 失败fail,报错不执行
  • 饱和截断sat

例如:bitfield w overflow sat  incrby u4 2 1

Redis的位数组是自动扩展的,如果设置了某个偏移位置超过了现有的内容范围,就会自动将位数组进行零填充。

可以用来开发的功能包括:用户一年的迁到记录,在线状态等

HyperLogLog

主要解决统计问题,HyperLogLog提供了不精确去重方案,标准误差是0.81%,减少大数据量下set集合统计的空间浪费

HyperLogLog需要占据12KB存储空间,在存储计数较小时,将采用系数矩阵,当稀疏矩阵超过阈值时,才会一次性转为稠密矩阵(12KB)

  • 增加计数:pfadd          pfadd key  xxx
  • 获取计数:pfcount       pfcount  key
  • 将多个pf计数值累加在一起形成一个新的pf值:pfmerge

HyperLogLog的实现原理:

给定一系列随机整数,低位连续零位的最大长度K,可以通过K估算出随机数的数量N。K与N的对数之间存在显著的线性相关性

为什么pf的占用时12KB?

HyperLogLog实现中用的是16384个桶,也就是2的14幂,每个桶的maxbits需要6个bit来存储,最大可以表示maxbits=63,于是总占用内存就是(2的14次幂*6 / 8)=12KB

Redis布隆过滤器:

  • bf.add
  • bf.exists
  • bf.madd   需要添加多个元素
  • bf.mexists  判断多个元素是否存在
  • 自定义参数布隆过滤器:bf.reserve   key  error_rate(错误率,默认0.01)  initial_size(预计放入的元素数量,当实际数量超过数值,误判率上升.默认100)

hash函数的最佳数量k = 0.7*(位数组长度l / 预计元素的数量n)

错误率f = 0.6185^ (位数组长度l  / 预计元素的数量n)

错误率为10%时,一个元素需要平均指纹空间为4792个bit,大约为5bit

错误率为1%时,一个元素需要平均指纹空间为9.585个bit,大约为10bit

错误率为0.1%时,一个元素需要的平均指纹空间为14.377个bit,大约为15bit

Redis-Cell限流:

cl.throttle   key  漏斗容量   漏水速率   可选参数

GeoHash:将二位的经纬度数据映射到一维的整数,将所有元素挂载到已条线上,距离靠近的二维坐标映射到一维后的点之间距离也会很接近。GeHash会继续对这个整数做一次base32编码变成一个字符串。

zset的value是key,score是GeoHash的52位整数值。通过zset的score排序就可以得到坐标附近的其他元素

p74

  • geoadd  key 经度  纬度  别名
  • geodist  计算两个元素之间的距离
  • geopos  geopops  company juejin    获取元素的坐标
  • geohash   获取元素的经纬度编码字符串
  • georadiusbymember     查询元素附近的其他元素,可以用withdist显示距离

georadiusbymember company  ireader 20 km withcoord withdist  withhash count 3 asc

  • georadius   根据用户的定位计算附近的一些服务或建筑

注意:在集群中单个key对应的数据量不宜超过1MB,否则会导致集群迁移出现卡顿现象,建议对Geo数据单独部署Redis实例;

如果数据量过大,需要对Geo数据进行拆分(比如按省、市等维度)

Scan:优化key *

scan  cursor整数值,key的正则表达模式,limit hint

事务:事务模型很不严格

Redis的单线程特性,可以保证“隔离性”,但Redis事务不具备原子性。因为事务队列中的指令失败,后面的指令会继续执行

  • multi  事务开始
  • watch  key   加上乐观锁
  • exec  事务执行,在执行exec命令前所有命令都缓存在一个事务队列中
  • discard  事务丢弃

Redis分布式乐观锁watch:当事务执行时,Redis会检查关键变量自watch之后是否被修改了,如果有改动则返回null

PubSub  消息多播

  • subscribe
  • publish

pubsub的缺点:

  1. 如果一个消费者都没有,那么消息会被直接丢弃
  2. 在断连期间生产者发送的消息,对于这个消费者就彻底丢失了
  3. PubSub的消息不会持久化

改进:Disque专门做多播消息队列

Redis安全:

  1. rename-command:将某些危险指令修改成其他名称,防止误操作
  2. 指定ip监听 + 密码访问限制
  3. ssl代理:spiped  客户端spiped接收请求数据加密后,发送到服务端spiped进程解密

常见的NoSQL  几张图看懂列式存储_香飘叶子的技术博客_51CTO博客

  • 列式存储:Cassandra、HBase  列式数据库把一列中的数据值串在一起存储起来,然后再存储下一列的数据

      特点:包括查询快,由于查询需要读取的blocks少;数据压缩比高,正因为同一类型的列存储在一起。Load快。 简化数据建模的复杂性。但是插入更新慢,不太适合数据老是变化,它是按列存储的

  • key-value存储:Memcached  Redis  它的数据按照键值对的形式进行组织,索引和存储
  • 文档存储:MongoDB  CouchDB   文档存储支持对结构化数据的访问,没有强制的架构 
    • 与关系模型不同的是,文档存储模型支持嵌套结构
    • 与键值存储不同的是,文档存储关心文档的内部结构

缓存击穿:热点数据过期,恰好大量并发请求过来

(解法:热点数据永不过期 / 当发现要过期了,异步进程进行缓存重建)

缓存穿透:用户大量请求在缓存和DB都没有,导致不断的查DB

(解法:DB查不到也缓存在DB中,或使用布隆过滤器)

缓存雪崩:大量key失效,此刻又有大量请求过来

(解法:1)设置不同的过期时间,防止同时失效;2)搭建高可用redis集群;3)设置多级缓存)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值