同舟共济 —— 事务

如果对redis事务不熟悉,建议避免使用redis事务

Redis 的事务使用非常简单,不同于关系数据库,它的事务模型很不严格。Redis 的事务根本不能算「原子性」,而仅仅是满足了事务的「隔离性」,隔离性中的串行化——当前执行的事务有着不被其它事务打断的权利。

Redis 事务命令分别是 multi/exec/discard。multi 指示事务的开始,exec 指示事务的执行,discard 指示事务的丢弃。但是,Redis的事务是不支持回滚的

优化

Redis 事务在发送每个指令到事务缓存队列时都要经过一次网络读写,当一个事务内部的指令较多时,需要的网络 IO 时间也会线性增长。所以通常 Redis 的客户端在执行事务时都会结合 pipeline 一起使用,这样可以将多次 IO 操作压缩为单次 IO 操作。

Watch

考虑到一个业务场景,Redis 存储了我们的账户余额数据,它是一个整数。现在有两个并发的客户端要对账户余额进行修改操作,这个修改不是一个简单的 incrby 指令,而是要对余额乘以一个倍数。Redis 可没有提供 multiplyby 这样的指令。我们需要先取出余额然后在内存里乘以倍数,再将结果写回 Redis。这就会出现并发问题,因为有多个客户端会并发进行操作。

我们可以通过 Redis 的分布式锁来避免冲突,这是一个很好的解决方案。分布式锁是一种悲观锁,那是不是可以使用乐观锁的方式来解决冲突呢?Redis 提供了这种 watch 的机制,它就是一种乐观锁。有了 watch 我们又多了一种可以用来解决并发修改的方法。

import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;

import java.text.MessageFormat;
import java.util.List;

/**
 * @Auther: majx2
 * @Date: 2019-3-23 20:29
 * @Description:
 */
public class TransactionDemo {

    public static void main(String[] args) {
        Jedis jedis = RedisDS.create().getJedis();
        String userId = "abc";
        String key = keyFor(userId);
        jedis.setnx(key, String.valueOf(5));
        System.out.println(doubleAccount(jedis, userId));
        jedis.close();
    }
    public static int doubleAccount(Jedis jedis, String userId) {
        String key = keyFor(userId);
        while (true) {
            jedis.watch(key);
            int value = Integer.parseInt(jedis.get(key));
            value *= 2; // 加倍
            Transaction tx = jedis.multi();
            tx.set(key, String.valueOf(value));
            List res = tx.exec();
            if (res != null) {
                break; // 成功了
            }
        }
        return Integer.parseInt(jedis.get(key)); // 重新获取余额
    }
    public static String keyFor(String userId) {
        return MessageFormat.format("account_{0}", userId);
    }

}
 

                            
                        
                    
                    
                    

你可能感兴趣的:(同舟共济 —— 事务)