redis详解

redis详细介绍

简单介绍

中文网
英文网
redis是单线程/单进程/单实例的,所谓单线程指的是对数据的处理是“顺序执行”的,但redis可以同时多线程处理不同的业务。
redis底层kenrel采用的是epoll(有关epoll的发展历程后续会更新)。
redis类似于mysql的数据库,默认有16个库,分别为0-15,每个库中的数据是相互隔离的。

基础知识

以下先介绍redis相关的基础知识:

  1. 常识 ,磁盘的寻址是ms级别的,内存的寻址是ns级别的,redis是基于内存级别的数据库,所以很快;OS无论读多少,都是最少4K从磁盘中拿出来的,所以读出来的大小一定是4K的倍数。
  2. 关系型数据库,建表时,需要给出schema,字段需要给出数据类型及字节宽度;存储时,是按照行级存储的;关系型数据库的索引,在磁盘中是B+树的结构,在表很大时,增删改查会变慢,从查询的角度来说,单个或少量的查询依旧会很快,但是,并发量很大时磁盘带宽会成为瓶颈。
  3. 缓存memcached的value没有类型的区分,都是string型的,若要存储对象,可以用json字符串,但是,取单个元素时需要把整个json字符串获取回来,然后在IO层面用代码实现;redis的value值有不同的类型,每个类型都带有自己的实现方法;实现了计算向数据移动

redis的安装

前提

  1. OS的版本需要与redis匹配,否则会出现make安装不成功的问题
  2. 安装wget,yum install wget
  3. 需要安装gcc,因为redis时C实现的,yum install gcc
  4. 为了方便管理,安装后最好把可执行程序迁出。
    安装步骤
1.yum install wget
2.cd #回到家目录
3.mkdir soft	#创建soft文件夹,将redis安装在此文件夹中
4.cd soft	#进入此文件夹
5.wget redis路径	#下载redis压缩包
6.tar xf xxx.tar.gz	#解压下载的压缩包
7.cd redis/src	#查看src下目录结构
8.vi README.md	#查看README文件,基本了解相关信息
9.make	#编译redis用
  #需要先安装
	yum install gcc
	make disclean	#make安装有问题时,先clean,再重新安装
10.make	#重新安装make
11.cd src #生成可执行文件
12.cd ..
13.make install 		  	PREFIX=/opt/redis/redis5
14.vi /etc/profile	#配置环境变量
	export REDIS_HOME=/opt/redis/redis5
	export PATH=$PATH:$REDIS_HOME/bin
15.source /etc/profile	#执行配置文件
16.cd utils
17../ install_server.sh	#运行redis

redis数据类型

二进制安全,redis是二进制安全的,具体的编码类型需要事先外界定义好,redis中存储的是字节流/字符流;若是utf-8格式的,可以用redis-cli --raw查看中文字样。
索引,redis中有正向索引/负向索引,正向索引从左至右为0,1,2,……,负向索引从右至左为-1,-2,-3,……。
key的操作,查看value的类型,type:value;查看value的编码,object encoding

String

1.字符串角度

命令描述返回值
set key value设置指定 key 的值SET 在设置操作成功完成时,才返回 OK
get key获取指定 key 的值返回 key 的值,如果 key 不存在时,返回 nil。 如果 key 不是字符串类型,那么返回一个错误。
getrange key start end返回 key 中字符串值的子字符截取得到的子字符串
getset key value将给定 key 的值设为 value ,并返回 key 的旧值(old value)返回给定 key 的旧值。 当 key 没有旧值时,即 key 不存在时,返回 nil
mget key [key …]获取所有(一个或多个)给定 key 的值返回 一个包含所有给定 key 的值的列表
mset key value [key value …]同时设置一个或多个 key-value 对设置成功返回ok
strlen key返回 key 所储存的字符串值的长度字符串值的长度。 当 key 不存在时,返回 0
setnx key value指定的 key 不存在时,为 key 设置指定的值设置成功,返回 1 。 设置失败,返回 0
msetnx key value [key value …]所有给定 key 都不存在时,同时设置一个或多个 key-value 对当所有 key 都成功设置,返回 1 。 如果所有给定 key 都设置失败(至少有一个 key 已经存在),那么返回 0

2.数值角度

命令描述返回值
incr key将 key 中储存的数字值+1返回old值+1之后的值
incrby key +count将key中的value值+count返回old值+1之后的值
incrbyfloat key +count将key中的value值+count(浮点数)返回的值的类型是float

数值使用场景
在并发场景下,为了规避对数据库的事务操作,可以使用redis内存代替,例如:商品秒杀,商品详情页的点赞数,评论数等数据准确性要求不那么高的可以使用。

3.位图角度

命令描述返回值
setbit key offset对 key 所储存的字符串值,设置或清除指定偏移量上的位(bit)指定偏移量原来储存的位
bitcount key start end从start到end设为1的个数设为1的个数
bitop operation destkey key [key …]对一个或多个key进行位元操作(and、or、xor、not)保存到destkey的字符串长度
bitpos key bit [start] [end]返回key中第一个被设为1或0的位value中第一个被设为1或0的位

位图说明

位图每一位非0即1;setbit k1 1 1,长度为1;setbit k1 8 1,长度为2;
位图使用场景

  1. 用户系统,统计随即窗口用户的登录天数(用户为key)
 	setbit sean 1 1	#sean第一天登录
	setbit sean 7 1	#sean第7天登录
	setbit sean 364 1	#sean第364天登录
	strlen sean	#sean总共登录的天数
	bitcount sean -2 -1	#sean最后两天登录的次数
  1. 电商系统做活动送礼,统计随即窗口的活跃用户数(时间为key)
 	setbit 20200603 1 1	#用户1在20200603登录
	setbit 20200604 1 1	#用户1在20200604登录
	setbit 20200604 7 1	#用户7在20200604登录
	bitop or destkey 20200603 20200604	#20200603与20200604登录的用户记录
	bitcount destkey 0 -1	#返回20200603与20200604登录的用户数

list

list是有序的,这里的有序指的是按插入顺序有序。

指令

指令描述返回值
lpop/rpop key移除并返回列表左边/右边的第一个元素列表左边/右边的第一个元素,key不存在时返回nil
lpush/rpush key v1 v2 …给列表中插入一个或多个元素,key不存在时报错列表元素的个数
ltrim key start end删除start — end范围外的元素执行成功返回true
lrange key start end取出start — end之间的元素列表,区间的元素
lindex key index通过索引获取列表中的元素下标为index的元素
linsert key before/after oldV newV在oldV之前/后插入newV若插入成功,返回list大小;若未找到oldV,返回-1;若未找到key,返回0
blpop/brpop list1 list 2 … listn timeout移除并获取列表的第一个元素若列表中有元素,则返回第一个元素,若没有元素,则阻塞直至有元素,或等待时间超过timeout返回nil

使用场景

  1. 作为使用,用lpush/lpop或者rpush/rpop同向指令,实现先进后出;
  2. 作为队列使用,用lpush/rpop或者rpush/lpop逆向指令,实现先进先出;
  3. 作为数组使用,用ltrim/lrange/lindex/linsert before/linsert after等指令,实现数组的插入/按下标获取值;
  4. 作为阻塞队列使用,用blpop/brpop实现单播阻塞队列,也是FIFO原则。

hash

hash类型的map(k,v)的形式存储的;
也可以对filed中的数字进行计算。

指令描述返回值
hmset key field1 value1 field2 value2 … fieldn valuen同时将多个filed value 存入hash表中 (覆盖原有值)若执行成功,返回ok
hmget key field1 field2 … fieldn取出hash表中,一个或多个给定字段的值给定字段的值,若没有则返回nil
hset key field valuehash表中字段赋值赋值成功,若原来没有则返回1,若原来有被覆盖返回0
hget key field获取执行字段的值返回指定字段的值,若不存在,返回nil
hgetall key返回key的所有字段和值若不存在返回nil,存在返回字段和字段值
hexists key field指定字段是否存在存在返回1,不存在返回0
hincrby(float) key field increment字段加上指定值计算之后的值
hlen key获取hash表中字段的数量返回数量,不存在时返回0
hdel key field1 … fieldn删除指定字段删除成功的字段数量
hvals key返回所有字段的值所有值的表,不存在时返回nil
hkeys key返回所有字段名字段名的表,不存在时返回nil
hsetnx key field value为不存在的字段赋值成功返回1,已存在返回0

指令

使用场景

  • 可以用来实现电商平台的商品详情页,将商品信息(点赞数/收藏数/详细信息等)作为对象放入filed中存储,方便展示。

set

set是无序的,同时也做了去重。
指令

指令描述返回值
sadd key val1 … val2将元素添加到集合中添加成功的数量
srandmember key count返回集合中的随机元素包含count元素的数组 count为正数:去重,个数不能保证满足;count 为负数,个数保证满足,未去重;
smembers key获取key中的所有元素,不推荐使用返回所有元素
scard key获取集合中元素的数量集合的数量,不存在时返回0
srem key v1 … vn移除集合中的指定元素被成功移除的元素个数
sismember key value判断成员元素是否是集合的成员是,返回1,否或不存在,返回0
SSCAN key cursor [MATCH pattern] [COUNT count]迭代集合中键的元素数组列表 eg:sscan myset1 0 match h*
spop key随机移除集合中的一个元素被取出的随机元素,若未空,则返回nil
smove oldkey newkey value将oldkey中的元素value移动到newkey的集合中移动成功,返回1,未移动,返回0
sdiff/sdiffstore (deskey) key key1 … keyn获取给定集合的差集,xxxstore是将结果存储在deskey中差集元素列表;xxx时返回deskey元素的个数
sinter/sinterstore (deskey) key key1 … keyn获取给定集合的交集交集元素列表
sunion/sunionstore (deskey) key1 key2 … keyn获取给定集合的并集并集元素列表

使用场景

抽奖的情况(随机):

  1. 礼品数少于人数的情况,用srandmembers实现,参数用正数,返回已经去重的结果集,且数量不会超过已有的值,实现不是所有人都可以领到奖品;
  2. 礼品数多余人数的情况,用strandmembers实现,参数用负数,返回的结果有重复值,且数量一定满足,实现所有奖品都被发放;
  3. 公司年会的抽奖活动,用spop实现,选中并从列表中删除该元素,实现同一员工不会重复中奖的情况。

zset(sorted set)

  1. zset是有序的,是按照score排序的,sorce是排序的规则。
  2. zset在物理内存中是左小右大的,不会随命令的执行而发生变化。
  3. 排序的底层是用跳跃表实现的,牺牲一定的空间换取时间,跳跃表的增删改查不一定是最优的,只是平均值相对优先,跳跃表层级的创建是随机的。

指令

指令描述返回值
zadd key score1 value1 … scoren valuen将一个或多个成员元素及其分数值加入到有序集当中添加成功数量
zlexcount key min max获取指定区间成员的数量指定区间成员的数量
zrevrank key member有序集中成员的排名有则返回排名(从0开始),无则返回nil
zincrby key increment membermember的score+increment增加后的score
zremrangbyrank key start stop移除有序集合中指定区间的元素被移除成员的数量
zremrangbyscore key min max移除指定分数之间的元素被移除成员的数量
zinterstore取交集目标key中成员的数量
zunionstore取并集目标key中成员的数量

使用场景

  1. 取出排名的前几位,后几位。

进阶使用

管道

类似于linux的管道,需要安装nc,yum install nc;若需要将多个命令一起发给服务端时,各命令间用换行符连接。

发布/订阅

指令

publish/subscribd #有订阅才可以发布成功

使用场景

  • 直播室消息聊天,可以监听聊天消息
  • 实时聊天信息,对于实时性的消息,可直接用发布订阅模式;对于历史性的消息,可以用redis的缓存技术,将热数据(3天内)存储下来,可用zset存储,历史数据作为全量数据存储到数据库中。

事务

redis 的事务不同于mysql的事务,redis事务是不支持回滚的;
redis 事务的实现,是将待执行的一组命令放在同一个事务中,然后触发执行。

实现过程

  1. multi开启事务,然后把要执行的命令放入队列中,注意此时不是立即执行的。
  2. exec触发事务队列中的命令执行,执行顺序不是按照开启事务的先后顺序,而是按照该指令触发的先后顺序决定的,在执行的过程中,只执行正确的指令。
  3. watch指令可以观察key在事务执行中的变化,此处用的是乐观锁的机制。

布隆过滤器

安装

1、redis.io ——> modules	#访问redis 的官网,点击modules
2、访问redisBloom的github
3、wget *.zip	#下载redisBloom的压缩包
4、yum install unzip	#安装解压zip文件的工具
5、unzip *.zip	#解压下载后的安装包
6、cd RedisBloom-master	#进入解压后的文件
7、make	#编译
8、cp redisbloom.so /opt/gaoyutao/redis5/redisbloom.so	#拷贝可执行文件到指定目录,方便管理
9、cd /opt/gaoyutao/redis5/	#进入可执行文件所在目录
8、redis-server --loadmodule /opt/gaoyutao/redis5/redisbloom.so	#启动
--------成功--------
redis-cli 	#访问

实现原理
把数据库中有的数据用bitmap(位图)表示,client访问穿透了不存在的也要做标记,value值标为0,尽可能地避免再次穿透;client请求时,先经过映射函数,但是这种操作不是绝对的,是一种概率事件,有时候即使数据库有也会被误标记。
布隆过滤器是为了处理缓存穿透问题。

实现方式

  • client中实现bloom,redis server不做处理
  • client中包含bloom算法,bitmap存储在redis server中
  • client中不做处理,redis server中集成bloom

指令

  • bf.add key value 插入
  • bf.exists 判断是否存在

弊端

  • 只能增加不能删除,可以设置空的key
  • 可以采用布谷鸟过滤器

作为缓存和数据库的区别

缓存和数据库的侧重点

  • 由于内存容量有限,缓存中存放的是热数据,随着业务的累积,缓存中的数据是变化的,缓存注重的是速度。
  • 数据库中存储的是全量的数据,注重的数数据的持久性和全量。

缓存数据的清除

1.key过期策略

key过期清除的方式是由业务推导实现的。

1.1 实现方式

set key value expire xxx	#设置有效期
set key value expireat xxx	#定时

1.2 判定原理

判定key过期有两种方式,一种是在访问的时候,发现key已经过期,清除掉(主动判定);另一种是redis server会有轮询机制,周期性地查看key是否过期,每次查看总量的25%,也就是说,最多有25%的过期key会被清除(被动判定)。

2.内存淘汰策略,冷数据回收
内存淘汰冷数据是随着业务的运转需要做的。

在redis的配置文件中,可以配置redis内存容量,指定maxmemory,范围设为1G——10G,超出内存时,可以通过maxmemory-policy指定回收策略。

回收策略

策略描述
noeviction默认策略,内存不足时,新写入操作会报错。不推荐。
allkeys-lru内存不足时,在键空间中,移除最近最少使用的 Key。推荐
allkeys-random内存不足时,在键空间中,随机移除某个 Key。不推荐。
volatile-lru内存不足时,在设置了过期时间的键空间中,移除最近最少使用的 Key。在redis既做缓存又做持久化存储时可以采用。
volatile-random内存不足时,在设置了过期时间的键空间中,随机移除某个 Key。不推荐。
volatile-ttl内存不足时,在设置了过期时间的键空间中,有更早过期时间的 Key 优先移除。不推荐。如果没有对应的键,则回退到noeviction策略。

redis作为缓存常见问题

问题描述解决方案
缓存击穿在高并发的场景下,某个key过期被清理,造成同一时间大量并发到达数据库使用互斥锁,用setnx设置一个key,设置成功再去操作db,失败则get key,直至获得锁。
缓存穿透client查询业务中没有的数据,直接访问数据库布隆过滤器
缓存雪崩大量的key在同一时间过期失效,间接造成该事件大量访问到达数据库1.随即设置过期时间(时点性无关);2若时间要求固定时(eg:0点更新数据),业务层加判断,依赖击穿方案(分布式锁)

互斥锁问题:若当前访问发生故障,未释放锁,会造成死锁,需要设置锁的过期时间;设置了锁的过期时间之后,若锁超时,但任务未执行完,会出现拥塞,用多线程来实现,A线程操作db,B线程监控锁是否超时,同步更新锁过期时间。

持久化(单机)

RDB

RDB是对当前数据做一个快照/副本,是具有时点性的,即保存的是某一时间点的数据;

1.实现过程
实现过程是非阻塞的,不会影响server端对外提供服务,会重新启动一个子线程来完成数据的落地,由父线程响应client。
原理是,在linux中,子线程与父线程是数据隔离的,数据不会相互影响,能够有效地保障时点的正确性。fock创建子进程,而且fock的速度快,空间小;copy on write 写时复制,在创建子进程时不会进行复制,实现数据的隔离。
实现方式是用save/bgsave指令,save指令一般不会使用,只有在关机时会触发。bgsave的也是配置save标识,配置方式为:

save 900 1
save 300 10 
save 60 10000

dbfilename.dump.rdb 	#(文件名)
dir /var/lib/redis/6379		#(文件路径)

2.优势

  • 数据恢复快,恢复的过程类似java的序列化

3.弊端

  • RDB不支持拉链,只能手动维护文件,下一次备份会覆盖原文件
  • RBD丢失的数据量比较多,会丢失一个时间点的数据

AOF

AOF,全称是append only file,备份的是增量数据,把redis的写操作记录到文件中。
若AOF与RDB同时开启,redis server重启后只会选择用AOF恢复数据。

1.优势

  • 数据丢失少

2.弊端

  • AOF文件的体量过大,恢复慢

3.发展

before 4.0,执行bgrewrite(重写),会把重复指令合并,可以抵消的指令删除,例如:

set k1 ooxx;
set k1 xxoo;
set k2 hello;

重写之后,第一条给k1赋值的指令就会被删除,最后生成的是纯指令的日志文件。

after 4.0,执行bgrewrite(重写),server中原有的老数据会触发ROB,落地到aof文件中,之后的增量指令继续存到aof文件中,此时的日志文件是aof与RDB的混合。

持久化的实现

appendonly yes
appendfilename “appendonly.aof"
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
appendfsync always
appendfsync everysec
appendfsync no
aof-use-rdb-preamble yes

此处,appendfsync的配置有3种情况,分别是alwayseverysecno,第一种数据最可靠,第二种会丢失~1s的数据,第三种丢失的数据最多,为1个buf;原因是redis是内存数据库,每次写操作都会触发IO,需要执行flush把数据持久化到磁盘,alway是立刻flush,everysec是每秒flush一次,no是等缓存中数据满了一个buf才会flush。

集群

单机问题

  • 会发生单点故障,若server发生故障,会造成整个业务的下线。
  • 会受内存容量的影响,数据量过大,会出现内存不够的问题。
  • server会存在访问压力,所有client端都会访问一台server,响应时间会非常慢。

redis模式

推导

采用一变多的形式,选择多台server,但是,多台server需要考虑CAP原则。
**数据一致性(C)**可以采用以下三种形式:

  • 同步阻塞,具体是指所有节点同步阻塞,直到数据一致,这是一种强一致性,会破坏可用性。
  • 异步,是弱一致性的,适用于可容忍一部分数据丢失的场景。
  • 中间件同步阻塞,确保数据的最终一致性,此种方式是利用中间件(如kafka)的可靠性、高效性来实现数据的最终一致,中间过程也会有数据不一致的现象出现。

分区容忍性(P)

  • 主机故障后,备机会采用投票的形式选出新主,但是,投票通过数需要过半,否则,势力范围不够,会出现脑裂即网络分区的现象。

主HA(高可用)(A)

  • 给主添加备机,组成集群,主故障以后,从备机中选一台做主机,继续提供服务;集群一般会选择奇数台,因为,相比较下一个偶数来说,风险相同,但是,成本会更高。如:5台和6台的话,都是有3台投票通过,就可以选出主,很明显6台的成本高于5台。
主从复制(master/slave)

主从复制采用的是异步非阻塞的方式来实现数据一致性,从挂机后重新启动,重新追随主以后,就会自动同步主的数据;

配置

  • 启动时通过replicaof去追随主,用replicaof none放弃追随。
  • 配置文件配置
reolica-server-stale-data yes  	#开启让别人查
replica-read-only yes		#备机是否只支持查询(只读模式)
repl-diskkess-sync no		#是否通过磁盘传输RDB文件
repl-backlog-size 1mb		#no 通过网络传输
min-replicas-to-write	#增量复制
min-replicas-max-lag	#规定最少几个写成功

解决问题

  • 可以解决单点故障的问题,也可以通过读写分离的方式,来缓解主的访问压力,但是没有解决容量问题,因为主备的数据都是全量复制的。

弊端

  • 主的故障问题需要人工去维护。
哨兵(sentinel)

sentinel的作用是监控redis-server的运行状态,从而实现故障的自动转移,所谓故障自动转移是指,sentinel监测主机故障之后,从会自动通过投票方式选出新主,其他salve追随,不需要手动去维护。
sentinel之间的通信是通过redis的发布/订阅方式来实现的。
sentinel依旧没有解决内存容量的问题。

配置

redis-server ./6379.conf --sentinel
redis-sentinel	#启动哨兵

psubscribe * 	#可查看哨兵之间发布的消息

#哨兵配置文件(redis源码目录)
port 26379

sentinel monitor mymaster 127.0.0.1 6381 2

集群

集群是redis的分布式数据库的方案。

1.数据分布
若数据可以拆分,则在client按照业务、逻辑等将数据拆分,若数据不可拆分,需要用sharding分区的方式进行拆分、共享,以此来解决容量有限的问题

sharding分片
倾向于用redis做缓存,不适用于数据库,每个节点存储的数据都是完整的。

  • hash取模,用redis的台数作为模数。这种方式的弊端是,模数是固定的,若redis台数变化的话,对应数据的存放就会造成全局洗牌,非常不利于分布式下的扩展性,一般不常用。
  • 随机(random)分片。可以用于消息队列的场景,指定client写数据操作同一个key,其他client消费。这种分片方式不适用于其他业务。
  • hash分片(一致性hash算法(kemata))。此种方式是将data、node都参与到映射算法中。
    • 实现方式是先规划一个环形hash环,然后node先进行映射算法,得到映射点,新增data时找到距离最近的node节点,hash环上被映射到的node节点是物理节点,剩下的点是虚拟节点,设定虚拟节点是为了解决环上数据倾斜的问题。
    • 优点,可分担其他节点的压力,不会造成节点的全局洗牌;
    • 缺点,造成一部分数据不能命中,会造成一部分请求穿透缓存直接到达数据库,针对这种问题,可以在获取数据的时候,从距离最近的两个node去取,尽可能地提高命中率。

2.代理分区
上述sharding分区方案,分区实现逻辑需要client实现,redis server 的连接成本依旧很高,可以采用proxy,来缓解连接压力。
常用的代理分区主要有,twemproxypredixycodis

3.查询路由方案
client随机地 请求任意一个 Redis 实例,然后由 Redis 将请求 重定向 到 正确 的 Redis 节点。Redis Cluster 实现了一种 混合形式 的 查询路由,但并不是 直接 将请求从一个 Redis 节点 转发 到另一个 Redis 节点,而是在 客户端 的帮助下直接 重定向( redirected)到正确的 Redis 节点。

  • 优点:无主模型,数据按照 hash槽 存储分布在多个 Redis 实例上,可以平滑的进行节点 扩容/缩容,支持 高可用 和 自动故障转移,运维成本低。
  • 缺点:严重依赖 Redis-trib 工具,缺乏 监控管理,需要依赖 Smart Client (维护连接,缓存路由表,MultiOp 和 Pipeline 支持)。Failover 节点的 检测过慢,不如 中心节点 ZooKeeper 及时。Gossip 消息具有一定开销。无法根据统计区分 冷热数据。

API

SpringData Redis

前提

  1. 配置文件redis.conf
  2. 防火墙配置(默认防火墙开启,不允许外界访问端口)
	vi /etc/sysconfig/iptables	#修改防火墙配置文件
 	-A INPUT -m state --state NEW -m tcp -p tcp --dport 6379 -j ACCEPT
	service iptables restart	# 重启防火墙

jedis

线程不安全

lettuce

支持同步异步

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值