redis事物介绍与应用

研究reids与使用已经有几个月的时间,中间自己总结了不少文档以及相关资料,接下来时间,会陆续分享一些相关资料,并且介绍其在我们应用中的使用情况。

下面是redis事物的相关介绍(参考redis.io):

一.事物的使用

1.Redis事物通过MULTI命令开始。 这条命令总是返回OK。

2.然后用户可以执行多条指令,redis不会马上执行这些指令,还只是放入到队列中。

3.当执行exec指令时,所有的指令执行。

4.调用discard指令,将会flush事物队列,并且退出事物。

如下:

redis 127.0.0.1:6379> multi

OK

redis 127.0.0.1:6379> set foo 1

QUEUED

redis 127.0.0.1:6379> incr foo

QUEUED

redis 127.0.0.1:6379> incr foo

QUEUED

redis 127.0.0.1:6379> exec

1) OK

2) (integer) 2

3) (integer) 3

 

从以上会话中能看到multi命令返回的回复是一个数组,每个元素即是事物中每条指令的回复,并且跟指令发布的顺序一样。当redis连接在multi请求下,所有的命令回复都是queued,除非这条指令的语句法不正确。而一些指令语法正确,但执行阶段出错也是允许的。

如以下

redis 127.0.0.1:6379> multi

OK

redis 127.0.0.1:6379> set t 13

QUEUED

redis 127.0.0.1:6379> lpop t

QUEUED

redis 127.0.0.1:6379> exec

1) OK

2) (error) ERR Operation against a keyholding the wrong kind of value

对于这种err,需要客户端给予合理的提示。

需要注意的是,所有在队列中的指令都会被执行,redis不会终止指令的执行。

二.取消队列指令

Discard为取消命令队列。可以终断一个事物。不会有命令会被执行,并且连接的状态是正常的。

如:

> SET foo 1

OK

> MULTI

OK

> INCR foo

QUEUED

> DISCARD

OK

> GET foo

"1"

三.Optimistic locking using check-and-set(乐观锁)

watch指令在redis事物中提供了CAS的行为。为了检测被watch的keys在是否有多个clients改变时引起冲突,这些keys将会被监控。如果至少有一个watch的key在执行exec命令前被修改,整个事物将会被终止,并且执行exec会得到null的回复。

例如:一个key自增长(假设redis不提供incr的功能)

val = GET mykey

val = val + 1

SET mykey $val

以上指令执行,如果是单一的client,整个操作是没问题的。如果多个client在同一时间操作。如client A与 client B读取了老的值,假如是10,这个值在两个client将会被增长到11,最后set这个key值时,这个key最终是11还不是12.

watch能够很好的处理这种问题:

WATCH mykey

val = GET mykey

val = val + 1

MULTI

SET mykey $val

EXEC

 

使用以上代码,如果在执行watch与exec指令这段时间里有其它客户端修改此key值,此事物将执行失败。以上形式的锁被称为乐观锁。在大多数使用场合中,多并发将会处理不能的keys,因为冲突不太可能。(通常没有必要重复操作)

四. Watch 指令说明

watch指令是exec指令的执行条件:保证在执行redis事物操作时没有任何client修改被watched的keys.否则事物不会执行。(注意:如果watch一个不稳定的key并且key过期,exec仍然会执行这条指令),当exec指令被调用,所有的keys将是unwatched,无论事物是否被终止。当client连接关闭后,所有的keys也会变成unwatched.为了flush所有的watched keys,也可以使用unwatch指令.有时间使用乐观锁锁住一些keys是很有用的,因为可能需要选择的keys需要事物操作,但是在执行读取现有的keys的内容后发现不需要继续执行,这时只要使用unwatch指令,使此连接能够继续被使用做其它新的事物操作。

 

ZPOP(取sorted set中socre最低的元素)指令就是通过watch指令实现的

WATCH zset

element = ZRANGE zset 0 0

MULTI

ZREM zset element

EXEC

五.总结

1.redis事物实现,multi开始,所有指令会被放入到队列中。当调用exec后,队列中所有指令会依次被执行。

2.multi-exec中指令执行时,所有指令只要语法合理都会被写入队列中。队列执行时,指令有可能会执行失败,但不影响其它指令执行。

3.redis事物提供了乐观锁,通过watch指令可以实现CAS操作。watch--multi--exec操作

在给key加上乐观锁后,当在执行exec指令前,有其它client修改此key,此事物将执行失败。从而保证原子操作。

 

说明:对于redis事物的应用其实需要灵活使用,上面介绍的例子是从官网翻译而来。其实在实际中可以通过watch一些标记位来保证多线程下缓存与数据库数据库的一致性。(我们的系统是分布式缓存与数据库的结合使用,缓存需要跟据数据库的一致性很重要,下面举例我们应用中的一个场景:)

如一个service方法,serviceA,执行DAO方法(1),然后更新缓存(2),两个并发线程,线程一执行了方法1,此刻他需要把DAO相关的数据更新到缓存2中,多线程情况下,线程二在线程一执行1后,也同样执行1,2相关的操作,并且比线程一优先完成,这样将导致线程一在执行2时,将出现缓存数据与数据库不一致的现象。(以上是针对单帐号的多并发操作,发生的概率还是存在),对于以上问题我们的解决方案是:

1.      帐号为acc,为每个acc在缓存中增加一个tag标识.

2.      当线程一执行方法1前,设置标记位tag.

3.      当执行方法2时,将会watch tag,并且比较tag是否发生了修改,如果一旦发生修改,则此次缓存操作不将更新,并清空此acc缓存。

4.      如果tag值达到预期,则提交缓存更新,在提交缓存这段时间,如果tag发生变换,则redisexec提交时,会返回null ,这样,虽然缓存内容更新成功,但跟据返回结果,可以即时清除此acc的缓存,从而清空了缓存的脏数据。

5.      通过以上事物保证了缓存数据与数据库数据不一致性的时间很短,甚至可以忽略,因为基本上在MS级别上。

6.      (我们的应用在缓存数据不存在acc的情况下,会尝试从数据库读取,而缓存的作用只是缓解我们系统数据库的压力,这样实现,很好的达到了我们的预期效果).


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章