那些年,我使用过的轮子(一)--Redis

背景

  最近面试准备加上工作交接有点小忙,导致原来的计划耽搁了蛮久,上午刚回京,偷个安静的下午来完成这篇文章。Redis 作为一个广泛使用NoSQL的存储工具,相信大家都比较熟悉了, 从工作至今3年的时间里面,对它的使用是比较多的,当然也一直把它当作工具用,没有涉及到底层源代码级别的改造。结合做过的业务,Redis有当作缓存来用的,也有当作存储系统来使用的,它支持的数据结构丰富,能满足的业务场景也比较多,比如消息队列、排行榜、索引等。也可以通过sharding、cluster方式组成各种分布式的集群,来应对不同的数据量的可扩展性;文章根据业务来简单的陈述下Redis的使用场景和问题,有些虽然能满足业务性能要求,然后并不是最优的,这个相信大多数开发同学都有体会,业务迭代的快的话,有时候没有时间给你优化或者追求最优的性能。

业务场景

标签检索服务

  初步接触Redis是12年在SH实习期间做的一个项目, 对外使用Thrift提供检索服务,内部使用Redis来存储各种维度的标签数据,数据类型比较简单,基本就是k-v类型。在Redis的使用方面,使用了2台Redis实例+Consistent Hashing来做的一个缓存集群,数据的写入是离线每天批量写入的,当检索不到对应的标签的时候不会再从第三方的存储系统查询和写入,即业务是接受数据丢失带来的影响的。数据方面基本都是k-v的类型,每天定时更新和过期超时,数据量规模不大,记录数大概在几KW的规模,key的长度在128字节,value波动较大,一般在1024字节。当时在完成功能的时候,还做了2个事情:第一是key压缩降低空间占用,对比了MurmurHash和CityHash等算法,印象中MurmurHash的冲突率最低,性能还是比较优的;第二个是提高可用性方面做了一些实验(当时就2台,所以有点玩票性质了),因为采用了Consistent Hashing来做的,数据存储在Hash Ring对应的虚拟节点下游最近一个实节点上面,如果这个节点挂了,服务的可用性会受到一定的影响,当时就把数据进行了双写,就是沿着Hash Ring再往下找一个对应不同实例的虚拟节点,再把数据写一份.
  这个是初步接触Redis完成的第一个项目,算是入门级别的了,当时在可靠性,可扩展性都没有太多的思考,在性能方面也没有太多关注,收获主要是在功能实现、一些Hash方法、以及Redis的一些调参方法上。

在线聚类服务

  13年需要对系统实时抓取的文章进行聚类,算是一个比较大的系统里面的一个子模块,涉及的东西比较多,比如RabbitMq, ZooKeeper等诸多工具,还有Spring一系列的框架工具,Redis在里面主要是用来存储在线聚类相关的数据,比如docId->wordIds,word->wordId,docId->clusterId, clusterId->docIds等倒排数据,使用的数据结构也比较多,Hash, List等;基本的流程是消费RabbitMq获取文章数据,然后分词、建立索引、聚类计算等, Redis的相关配置信息保存在ZooKeeper里面,方便配置更新和升级,采用的Sharding的方式来组建的集群,在灾备方面也经历了本地持久化+启动预热数据和Sentinel的方式。
  正式工作做的一个项目,碰到的问题比较多,比如Sharding扩容问题和升级的问题,比如服务恢复的问题,RabbitMq消费慢的问题等。使用Redis的时候,数据规模和增长、性能最好提前考虑好,以免到时候手忙脚乱的。Sharding的时候,可以将实例分的稍微多点,比如开始的时候每个8G的空间,单台服务器4个实例,扩容的时候,可以扩展到每台服务器2个16G的实例或者每台服务器1个32G的实例等,关闭主的数据持久化,由从来进行数据持久化等.

线上数据迁移

  自告奋勇接的一个活,线上某种维度的数据即将撑爆Redis,考虑当前的Redis单实例存储空间已经占用64G了,不改变数据分布扩容的代价比较高,遂采用把数据分布重做即re-sharding的方式来迁移,将原来的4*64的实例修改成16*6的方式。目前的Redis是当作持久化存储来使用的,虽然有使用MySQL作为落地,但是MySQL是分库分表的,服务使用的数据需要较大代价从MySQL重建,所以在线服务只读取Redis,数据没有读取到的时候也不会从MySQL去加载。当时做数据re-sharding的时候,没有直接去线上的Redis去取数据来重新做sharding,主要是考虑到线上数据的动态变化还有做遍历的时候对线上服务的影响,还有就是MySQL数据是append写入的, 可以完整构造出Redis的数据。在re-sharding的时候,测试和使用了pipeline方式,上线时候配置切换采用了ZooKeeper Watcher刷新的机制。
  原本手头有其它项目,这个在我这里优先级不高,没有意识到这个项目的重要性,还被老大教训了。上线很顺利,接下来一周就日狗了, 线上反馈各种数据丢失,一些数据莫名其妙丢失了,最终定位到数据库里面有脏数据,导致原有数据会被覆盖替换掉,按照我们正常的设计,不会产生重复的key,但是从数据库里面构造就有,应该是系统开发过程产生的bug引起的。教训是惨痛的,这次使我意识到用户系统产生的数据不一定是预想的(即使程序是你开发的)以及回归测试是多么重要、没有消息就是坏消息、和用户相关的所有东西都要严格测试再上线、提前做好失败预案等等。

实时计算

  做推荐系统的时候基础服务模块,基本思想是把用户的实时反馈行为数据进行计算来给其它模块提供数据和服务的。这个项目可以参考实时数据处理这篇文章,里面主要用Redis里面的Hash,List, SortedSet等数据结构来实现排序,异步消息队列等功能。
  线上服务部署了多个节点,采用了Redis的Cluster的方式来做分布式的数据存储和解耦,利用ZooKeeper+Thrift实现了服务发现相关的功能。

学生能力评估

  作业推荐的时候需要去计算学生和班级在各个知识点的能力水平,也是一个简单的实时计算场景,不过数据维度比较多,特别是中间计算结果,而且为了方便业务批量获取,对最终结果也做了优化存储,用的比较多的就是Hash了,数据量和增长规模比较大,数据存储时间也比较长。
  业务上线前一学期,每天增长存储空间在5G左右,扩容压力很大,针对数据特点进行了一部分优化,比如利用Hash降低Key的数量,中间计算数据转移等手段,目前每天增长的存储空间在1-2G左右。Cluster方式占用空间比Sharding方式大的多, 将Sharding的128*3修改成64*6的方式,方便扩容。

一些教训

  1. 提前考虑数据规模和扩容方法,扩容通常都比较棘手,特别是线上服务直接依赖的,提前扩容,数据规模越大,风险一般越高。无论是Sharding方式还是Cluster方式都不太容易,提前演练比较好,而且最好在接近线上的环境和数据来进行演练,不然到线上实操的时候容易傻眼。
  2. 数据存储的时效性和过期策略。内存永远是不够用的,只要数据规模一直在增加,不需要长久存储的数据需要提前设计好失效时间和过期策略,虽然Redis支持主动过期和被动的方式,但是在理解数据的情况下,自己去删永远是最好的方式。
  3. Redis 的Hash是个好东西,合理设计Key的情况下,能节省很大空间。
  4. 考虑数据是利用Redis来进行缓存还是持久化存储的,两种存储下使用的方式还不太一样。比如缓存的话,关闭持久化,不设主从等。
  5. 数据分布和存储尽量细化,比如不同维度的数据存储在不同的集群以达到比较高的利用率和方便后期扩容和服务升级。
  6. Redis的一些结构比如SortedSet,HyperLogLog 在设计思想和某些业务下使用能极大的提升效率和性能,把它们当作源码读读也是极好的。

你可能感兴趣的:(工作,NoSQL,redis)