【Redis技术专区】「优化案例」谈谈使用Redis慢查询日志以及Redis慢查询分析指南

前提介绍

本篇文章主要介绍了Redis的执行的慢查询的功能的查询和配置功能,从而可以方便我们在实际工作中,进行分析Redis的性能运行状况以及对应的优化Redis性能的佐证和指标因素。

在我们5.0左右的版本中Redis使用单线程架构和I/O多路复用模型来实现高性能的内存数据服务。接下来主要分析Redis单线程命令处理机制,接着分析Redis单线程模型为什么性能如此之高。

单线程命令的处理机制

Redis客户端与服务端的模型主要是下图所示。

每次客户端调用都经历了发送命令、执行命令、返回结果三个过程。

本章内容

本章的文章内容主要是一下几点。

什么是慢查询

慢查询就是当Redis在处理一条指令的时候,当超过了系统配置的执行时间的阈值的时候,就会被系统当作慢查询统计和判定。

慢查询日志

慢查询日志就是系统在命令执行前后计算每条命令的执行时间,当超过预设阈值,就将这条命令的相关信息(例如:发生时间、耗时、命令的详细信息)记录下来。

Redis慢查询日志

Redis慢查询日志功能是用于记录执行时间超过给定时长的命令请求,可以通过查看慢查询日志来监控和优化查询速度。

Redis慢查询的危害

说到了Redis慢查询的危害,就会先说一下Redis的运行机制。

Redis客户端执行一条命令的步骤

Redis是单线程来处理命令,所以一条命令从客户端到达服务端不会立即被执行,所有的命令都会进入一个队列,然后逐个被执行。

因此Redis服务端去执行操作的是可以主要以下几个步骤:

所有的指令都有先后顺序,但是真正意义到达服务端的执行顺序也是不确定的,因为中间有网络传输。但是可以肯定的是,不会有两条命令被同时执行,这样就不会产生并发问题,这就是Redis单线程的基本模型。

慢查询引发的问题
  1. 单线程机制:所有命令放在一个队列中
  2. Redis执行指令主要是在内存中执行、非IO阻塞、避免线程切换和竞态产生的消耗。

因为单线程的问题,一个命令不能执行太长时间,不然会阻塞其他命令的执行。所以慢查询会造成整体的Redis服务的性能的下降以及CPU的耗时和负载变高。

注意:慢查询只会记录执行命令的时间,没有慢查询并不代表客户端没有超时问题

阈值和慢查询的日志的设置

监控Redis指令的慢查询功能,需要明确两件事:

  1. 预设阈值怎么设置?
  2. 慢查询记录存放在哪里?

预设阈值怎么设置?

针对于慢查询的阈值,在Redis提供了slowlog-log-slower-thanslowlog-max-len配置来解决这两个问题。

阈值参数设置
慢查询执行时间阈值
  • slowlog-log-slower-than:指定执行时间超过多少微秒的命令会被记录到日志上,它的单位是微妙(1秒=1000毫秒=1000000微秒),默认值10000。

例如,该值设为100,执行了一条很慢的命令(例如 keys * ),如果它的执行时间超过了100微秒,则这个命令会被记录到慢查询日志中。

慢查询数据存储阈值

慢查询日志最多存储多少条,并没有说明存放在那里?主要通过slowlog-max-len进行控制和设置。从底层角度分析,Redis使用了一个列表来存储慢查询日志,slowlog-max-log就是列表的最大长度。

  • slowlog-max-len:指定服务器上最多保存慢查询日志的条数。
    • slowlog-log-slower-than=0,那么系统会记录所有的命令
    • slowlog-log-slower-than<0,那么对任何命令都不会记录。

例如,该值设为5,那么命令执行时间超过slowlog-log-slower-than设置的时间的命令会被记录到慢查询日志上,如果慢查询日志的数量等于5,那么再添加慢查询日志时,需要把最早添加的慢查询日志删除,即慢查询日志删除采用先进先出的方式。

慢查询的配置类型和方式

Redis有两种修改配置的方法:1. 修改配置文件;2. config set命令动态修改。

例如,使用config set命令将slowlog-log-slower-than设置为20000微秒,slowlog-max-len设置为10000:

config set slowlog-log-slower-than 20000
config set slowlog-max-len 10000
config rewrite

如果要Redis将配置持久化到本地配置文件,要执行config rewrite命令,它会重写配置文件。

慢查询日志的操作命令

慢查询日志的的查询和维护主要是通过:slowlog get、slowlog len、slowlog reset这几条命令。

slowlog get [n]

慢查询日志有4个属性组成:日志id、发生时间戳、命令耗时、执行命令和参数。

127.0.0.1:6379> slowlog get
1) 1) (integer) 1
   2) (integer) 1513709400
   3) (integer) 11
   4) 1) "slowlog"
      2) "get"
2) 1) (integer) 0
   2) (integer) 1513709398
   3) (integer) 4
   4) 1) "config"
      2) "set"
      3) "slowlog-log-slower-than"
      4) "2"

slowlog len

获取慢查询日志列表当前的长度

127.0.0.1:6379> slowlog len
(integer) 2

上面则说明当前的满查询列表中至于连个慢查询日志。

slowlog reset

实际是对慢查询日志列表做清理操作。

127.0.0.1:6379> slowlog len
(integer) 6
127.0.0.1:6379> slowlog reset
OK
127.0.0.1:6379> slowlog len
(integer) 1

为什么还有1个,因为阈值设的比较小,slowlog reset就属于慢查询。

本文来自博客园,作者:洛神灬殇,转载请注明原文链接:http://www.cnblogs.com/liboware/p/17066112.html,任何足够先进的科技,都与魔法无异。

本文转载于网络 如有侵权请联系删除

相关文章

  • 如何实现按键的短按、长按检测?

    在电子产品中经常用到按键,尤其是经常需要MCU判断短按和长按这两种动作,本篇我们来专门聊下这个话题。只谈理论太无聊,我们还是结合着实际应用来说明。之前写过一篇关于《CH573第一篇:实现自拍杆蓝牙遥控器1》的文章,例子默认的功能是蓝牙连接后不断的发送数据,从而不断的拍照。而实际中的遥控器通常是按一次按键,控制一次,我们在来实现该功能。板子上只有两个按键,一个是RESET按键,一个是DOWNLOAD按键,我们使用DOWNLAOD按键,按键的一端接GND,另外一端接CH573的PB22引脚。原理图中有一个NC的C5,但是实际板子上我却没有找到它,可能是版本不一致。提前说明一下:CH573的代码里跑了TMOS(TaskManagementOperatingSystem),可以理解为一个简单的操作系统,所以下面的代码一般的裸机代码看着略有不同,不过核心思想都是一样的,用在其他地方也很容易移植,只需要将其中的定时器部分改写即可。最初我是这么做的,把PB22配置为上拉输入,开启下降沿中断,在中断服务函数里,启动一个事件,执行蓝牙发送。代码如下:voidKey_Init() { GPIOB_Mode

  • Redis的主从集群环境搭建

    三台主机搭建Redis的三对主从服务器集群环境准备host1:192.168.1.9:6379           192.168.1.9:6380 host2:192.168.1.106:6379           192.168.1.106:6380 host3:192.168.1.110:6379           192.168.1.110:6380 注意: (1)在建立redis的cluster环境时必须清空所有redis服务的所有key-value数据,没有任何数据 (2)每个redisnode节点采用相同的硬件配置、相同的密码1.分别开启三台主机的6379和6380两个端口,需给redis配置两个独立的配置文件,以host1为例1)给redis配置6379监听端口  [root@localhost~]#vim/app/redis/etc/redis.conf     ……   bind192.168.1.9 #绑定ip     ……   port6379 #绑定6379端口   ……   cluster-enabledyes #开启redis的集群功能   ……   

  • 项目现场部署EasyNVS 出现 EasyNVR 无法查看对应通道问题排查

    EasyNVS是EasyNVR的管理平台,可以远程控制多台EasyNVR。部署EasyNVS后,客户端通过EasyNVS云管理平台上的EasyNVR列表、EasyGBS列表以及每个网络摄像头的在线状态和快照,获取对应摄像头的视频流数据远程运维,同时实现按需直播,降低网络宽带及服务器压力。在部分现场,客户发现配置EasyNVS后无法起作用,EasyNVR上线但是无法查看对应通道。因此我们先查看EasyNVR中的配置如下:发现其中的用户名并不是管理员,与EasyNVS的配置不匹配,因此此处将用户名修改为管理员。再次查看EasyNVS通道是否上来,则可以看到已经正常显示所有通道了。区别于市场上其它的视频大数据运营产品,EasyNVS云管理平台具备众多独特优势特性:包括通过设备端简单的接口调用,可快速实现设备及平台接入,有效缩短开发周期。同时,EasyNVS云管理平台的视频解决方案会更加的全面,技术上面也更加先进,可用在多方面场景实现,例如帮助校园进行精准管理,构建和谐校园,进行高速车牌识别、人脸识别视频大数据分析等,因此如果大家有以上类似的需求,均可使用EasyNVS进行测试。

  • Android MediaPlayer 音频倍速播放,调整播放速度

    本文链接:AndroidMediaPlayer音频倍速播放,调整播放速度现在市面上的很多音视频App都有倍速播放的功能,例如把播放速度调整为0.5、1.5、2倍等等。从AndroidAPI23(AndroidM)开始,MediaPlayer支持调整播放速度。 使用的方法是setPlaybackParams,传入一个代表播放属性的类PlaybackParams。本文介绍如何使用MediaPlayer调整播放速度。MediaPlayer.setPlaybackParams说明播放速度设置在PlaybackParams对象中,再将此对象传入setPlaybackParams。setPlaybackParams是一个native方法。 如果MediaPlayer没有准备(在prepared之前),调用此方法并不会改变MediaPlayer的状态。 在MediaPlayer成功prepare之后,如果设置的速度为0,相当于调用了pause方法;如果设置速度不为0,相当于调用了start方法。异常情况如果MediaPlayer没有初始化或者已经被释放,即处于Idle或End状态,调用setPlay

  • 《Linux 高级路由与流量控制手册(2012)》第九章

    更多奇技淫巧欢迎订阅博客:https://fuckcloudnative.io译者序本文内容来自LinuxAdvancedRouting&TrafficControlHOWTO[1](2012),这是一份在线文档(小书),直译为《Linux高级路由与流量控制手册》。本文翻译第九章Chapter9.QueueingDisciplinesforBandwidthManagement[2]。这份文档年代略久,但qdisc部分整体并未过时,并且是我目前看过的内容最详实、可读性最好的tcqdisc教程。tc/qdisc是Cilium/eBPF依赖的最重要的网络基础设施之一。由于译者水平有限,本文不免存在遗漏或错误之处。如有疑问,请查阅原文。以下是译文。初次发现Linux的这些功能时,我感到无比震惊。Linux的带宽管理能力足以媲美许多高端、专用的带宽管理系统(high-enddedicatedbandwidthmanagementsystems)。1.队列(Queues)和排队规则(QueueingDisciplines)通过对包进行排队(queuing),我们可以决定数据的发送方式(t

  • 搬砖武士|手把手教你在容器服务 TKE 上使用 LB直通 Pod

    roc,腾讯工程师,负责腾讯云TKE的售中、售后的技术支持,根据客户需求输出合理技术方案与最佳实践,为客户业务保驾护航。什么是LB直通Pod?Kubernetes官方提供了NodePort类型的Service,即给所有节点开一个相同端口用于暴露这个Service,大多云上LoadBalancer类型Service的传统实现也都基于NodePort,即LB后端绑各节点的NodePort,LB接收外界流量,转发到其中一个节点的NodePort上,再通过Kubernetes内部的负载均衡,使用iptables或ipvs转发到Pod:TKE默认的LoadBalancer类型Service与默认的Ingress也都是这样实现的,但目前也支持了LB直通Pod的方式,即LB后端直接绑PodIP+Port,不绑节点的NodePort: 为什么需要LB直通Pod? LB直接绑NodePort来实现云上的Ingress或LoadBalancer类型Service是最简单通用的方法,那为什么有了这种实现还不够,还要搞个LB直通Pod的模式?首先,我们分析下传统NodePort实现方式存在的一些问题:流量从L

  • 微信域名检测API接口PHP代码

    从这里可以看出,检测域名是否被微信屏蔽,是这里的核心。但是在网上搜索和查看微信的文档,微信官方没有提供相关的查询方法。分享一个接口地址,分享给有需要的朋友。<?php //您要检测的域名 $url='https://xgdb.net'; //您的令牌 $token='e245b6542564b812b5205f872a861fe7'; $content=get_check($token,$url); $data=json_decode($content,true); if($data['code']==200){ echo"域名正常"; }else{ echo$data['msg']; } functionget_check($token,$domain){ $url='http://check.uomg.com/api/urlsec/vx?'; $url.=http_build_query(array( 'token'=>$to

  • 个人年度总结及AWD线下赛复盘 拖了很久

    --------------------------------本文来自一个非常努力认真的同学——JohnnySuen——的一次总结。------------------------------ 今天记录下第一次参加某全国awd比赛后的一些感想以及学到的些许皮毛知识,也算是对自己自学了小一年的学习成果的检验,防守方面由W.B战队防守队员记录,其他由笔者记录。开头有一小段水文警告~(不想看可以直接跳到AWD线下赛介绍)与网安的渊源记得初中呢会儿,就觉得黑客很酷,自己加了很多qq群,呢个时候差不多是09,10年吧,不知道我玩的时候1433抓鸡算不算过时了。反正呢会儿就是拿着****hacker论坛的1433抓鸡工具,搞个纯真IP库一顿扫,呢会儿算已经玩过葛X写的某鸽子了,呢会儿很费解,不懂为什么马传给对方,自己这里不上线,后来才了解到原来自己所处内网,需要做端口转发,呢个时候还是3322花生域名哈哈哈,做了转发,对方打开才能上线,后来又玩3389端口爆破,直到现在学了网络安全法,才了解到原来这都是违法的!!!到现在还心有余悸。之后初中毕业,反正后面书也没念好。后来只顾着减肥了,也就没继续学

  • tf.unstack

    tf.unstack( value, num=None, axis=0, name='unstack' )复制将秩为R张量的给定维数分解为秩为(R-1)张量。通过沿着轴维对num张量进行切分,从值中解压缩num张量。如果没有指定num(默认值),则从值的形状推断它。如果value.shape[axis]未知,将引发ValueError。例如,给定一个形状张量(A,B,C,D);如果axis==0,那么输出中的第i张量就是切片值[i,:,:,:],而输出中的每个张量都有形状(B,C,D)。(注意,与split不同的是,未打包的维度已经没有了)。如果axis==1,则输出中的第i张量为切片值[:,i,:,:],输出中的每个张量都有形状(A,C,D)等。这是堆栈的反面。参数:value:一个秩为R的>0张量要被解压。num:一个int类型,一个整型数。尺寸轴的长度。如果没有(默认值)就自动推断。axis:一个整型数。沿着整型数展开堆栈。默认为第一个维度。负值环绕,所以有效范围是[-R,R]。name:操作的名称(可选)。返回值:张量对象的列表从值中分解。异常:Val

  • 2016前端开发者调查结果

    最流行的JS库和框架主要看绿色柱和橙色柱即可,绿色相当于知名度,橙色相当于使用程度。使用最多的库和框架:jquery,underscore,lodash,angular1,react他们可以说是前端开发者的必备技能了。再看下使用程度不高,但知名度高的,主要有:angular2,ember,polymer,vue.js,meteorjs,knockout他们还没有被普遍应用,但很受关注,代表了技术趋势,可以了解一下。JS模块绑定器从图上看,主要有3部分:webpack don'tuse不使用browserify模块化的JS开发方式越来越流行,今年已经有三分之二的人在使用ModuleBundler,只有三分之一的人还没使用,don'tuse这部分较2015的调查结果下降了21.75%。ModuleBundler中用的最多的是webpack,他发展很快,今年已经第一,并且较2015年提升了31.11%。JSLinting代码检验可以看到,JS的代码质量已经很受重视,77%的人都使用了检查工具。其中eslint普及度最高。JS单元测试在单元测试方面,用与不用的人数差不多,但

  • Oracle计算时间差函数

    1、months_between(date1,date2) 返回两个日期之间的月份的差值(1)、如果两个日期月份内天数相同,或者都是某个月的最后一天,返回一个整数。否则,返回数值带小数selectmonths_between(sysdate,addtime)asdiff_monthfromtest6复制2、interval 时间间隔函数Oracle语法:  INTERVAL'integer[-integer]'{YEAR|MONTH}[(precision)][TO{YEAR|MONTH}] 该数据类型常用来表示一段时间差,注意时间差只精确到年和月.precision为年或月的精确域,有效范围是0到9,默认值为2. i、selectINTERVAL'123-2'YEAR(3)TOMONTHfromdual复制表示:123年2个月,"YEAR(3)"表示年的精度为3,可见"123"刚好为3为有效数值,如果该处YEAR(n),n<3就会出错,注意默认是2. ii、selectINTERVAL'4

  • iOS 组件化及二进制化的探索

    组件化的优缺点 组件化的拆分 组件与组件之间如何进行通讯(路由) 从Cocopods拉取代码的过程 远程索引库里很多的.spec文件,该文件记录了很多内容,如用户名,框架名称,描述,框架的地址 Podfile文件是拉取框架源码的配置文件,podinstall命令会根据Podfile中配置来拉取框架源码 Podsetup命令会把远程索引库拷贝到本地,在本地还有一个检索文件 执行podsearch命令过程,并不是直接从远程索引库查找,它是从本地的检索文件,该检索文件是以键值对的形式存在。在本地索引库中会找到对应的.spec文件,.spec文件中有框架源码的地址,直接从这个地址来下载了。 Podrepo命令可以查看本地的索引库列表 创建本地私有库 Podlibcreate命令是创建lib库的模板 更正:创建demo的位置,应该选择YES 创建成功后的目录结构如下: 接下来,我们可以把拆分的代码放到如下这个目录下: 放入代码后的目录结构如下: Xcode中的工程并不会显示新加入的文件,如下图所示: 我们只需要在命令行工具中cd到Pods工程所在

  • Centos小技巧

    1.修改终端显示颜色exportPS1='[\[\e[35;40m\]\u\[\e[32;40m\]@\[\e[36;40m\]\h\[\e[35;40m\]\w\[\e[37;40m\]]\[\e[32;40m\]\$\[\e[37;40m\]' #写入到$HOME/.bashrc复制2.ssh登录服务器很慢echo'UseDNSno'>>/etc/ssh/sshd_config复制3.查看已经删除的文件,空间有没有释放,没有的话kill掉pidlsof-n|grepdeleted|awk'{print$2}'|xargskill-9复制

  • Chrome Console Cookie 控制台操作命令

    Chrome控制台 Cookie操作命令 读: document.cookie;  写:document.cookie["Key"]="value";

  • 前端gitLab ci/cd搭建

    目录 一.概念介绍 1.1gitlab-ci&&自动化部署工具的运行机制 1.2自动化部署给我们带来的好处 二.知识预备 2.1gitlab-ci涉及的抽象概念(Runner/PipeLine/Executor/Job) 2.2YML文件的基本语法规则 2.3.gitlab-ci.yml配置的特定关键字 三.CI实战 3.1编写一个gitlab-ci的“helloworld” 四.坑点总结 五.gitlab-ci进阶 5.1YML的片段复用和模块化 5.2gitlab-ci提供的其他配置关键字复制 一.概念介绍 1.1gitlab-ci&&自动化部署工具的运行机制 以gitlab-ci为例: (1)通过在项目根目录下配置.gitlab-ci.yml文件,可以控制ci流程的不同阶段,例如install/检查/编译/部署服务器。gitlab平台会扫描.gitlab-ci.yml文件,并据此处理ci流程 (2)ci流程在每次团队成员push/merge后之后触发。每当你push/merge一次,gitlab-ci都会检查项目下有没有.gitlab

  • 转:IT公司的十大内耗,别说你公司没有!

    这篇文章是以前看到的,觉得写得非常好,转载在自己BLOG作为记录。原文:http://www.pmtoo.com/news/2015/0108/7260.html。 当企业发展到一定时期时,会不可避免地沾染上“大公司病”。电信制造领域巨头华为也不例外,这是华为一位底层员工在10年所写的华为的十大内耗。在过去的四年里,创始人任正非提出华为狼性文化的背后,还要有勇于追赶的乌龟精神和管理组织上的眼镜蛇特质,以此激活华为人的斗志,规避“大公司病”。   四年过去了,华为今日的成绩证明了任正非的努力是多么富有远见。今天我们再次重温这十大内耗,是因为它像一面镜子,能照出公司的管理问题,对当下传统企业转型仍具有十分重大的意义。知耻而后勇,知不足而奋进,敢于面对,勇于变革,才有新生!   最近和很多中基层优秀人才交流,面对复杂低效的现状,普遍有种无力感。本人和华为各阶层,下至贩夫走卒,上至皇亲贵胄都有着广泛的接触,也经历过很多领域和业务,相信视角也并非只是管中窥豹。同时也希望下文不至于引起普遍的反感,或者带来“不能生鸡蛋,凭什么评价鸡蛋”之类的指责。   无比厚重的部门墙   一般产品出了问题,我们都

  • Windbg命令系列---!chkimg

    !chkimg扩展命令通过将可执行文件的映像与符号存储库或其他文件存储库上的副本进行比较来检测可执行文件映像中的损坏。 语法 !chkimg[Options][-mmwLogFileLogOptions][Module] 参数 Options 以下选项的任意组合: -pSearchPath 在访问符号服务器之前,递归地搜索SearchPath路径。 -f 修复Image中的错误。每当扫描检测到符号存储中的文件与内存中的图像之间存在差异时,符号存储中的文件内容将复制到Image上。如果正在执行实时调试,则可以在执行!chkimg-f扩展之前创建一个转储文件。 -nar防止移动符号服务器上文件的映射。默认情况下,当文件副本位于符号服务器上并映射到内存时!chkimg移动符号服务器上文件的Image。但是,如果使用-nar选项,则不会移动服务器上的文件映像。已在内存中的可执行映像(即正在扫描的映像)将被移动,因为调试器总是重新定位它加载的映像。仅当操作系统已移动原始映像时,此开关才有用。如果Image没有被移动!chkimg和调试器将移动Image。很少使用这个开关。-ssSectionNa

  • Echarts无数据时只显示文字不显示动画

    只需要在option中加入如下代码即可: noDataLoadingOption:{                       text:'暂无数据',                       effect:'bubble',                       effectOption:{&n

  • 自定义KVO

    1.不调用实例变量的方法 2.动态生成子类(利用runtime生成:申请类,添加一些方法-set-class等方法,注册类) ****常量类型不能添加观察者 p.p1{margin:0;font:11pxMenlo;color:rgba(0,0,0,1);background-color:rgba(255,255,255,1);min-height:13px} p.p2{margin:0;font:11pxMenlo;color:rgba(163,21,21,1);background-color:rgba(255,255,255,1)} p.p3{margin:0;font:11pxMenlo;color:rgba(0,0,255,1);background-color:rgba(255,255,255,1)} p.p4{margin:0;font:11pxMenlo;color:rgba(0,0,0,1);background-color:rgba(255,255,255,1)} span.s1{color:rgba(0,0,255,1)} span.s2{color:rgba(0

  • 洛谷 P1004 方格取数 - DP

    洛谷P1004方格取数 题目链接:洛谷P1004方格取数 算法标签:动态规划(DP) 题目 题目描述 设有\(N\timesN\)的方格图\((N\le9)\),我们将其中的某些方格中填入正整数,而其他的方格中则放入数字0。如下图所示(见样例): A 00000000 001300600 00007000 000140000 021000400 001500000 014000000 00000000 B 复制 某人从图的左上角的\(A\)点出发,可以向下行走,也可以向右走,直到到达右下角的\(B\)点。在走过的路上,他可以取走方格中的数(取走后的方格中将变为数字0)。 此人从\(A\)点到\(B\)点共走两次,试找出2条这样的路径,使得取得的数之和为最大。 输入格式 输入的第一行为一个整数\(N\)(表示\(N\timesN\)的方格图),接下来的每行有三个整数,前两个表示位置,第三个数为该位置上所放的数。一行单独的0表示输入结束。 输出格式 只需输出一个整数,表示2条路径上取得的最大的和。 输入输出样例 输入#1 8 2313 266 357 4414 5221 564 6315

  • JS原型与原型链终极详解

    一.普通对象与函数对象  JavaScript中,万物皆对象!但对象也是有区别的。分为普通对象和函数对象,Object,Function是JS自带的函数对象。下面举例说明 functionf1(){}; varf2=function(){}; varf3=newFunction('str','console.log(str)'); varo3=newf1(); varo1={}; varo2=newObject(); console.log(typeofObject);//function console.log(typeofFunction);//function console.log(typeofo1);//object console.log(typeofo2);//object console.log(typeofo3);//object console.log(typeoff1);//function console.log(

相关推荐

推荐阅读