Redis:多线程修改同一个Key使用watch+事务(mutil)实现乐观锁

Posted yy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Redis:多线程修改同一个Key使用watch+事务(mutil)实现乐观锁相关的知识,希望对你有一定的参考价值。

本篇文章是通过watch(监控)+mutil(事务)实现应用于在分布式高并发处理等相关场景。下边先通过redis-cli.exe来测试多个线程修改时,遇到问题及解决问题。

高并发下修改同一个key遇到的问题:

1)定义一个hash类型的key,key为:lock_test,元素locker的值初始化为0。

2)实现高并发下对locker元素的值递增:定义64个多线程,并发的对lock_test元素locker的值进行修改。

package com.dx.es;

import java.util.concurrent.CountDownLatch;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

public class Test_UnLock {
    public static void main(String[] args) {
        final JedisPool pool = RedisUtil.getPool();

        // 获得jedis对象
        Jedis jedis = pool.getResource();
        jedis.hset("lock_test", "locker", "0");
        String val = jedis.hget("lock_test", "locker");
        System.out.println("lock_test.locker的初始值為:" + val);
        jedis.close();

        int threahSize = 64;
        final CountDownLatch threadsCountDownLatch = new CountDownLatch(threahSize);

        Runnable handler = new Runnable() {
            public void run() {
                Jedis jedis = pool.getResource();

                Integer integer = Integer.valueOf(jedis.hget("lock_test", "locker"));
                jedis.hset("lock_test", "locker", String.valueOf(integer + 1));

                jedis.close();
                threadsCountDownLatch.countDown();
            }
        };

        for (int i = 0; i < threahSize; i++) {
            new Thread(handler).start();
        }

        // 等待所有并行子线程任务完成。
        try {
            threadsCountDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("complete");

        val = jedis.hget("lock_test", "locker");
        System.out.println(val);
    }
}

此时,会出现以下问题:

  1. A线程获取key的值为0,而B线程也获取jkey的值0,则A把key值递增为1,B线程也实现把key值递增为1。两个线程都执行了key值修改:0到1。
  2. 在1)中最终key修改为了1,但是c线程获取key的值为0(因为c线程读取key值时,a、b线程还未触发修改,因此c线程读取到的值为0),此时d线程读取到的值为1(因为d线程读取key值时,a、b线程已触发修改,一次d线程取到的值为1)。
  3. 此时假设d线程优先触发递增,则在c线程未触发提交之前d线程已经把值修改了2,但是c此时并不知道在它获取到值到修改之前这段时间发生了什么,直接把值修改1。

此时执行打印结果为:

lock_test.locker的初始值為:0
complete
24 #备注:也可能是其他值,可能是正确值64的可能性比较小。

通过watch(监控)+mutil(事务)解决上边的问题:

redis-cli.exe下的事务操作:

# 事务被成功执行
redis 127.0.0.1:6379> MULTI
OK

redis 127.0.0.1:6379> INCR user_id
QUEUED

redis 127.0.0.1:6379> INCR user_id
QUEUED

redis 127.0.0.1:6379> INCR user_id
QUEUED

redis 127.0.0.1:6379> PING
QUEUED

redis 127.0.0.1:6379> EXEC
1) (integer) 1
2) (integer) 2
3) (integer) 3
4) PONG

并发情况下使用watch+mutil操作:

事务块内所有命令的返回值,按命令执行的先后顺序排列。 当操作被打断时,返回空值 nil 。

A线程:

# 监视 key ,且事务成功执行
redis 127.0.0.1:6379> WATCH lock lock_times
OK

redis 127.0.0.1:6379> MULTI
OK

redis 127.0.0.1:6379> SET lock "huangz"
QUEUED

redis 127.0.0.1:6379> INCR lock_times
QUEUED

redis 127.0.0.1:6379> EXEC
1) OK
2) (integer) 1

B线程:

# 监视 key ,且事务被打断
redis 127.0.0.1:6379> WATCH lock lock_times
OK

redis 127.0.0.1:6379> MULTI
OK

redis 127.0.0.1:6379> SET lock "joe"        # 就在这时,另一个客户端修改了 lock_times 的值
QUEUED

redis 127.0.0.1:6379> INCR lock_times
QUEUED

redis 127.0.0.1:6379> EXEC                  # 因为 lock_times 被修改, joe 的事务执行失败
(nil)

上边演示了A、B线程并发下的watch+mutil操作情况。

需要掌握Redis 事务命令:

Watch 命令用于监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。可用版本:>= 2.2.0

       
       
       
       
       
       
序号命令及描述
1 DISCARD
取消事务,放弃执行事务块内的所有命令。
2 EXEC
执行所有事务块内的命令。
3 MULTI
标记一个事务块的开始。
4 UNWATCH
取消 WATCH 命令对所有 key 的监视。
5 WATCH key [key ...]
监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。

 

以上是关于Redis:多线程修改同一个Key使用watch+事务(mutil)实现乐观锁的主要内容,如果未能解决你的问题,请参考以下文章

使用redis分布式锁解决并发线程资源共享问题

大数据之Redis:watch key 和unwatch

Redis事务

python学习笔记4-redis multi watch实现锁库存

redis可以多key对应一个value吗

redis使用总结记录