使用Redis事务的注意事项

1、使用Jedis实现事务

    @Test
    public void testJedis(){
        Jedis jedis = jedisPool.getResource();
        jedis.watch("key1");
        //开启事务
        Transaction transaction = jedis.multi();
        //命令入队
        transaction.set("key1", "hello");
        transaction.expire("key1", 20);
        //获取一个新的Jedis实例修改key1
        Jedis jedis2 = jedisPool.getResource();
        jedis2.set("key1", "world");
        //提交事务
        List<Object> result = transaction.exec();
        System.out.println("transaction exec result : "+result);
        System.out.println("jedis get key1: "+jedis.get("key1"));
    }

在这里插入图片描述
在事务exec之前,使用jedis2去修改key1,使得watch起作用导致事务失败。

2、使用redisTemplate时一定要将enableTransactionSupport设置为true,否则事务将不会起作用。

@Component
public class RedisTest implements CommandLineRunner {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Override
    public void run(String... args) throws Exception {
        redisTemplate.setEnableTransactionSupport(true);

        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    int i = 0;
                    while (true) {
                        redisTemplate.opsForValue().set("key1", "hw" + i, 5000, TimeUnit.SECONDS);
                        i++;
                    }
                }
            }).start();
        }
        redisTemplate.opsForValue().set("key1", "0");

        new Thread(new Runnable() {
            @Override
            public void run() {
                redisTemplate.watch("key1");
                Object result = redisTemplate.execute(new SessionCallback<Object>() {
                    @Override
                    public <K, V> Object execute(RedisOperations<K, V> redisOperations) throws DataAccessException {
                        redisOperations.multi();
                        redisOperations.opsForValue().set((K) "key1", (V) ("test1"));
                        redisOperations.expire((K) "key1", 50000, TimeUnit.SECONDS);
                        redisOperations.opsForValue().get("key1");
                        return redisOperations.exec();
                    }
                });
                System.out.println("redis exec result:" + result);

            }
        }).start();


        Thread.sleep(5 * 1000);
        System.out.println("get key:" + redisTemplate.opsForValue().get("key1"));
    }
}

一个线程开始事务watch key1,并开启事务修改key1为test1,另一个线程一直在修改key1。
执行后事务的结果为空数组,说明事务执行失败了,我们的watch成功了。
在这里插入图片描述

3、使用自旋让修改操作成功

高并发的场景下操作redis并不是watch到有其他线程修改就失败,而需要的效果一般是线程安全的修改成功。借鉴juc中自旋锁的方式,修改第二个例子:

@Component
public class RedisTest implements CommandLineRunner {

    @Autowired
    private StringRedisTemplate redisTemplate;

    private volatile boolean isExist = false;

    @Override
    public void run(String... args) throws Exception {
        redisTemplate.setEnableTransactionSupport(true);

        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    int i = 0;
                    while (!isExist) {
                        redisTemplate.opsForValue().set("key1", "hw" + i, 5000, TimeUnit.SECONDS);
                        i++;
                    }
                }
            }).start();
        }
        redisTemplate.opsForValue().set("key1", "0");

        new Thread(new Runnable() {
            @Override
            public void run() {
                //事务是否成功
                boolean isSuccess = false;
                while(!isSuccess) {
                    redisTemplate.watch("key1");
                    Object result = redisTemplate.execute(new SessionCallback<Object>() {
                        @Override
                        public <K, V> Object execute(RedisOperations<K, V> redisOperations) throws DataAccessException {
                            redisOperations.multi();
                            redisOperations.opsForValue().set((K) "key1", (V) ("test1"));
                            redisOperations.expire((K) "key1", 50000, TimeUnit.SECONDS);
                            redisOperations.opsForValue().get("key1");
                            return redisOperations.exec();
                        }
                    });
                    if (ObjectUtils.isEmpty(result)) {
//                        isExist = true;
                        isSuccess = false;
                    }else{
                        isSuccess = true;
                    }
                    System.out.println("redis exec result:" + result);
                }

            }
        }).start();


        Thread.sleep(10 * 1000);
        System.out.println("get key:" + redisTemplate.opsForValue().get("key1"));
    }
}

在这里插入图片描述
在尝试多次后事务终于成功了。

4、事务中不适合做逻辑判断

例如,我想要如下的效果,当key2为5的时候,我将其修改为6:

		redisTemplate.setEnableTransactionSupport(true);

        redisTemplate.opsForValue().set("key2", "5", 5000, TimeUnit.SECONDS);
        Object result = redisTemplate.execute(new SessionCallback<Object>() {
            @Override
            public <K, V> Object execute(RedisOperations<K, V> operations) throws DataAccessException {
                operations.multi();
                V key2 = operations.opsForValue().get("key2");
                System.out.println("tranctioning get key2: "+ key2);
                if("5".equals(key2)){
                    operations.opsForValue().set((K)"key2", (V)"6");
                }
                return operations.exec();
            }
        });
        System.out.println("exec result:" + result);
        System.out.println("get key2: "+ redisTemplate.opsForValue().get("key2"));

在这里插入图片描述
可以发现事务执行过程中,我们根本获取不到任何值。这是因为exec前的所有操作仅仅是将命令放入了队列中,根本没有实际执行,这时候获取结果都是null。

有人说既然事务中不能获取,是否可以在事务之前先获取值,事务中判断呢?更不行哒。看下面这个例子,模拟INCR,10个线程同时操作key3,每个线程进行100次的自增操作,由于事务中无法获取当前的key3的值,只能在事务之前获取。

	redisTemplate.setEnableTransactionSupport(true);
	redisTemplate.opsForValue().set("key3", "0");
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                for (int j = 0; j <100 ; j++) {
                    redisTemplate.watch("key3");
                    boolean isSuccess = false;
                    while(!isSuccess) {
                        final Integer[] num = {Integer.parseInt(redisTemplate.opsForValue().get("key3"))};
                        Object result = redisTemplate.execute(new SessionCallback<Object>() {
                            @Override
                            public <K, V> Object execute(RedisOperations<K, V> operations) throws DataAccessException {
                                operations.multi();
                                operations.opsForValue().set((K) "key3", (V) (++num[0] + ""), 5000, TimeUnit.SECONDS);
                                return operations.exec();
                            }
                        });
                        if(!ObjectUtils.isEmpty(result)){
                            isSuccess = true;
                        }
                        System.out.println(Thread.currentThread().getName()+"-"+j+"-exec result:" + result);
                    }
                }
            }, "Thread-"+i).start();
        }
        Thread.sleep(20 * 1000);
        System.out.println("get key3:" + redisTemplate.opsForValue().get("key3"));

即使可以保证每次事务都成功,依然无法保证最后的结果是1000。
在这里插入图片描述
这种怎么办呢?目前所知估计只有lua脚本了。

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