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的初始值為:0complete24 #备注:也可能是其他值 , 可能是正确值64的可能性比较小 。通过watch+mutil解决并发修改的问题:
需要掌握Redis 事务命令:
Redis:解决分布式高并发修改同一个Key的问题

文章插图
 
Redis 事务可以一次执行多个命令 ,  并且带有以下两个重要的保证:
  • 批量操作在发送 EXEC 命令前被放入队列缓存 。
  • 收到 EXEC 命令后进入事务执行 , 事务中任意命令执行失败 , 其余的命令依然被执行 。
  • 在事务执行过程 , 其他客户端提交的命令请求不会插入到事务执行命令序列中 。
一个事务从开始到执行会经历以下三个阶段:
  • 开始事务 。
  • 命令入队 。
  • 执行事务 。
备注:概念性摘自《http://www.runoob.com/redis/redis-transactions.html》
redis-cli.exe下的事务操作:
# 事务被成功执行redis 127.0.0.1:6379> MULTIOKredis 127.0.0.1:6379> INCR user_idQUEUEDredis 127.0.0.1:6379> INCR user_idQUEUEDredis 127.0.0.1:6379> INCR user_idQUEUEDredis 127.0.0.1:6379> PINGQUEUEDredis 127.0.0.1:6379> EXEC1) (integer) 12) (integer) 23) (integer) 34) PONG并发情况下使用watch+mutil操作:
事务块内所有命令的返回值 , 按命令执行的先后顺序排列 。当操作被打断时 , 返回空值 nil。


推荐阅读