Redis事务的分析及改进

Redis事务的分析及改进

Redis的事务特性

数据ACID特性知足了几条?
为了保持简单,redis事务保证了其中的一致性和隔离性;
不知足原子性和持久性;html

原子性

redis事务在执行的中途遇到错误,不会回滚,而是继续执行后续命令;(违反原子性)redis

事务能够理解为一个打包的批量执行脚本,但批量指令并不是原子化的操做;
中间某条指令的失败不会致使前面已作指令的回滚,也不会形成后续的指令不作;
好比:服务器

redis 127.0.0.1:7000> multi
OK
redis 127.0.0.1:7000> set a aaa
QUEUED
redis 127.0.0.1:7000> set b bbb
QUEUED
redis 127.0.0.1:7000> set c ccc
QUEUED
redis 127.0.0.1:7000> exec
1) OK
2) OK
3) OK

若是在set b bbb处失败,set a已成功不会回滚,set c还会继续执行;网络

持久性

事务不过是用队列包裹起了一组 Redis 命令,并无提供任何额外的持久性功能,因此事务的持久性由 Redis 所使用的持久化模式决定:app

  • 在单纯的内存模式下,事务确定是不持久的。
  • 在 RDB 模式下,服务器可能在事务执行以后、RDB 文件更新以前的这段时间失败,因此 RDB 模式下的 Redis 事务也是不持久的。
  • 在 AOF 的“老是 SYNC ”模式下,事务的每条命令在执行成功以后,都会当即调用 fsync 或 fdatasync 将事务数据写入到 AOF 文件。可是,这种保存是由后台线程进行的,主线程不会阻塞直到保存成功,因此从命令执行成功到数据保存到硬盘之间,仍是有一段很是小的间隔,因此这种模式下的事务也是不持久的。
  • 其余 AOF 模式也和“老是 SYNC ”模式相似,因此它们都是不持久的。

隔离性和一致性

redis事务在执行的过程当中,不会处理其它命令,而是等全部命令都执行完后,再处理其它命令(知足隔离性)
redis事务在执行过程当中发生错误或进程被终结,都能保证数据的一致性;(详见参考资料1)async

redis事务的缺陷

除了不保证原子性和持久性,在实际使用中还有如下问题:函数

1) 遇到有查询的状况穿插在事务中间,不会返回结果;
设置事务开始标志后,全部的命令都是queued,即便是查询指令;
若是后续的更新操做须要依赖于前面的查询指令,那redis事务就没法有效的完成任务;
例如:性能

redis 127.0.0.1:7000> multi
OK
redis 127.0.0.1:7000> set a aaa
QUEUED
redis 127.0.0.1:7000> get b
QUEUED
业务逻辑...
redis 127.0.0.1:7000> set c ccc
QUEUED
redis 127.0.0.1:7000> exec
1) OK
2) bbb
3) OK

第二步 get a 返回的是queued,并非a的查询结果,
若是后续的set操做依赖于get的结果(存在依赖业务逻辑),就不能将get操做放在事务操做中;优化

2) 事务中的每条命令都与redis服务器进行了一次网络交互;
redis 事务指定开始后,执行一个事务返回的都是queued,那这个入队操做是在客户端实现,仍是在服务器端实现的?
查看源码,很容易发现是在服务器端实现;
在Redis.c中有这么一段:lua

int processCommand(redisClient *c) {
...
    /* Exec the command */
    if (c->flags & REDIS_MULTI &&
        c->cmd->proc != execCommand && c->cmd->proc != discardCommand &&
        c->cmd->proc != multiCommand && c->cmd->proc != watchCommand)
    {
        queueMultiCommand(c); // 将事务中的命令都放入到队列中,而后返回"QUEUED"
        addReply(c,shared.queued);
    } else {
        if (server.vm_enabled && server.vm_max_threads > 0 &&
            blockClientOnSwappedKeys(c)) return REDIS_ERR;

        //调用该命令函数来处理命令
        call(c);
    }
    return REDIS_OK;
}

这里就涉及到客户端与服务器端的屡次交互,明明是须要一次批量执行的n条命令,还须要经过屡次网络交互,有些浪费;

更新操做中的查询实现

若是有这样的需求:在事务开始后,中间穿插有查询逻辑;
那么使用redis事务(单库),没法知足这个要求;

可能的解决方案:

  1. 能够考虑使用多个库,读写分离,查询库只用来查询,更新库用来开事务作写操做;

  2. 再也不使用redis的事务指令,本身在客户端将待执行的命令批量打包,决定是否回滚仍是所有执行;这样能够在更新的间隙执行查询逻辑;而不须要将查询逻辑提早到事务指令multi以前;

  3. 将查询业务逻辑提早;严格规范代码编写要求,全部的redis查询逻辑都放在事务以外:

    redis 127.0.0.1:7000> get b
     bbb
     业务逻辑...
     redis 127.0.0.1:7000> multi
     OK
     redis 127.0.0.1:7000> set a aaa
     QUEUED
     redis 127.0.0.1:7000> set c ccc
     QUEUED
     redis 127.0.0.1:7000> exec
     1) OK
     2) OK

优化网络特性

将多个命令打包批量发送到redis服务器执行,减小网络交互,优化性能,可能的解决方案:

  1. 对于全部的get/set操做,可以使用现有的mget/mset指令;
  2. 对于各类不一样类型的更新操做,可以使用lua脚本将命令打包后,发送到服务器端一次执行;

参考

http://redisbook.readthedocs.org/en/latest/feature/transaction.html

Posted by: 大CC | 10MAR,2015
博客:blog.me115.com [订阅]
微博:新浪微博

相关文章
相关标签/搜索