Redis相关内容

1. 什么是redis大key?

Redis大key问题指的是某个key对应的value值所占的内存空间比较大(10kb或者1M),导致Redis的性能下降、内存不足、数据不均衡以及主从同步延迟等问题。

2. 怎么排查大key

1. bigkeys参数:redis-cli -h 127.0.0.1 -p 6379 —bigkeys
2. 开源工具Redis RDB Tools

3. redis大key怎么解决

1. 根本上避免bigkey需要从规范使用上入手:尽量添加TTL、使用“拆分存储”的方式、更大的数据的存储需求不要使用Redis
2. 如果删除bigkey导致服务瘫痪,则说明此时删除操作一定是在主线程中触发,可能是手动执行DEL命令,或者是未开启Lazy Free机制导致过期时在主线程触发删除(过期事件在主线程中执行)

4. 删除redis大key

1. 异步删除
开启lazy free功能,如果触发自动过期删除,则会异步执行
使用unlink命令手动触发,会异步执行删除操作
2. 使用分批删除
对于集合类型的数据,可以通过scan轮询的方式,每次只删除一部分的数据

5. redis为什么那么快

1. Redis是基于内存存储实现的数据库,相对于数据存在磁盘的数据库,就省去磁盘I/O的消耗时间
2. 丰富的对象类型,包含8种对象类型,满足不同场景的需求
3. 高效的数据结构,减少了内存占用和计算复杂度,提高了数据操作的效率
4. 单线程模型,避免了多线程之间的上下文切换和竞争条件,提升CPU利用率
5. 非阻塞IO多路复用机制,充分利用CPU和网络资源,提高了并发处理能力

6. Redis为什么是单线程的

1. Redis单线程指的是「接收客户端请求->解析请求->进行数据读写等操作->发送数据给客户端」这个过程是由一个线程(主线程)来完成的。
2. Redis 在4.0版本之后,新增了一个新的后台线程,用来异步释放Redis内存,也就是lazyfree 线程。例如执行 unlink key / flushdb async / flushall async等命令,会把这些删除操作交给后台线程来执行,好处是不会导致Redis主线程卡顿。因此,当我们要删除一个大 key 的时候,不要使用del命令删除,因为del是在主线程处理的,这样会导致Redis主线程卡顿,因此我们应该使用 unlink 命令来异步删除大key。
3. Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章地采用单线程的方案了。
4. Redis采用了IO多路复用机制处理大量的客户端Socket请求,IO多路复用机制是指一个线程处理多个IO流,就是我们经常听到的select/epoll机制。简单来说,在Redis只运行单线程的情况下,该机制允许内核中,同时存在多个监听Socket和已连接Socket。内核会一直监听这些Socket上的连接请求或数据请求。一旦有请求到达,就会交给Redis线程处理,这就实现了一个Redis线程处理多个IO流的效果。
5. Redis 对 CPU 的要求并不高,反而是对内存和磁盘的要求很高,因为Redis大部分时候都在做读写操作,使用更多的内存和更快的磁盘,对 Redis 性能的提高非常有帮助

7. Redis的基本数据类型有哪些

Redis有6种数据结构sds(简单动态字符串)、ziplist(压缩列表)、linkedlist(链表)、intset(整数集合)、hashtable(字典)、skiplist(跳跃表)

1. String(字符串)

a. 字符串类型的内部编码有3种:int:8个字节的长整型。embstr:小于等于39个字节的字符串。raw:大于39个字节的字符串
b. 可以用来做缓存、计数器(网站访问次数,incr命令对值做自增操作)、限流、分布式锁(SETNX)、分布式Session等。

2. List(列表)

a. 底层数据结构是双向链表和压缩列表,Redis3.2版本之后,List数据类型底层数据结构就只有quicklist【快速链表就是双向链表与压缩列表的组合】
b. 列表是有序的且两端都可以插入和弹出元素。可以用来做消息队列(用户自行实现全局唯一ID)、lpush+lpop或者rpush+rpop实现栈、排行榜、最近访问记录等。

3. Set(集合)

a. 底层数据结构是哈希表和整数集合
b. 集合不允许有重复元素并且集合中的元素是无序的。可以用来做标签系统(sadd)【使用交集(sinter)求两个user的共同标签】、好友关系、共同好友、排名系统、订阅关系等。

4. Zset(有序集合)

a. 底层数据结构是压缩列表和跳表【压缩列表每个节点可以保存一个字节数组或者一个整数值,节点存储列表所占用内存、节点数量及列表长度】
b. 有序集合不能有重复元素但是可以排序。可以用来做排行榜【使用score来记录点赞数,有序集合会根据score排行:zrevrangebyscore】、延迟消息队列【下单系统,下单后需要在15分钟内进行支付,如果15分钟未支付则自动取消订单。将下单的十五分钟后的时间作为score,订单作为value存入redis,消费者轮询去消费,如果消费的时间大于等于这笔记录的score,则将这笔记录移除队列,取消订单】等。

5. Hash(哈希)

a. 底层数据结构:压缩列表和hashtable
b. 可以用来存储复杂对象【hmset/hmget :向Hash表中存入/取出该对象的多个属性值:hmset people name zz age 22 id 1】。

6. Geo(地理位置)

可以用来做位置服务、物流配送、电商推荐、游戏地图等。

7. HyperLogLog(基数统计)

可以用来做用户去重、网站UV统计、广告点击统计、分布式计算等。

8. Bitmaps(位图)

可以用来做在线用户数统计、黑白名单统计、布隆过滤器等

9. Stream(5.0 版新增)

消息队列,相比于基于List类型实现的消息队列,有这两个特有的特性:自动生成全局唯一消息ID,支持以消费组形式消费数据

8. redis缓存更新问题

1. 对于实时性不高的情况:更新数据后再删除缓存,借助监听binlog的消息队列来做删除缓存的操作。这样做的好处是,不用你自己引入,侵入到你的业务代码中,中间件帮你做了解耦,同时,中间件的这个东西本身就保证了高可用。
【避免更新数据时更新缓存造成浪费:如果数据库1小时内更新了1000次,那么缓存也要更新1000次,但是这个缓存可能在1小时内只被读取了1次,那么这1000次的更新没有必要】

2. 缓存更新步骤:
a. 更新数据库数据
b. 数据库会将操作信息写入binlog日志当中
c. 订阅程序提取出所需要的数据以及key【订阅binlog程序在mysql中有现成的中间件叫canal】
d. 另起一段非业务代码,获得该信息
e. 尝试删除缓存操作,发现删除失败
f. 将这些信息发送至消息队列
g. 重新从消息队列中获得该数据,重试操作。

3. 业务T+1,更新的数据T+1天生效
如修改已经在使用的聚合指标:上线各个系统均在使用的不允许修改,要审核下线才能修改,预发布态仅本组使用可后台修改,明细表新增一条更新后的记录,晚上11点半点定时刷新缓存

9. 缓存相关名词

1. 缓存预热:

a. 缓存预热就是系统启动前,提前将相关的缓存数据直接加载到缓存系统。避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据
b. 统计访问频度较高的热点数据,使用脚本程序固定触发数据预热过程

2. 缓存击穿:

a. 缓存击穿问题也叫热点Key问题,就是一个被高并发访问并且缓存重建业务较复杂的key突然失效了,无数的请求访问会在瞬间给数据库带来巨大的冲击
b. 解决方案:
用互斥锁:利用 redis 的 setnx 方法来表示获取锁,方法含义是redis中如果没有这个key,则插入成功返回1,则成功获取锁,然后持有锁的线程查询数据写入缓存

3. 缓存雪崩

a. 缓存雪崩是指在同一时段大量的缓存key同时失效或者Redis服务宕机,导致大量请求到达数据库,带来巨大压力。
b. 解决方案:
1.给不同的Key的TTL添加随机值
2.利用Redis集群提高服务的可用性

4. 缓存穿透:

a. 缓存穿透是指客户端请求的数据在缓存和数据库中都不存在,这样缓存永远不会生效,这些请求都会打到数据库
b. 解决方案:
1. 缓存不存在且数据库也不存在,则将空值写入缓存
2. 布隆过滤:布隆过滤器其实采用的是哈希思想来解决缓存穿透,通过一个庞大的二进制数组,走哈希思想去判断当前这个要查询的这个数据是否存在,如果布隆过滤器判断存在,则放行,这个请求会去访问redis,哪怕此时redis中的数据过期了,但是数据库中一定存在这个数据,在数据库中查询出来这个数据后,再将其放入到redis中,假设布隆过滤器判断这个数据不存在,则直接返回这种方式优点在于节约内存空间,存在误判,误判原因在于:布隆过滤器走的是哈希思想,只要哈希思想,就可能存在哈希冲突

  1.布隆过滤器(BloomFilter)本质上是由长度为m的位向量或位列表(仅包含0或1位值的列表)组成,最初所有的值均设置为0
  2.它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都比一般的算法要好的多,缺点是有一定的误识别率和删除困难
  3.当我们搜索一个值的时候,若该值经过 K 个哈希函数运算后的任何一个索引位为 ”0“,那么该值肯定不在集合中。但如果所有哈希索引值均为”1“,则只能说该搜索的值可能存在集合中(哈希碰撞)。
  4.利用布隆过滤器我们可以预先把数据查询的主键,比如用户ID或文章ID缓存到过滤器中。当根据 ID 进行数据查询的时候,我们先判断该 ID 是否存在,若存在的话,则进行下一步处理。若不存在的话,直接返回,这样就不会触发后续的数据库查询。需要注意的是缓存穿透不能完全解决,我们只能将其控制在一个可以容忍的范围内
  5.redis的bitmap过滤器:
	初始过滤器:BF.RESERVE {key} {error_rate} {capacity}
	添加元素到过滤器:BF.ADD {key} {item}
	判断元素是否存在:BF.EXISTS {key} {item}
	redisson过滤器实战:
	RBloomFilter<T> bloomFilter = redissonClient.getBloomFilter(filterName);
    bloomFilter.tryInit(expectedInsertions, falseProbability);
    bloomFilter.add(i);
    bloomFilter.contains(i)
  6.Google 著名的 Guava 库所提供布隆过滤器(Bloom Filter)的基本使用
  BloomFilter<CharSequence> bf = BloomFilter.create(Funnels.stringFunnel(Charsets.UTF_8), total, 0.0002);
  boolean b = bf.mightContain("");

10. redisson分布式锁

1. 如何获取锁:
redissionClient = redission.create(config);
lock = redissionClient.getLock("name");
lock.lock();//如果不能立即获得锁,代码将阻塞,直到锁可用为止
boolean isLocked = lock.tryLock();//如果成功获得锁,则在持有锁时执行if中的代码。如果无法获取锁,则执行else块中的代码。tryLock()方法可以设置等待时间,如果在等待时间内没有获取到锁,则返回false
2. watchDog(看门狗)

当lock和trylock方法中不传入realseTime或者传-1时启动看门狗机制,watchdog在当前节点存活时默认每10s给分布式锁的key续期30s【可通过修改lockWatchdogTimeout,watchdog 会每lockWatchdogTimeout/3时间,去延时】,watchdog机制启动,且代码中没有释放锁操作时,watch dog 会不断的给锁续期;

3. 释放锁:lock.unlock()
	注:
	1.如果程序释放锁操作时因为异常没有被执行,那么锁无法被释放,所以释放锁操作一定要放到 finally {} 中;
	2.如果释放锁操作本身异常了,watchdog还会不停的续期吗?
	不会,因为无论释放锁操作是否成功,都会执行释放锁的回调函数,会将EXPIRATION_RENEWAL_MAP中的目标 ExpirationEntry 对象移除,watch dog 通过判断后就不会继续给锁续期了。
4. 加锁流程

在这里插入图片描述

11. Redis高可用和集群

1. 高可用有两个含义:一是数据尽量不丢失,二是保证服务尽可能可用。 AOF 和 RDB 数据持久化保证了数据尽量不丢失,那么多节点来保证尽可能提供服务
2. Redis 实现高可用有三种部署模式:主从模式,哨兵模式,集群模式
3. 对于高可用,通过 Redis 主从架构 + 哨兵可以实现高可用,一主多从,任何一个实例宕机,可以进行主备切换。一般来说,很多项目其实就足够了,单主用来写入数据,单机几万QPS,多从用来查询数据,多个从实例可以提供每秒 10w 的 QPS。sentinel通常翻译成哨兵,这里它就是用来监控主从节点的健康情况。客户端连接redis主从的时候,先连接 sentinel,sentinel会告诉客户端主redis的地址是多少,然后客户端连接上redis并进行后续的操作。当主节点挂掉的时候,客户端就得不到连接了因而报错了,无需人工手动修改,客户端重新向sentinel询问主master的地址,然后客户端得到了[新选举出来的主redis],
4. 最小的redis集群,需要至少3个主节点,既然有3个主节点,而一个主节点搭配至少一个从节点,因此至少得6台redis
5. 哨兵模式基于主从模式,实现读写分离,它还可以自动切换,系统可用性更高。但是它每个节点存储的数据是一样的,浪费内存。因此,在Redis3.0后Cluster集群应运而生,它实现了Redis的分布式存储。对数据进行分片,也就是说每台Redis节点上存储不同的内容,来解决在线扩容的问题。
6. 哨兵的工作原理是每个哨兵会以每秒钟 1 次的频率,向已知的主服务器和从服务器,发送一个 PING 命令。如果最后一次有效回复PING命令的时间,超过了配置的最大下线时间(Down-After-Milliseconds)时,默认是 30s,那么这个实例会被哨兵标记为主观下线。如果一个主服务器被标记为主观下线,那么正在监视这个主服务器的所有哨兵节点,要以每秒1次的频率确认主服务器是否进入了主观下线的状态。如果有足够数量(quorum 配置值)的哨兵证实该主服务器为主观下线,那么这个主服务器被标记为客观下线。此时所有的哨兵会按照规则(协商)自动选出新的主节点服务器,并自动由主哨兵完成主服务器的自动切换功能,整个过程都是无须人工干预的。

12. Redis如何持久化

1. RDB快照(默认开启):

a. RDB 就是 Redis DataBase 的缩写,中文名为快照/内存快照,RDB持久化是把当前进程全量数据生成快照保存到磁盘上的过程,由于是某一时刻的快照,那么快照中的值要早于或者等于内存中的值,RDB文件的内容是经过压缩后的二进制数据,rdb默认保存dump.rdb 文件
b. redis.conf中配置RDB:save 【如果seconds秒内有changes条Key信息发生变化,则进行快照】默认配置主进程创建bgsave子进程,执行 bgsave 命令的时候,会通过 fork() 创建子进程【若手动save命令则主进程进行RDB操作】。
c. fork本身这个操作执行时,内核需要给子进程拷贝主线程的页表。如果主线程的内存大,页表也相应大,拷贝页表耗时长,会阻塞主线程。这个操作在实际执行过程中,是子进程复制了主线程的页表,所以通过页表映射,能读到主线程的原始数据,而当有新数据写入或数据修改时,
d. OS写时复制(COW copy on write), bgsave保存RDB时,如果有写请求,此时的阻塞会来自于主线程申请新内存空间以及复制原数据,主线程会把新数据或修改后的数据写到一个新的物理内存地址上,os修改主线程自己的页表映射。所以,子进程读到的类似于原始数据的一个副本,而主线程也可以正常进行修改。
e. 子进程完成对临时RDB文件的写入时,Redis用新RDB文件替换原来的 RDB 文件,并删除旧的 RDB 文件。

2. AOF日志

a. Redis的AOF(Append Only File) 将所有对数据库进行过写入的命令(及其参数)记录到AOF文件,注意只会记录写操作命令,读操作命令是不会被记录的,因为没意义。AOF持久化默认是关闭的,可以通过配置:appendonly yes 开启。
b. Redis是先执行写操作命令后,才将该命令记录到AOF日志里的,这么做其实有两个好处:

   第一个好处,避免额外的检查开销.只有在该命令执行成功后,才将命令记录到AOF日志里,这样就不用额外的检查开销,保证记录在 AOF日志里的命令都是可执行并且正确的。 			
   第二个好处,不会阻塞当前写操作命令的执行,因为当写操作命令执行成功后,才会将命令记录到 AOF日志。

c. Redis 执行完写操作命令后,会将命令追加到 server.aof_buf 缓冲区;然后通过write()系统调用,将aof_buf缓冲区的数据写入到AOF文件,此时数据并没有写入到硬盘,而是拷贝到了内核缓冲区page cache,等待内核将数据写入硬盘;具体内核缓冲区的数据什么时候写入到硬盘,由内核决定。

Redis 提供了 3 种写回硬盘的策略Redis 提供了三种将 AOF 日志写回硬盘的策略,分别是 Always(总是)、Everysec(每秒)和No(不由redis控制由操作系统控制),这三种策略在可靠性上是从高到低,而在性能上则是从低到高。
	Always 策略就是每次写入 AOF 文件数据后,就执行 fsync() 函数;
	Everysec 策略就会创建一个异步任务来执行 fsync() 函数;
	No 策略就是永不执行 fsync() 函数;

d. 为了避免日志文件过大, Redis 提供了 AOF 重写机制,它会直接扫描数据中所有的键值对数据,然后为每一个键值对生成一条写操作命令,接着将该命令写入到新的AOF文件,重写完成后,就替换掉现有的AOF日志。重写的过程是由后台子进程完成的,这样可以使得主进程可以继续正常处理命令。
e. 在bgrewriteaof子进程执行AOF重写期间,主进程需要执行以下三个工作:
执行客户端发来的命令;
将执行后的写命令追加到 「AOF 缓冲区」;
将执行后的写命令追加到 「AOF 重写缓冲区」

 1. 当子进程完成 AOF 重写工作(扫描数据库中所有数据,逐一把内存数据的键值对转换成一条命令,再将命令记录到重写日志)后,会向主进程发送一条信号,信号是进程间通讯的一种方式,且是异步的。
 2. 主进程收到该信号后,会调用一个信号处理函数,该函数主要做以下工作: 
 	a. 将 AOF 重写缓冲区中的所有内容追加到新的 AOF 的文件中,使得新旧两个 AOF 文件所保存的数据库状态一致; 			
 	b. 新的 AOF 的文件进行改名,覆盖现有的 AOF 文件。
3. 两种方式优缺点

a. RDB是压缩二进制文件不可读,AOF纯追加存储可读的redis命令,对于相同的数据集,AOF 文件的大小一般会比 RDB 文件大;
b. RDB 快照就是记录某一个瞬间的内存数据,记录的是实际数据,而AOF文件记录的是命令操作的日志,而不是实际的数据;
c. AOF 比 RDB可靠,AOF可设置不同的 fsync 策略,默认是 everysec,在这种配置下,redis仍然可以保持良好的性能,并且就算发生故障停机,也最多只会丢失一秒钟的数据;
d. 在Redis恢复数据时, RDB恢复数据的效率会比AOF高些,因为直接将RDB文件读入内存就可以,不需要像AOF那样还需要额外逐一执行操作命令的步骤才能恢复数据。

4. 混合持久化

a. redis的4.0 版本的混合持久化功能默认关闭,我们可以通过aof-use-rdb-preamble 配置参数控制该功能的启用。5.0 版本之后 默认开启。
b. 在开启混合持久化的情况下,AOF 重写时会把 Redis 的持久化数据,以 RDB 的格式写入到 AOF 文件的开头,之后的数据再以 AOF 的格式化追加的文件的末尾。这里的AOF日志不再是全量的日志,而是自持久化开始到持久化结束的这段时间发生的增量 AOF 日志。
c. 这样做的好处是可以结合RDB和AOF的优点,快速加载同时避免丢失过多的数据,缺点是AOF 文件中添加了 RDB 格式的内容,使得 AOF 文件的可读性变得很差(appendonly.aof)。
d. 这样一来,快照不用很频繁地执行,这就避免了频繁fork对主线程的影响。而且,AOF 日志也只用记录两次快照间的操作,也就是说,不需要记录所有操作了,因此,就不会出现文件过大的情况了,也可以避免重写开销。两次快照中间时刻的修改,用AOF日志记录,等到第二次做全量快照时,就可以清空AOF日志,因为此时的修改都已经记录到快照中了,恢复时就不再用日志了.
e. Redis 通过关键字 REDIS 判断判断 AOF文件的开头是否是RDB格式的,RDB 文件的开头一定是 REDIS 关键字开头的,AOF 格式的开头是 *
f. 混合持久化的数据恢复和 AOF 持久化过程是一样的,只需要把appendonly.aof 放到 Redis的根目录,在Redis启动时,只要开启了AOF持久化,Redis就会自动加载并恢复数据。

5. Redis大key的影响

a. redis单线程处理会造成客户端超时阻塞、引发网络阻塞
b. AOF 重写机制和 RDB 快照(bgsave命令)的过程,主进程都会分别通过fork()函数创建一个子进程来处理任务。会有两个阶段会导致阻塞父进程(主线程):

 创建子进程的途中,由于要复制父进程的页表等数据结构,阻塞的时间跟页表的大小有关,页表越大,阻塞的时间也越长;
 
 创建完子进程后,如果父进程修改了共享数据中的大Key,就会发生写时复制,这期间会拷贝物理内存,由于大 Key占用的物理内存会很大,那么在复制物理内存这一过程,就会比较耗时,所以有可能会阻塞父进程。
注:

Redis 的服务器进程就是一个事件循环,最重要的有两个事件:文件事件和时间事件。Redis在服务器初始化后,会无限循环,处理产生的文件事件和时间事件。文件事件常见的有:接受连接(accept)、读取(read)、写入(write)、关闭连接(close)等。时间事件中常见的就是serverCron,redis核心流程中通常也只有这个时间事件。serverCron 默认配置下每100ms会被触发一次,在该时间事件中,会执行很多操作:清理过期键、AOF 后台重写、RDB 的 save point 的检查、将 aof_buf 内容写到磁盘上(flushAppendOnlyFile 函数)等等。

13. redis过期删除策略和内存淘汰策略

1. 过期删除策略

Redis 选择「惰性删除+定期删除」这两种策略配和使用,以求在合理使用 CPU 时间和避免内存浪费之间取得平衡
惰性策略: Redis在访问或者修改key之前,都会调用expireIfNeeded函数对其进行检查,检查 key 是否过期:

如果过期,则删除该 key,至于选择异步删除,还是选择同步删除,根据 lazyfree_lazy_expire 参数配置决定(Redis 4.0版本开始提供参数),然后返回 null 客户端;
如果没有过期,不做任何处理,然后返回正常的键值对给客户端;

定期策略: 在 Redis 中,默认每秒进行 10 次过期检查一次数据库,此配置可通过 Redis 的配置文件 redis.conf 进行配置,配置键为 hz,默认值是 hz 10,每次检查数据库并不是遍历过期字典【哈希表,O(1)快速查找】中的所有key,而是从中随机抽取一定数量的key进行过期检查,数据库每轮抽查时,会随机选择 20 个 key 判断是否过期

定期删除流程:
 	a.从过期字典中随机抽取 20 个 key;
	b.检查这 20 个 key 是否过期,并删除已过期的 key;
	c.如果本轮检查的已过期 key 的数量,超过 5 个(20/4),也就是过期数大于 25%,且定期删除循环流程的时间上限不超过25ms则继续重复步骤a;如果已过期的 key 比例小于 25%,则停止继续删除过期 key,然后等待下一轮再检查。
2. redis8种内存淘汰策略

a. noeviction(Redis3.0之后,默认的内存淘汰策略) :它表示当运行内存超过最大设置内存时,不淘汰任何数据,而是不再提供服务,直接返回错误。
b. volatile-random:随机淘汰设置了过期时间的任意键值;
c. volatile-ttl:优先淘汰更早过期的键值。
d. volatile-lru(Redis3.0之前,默认的内存淘汰策略):淘汰所有设置了过期时间的键值中,最久未使用的键值【实现方式是在Redis的对象结构体中添加一个额外的字段,用于记录此数据的最后一次访问时间】;
e. volatile-lfu(Redis4.0后新增的内存淘汰策略):淘汰所有设置了过期时间的键值中,使用频次最少的键值【LFU算法相比于LRU算法的实现,多记录了「数据的访问频次」的信息】;
f. allkeys-random:随机淘汰任意键值;
g. allkeys-lru:淘汰整个键值中最久未使用的键值;
h. allkeys-lfu(Redis4.0后新增的内存淘汰策略):淘汰整个键值中最少使用的键值。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值