Redis随笔

NoSql概述

为什么要用Nosql

1、单机Mysql年代

90年代,业务访问量不多,一个基本的网站单个数据库完全足够

2、Memcached(缓存)+Mysql+垂直拆分(读写分离)

网站80%的情况都在读,每次都要取查询数据库的话就十分麻烦!为了减轻数据压力,我们使用缓存来保证效率。

发展过程:优化数据结构和索引->文件缓存(IO)->Memcached(当时最热门技术)

image-20211222225119978

3、分库分表+水平拆分+Mysql集群

技术和业务在发展的同时,对人的要求也越来越高

本质:数据库的读和写

早些年MyISAM:表锁,(读写时,所在的表被锁,十分影响并发性)

Innodb:行锁

目前一个基本的互联网项目

image-20211222190927740

什么是NoSql

NoSql

NoSql = Not Only Sql(不仅仅是Sql)

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

泛指非关系型数据库,传统关系型数据库很难对付web2.0时代,尤其是超大规模的高并发的社区。NoSql在当今大数据环境下发展十分迅速,Redis是发展最快的,而且是我们当下必须要掌握的一个技术!

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

NoSql特点–解耦

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

2、大数据量高性能(NoSql的缓存记录级,是一种细粒度的缓存,性能会比较高)

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

4、传统的RDBMS和NoSql

传统的 RDBMS(关系型数据库)
- 结构化组织
- SQL
- 数据和关系都存在单独的表中
- 操作语言,定义语言
- 严格的一致性
- ...

Nosql
- 不仅仅是数据
- 没有固定的查询语言
- 键值对存储,文档存储,图形数据库(社交关系)
- 最终一致性
- CAP定理和BASE (异地多活)
- 高性能,高可用,高可扩
- ...

了解:3V+3高

  • 大数据时代的3V:主要是描述问题的

1、海量Volume

2、多样Variety

3、实时Velocity

  • 互联网需求的3高:主要是对程序的要求

1、高并发(Java JUC)

2、高可拓(随时水平拆分,机器不够了,扩展机器来解决)

3、高性能(保证用户体验和性能)

NoSql+RDBMS->《阿里巴巴的架构演进》

image-20211222193904184

技术急不得,越是慢慢学,精学细学,才能学得越扎实

大量公司做的都是相同的业务(签竞品协议)

高中,大一开始就应该开始学习了,真的太多东西要学了

image-20211222195619756

如果你想当一个架构师:没有什么是加一层解决不了的!

推荐文章:阿里云的这群疯子—认输,才是真的输了

# 1、基本结构型数据
	关系型数据库
# 2、散列文档,结构松散型数据
	文档型数据库,mongodb
# 3、图片,文件资源
	分布式文件系统 FastDFS
	- 淘宝 TFS
	- Google GFS
	- Hadoop HDFS
	- 阿里云 oss
# 4、商品的关键字(搜索)
	- 搜索引擎 solr elasticsearch
	- ISerach: 多隆(大佬!!) 
	所有牛逼的人都有一段苦逼的岁月!但是你只要像SB一样的去坚持,终将牛逼!
# 5、商品热本的波段信息
	内存数据库
	- Redis Tair、MemCache
# 6、商品的交易、外部的支付接口
	- 三方应用

大型互联网应用问题:

  • 数据类型太多了
  • 数据源繁多,经常重构
  • 数据要改造,大面积更改

解决方案:

image-20211222224731709

image-20211222225008032

思想的提高永远大于知识的学习!

NoSql的四大分类

KV键值对

  • 新浪:Redis
  • 美团:Redis+Tair
  • 阿里、百度:Redis+MemCache

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

  • MongoDB
    • mongodb基于分布式文件存储的数据库,C++编写,主要用来处理大量的文档
    • MongoDB是一个介于关系型数据库和非关系型数据库中间的产品,MongoDB是非关系型数据库中功能最丰富,最想关系型数据库的。
  • ConthDB

列存储数据库

  • HBase
  • 分布式文件系统

图关系数据库

  • neo4j
  • InfoGrid
  • redisgraph

Redis入门

概述

Redis是什么?

Redis(RemoteDictionaryServe),即远程字典服务

Redis能干嘛?

1、内存存储,持久化,内存中是断电即失,所以说持久化很重要(rdb、aof)

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

3、发布订阅系统

4、地图信息分析

5、计时器、计数器(浏览量! )

特性

1、多样的数据类型

2、持久化

3、集群

4、事物

Window安装[不推荐]

1、官网或者GitHub下载安装包

2、解压到指定电脑目录,可配置相应环境变量

3、开启运行服务器

4、使用redis客户端连接服务器

Linux安装

1、下载安装包

2、解压安装包

3、进入解压后的文件夹,可以看到redis.conf

yum install gcc-c++
make # 编译
make install # 安装

5、redis的默认安装路径: /usr/local/bin

6、将配置文件复制拷贝到备份

7、redis默认不是后台启动

daemonize yes

8、启动redis服务

image-20211222234344752

9、查看进程

ps -ef | grep redis

image-20211222234519231

测试性能

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

官方自带性能测试工具

image-20211223090651782

测试一下:

# 测试: 100个并发,100000请求
redis-benchmark -h localhost -p 6379 -c 100 -n 100000

image-20211223091617749

基础知识

redis默认有16个数据库,默认使用第0个数据库,使用select进行切换

C:\Users\86150>redis-cli
127.0.0.1:6379> select 1
OK
127.0.0.1:6379[1]> dbsize
(integer) 0

keys *查看所有的key

flushdbflushall清空当前数据库,清空所有数据库

exists key,判断值是否存在

move name 1,移动key为name到数据库1

expire name 10,设置key为name的值10s后过期

ttl name,查看当前key剩余秒数

为什么端口号是:6379(九宫格缩写,以为女星名字)

redis是单线程

官方表示,redis是基于内存操作,cpu不是redis性能瓶颈,redis的瓶颈是根据机器的内存和网络带宽。

redis为什么单线程还这么块

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

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

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 *  #查看所有的key
flushdb   #清空当前数据库,
flushall  #清空所有数据库
exists key #判断值是否存在
move name 1 #移动key为name到数据库1
expire name 10 #设置key为name的值10s后过期
ttl name  #查看当前key剩余秒数
type name #查看当前key为name的值类型
append key1 "hello" #追加
strlen key1 # 查看key为key1的长度

string

set key1 v1 #设置值
get key1 #获得值
append key1 "hello" #追加,如果key不存在,相当于set key
strlen key1 # 查看key为key1的长度

incr views # 自加1
decr views # 自减1

incrby views 10 # 自加10,步长为10
decrby views 10 # 自减10,步长为10

getrange key1 0 3 # 查看字符串范围 0到3(包括3)
getrange key1 0 -1 # 查看字符串全部范围

setrange key1 3 xxx # 替换指定位置3的字符串

setex #(set with expire) 设置过期时间
setex key3 30 "hello"

setnx #(set if not exist)	不存在再设置
setnx mykey "redis"

mset k1 v1 k2 v2 k3 v3  # 批量设置值
mget k1 k2 k3 	# 批量获取值

msetnx k1 v1 k4 v4 # 当不存在时批量设置值 msetnx是一个原子性的操作,要么一起成功,要么一起失败

# 对象
set user:1 {name:zhangsan,age:3}

# 批量设置对象
# 这里的key是一个巧妙的设计:user:{id}:{filed},如此设计可以极大的提升复用率
mset user:2:name lisi user:2:age 21
OK
mget user:2:name user:2:age
1) "lisi"
2) "21"


getset key1 redis #先获取,再设置,如果不存在返回nil,设置新值,如果存在,替换原来的值


数据结构是相同的,string类似的使用场景:value除了是我们的字符串还可以是我们的数字

  • 计数器
  • 统计多单位的数量

list

基本的数据类型,列表

在redis中,可以设计成栈,队列,阻塞队列

所有的list命令都是l开头的,并且获取命令都是默认先进后出原则

127.0.0.1:6379> LPUSH list1 one
(integer) 1
127.0.0.1:6379> LPUSH list1 two
(integer) 2
127.0.0.1:6379> LPUSH list1 three
(integer) 3
127.0.0.1:6379> LRANGE list1 0 -1
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> LRANGE list1 0 1
1) "three"
2) "two"

127.0.0.1:6379> RPUSH list2 one
(integer) 1
127.0.0.1:6379> RPUSH list2 two
(integer) 2
127.0.0.1:6379> RPUSH list2 three
(integer) 3
127.0.0.1:6379> LRANGE list2 0 -1
1) "one"
2) "two"
3) "three"
127.0.0.1:6379> LRANGE list2 0 1
1) "one"
2) "two"

# lpop 默认弹出左边第一个元素,rpop默认弹出右边第一个元素
127.0.0.1:6379> LRANGE list1 0 -1
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> LPOP list1
"three"
127.0.0.1:6379> RPOP list2
"three"

# lindex 通过下标获取值
127.0.0.1:6379> LINDEX list1 0
"two"
127.0.0.1:6379>

# llen 获取keylist的长度
127.0.0.1:6379> LLEN list1
(integer) 2

# 精确移除list1中的一个值:one,精度匹配
127.0.0.1:6379> LREM list1 1 one
(integer) 1
127.0.0.1:6379> LRANGE list1 0 -1
1) "two"

# 通过下标截取指定下标的list操作,截取后只剩下截取后的结果
ltrim mylist 1 2

# 弹出一个值到另一个列表中
lpoplpush mylist myotherlist # 移除列表的最后一个元素,将他移动到新的列表中

# lset更新操作,目标元素要先存在,否则失败
lset list 0 item

# linsert 列表插入操作
linsert mylist before "world" "other"
127.0.0.1:6379> LRANGE list1 0 -1
1) "three"
2) "one"
3) "two"
127.0.0.1:6379> LINSERT list1 before one onebefore
(integer) 4
127.0.0.1:6379> LRANGE list1 0 -1
1) "three"
2) "onebefore"
3) "one"
4) "two"

小结

  • list实际上是一个链表,before node after,left,right 都可以插入值
  • 如果key不存在,创建新的链表
  • 如果key存在,新增内容
  • 如果移除了所有值,空链表,也代表不存在!
  • 在两边插入或者改动值,效率最高!中间元素,相对来说效率会低一点

消息队列,栈的实现

set

set中的值是不能重复的!

所有set命令的值都是s开头的

127.0.0.1:6379> SADD myset hello
(integer) 1
127.0.0.1:6379> SADD myset world
(integer) 1
127.0.0.1:6379> SMEMBERS myset #查看set 集合中添加的值
1) "world"
2) "hello"
127.0.0.1:6379> SISMEMBER myset hello # 判断set 集合中的值是否存在
(integer) 1
127.0.0.1:6379> SCARD myset # 查看集合中有多少元素
(integer) 
127.0.0.1:6379> SREM myset hello # 移除指定的一个集合元素
(integer) 1
127.0.0.1:6379> SMEMBERS myset
1) "world"


# set无序不重复集合,抽随机
127.0.0.1:6379> SMEMBERS myset
1) "test3"
2) "test2"
3) "test4"
4) "world"
5) "test1"
127.0.0.1:6379> SRANDMEMBER myset # 随机抽取一个集合中的元素
"test4"
127.0.0.1:6379> SRANDMEMBER myset 1
1) "test4"
127.0.0.1:6379> SRANDMEMBER myset 2
1) "test2"
2) "test4"

# 随机删除指定的key
127.0.0.1:6379> SPOP myset
"test2"
127.0.0.1:6379> SMEMBERS myset
1) "test3"
2) "test4"
3) "world"
4) "test1"
127.0.0.1:6379> SPOP myset 2
1) "test1"
2) "test3"

# 将一个指定的值,移动到另一个set集合中
127.0.0.1:6379> SMEMBERS myset
1) "test5"
2) "test6"
3) "test4"
4) "world"
127.0.0.1:6379> SMOVE myset myset2 world
(integer) 1
127.0.0.1:6379> SMEMBERS myset2
1) "world"
2) "t1"


# 共同关注 (并集)
# 数字集合类:
- 差集
- 交集
- 并集
# 查看差集
127.0.0.1:6379> sadd skey1 a
(integer) 1
127.0.0.1:6379> sadd skey1 b
(integer) 1
127.0.0.1:6379> sadd skey1 v
(integer) 1
127.0.0.1:6379> sadd skey2 v
(integer) 1
127.0.0.1:6379> sadd skey2 c
(integer) 1
127.0.0.1:6379> sadd skey2 d
(integer) 1
127.0.0.1:6379> SDIFF skey1 skey2
1) "b"
2) "a"

# 查看交集
127.0.0.1:6379> SINTER skey1 skey2
1) "v"

# 查看并集
127.0.0.1:6379> SUNION skey1 skey2
1) "v"
2) "b"
3) "a"
4) "d"
5) "c"

微博,A用户将所有关注的人放在一个set集合中!将它的粉丝也放在一个集合中!共同关注,共同爱好,二度好友,推荐好友(六度分割理论)

hash

map集合,key-map,这时候值是一个map集合,本质上和string类型没有太大区别,还是一个简单的key-vlaue,类型变成是hash

set myhash field value

# 设置一个hash值
127.0.0.1:6379> HSET myhash field1 testhash
(integer) 1
127.0.0.1:6379> HGET myhash field1
"testhash"
# 设置多个hash值,当key重复时会覆盖原来的内容
127.0.0.1:6379> hmset myhash field1 hello field2 world
OK
127.0.0.1:6379> hmget myhash field1 field2
1) "hello"
2) "world"

# 删除指定字段内容
127.0.0.1:6379> hmset myhash1 test1 111 test2 222
OK
127.0.0.1:6379> get myhash1
(error) WRONGTYPE Operation against a key holding the wrong kind of value
127.0.0.1:6379> hmget myhash1 test1 test2
1) "111"
2) "222"
127.0.0.1:6379> hdel myhash1 test1
(integer) 1
127.0.0.1:6379> hmget myhash1 test1 test2
1) (nil)
2) "222"

# 获取所有hash字段
127.0.0.1:6379> hgetall myhash1
1) "test2"
2) "222"
# 获取hash内容长度
127.0.0.1:6379> hlen myhash1
(integer) 1

# 判断hash字段是否存在
127.0.0.1:6379> hexists myhash1 test2
(integer) 1
127.0.0.1:6379> hexists myhash1 test3
(integer) 0

# 获取所有hash key或者value
127.0.0.1:6379> hkeys myhash
1) "field1"
2) "field2"
127.0.0.1:6379> hvals myhash
1) "hello"
2) "world"

127.0.0.1:6379> hset myhash field3 5
(integer) 1
# 给hash某个字段指定增量
127.0.0.1:6379> hincrby myhash field3 1
(integer) 6
# 给hash某个字段指定减量
127.0.0.1:6379> hincrby myhash field3 -1
(integer) 5
# 判断hash某个字段是否存在,不存在则可以设置,存在则不可以设置(应用分布式锁)
127.0.0.1:6379> hsetnx myhash field4 hello
(integer) 1
127.0.0.1:6379> hsetnx myhash field4 world
(integer) 0

hash变更的数据user name age,尤其是用户信息之类的,经常变动的信息!hash更适合对象的存储

zset

有序集合

在set的基础上,增加了一个值,对比:sadd myset hello,zadd myzset 1 hello

所有zset的命令都是以z开头的

# 设置一个有序集合一个值
127.0.0.1:6379> zadd myzset 1 one
(integer) 1
127.0.0.1:6379> zadd myzset 2 two 3 three
(integer) 2
# 获取有序集合
127.0.0.1:6379> ZRANGE myzset 1 -1
1) "two"
2) "three"
127.0.0.1:6379> ZRANGE myzset 0 -1
1) "one"
2) "two"
3) "three"
127.0.0.1:6379>

# 排序
127.0.0.1:6379> zadd salary 2500 xiaohong
(integer) 1
127.0.0.1:6379> zadd salary 5000 zhangsan
(integer) 1
127.0.0.1:6379> zadd salary 500 xiaoming
(integer) 1
# 升序排序 从小到大
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf
1) "xiaoming"
2) "xiaohong"
3) "zhangsan"
# 升序排序带参数
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf withscores
1) "xiaoming"
2) "500"
3) "xiaohong"
4) "2500"
5) "zhangsan"
6) "5000"
# 从负无穷到2500进行排序并且附带参数
127.0.0.1:6379> ZRANGEBYSCORE salary -inf 2500 withscores
1) "xiaoming"
2) "500"
3) "xiaohong"
4) "2500"
# 降序排序,从大到小
127.0.0.1:6379> ZREVRANGE salary 0 -1
1) "zhangsan"
2) "xiaohong"
127.0.0.1:6379> ZREVRANGE salary 0 -1 withscores
1) "zhangsan"
2) "5000"
3) "xiaohong"
4) "2500"

# zrem移除元素
127.0.0.1:6379> ZRANGE salary 0 -1
1) "xiaoming"
2) "xiaohong"
3) "zhangsan"
127.0.0.1:6379> zrem salary xiaoming
(integer) 1
127.0.0.1:6379> ZRANGE salary 0 -1
1) "xiaohong"
2) "zhangsan"

# 获取有序集合中的个数
127.0.0.1:6379> zcard salary
(integer) 2

# 获取集合不同区间中的个数
127.0.0.1:6379> zcount myzset 1 2
(integer) 2
127.0.0.1:6379> zcount myzset 1 2
(integer) 2
127.0.0.1:6379> zcount myzset 1 3
(integer) 3
127.0.0.1:6379> zcount myzset 1 4
(integer) 3
127.0.0.1:6379> zcount myzset 0 4
(integer) 3

案例思路:set排序,存储班级成绩表,工资表排序

普通消息:1,重要消息:2 带权重进行判断

排行榜应用实现,取Ton N实现

三种特殊数据类型

geospatial 地理位置存储

朋友的定位,附近的人,打车距离计算

Redis的Geo在Redis3.2版本已经推出,这个功能可以推算地理位置的信息,两地之间的距离,方圆几里的人!查询测试一些数据

只有六个命令

image-20211225154134901

getadd

getadd 添加地理位置

# 添加地理位置
# 规则:两级无法直接添加,我们一般会下载城市数据,直接通过相关程序一次性导入
# error:127.0.0.1:6379> geoadd china:city 39.90 116.40 beijing
# (error) ERR invalid longitude,latitude pair 39.900000,116.400000
# 当经纬度超出一定范围时,会报超范围错误
127.0.0.1:6379> geoadd china:city 116.40 39.90 beijing
(integer) 1
127.0.0.1:6379> geoadd china:city 121.47 31.23 shanghai
(integer) 1
127.0.0.1:6379> geoadd china:city 106.50 29.53 chongqing 114.05 22.52 shenzhen
(integer) 2
127.0.0.1:6379> geoadd china:city 120.16 30.24 hangzhou
(integer) 1
127.0.0.1:6379> geoadd china:city 108.96 34.26 xian

geopos

geopos 获取地理位置

获得当前定位,一定是一个坐标值

# 获取地理位置
127.0.0.1:6379> geopos china:city beijing
1) 1) "116.39999896287918091"
   2) "39.90000009167092543"
   
127.0.0.1:6379> geopos china:city beijing shanghai
1) 1) "116.39999896287918091"
   2) "39.90000009167092543"
2) 1) "121.47000163793563843"
   2) "31.22999903975783553"

geodist

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

单位:

  • m:米
  • km:千米
  • mi:英里
  • ft:英尺
# 查看两个城市之间的距离
127.0.0.1:6379> geodist china:city beijing shanghai
"1067378.7564"
127.0.0.1:6379> geodist china:city beijing shanghai km
"1067.3788"

georadius

georadius 以给定的经纬度为中心,找出某一半径内的元素 --附近的人

# 找出110 30为中心 500 km半径内的元素
127.0.0.1:6379> georadius china:city 110 30 500 km
1) "chongqing"
2) "xian"
# 显示直线距离
127.0.0.1:6379> georadius china:city 110 30 500 km withdist
1) 1) "chongqing"
   2) "341.9374"
2) 1) "xian"
   2) "483.8340"
# 显示直线距离跟经纬度
127.0.0.1:6379> georadius china:city 110 30 500 km withdist withcoord
1) 1) "chongqing"
   2) "341.9374"
   3) 1) "106.49999767541885376"
      2) "29.52999957900659211"
2) 1) "xian"
   2) "483.8340"
   3) 1) "108.96000176668167114"
      2) "34.25999964418929977"
# 限定获得数量
127.0.0.1:6379> georadius china:city 110 30 500 km withdist withcoord count 1
1) 1) "chongqing"
   2) "341.9374"
   3) 1) "106.49999767541885376"
      2) "29.52999957900659211"

georadiusbymember

georadiusbymember 找出指定元素的一定半径周围的元素

127.0.0.1:6379> GEORADIUSBYMEMBER china:city beijing 1000 km
1) "beijing"
2) "xian"

geohash

geohash 返回一个或多个位置元素的geohash表示

该命令将返回11 个字符串的geohash字符串

# 将2维的经纬度转换为一维的hash字符串
127.0.0.1:6379> geohash china:city beijing shanghai
1) "wx4fbxxfke0"
2) "wtw3sj5zbj0"

geo底层的原理其实就是zset,我们可以使用zset命令来操作geo

# 查看地图中的所有元素
127.0.0.1:6379> zrange china:city 0 -1
1) "chongqing"
2) "xian"
3) "shenzhen"
4) "hangzhou"
5) "shanghai"
6) "beijing"

# 使用zrem来移除位置元素
127.0.0.1:6379> zrem china:city chongqing
(integer) 1

hyperloglogs 基数存储

什么是基数?-不重复的元素,可以接受误差

A{1,3,5,7,8,7},B{1,3,5,7,8}

简介

Redis2.8.9版本就更新了Hyperloglog数据结构

Redis Hyperloglog基数统计的算法

优点:占用内存是固定的,2^64不同的元素的技术,只需要废12kb内存!如果要从内存角度来比较的话Hyperloglog首选!

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

传统的方式,set保存用户的id,然后就可以统计set中的元素数量作为标准判断

这个方式如果保存大量的用户id,就会比较麻烦,我们的目的是为了计数,而不是保存用户id

0.81%错误率!统计UV任务,可以忽略不计的

# 添加一个hyperloglog ,重复则替换
127.0.0.1:6379> pfadd mykey a b c d e f g
(integer) 1
# 统计一个key中有多少元素
127.0.0.1:6379> PFCOUNT mykey
(integer) 7
127.0.0.1:6379> pfadd mykey2 a b c d e f g h i j k l m n
(integer) 1
127.0.0.1:6379> PFCOUNT mykey2
(integer) 14
# 合并两个hyperloglog,求并集
127.0.0.1:6379> PFMERGE mykey3 mykey mykey2
OK
127.0.0.1:6379> PFCOUNT mykey3
(integer) 14

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

bitmaps 位存储

位存储

统计疫情感染人数:01010110;统计用户信息,活跃,不活跃,登录,不登录,都可以使用Bitmaps!

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

365天=365bit 1字节=8bit 46个字节左右

测试

使用bitmap来记录 周一到周日的打卡!

周一(0):1;周二(1):0;周三(2):1;周四(3):0

# 存储对应的值
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 1
(integer) 0
127.0.0.1:6379> setbit sign 3 0
(integer) 0

# 查看对应存的值
127.0.0.1:6379> getbit sign 2
(integer) 1

# 统计打卡的天数 (为1)
127.0.0.1:6379> bitcount sign
(integer) 2

事务

Mysql:ACID!一致性,原子性…(要么同时成功要么同时失败(原子性))

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

一次性、顺序性、排他性!执行一些列的命令!

------队列 set set set 执行------

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

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

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 k1
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
# 执行事务
127.0.0.1:6379> exec
1) OK
2) OK
3) "v1"
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)

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

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 k3 v3
QUEUED
# 错误的命令,编译提示错误
127.0.0.1:6379> getset k3
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379> set k4 v4
QUEUED
# 所有命令都不会被执行
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.

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

127.0.0.1:6379> multi
OK
127.0.0.1:6379> incr k1
QUEUED
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> get k4
QUEUED
# 虽然第一天命令报错了,但是其他命令依旧可以正常使用
127.0.0.1:6379> exec
1) (error) ERR value is not an integer or out of range
2) OK
3) "v4"
127.0.0.1:6379> get k4
"v4"

监控 --乐观锁(watch 监控)

悲观锁:

  • 很悲观,什么时候都会出问题,无论什么时候都会加锁

乐观锁:

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

redis 监控测试

127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set out 0
OK
# 监控money对象
127.0.0.1:6379> watch money 
OK
# 期间数据没有发生变动,这时候就正常执行成功
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> INCRBY out 10
QUEUED
127.0.0.1:6379> DECRBY money 10
QUEUED
127.0.0.1:6379> EXEC
1) (integer) 30
2) (integer) 70

# 线程1进行监控
127.0.0.1:6379> watch money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> DECRBY money 10
QUEUED
127.0.0.1:6379> INCRBY out 10
QUEUED
# 执行之前,另一个线程修改了我们的值,这时候,事务命令执行失败
127.0.0.1:6379> exec
(nil)


# 线程2进行插队执行修改
127.0.0.1:6379> get money
"70"
127.0.0.1:6379> set money 1000
OK

如果发现事务执行失败,unwatch解除监控,重新监控获取最新值即可

jedis

什么是jedis?jedis是redis官方推荐的Java连接开发工具!

p23-p25

SpringBoot整合

Redis.conf详解

单位

单位

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TBPBuKzG-1646576766195)(http://h9x14s4c.xyz/img/202112271452388.png)]

配置文件unit单位对大小写不敏感

包含

包含

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Frls1lIA-1646576766196)(http://h9x14s4c.xyz/img/202112271453736.png)]

可以包含多个配置文件,就好比Spring、Import、include

网络

网络

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fg1UolSx-1646576766196)(http://h9x14s4c.xyz/img/202112271456953.png)]

绑定具体ip(*)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7wHd83bp-1646576766197)(http://h9x14s4c.xyz/img/202112271457541.png)]

是否受保护,端口号

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

通用配置

通用配置

image-20211227145959981

允许后台启动

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hg1VyZ3E-1646576766198)(http://h9x14s4c.xyz/img/202112271502801.png)]

daemonize yes # 以守护进程的方式运行,默认no,需要开启yes(允许后台运行)
pidfile /var/run/redis_6379.pid #如果以后台的方式运行,我们需要指定一个pid进程文件

loglevel notice #日志的级别
logfile "" #日志的文件位置名

databases 16 #默认的16个数据库,数据库的数量
always-show-logo yes #是否显示logo

快照

快照

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zw2ZXFoQ-1646576766198)(http://h9x14s4c.xyz/img/202112271507973.png)]

持久化,在规定的时间内,执行了多少次操作,则会持久化到文件==.rbd==,.aof

redis是内存数据库,如果没有持久化,那么数据断电即失

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZvAlstQ1-1646576766199)(C:/Users/86150/AppData/Roaming/Typora/typora-user-images/image-20211227151352058.png)]

image-20211227151512294

image-20211227151648087

# 如果900秒内,如果至少有1个key进行了修改,我们即进行持久化操作 
save 900 1 
# 如果300秒内,如果至少有10个key进行了修改,我们即进行持久化操作 
save 300 10
# 如果60秒内,如果至少有10000个key进行了修改,我们即进行持久化操作 
save 60 10000

stop-writes-on-bgsave-error yes # 持久化如果出错了,是否还需要继续工作
rdbcompression yes #是否压缩rdb文件(持久化文件)需要消耗cpu资源
rdbchecksum yes #保存rdb文件的时候,进行错误的检查校验!

 dir ./ #rdb文件保存的目录

主从复制

主从复制

image-20211227151754694

安全

安全

image-20211227151855663

密码默认为空

可以在这里设置密码

config get requirepass #获取redis的密码
config set requirepass "123456" #设置redis密码
auth 123456 #使用密码登录,校验密码

客户端

客户端

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Iwmh3vGh-1646576766202)(http://h9x14s4c.xyz/img/202112271522715.png)]

image-20211227152540237

maxclients 10000 #设置能连接上redis的最大客户端的数量
maxmemory <bytes> #redis配置最大的内存容量

maxmemory-policy noeviction #内存到达上限之后的处理策略#移除一些过期的key#报错 
    redis.conf中的默认的过期策略是 volatile-lru
    maxmemory-policy 六种方式
    1、volatile-lru:只对设置了过期时间的key进行LRU(默认值) 
    2、allkeys-lru : 删除lru算法的key   
    3、volatile-random:随机删除即将过期key   
    4、allkeys-random:随机删除   
    5、volatile-ttl : 删除即将过期的   
    6、noeviction : 永不过期,返回错误
    
    

aof配置(持久化配置)

aof配置(持久化配置)

image-20211227152933928

image-20211227153042059

appendonly no #默认不开启aof模式,默认是使用rdb方式持久化,在大部分所有的情况下,rdb完全够用
appendfilename "appendonly.aof" #持久化的文件名字 

appendfsync always # 每次修改都会sync,消耗性能!
appendfsync everysec # 每秒执行一次sync,可能会丢失这1s的数据!
appendfsync no # 不执行sync同步,这个时候操作系统自己同步数据,速度最快!

redis持久化

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

rdb(redis database)

什么是rdb

在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是行话讲的Snapshot快照,它恢复时是将快照文件直接读到内存里。

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

image-20211227163732708

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

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

触发机制

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

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

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

备份就会自动生成一个dump.rdb

如何恢复rdb文件!

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

2、查看需要存放的位置

127.0.0.1:6379> config get dir
1) "dir"
2) "C:\\Program Files\\Redis" # 如果这个目录下存在dump.rdb文件,启动就会自动恢复其中的数据

优点:

1、适合大规模的数据恢复!

2、对数据的完整性不高!

缺点:

1、需要一定的时间间隔进程操作,如果redis意外宕机了,这个最后一次修改的数据就没有了。

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

aof (append only file)

aof 是什么

追加文件

image-20211227163715864

将我们的所有命令记录下来,history,恢复的时候就把这个文件全部再执行一遍!

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

aof保存的是appendonly.aof文件

append

默认是不开启的,我们需要手动进行配置!只需要将appendonly 改为yes就开启了aof!

重启,redis就生效了

如果这个aof文件有错,这时候redis是启动不起来的。我们需要修复这个aof文件,redis给我们提供了一个工具redis-check-aof --fix

image-20211227163149473

如果文件正常,重启就可以直接恢复了。

重写规则说明

aof默认就是文件的无线追加,文件会越来越大

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T4fHuVje-1646576766205)(http://h9x14s4c.xyz/img/202112272246773.png)]

如果aof文件大于64m,太大,那么redis会fork一个新的进程来将我们的文件进行重写

优点和缺点:

优点:

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

2、每秒同步一次,可能会对视一秒的数据

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

缺点:

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

2、aof运行效率也要比rdb慢,所以我们redis默认的配置就是rdb持久化!

扩展

1、rdb持久化方式能够在指定的时间间隔内对你的数据进行快照存储

2、aof持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,aof命令以redis协议追加保存每次写的操作到文件末尾,redis还能对aof文件进行后台重写,使得aof文件的体积不至于过大。

3、只做缓存,如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化

4、同时开启两种持久化方式

  • 在这种情况下,当redis重启的时候会优先载入aof文件来恢复原始的数据,因为在通常情况下aof文件保存的数据集要比rdb文件保存的数据集要完整
  • rdb的数据不实时,同时使用两者时服务器重启也只会找aof文件,那要不要只使用aof呢?作者建议不要,因为rdb更适合用于备份数据库(aof在不断变化不好备份),快速重启,而且不会有aof可能潜在的bug,留着作为一个万一的手段。

5、性能建议

  • 因为rdb文件只用作后备用途,建议只在slave上持久化rdb文件,而且只要15分钟备份一次就够了,只保留save 900 1 这条规则。
  • 如果enable aof 好处是在最恶劣的情况下也只会丢失不超过两秒数据,启动脚本较简单只load自己的aof文件就可以了,代价一是带来了持续的IO,二是aof rewrite的最后将rewrite过程中产生的新数据写到新文件造成阻塞几乎是不可避免的。只要硬盘许可,应该尽量减少aof rewrite 的频率,aof重写的基础大小默认值64m太小了,可以设到5G以上,默认超过原大小100%大小重写可以改到适当的数值。
  • 如果不enable aof,仅靠master-slave repllcation 实现高可用性也可以,能省掉一大笔IO,也减少了rewrite时带来的系统波动。代价是如果master/slave同时倒掉,会丢失十几分钟的数据,启动脚本也要比较两个master/slave中的rdb文件,载入较新的那个,微博就是这种架构。

Redis发布订阅

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

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

订阅/发布消息图

命令

序号命令及描述
1[PSUBSCRIBE pattern pattern …] 订阅一个或多个符合给定模式的频道。
2[PUBSUB subcommand argument [argument …]] 查看订阅与发布系统状态。
3PUBLISH channel message 将信息发送到指定的频道。
4[PUNSUBSCRIBE pattern [pattern …]] 退订所有给定模式的频道。
5[SUBSCRIBE channel channel …] 订阅给定的一个或多个频道的信息。
6[UNSUBSCRIBE channel [channel …]] 指退订给定的频道。

测试

C:\Users\86150>redis-cli
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> SUBSCRIBE naughty # 设置一个sub(订阅端频道)等待推送
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "naughty"
3) (integer) 1
1) "message" # 消息
2) "naughty" # 那个频道的消息
3) "hello naughty" # 消息的具体内容
C:\Users\86150>redis-cli
127.0.0.1:6379> PUBLISH naughty "hello naughty" # 将消息推送到sub(发送端)推送消息
(integer) 1

原理

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

redis通过publish、subscribe和psubscribe等命令实现发布和订阅功能。

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

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

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

原理图

复杂的场景会使用到消息中间件:MQ

Redis主从复制

环境配置

只配置从库,不用配置主库

127.0.0.1:6379> info replication #查看当前库信息
# Replication
role:master # 角色
connected_slaves:0 # 从机连接数
master_replid:357827a033cdaaaa7d44a9a8792d326d4a2ffe1c
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

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

1、端口

2、pid名字

3、log文件名字

4、dump.rdb名字

image-20220107153311385

默认情况下,每台redis服务器都是主节点;我们一般只配置从机就好了,认老大!

slaveof 127.0.0.1 6379 # 成为127.0.0.1下的6379端口的redis从机 (认老大)

注意:真正生产环境应该在配置环境中进行修改

image-20220107154219053

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

复制原理

slave 启动成功连接到master后会发送一个sync同步命令

Master接到命令,启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕之后,master将传送整个数据文件到slave,并完成一次完全同步。

全量复制:而slave服务在接收到数据库文件数据后,将其存盘并加载到内存中。

增量复制:master继续将新的所有收集到的修改命令依次传给slave,完成同步

但只要重新连接master,一次完全同步(全量复制)将被自动执行

slaveof no one取消从机模式,成为主机模式

哨兵模式

(自动选取老大)

主从切换技术的方法是:当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费事费力,更多时候我们优先考虑哨兵模式。redis从2.8开始正式提供了sentinel(哨兵)架构来解决这个问题

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

image-20220107160907540

image-20220107161048269

image-20220107161147990

image-20220107162527167

image-20220107162637677

Redis缓存穿透和雪崩

面试高频,工作常用

缓存穿透

概念

缓存穿透的概念很简单,用户想要查询一个数据,发现redis内存数据库没有,也就是缓存没有命中,于是向持久层数据库查询。发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中,于是都去请求了持久层数据库。这会给持久层数据库造成很大的压力,这时候就相当于出现了缓存穿透

解决方案

布隆过滤器

image-20220107163900529

缓存空对象

image-20220107164042347

image-20220107164107728

缓存击穿

概述

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

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

解决方案

设置热点数据永不过期

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

加互斥锁

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

缓存雪崩

概念

缓存雪崩,是指在某一个时间段,缓存集中过期失效。或者redis集群宕机

image-20220107165310253

解决方案

image-20220107165607838

启动成功连接到master后会发送一个sync同步命令

Master接到命令,启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕之后,master将传送整个数据文件到slave,并完成一次完全同步。

全量复制:而slave服务在接收到数据库文件数据后,将其存盘并加载到内存中。

增量复制:master继续将新的所有收集到的修改命令依次传给slave,完成同步

但只要重新连接master,一次完全同步(全量复制)将被自动执行

slaveof no one取消从机模式,成为主机模式

哨兵模式

(自动选取老大)

主从切换技术的方法是:当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费事费力,更多时候我们优先考虑哨兵模式。redis从2.8开始正式提供了sentinel(哨兵)架构来解决这个问题

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

[外链图片转存中...(img-SvqnUTZE-1646576766208)]

[外链图片转存中...(img-K6EOaWMS-1646576766209)]在这里插入图片描述

[外链图片转存中...(img-gDAJ18Ad-1646576766209)]
[外链图片转存中...(img-z7HyrQh0-1646576766209)]

Redis缓存穿透和雪崩

面试高频,工作常用

缓存穿透

概念

缓存穿透的概念很简单,用户想要查询一个数据,发现redis内存数据库没有,也就是缓存没有命中,于是向持久层数据库查询。发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中,于是都去请求了持久层数据库。这会给持久层数据库造成很大的压力,这时候就相当于出现了缓存穿透

解决方案

布隆过滤器

[外链图片转存中...(img-HlilzMHo-1646576766210)]

缓存空对象

[外链图片转存中...(img-0x9yJmEi-1646576766211)]

[外链图片转存中...(img-04a3dan6-1646576766211)]

缓存击穿

概述

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

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

解决方案

设置热点数据永不过期

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

加互斥锁

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

缓存雪崩

概念

缓存雪崩,是指在某一个时间段,缓存集中过期失效。或者redis集群宕机

[外链图片转存中...(img-CBpz7eXv-1646576766211)]

解决方案

[外链图片转存中...(img-LSpQcvxG-1646576766212)]

关注·狂神说,点击传送学习

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Joker.our

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值