2.5 Set
Set就是Set,可以将重复的元素随便放入而Set会自动去重,底层实现也是hash table。
2.6 Sorted Set
有序集,元素放入集合时还要提供该元素的分数。
Sorted Set的实现是hash table(element->score, 用于实现ZScore及判断element是否在集合内),和skip list(score->element,按score排序)的混合体。 skip list有点像平衡二叉树那样,不同范围的score被分成一层一层,每层是一个按score排序的链表。
ZAdd/ZRem是O(log(N)),ZRangeByScore/ZRemRangeByScore是O(log(N)+M),N是Set大
小,M是结果/操作元素的个数。可见,原本可能很大的N被很关键的Log了一下,1000万大小的Set,复杂度也只是几十不到。当然,如果一次命中很多
元素M很大那谁也没办法了。
2.7 事务
用Multi(Start Transaction)、Exec(Commit)、Discard(Rollback)实现。 在事务提交前,不会执行任何指令,只会把它们存到一个队列里,不影响其他客户端的操作。在事务提交时,批量执行所有指令。《Redis设计与实现》中的详述。
注意,Redis里的事务,与我们平时的事务概念很不一样:
- 它仅仅是保证事务里的操作会被连续独占的执行。因为是单线程架构,在执行完事务内所有指令前是不可能再去同时执行其他客户端的请求的。
- 它没有隔离级别的概念,因为事务提交前任何指令都不会被实际执行,也就不存在”事务内的查询要看到事务里的更新,在事务外查询不能看到”这个让人万分头痛的问题。
- 它不保证原子性——所有指令同时成功或同时失败,只有决定是否开始执行全部指令的能力,没有执行到一半进行回滚的能力。在redis里失
败分两种,一种是明显的指令错误,比如指令名拼错,指令参数个数不对,在2.6版中全部指令都不会执行。另一种是隐含的,比如在事务里,第一句是SET
foo bar, 第二句是LLEN
foo,对第一句产生的String类型的key执行LLEN会失败,但这种错误只有在指令运行后才能发现,这时候第一句成功,第二句失败。还有,如果事
务执行到一半redis被KILL,已经执行的指令同样也不会被回滚。
Watch指令,类似乐观锁,事务提交时,如果Key的值已被别的客户端改变,比如某个list已被别的客户端push/pop过了,整个事务队列都不会被执行。
2.8 Lua Script
Redis2.6内置的Lua Script支持,可以在Redis的Server端一次过运行大量逻辑,就像存储过程一样,避免了海量中间数据在网路上的传输。
- Lua自称是在Script语言里关于快的标准,Redis选择了它而不是流行的JavaScript。
- 因为Redis的单线程架构,整个Script默认是在一个事务里的。
- Script里涉及的所有Key尽量用变量,从外面传入,使Redis一开始就知道你要改变哪些key。(but why?)
- Eval每次传输一整段Script比较费带宽,可以先用Script Load载入script,返回哈希值。然后用EvalHash执行。因为就是SHA-1,所以任何时候执行返回的哈希值都是一样的。
- 内置的Lua库里还很贴心的带了CJSON,可以处理json字符串。
- 一段用Redis做Timer的示例代码,下面的script被定期调用,从以触发时间为score的sorted set中取出已到期的Job,放到list中给Client们blocking popup。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
-- KEYS: [ 1 ]job:sleeping, [ 2 ]job:ready
-- ARGS: [ 1 ]currentTime
-- Comments: result is the job id
local jobs=redis.call( 'zrangebyscore' , KEYS[ 1 ], '-inf' , ARGV[ 1 ])
local count = table.maxn(jobs)
if count> 0 then
-- Comments: remove from Sleeping Job sorted set
redis.call( 'zremrangebyscore' , KEYS[ 1 ], '-inf' , ARGV[ 1 ])
-- Comments: add to the Ready Job list
-- Comments: can optimize to use lpush id1,id2,... for better performance
for i= 1 ,count do
redis.call( 'lpush' , KEYS[ 2 ], jobs[i])
end
end
|
2.9 过期数据清除
官方文档 与 《Redis设计与实现》中的详述,
过期数据的清除从来不容易,为每一条key设置一个timer,到点立刻删除的消耗太大,每秒遍历所有数据消耗也大,Redis使用了一种相对务实的做
法: 当client主动访问key会先对key进行超时判断,过时的key会立刻删除。 如果clien永远都不再get那条key呢?
它会在Master的后台,每秒10次的执行如下操作:
随机选取100个key校验是否过期,如果有25个以上的key过期了,立刻额外随机选取下100个key(不计算在10次之内)。可见,如果过期的
key不多,它最多每秒回收200条左右,如果有超过25%的key过期了,它就会做得更多,但只要key不被主动get,它占用的内存什么时候最终被清
理掉只有天知道。 |