上个星期同事做一个业务模块,需要将一个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的时间间隔太大,说明阻塞产生了,想法就是这样,说干就干。。。
1 System.Threading.Tasks.Task.Factory.StartNew(() => 2 { 3 try 4 { 5 var content = File.ReadAllText(@"D:\20151120-13\20151120-13.log", Encoding.Default); 6 7 Console.WriteLine("主线程 读取txt内容完毕,准备插入redis {0}", DateTime.Now); 8 var result = CacheUtil.CacheClient.HashSet("myredis", "mykey", content); 9 10 Console.WriteLine("主线程 插入Redis 成功:{0} , {1}", result, DateTime.Now);11 12 var r = CacheUtil.CacheClient.HashGet<string>("myredis", "mykey");13 14 Console.WriteLine("主线程,读取Redis成功,总长度{0}, {1}", r.Length, DateTime.Now);15 }16 catch (Exception ex)17 {18 Console.WriteLine(ex.Message);19 }20 });21 22 System.Threading.Tasks.Task.Factory.StartNew(() =>23 {24 try25 {26 var result = CacheUtil.CacheClient.Add<string>("myfruits", "asdfasdfasdfasdfasd");27 28 for (int i = 0; i < 10; i++)29 {30 var content = CacheUtil.CacheClient.Get<string>("myfruits");31 32 Console.WriteLine("第 {0} 次读取 {1} ,{2}", i, content, DateTime.Now);33 34 Thread.Sleep(1000);35 }36 }37 catch (Exception ex)38 {39 Console.WriteLine(ex.Message);40 }41 });
读取string的线程被Hashset阻塞了6s之多,很恐怖,这个就属于典型的慢查询,它的慢果然阻塞了其他client,接下来就拿着问题找同事,第一个想
法就是问同事为什么要存这么大的数据,得知为了避免海量运算必须要存这么大数据之后,没撤只能从假定80M的数据量做优化,第二个想法就是拆,
既然是80M的数据,我可以拆成8份10M的数据,这样有两个好处,第一个不会太拖长Hset的时间,第二个尽最大努力不阻塞其他client,但是呢,同
事不想改动代码,还要问我还有其他解决方案不???然后我就提了一个下下策,隔离你的缓存业务,既然你都是存储大数据,那我专门给你开一个
redis去存储缓存,几秒钟就几秒钟吧,估计对他业务还能够承受,我可不能让我的主redis因为这个吊毛业务挂了。。。
二:慢查询日志
自从发生这个事情之后,我就有一个想法了,我是不是也需要记录一下主redis中那些“慢操作”的命令,然后找到相关的业务方,不然的话,阻塞
就不好玩了。然后就直接在redis手册中就找到了相关的命令。
SLOWLOG subcommand [argument]什么是 SLOWLOGSlow log 是 Redis 用来记录查询执行时间的日志系统。查询执行时间指的是不包括像客户端响应(talking)、发送回复等 IO 操作,而单单是执行一个查询命令所耗费的时间。另外,slow log 保存在内存里面,读写速度非常快,因此你可以放心地使用它,不必担心因为开启 slow log 而损害 Redis 的速度。设置 SLOWLOGSlow 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是用“双向链表”实现的。
- 21楼dotnetgeek
- 一线,有遇到多线程同时读写redis,然后保持数据完整性的场景吗?
- Re: 一线码农
- @dotnetgeek,多线程对redis来说还是单线程的,你要是有读出来再修改的这类情况,可以用multi或者lua脚本,终极解决方案哈。
- 20楼Tryparse
- 膜拜
- Re: 一线码农
- @Tryparse,谢谢
- 19楼永远的麦子
- 这个系列要支持一下。
- 18楼小宝er
- 慢查询是木有解决办法了?只能查日志了吗?
- Re: 一线码农
- @小宝er,因为这个性能瓶颈在带宽,在现有硬件条件下最好的办法就是不要有获取大数据,要么就只能拆开读取了哦。
- 17楼xiayu324
- 又来膜拜一线哥哥了,流口水,虽然还是看不懂,不过我已经开始学h5了,而且我上面有高人指点,巴啦啦啦#128514;
- 16楼Yq.W
- 不明觉历
- Re: 一线码农
- @Yq.W,哈哈~
- 15楼watermalon
- 帮大忙了找了好久redis方面的知识,灰常感谢博主的分享.
- Re: 一线码农
- @watermalon,能帮助到你,还是很开心的哈。
- 14楼孤月狼
- 学习了,博主厉害!
- 13楼钻葛格
- 膜拜
- Re: 一线码农
- @钻葛格,谢谢哈
- 12楼天生讨女生喜欢
- 很生动 看着有味 故事写得不错
- Re: 一线码农
- @天生讨女生喜欢,我的风格哈~~
- 11楼我爱吃土豆
- 那个数据太大的 考虑一下压缩 如果是文字性的 压缩比可以达10倍以上 再使用合适的压缩协议(比如protobuf/fastinfoset等),压缩比甚至可以达到上百倍,我曾经压缩过一个56M的文字到600K,你感受一下。
- Re: 一线码农
- @我爱吃土豆,实不相瞒,这80M还是protobuf压缩过后的大小,也尝试过gzip,fastinfoset到没有试过,我试试看。
- 10楼kevin0227
- 终于可以看到redis方面的文章了,哈哈,写的不错,期待楼主后面的文章
- 9楼@清道夫
- 虽然没做开发了,但一线的文章是一定要看的
- Re: 一线码农
- @@清道夫,厂长,谢谢支持
- 8楼我和小菜
- 厉害
- Re: 一线码农
- @我和小菜,谢谢夸奖~
- 7楼iImax
- 受教,以后用redis就有参考了
- 6楼深蓝医生
- Redis能够存储这么大的数据,不错!
- 5楼xiayu324
- 又来膜拜一线哥哥了,流口水,虽然还是看不懂,不过我已经开始学h5了,而且我上面有高人指点,巴啦啦啦#128514;
- Re: 一线码农
- @xiayu324,好一个小正太~
- 4楼莴笋Ho
- 谢谢分享~80MB的数据,是一次读取呢?
- 3楼changhongmiao
- 楼主 你好,,我这里有个问题,希望可以指点一下,我目前在Ubuntu上部署了Redis Cluster (v3.0.2),3主3从(3从挂在同一主下),如主节点M1,下属有S1,S2,S3;额外有M2,M3为空槽主节点,只做投票用,,这个时候集群运行是正常的,然后将主节点M1关闭的时候,我希望S1,S2,S3其中一个可以自动接管M1的所有槽,并成为新主节点,,但是现状是:, 1. 关闭M1后,其余5台集群的节点均标识M1为FAIL状态, 2. 主节点FAIL之后,从节点发送的消息可以选举出最先投票的从节点(currentEpoch),,不知道还欠缺什么必要的条件?仍然无法完成自动切换
- 2楼bruce.cui
- 学习学习!!
- Re: 一线码农
- @bruce.cui,谢谢
- 1楼Code的那些事
- 全能
- Re: 一线码农
- @Code的那些事,期待你的分享哈