《非关系型数据库》~ Redis学习总结

Nosql

什么是Nosql

Nosql(Not only sql) 不仅仅是SQL, 泛指非关系型数据库,NoSQL在当今大数据环境下发展的十分迅速,处理海量数据的效率很高!

Nosql产品的特点

  • 支持海量数据的存储, 高性能
  • 易扩展(数据之间没有关系,很好扩展!)
  • 高可用性
  • 数据类型是多样型的!(不需要事先设计数据库!随取随用!)

大数据时代的3V+3高

3V:主要是描述问题的

  • 海量Volume 多样Variety 实时Velocity

3高:主要是对程序的要求

  • 高并发 高可扩 高性能

Nosql产品的四大类

  1. KV键值对数据库:redis
  2. 文档型数据库:mongoDB
  3. 列存储数据库:HBase
  4. 图型关系数据库:Neo4j,InfoGrid

什么是redis

redis是一个开源的、c语言编写的、可基于内存、可持久化的日志型、Key-Value数据库

redis的特点

  1. 基于内存存储和操作数据
  2. 数据持久化机制(宕机或突然断电时,redis会自动将内存的数据持久化到磁盘)
  3. 发布/订阅系统
  4. 丰富的数据类型

安装redis

1、下载安装包! redis-4.0.14.tar.gz
将安装包放入linux系统下
2、安装gcc的编译环境
yum install -y gcc
// yum源安装,这个命令会自动到互联网上的yum源仓库中下载并安装指定的软件或依赖
成功
3、解压
在这里插入图片描述
4、进入解压后的目录:
执行make
在这里插入图片描述
执行make install
在这里插入图片描述
5、启动redis
①使用redis的默认配置启动: redis-server
②配置redis为后台启动,并指定使用配置文件启动: redis-server redis.conf
需要修改redis.conf中的设置
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
6、连接redis:
①访问本机的redis: redis-cli
②访问指定服务器上的redis : redis-cli -h IP -p port
需要修改redis.conf中的设置
在这里插入图片描述
7、连接测试:
在这里插入图片描述
8、查看redis的进程是否开启/关闭进程
在这里插入图片描述
9、关闭Redis服务
在这里插入图片描述

基础命令

命令说明
keys *遍历展示所有的key
expire key 时间/秒设置指定key的过期时间
ttl key查看指定key剩余过期时间(返回整数 代表剩余过期时间)(返回-2 代表指定的key不存在)(返回-1 代表指定的key存在,但是没有设置过期时间)
persist key取消过期时间设置
del key …手动删除key,可以一次删除多个
flushdb清除当前子库下所有的key
flushall清除所有子库的key
select dbindex切换子库, redis默认提供了16个子库,默认使用的是第0个
dbsize统计所有key的总数

遇到不会的命令,可以在官网查看帮助文档!
在这里插入图片描述

五大数据类型

String(字符串)

命令说明
set设置一个key/value
get根据key获得对应的value
mset一次设置多个key value
mget一次获得多个key的value
getset获得原始key的值,同时设置新值
strlen获得对应key存储value的长度
append为对应key的value追加内容,如果当前key不存在,就相当于set
range(getrange key start end 获取截取的字符串) (getrange key 0 -1 获取全部的字符串) (setrange key start end 替换指定位置开始的字符串)
setex设置一个key存活的有效期(秒)
psetex设置一个key存活的有效期(豪秒)
setnx只有当这个key不存在时等效set操作
msetnx可以同时设置多个key,原子性操作,要么一起成功,要么一起失败
decr进行数值类型的-1操作
decrby根据提供的数据(步长)进行减法操作
incrby根据提供的数据(步长)进行加法操作
Incr进行数值类型的+1操作
Incrbyfloat根据提供的数据加入浮点数

List(列表)

list列表类型:存储的元素有序、有下标、可以重复。
可使用list命令组合一个结构( Lpush Lpop)
也可使用list命令组合一个队列结构(Lpush Rpop)

命令说明
lpush将某个值加入到一个key列表头部
lpushx同lpush,但是必须要保证这个key存在
rpush将某个值加入到一个key列表末尾
rpushx同rpush,但是必须要保证这个key存在
linsert在某一个元素之前,之后插入新元素
lpop返回和移除列表的第一个元素
rpop返回和移除列表的最后一个元素
lrange获取某一个下标区间内的元素 ( lrange list 0 -1 获取全部的值)
llen获取列表元素个数
lset给列表指定下标的值替换值
lindex通过下标获取某一个值
lrem(lrem 目标集合 个数 目标元素) 删除元素
ltrim(ltrim 目标集合 start end ) 保留列表中特定下标区间内的元素
rpoplpush移出列表的最后一个元素,将它移到新的列表中

Set(集合)

set集合类型: 元素无序、无下标、不能重复。

命令说明
sadd为集合添加元素
smembers显示集合中所有元素 无序
scard返回集合中元素的个数
spop随机删除一个元素
smove从一个集合中向另一个集合移动元素
srem从集合中删除一个元素
sismember判断一个集合中是否含有这个元素
srandmember随机返回指定个数的元素
sdiff以第一个为参照数跟第二个对比,减去集合中共有的元素(求差集)
sinter求交集
sunion求和集

Zset(有序集合)

在set的基础上,增加了一个元素分值的概念!

命令说明
zadd添加一个/多个有序集合元素
zcard返回集合的元素
zrange返回一个范围内的元素
zrangebyscore按照分数查找一个范围内的元素
zrank返回排名
zrevrank倒序排名
zcount获取指定区间的元素数量
zscore显示某一个元素的分数
zrem移除某一个元素
zincrby给某个特定元素加分

Hash(哈希)

hash类型: hash类型里面存储的元素是键值对,这个值是map集合!
hash 更适合于对象的存储,String更加适合字符串存储!

命令说明
hget设置一个key/value对
hgetall获得所有的key/value对
hdel删除某一个key/value对
hlen获取哈希表中字段的数量,当 key 不存在时,返回 0
hexists判断一个key是否存在
hkeys获得所有的key
hvals获得所有的value
hmset设置多个key/value
hmget获得多个key的value
hsetnx设置一个不存在的key的值
hincrby为value进行加法运算
hincrbyfloat为value加入浮点值

三种特殊数据类型

Geospatial 地理位置

这个功能可以推算地理位置的信息,两地之间的距离,附近多少距离的人/城市…!
Geospatial一种有六个命令
官方文档

1、getadd
将指定的地理空间位置(纬度、经度、名称)添加到指定的key中,一般会下载城市数据,直接通过java程序一次性导入!

# 有效的经度从-180度到180度。
# 有效的纬度从-85.05112878度到85.05112878度。
192.168.17.151:6379> geoadd china:city 116.40 39.90 beijing
(integer) 1
192.168.17.151:6379> geoadd china:city 121.47 31.23 shanghai
(integer) 1
192.168.17.151:6379> geoadd china:city 106.50 29.53 chongqing 114.05 22.52 shenzhen
(integer) 2
192.168.17.151:6379> geoadd china:city 120.16 30.24 hangzhou
(integer) 1

2、getpos
从key里返回所有给定位置元素的位置(经度和纬度)。

192.168.17.151:6379> geopos china:city beijing
1) 1) "116.39999896287918091"
   2) "39.90000009167092543"
192.168.17.151:6379> GEOPOS china:city shanghai hangzhou
1) 1) "121.47000163793563843"
   2) "31.22999903975783553"
2) 1) "120.1600000262260437"
   2) "30.2400003229490224"

3、getdist
返回两个给定位置之间的距离。

m 表示单位为米。
km 表示单位为千米。
mi 表示单位为英里。
ft 表示单位为英尺。
192.168.17.151:6379> geodist china:city beijing shanghai km
"1067.3788"
192.168.17.151:6379> geodist china:city beijing chongqing mi
"909.7337"

4、georadius
以给定的经纬度为中心, 找出某一半径内的元素.

192.168.17.151:6379> georadius china:city 120 30 1000 km           # 以11030 这个经纬度为中心,寻找方圆1000km内的城市
1) "hangzhou"
2) "shanghai"
192.168.17.151:6379> georadius china:city 120 30 1000 km withcoord     # 显示他人的定位信息
1) 1) "hangzhou"
   2) 1) "120.1600000262260437"
      2) "30.2400003229490224"
2) 1) "shanghai"
   2) 1) "121.47000163793563843"
      2) "31.22999903975783553"
192.168.17.151:6379> georadius china:city 120 30 1000 km withdist       # 显示到中间距离的位置
1) 1) "hangzhou"
   2) "30.8146"
2) 1) "shanghai"
   2) "196.2512"
192.168.17.151:6379> georadius china:city 120 30 1000 km count 1         #筛选出指定的结果数量!
1) "hangzhou"

5、georadiusbymember
找出位于指定元素周围的其他元素!

192.168.17.151:6379> georadiusbymember china:city beijing 1100 km
1) "beijing"
2) "shanghai"
192.168.17.151:6379> georadiusbymember china:city beijing 1500 km
1) "chongqing"
2) "hangzhou"
3) "shanghai"
4) "beijing"

6、geohash
将二维的经纬度转换为一维的字符串,如果两个字符串越接近,那么则距离越近!

192.168.17.151:6379> geohash china:city beijing chongqing
1) "wx4fbxxfke0"
2) "wm5xzrybty0"

GEO 底层的实现原理其实就是 Zset!我们可以使用Zset命令来操作geo!

192.168.17.151:6379> ZRANGE china:city 0 -1   # 查看地图中全部的元素
1) "chongqing"
2) "shengzhen"
3) "hangzhou"
4) "shanghai"
5) "beijing"
192.168.17.151:6379> zrem china:city beijing    # 移除指定元素
(integer) 1

Hyperloglog

Redis Hyperloglog 采取基数统计的算法!
优点:占用的内存是固定,2^64 不同的元素的基数,只需要废 12KB内存!大概有0.81% 错误率!

如果计算网页的 UV【访问量】 (一个人访问一个网站多次,但是还是算作一个人!)
传统的方式, set 保存用户的id,然后就可以统计 set 中的元素数量作为标准判断 !
这个方式如果保存大量的用户id,比较麻烦!且目的是为了计数,而不是保存用户id;

Hyperloglog 方法的测试
192.168.17.151:6379> PFADD mykey1 a b c d e f g h i       # 创建第一组元素 mykey1
(integer) 1
192.168.17.151:6379> PFADD mykey2 i u t k g l r w v       # 创建第二组元素 mykey2
(integer) 1
192.168.17.151:6379> PFCOUNT mykey1		                  # 统计mykey1元素的基数数量
(integer) 9
192.168.17.151:6379> PFCOUNT mykey2             
(integer) 8
192.168.17.151:6379> PFMERGE mykey3 mykey1 mykey2       # 合并两组 mykey1 mykey2 => mykey3 并集
OK
192.168.17.151:6379> PFCOUNT mykey3
(integer) 15

如果允许容错,可以使用 Hyperloglog !
如果不允许容错,就使用 set 或者自己的数据类型即可!

Bitmap

Bitmap 位图,一种数据结构! 都是操作二进制位来进行记录,就只有0 和 1 两个状态!
可统计用户信息,活跃,不活跃! 登录 、 未登录! 打卡,365打卡!

192.168.17.151:6379> SETBIT sign 0 1           # 周一签到
(integer) 0
192.168.17.151:6379> SETBIT sign 1 0           # 周二未签到
(integer) 0
192.168.17.151:6379> SETBIT sign 2 0          
(integer) 0
192.168.17.151:6379> SETBIT sign 3 1
(integer) 0
192.168.17.151:6379> SETBIT sign 4 1
(integer) 0
192.168.17.151:6379> SETBIT sign 5 1           # 周六签到
(integer) 0
192.168.17.151:6379> getbit sign 3             # 查看某一天是否签到
(integer) 1
192.168.17.151:6379> bitcount sign             # 统计操作,统计签到的天数!
(integer) 4

事务

Redis 事务本质:一组命令的集合! 一个事务中的所有命令都会被序列化,在事务执行过程的中,会按照顺序执行!
事务的特性:
一次性、顺序性、排他性(事务在运行中不允许别人干扰的)!执行一些列的命令!

------ 队列 set rpush sadd 执行------

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

Redis事务没有隔离级别的概念
Redis单条命令是保证原子性的,但是事务不保证原子性

redis的事务:

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

1、正常执行事务

192.168.17.151:6379> MULTI               # 开启事务
OK
192.168.17.151:6379> set k1 v1
QUEUED
192.168.17.151:6379> set k2 v2
QUEUED
192.168.17.151:6379> get k1
QUEUED
192.168.17.151:6379> set k3 v3
QUEUED
192.168.17.151:6379> EXEC               # 执行事务
1) OK
2) OK
3) "v1"
4) OK

2、放弃事务!

192.168.17.151:6379> MULTI					# 开启事务
OK
192.168.17.151:6379> set k1 v1
QUEUED
192.168.17.151:6379> set k2 v2
QUEUED
192.168.17.151:6379> DISCARD				# 放弃事务
OK
192.168.17.151:6379> get k1 				# 事务队列中的命令都不会被执行
(nil)

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

192.168.17.151:6379> MULTI
OK
192.168.17.151:6379> set k1 v1 
QUEUED
192.168.17.151:6379> set k2 v2
QUEUED
192.168.17.151:6379> getset k2       # 错误的命令
(error) ERR wrong number of arguments for 'getset' command
192.168.17.151:6379> EXEC
(error) EXECABORT Transaction discarded because of previous errors.
192.168.17.151:6379> get k1			# 所有的命令都不会执行!
(nil)

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

192.168.17.151:6379> set k1 v1
OK
192.168.17.151:6379> MULTI
OK
192.168.17.151:6379> INCR k1    		①
QUEUED
192.168.17.151:6379> set k2 v2			②
QUEUED
192.168.17.151:6379> get k2				③
QUEUED
192.168.17.151:6379> exec
1) (error) ERR value is not an integer or out of range     # 第一条命令报错的,但依旧执行成功了!
2) OK
3) "v2"

监控! Watch

悲观锁:

  • 认为什么时候都会出问题,无论做什么都会加锁!

乐观锁:

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

Redis监控测试

1、正常执行成功!

192.168.17.151:6379> set money 100
OK
192.168.17.151:6379> set out 0
OK
192.168.17.151:6379> watch money
OK
192.168.17.151:6379> multi
OK
192.168.17.151:6379> DECRBY money 40
QUEUED
192.168.17.151:6379> INCRBY out 40
QUEUED
192.168.17.151:6379> exec
1) (integer) 60
2) (integer) 40

2、多线程修改值 , 使用watch 可以当做redis的乐观锁操作!

192.168.17.151:6379> watch money			# 监控 money
OK
192.168.17.151:6379> multi
OK
192.168.17.151:6379> DECRBY money 10
QUEUED
192.168.17.151:6379> INCRBY out 10
QUEUED
192.168.17.151:6379> exec					# 执行之前,另一个线程修改了money的值,就会导致事务失败!
(nil)

如果修改失败,就获取最新的值,再次监控

192.168.17.151:6379> UNWATCH			# 1、如果发现事务执行实失败,先进行解锁
OK
192.168.17.151:6379> WATCH money		# 2、获取最新的值,再次监控,
OK
192.168.17.151:6379> MULTI
OK
192.168.17.151:6379> DECRBY money 1
QUEUED
192.168.17.151:6379> INCRBY out 1
QUEUED
192.168.17.151:6379> EXEC				# 3、比对监控的值是否发生了变化,没有发生变化可以执行成功,发生变话执行失败
1) (integer) 999
2) (integer) 41

Jedis

Jedis 是 Redis 官方推荐的 java连接开发工具! 使用Java 操作Redis 中间件!

  • 使用:

1、引入依赖

    <dependency>
      <groupId>redis.clients</groupId>
      <artifactId>jedis</artifactId>
      <version>2.9.0</version>
    </dependency>

2、远程访问redis并调用方法

    @Test
    public void test(){
        //创建连接redis对象
        Jedis jedis = new Jedis("192.168.17.151",6379);
        System.out.println(jedis.ping());
        //jesis所有的方法就是数据类型的命令
        jedis.close();
    }

常用的API就是对应的上面学习的指令

3、创建jedis连接池配置对象

    @Test
    public void test(){
        /*
        * 创建jedis连接池配置对象
        * */
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        poolConfig.setMaxIdle(2);//最大空闲连接数
        poolConfig.setMaxTotal(5);//最大创建连接数
        poolConfig.setMaxWaitMillis(3000);//最大等待时间
        JedisPool jedisPool = new JedisPool(poolConfig,"192.168.17.151",6379);
        /*
        * 从jedis连接池获取jedis连接对象
        * */
        Jedis jedis = jedisPool.getResource();
        jedis.set("score","100");
        jedis.close();
    }

3、事务

    @Test
    public void test(){
        //创建连接redis对象
        Jedis jedis = new Jedis("192.168.17.151",6379);
        jedis.flushDB();
        String s = new String("张三");
        Transaction multi = jedis.multi();//开启事务
//        jedis.watch(s);//监控
        try {
            multi.set("user1","张三");
            multi.set("user2","李四");
//            int i = 1/0 ; // 代码抛出异常事务,执行失败!
            multi.exec();//执行事务
        } catch (Exception e) {
            multi.discard();//放弃事务
            e.printStackTrace();
        } finally {
            System.out.println(jedis.get("user1"));
            System.out.println(jedis.get("user2"));
            jedis.close();//关闭连接
        }
    }

SpringBoot整合Redis

在 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、配置redis服务器连接参数

spring:
  redis:
    host: 192.168.17.149      #ip地址
    port: 6379              #端口号
    database: 2           #操作的库

3、使用

@SpringBootTest
class Redis01SpringbootApplicationTests {
    //注入RedisTemplate
    @Autowired
    private RedisTemplate redisTemplate;

    @Test
    public void test() {
        // redisTemplate.xxx 操作不同的数据类型,api和指令是一样的
        // redisTemplate.opsForValue 操作字符串
        // redisTemplate.opsForList  操作List...等等类型
        // opsForSet
        // opsForHash
        // opsForZSet
        // opsForGeo
        // opsForHyperLogLog
        /*
        * 除了基本的操作,常用的方法都可以直接通过redisTemplate操作,比如事务,和基本的CRUD
        * */

        //获取redis连接对象
        RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
        connection.flushAll();
        connection.flushDb();
    }
}

Redis.conf详解

在这里插入图片描述
在这里插入图片描述
网络配置 NETWORK

bind 127.0.0.1   # 绑定的ip
protected-mode yes # 保护模式
port 6379  # 端口设置

通用配置 GENERAL

daemonize yes  # 以守护进程的方式运行,默认是 no,我们需要自己开启为yes!

pidfile /var/run/redis_6379.pid  # 如果以后台的方式运行,我们就需要指定一个 pid 文件!

# 日志
# Specify the server verbosity level.
# This can be one of:
# debug (a lot of information, useful for development/testing) 
# verbose (many rarely useful info, but not a mess like the debug level)
# notice (moderately verbose, what you want in production probably) 生产环境
# warning (only very important / critical messages are logged)
loglevel notice    #日志级别
logfile "" 		# 日志的文件位置名
databases 16  # 数据库的数量,默认是 16 个数据库
always-show-logo yes  # 是否总是显示LOGO

快照 SNAPSHOTTING

当redis进程意外退出, redis会自动触发生成RDB快照文件。 按照redis配置文件指定的规则触发
save 900 1			# 900s内,超过1个key被修改,触发
save 300 10			# 300s内,超过10个key被修改,触发
save 60 10000		# 60s内,超过10000个key被修改,触发
可自定义触发时机

stop-writes-on-bgsave-error yes  # 持久化如果出错,是否还需要继续工作!
rdbcompression yes 	# 是否压缩 rdb 文件,需要消耗一些cpu资源!
rdbchecksum yes	 # 保存rdb文件的时候,进行错误的检查校验!
dir ./  	  	#redis持久化文件保存的位置 , 默认存放在 / 跟目录下,我们可以手动修改

SECURITY 安全

192.168.17.151:6379> ping
PONG
192.168.17.151:6379> config get requirepass  		# 获取redis的密码,默认是没有密码
1) "requirepass"
2) ""
192.168.17.151:6379> config set requirepass "123456"  # 设置redis的密码
OK
192.168.17.151:6379> config get requirepass  		# 所有的命令都没有权限了,需要使用密码登录
(error) NOAUTH Authentication required.
192.168.17.151:6379> ping
(error) NOAUTH Authentication required.
192.168.17.151:6379> auth 123456 		 # 使用密码进行登录!
OK
192.168.17.151:6379> config get requirepass
1) "requirepass"
2) "123456"

客户端限制 CLIENTS

maxclients 10000  # 设置能连接上redis的最大客户端的数量
maxmemory <bytes>  # redis 配置最大的内存容量
maxmemory-policy noeviction  # 内存到达上限之后的处理策略
  1volatile-lru:只对设置了过期时间的key进行LRU(默认值)
  2、allkeys-lru : 删除lru算法的key 
  3volatile-random:随机删除即将过期key 
  4、allkeys-random:随机删除 
  5volatile-ttl : 删除即将过期的 
  6、noeviction : 永不过期,返回错误

AOF配置 APPEND ONLY MODE

appendonly no   # 默认是不开启AOF模式的,默认是使用rdb方式持久化的
appendfilename "appendonly.aof"  # AOF持久化的文件的名字

↓AOF持久化触发时机
# appendfsync always  # 每执行一个操作,都会记录到命令日志文件中。消耗性能
appendfsync everysec  # 每秒写入磁盘一次,默认开启,可能会丢失这1s的数据!
# appendfsync no    # 系统有空闲时同步

Redis持久化机制

RDB(Redis DataBase)

  • RDB持久化是指,redis将当前进程中的内存数据,以一个快照文件的方式,保存到磁盘上。
  • redis恢复数据是通过加载RDB快照文件完成。

RDB触发机制

  1. bgsave命令触发 : 执行bgsave命令时, redis会执行fork操做创建一个子进程, 使用子进程完成快照文件的生成。 阻塞只发生在fork阶段,不会造成主进程的阻塞!
    在这里插入图片描述
  2. 按照redis配置文件指定的规则触发
    在这里插入图片描述
  3. 退出redis,也会产生 rdb 文件!

RDB持久化文件保存的位置
在这里插入图片描述
恢复rdb文件

rdb文件在redis启动目录就可以,redis启动的时候会自动检查dump.rdb 恢复其中的数据!

查看rdb文件需要存在的位置
192.168.17.151:6379> config get dir
1) "dir"
2) "/usr/local"			# 如果在这个目录下存在 dump.rdb 文件,启动就会自动恢复其中的数据

优点:

  • RDB持久化文件经过了redis内部压缩算法处理, 持久化文件的大小要小很多,从而可以快速的完成数据的恢复。

缺点:

  • 无法实现秒级备份
  • fork进程的时候,会占用一定的内存空间!

AOF(Append Only File)

  • AOF持久化是指,将当前内存的数据,以命令日志文件的方式保存到磁盘!
  • 当redis重启服务的时候, redis将命令日志文件里的命令读取执行完成数据的恢复。

RDB触发机制

按照redis配置文件指定的规则触发
在这里插入图片描述

AOF持久化文件保存的位置
在这里插入图片描述
文件修复
如果这个 aof 文件有错位,这时候 redis 是启动不起来的吗,我们需要修复这个aof文件,
redis 给我们提供了一个工具 redis-check-aof --fix
在这里插入图片描述

优点:

  • 可以实现秒级备份

缺点:

  • aof命令日志文件远远比rdb大,修复速度比rdb慢!

AOF重写
通过重写命令日志文件, 缩小命令日志文件(优化掉无效的命令、过期数据的命令等)。
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值