15天玩转redis —— 第七篇 同事的一次缓存操作引起对慢查询的认识

上个星期同事做一个业务模块,需要将一个80M的数据存入到redis缓存中,想法总是好的,真操作的时候遇到了HSet超时,我们使用的是C#的

StackExchange.Redis驱动。

 <redisCacheClient allowAdmin="true" ssl="false" connectTimeout="5000" abortConnect="false" database="0">
 <hosts>
 <add host="192.168.202.129" cachePort="6379"/>
 </hosts>
 </redisCacheClient>

由于封装代码啥的都是网上下载的,第一反应就是把上面的“connectTimeout”设为5000 * 60 =3min,但是丝毫没有用处,也就是3min没有起

到作用,码蛋的,这可怎么办???只能硬着头皮去看StackExchange的源码,终于在1个小时的地毯式搜索中找到了两处凶杀现场,如下所示:


接着我发现其中的 timeoutMilliseconds 和 this.multiplexer.RawConfig.ResponseTimeout的取值决定着是否会抛异常,感谢感谢,接下来我继续

顺藤摸瓜,找到了两个属性的赋值处。


当我看到了上面的syncTimeout.GetValueOrDefault(1000)的时候一颗悬着的心也就放下了,也懒得改了,直接将这里的1000改成1000*60*5

就好啦,commit代码后让同事再运行下看看效果。。。终于拨开迷雾见青天,数据出来啦,遗憾的是,读写操作需要耗时3s,虽然问题表面上看似

解决了,但是问题来了,3s延时真的不是什么好事情,我们都知道redis是单线程的,那就意味着什么??? 意味着这3s的时间内其他redis客户端

是阻塞的。。。虽然心里是这么想的,但是还是存有一点侥幸心理觉得不是这样的,不过还是决定做一个实验看一看。

一:阻塞演示

  我决定开一个线程将一个txt中140M的数据插入到redis的hashset中,同时我开另一个线程1秒钟一次的从string中获取数据,同时记录下获

取时间,如果获取string的时间间隔太大,说明阻塞产生了,想法就是这样,说干就干。。。

System.Threading.Tasks.Task.Factory.StartNew(() =>
 {
 try
 {
 var content = File.ReadAllText(@"D:\20151120-13\20151120-13.log", Encoding.Default);

 Console.WriteLine("主线程 读取txt内容完毕,准备插入redis {0}", DateTime.Now);
 var result = CacheUtil.CacheClient.HashSet("myredis", "mykey", content);

 Console.WriteLine("主线程 插入Redis 成功:{0} , {1}", result, DateTime.Now);

 var r = CacheUtil.CacheClient.HashGet<string>("myredis", "mykey");

 Console.WriteLine("主线程,读取Redis成功,总长度{0}, {1}", r.Length, DateTime.Now);
 }
 catch (Exception ex)
 {
 Console.WriteLine(ex.Message);
 }
 });

 System.Threading.Tasks.Task.Factory.StartNew(() =>
 {
 try
 {
 var result = CacheUtil.CacheClient.Add<string>("myfruits", "asdfasdfasdfasdfasd");

 for (int i = 0; i < 10; i++)
 {
 var content = CacheUtil.CacheClient.Get<string>("myfruits");

 Console.WriteLine("第 {0} 次读取 {1} ,{2}", i, content, DateTime.Now);

 Thread.Sleep(1000);
 }
 }
 catch (Exception ex)
 {
 Console.WriteLine(ex.Message);
 }
 });


读取string的线程被Hashset阻塞了6s之多,很恐怖,这个就属于典型的慢查询,它的慢果然阻塞了其他client,接下来就拿着问题找同事,第一个想

法就是问同事为什么要存这么大的数据,得知为了避免海量运算必须要存这么大数据之后,没撤只能从假定80M的数据量做优化,第二个想法就是拆,

既然是80M的数据,我可以拆成8份10M的数据,这样有两个好处,第一个不会太拖长Hset的时间,第二个尽最大努力不阻塞其他client,但是呢,同

事不想改动代码,还要问我还有其他解决方案不???然后我就提了一个下下策,隔离你的缓存业务,既然你都是存储大数据,那我专门给你开一个

redis去存储缓存,几秒钟就几秒钟吧,估计对他业务还能够承受,我可不能让我的主redis因为这个吊毛业务挂了。。。

二:慢查询日志

  自从发生这个事情之后,我就有一个想法了,我是不是也需要记录一下主redis中那些“慢操作”的命令,然后找到相关的业务方,不然的话,阻塞

就不好玩了。然后就直接在redis手册中就找到了相关的命令。

SLOWLOG subcommand [argument]

什么是 SLOWLOG

Slow log 是 Redis 用来记录查询执行时间的日志系统。

查询执行时间指的是不包括像客户端响应(talking)、发送回复等 IO 操作,而单单是执行一个查询命令所耗费的时间。

另外,slow log 保存在内存里面,读写速度非常快,因此你可以放心地使用它,不必担心因为开启 slow log 而损害 Redis 的速度。

设置 SLOWLOG

Slow log 的行为由两个配置参数(configuration parameter)指定,可以通过改写 redis.conf 文件或者用 CONFIG GET 和 CONFIG SET 命令对它们动态地进行修改。

第一个选项是 slowlog-log-slower-than ,它决定要对执行时间大于多少微秒(microsecond,1秒 = 1,000,000 微秒)的查询进行记录。

比如执行以下命令将让 slow log 记录所有查询时间大于等于 100 微秒的查询:

CONFIG SET slowlog-log-slower-than 100

而以下命令记录所有查询时间大于 1000 微秒的查询:

CONFIG SET slowlog-log-slower-than 1000

另一个选项是 slowlog-max-len ,它决定 slow log 最多能保存多少条日志, slow log 本身是一个 FIFO 队列,当队列大小超过 slowlog-max-len 时,最旧的一条日志将被删除,而最新的一条日志加入到 slow log ,以此类推。

以下命令让 slow log 最多保存 1000 条日志:

CONFIG SET slowlog-max-len 1000

从上面这段话中,大概看出了两个属性: slowlog-log-slower-than 和 slowlog-max-len,为了测试方便,我就不config set了,直接改掉

redis.conf文件即可。。。

# The following time is expressed in microseconds, so 1000000 is equivalent
# to one second. Note that a negative number disables the slow log, while
# a value of zero forces the logging of every command.
slowlog-log-slower-than 0

# There is no limit to this length. Just be aware that it will consume memory.
# You can reclaim memory used by the slow log with SLOWLOG RESET.
slowlog-max-len 10

然后我简单测试一下,所有command都会被记录到slowlog里面去了,下图中的红色框框就是comand的执行时间。


有了这个,我现在是不是可以找到所有生产线上哪些慢的command命令呢???这样大家就不会扯皮了。。。最后我们简单看下他们的源码,从源码

中你可以看到其实slowlog是用List实现的,而我们也知道在Redis中List是用“双向链表”实现的。

时间: 2023-12-03 16:33:01

15天玩转redis —— 第七篇 同事的一次缓存操作引起对慢查询的认识的相关文章

15天玩转redis —— 第十篇 对快照模式的深入分析

我们知道redis是带有持久化这个能力了,那到底持久化成到哪里,持久化成啥样呢???这篇我们一起来寻求答案. 一:快照模式 或许在用Redis之初的时候,就听说过redis有两种持久化模式,第一种是SNAPSHOTTING模式,还是一种是AOF模式,而且在实战场景下用的最多的 莫过于SNAPSHOTTING模式,这个不需要反驳吧,而且你可能还知道,使用SNAPSHOTTING模式,需要在redis.conf中设置配置参数,比如下面这样: # Save the DB on disk: # # sa

15天玩转redis —— 第六篇 有序集合类型

今天我们说一下Redis中最后一个数据类型 "有序集合类型",回首之前学过的几个数据结构,不知道你会不会由衷感叹,开源的世界真好,写这 些代码的好心人真的要一生平安哈,不管我们想没想的到的东西,在这个世界上都已经存在着,曾几何时,我们想把所有数据按照数据结构模式组成 后灌输到内存中,然而为了达到内存共享的方式,不得不将这块内存包装成wcf单独部署,同时还要考虑怎么序列化,何时序列互的问题,烦心事太多 太多...后来才知道有redis这么个吊毛玩意,能把高级的,低级的数据结构单独包装到一

15天玩转redis —— 第八篇 你不得不会的事务玩法

我们都知道redis追求的是简单,快速,高效,在这种情况下也就拒绝了支持window平台,学sqlserver的时候,我们知道事务还算是个比较复杂的东西, 所以这吊毛要是照搬到redis中去,理所当然redis就不是那么简单纯碎的东西了,但是呢,事务是我们写程序无法逃避的场景,所以redis作者折衷的写了个简 化版的事务机制,下面我来扯一下它的蛋蛋.   一: 事务实战 具体到事务是什么,要保证什么...这个我想没必要说了,先不管三七二十一,看一下redis手册,领略下它的魔力. 1. mult

15天玩转redis —— 第五篇 集合对象类型

这篇我们来看看Redis五大类型中的第四大类型:"集合类型",集合类型还是蛮有意思的,第一个是因为它算是只使用key的Dictionary简易版, 这样说来的话,它就比Dictionary节省很多内存消耗,第二个是因为它和C#中的HashSet是一个等同类型,废话不多说,先看redis手册,如下: 上面就是redis中的set类型使用到的所有方法,还是老话,常用的方法也就那么四个(CURD)... 一: 常用方法 1. SAdd 这个方法毫无疑问,就是向集合里面添加数据,比如下面这样,

15天玩转redis —— 第三篇 无敌的列表类型

据说60%的人使用redis看重的是redis中的list类型,那这个list有什么用呢???不用我说大家都明白,做队列使用呗,为什么用它呢,很简单呗, 因为有了它我就不需要专门的MQ产品啦,比如说RabbitMQ,ActiveMQ等等...对吧. 一:实战 先我们还是看一下List列表给我们提供的方法. 这些方法还是稀里糊涂的有一些的,没关系,做队列使用的话,常用的也就四个:LPOP,LPUSH,RPOP,RPUSH,从这四个单词上面,你应该就明白这 有点像数据结构中的"双端队列",

15天玩转redis —— 第十一篇 让你彻底了解RDB存储结构

接着上一篇说,这里我们来继续分析一下RDB文件存储结构,首先大家都知道RDB文件是在redis的"快照"的模式下才会产生,那么如果 我们理解了RDB文件的结构,是不是让我们对"快照"模式能做到一个心中有数呢??? 一:RDB结构剖析 首先呢,我们要对RDB文件有一个概念性的认识,比如下面画的图一样: 从图中,我们大概看到了RDB文件的一个简要的存储模式,但为了更好的方便对照,我准备save一个empty database,对比一下看看效果: 然后我们用winHex打

15天玩转redis —— 第一篇 开始入手

一:Redis是什么? 这个我想怎么总结呢,突然发现再好的解释也没有redis官网解释的好,它的解释已经达到超宇宙的级别了...不信你可以看看. 人家也说了,redis是个内存存储的数据结构服务器,这个听起来有多么牛逼啊....一说到数据结构,第一反映就会想到C#中那些dictionary,hashset,list, SortDictionary等等...然后你也会想到这些数据结构有如下一些缺点. 比如: 1. dictionary不能在多台机器中共享内存,除非你用wcf把dictionary单

15天玩转redis —— 第九篇 发布/订阅模式

本系列已经过半了,这一篇我们来看看redis好玩的发布订阅模式,其实在很多的MQ产品中都存在这样的一个模式,我们常听到的一个例子 就是邮件订阅的场景,什么意思呢,也就是说100个人订阅了你的博客,如果博主发表了文章,那么100个人就会同时收到通知邮件,除了这个 场景还能找到其他场景么,当然有啦,你想想,如果你要在内存里面做一个读写分离的程序,为了维持数据的完整性,你是不是需要保证在写入 的时候,也要分发到各个读内存的程序中呢?所以说场景还是很多的,在于你的挖掘~~~ 下面还是从基本命令入手: 一

15天玩转redis —— 第二篇 基础的字符串类型

我们都知道redis是采用C语言开发,那么在C语言中表示string都是采用char[]数组的,然后你可能会想,那还不简单,当我执行如下命令,肯定是直 接塞给char[]数组的. 如果你真的这么想的话,会有几个问题就要过来砍你了,先我们来找一个redis手册,http://doc.redisfans.com/ 第一:如果你每次都执行Append函数,那是不是redis的char[]每次都需要再次扩容,这样是不是每次都是耗时操作呢? 第二:如果你每次执行String中的StrLen,那redis底