Redis 索引设计

发布于 / Wirte / 0 条评论

redis并不直接支持索引,需要通过自己来维护。

对于非范围唯一索引,我们可以简单的把索引也存成KV对,v保存主key即可,

而范围检索,或者非唯一索引,则要使用redis 的 zset来实现。

举例一个传统的用户系统例子
uid 用户id
name 用户名
credit 用户积分
type 类型

可以直接放到一个hashset中
hmset usr:1 uid 1 name aaa credit 10 type 0
hmset usr:2 uid 2 name bbb credit 20 type 1

通过uid检索很快,但是如果要查询type=1的用户,则只能全扫描!
在关系数据库中,我们可以简单在type上建立索引
select * from usr where type=1

这样的SQL就可以高效执行了。redis中需要我们自己再维护一个zset
zadd usr.index.type 0 0:1
zadd usr.index.type 0 1:2

注意,所有权重都设置成0,这样可以直接按值检索,然后可以通过
zrangebylex usr.index.type [1: (1;

(这里有一个小技巧,字符:后面就是;,所以>=1:且<1;的就是以1:开头的
比 [1: [1:\xff 这种形式看起来简洁一些。)

显然得到的结果是1:2,还要自己处理一下获得用户id=2,然后就可以通过usr:2再获取其它字段了。
这里如果更新很少,那么也可以考虑直接将其它字段接到1:2之后,这样一次可获得,等于是SQL中的带冗余字段的索引。

为了保证索引一致,需要做很多额外工作,

新增比较简单:
hmset usr:3 uid 3 name ccc credit 30 type 3
zadd usr.index.type 0 3:3

为了保持一致性,最好也用脚本来实现。而修改的话,有些复杂,
必须先读出原值,还是使用lua吧

local usr=KEYS[1] –usr:1 所有用到的key都要用参数提供
local index=KEYS[2] –usr.index.type
local uid=ARGV[1] –1
local newtype=ARGV[2] –9

–保存原始值
local oldtype=redis.call('hget',usr,'type')
–type修改为新值
redis.call('hset',usr,'type',newtype)

–删除老的索引
redis.call('zrem',index,oldtype ..":" .. uid)
–增加新索引
redis.call('zadd',index,0, newtype .. ":" .. uid)

为了保持一致性,所有修改type的地方都要使用这个lua脚本,
删除一条usr记录时也要读出原始的type,同时删除索引,脚本类似。

显然,SQL数据库之所以索引很好用,无非是他帮你内部实现了,其实现原理也是类似的。
同样redis也可以建立多个索引,甚至复合索引,比如:

zadd usr.index.type_credit 0 3:20:3

type:credit:uid的格式,这里需要注意的是,参与索引的数字字段必须左补0对齐(补几个则根据字段长度),比如
003:00020:3

除非你不需要范围检索,只需要精确检索,比如:
zrangebylex usr.index.type_credit [3:20: (3:20;
只需要检索type=? and credit=?的用户,否则你就必须用

zrangebylex usr.index.type_credit [3:00020: (3:00030;
检索type=? and credit between ? and ?

显然redis使用自建索引还是比较费事的,是否真的需要?
是否可直接使用SQL数据库而让redis缓存查询结果? 需要架构师仔细权衡。

但是的确掌握建立索引技巧后,redis又变强大许多。

转载原创文章请注明,转载自: 风过不留痕 » Redis 索引设计
Not Comment Found