Redis:解决分布式高并发修改同一个Key的问题

本篇文章是通过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);
    }
}

RedisUtil.java

package com.dx.es;

import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

public class RedisUtil {
    public static JedisPool getPool() {
        // 简单创建 Jedis的方法:
        // final Jedis jedis = new Jedis("127.0.0.1",6379);

        // 下边使用线程池的方案:jedis对象是线程不安全的,因此在并发情况下要使用JedisPool,默认情况下jedisPool只支持8个连接,因此在声明JedisPool时要先修改JedisPool的最大连接数
        JedisPoolConfig config = new JedisPoolConfig();
        // 修改最大连接数
        config.setMaxTotal(32);
        
        // 声明一个线程池
        JedisPool pool = new JedisPool(config, "127.0.0.1", 6379);

        return pool;
    }
}

此时,会出现以下问题:

  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 事务命令:

下表列出了 redis 事务的相关命令:

序号 命令 描述 可用版本
 1  DISCARD

Redis Discard 命令用于取消事务,放弃执行事务块内的所有命令。

语法 redis Discard 命令基本语法如下:

redis 127.0.0.1:6379> DISCARD
 >= 2.0.0
 2  EXEC

Redis Exec 命令用于执行所有事务块内的命令。

语法 redis Exec 命令基本语法如下:

redis 127.0.0.1:6379>Exec
 >= 1.2.0
 3  MULTI

Redis Multi 命令用于标记一个事务块的开始。

事务块内的多条命令会按照先后顺序被放进一个队列当中,最后由 EXEC 命令原子性(atomic)地执行。

语法 redis Multi 命令基本语法如下:

redis 127.0.0.1:6379>Multi
 >= 1.2.0
 4  UNWATCH

Redis Unwatch 命令用于取消 WATCH 命令对所有 key 的监视。

语法 redis Unwatch 命令基本语法如下:

redis 127.0.0.1:6379> UNWATCH 
 >= 2.2.0
 5  WATCH

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

语法 redis Watch 命令基本语法如下:

WATCH key [key ...]
 >= 2.2.0

 Redis 事务可以一次执行多个命令, 并且带有以下两个重要的保证:

  • 批量操作在发送 EXEC 命令前被放入队列缓存。
  • 收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行。
  • 在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中。

一个事务从开始到执行会经历以下三个阶段:

  • 开始事务。
  • 命令入队。
  • 执行事务。

备注:概念性摘自《http://www.runoob.com/redis/redis-transactions.html》

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操作情况。

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

package com.dx.es;

import java.util.List;
import java.util.concurrent.CountDownLatch;

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

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

        // 对测试key赋初始值
        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();

                while (true) {
                    jedis.watch("lock_test");
                    String val = jedis.hget("lock_test", "locker");
                    Integer integer = Integer.valueOf(val);
                    Transaction tx = jedis.multi();

                    tx.hset("lock_test", "locker", String.valueOf(integer + 1));

                    List<Object> exec = tx.exec();

                    if (exec == null || exec.isEmpty()) {
                        System.out.println(Thread.currentThread().getName() + ":" + "Error:(" + val + "=>" + (integer + 1) + ")");
                    } else {
                        String values = "";
                        for (int i = 0; i < exec.size(); i++) {
                            values += exec.get(i).toString();
                        }
                        System.out.println(Thread.currentThread().getName() + ":" + values + ":(" + val + "=>" + (integer + 1) + ")");
                        break;
                    }

                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                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);
    }
}

打印结果:

lock_test.locker的初始值為:0
Thread-8:0:(0=>1)
Thread-56:Error:(1=>2)
Thread-53:0:(1=>2)
Thread-25:Error:(0=>1)
Thread-18:Error:(0=>1)
Thread-3:Error:(0=>1)
Thread-30:Error:(2=>3)
Thread-11:Error:(2=>3)
Thread-9:Error:(0=>1)
Thread-63:0:(2=>3)
Thread-7:Error:(0=>1)
Thread-10:0:(3=>4)
Thread-34:0:(4=>5)
Thread-65:Error:(4=>5)
Thread-24:Error:(0=>1)
Thread-17:Error:(0=>1)
Thread-62:0:(5=>6)
Thread-29:Error:(6=>7)
Thread-61:0:(6=>7)
Thread-64:0:(7=>8)
Thread-16:Error:(0=>1)
Thread-19:Error:(8=>9)
Thread-6:Error:(0=>1)
Thread-28:Error:(0=>1)
Thread-21:Error:(0=>1)
Thread-14:Error:(0=>1)
Thread-20:Error:(0=>1)
Thread-5:Error:(0=>1)
Thread-13:Error:(0=>1)
Thread-15:Error:(0=>1)
Thread-22:Error:(0=>1)
Thread-4:Error:(0=>1)
Thread-12:Error:(0=>1)
Thread-23:Error:(0=>1)
Thread-54:Error:(0=>1)
Thread-57:Error:(0=>1)
Thread-26:0:(8=>9)
Thread-27:Error:(8=>9)
Thread-32:Error:(9=>10)
Thread-35:Error:(9=>10)
Thread-56:0:(9=>10)
Thread-33:Error:(10=>11)
Thread-50:0:(10=>11)
Thread-31:0:(11=>12)
Thread-38:0:(12=>13)
Thread-25:Error:(13=>14)
Thread-36:0:(13=>14)
Thread-39:Error:(14=>15)
Thread-14:0:(14=>15)
Thread-19:Error:(14=>15)
Thread-17:Error:(14=>15)
Thread-6:Error:(14=>15)
Thread-9:Error:(14=>15)
Thread-33:Error:(14=>15)
Thread-35:Error:(14=>15)
Thread-23:Error:(14=>15)
Thread-18:Error:(14=>15)
Thread-15:Error:(14=>15)
Thread-11:Error:(14=>15)
Thread-7:Error:(14=>15)
Thread-57:Error:(14=>15)
Thread-27:Error:(14=>15)
Thread-16:Error:(14=>15)
Thread-65:Error:(14=>15)
Thread-24:Error:(14=>15)
Thread-13:Error:(14=>15)
Thread-32:Error:(15=>16)
Thread-28:Error:(14=>15)
Thread-21:Error:(14=>15)
Thread-30:Error:(14=>15)
Thread-54:Error:(14=>15)
Thread-22:Error:(14=>15)
Thread-25:Error:(14=>15)
Thread-3:Error:(14=>15)
Thread-29:Error:(14=>15)
Thread-5:Error:(14=>15)
Thread-12:Error:(14=>15)
Thread-20:Error:(14=>15)
Thread-40:0:(15=>16)
Thread-4:Error:(14=>15)
Thread-41:0:(16=>17)
Thread-44:0:(17=>18)
Thread-45:0:(18=>19)
Thread-47:0:(19=>20)
Thread-43:0:(20=>21)
Thread-48:0:(21=>22)
Thread-37:0:(22=>23)
Thread-49:0:(23=>24)
Thread-55:0:(24=>25)
Thread-60:0:(25=>26)
Thread-42:0:(26=>27)
Thread-52:0:(27=>28)
Thread-46:0:(28=>29)
Thread-58:0:(29=>30)
Thread-51:0:(30=>31)
Thread-66:0:(31=>32)
Thread-59:0:(32=>33)
Thread-17:0:(33=>34)
Thread-19:Error:(33=>34)
Thread-39:Error:(33=>34)
Thread-28:0:(34=>35)
Thread-54:Error:(34=>35)
Thread-65:Error:(34=>35)
Thread-25:Error:(34=>35)
Thread-30:Error:(34=>35)
Thread-5:Error:(35=>36)
Thread-13:Error:(35=>36)
Thread-16:Error:(34=>35)
Thread-6:Error:(34=>35)
Thread-9:Error:(34=>35)
Thread-21:Error:(35=>36)
Thread-29:Error:(35=>36)
Thread-33:Error:(34=>35)
Thread-57:Error:(35=>36)
Thread-24:Error:(34=>35)
Thread-22:Error:(34=>35)
Thread-32:Error:(35=>36)
Thread-23:Error:(34=>35)
Thread-7:Error:(34=>35)
Thread-15:Error:(34=>35)
Thread-4:0:(35=>36)
Thread-20:Error:(35=>36)
Thread-12:Error:(35=>36)
Thread-35:0:(36=>37)
Thread-18:Error:(36=>37)
Thread-11:Error:(36=>37)
Thread-3:Error:(36=>37)
Thread-27:Error:(37=>38)
Thread-39:Error:(37=>38)
Thread-19:0:(37=>38)
Thread-7:Error:(38=>39)
Thread-33:0:(38=>39)
Thread-29:Error:(38=>39)
Thread-16:Error:(38=>39)
Thread-22:Error:(38=>39)
Thread-65:Error:(38=>39)
Thread-54:Error:(38=>39)
Thread-57:Error:(38=>39)
Thread-30:Error:(38=>39)
Thread-21:Error:(38=>39)
Thread-24:Error:(38=>39)
Thread-32:Error:(39=>40)
Thread-5:Error:(39=>40)
Thread-13:Error:(39=>40)
Thread-6:Error:(38=>39)
Thread-25:Error:(38=>39)
Thread-9:Error:(38=>39)
Thread-20:0:(39=>40)
Thread-12:Error:(39=>40)
Thread-15:Error:(38=>39)
Thread-23:Error:(38=>39)
Thread-18:Error:(40=>41)
Thread-3:Error:(40=>41)
Thread-27:0:(40=>41)
Thread-11:Error:(40=>41)
Thread-39:0:(41=>42)
Thread-7:Error:(42=>43)
Thread-54:0:(42=>43)
Thread-22:Error:(42=>43)
Thread-30:Error:(42=>43)
Thread-57:Error:(42=>43)
Thread-65:Error:(42=>43)
Thread-32:Error:(43=>44)
Thread-24:Error:(43=>44)
Thread-5:Error:(42=>43)
Thread-21:Error:(42=>43)
Thread-16:Error:(43=>44)
Thread-29:Error:(43=>44)
Thread-6:0:(43=>44)
Thread-9:Error:(43=>44)
Thread-23:Error:(43=>44)
Thread-25:Error:(43=>44)
Thread-15:Error:(43=>44)
Thread-12:Error:(43=>44)
Thread-13:Error:(44=>45)
Thread-11:0:(44=>45)
Thread-18:Error:(44=>45)
Thread-3:Error:(44=>45)
Thread-57:0:(45=>46)
Thread-7:Error:(45=>46)
Thread-22:Error:(45=>46)
Thread-30:Error:(45=>46)
Thread-9:0:(46=>47)
Thread-65:Error:(46=>47)
Thread-25:Error:(46=>47)
Thread-24:Error:(46=>47)
Thread-21:Error:(47=>48)
Thread-32:Error:(47=>48)
Thread-5:Error:(47=>48)
Thread-15:Error:(47=>48)
Thread-16:0:(47=>48)
Thread-29:Error:(47=>48)
Thread-12:Error:(48=>49)
Thread-23:Error:(47=>48)
Thread-13:0:(48=>49)
Thread-18:Error:(49=>50)
Thread-3:Error:(49=>50)
Thread-7:0:(49=>50)
Thread-30:Error:(49=>50)
Thread-22:Error:(49=>50)
Thread-25:0:(50=>51)
Thread-65:Error:(50=>51)
Thread-12:0:(51=>52)
Thread-21:Error:(51=>52)
Thread-32:Error:(52=>53)
Thread-24:Error:(52=>53)
Thread-29:Error:(52=>53)
Thread-5:Error:(52=>53)
Thread-23:Error:(52=>53)
Thread-15:Error:(52=>53)
Thread-18:0:(52=>53)
Thread-3:Error:(52=>53)
Thread-30:0:(53=>54)
Thread-22:Error:(53=>54)
Thread-65:0:(54=>55)
Thread-24:0:(55=>56)
Thread-21:Error:(55=>56)
Thread-32:Error:(55=>56)
Thread-5:0:(56=>57)
Thread-29:Error:(57=>58)
Thread-15:0:(57=>58)
Thread-23:Error:(57=>58)
Thread-3:0:(58=>59)
Thread-22:0:(59=>60)
Thread-32:0:(60=>61)
Thread-21:Error:(60=>61)
Thread-29:0:(61=>62)
Thread-23:Error:(61=>62)
Thread-21:0:(62=>63)
Thread-23:0:(63=>64)
complete
64

猜你喜欢

转载自www.cnblogs.com/yy3b2007com/p/9383713.html