Redis 学习笔记4 - 数据结构的使用

1. 数据结构的使用

1.1 时间复杂度

谈到数据结构,一定会谈到 “时间复杂度”。

在计算机科学中,算法的时间复杂度是一个函数,它定性描述该算法的运行时间。
时间复杂度常用大O符号表述。
时间复杂度可被称为是渐近的,即考察输入值大小趋近无穷时的情况。

在 Redis 中,用它来表示,基于我们处理的数据的数量,命令执行的速度将会如何。

O(1)

最快的应该是 O(1) ,一个常量。sismember 命令,用于查询一个值是否属于一个集合,就是 O(1)。

sismember 是个强力的命令,很大一个原因就是快。Redis 中的大多数命令都是 O(1)。

O(log(N))

O(log(N)), 是第二快的,因为它需要扫描的区间范围越来越小。通过使用这种类型的切分和处理方法,一个非常大的集合仅需要做几次迭代就会被迅速的分解。

zadd 是一个 O(log(N)) 命令,N 表示在有序集合中的元素个数。

O(N)

O(N) 在表中查找没有做索引的列就是一个 O(N) 操作。就像用 ltrim 命令一样。但是,在 ltrim 中,N 不是列表的元素个数,而是要移除的元素的个数。

O(log(N)+M)

zremrangebyscore 用来从有序列表中删除那些权重在最小值和最高值之间的元素,拥有复杂度 O(log(N)+M)。

O(N+M*log(M))

sort 命令,的复杂度 O(N+M*log(M))

其他

还有两个比较常用的是 O(N^2) 和 O(C^N)。N 越大,性能越差。Redis 没有这种复杂度的命令

1.2 多 Key 查询

假如说,一个用户叫 users:leto 的用户,他的 基本信息 有 '{"id": 9001, "email": "leto@dune.gov", ...}'
如果,想通过用户 id, 和 email 都能快速检索到要怎么做?

方案1:使用 string 结构,存两份
即用 id ,和 email 作为键,存储两次 。示例:

set users:leto@dune.gov '{"id": 9001, "email": "leto@dune.gov", ...}'
set users:9001 '{"id": 9001, "email": "leto@dune.gov", ...}'

当用户很多时,这样的方式绝对是个噩梦,并且它们会占用你两倍内存空间。

方案2:使用哈希结构,用字段作为伪二阶索引

先用 string 结构存储基本信息,再用哈希结构 将 email 作为字段,id 作为值

set users:9001 '{"id": 9001, "email": "leto@dune.gov", ...}'
hset users:lookup:email leto@dune.gov 9001

那么,通过 id 获取 用户,我们可以用普通的 get:

get users:9001

想要通过 email 来获取用户,我们用 hget 配合 get (Ruby):

id = redis.hget('users:lookup:email', 'leto@dune.gov')
user = redis.get("users:#{id}")

这样的方式很节约 key 的数量。使用 伪二阶索引 建立了 映射。

1.3 引用和索引(References and Indexes)

上面的查询优化的例子,其实是 手工维护你的 value 之间的索引和
引用。这样的方式很常见。

在集合中维护索引:

sadd friends:leto ghanima paul chani jessica

上面标示了 leto 有 ghanima ,paul ,chani等几个 朋友。而 ghanima 这样的又可以指向具体的 用户信息。

再跟踪一下反向关系:

sadd friends_of:chani leto paul

上面标示了 chani 是 leto, paul的 朋友。

这些额外的索引值的处理和内存开销会让人吓到,我们通过使用额外的查询次数降低性能开销。其实关系型数据库也有一样的开销。

1.4 Round Trips and Pipelining

添加一个或多个记录
sadd 命令向集合中添加一个或多个记录:

sadd friends:vladimir piter
sadd friends:paul jessica leto "leto II" chani

接收多个参数
许多命令都可以接收一个或者多个参数,或者有一个带有多个参数的子查询

ids = redis.lrange('newusers', 0, 9)
redis.mget(*ids.map {|u| "users:#{u}"})

Redis 也支持管道
通常,一个客户端向 Redis 发送一个请求,然后在下次请求之前会一直等待返回。而用管道你可以发送一堆请求却不用等待它们的响应。这不单降低了网络开销,也在性能上有显著提高。

···
redis.pipelined do
9001.times do
redis.incr('powerlevel')
end
end

···

批处理会被管道加速。

1.5 事务(Transactions)

Redis 所有的命令都是原子性的,包括那些一次可以执行多项操作的命令也一样。此外,在使用多命令的时候,Redis 支持事务。

Redis 确实是单线程的,这就是为什么每个命令都是原子性的原因。
一次只能执行一个命令

  • incr 实际上是一个 get 后面跟个 set
  • getset 设置一个新值之后返回原值
  • setnx 首先检查 key 是否存在,当它不存在的时候设值

事务的使用:

  • 首先你需要 multi 命令, 作为开始
  • 然后接下来是你希望作为一组事务执行的所有命令
  • 最后用 exec 来实际执行命令,或者用 discard 来放弃取消执行所有的命令

示例:

multi
hincrby groups:1percent balance -9000000000
hincrby groups:99percent balance 9000000000
exec

** watch**

redis.watch('powerlevel')
current = redis.get('powerlevel')
redis.multi()
redis.set('powerlevel', current + 1)
redis.exec()

如果另一个客户端在调用 watch 之后,改变了 powerlevel 的话,我们的事务将会失败。

1.6 Keys Anti-Pattern

keys 命令。该命令通过指定模式返回所有匹配的 key。这个命令看起来在某些情况下很适用,但是它绝对不应当用在产品代码中。因为它为了查找匹配的 key 会对所有的 key 做一个线性扫描,它很慢。

于是,考虑用哈希结构。就像我们可以用哈希来暴露二级索引那样,所以我们也可以用它来组织我们的数据:

hset bugs:1233 1 '{"id":1, "account": 1233, "subject": "..."}'
hset bugs:1233 2 '{"id":2, "account": 1233, "subject": "..."}'

为了取得一个账户下的所有 bug 标识符,我们只需要调用 hkeys bugs:1233。要删除指定 bug 我们可以 hdel bugs:1233 2,要删除账户的话我们可以通过 del bugs:1233 来删除 key。

END

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值