Redis事务

99 阅读2分钟

Redis事务和传统数据库事务相比,它的事务模型很不严格。
传统数据库每个事务的操作指令都有begin 、commit 和rollback, begin指示事务的开始,commit 指示事务的提交,rollback指示事务的回滚。 Redis事务指令:multi,exec,discard。multi指事务的开始,exec指事务的执行,discard指事务的丢弃。

192.168.230.131:6379> multi
OK
192.168.230.131:6379> incr books
QUEUED
192.168.230.131:6379> incr books
QUEUED
192.168.230.131:6379> exec
1) (integer) 1
2) (integer) 2

事务块内的多条命令会按照先后顺序被被服务器缓存到队列当中去。

原子性

事务的原子性是指要么全部成功要么全部失败,那么redis事务执行是原子性的吗? 举个特殊例子:

192.168.230.131:6379> multi
OK
192.168.230.131:6379> set books super
QUEUED
192.168.230.131:6379> incr books
QUEUED
192.168.230.131:6379> set poorman iam
QUEUED
192.168.230.131:6379> exec
1) OK
2) (error) ERR value is not an integer or out of range
3) OK
192.168.230.131:6379> get books
"super"
192.168.230.131:6379> get poorman
"iam"

books是字符串执行incr操作会失败,但poorman会继续执行。事务在遇到指令执行失败后,后面的指令还会继续执行,显然redis事务不是原子性的。

192.168.230.131:6379> get book
(nil)
192.168.230.131:6379> multi
OK
192.168.230.131:6379> incr book
QUEUED
192.168.230.131:6379> incr book
QUEUED
192.168.230.131:6379> discard
OK
192.168.230.131:6379> get book
(nil)

redis的事务执行通常需要结合pipeline一起执行,减少io。

如果有多个客户端并行操作就会出现并发问题。我们可以用redis分布式锁来实现,分布式锁为悲观锁,可不可以用乐观锁来实现呢?redis提供了watch机制.

package com.redis;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;
import java.util.List;
public class TransactionDemo {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("192.168.230.131", 6379);
        String useId = "abc";
        String key = keyFor(useId);
        jedis.setnx(key, String.valueOf(5));
        System.out.println(doubleAccount(jedis,useId));
        jedis.close();
    }

    public static String keyFor(String userId){
        return String.format("account_%s",userId);
    }
    public static int doubleAccount(Jedis jedis,String useId){
        String key = keyFor(useId);
        while(true){
            jedis.watch(key);
            int value = Integer.parseInt(jedis.get(key));
            value *= 2;
            Transaction tr = jedis.multi();
            tr.set(key, String.valueOf(value));
            List<Object> res = tr.exec();
            if (res != null) {
                break;
            }
        }
        return Integer.parseInt(jedis.get(key));
    }
}

redis事务为什么不需要回滚?
只有当发生语法错误(这个问题在命令队列时无法检测到)了,Redis命令才会执行失败, 或对keys赋予了一个类型错误的数据:这意味着这些都是程序性错误,这类错误在开发的过程中就能够发现并解决掉,几乎不会出现在生产环境。由于不需要回滚,这使得Redis内部更加简单,而且运行速度更快。