Redis快速入门和重点详解

一、Redis概述

Redis是什么?

Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并

提供多种语言的API,免费和开源,是当下最热门的NoSql技术之一,也被人们称之为机构化数据库!

Redis能干嘛?

  1. 内存储存、持久化、内存中是断电即失、所以说持久化很重要(RDB、AOF)
  2. 效率高,可以用于高速缓存
  3. 发布订阅系统
  4. 地图信息分析
  5. 计数器、计时器(浏览量和计步器)

特性

  1. 多样的数据类型
  2. 持久化、集群、事务

1、Linux上安装Redis

  1. 官网(https://redis.io)下载安装包
  2. 解压redis的安装包,程序放/opt目录下(解压命令 tar -zxvf)
  3. 进入解压后的文件夹,可以看到redis的配置文件(redis.conf)
  4. 基本环境安装
yum install gcc-c++  # 安装gcc-c++

make # 安装环境

make install # 安装
  1. redis的默认安装路径/user/local/bin
  2. 将redis配置文件复制到当前目录下
  3. redis默认不是后台启动的,修改配置文件(daemonize的值改为yes)
  4. 启动redis服务(在bin下输入命令redis-server config/redis.conf
  5. 使用redis客户端(redis-cli -p 6379),连接redis服务
  6. 查看redis服务是否开启(ps -ef|grep redis)
  7. 如何关闭redis服务(shutdown)

2、测试性能(benchmark)

  • 一个官方的压力测试工具
  • 测试
# 100个并发连接,对我们十万个请求进行写入测试
redis-benchmark -h localhost -p 6379 -c 100 -n 100000

3、基础知识

  • redis默认有16个数据库,默认使用的是第0个,可以是select进行切换
  • flushdb:清除当前数据库
  • FLUSHALL:清除全部数据库
  • redis是单线程的,是基于内存操作的,cpu不是redis的性能瓶颈,是根据机器的内存和网络带宽

二、五大数据类型

可用作数据库,高速缓存和消息队列代理,它支持字符串、哈希表、列表、集合、有序集合,位图,hyperloglogs等数据类型

1、String(字符串)

incr views    # 自增1
decr views    # 自减1
incrby views key    # 指定增量
decr views key      # 指定减量
getrange(key start end)    # 截取字符串
setrange(key offset value) # 替换字符串
setex(set with expire)     # 设置过期时间
setnx(set if not exist)    # 不存在再设置
mset     # 批量设置值 
mget	 # 批量获取值
getset   # 先get再设置值

2、List(列表)

在redis中,可以把list当成栈、队列、阻塞队列

所有的list命令都是L开头的

127.0.0.1:6379> LPUSH list one        # 将一个值或者多个值,插入列表的头部(左)
(integer) 1
127.0.0.1:6379> LPUSH list two
(integer) 2
127.0.0.1:6379> LPUSH list three
(integer) 3
127.0.0.1:6379> LRANGE list 0 -1      # 获取list中的值(可以选择区间)
1) "three"
2) "two"
3) "one"
#########################################################################
127.0.0.1:6379> RPUSH list right      # 将一个值或者多个值,插入列表的尾部(右)
(integer) 4
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "two"
3) "one"
4) "right"
#########################################################################
LPOP
RPOP
127.0.0.1:6379> LRANGE list 0 -1      
1) "three"
2) "two"
3) "one"
4) "right"
127.0.0.1:6379> LPOP list             # 将一个值从头部移除
"three"
127.0.0.1:6379> RPOP list             # 将一个值从尾部移除
"right"
127.0.0.1:6379> LRANGE list 0 -1
1) "two"
2) "one"
#########################################################################
LINDEX list 0                         # 通过下标获取值
127.0.0.1:6379> LINDEX list 0
"two"
127.0.0.1:6379> LINDEX list 1
"one"
127.0.0.1:6379> LRANGE list 0 -1
1) "two"
2) "one"
#########################################################################
llen                                  # 返回列表长度
127.0.0.1:6379> LLEN list
(integer) 2
#########################################################################
lrem                                  # 移除指定个数的value
127.0.0.1:6379> LRANGE list 0 -1
1) "two"
2) "one"
127.0.0.1:6379> LREM list 1 one
(integer) 1
127.0.0.1:6379> LRANGE list 0 -1
1) "two"
#########################################################################
127.0.0.1:6379> LRANGE list 0 -1
1) "4"
2) "3"
3) "2"
4) "1"
127.0.0.1:6379> LTRIM list 1 2        # 截取指定范围的value,这个list已经被改变了
OK
127.0.0.1:6379> LRANGE list 0 -1
1) "3"
2) "2"
#########################################################################
rpoplpush                             # 移除列表的最后一个元素,将他移动到新的列表中
#########################################################################
linsert                               # 将某一个具体的值插入到列表中某个元素的前面或者后面
127.0.0.1:6379> rpush list one
(integer) 1
127.0.0.1:6379> rpush list two
(integer) 2
127.0.0.1:6379> LINSERT list after two three
(integer) 3
127.0.0.1:6379> LRANGE list 0 -1
1) "one"
2) "two"
3) "three"

小节

  • 他实际上是一个链表,BEFORE NODE AFTER,left和right都可以插入值
  • 如果key不存在,创建新的链表,如果存在,新增内容
  • 可以做消息队列、消息排队Lpush、Rpop,栈(Lpush、Lpop)

3、Set(集合)

Set中的值不能重复,命令都是S开头

127.0.0.1:6379> SADD set 1            # SET集合中添加元素
(integer) 1
127.0.0.1:6379> SADD set 2
(integer) 1
127.0.0.1:6379> SMEMBERS set	      # 查看SET集合中所有的值
1) "1"
2) "2"
#########################################################################
127.0.0.1:6379> SMEMBERS set
1) "1"
2) "2"
127.0.0.1:6379> SISMEMBER set 1       # 判断一个值是不是在set集合中
(integer) 1
127.0.0.1:6379> SISMEMBER set 3
(integer) 0
#########################################################################
127.0.0.1:6379> SCARD set             # 查看SET中的元素个数
(integer) 2
#########################################################################
127.0.0.1:6379> SREM set 1            # 移除SET中的指定元素
(integer) 1
127.0.0.1:6379> SMEMBERS set
1) "2"
#########################################################################
127.0.0.1:6379> SMEMBERS set          # 随机抽选出一个元素(后面可以加数量)
1) "2"
2) "3"
3) "4"
4) "5"
127.0.0.1:6379> SRANDMEMBER set
"3"
127.0.0.1:6379> SRANDMEMBER set
"2"
127.0.0.1:6379> SRANDMEMBER set
"5"
127.0.0.1:6379> SRANDMEMBER set
"4"
#########################################################################
127.0.0.1:6379> SADD k1 1
(integer) 1
127.0.0.1:6379> SADD k1 2
(integer) 1
127.0.0.1:6379> SADD k1 3
(integer) 1
127.0.0.1:6379> SADD k2 3
(integer) 1
127.0.0.1:6379> SADD k2 4
(integer) 1
127.0.0.1:6379> SADD k2 5
(integer) 1
127.0.0.1:6379> SDIFF k1 k2           # 查看k1、k2的差集合
1) "1"
2) "2"
127.0.0.1:6379> SINTER k1 k2          # 查看k1、k2的交集
1) "3"
127.0.0.1:6379> SUNION k1 k2          # 查看k1、k2的并集
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"

微博或者B站中,A用户所有关注的人放在一个SET集合中,粉丝也放在一个集合中,可以实现共同关注,共同爱好,二度好友

4、Hash(哈希)

map集合,key-,这时候就是一个map集合,本质和String类型没有太大区别

127.0.0.1:6379> HSET hash k1 v1                 # SET一个具体的key-value
(integer) 1
127.0.0.1:6379> HMSET hash k2 v2 k3 v3          # SET多个key-value
OK
127.0.0.1:6379> HGETALL hash                    # 获取hash中的全部数据
1) "k1"
2) "v1"
3) "k2"
4) "v2"
5) "k3"
6) "v3"

5、Zset(有序集合)

在Set的基础上,增加了一个值

127.0.0.1:6379> ZADD set 1 one                   # 添加一个值
(integer) 1
127.0.0.1:6379> ZADD set 2 two 3 three           # 添加多个值
(integer) 2
127.0.0.1:6379> ZRANGE set 0 -1
1) "one"
2) "two"
3) "three"
#########################################################################
127.0.0.1:6379> ZADD salary 2500 zhangsan
(integer) 1
127.0.0.1:6379> ZADD salary 4000 lisi
(integer) 1
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf              # 显示所有用户
1) "zhangsan"
2) "lisi"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf withscores   # 显示所有用户并且附带成绩
1) "zhangsan"
2) "2500"
3) "lisi"
4) "4000"

三、三种特殊数据类型

1、geospatial(地理位置)

朋友的定位、附近的人、打车距离

相关命令

  • Redis GEOHASH
  • Redis GEOPOS
  • Redis GEODIST
  • Redis GEORADIUS
  • Redis GEOADD
  • Redis GEORADIUSBYMEMBER
# GEOADD 添加地理位置
# 规则:两级无法直接添加,我们一般会下载城市,直接通过JAVA程序一次性导入

# GEOPOS 获取指定城市的经度和纬度

# GEODIST 返回两个给定位置之间的距离

# GEORADIUS 以给定的经纬度为中心, 找出某一半径内的元素

2、Hyperloglog(基数统计)

基数统计的算法、页面统计

网页的UV(一个人访问一个网站多次,但是还是算作一个人)

  • 传统方式,SET保存用户ID,然后就可以统计SET中的元素数量作为标准判断,这种方式保存大量的ID就会比较麻烦,占内存
  • Hyperloglog优点:占用的内存是固定,2^64不同的元素,只需要占用12KB内存
127.0.0.1:6379> PFADD key a b c d e f                # 创建第一组元素key
(integer) 1
127.0.0.1:6379> PFCOUNT key                          # 统计key元素的基数数量
(integer) 6
127.0.0.1:6379> PFADD key1 e f i j k                 # 创建第二组元素key
(integer) 1
127.0.0.1:6379> PFMERGE key2 key key1                # 合并两组key、key1
OK
127.0.0.1:6379> PFCOUNT key2                         # 查看并集的数量
(integer) 9 

3、Bitmaps(位图)

应用场景:两个状态的数据都可以使用Bitmaps,都是操作二进制位来进行记录

  • 疫情疫情感染人数(0/1)
  • 统计用户信息活跃或者不活跃
  • 登录或者未登录的人数
  • 测试:使用bitmap来记录周一到周日的打卡(0为没打,1为打了)
127.0.0.1:6379> SETBIT sign 0 1
(integer) 0
127.0.0.1:6379> SETBIT sign 1 0
(integer) 0
127.0.0.1:6379> SETBIT sign 2 0
(integer) 0
127.0.0.1:6379> SETBIT sign 3 1
(integer) 0
127.0.0.1:6379> SETBIT sign 4 1
(integer) 0
127.0.0.1:6379> SETBIT sign 5 0
(integer) 0
127.0.0.1:6379> SETBIT sign 6 1
(integer) 0
#########################################################################
127.0.0.1:6379> GETBIT sign 1                 # 查看某一天是否打开
(integer) 0
127.0.0.1:6379> GETBIT sign 4
(integer) 1
#########################################################################
127.0.0.1:6379> BITCOUNT sign                 # 统计打卡的天数,查看是否全勤
(integer) 4

四、Redis事务

  • redis事务本质:一组命令的集合,一个事务中的所有命令都会被序列化,在事务执行过程中,会按照队列执行
  • Redis单条命令要保存原子性,但是事务不保证原子性,但是具有一次性、顺序性、排他性
  • redis的事务:
    • 开启事务(MULTI)
    • 命令入队(命令)
    • 执行事务(EXEC)

正常执行事务

127.0.0.1:6379> MULTI               # 开启事务
OK
127.0.0.1:6379> set k1 v1           # 命令入队
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> EXEC                # 事务执行
1) OK
2) OK
3) "v2"
4) OK
#########################################################################
127.0.0.1:6379> MULTI               # 开启事务
OK
127.0.0.1:6379> set k1 v1 
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> DISCARD             # 取消事务
OK
127.0.0.1:6379> get k4
(nil)

五、Jedis

  • 使用JAVA来操作Redis,是官方推荐的Java开发连接工具,是操作Redis的中间件

  • 测试:

    • 导入对应依赖
    <dependencies>
        <!--导入Jedis的依赖-->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>3.2.0</version>
        </dependency>
        <!--Fastjson-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.72</version>
        </dependency>
    </dependencies>
    
    • 编码测试:

      • 连接数据库
      package com.shdq;
      
      import redis.clients.jedis.Jedis;
      
      public class Ping {
          public static void main(String[] args) {
              // new一个Jedis对象
              Jedis jedis = new Jedis("192.168.126.131",6379);
              System.out.println(jedis.ping());
          }
      }
      
      
      • 操作命令
      • 断开连接

六、SpringBoot整合

  • springboot操作数据:都要和spring-data
  • spring-data也是和spring-boot齐名的项目
  • springboot2.X以后,原来使用的Jedis被替换成了Lettuce
    • Jedis:采用直连,多个线程操作是不安全的,要避免就要使用JedisPool连接池(BIO模式)
    • Lettuce:采用netty,实例可以在多个线程中共享,不存在线程不安全的情况,可以减少线程数据(NIO模式)

整合测试

  • springboot所有的配置类,都有一个自动配置类(RedisAutoConfiguration)
  • 自动配置类都会绑定一个properties配置文件(RedisProperties)
  • 配置连接、测试
spring:
  redis:
    host: 192.168.126.132
    port: 6379
@SpringBootTest
class SpringRedisApplicationTests {

    @Autowired
    private RedisTemplate redisTemplate;

    @Test
    void contextLoads() {
        // 测试连接对象
        RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
        connection.flushAll();
    }
  • 真实开发一般都适用JSON来传递对象,所有对象都需要序列化
  • 所有的redis操作,对Java开发人员来说,十分简单,主要是理解redis每一种数据结构的应用场景

七、Redis.conf详解

  • 通过配置文件启动(redis-server config/redis.conf)

单位(unit单位对大小写不敏感)

# 1k => 1000 bytes
# 1kb => 1024 bytes
# 1m => 1000000 bytes
# 1mb => 1024*1024 bytes
# 1g => 1000000000 bytes
# 1gb => 1024*1024*1024 bytes
#
# units are case insensitive so 1GB 1Gb 1gB are all the same.

包含

# include /path/to/local.conf
# include /path/to/other.conf

网络

bind 127.0.0.1          # 绑定IP
protected-mode no       # 保护模式
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

快照

  • 持久化,在规定的时间内,执行了多少次操作,则会持久化到文件.rdb、.aof
  • redis是内存数据库,没有持久化,那么数据断电即
################################ SNAPSHOTTING  ################################
# 如果900s内,如果至少有一个key进行了修改,我们就进行持久化操作
save 900 1
# 如果300s内,如果至少有十个key进行了修改,我们就进行持久化操作
save 300 10
# 如果60s内,如果至少有一万个key进行了修改,我们就进行持久化操作
save 60 10000

SECURITY(安全)

################################## SECURITY ###################################
requirepass 123456                  # 登录即需要密码
CONFIG SET requirepass "123456"     # 命令行设置密码

八、Redis持久化

  • 面试和工作,持久化都是重点!
  • redis是内存数据库,如果不将内存中的数据保存到硬盘中,那么一旦服务器进程退出,服务器中的数据库状态也会消失,所以redis提供了持久化功能

1、RDB(RedisDataBase)

Redis会单独创建一个(fork)子进程来进行持久化,会先将数据写入一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程,主进程不进行任何IO操作,这也确保了极高的性能,如果需要进行大规模恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加高效,RDB的缺点是最后一次持久化的数据可能会丢失,默认的持久化方式就是RDB,rdb保存的文件是dump.rdb

sava 5 60                   # 只要60内修改超过5次,就会触发rdb操作
  • 触发机制
    • sava的规则满足的情况下,会自动触发rdb操作
    • 执行flushall命令,也会触发rdb操作
    • 退出redis,也会产生rdb文件
  • 恢复
    • 只需要把rdb文件放在我们的redis启动目录下就可以了,redis启动会自动检查dump.rdb,恢复其中的数据

2、AOF(Append Only File)

  • 将我们所有的命令都记录下来,history文件,恢复的时候就把这个文件的命令全部执行一遍
  • 以日志的形式记录每一次写操作,将Redis执行过的所有指令记录下来(读操作不记录),只许追加文件不许改写文件,redis启动之初会读该文件并重新构建数据,redis重启的话根据日志文件的内容将写指令从前到后执行一次以完成数据恢复工作,AOF保存的文件是appendonly.aof

九、Redis发布订阅

  • Redis发布订阅(pub/sub)是一种消息通信模式:发送者发布消息、订阅者接受消息(微信、微博订阅发布)
127.0.0.1:6379> SUBSCRIBE zhou                    # 订阅一个频道
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "zhou"
3) (integer) 1
1) "message"
2) "zhou"
3) "Hello"
#########################################################################
127.0.0.1:6379> PUBLISH zhou Hello                # 发布消息
(integer) 1

十、Redis主从复制

主从复制:是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点、后者称为从节点,数据的复制是单向的,只能由主节点到从节点,Master以写为主,Slave以读为主

默认情况下,每台Redis服务器都是主节点,且一个主节点可以有多个从节点(或者没有从节点),但一个从节点只能有一个主节点

  • 数据冗余:主从复制实现在数据的热备份,是持久化之外的一种数据冗余方式
  • 故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的恢复,实际上是一种服务的冗余
  • 负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务,分担服务器负载
  • 高可用:主从复制是哨兵和集群能够实施的基础,因此主从复制是Redis高可用的基础

1、环境配置

  • 只配置从库,不配置主库
127.0.0.1:6379> INFO replication                                  # 查看当前库的信息
# Replication
role:master                                                       # 角色
connected_slaves:0                                                # 从机数量
master_replid:f35ff87d0af7d90ce5c3609b56e3479a7435ae8f
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
  • 配置多个服务器,复制三个配置文件,然后修改对应信息
    • 端口、pid名字
    • log文件名字
    • dump.rdb名字
  • 从机配置
127.0.0.1:6380> SLAVEOF 127.0.0.1 6379                            # 配置从机命令(SLAVEOF host port)
127.0.0.1:6380> INFO replication                                  # 查看当前库的信息
# Replication
role:slave                                                        # 角色
connected_slaves:0    

复制原理

  • Slave启动成功连接到master后会发送一个sync同步命令
  • Master接到命令后,启动后台的存盘进程,同时收集所有接受到用于修改数据的命令,在后台进程执行完毕之后,master将数据文件发到slave,完成同步
  • 全量复制:slave服务在接受到数据库服务后,将其存盘并加载到内存中
  • 增量复制:Master继续将新的所有收集到的修改命令依次传到slave,完成同步

十一、哨兵模式

自动选取主机模式

主从切换技术的方法是:当主服务器宕机后,需要手动把一台从服务器切换成主服务器,这需要人工干预,更多时候优先考虑哨兵模式,Redis从2.8以后开始使用Sentinel(哨兵)架构来解决这个问题

哨兵模式是一种特殊模式,redis提供了哨兵命令,哨兵是一个独立的进程,作为进程会独立运行,其原理是哨兵通过发送命令,等待redis服务器响应,从而监控运行多个redis实例

  • 配置哨兵文件(sentinel.conf)
sentinel monitor redis 127.0.0.1 6379 1

后面的数字1,代表主机挂了,slave投票看让谁接替成为主机,票数最多的,就会成为主机!

  • 启动哨兵
[root@localhost bin]# redis-sentinel config/sentinel.conf 
12021:X 10 Sep 2020 16:43:03.570 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
12021:X 10 Sep 2020 16:43:03.570 # Redis version=5.0.4, bits=64, commit=00000000, modified=0, pid=12021, just started
12021:X 10 Sep 2020 16:43:03.570 # Configuration loaded
12021:X 10 Sep 2020 16:43:03.571 * Increased maximum number of open files to 10032 (it was originally set to 1024).
                _._                                                  
           _.-``__ ''-._                                             
      _.-``    `.  `_.  ''-._           Redis 5.0.4 (00000000/0) 64 bit
  .-`` .-```.  ```\/    _.,_ ''-._                                   
 (    '      ,       .-`  | `,    )     Running in sentinel mode
 |`-._`-...-` __...-.``-._|'` _.-'|     Port: 26379
 |    `-._   `._    /     _.-'    |     PID: 12021
  `-._    `-._  `-./  _.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |           http://redis.io        
  `-._    `-._`-.__.-'_.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |                                  
  `-._    `-._`-.__.-'_.-'    _.-'                                   
      `-._    `-.__.-'    _.-'                                       
          `-._        _.-'                                           
              `-.__.-'                                               

12021:X 10 Sep 2020 16:43:03.572 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
12021:X 10 Sep 2020 16:43:03.573 # Sentinel ID is 0fb4a488a02b611e5dd2eae5e60d7e9e5055e6e2
12021:X 10 Sep 2020 16:43:03.573 # +monitor master redis 127.0.0.1 6379 quorum 1
  • 哨兵模式
    • 哨兵集群:基于主从复制的模式,所有的主从配置有点,它都有
    • 主从可以切换,故障可以转移,系统的可用性就会更好
    • 哨兵模式就是主从模式的升级,从手动到自动
    • redis不好在线扩容,实现哨兵模式的配置很麻烦,里面有很多选择

十二、Redis缓存穿透和雪崩

Redis缓存的作用,极大的提高了应用程序的性能和效率,特别是数据查询方面,如果对数据一致性要求很高,就不能使用缓存

1、缓存穿透

概念:用户想要查询一个数据,发现redis内存数据库没有,也就是缓存没用命中,于是向持久层数据库查询,发现也没有,一旦用户很多的时候,缓存都没有命中(比如秒杀活动),于是都去请求持久层,给持久层数据库造成很大的压力,这时候就相当于出现了缓存穿透

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值