本章概要
- Redis基础介绍
- Redis命令介绍
- Redis相关配置介绍
- Redis数据持久化
- Redis主从复制
- Redis监控组件sentinel介绍
- Redis Cluster
- Redis读写分离介绍
1、Redis基础介绍
- 官网:redis.io
- 中文网站:http://www.redis.cn
- 用途:数据库、缓存、消息队列
- 支持的数据类型:字串(包含数字和字符串)、映射(关联数组)、列表、集合、有序集合(支持范围查询)
- 还支持:位图、hyperloglogs、地理位置索引(支持辐射查询)
- 其他功能:内建复制功能、支持Lua脚本、基于LRU缓存项的淘汰算法、支持事务、不同级别基于磁盘的数据持久化、高可用性(监控主从复制集群,保证主节点的可用性)、基于redis集群自动分区
- 单进程:单进程处理n个请求
- 数据持久化:
snapshotting 快照持久化
AOF:Append Only File
2、Redis命令介绍
redis客户端命令
- epel仓库安装
yum -y install redis - 监听端口:6379
- C/S架构:
C: redis-cli
S: redis-server - redis-cli命令:
Usage: redis-cli [OPTIONS] [cmd [arg [arg ...]]]
-h HOST, 127.0.0.1
-p PORT, 6379/tcp
-a PASSWORD
-n DBID(数据库id号),id范围:0-15
与Connection相关命令:
help @connection 查看帮助信息
AUTH \输入认证密码
ECHO \用于探测服务器存活状态
PING 用于探测服务器存活状态
QUIT 关闭连接,退出数据库
SELECT dbid 切换数据库 - 清空数据库:
FLUSHDB:Remove all keys from the current database 清空当前数据库;
FLUSHALL:Remove all keys from all databases 清空所有数据库;
redis-cli 连接redis的客户端命令,需要指定redis的地址和端口,如果不指定,默认为链接本地的6379端口
示例:
[root@centos7 ~]# redis-cli
127.0.0.1:6379>
进入redis后,使用help命令查看帮助
键入help命令后,空一格敲tab键可以查看针对不同配置的帮助信息,敲一次tab键会自动变化为不同的数据类型
如:第一次敲tab键127.0.0.1:6379> help @generic 通用格式
再敲一次tab键为:127.0.0.1:6379> help @string 字串格式
redis数据结构相关配置命令
- @generic 常用格式
DEL 删除数据库
EXISTS 查看键是否存在
KEYS 基于模式查询某个键
TTL 获取键的剩余可用市场
MOVE 把键从一个数据库移到其他数据库
RENAME 为某个键重命名
SELECT 切换数据库 - @string 列表
SET 设置一个键的值
GET 获取一个键的值
EXISTS 判断一个键是否存在
INCR 是否支持自增
DECR 是否支持自减
SETNX 当键的值不存在时,设置一个键的值
SETEX 设置一个键的过期时间
INCRBYFLOAT
MGET 一次获取多个键
MSET 一次设置多个键
示例:
127.0.0.1:6379> set name tom 设置一个键name,值为tom
OK
127.0.0.1:6379> keys * 查看所有键
1) "name"
127.0.0.1:6379> get name 获取键值
"tom"
127.0.0.1:6379> set name jerry ex 10 #把键name的值重命令为jerry并且过期时间为10s
OK
127.0.0.1:6379> get name #获取键
"jerry"
127.0.0.1:6379> get name #过10s后再查看键值,发现已经失效
(nil)
127.0.0.1:6379> setnx color green #在不知道是否存在某个键时,为了防止设置键时产生冲突,使用setnx命令,仅在该键不存在时才会设置
(integer) 1
127.0.0.1:6379> get color #获取键值
"green"
127.0.0.1:6379> setnx color red #再次设置键color的值
(integer) 0
127.0.0.1:6379> get color #获取键值,发现之前的设置不生效,这是应为使用了setnx命令,color键已经存在,因此上一个命令不生效
"green"
127.0.0.1:6379> set counter 0 #设置一个计数器,从0开始
OK
127.0.0.1:6379> incr counter #使用incr设置该计数器为自增
(integer) 1
127.0.0.1:6379> incr counter #每一次查看都会自动增加
(integer) 2
127.0.0.1:6379> incr counter
(integer) 3
127.0.0.1:6379> decr counter #使用decr设置该计数器为自减
(integer) 3
127.0.0.1:6379> decr counter
(integer) 2
127.0.0.1:6379> decr counter
(integer) 1
- @list 列表
LPUSH
RPUSH
LPOP
RPOP
LPUSHX
RPUSHX
LRANGE 获取列表中元素的范围
LINDEX 获取索引信息
LSET 设置列表中某个键的值
LLEN 判定列表中有多少个元素
LINSERT 在指定位置插入元素
命令说明:
列表实际上是一个数组
color[0] 取列表的第0个键的值
color[1:3] 取列表的第1、第2、第3个键的值
color[:3] 取列表的第0到第3个键的值
对列表中元素的操作:修改已有元素的值、新增元素项、移除元素项
删除操作:删除首部元素、删除尾部元素、删除指定的某个元素
删除首部或尾部的元素叫弹出(POP)
从首部或尾部增加一个元素(增加一个元素作为首部或尾部)叫压入(PUSH)
从左右方向来看,首部为左边,尾部为右边
从首部(左边)压入为LPUSH,从尾部(右边)压入为RPUSH
从首部(左边)弹出为LPOP,从尾部(右边)弹出为RPOP
此时,如果从左侧压入,从右侧弹出我们称之为队列(先进先出),反方向(右进左出)也是如此;如果从左侧压入,从左侧弹出我们称之为栈(先进后出或后进先出),方向(右进右出)也是如此。
如:
127.0.0.1:6379> LPUSH weekdays Sat Fri Thu Wed Tue Mon Sun #从左侧压入数据,第一个进入的数据为Sat,最后一个进入的数据为Sun,列表中最左侧的元素为Sun,最右侧元素为Sat
(integer) 7
127.0.0.1:6379> LINDEX weekdays 0 #从列表中按照索引获取数据
"Sun"
127.0.0.1:6379> LPOP weekdays #从左侧弹出一个元素为Sun
"Sun"
127.0.0.1:6379> LINDEX weekdays 0 #查看weekdays列表的索引
"Mon"
127.0.0.1:6379> RPUSH weekdays Sun #从列表右侧压入元素Sun
(integer) 7
127.0.0.1:6379> LLEN weekdays #查看weekdays列表的元素个数
(integer) 7
127.0.0.1:6379> LINDEX weekdays 6 #查看列表第6个元素(列表中元素从0开始,因此第6个为从右侧压入的Sun)
"Sun"
- @set 集合
SADD 向集合中添加元素
SCARD 获取元素个数
SDIFF 做叉集
SDIFFSTORE 做叉集后存储结果
SINTER 做交集
SINTERSTORE 做交集后存储结果
SISMEMBER 判定内部是否有某个成员
SMEMBERS 获取所有成员
SMOVE 把成员从一个数据库移动到另外一个数据库
SPOP 弹出成员(弹出是随机的某个成员)
SRANDMEMBER 随机获取一个成员
SREM 移除某个成员(移除是指定的成员)
SUNION 并集
SUNIONSTORE 把并集结果存储下来
示例:
127.0.0.1:6379> SADD colors1 red green gray purple #增加集合colors1
(integer) 4
127.0.0.1:6379> SADD colors2 green yellow blue pink gray #增加集合colors1
(integer) 5
127.0.0.1:6379> SINTER colors1 colors2 #交集
1) "gray"
2) "green"
127.0.0.1:6379> SUNION colors1 colors2 #并集
1) "red"
2) "gray"
3) "purple"
4) "green"
5) "pink"
6) "yellow"
7) "bule"
127.0.0.1:6379> SDIFF colors1 colors2 #叉集,colors1有,colors2没有的
1) "red"
2) "purple"
127.0.0.1:6379> SDIFF colors2 colors1 #叉集,colors2有,colors1没有的
1) "pink"
2) "yellow"
3) "bule"
注意:叉集要注意先后次序
127.0.0.1:6379> SISMEMBER colors1 red #判定colors1是否有red
(integer) 1 #注意:这里1表示true,0表示false
127.0.0.1:6379> SISMEMBER colors1 bule #判定colors1是否有blue
(integer) 0 #0表示不存在
- @sorted_set 有序集合
ZADD 向集合中添加成员,语法:key [NX|XX] [CH] [INCR] score member [score member ...]如:成员 分数,成员 分数;以分数进行排序
ZCARD 获取指定集合的元素数量
ZCOUNT 在最小值和最大值之间成员的得分
ZINCRBY 增加集合中成员的分数
ZINTERSTORE 做交集并存储结果
ZLEXCOUNT 计算给定词典范围内已排序集合中的成员数
ZRANGE 根据索引返回有序集合中的成员范围
ZRANK 判定成员在有序集合中的索引
ZSCORE 获取成员的得分
示例:
127.0.0.1:6379> ZADD liangshan 108 songjiang 107 wuyong 106 chaogai #增加一个有序集合liangshan,并添加成员
(integer) 3
127.0.0.1:6379> ZSCORE liangshan wuyong #获取成员wuyong的得分
"107"
127.0.0.1:6379> ZRANK liangshan wuyong #查看成员wuyong的排位(注意:这里从0开始计数,所以wuyong的排位为1)
(integer) 1
- @hash 关联数组,不以数字进行位置标记
每一个key内部有一个字段,每一个字段对应一个值,字段就是指field
HSET 设置一个hash的值
HMSET
HGET
HMGET
HKEYS 获取所有元素的键
HVALS 显示所有的值
HDEL 删除某个字段
HGETALL
HEXISTS 判断某个字段是否存在
操作:删除某个指定的元素、获取所有元素的键、获取所有元素的值、删除某个元素、更改某个元素的值、新增一个元素
示例:
127.0.0.1:6379> HMSET stu1 id 1 name tom age 13 course "HamaGong" #设置第一个学生的id、name、age、course
OK
127.0.0.1:6379> HGET stu1 course #获取第一个学生的课程
"HamaGong"
127.0.0.1:6379> HGET stu1 name #获取第一个学生的课程
"tom"
127.0.0.1:6379> HKEYS stu1 #获取所有的键
1) "id"
2) "name"
3) "age"
4) "course"
127.0.0.1:6379> HVALS stu1 #获取所有的值
1) "1"
2) "tom"
3) "13"
4) "HamaGong"
127.0.0.1:6379> HGETALL stu1 #获取所有键值字段
1) "id"
2) "1"
3) "name"
4) "tom"
5) "age"
6) "13"
7) "course"
8) "HamaGong"
- @pubsub publish subscription 发布订阅,消息队列
PUBLISH 发布者向频道发布信息
SUBSCRIBE 订阅频道
PUBSUB 查看频道的详细信息
UNSUBSCRIBE 以指定的频道取消对某些队列的订阅
PSUBSCRIBE 基于模式订阅频道
PUNSUBSCRIBE 基于模式取消对某些队列订阅
示例:
新克隆两个窗口,连接redis并订阅某个频道
三个窗口分别是:窗口1(发布者),窗口2和窗口3(订阅者)
在订阅者窗口:
[root@centos7 ~]# redis-cli
127.0.0.1:6379> SUBSCRIBE news #订阅news队列
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "news"
3) (integer) 1
1) "message"
2) "news"
3) "Dongge men"
在发布者窗口在news队列发布消息
127.0.0.1:6379> PUBLISH news "Dongge men"
(integer) 2
再次在订阅者窗口查看订阅队列的信息
127.0.0.1:6379> SUBSCRIBE news
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "news"
3) (integer) 1
1) "message"
2) "news"
3) "Dongge men"
- @geo 位图,地理位置查询
- @hyperloglog 日志相关配置
- @connection 查看连接数
- @server 查看服务
3、Redis相关配置介绍
配置文件组成
- 配置文件:
/etc/redis.conf 主配置文件
/usr/bin/redis-server 主程序
/usr/lib/systemd/system/redis.service redis的unitfile
/usr/bin/redis-cli redis命令行接口
/etc/redis-sentinel.conf redis高可用集群配置文件
/var/lib/redis 数据存储目录
/var/log/redis 日志文件 - 配置文件内容:
NETWORK 网络相关配置参数,如:监听的地址和端口
GENERAL 一般性选项或参数
SPANSHOTTING 快照持久化
REPLICATION 主从复制
SECURITY 安全相关
LIMITS 资源限制
APPEND ONLY MODE 第二种持久化,仅追加文件,AOF持久化
LUA SCRIPTING LUA脚本
REDIS CLUSTER redis集群
SLOW LOG 慢查询日志
LATENCY MONITOR 延迟监控
EVENT NOTIFICATION 事件组织
ADVANCED CONFIG 高级配置
示例:
[root@centos7 ~]# grep -i "^###" /etc/redis.conf
################################## INCLUDES ###################################
################################## NETWORK #####################################
################################# GENERAL #####################################
################################ SNAPSHOTTING ################################
################################# REPLICATION #################################
################################## SECURITY ###################################
################################### LIMITS ####################################
############################## APPEND ONLY MODE ###############################
################################ LUA SCRIPTING ###############################
################################ REDIS CLUSTER ###############################
################################## SLOW LOG ###################################
################################ LATENCY MONITOR ##############################
############################# EVENT NOTIFICATION ##############################
############################### ADVANCED CONFIG ###############################
配置文件配置段详细说明
- NETWORK(网络)配置段
配置如下:
bind 0.0.0.0 #指定监听ip地址,如:bind 0.0.0.0是指监听本地所有ip地址
注意:如果bind监听本机所有ip,那么互联网任何用户都可以访问redis,这是极不安全的,因此要开启认证
protected-mode yes #开启保护(认证)模式
注意:当同时满足以下两个条件时,保护模式将会自动开启。1.没有使用bind命令监听一个ip地址,2.没有配置密码password。如果使用bind命令监听所有ip地址,只符合第一个条件,保护模式将不会自动开启,仍然可以访问redis数据库
port 6379 #监听端口,默认为6379
tcp-backlog 511 #设置后援队列,默认为511
unixsocket /tmp/redis.sock #如果redis数据库为本机连接,可以使用socket文件进行连接,更为高效
timeout 0 #客户端允许空闲多长时间后断开连接
tcp-keepalive 300 #tcp连接超时时长
- SECURITY(安全)配置段
requirepass magedu.com #开启认证后配置密码
示例:配置后重启redis服务
[root@centos7 ~]# redis-cli -h 192.168.32.140 -a magedu.com #输入密码访问redis数据库
192.168.32.140:6379> KEYS *
1) "colors1"
2) "stu1"
3) "colors2"
4) "color"
5) "counter"
6) "weekdays"
7) "liangshan"
[root@centos7 ~]# redis-cli -h 192.168.32.140 #不加密码选项连接redis数据库,发现仍然能够连接数据库
192.168.32.140:6379> KEYS * #查看所有键,发现需要进行认证
(error) NOAUTH Authentication required.
192.168.32.140:6379> AUTH magedu.com #使用AUTH命令进行认证,后跟配置文件中配置的认证密码
OK
192.168.32.140:6379> KEYS * #再次查看所有键,发现可以正常访问
1) "colors1"
2) "stu1"
3) "colors2"
4) "color"
5) "counter"
6) "weekdays"
7) "liangshan"
- GENERAL(通用)配置段
配置如下:
daemonize no #是否运行为守护进程。在centos6系统中要配置为yes,在centos7系统中要配置为no。
这是因为,centos6系统中使用upstart管理进程,实际上就是脚本方式进行管理,如果不以守护进程方式运行就会在前台占据终端;而在centos7系统中使用systemd管理进程,它是一个总线,无法识别到运行于后台的守护进程,因此要服务进程运行于systemd的前台(注意:这里是systemd的前台,而不是系统前台,因此并不会占据终端)才能识别到该服务。
pidfile /var/run/redis_6379.pid #进程的pid文件
loglevel notice 日志级别
logfile /var/log/redis/redis.log 日志文件
databases 16 #设定redis服务器最多支持多少个数据库,默认16个;如果想要用无限个,只需要16更改为-1即可。数据库的名称以数字进行编号,从0开始编号,16个据库,则编号为0-15。
- LIMITS(资源限制)配置段
配置如下:
redis是基于内存的数据库,为了防止内存被用完导致服务器性能下降,要对资源进行限制,这就是limits配置段的作用。因此要设置内存使用的最大值(默认为60%),以及交换分区被使用的倾向性
maxclients 10000 #最大客户端数量,也即是最大并发连接数
maxmemory 1073741824 #设置本地启用的最大内存的值为1G,以字节为单位,配置时要注意进行单位换算。
OOM:Out Of Memory,一旦内存使用完,内核将自动杀死最吃内存资源的进程
oom_score:对占用内存的进程进行评估打分,便于比较
oom_adj:一般占用内存最多的进程比较重要,可以通过手动调整得分相对较高(得分第二或第三),但重要程度较低的进程分数为最高,这样就防止比较重要的进程被杀掉。
maxmemory-policy noeviction #最大内存使用策略,即内存被使用完以后使用什么方法淘汰数据,默认为noeviction,意为不淘汰任何现有的数据项。
注意:当redis被作为数据库使用时,即使内存被使用完数据也不能被淘汰;当redis被作为缓存使用时,则可以淘汰数据项,对原始数据不会产生影响。
当内存使用完以后,有以下6个策略:
# volatile-lru -> 对设置了过期时间的键基于LRU算法淘汰,对没有设置过期时间需要持久保存的数据不进行淘汰
# allkeys-lru -> 对所有键基于LRU算法淘汰
# volatile-random -> 对设置了过期时间的键基于随机算法进行淘汰
# allkeys-random -> 对所有键基于随机算法进行淘汰
# volatile-ttl -> 对离过期时间较短的键进行淘汰
# noeviction -> 不淘汰任何现有的数据项
maxmemory-samples 5 #淘汰算法运行时的采样样本数,即在内存最大值淘汰时,如果全部进行比较,则遍历所有数据比较浪费资源,因此采用样本方式,比如要淘汰1键,就拿来5个样本,在其中淘汰掉1个;样本越多越精确,样本越少越高效。
- SLOW LOG (慢查询)日志配置段
配置如下:
slowlog-log-slower-than 10000 #慢查询超时时长,单位是微秒
slowlog-max-len 128 #慢查询日志记录的日志最大条目
- ADVANCED CONFIG(高级配置)配置段
配置如下:
hash-max-ziplist-entries 512 #一个hash数据结构最多有512个字段
hash-max-ziplist-value 64 #每个字段的值不能超过64字节
client-output-buffer-limit normal 0 0 0 #正常客户端的缓冲区(进行增删改查操作),三个值分别是硬限制、软限制、宽限期
client-output-buffer-limit slave 256mb 64mb 60 #从服务器的客户端的缓冲区,三个值分别是硬限制、软限制、宽限期
client-output-buffer-limit pubsub 32mb 8mb 60 #发布订阅客户端的缓冲区,三个值分别是硬限制、软限制、宽限期
<hard-limit>
<soft-limit>
<soft-limit seconds>
注意:三个数值中,软限制在某一时刻缓冲区的值可以被临时超过,可以被超过的最大值是硬限制的值,宽限期是指缓冲区的软限制到硬限制之间的空间可以使用的时长,如256mb 64mb 60,是指缓冲区最大使用空间为64m,在某一峰值时刻,可以超过64m,但最多能到256m,并且超过的部分(64m-256m)只能存在60s,一旦达到60s,该部分的数据将会被清空
- 常用的配置段为:网络、安全、资源限制
4、Redis数据持久化
- redis的持久化有两种方法:RDB和AOF
- RDB:snapshotting 快照
直接把内存的空间复刻一份,放到磁盘上,也就意味着把内存中的结构转为文件格式放在磁盘上;等服务器停止再启动时,在把磁盘上的文件恢复到内存中的结构即可
存储在磁盘上的文件为二进制格式文件,因为它是复刻的内存空间
类似于mysql中的全量备份,不能频繁进行,一旦服务器崩溃,只能恢复到备份时的状态,不能恢复到最新状态的数据 - AOF:Append Only File
把每次写操作的语句追加保存到指定的文件中实现的数据持久化;当redis重启时,可通过重新执行文件中的命令在内存中重建出数据库;
当服务器崩溃时,可以把数据恢复到最新状态
缺陷:数据同步不及时。当数据提交到内核中,内核会把数据保存到内核的内存中,而不会立即写入到磁盘中,这个时间是不可预测的,因此一旦服务器崩溃仍然有丢失数据的风险
解决方法:fsync 内核库调用,文件同步
当把数据提交给内核后,内核不但要把数据保存在内核内存中,而且立即把数据写入磁盘中,进而确保数据被持久化到磁盘中
但是这样带来的问题:内核之所以在一段时间内才写入数据到磁盘,是因为降低磁盘IO的压力,一旦使用fsync,就会大大增加磁盘IO的压力
RDB持久化
- RDB相关的配置:
配置如下:
SNAPSHOTTING配置段
save <seconds> <changes>
save 900 1
save 300 10
save 60 10000
save 10 50000
注意:以上策略逆向(从下往上)生效
表示:三个策略满足其中任意一个均会触发SNAPSHOTTING操作;900s内至少有一个key有变化,300s内至少有10个key有变化,60s内至少有1W个key发生变化;
stop-writes-on-bgsave-error yes
#如果快照创建失败,是否禁止新的写入操作请求;
rdbcompression yes #rdb快照完成后,存储到磁盘时是否压缩
rdbchecksum yes #rdb快照完成后,是否检查其校验码,校验数据是否完整
dbfilename dump.rdb:指定rdb文件名
dir /var/lib/redis:rdb文件的存储路径
手动进行快照:
使用以下客户端命令
SAVE:同步,即在主线程中保存快照,在前台执行,此时会阻塞所有客户端请求,直至快照保存完成
BGSAVE:backgroud,异步;启动后台专用线程保存快照,客户端请求操作可以继续进行,不受影响
AOF持久化
- AOF相关配置:
配置如下:
APPEND ONLY MODE配置段
appendonly no #默认关闭AOF持久化
appendfilename "appendonly.aof" #AOF文件名称,相对路径,相对于/var/lib/redis来说
appendfsync everysec #设置多久做一次IO,把数据直接从内核写入磁盘,默认为everysec
Redis supports three different modes:
always:每语句一次;即只要有写操作的语句写入内核,立即把该语句写入磁盘中,不推荐该选项
everysec:每秒一次;
no:redis不执行主动同步操作,而是由内核决定;
no-appendfsync-on-rewrite no #是否在后台执行aof重写期间不调用fsync,默认为no,表示调用;
注意:如果某些语句执行很多次,结果却只是数值的累加,如果执行所有语句仅得到一个数值将会十分浪费资源,这时只需对这些语句进行重写后得到执行结果写入内核即可,这样能够节约资源
auto-aof-rewrite-percentage 100 #新增的内容与原有内容进行比较,变化量达到100%就会进行重写。如:原内容有4行,新增4行内容就会触发重写
auto-aof-rewrite-min-size 64mb #文件大小超过64m才会触发重写
上述两个条件同时满足时,方会触发重写AOF;即与上次aof文件大小相比,其增长量超过100%,且大小不少于64MB;
aof重写:当需要重构aof文件时,源文件不发生变化,而是启动一个后台线程,把aof文件当前状态保存下来转为aof语句保存到磁盘文件中,一旦保存完毕,就会删除源文件。
aof-load-truncated yes #
手动持久化
执行客户端命令:
BGREWRITEAOF:AOF文件重写;
不会读取正在使用AOF文件,而是通过将内存中的数据以命令的方式保存至临时文件中,完成之后替换原来的AOF文件;
- RDB与AOF同时启用时要注意:
(1) BGSAVE和BGREWRITEAOF不会同时进行;
(2) Redis服务器启动时用持久化的数据文件恢复数据,会优先使用AOF;
注意:持久机制本身不能取代备份;应该制订备份策略,对redis库定期备份;
5、Redis主从复制
- redis主从复制能降低数据库读操作的压力,但写操作的压力仍然存在
- redis还支持把数据分散后进行分片存储到各节点
- redis主从复制工作过程:
从节点接入redis主节点后,由redis主节点做一个rdb快照并通过网络传给从节点,主节点边做快照边传输,从节点边接收边恢复到本地内存中,等传输完成,从节点就拿到了所有数据。当主节点每记录一条语句,会同时把该语句发送一份给从节点,从节点在本地进行重放,从节点在向其他从节点同步数据时,并不是通过二进制文件或aof文件与其他从节点进行同步,而是把所有的写操作通过网络IO发送给每一个从节点。
一旦从节点在某一时间段内与主节点断开连接,会导致主从数据不一致。redis主从复制并不会找到断开连接的时间点继续复制,而是一旦超过指定时间,从节点再次连接到redis主节点后,会直接放弃本地数据,重新完整复制,主节点发送所有数据的快照给从节点,从节点再次进行复制
redis主从复制中,主节点只需要把所有数据传递给网络连接,不管从节点何时接收和在本地应用,主节点都会认为该复制操作已经完成
redis主从复制中需要把写操作发送给主节点,读操作发送给从节点,这就需要客户端或中间件能够实现读写语句的"路由",把写语句调度到主节点,把读语句调度到从节点,这个中间件称之为语句路由器 - redis主从复制配置:
配置redis主从复制有两种方式:
方式1:通过配置文件配置
方式2:通过命令行方式配置 - 实验环境:
主节点:192.168.32.140 node01
从节点1:192.168.32.141 node02
从节点2:192.168.32.142 node03
分别在三个节点上安装redis
yum -y install redis
方法1:通过配置文件配置redis主从复制
- 配置文件参数介绍:
slave-serve-stale-data yes #当从节点无法连接到主节点,是否允许从节点使用过期的数据服务客户端
slave-read-only yes #从服务器设置为只读,保证数据的一致
repl-diskless-sync no #当主从节点之间网络延迟较大时,是否要把数据保存在主节点本地磁盘中,然后从磁盘向从节点进行同步
有三个选项:no, Disk-backed, Diskless
新的从节点或某较长时间未能与主节点进行同步的从节点重新与主节点通信,需要做“full synchronization",此时其同步方式有两种style:
Disk-backend:主节点新创建快照文件于磁盘中,而后将其发送给从节点;
Diskless:主节占新创建快照后直接通过网络套接字文件发送给从节点;
repl-diskless-sync-delay 5 #是指第一个从节点连接到主节点后,延迟多长时间才会发送快照
由于从节点连接到主节点有先后顺序,因此会形成主节点多次发送快照给从节点,这样就浪费了带宽和资源。
可以通过创建一个快照并行发送给多个从节点,这样为了实现并行复制,通常需要在复制启动前延迟一个时间段;
repl-ping-slave-period 10 #主节点每隔多久探测一次从节点是否在线
注意:服务器会把每个客户端的连接状态保存为文件保存在内存空间中,如果不把链接断开,会一直占用服务器的内存,这样就会造成资源的浪费以及增加服务器的压力,因此,如果客户端不在线,要断开与客户端的连接
repl-timeout 60 #复制超时时长,从节点复制数据的超时时长,超时后,从节点再次连接到主节点必须重新复制
repl-disable-tcp-nodelay no #允许延时发送
delay是指当要发送的数据很小时,开销较大,可以多等几个数据,等到数据量达到一定程度再发送数据,此时注重开销不注重效率;
而nodelay正好相反,只要有数据就会发送,不会等待,此时注重效率不注重开销。
repl-backlog-size 1mb #后援队列大小
slave-priority 100 复制集群中,主节点故障时,sentinel应用场景中的主节点选举时使用的优先级;数字越小优先级越高,但0表示不参与选举;
min-slaves-to-write 3:当从节点数量超过多少个后,主节点才开始写操作,此处数量为3。
原因:当主从集群只有主节点在线时,主节点负责向从节点进行写操作,如果此时没有一个从节点在线,那么主节点又要负责客户端的读操作访问,这对于主节点来说压力过大,因此要等到从节点上线并且超过一定数量后才开始写操作,减轻主节点的压力
min-slaves-max-lag 10:允许从节点落后主节点多长时间,超过该时间后,主节点会拒绝向从节点写入操作,这里设置为10s
在配置文件中进行配置:
在主节点192.168.32.140上
更改配置文件,当前为主节点,以下配置无需更改
vim /etc/redis.conf
REPLICATION配置段
slaveof <masterip> <masterport> #配置当前节点是谁的从节点,后跟主节点的ip和端口
masterauth <master-password> #如果开启认证功能,需要填写主节点认证密码
在从节点1 192.168.32.141上
更改配置文件
vim /etc/redis.conf
NETWORK配置段
bind 0.0.0.0
REPLICATION配置段
slaveof 192.168.32.140 6379 #配置当前节点是谁的从节点,后跟主节点的ip和端口
masterauth magedu.com #如果开启认证功能,需要填写主节点认证密码。在之前配置文件讲解中,主节点已经开启认证功能,密码为magedu.com
slave-serve-stale-data yes #当从节点无法连接到主节点,是否允许从节点使用过期的数据服务客户端
slave-read-only yes #从服务器设置为只读,保证数据的一致
启动redis服务
systemctl start redis
在主节点上连接redis数据库查看其状态
[root@centos7 ~]# redis-cli -a magedu.com
127.0.0.1:6379> INFO
# Server 服务端
# Client 客户端
# Memory 内存
# Persistence 启用的持久化功能是哪一种以及如何工作的
# Stats 统计数据
# Replication 记录主从复制中主从节点的相关信息
# CPU 内存使用情
# Cluster 记录集群状况
# Keyspace 记录键空间
127.0.0.1:6379> INFO replication #INFO后跟想要查看的单个字段即可值查看单个想要查看的字段信息
# Replication
role:master
connected_slaves:1
slave0:ip=192.168.32.141,port=6379,state=online,offset=547,lag=0 #从节点的ip和端口以及状态
master_repl_offset:547
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:2
repl_backlog_histlen:546
在从节点1 192.168.32.141上
[root@centos7 ~]# redis-cli -h 192.168.32.141
192.168.32.141:6379> KEYS *
1) "colors1"
2) "liangshan"
3) "counter"
4) "weekdays"
5) "color"
6) "colors2"
7) "stu1"
连接redis数据库并查看所有键,发现主节点上创建的键已经被复制过来,说明主从复制配置成功
注意:如果主节点启用了认证功能,建议从节点也启动认证功能
方法2:通过命令行方式配置redis主从复制
- 在redis数据库命令行进行配置
redis支持在命令行配置主从复制,然后把配置保存到配置文件中
常用命令:
CLIENT LIST #列出客户端
CLIENT KILL #关闭客户端
CLIENT KILL [ip:port] [ID client-id] [TYPE normal|master|slave|pubsub] [ADDR ip:port] [SKIPME yes/no]
关闭客户端时,可写客户端id号、客户端的ip地址和端口等
CLIENT PAUSE
CLIENT PAUSE timeout
CLIENT REPLY
CLIENT SETNAME:Set the current connection name
配置参数可运行时修改:
CONFIG GET #获取某个参数的值
CONFIG RESETSTAT #把状态(info)信息中的计数器置0,重新开始计数
CONFIG REWRITE #在命令行设置的参数不会自动保存到配置文件中,要想保存必须使用CONFIG REWRITE才能保存
CONFIG SET #设置某个参数的值
示例:
连接redis数据库
[root@centos7 ~]# redis-cli -a magedu.com
127.0.0.1:6379> CLIENT LIST #查看所有连接的客户端
id=2 addr=192.168.32.141:32990 fd=5 name= age=973 idle=1 flags=S db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=replconf #从节点客户端
id=4 addr=127.0.0.1:51122 fd=6 name= age=917 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=32768 obl=0 oll=0 omem=0 events=r cmd=client #本机客户端
- redis主从配置(命令行方式)
主从配置中master节点配置使用CONFIG命令进行配置,slave节点使用SLAVEOF命令
这里为了区分两种配置方式,我们使用主节点master 192.168.32.140与从节点2 192.168.32.142进行配置 - 命令行配置参数:
slaveof #配置主从复制从节点的命令
masterauth #配置主从复制主节点认证密码的命令
示例:
在从节点2 192.168.32.142上
vim /etc/redis.conf
bind 0.0.0.0
注意:由于这里要使用命令行进行配置,因此在配置文件中不做其他更改
启动redis服务
systemctl start redis
连接redis数据库通过命令行进行主从配置
[root@centos7 ~]# redis-cli
127.0.0.1:6379> SLAVEOF 192.168.32.140 6379 #通过命令指定主节点的ip和端口
OK
127.0.0.1:6379> CONFIG SET masterauth magedu.com #配置主节点的认证密码
OK
在主节点上查看主从配置情况
127.0.0.1:6379> INFO replication
# Replication
role:master
connected_slaves:2
slave0:ip=192.168.32.141,port=6379,state=online,offset=2829,lag=0 #从节点1的ip和端口以及状态
slave1:ip=192.168.32.142,port=6379,state=online,offset=2829,lag=0 #从节点2的ip和端口以及状态
master_repl_offset:2829
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:2
repl_backlog_histlen:2828
再次登录从节点查看:
127.0.0.1:6379> KEYS * #查看所有键,发现主节点上的键已经复制过来,说明主从复制完成
1) "stu1"
2) "counter"
3) "color"
4) "liangshan"
5) "colors2"
6) "colors1"
7) "weekdays"
127.0.0.1:6379> CONFIG REWRITE #使用此命令把配置保存到配置文件中
OK
查看从节点配置文件验证主从配置信息是否已经保存
[root@centos7 ~]# tail /etc/redis.conf
# Generated by CONFIG REWRITE
slaveof 192.168.32.140 6379
masterauth "magedu.com"
注意:通过命令行配置的主从信息在保存时会被追加保存到配置文件尾部
通过命令行对两个从节点配置认证
在两个从节点上分别进行以下配置:
[root@centos7 ~]# redis-cli
192.168.32.141:6379> CONFIG SET requirepass magedu.com #为认证参数设置认证密码
OK
192.168.32.141:6379> CONFIG REWRITE #此时由于认证已经生效,因此使用保存命令无法成功,必须进行认证后才能保存配置
(error) NOAUTH Authentication required.
192.168.32.141:6379> AUTH magedu.com #进行认证
OK
192.168.32.141:6379> CONFIG REWRITE #认证后再次保存,生效。
OK
注意:配置认证密码时,主从节点的认证密码要保持一致
6、Redis监控组件sentinel介绍
- sentinel:哨兵,负责管理多个集群
主要完成三个功能:监控、通知、自动故障转移
选举时遵循的协议:流言协议、投票协议
其故障转移过程类似于mysql集群中故障转移过程,当主节点出现故障后,会自动提升一个从节点作为主节点提供服务。 - 配置项:
配置项如下:
监听端口port: 26379
sentinel monitor <master-name> <ip> <redis-port> <quorum>
主节点名称 ip地址 端口 法定票数
sentinel auth-pass <master-name> <password>
<quorum>表示sentinel集群的quorum机制,即至少有quorum个sentinel节点同时判定主节点故障时,才认为其真的故障;
s_down: subjectively down
o_down: objectively down
sentinel down-after-milliseconds <master-name> <milliseconds>
监控到指定的集群的主节点异常状态持续多久方才将标记为“故障”;
sentinel parallel-syncs <master-name> <numslaves>
指在failover过程中,能够被sentinel并行配置的从节点的数量;
sentinel failover-timeout <master-name> <milliseconds>
故障转移超时时长,sentinel必须在此指定的时长内完成故障转移操作,否则,将视为故障转移操作失败;
sentinel notification-script <master-name> <script-path>
通知脚本,此脚本被自动传递多个参数;
redis-cli -h SENTINEL_HOST -p SENTINEL_PORT
redis-cli>
SENTINEL masters
SENTINEL slaves <MASTER_NAME>
SENTINEL failover <MASTER_NAME>
SENTINEL get-master-addr-by-name <MASTER_NAME>
模拟实验:配置sentinel进行故障自动转移
在实际生产环境中sentinel节点要与redis集群独立开来,分别配置,并且sentinel至少有3个(奇数个)。这里为了方便实验,把sentinel节点与redis配置在一起
实验环境:
主节点:192.168.32.140 node01
从节点1:192.168.32.141 node02
从节点2:192.168.32.142 node03
在主节点192.168.32.140上
编辑配置文件:
vim /etc/redis-sentinel.conf
port 26379 #sentinel监听端口
bind 0.0.0.0 #该选项默认不存在,由于redis配置文件中配置了保护模式,如果不启用bind指令会进入保护模式,这样sentinel监听端口26379将会无法工作,因此需要手动添加该选项
sentinel monitor mymaster 192.168.32.140 6379 2 #sentinel监控的主从复制集群中主节点的ip地址和端口,最后的数字2是表示quorum法定票数,即只有sentinel节点(至少有三个)超过半数(这里设置为2票)以上认为主节点down才会将主节点标记为down。
sentinel auth-pass mymaster magedu.com #主节点启用认证功能,要写入认证密码
sentinel down-after-milliseconds mymaster 5000 #监控到指定的集群的主节点异常状态持续多久方才将标记为“故障”,单位为毫秒,默认为30s,这里我们更改为5s
注意:只有sentinel节点(至少有三个)超过半数以上认为主节点down才会将主节点标记为down。
sentinel parallel-syncs mymaster 2 #指在failover过程中,能够被sentinel并行配置的从节点的数量,默认为1,该数值可以根据从节点数量进行调整,这里配置为2
sentinel failover-timeout mymaster 180000 #故障转移超时时长,sentinel必须在此指定的时长内完成故障转移操作,否则,将视为故障转移操作失败,单位为毫秒,默认为3分钟
另外两个sentinel节点配置大致相同,可以把该文件复制到另外两个节点上
scp /etc/redis-sentinel.conf 192.168.32.141:/etc/
scp /etc/redis-sentinel.conf 192.168.32.142:/etc/
分别在主节点和两个从节点上继续以下操作
启动redis-sentinel服务
systemctl start redis-sentinel.service
查看sentinel端口26379是否启动
ss -ntl
在从节点2上查看自动生成的sentinel配置
[root@centos7 ~]# tail /etc/redis-sentinel.conf
logfile "/var/log/redis/sentinel.log"
# Generated by CONFIG REWRITE
supervised systemd
sentinel leader-epoch mymaster 0
sentinel known-slave mymaster 192.168.32.142 6379
sentinel known-slave mymaster 192.168.32.141 6379
sentinel known-sentinel mymaster 192.168.32.140 26379 6156c7f721c452cdae542c95632330c391f58d85
sentinel known-sentinel mymaster 192.168.32.142 26379 45d7535584220017ed6d59a8d7715e7540b8f8bb
sentinel current-epoch 0
测试:
在主节点192.168.32.140上关闭上的redis服务,查看故障转移效果
systemctl stop redis
查看redis的监听端口6379确认关闭redis服务
ss -ntl
连接sentinel查看监控状态
[root@centos7 ~]# redis-cli -p 26379
127.0.0.1:26379> SENTINEL masters #查看当前主节点的ip,发现已经切换为192.168.32.141
1) 1) "name"
2) "mymaster"
3) "ip"
4) "192.168.32.141"
5) "port"
6) "6379"
7) "runid"
8) "2dc54cd7fd0b313f50888a25777febd19100aac1"
9) "flags"
10) "master"
127.0.0.1:26379> SENTINEL slaves mymaster #查看集群mymaster中从节点状态,发现从节点自动切换为192.168.32.140和192.168.32.142,而且192.168.32.140处于down状态。这里只贴出部分结果。
1) 1) "name"
2) "192.168.32.140:6379" #自动切换为从节点
3) "ip"
4) "192.168.32.140"
5) "port"
6) "6379"
7) "runid"
8) ""
9) "flags"
10) "s_down,slave,disconnected" #状态为down,未连接主节点
2) 1) "name"
2) "192.168.32.142:6379" #当前状态为从节点,状态正常
3) "ip"
4) "192.168.32.142"
5) "port"
6) "6379"
7) "runid"
8) "b180859aa58010d639b90bf8f4602823eae89f17"
9) "flags"
10) "slave"
11) "link-pending-commands"
由以上结果可以看出,目前已经完成故障的自动切换
如果原来的主节点被修复,而当前的主节点为新选举的主,那么原有的主节点此时只能作为从节点。
修复原有主节点,并将其设置为当前主节点192.168.32.141的从:
vim /etc/redis.conf
slaveof 192.168.32.141 6379
masterauth magedu.com
启动redis服务并查看6379端口是否启用
systemctl start redis && ss -ntl
在新的主节点192.168.32.141上查看当前主从复制信息
[root@centos7 ~]# redis-cli -a magedu.com
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=192.168.32.142,port=6379,state=online,offset=187479,lag=0
slave1:ip=192.168.32.140,port=6379,state=online,offset=187479,lag=1
master_repl_offset:187479
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:2
repl_backlog_histlen:187478
由以上结果可以看到一主两从已经恢复完成。
7、Redis Cluster
- redis cluster
通过二级路由结构进行查询 - 集群相关的配置:
cluster-enabled 是否开启集群配置
cluster-config-file 集群节点集群信息配置文件,每个节点都有一个,由redis生成和更新,配置时避免名称冲突
cluster-node-timeout 集群节点互连超时的阈值,单位毫秒
cluster-slave-validity-factor 进行故障转移时,salve会申请成为master。有时slave会和master失联很久导致数据较旧,这样的slave不应该成为master。这个配置用来判断slave是否和master失联时间过长。
集群配置过程:
(1) 设置配置文件,启用集群功能;
(2) 启动redis后为每个节点分配slots;
CLUSTER ADDSLOTS
注意:每个slot要独立创建;可用范围是0-16383,共16384个;
配置示例:
redis-cli -c -h 192.168.1.100 -p 7000 cluster addslots {0..5000}
slot范围:
0 5499
5500 10999
11000 16383
(3) 设定集群成员关系;
CLUSTE MEET
(4) 获取集群状态
CLUSTER INFO
CLUSTER NODES
模拟实验:配置redis集群
实验环境
主机1:192.168.32.140 node01
主机2:192.168.32.141 node02
主机3:192.168.32.142 node03
在三个节点上关闭redis和redis-sentinel服务
systemctl stop redis redis-sentinel.service
在主机1:192.168.32.140上
更改配置文件:
vim /etc/redis.conf
REPLICATION配置段
slaveof 192.168.32.141 6379 #删除该配置,关闭主从配置项
masterauth magedu.com #删除该配置,关闭主从配置项
REDIS CLUSTER配置段
cluster-enabled yes #启用集群
cluster-config-file redis-cluster.conf #设置集群配置文件名为redis-cluster.conf,该文件相对路径为dir,dir路径默认为/var/lib/redis,也可以使用绝对路径指定文件。此外还要确保redis用户对该文件有读写权限
cluster-node-timeout 15000 #集群节点互连超时的阈值,单位毫秒
cluster-slave-validity-factor 10 #进行故障转移时,salve会申请成为master。有时slave会和master失联很久导致数据较旧,这样的slave不应该成为master。这个配置用来判断slave是否和master失联多长时间不允许slave提升为主节点,这里默认为10s
三个节点配置文件一致,因此把该文件复制到其他两个节点上
scp /etc/redis.conf 192.168.32.141:/etc/
scp /etc/redis.conf 192.168.32.142:/etc/
由于之前做实验时已经生成数据,因此需要把数据删除。如果设备中的数据比较重要则不能直接删除,要对数据进行备份,导入到其他新的集群节点中。然后进行以下操作:
删除三个节点上的redis数据
rm -f /var/lib/redis/dump.rdb
启动三个节点上的redis服务并查看6379端口
systemctl start redis
启动redis后为每个节点分配slots;
注意:每个slot要独立创建,如1-5000,每个数字都要写出,但在redis命令行不支持命令展开,我们可以通过shell命令进行配置,如{1..5500};slot可用范围是0-16383,共16384个;
slot规划范围:0-5500 5501-11000 11001-16383
在主机1上:
为第一个节点分配slot,通过命令行执行以下命令完成
[root@centos7 ~]# redis-cli -h 192.168.32.140 -c -a magedu.com cluster addslots {0..5500}
OK
连接redis,查看集群详细信息
[root@centos7 ~]# redis-cli -a magedu.com
127.0.0.1:6379> cluster info
cluster_state:fail
cluster_slots_assigned:5501 #分配了5501个slot
cluster_slots_ok:5501
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:1
cluster_size:1
cluster_current_epoch:0
cluster_my_epoch:0
cluster_stats_messages_sent:0
cluster_stats_messages_received:0
在主机2上:
为第二个节点分配slot,通过命令行执行以下命令完成
[root@centos7 ~]# redis-cli -h 192.168.32.141 -c -a magedu.com cluster addslots {5501..11000}
OK
连接redis,查看集群详细信息
[root@centos7 ~]# redis-cli -a magedu.com
127.0.0.1:6379> cluster info
cluster_state:fail
cluster_slots_assigned:5500 #分配了5500个slot
cluster_slots_ok:5500
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:1
cluster_size:1
cluster_current_epoch:0
cluster_my_epoch:0
cluster_stats_messages_sent:0
cluster_stats_messages_received:0
在主机3上
为第三个节点分配slot,通过命令行执行以下命令完成
[root@centos7 ~]# redis-cli -h 192.168.32.142 -c -a magedu.com cluster addslots {11001..16383}
OK
连接redis,查看集群详细信息
[root@centos7 ~]# redis-cli -a magedu.com
127.0.0.1:6379> cluster info
cluster_state:fail
cluster_slots_assigned:5383 #分配了5383个slot
cluster_slots_ok:5383
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:1
cluster_size:1
cluster_current_epoch:0
cluster_my_epoch:0
cluster_stats_messages_sent:0
cluster_stats_messages_received:0
集群状态显示为fail,这是因为刚分配的slot是静态分配的,分配完以后还没有称为集群,此时需要几个节点相互“接触”,进而成为集群
在主机1上:
连接redis数据库
[root@centos7 ~]# redis-cli -a magedu.com
127.0.0.1:6379> CLUSTER MEET 192.168.32.141 6379 #使当前节点与其他节点“meet”即可组成集群,CLUSTER MEET后跟集群其他节点的ip和端口
OK
127.0.0.1:6379> CLUSTER MEET 192.168.32.142 6379 #使当前节点与其他节点“meet”即可组成集群,CLUSTER MEET后跟集群其他节点的ip和端口
OK
127.0.0.1:6379> CLUSTER INFO #查看集群状态,发现集群状态切换为“OK”状态,说明集群创建成功
cluster_state:ok
cluster_slots_assigned:16384 #slot数量为16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:3
cluster_size:3
cluster_current_epoch:2
cluster_my_epoch:1
cluster_stats_messages_sent:52
cluster_stats_messages_received:52
测试:
在主机2和主机3上查看集群状态验证集群结果
存储数据:
在主机1上存储数据
127.0.0.1:6379> SET name tom
(error) MOVED 5798 192.168.32.141:6379
发现出现错误,这是因为slot 5798应该在节点2上,而当前为节点1无法把数据存储到节点2上,但会返回结果给客户端,如果在节点2上存储该数据则不会报错
在主机2上:
127.0.0.1:6379> SET name tom #数据存储成功
OK
由此,需要一个智能客户端进行路由,自动把数据存储到对应的节点上
除了智能客户端之外,还可以使用一个智能路由软件。推荐使用上面提到的智能路由软件,如:Cerberus(芒果TV)
除此之外,为了防止数据丢失,还需要对每个节点做redis主从复制
另外,如果在集群中新增节点,此时slot已经被分配完,此时需要我们手动重新分配slot,分配完成后,数据会自动加载到对应的节点
8、Redis读写分离介绍
- redis读写分离需要一个中间件,即redis语句路由器
Twemproxy (Twitter)
Codis(豌豆荚) 需要了解zoopker和etcd
Redis Cluster(redis官方)
Cerberus(芒果TV) - Redis Cluster 无中心节点架构
所谓无中心节点也可以称之为多主集群架构
每一个节点都是主节点,存储所有数据的元数据和部分数据,当某个主节点出现故障,元数据不会受到影响,但是数据却会丢失一部分,因此需要对每一个节点做主从复制,这样一来就形成了三个主从复制集群,当某个主从复制集群中主节点发生故障,可以把从节点提升为主节点继续工作,但要想实现这一功能,需要部署sentinel进行监控三个主从复制集群完成故障自动转移。
对于客户端来说,当客户端访问某个节点请求数据时,如果数据不在该节点之上,会根据元数据返回请求数据的路径或地址给客户端,客户端再根据此路径请求所需的数据,这就要求客户端具备一定的“智能”。我们称之为智能客户端(smart client)
代表产品:Cerberus(芒果TV)
文章评论