Introducing Redis String And Set

最近看了《Redis 设计与实现》,机械工业出版社,黄健宏著。
阅读之后,虽然没有很好的理解 Redis 底层数据结构的设计和实现。
但是,还是加深了对 Redis 性能高效方面的认识。

Redis 内部实现的主要数据结构有:简单动态字符串(SDS)、双端链表、字典、压缩列表、整数集合等等。
Redis 没有直接使用这些数据结构,来实现键值对数据库,而是基于这些数据结构,创建了一个对象系统。
包含:字符串对象(strings), 哈希对象(hashes), 列表对象(lists), 集合对象(sets) 和 有序集合对象(sorted sets)。

通过这五种不同类型的对象,Redis 在执行命令之前,根据对象的类型来判断一个对象,是否可以执行给定的命令。
另一个好处在于,使用者针对不同的使用场景,选用最佳的数据对象,从而优化系统的使用效率。
最后总结一下,目前在项目中使用到的 strings 和 sets 的特性和用法。

字符串对象的特性和用法

1. String 的特性
Redis 中一个字符串对象,包含两个对象:一个对象用作键值对的键,一个对象用作键值对的值。
字符串对象值的编码可以是:int、raw 或者 embstr,因此,支持保存:整数、浮点数和字符串。
如果,字符串对象保存的值为整数,则可以调用 加、减法的计数方法。

2. String 的方法
GET key
返回 key 所关联的字符串值。
如果 key 不存在则返回特殊值 nil。
时间复杂度:O(1)

SETEX key seconds value
将值 value 关联到 key,并将 key 的生存时间设为 seconds (以秒为单位)。
如果 key 已经存在,SETEX 命令将覆写旧值。
这个命令类似于以下两个命令:

  SET key value
  EXPIRE key seconds  # 设置生存时间
  
不同之处是,SETEX 是一个原子性(atomic)操作,关联值和设置生存时间两个动作会在同一时间内完成,该命令在 Redis 用作缓存时,非常实用。
时间复杂度:O(1)

3. String 的案例
统计商品当日积分兑换的次数
特点:该数值,只保存一天,默认值为 0;被兑换一次,则增一。

  # 设置 key
  def merchant_exchange_key(merchant_id)
    "merchant:exchange:count:#{merchant_id}:#{Date.today.strftime("%m%d")}"
  end

  # 读取商品当日兑换的次数
  def current_exchange_count(merchant_id)
    key = merchant_exchange_key(merchant_id)
    get_count(key)
  end

  # 设置商品当日兑换的次数
  def increase_exchange_count(merchant_id)
    key = merchant_exchange_key(merchant_id)
    increase_count(key)
  end

  def get_count(key)
    $redis.get(key).to_i
  end

  def set_count(key, count, ttl = 24*3600)
    $redis.setex(key, ttl, count)
  end

  def increase_count(key)
    num = $redis.get(key).to_i
    if num == 0
      set_count(key, 1)
    else
      $redis.incr(key)
    end
  end
  

集合对象的特性和用法

1. Set 的特性
Set 是一组没有序列的字符串的集合体。 同样包含两个对象:键对象用作键值对的键,值对象用作键值对的值。
值的编码可以是:intset 或者 hashtable,因此,可以保存:整数和字典,而字典里存储的是字符串。
集合对象的值是唯一的,重复的值将被覆盖。

2. Set 的方法
SADD key member [member ...]
将一个或多个 member 元素加入到集合 key 当中,已经存在于集合的 member 元素将被忽略。
假如 key 不存在,则创建一个只包含 member 元素作成员的集合。
时间复杂度:O(N),N是被添加的元素的数量。

SMEMBERS key
返回集合 key 中的所有成员。
不存在的 key 视为空集合,返回值为 0 。
时间复杂度:O(N),N为集合的基数。

SREM key member [member ...]
移除集合 key 中的一个或多个 member 元素,不存在的 member 元素会被忽略。
当 key 不是集合类型,返回一个错误。
时间复杂度:O(N),N为给定member元素的数量。

3. Set 的案例
统计用户加入的结伴小组
特点:默认值为 0,即没有加入任何小组;成功加入小组,则将小组 ID 加入 Set;若用户退出小组,则将小组 ID 移除 Set。


  def user_members_key(user_id)
    "user:members:#{user_id}"
  end

  # 读取用户当前加入的小组
  def get_active_members(user_id)
    smembers(user_members_key(user_id)).map{|t| t.to_i }
  end

  # 添加用户当前加入的小组
  def add_active_member(member)
    sadd(user_members_key(member.user_id), member.companion_id)
  end

  # 移除用户当前加入的小组
  def remove_active_member(member)
    srem(user_members_key(member.user_id), member.companion_id)
  end

  def sadd(key, value)
    $redis.sadd(key, value)
  end

  def smembers(key)
    $redis.smembers(key)
  end

  def srem(key, value)
    $redis.srem(key, value)
  end
  

参考链接
Redis 官网
Redis命令参考简体中文版

2015-04-02

rocket-wing