Redis

Redis

  • nosql讲解

  • 阿里巴巴架构演进

  • nosql数据模型

  • nosql四大分类

  • CAP

  • BASE

  • Redis入门

  • Redis安装(Windows Linux服务器)

  • 五大基本类型

    String

    List

    Set

    Hash

    Zset

  • 三种特殊数据类型

    geo

    hyperloglog

    bitmap

  • Redis配置详解

  • Redis持久化

​ RDB

​ AOF

  • Redis事务操作
  • Redis实现订阅发布
  • Redis主从复制
  • Redis哨兵(现在公司中的集群都用哨兵模式)
  • 缓存穿透及解决方案
  • 缓存击穿及解决方案
  • 缓存雪崩及解决方案
  • 基础API之Jedis详解
  • SpringBoot集成Redis操作
  • Redis的实践分析

NoSql概述

1、单机MySQL的年代
在这里插入图片描述

2、思考一下整个网站的瓶颈是什么?

  • 数据量太大,一个机器内存放不下
  • 数据索引(B+Tree),一个机器内存放不下
  • 访问量(读写混合),一个服务器承受不了

3、Memcached(缓存) + MySQL +垂直拆分(读写分离)

发展过程:优化数据结构和索引----->文件缓存(IO)------>Memcahed
在这里插入图片描述

分库分表 + 水平拆分 + MySQL集群

本质:数据库(读、写)

早年MyISAM:表锁,十分影响效率,高并发下会出现严重的锁问题

转战Innodb:行锁

后来用分库分表来解决写的压力
在这里插入图片描述

4、如今最近的年代

MySQL用来存储比较大的文件,博客、图片!数据库表很大,效率低

目前一个基本互联网项目
在这里插入图片描述

什么是NoSQL

NoSQL:nosql = Not Only SQL(不仅仅是SQL)

关系型数据库:表格、行、列

很多的数据类型用户的个人信息,社交网络,地理位置。这些数据类型的存储不需要一个固定的格式,不需要很多的操作就可以横向扩展,Map使用键值对来控制

NoSQL特点

解耦!

1、方便扩展(数据之间没有关系,很好扩展)

2、大数据量高性能(Redis一秒写8万次,读取11万次,Nosql的缓存记录级,是一种细粒度的缓存,性能比较高)

3、数据类型是多样性(不需要事先设计数据库,随取随用)

4、传统RDBMS和NoSQL

传统RDBMS

-结构化组织

-SQL

-数据和关系都存在单独的表中

-严格的一致性

-基础事务

-.....................
NoSQL

-不仅仅是数据

-没有固定的查询语言

-键值对存储,列存储,文档存储,图形数据库(社交关系)

-最终一致性

-CAP定理和BASE

-高性能、高可用、高可扩展性

-.........................................

NoSQL的四大分类

KV键值对:

新浪:Redis

美团:Redis + Tair

阿里、百度:Redis + memecahe

文档型数据库(bson格式和json一样):

  • MongoDB(一般必须掌握)

    MongoDB是一个基于分布式文件存储的数据库,C++编写,主要用来处理大量文档

    MongoDB是一个介于关系型数据库和非关系型数据库中中间的产品,MongoDB是非关系型数据库中功能最丰富、最像关系型数据库的。

列存储数据库

  • HBase
  • 分布式文件系统

图关系数据库

Redis入门

概述

Redis是什么?

Redis是现在最受欢迎的NoSQL数据库之一,Redis是一个使用ANSI C编写的开源、包含多种数据结构、支持网络、基于内存、可选持久性的键值对存储数据库。

Redis能干嘛?

1、内存存储、持久化,内存中是断电即失。(rdb、aof)

2、效率高,可以用于高速缓存

3、发布订阅系统

4、地图信息分析

5、计时器、计数器

6…

特性

1、持久化

2、集群

3、事务

性能测试

Redis-benchmark是一个压力测试工具

官方自带性能测试工具

Redis-benchmark命令参数
在这里插入图片描述

测试例子:

测试:100个并发连接,100000请求

redis-benchmark -h localhost -p 6379 -c 100 -n 100000
基础知识

Redis有16个数据库

默认使用第0个数据库

使用select切换数据库

keys * 查看当前数据库所有的key

flushdb 清空当前数据库

flushall清除全部的数据库的内容

Redis为什么是单线程的?

Redis是基于内存操作,CPU不是Redis性能的瓶颈,Redis的瓶颈是根据机器的内存和网络带宽,单线程可以实现所以就用单线程。

Redis是C语言写的,官方提供的数据为100000+的QPS,完全不比同样使用key-vale的Memecache差

为什么Redis使用单线程还那么快?

误区1:高性能的服务器一定是多线程?

误区2:多线程(CPU上下文会切换)一定比单线程效率高

核心:Redis是将所有的数据全部放在内存中,所以使用单线程去操作效率就是最高的,多线程(CPU上下文会切换:耗时操作),对于内存系统来说,如果没有上下文切换效率就是最高的,多次读写都是在CPU上的,在内存情况下这就是最佳的方案!

五大数据类型

官方文档

Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings)散列(hashes)列表(lists)集合(sets)有序集合(sorted sets) 与范围查询, bitmapshyperloglogs地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication)LUA脚本(Lua scripting)LRU驱动事件(LRU eviction)事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。

Redis-Key
keys * #查看当前数据库的可以
set nema sst #设置name值
exists name #判断当前key是否存在
move name #移除当前的key
expire name 10 #设置当前key的过期时间,单位是秒
ttl name  #查看当前key的剩余时间
type name #查看当前key的数据类型
String(字符串)
##############################################################################
set key1 v1 #设置值
get key1 #获取值
keys * #获取所有的key
exists #判断某一个key是否存在
append key1 #追加字符串,如果当前key不存在,就相当于set key
strlen key1 #获取字符串的长度
############################################################################
# i++
#步长 i+=
set views 0 #初始浏览强为0
get views #查看浏览强
incr views #自增1
decr views #自减1
incrby views #可以设置步长,指定增量
decrby views #设置步长指定减量
#############################################################################
字符串范围值range
set key1 "hello,kuangshen" #设置key1的值
getrange key1 0 3 #截取字符串[0,3]
getrange key1 0 -1 #获取全部字符串和get key是一样的
setrange key2 1 xx #替换指定位置的字符串
#############################################################################
# setex (set with expire) #设置过期时间
# setnx (set if not exist) #不存在再设置(在分布式锁中会常常使用)
setex key3 30 "redis" #设置key3的值为hello,30秒过期
setnx mykey "redis" #如果mykey不存在,创建mykey
setnx mykey "MongoDB" #如果mykey存在创建失败
#############################################################################
#mset
#mget 
mset k1 v1 k2 v2 k3 v3 #同时设置多个值
mget k1 k2 k3 #同时获取多个值
msetnx k1 v1 k2 v2 k3 v3 #msetnx是一个原子性的操作,要么一起成功,要么一起失败

#对象
set user:1{name:zahngsan,age:3} #设置一个user:1对象值为json字符串对象
这里的key是一个巧妙的设计:user:{id}:{filed}

getset #先get在set
getset db redis #如果不存在值,则返回nil,如果存在值,回去原来的值,又赋新的值

数据结构相同

String类似的使用场景:value除了是我们的字符串还可以是我们的数字

  • 计数器
  • 统计多单位数量
  • 粉丝数
  • 对缓存存储
List(列表)

基本数据类型,列表

所有的list命令都是以l开头,Redis不区分大小写

#############################################################################
lpush list one #将一个或多个值,插入列表的头部(左)
lrange list three #获取list中的值
lrange list 0 1 #通过区间获取具体的值
rpush list righr #将一个或多个值插入到列表尾部(右)
#############################################################################
lpop 
rpop
lpop list #移除list的第一个元素
rpop list #移除list的最后一个元素
#############################################################################
lindex
lindex list 1 #通过下标获取list中的某一个值
#############################################################################
Llen
Llen list #返回列表的长度
#############################################################################
移除指定的值
Lrem
Lrem list 1 one #移除list集合中指定的个数的value,精确匹配
#############################################################################
trim修剪,list截断
ltrim mylist 1 2 #通过下标截取指定的长度,这个list已经被改变,截断只剩下截取的元素
#############################################################################
rpop lpush #移除列表的最后一个元素,将他移到新的列表中
rpoplpush mylist myotherlist #移除列表的最后一个元素,将他移到新的列表中
lrange mylist 0 -1 #查看原来的列表
lrange myotherlist 0 -1 #查看目标列表中,确实存在该值
#############################################################################
lset #将列表中指定下标的值替换为另一个值,更新操作
exists list #判断这个列表是否存在
lset list 0 item #如果不存在就会报错,如果存在就更新值
#############################################################################
linsert #将某个具体的value插入列表的前面或者后面
linsert mylist before "world" "other" #在world前面插入值
linsert mylist after "world" "other" #在world后面插入值

小结

  • 实际上是一个链表,before node after,left,right都可以插入值
  • 如果key不存在,创建新的列表
  • 如果key存在,新增内容
  • 如果移除所有值,空链表,也代表存在
  • 在左边插入或则改动值,效率最高,中间元素,相对来说效率会低一点,消息排队,消息队列,栈
Set集合

set中的值是不能重复的

#############################################################################
sadd myset "hello" #set集合中添加元素
smembers myset #查看指定set的所有值
sismember myset world #判断某个元素是否在set集合中
#############################################################################
acard myset #获取set集合中的内容元素个数
#############################################################################
srem myset hello #移除set中的指定元素
#############################################################################
set 无序不重复集合,随机抽取
srandmember myset #随机抽取一个元素
srandmember myset 2 #随机抽取指定个数的元素
#############################################################################
删除指定的key,随机删除key
spop myset #随机删除一些set集合中的元素
#############################################################################
将一个指定的值移动到另一个set集合中
smove myset myset2 "kuangshen" #将一个指定的值,移动到另一个set集合中
#############################################################################
微博、B站共同关注(并集)
数字集合类:
	-差集
	-交集
	-并集
sdiff key1 key2 #差集
sinter key1 key2 #交集
sunion ke1 key2 #并集
Hash(哈希)

Map集合,key-map,map集合,本质和String没有太大区别

#############################################################################
hset myhash field1 kaungshen #set一个具体key-vlaue
hmset myhash field1 hello field2 woeld #set多个key-vlaue
hmget myhash field1 field2 #获取多个key-vlaue
hgetall myhash #获取全部的数据
hdel myhash field1 #删除hash指定字段
#############################################################################
hlen myhash #获取hash的长度
#############################################################################
#只获得所有的field
#只获得所有的value
hkeys myhash #只获得所有的field
hvals myhash #只获得所有的value
#############################################################################
incr decr
hset myhash field3 #指定增量
hsetnx myhash field4 hello #如果不存在则可以设置,如果存在则不可设置
Zset (有序集合)

在set的基础上,增加一个值,set k1 v1 zset k1 score1 v1

#############################################################################
zadd myset 1 one  #增加一个值
zadd myset 2 two 3 three  #增加多个值
zrange myset 0 -1 #查看集合元素
#############################################################################

Redis三种特殊数据类型

Geospatial地理位置

地图定位坐标计算位置

Hyperloglog

redis Hyperloglog是基数统计算法,例如:统计网站访问数量

Bitmaps

位存储

统计用户信息,活跃,不活跃,登录,未登录,打卡,未打卡,两个状态的都可以使用Bitmaps

Bitmaps位图,数据结构,都是操作二进制位来进行记录,就只有0和1两个状态

事务

Redis事务本质:一组命令的集合,一个事务的所有命令都会被序列化,在事务执行过程中,会按照顺序执行

Redis事务没有隔离级别的概念

Redis单挑命令是保证原则性的,但是事务不保证原则性

所有的命令在事务中,并没有直接被执行,只有发起执行命令的时候才会被执行

关系型数据库的事务,原子性:要么同时成功,要么同时失败!

Redis的事务:

  • ​ 开启事务(multi)
  • ​ 命令入队(…)
  • ​ 执行事务(exec)

正常事务

mukti #开启事务
set k1 v1 
set k2 v2
get k2
set k3 v3
exec #执行事务

放弃事务

mukti #开启事务
set k1 v1 
set k2 v2
set k3 v3
discard #取消事务

编译型异常(代码有问题,命令有错),事务中所有的命令都不会被执行

运行时异常(1/0),如果事务队列中存在语法性错误,执行的时候其他命令可以正常执行,错误命令抛出异常

监控!watch(面试常问)

  • 悲观锁:认为什么时候都会被出问题,无论做什么都会加锁

  • 乐观锁:认为什么时候都不会出问题,所以不会加锁,更新数据的时候去判断一下,在此期间是否有人修改过这个数据

    ​ 获取version

    ​ 更新的时候比较version

Redis监视测试

set money 100
set out 0
watch money #监视money对象
multi #事务正常结束,数据期间没有发生变动,这时候事务正常执行成功
decrby money 20
incrby out 20
exec

测试多线程修改值,使用watch可以当做Redis的乐观锁操作

Jedis

我们使用Java来操作Redis

什么是Jedis?

它是Redis官网推荐的java连接开发工具,使用java操作Redis的中间件,如果使用java操作Redis,要Jedis十分熟悉

常用API
  • String
  • List
  • Set
  • Hash
  • Zset

所有的API命令就是上面的命令

事务
在这里插入图片描述
在这里插入图片描述

SpringBoot整合

SpringBoot操作数据层:springboot-date jpa jdbc mongodb redis

SpringData也是和SpringBoot齐名的项目

在SpringBoot2.x之后,原来使用的jedis被替换为了lettuce

jedis:底层采用直连,多个线程操作的话,是不安全的,如果想要避免不安全的,使用jedis pool连接池,更像BIO模式

lettuce:采用netty,实例可在多线程中进行共享,不存在线程不安全的情况,可以减少线程数据,更像NIO模式

测试

1、导入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2、配置连接

#配置连接
spring.redis.host=127.0.0.1
spring.redis.port=6379

3、测试

Redis.conf详解

启动的时候通过配置文件来启动

redis.windows.conf

Redis持久化

面试持久化是重点!

Redis是内存数据库,如果不将内存中的数据状态保存到磁盘,那么一旦服务器进程退出,服务器中的数据库状态也会消失,所以Redis提供了持久化功能。

RDB

在主从复制中,rdb就是备用了!从机上面

在指定的时间间隔内将内存中的数据集快照写入磁盘,回复时将快照文件直接读到内存。

Redis会单独创建一个子进程来进行持久化,会先将数据写到一个临时文件中,待持久化过程都结束了,在用这个临时文件替换上传持久化的文件,整个过程主进程不进行任何IO操作,性能极高,在大规模恢复数据且对数据的完整性不是很敏感,那RDB方式比AOF方式更加高效,RDB的缺点就是最后一次持久化数据可能丢失。

rdb保存文件是dump.rdb都是在配置文件中快照中进行配置

触发机制

1、save的规则满足的情况下,会自动触发rdb规则

2、执行flushall命令,也会触发我们的rdb规则

3、退出redis,也会产生rdb文件

备份就会自动生成dump.rdb

如果恢复rdb文件

1、只需要将rdb文件放在我们redis启动目录就可以,redis启动的时候会自动检查dump.rdb恢复其中的数据

2、查看需要存放的位置

  • 优点

1、适合大规模恢复数据

2、对数据完整性要求不高

  • 缺点

1、需要一定时间间隔进行操作,如果redis意外宕机,最后一次数据就没有

2、fork进程的时候,会占用一定的内存空间

AOF

将所有命令记录下来,history,恢复的时候就把这个文件全部在执行

以日志的形式记录每个操作,将Redis执行过程的所有指令记录下来(读操作不记录),只允许追加文件单不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件内容将写指令从前到后执行一次已完成数据的恢复工作

AOF保存的是appendonly文件

append

默认不开启,需要手动配置,我们只需要将appendonly改为yes就开启AOF

  • 优点:

1、每次修改都是同步的,文件完整性会更好

2、每秒同步一次,可能会丢失一秒的数据

3、从不同步,效率最高的

  • 缺点:

1、相对数据文件来说,AOF远大于rdb,修复的速度也比rdb慢

2、aof运行效率也要比rdb慢,所以redis默认配置就是使用rdb

Redis发布订阅

Redis发布订阅(pub/sub)是一种消息通信模式,发送者(pub)发送消息,订阅者(sub)接收消息

Redis客户端可以订阅任意数量的频道

发布订阅消息图:

第一个:消息发送者,第二个:频道,第三个:消息订阅者
在这里插入图片描述
在这里插入图片描述

原理

Redis是使用C实现的,通过分析Redis源码的pubsub.c文件,了解发布和订阅机制的底层实现,加深对Redis的理解。

Redis通过PUBLISH、SUBSCRIBE和PSUBSCRIBE等命令实现发布和订阅

通过SUBSCRIBE命令订阅某频道后,redis-server里维护了一个字典,字典的键就是一个个频道,而字典则是一个链表,链表中保存了所有订阅这个channel的客户端,SUBSCRIBE命令的关键,就是将客户端添加到给定channel的订阅链表中。
在这里插入图片描述

通过publish命令向订阅者发送消息,redis-server会使用给定的频道作为键,在他所维护的channel字典中查找记录了订阅这个频道的所有客户端的链表,遍历这个链表,将消息发送给订阅者。

pub/sub从字面上理解就是发布与订阅,在redis中,可以设定某一个key值进行消息发布及消息订阅,当一个key值进行消息发布,所有订阅的客户都会收到消息,该功能明显用法就是用作实时消息系统,比如普通的即时聊天,群聊等功能。

使用场景:

1、实时消息系统

2、实时聊天(频道当做聊天室,将信息返回给所有人)

3、订阅、关注系统

Redis主从复制

概念

主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器,前者称为主节点(master/leader),后者称为从节点(slave/follower);数据的复制是单向的,只能从主节点到从节点,master以写为主,slave以读为主。
在这里插入图片描述

在这里插入图片描述

主从复制,读写分离,80%的情况是进行读操作,减缓服务器压力,架构中经常使用一主二从。

在真实项目中主从复制必须使用,并且不可能单机使用Redis。

环境配置

只配置从库,不配置主库
在这里插入图片描述

复制3个配置文件,然后修改对应的信息

1、端口

2、pid名字

3、log文件名字

4、dump.rdb名字

修改完之后,启动3个Redis服务器,可以通过进程信息查看
在这里插入图片描述

一主二从

默认情况下,每台Redis服务器都是主节点。一般情况下只配置从机。

一主(6379)二从(6380、6381)
在这里插入图片描述
在这里插入图片描述

真实的从主配置是在配置文件中配置,这种是永久的。

细节

主机可以写,从机不能写只能读,主机中的所有信息和数据,都会被从机保存

主机写:
在这里插入图片描述

从机读取内容:
在这里插入图片描述

测试

主机断开连接,从机依旧连接主机,但是没有写操作,但是主机恢复从机依旧可以读取主机内容。

如果是使用命令行来配置的主从,这个时候如果重启了从机,它就会变会主机。只要变回为从机,立马就会从主机中获取值。

复制原理
在这里插入图片描述

层层链路

上一个M链接下一个S
在这里插入图片描述

如果主机断开连接,我们可以使用SLAVEOF no one让自己变成主机,其他的节点就可以手动连接到最新的这个主节点(手动)。如果主机修复,只能重新配置。

哨兵模式

自动选择主机

概述

主从切换的方式:当主机服务器宕机后,需要手动把一台从服务器切换为主服务器,需要人工干预,费时费力还会造成服务器一段时间不可用状态,这时候优先考虑哨兵模式,Redis从2.8开始正式提供了哨兵架构来解决这个问题。

哨兵模式能够自动监测主机是否故障,如果故障了根据投票数自动将从机转换为主机。

哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,最为进程,他会独立运行,其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。
在这里插入图片描述

哨兵的两个作用:

  • 通过发送命令,让Redis服务器返回运行状态,包括主服务器和从服务器。
  • 当哨兵监测到master宕机,会自动将slaver切换成master,然后将通过发布订阅通知其他的服务器,修改配置文件,让他们切换为主机。

哨兵进程对Redis进行监控,可能会出现问题,可以使用多个哨兵进行监控,各个哨兵之间还会进行监控,形成多哨兵模式。
在这里插入图片描述

假设主服务器宕机,哨兵1检查到结果,系统不会马上进行failover过程,仅是哨兵1主观认为主服务器不可用,这个现象成为主观下线,当后面哨兵也检查到主服务器不可用,并达到一定数量值,哨兵之间在进行投票,投票结果又一个哨兵发起,进行failover操作,切换成功,通过发布订阅模式,让各个哨兵把自己监控的从服务器实现换主机,这个过程称为客观下线。

如果master节点断开后,就会从从机中随机中选择一个服务器(这里面有一个投票算法)

如果主机恢复了,只能归并到新的主机下做从机,这是哨兵模式的规则。

哨兵模式

优点:

1、哨兵集群,基于主从复制模式,所有的主从配置优点他全有

2、主从可以切换,故障可以转移,系统可用性就会更好

3、哨兵模式就是主从模式的升级,手动到自动,更加健壮

缺点:

1、Redis不好在扩容,集群容量达到一定上限,扩容十分麻烦

2、实现哨兵模式的配置十分麻烦,里面就有很多选择

Redis缓存穿透和雪崩

在这里插入图片描述

缓存穿透(查不到导致)
  • 概念

当用户查询一个数据,发现Redis数据库没有,也就是缓存没有命中,然后向持久层数据库查询,发现也没有,于是查询失败,当用户很多的时候,缓存没有命中(秒杀),于是都去请求持久层数据库,这会给持久层造成很大的压力,这是就相当于缓存穿透。

  • 解决方案

1、布隆过滤器

它是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先进性校验,不符合则丢弃,从而避免对底层存储系统的压力。
在这里插入图片描述

2、缓存空对象

当存储层不命中后,即使返回的空对象也将其缓存起来,同时会设置一个过期时间,之后在访问这个数据将会从缓存中获取,保护了后端的数据源。
在这里插入图片描述

这个方法存在两个问题:

A、如果空值能够被缓存起来,这就意味着缓存需要更多的空间存储更多的键,因为这当中可能会有很多控制的键;

B、对空值设置过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口不一致,这对于需要保持一致性的业务会有影响。

缓存击穿(查的量太大)
  • 概述

缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中对这一点进行访问,当这个key在失效的瞬间,持续大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开一个洞。

当某个key在过期的瞬间,有大量的请求并发访问,这一数据一般是热点数据,由于缓存过期,会同时访问数据库来查询最新数据,并且回写缓存,会导致数据库瞬间压力过大。

  • 解决方案

1、设置热点数据不过期

从缓存层面来看,没有设置过期时间,所以不会出现热点key过期产生的问题。

2、加互拆锁

分布式锁:使用分布式锁,保证对于每一个key同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限,因此只需要等待即可,这种方式将高并发的压力转移到分布式锁,因此对分布式锁的考验很大。
在这里插入图片描述

缓存雪崩
  • 概念

缓存雪崩,是指在某一个时间段,缓存集中失效,Redis宕机。
在这里插入图片描述

解决方案

1、Redis高可用

Redis有可能会宕机,多增加几台Redis,这样一台挂掉之后还有其他可以用,实质就是搭建集群。

2、限流降级

在缓存失效后,通过加锁或则队列来控制读数据库缓存的线程数量,比如对某一个key只允许一个线程查数据和写缓存,其他线程等待。

3、数据预热

数据预热的含义就是在正式部署之前,先把可能的数据预先访问一遍,这部分可能大量的数据就会事先加载到缓存中,在即将大并发访问手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间尽量均匀。

申明:本文章源于B栈UP博主----->遇见狂神说

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值