redis cluster的使用经验
发布日期:2016-4-18 22:4:5
redis cluster的使用经验 我马上要从有道离职。除了MSRA实习外,这是我人生第一份正式工作,但是它即将结束,所以在这个隆重的时刻自然是需要写点东西纪念一番。感性的文字不忙写,作为一个搞技术的,还是要先写点技术文章争取对同行有所帮助。所以第一篇就说redis3.0正式版刚发布,先说说redis cluster吧。 我在有道引入redis cluster是14年8月,到现在已经8个月了。在当时那个时间点,有道至少是词典在缓存这块的基础设施搭建还是比较薄弱的
当然由于我们也不把redis当数据库,只当做一个单纯的缓存,因此挂了的结果就是redis超时之后请求全落在下层存储上。感谢redis还是足够稳定,也感谢贵司的挫机器挂了这么多也没在redis所在机器上挂过,至少我印象中redis单点挂掉这种事情到现在还没发生,即使后来因为个人风格问题有的人写的服务是一旦redis挂了彻底不能用,也暂时没有出过这个问题。倒是惠惠前段时间(那边暂时没用任何redis的集群方案)由于redis占用内存满了然后挂过…… 然后是那年的7月底,redis的3.0出了beta8,是最后一个beta,大概介绍了下3.0开始支持cluster。因为词典实际上除了主查询服务和翻译的访问量非常大之外(而词典不用独立的缓存服务,翻译用memcache),其他服务的访问量和缓存的数据量基本上单机(即使是有道那些稍微挫了点的机器)的redis全都能搞定。我对cluster感兴趣的主要原因其实是为了把散乱的缓存资源整合到一起,大家所有服务公用一个redis集群,实现了资源利用的最大化。所以简单看了下redis cluster的设计:P2P,gossip,smart client。前两者因为跟Cassandra一样,对我来说比较亲切,而不像一些人对去中心化的结构总是抱有怀疑的态度。至于smart client,就意味着客户端连接redis的driver必须额外开发支持redis cluster的协议才能用,而且这也是我认为当前甚至中短期内redis cluster最大的问题。当然这也意味着他理论上的延迟会比其他proxy的方案低(毕竟不需要多一次请求和数据的转发)。 后面我就搭了个测试用的redis集群,redis cluster的设计在这块有点奇葩,跟集群相关的操作需要一个外部的ruby脚本来协助(当然可能是为了让主程序的代码足够简洁?),然后那个脚本还只支持填实例的ip不支持host,还不告诉你不支持让你用host之后各种莫名其妙(不知道后来改进没)。不过反正也不是很经常用到,也无所谓了。还是那个原因——机器比较少——于是所有节点都是master,没slave。做了各种测试,压力测试遇到个问题是max和.99的响应时间高的莫名其妙,然后后来发现是因为默认开了bgsave,在fork的时候会导致停止响应,关掉bgsave开aof就搞定了。然后试了下让其中1个实例挂掉,发现整个redis cluster都不可用了,即使是有active的节点所服务的slot也不能读写,而且这是故意这么干的,这设计简直脑残。但我权衡了下利弊,无视了这个脑残设计,决定还是找个访问量即使是全落在mysql也能抗住的线上服务先试试,于是从当时是全公司最牛逼的一批机器(64G内存、E5620的CPU……)里找了两台比较闲的(还有其他低load的服务在跑),各搭了8个实例,一共16个,搭出了准备给一套线上用的集群……我很好奇这是不是全球用户量超过千万的公司中第一批甚至第一个用于生产环境的redis cluster…… cluster搭好了,上层应用就应该迁移了。幸亏我们是个java公司,jedis可能是各种语言的redis driver里第一个能用来连cluster的(官方出了个ruby的当例子不算),可能至今还是唯一一个,但是实际使用的时候发现非常坑爹——很多功能支持不全。例如JedisCluster作为接口类,各种byte[]相关的接口不支持只能String;比如无论你的timeout设成多少,JedisCluster请求的时候timeout永远是2000ms(这个在今年3月出的2.7.0才改对)。虽然说框架写好之后基于单机版本把JedisCluster改成自己想要的功能不算很难也不麻烦(我们在迁移的时候也确实这么做了),但是终究是有工作量的,对技术能力弱一些的公司,完全就能不现实了。更别说其他语言根本没法用了。所以在一顿猛改jedis后,在一段时间内冒着一旦某个实例挂掉整个集群都不可用的风险,各种服务陆续切换上来了。然后翻译看我们这边基本靠谱就也在好像是9月或者10月也迁移过来了。也由于我们只当他是缓存,所以基本不存在数据迁移的问题,缓存预热的时候稍微控制下就可以抗住。然后我们就准备过上幸福的生活了…… 然而,突然有一天,有道翻译的服务崩溃了,没有任何响应。 打个jstack,底下最醒目的deadlock。一看,jedis干的。然后看代码,发现维护集群meta信息的类里一堆synchronized方法与一堆非synchronized方法中间共用了一个读写锁。
所以就喜闻乐见的死锁了。更……的是其实那个类里所有的synchronized都是多余的,而最新的代码里我发现他们已经把synchronized去掉了,理由是为了提升性能。于是开issue跟他们说了下旧的代码会死锁,建议他们尽快把最新代码发布新版,然后有人说虽然这是bug,但只要timeout别设成无穷,死锁的代码会自动超时释放的,可我们明明把timeout设的很短好不好……总之懒得理论这些事情了,改了bug之后死锁问题没了,但翻译被吓尿了,切回memcache,也因为事多人少,直到现在也没功夫重新换回redis…… 后来就没遇到过问题了。现在开始总结吧。 首先先说前提:twemproxy作为老牌的redis集群方案,他确实在特定历史阶段实现了他的价值,但他肯定是不如现在的codis,mssql等,具体codis、mssql哪好可以看很多文章介绍。 然后是官方cluster的优点,其实真的只有一个,就是没有proxy转发之后极限性能好,但绝大多数场景真的不重要。非说第二个优点就是他是官方的,只要redis还在维护,redis cluster被弃坑的概率就比较低,项目会持续有人维护,而第三方的方案理论上确实开发者弃坑的概率会比redis官方要大。不过只要第三方的方案真正成熟到一定程度,就算弃坑不更新大家也还是可以用。就像redis如果截止2.8.x就不开发了,大家照样会用一样。 至于缺点,那就非常严重了。
关于redis cluster的设计,Gossip/P2P的去中心化架构本身不是问题,但是一旦有了中心节点,能做的事情就多了,例如sharding不均匀是很容易自动rebalance的,而无中心的只能靠外界来搞。而redis cluster又是slot的形式而非C*式的一致性哈希,新节点分slot又不自动,依赖外界(ruby脚本)来分配显得不方便更不优美和谐。而且因为是master-slave的系统而非W+R>N的那种,master挂掉之后尽快发现是比较重要的,gossip对于节点挂掉的发现终究没有中心节点/zookeeper方便快速。不知道有没有其他系统是gossip+主从的模式。 redis作为一个非常成功的NoSQL,其实其协议是可以发扬光大的,基于proxy做转发意味着屏蔽了下层存储,完全可以根据前缀/tag/冷热程度,来把部分甚至大多数数据放在磁盘从而节约成本又保证一致性,这都是有中心节点所带来的好处。对于只需要NoSQL的业务来说,将持久层与缓存简化成一个显然是最方便的,一个set、一个get就能搞定,并且不需要业务自己维护缓存与持久化的一致性,也更安全。当然这种让redis协议支持磁盘读写的竞争对手就是那些原本就是磁盘上的NoSQL直接开内存缓存,例如Cassandra:
虽然是做redis这一块,但是我还是了解mssql的。接下来几篇可能会发出一些mssql的文。。 下一条: 数据库设计 :扬帆启航
|