想起来几年前挺火的前岛国国民女神学霸-小岛方晴子。当时替她说话的人都很惨,导师被逼自杀。她收到的压力侮辱不是常人可以想象的。但是她却坚强的活着,去年还出了书。我去日本的时候,下了新干线,前面有一群女学生,她们看到我了,立刻聚集成一团,一边看我一边说悄悄话。我才发现日本人穿的衣服基本就是黑,白,灰。他们也不穿羽绒服,女孩子大冬天都是光着腿。而我穿着黄绿色的羽绒服,确实像个怪胎。为什么来之前没人告诉我[大哭][大哭]。8年过去了,想起来还觉得尴尬。日本人是很爱背后说别人坏话的。所以我感谢我是个很普通的女孩子,而且生在中国。没人闲的没事去扒我之前做过的坏事。比如我很久前看不惯一个自己没能力还势利的混蛋,所以写了个程序刷爆了他的邮箱。再比如我自己写了个小程序循环注册一个网站参加抽奖,得了好多小东西。
但是女孩子天生就是很坚强的,女孩子为爱而活,其他的什么都扛的下。写《傲慢与偏见》的女作家简.奥斯汀,大家看到她写的书的女主角们大概都能想象到作者本人是个聪明,智慧,自己漂亮且有一个更漂亮的姐姐,又很幽默的女孩。但是她却在自己心爱的男孩子傍了个富婆之后终生未嫁,高富帅向她求婚她没同意。一生寄人篱下,与恋人死去不愿做他人妇的姐姐相依为命,心中的痛苦又与谁人说。30多岁开始患有严重的忧郁症,直到她得知自己心爱的男孩去世的消息,自己也郁郁而终。感恩自己运气好,可以快快乐乐做个普通人。
我们部门并发量最大的接口服务前段时间发生了几次业务端的流量猛增,扛不住的情况。瓶颈在缓存上。根本性的改造正在进行中。谈一谈这段时间由这个问题引发的思考。
首先说之前的架构确实很老了,现在直接负责这个服务的男神哥哥也很年轻,有问题是正常的。缓存选用的是乐视统一的couchbase集群,是个memcached升级版,已经实现了持久化,本质是一个文档型的数据库,有人评价其性能要超过mongoDB。然后乐视网封装了它,自己起名叫cbase,前面用moxi代理。实际上使用觉得其性能让人擦汗。
接口服务将数据库里的全量视频和专辑刷入缓存。缓存扛不住了都不会穿透DB。这里我只想说:如果咱们要是发现数据库可能要雪崩,做熔断,做隔离都是可取的。但是完全不用,要它作甚[汗]. 媒资接口是一个多维度的查询服务,缓存直接当DB用,而这个缓存的结构对数据的计算是很不利的。从数据库里取数据耗时一般也就是几ms。从缓存中取数据量级并没有减少,还会过量使用缓存造成cbase集群的高负载。而且mysql是有自己的缓存的,查询一点儿都不慢,加上索引,线上已经有的读写分离,其他成熟的技术,性能也不差。业务复杂性大大增加,业务处理的CPU计算量大大增加,实际性的缓存的高速也微乎其微。
话说到此,先比较一下mysql和memcached。
之前普遍的理解是关系型数据库自身庞大,处理过程非常耗时。但是随着mysql的优化,解析sql语句的时间还好。一般耗时的就是读,由于读写分离的技术,也还好。重要的是控制并发线程数,也就是连接数在100个以下。这个我自身在一个系统没上线的时候用线上服务器实际试验过。QPS可承受的压力在1w多。
来看看我们项目,接口服务前台11台服务器,平均每台QPS2k多,峰值在3k多,合起来不会超过4w。写的主库是单节点,压力很小。从库是三个节点的集群。DBA说从库可以承受QPS4w(我们用的是mariadb)。但是我们都直接访问memcached了。memcached集群运维说用一个key去压QPS可达到2.5w。实际上压测value大小在5k的,也就几k[狂汗],而我们项目中超过1k的占绝大多数。看文章说mysql5.7中使用InnoDB Memcached插件可实现QPS100w。阿里云开发了一个AliSQL,也是mysql官方版本的一个分支,说是性能还要好。
所以,要是我,宁愿不用memcached缓存,也不能不用DB啊。所以一般大家的使用方法是memcached缓存计算结果,采用最近最少使用算法或者是最不经常使用算法等失效策略,尽量少的缓存。第一次去取数据的时候查询DB,将结果缓存到memcached中。有数据更新或者删除,就删除memcached的相关记录。经典用法有经典用法的设计理念。
我们的cbase集群分配了500G内存,实际上只用到了80G。且不说在这种使用率的情况下的哈希分布,实际上memcache内存管理的原理是将内存分成大大小小的片段,
这种结构内存浪费本身就比较严重。如果我们的数据大数据比较多的话,这种内存的浪费就更明显。另外,如果数据块比较大,大数据比较多的时候,计算哈希地址发生碰撞的几率会增加。碰撞的地址需要rehash,增加了计算时间。
那将redis换成memcached会怎样?
Redis有一些高级功能,但是Redis是单线程,高级功能占据CPU, I/O操作会被阻塞,所以还是比较建议只作为一个k/v的缓存。举个例子,Redis不是支持有序集合嘛,如果取有序集合的一定范围的元素,它内部使用了skiplist,关于跳表的详细描述请看我的另一篇<看Lucene源码必须知道的基本规则和算法>。时间复杂度是log(n),还是需要计算的。而且由于这个单线程,Redis在处理100K以上1M以下的大数据的时候比memcached还是稍显逊色的。这个1M以下是怎么来的呢?memcached内存结构规定最大的value值只能达到1M了,而Redis可达到512M。
Redis不仅仅支持简单的k/v类型的数据,同事还提供list,set,zset,hash等数据结构的存储。这一点确实很有用。
Redis支持数据的备份,即master-slave模式的数据备份。不管是备份也好,集群间的数据同步也好,宕机后的aof恢复也好,现在主流的解决方法使用的都是操作日志。实现原理和mysql采用binlog的方式是一样的。在使用的时候要注意其延时状况。
Redis会在内存中长期存储所有的key。但它采用数据回收机制,能够将陈旧value从内存中删除以提供新value所必需的缓存空间。旧的数据只是内存中被删除,磁盘上还有。不会因为回收而影响命中。所以Redis没有最大过期时间限制。Memcached最大过期时间是一个月,否则会写入失败。
听说Redis是支持身份验证的,实际开发中没用过这个功能。
Redis的作者和我是一个风格的,什么都不想用现成的。去年夏天我自己剪了一次头发,剪得比较成功。后来又剪了一次,惨不忍睹,但是为了纪念这次失败,好几个月没换发型。Redis的作者管理IO用的不是memcached那样现成的libevent,而是自己封装了一个简单的AeEvent事件处理框架。内存管理也是一样,自己写了一个zmalloc.h和zmalloc.c,将内存大小存入内存块头部。用zmalloc代替malloc,zcalloc代替calloc,zrealloc代替realloc,zfree代替free。做C开发的就是高大上,想怎么分配内存就怎么分配内存。
综上所述,Redis可能可以解决部分问题,但不是终极解决方案。因为接口服务是基于多个维度来查找的,更合适用只存储和索引,不分词的搜索引擎,可以有多少内存吃多少内存,速度优势的原理和上面介绍的缓存都是一样的。为了更好的性能,我要实现自己的搜索引擎,具体规划请参考我的其他文章。