Permalink
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
76 lines (45 sloc) 5.23 KB

优雅的使用Redis(二)——数据结构的选择

数据结构不同造成的问题

Redis提供了丰富的数据结构,如string、hash、set、list、hyperLogLog等。有一些数据结构虽然没有直接名称相同的,但是可以使用现有数据,选择合适的操作方案,就能够当作那些数据结构来使用。比如Redis并没有直接给出queue和stack这样名称的数据结构,但是list提供了丰富的操作,使得我们可以非常方便的模拟出先进先出和先进后出的效果。

Redis丰富的数据结构,使得我们在同一场景下会有多种选择方案。但是不同的选择方案可能造成以下几个方面的差距:

  • 内存使用差距。
  • 原子操作数量差距
  • 客户端和Redis通信次数的差距
  • 网络IO的差距。

这些问题我所在的公司都遇到过,主要还是开发人员没有选择最合适的数据结构造成的。

如何选择

那么,我们如何选择数据结构呢?我总结我和身边的小伙伴的经验和教训,我觉得可以从以下几点入手:

  1. 在适合业务场景的数据结构中选择最节省内存的数据结构。这个需要经验和量化分析。
  2. 对于一个业务逻辑,选择Redis的客户端和服务器交互次数最少和操作最少的数据结构
  3. 选择Redis客户端和服务器交互时,通过网络传输数据量最少的数据结构。

实践案例分析

UV的统计从Bitmap->HyperLogLog

最早的时候,公司里统计UV时候,使用的bitmap,bitmap就是一个矩阵。每个uid占用一个列,每个行是一种去重的维度,每个广告一个bitmap,在有交叉(访问)的地方放1,无交叉(无访问)的地方放0。如下所示

uid=1 uid=2 uid=3 ...
view 1 0 1 .....

这样对于每个广告的UV需要1个bitmap。这也是以前业界惯用方案。但是这个方案存在一个问题,随着uid的增长,uid越来越大,后面我们单个uid在redis需要占用4M内存。这个时候,内存开销就非常大了。 当时我们线上用的都是60G的redis主机,每台机器占用内存一天的峰值的时候超过了48G,部分时候达到53G,当到了53G的时候,Redis就无法正常使用。好在这些数据是有效期是到第二天凌晨。

如何解决这个问题呢?

我们在调研发现Redis中新出来的数据结构HyperLogLog非常适合我们的业务场景。 HyperLogLog 可以接受多个元素作为输入,并给出输入元素的基数估算值:

  • 基数:集合中不同元素的数量。比如 {'apple', 'banana', 'cherry', 'banana', 'apple'} 的基数就是 3 。
  • 估算值:算法给出的基数并不是精确的,可能会比实际稍微多一些或者稍微少一些,但会控制在合 理的范围之内。

HyperLogLog 的优点是,即使输入元素的数量或者体积非常非常大,计算基数所需的空间总是固定 的、并且是很小的。 在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基 数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。 但是,因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。

更换到HyperLogLog后,服务器内存直接下降了25G。我们的做法是每个广告一个HyperLogLog,我们每天广告数量是有限的,无论用户增加多少,对我们的内存影响不大。

string -> hash

在我们的产品中,有一个页面,用户可以看到自己评论数量、发帖数量、未读消息数量。对于计数器这样的操作,我们可以使用string和hash,string中有incr和incrBy,hash有hincrBy。那么在string和hash之间我如何选择呢?

  1. 每个用户访问这个页面时,选择string我需要使用3个命令,然而使用hash我只要使用一个hgetAll就足够了,这样省了操作。

  2. 3个string比hash3个field花费的内存更大。

  3. 扩展方便。就算需要增加其他的统计,使用hash还是那个hgetAll,对于string就需要增加命令。

string -> hyperLogLog

在我们公司一款女性垂直社交app中,使用了Gossip最内容路由传递。为了过滤,最早的时候是使用bloom filter来的,为了保证一致性,每次都把bloom对象序列化存储到Redis上。该app是每天日活10w左右,每天峰值时Redis占用贷款6M,然而另外一款用户上亿的产品,内容业务,每天峰值都不到2M。

就是因为这个过滤去重的业务逻辑,造成那款社交后台每天Redis服务卡顿,服务延时很严重。其实当时还有一个潜在的问题,这种bloomfiler对象序列化存放必然会存在数据不一致的问题(迫于业务的需求,先上线再优化)。

后面借鉴统计使用hyperLogLog经验,使用hyperLogLog替换了bloomfilter,网络带宽里面降下来了4M多。而且还解决数据的一致性问题。