本篇文章是通过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:> DISCARD
 >= 2.0.0
 2  EXEC

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

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

redis 127.0.0.1:>Exec
 >= 1.2.0
 3  MULTI

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

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

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

redis 127.0.0.1:>Multi
 >= 1.2.0
 4  UNWATCH

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

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

redis 127.0.0.1:> 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)

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的初始值為:
Thread-::(=>)
Thread-:Error:(=>)
Thread-::(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-::(=>)
Thread-:Error:(=>)
Thread-::(=>)
Thread-::(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-::(=>)
Thread-:Error:(=>)
Thread-::(=>)
Thread-::(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-::(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-::(=>)
Thread-:Error:(=>)
Thread-::(=>)
Thread-::(=>)
Thread-::(=>)
Thread-:Error:(=>)
Thread-::(=>)
Thread-:Error:(=>)
Thread-::(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-::(=>)
Thread-:Error:(=>)
Thread-::(=>)
Thread-::(=>)
Thread-::(=>)
Thread-::(=>)
Thread-::(=>)
Thread-::(=>)
Thread-::(=>)
Thread-::(=>)
Thread-::(=>)
Thread-::(=>)
Thread-::(=>)
Thread-::(=>)
Thread-::(=>)
Thread-::(=>)
Thread-::(=>)
Thread-::(=>)
Thread-::(=>)
Thread-::(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-::(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-::(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-::(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-::(=>)
Thread-:Error:(=>)
Thread-::(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-::(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-::(=>)
Thread-:Error:(=>)
Thread-::(=>)
Thread-:Error:(=>)
Thread-::(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-::(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-::(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-::(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-::(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-::(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-::(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-::(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-::(=>)
Thread-:Error:(=>)
Thread-::(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-::(=>)
Thread-:Error:(=>)
Thread-::(=>)
Thread-:Error:(=>)
Thread-::(=>)
Thread-::(=>)
Thread-:Error:(=>)
Thread-:Error:(=>)
Thread-::(=>)
Thread-:Error:(=>)
Thread-::(=>)
Thread-:Error:(=>)
Thread-::(=>)
Thread-::(=>)
Thread-::(=>)
Thread-:Error:(=>)
Thread-::(=>)
Thread-:Error:(=>)
Thread-::(=>)
Thread-::(=>)
complete

备注:实际应用场景中我这里不是实现自增,上边代码自增目的:测试是否能够在高并发下修改同一个key时实现锁的功能。

实际应用:高并发使用redis同一个key存储统计TOPN结果

需求:

1)拥有多个记录,每条记录数据格式:日期,统计指标1,统计指标2,统计指标3,统计指标4

2)要求分别统计出4个指标的最大值,并记录各自指标最大值时对应的记录。

3)记录中各个字段的模拟公式:

// 数据格式:
// 第一列:日期
// 第二列:f(x) = x*0.7+1(x自然数) 最大值 45.1
// 第三列:f(x)= -x*x+20x+1(自然数) a=-1,b=20,c=1 , 当x=-b/2a时 y 最大值:-10*10+20*10+1=101
// 第四列:f(x)= -x*x+30x+1(自然数) a=-1,b=30,c=1 , 当x=-b/2a时 y 最大值:-15*15+30*15+1=445-225+1=221
// 第五列:f(x)= 640/(x+1)(x自然数) 最大值 640

使用mutil+watch实现:

首先,把最终统计结果存储到redis的key(lock_test)中,lock_test是一个hash类型,其中存储四个键值对,每个键值对分别存储各自的指标最大值时对应的记录。

指标配置项存储到FeildConfig.java

    static class FieldConfig {
private String key;
private Integer index; public String getKey() {
return key;
} public void setKey(String key) {
this.key = key;
} public Integer getIndex() {
return index;
} public void setIndex(Integer index) {
this.index = index;
} public FieldConfig(String key, Integer index) {
super();
this.key = key;
this.index = index;
}
}

在测试类的static构造函数中初始化各自指标在一条记录的中索引位置,及一共包含的指标类型:

public class Test_Lock3 {
。。。
final static List<FieldConfig> keys = new ArrayList<FieldConfig>(); static {
keys.add(new FieldConfig("101", 1));
keys.add(new FieldConfig("102", 2));
keys.add(new FieldConfig("103", 3));
keys.add(new FieldConfig("104", 4));
}
。。。
}

备注:101代表指标1,对应在被统计记录中的第1索引位置(备注:这里索引是对记录按照“,”分割后的数组索引位置)。

制作测试数据:

        List<String> dataItems=new ArrayList<String>();

        // 数据格式:
// 第一列:日期
// 第二列:f(x) = x*0.7+1(x自然数) 最大值 45.1
// 第三列:f(x)= -x*x+20x+1(自然数) a=-1,b=20,c=1 , 当x=-b/2a时 y 最大值:-10*10+20*10+1=101
// 第四列:f(x)= -x*x+30x+1(自然数) a=-1,b=30,c=1 , 当x=-b/2a时 y 最大值:-15*15+30*15+1=445-225+1=221
// 第五列:f(x)= 640/(x+1)(x自然数) 最大值 640
for(int x=0;x<threahSize;x++){
String dataItem = "2018-07-29 00:00:00," + (x*0.7d+1) + "," + (-1d*x*x+20*x+1) + "," + (-1d*x*x+30*x+1) + "," + (640d/(x+1));
dataItems.add(dataItem);
}

以下是整体测试代码:

package com.dx.es;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch; import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.Transaction; public class Test_Lock3 {
final static JedisPool pool = RedisUtil.getPool();
final static List<FieldConfig> keys = new ArrayList<FieldConfig>(); static {
keys.add(new FieldConfig("101", 1));
keys.add(new FieldConfig("102", 2));
keys.add(new FieldConfig("103", 3));
keys.add(new FieldConfig("104", 4));
} public static void main(String[] args) {
int threahSize = 64;
final CountDownLatch threadsCountDownLatch = new CountDownLatch(threahSize);
Jedis jedis = pool.getResource();
jedis.del("lock_test"); List<String> dataItems=new ArrayList<String>(); // 数据格式:
// 第一列:日期
// 第二列:f(x) = x*0.7+1(x自然数) 最大值 45.1
// 第三列:f(x)= -x*x+20x+1(自然数) a=-1,b=20,c=1 , 当x=-b/2a时 y 最大值:-10*10+20*10+1=101
// 第四列:f(x)= -x*x+30x+1(自然数) a=-1,b=30,c=1 , 当x=-b/2a时 y 最大值:-15*15+30*15+1=445-225+1=221
// 第五列:f(x)= 640/(x+1)(x自然数) 最大值 640
for(int x=0;x<threahSize;x++){
String dataItem = "2018-07-29 00:00:00," + (x*0.7d+1) + "," + (-1d*x*x+20*x+1) + "," + (-1d*x*x+30*x+1) + "," + (640d/(x+1));
dataItems.add(dataItem);
} for (int i = 0; i < threahSize; i++) {
Jedis jedisT = pool.getResource(); Runnable handler = new MyRunable(jedisT, threadsCountDownLatch, dataItems.get(i)); new Thread(handler).start();
} // 等待所有并行子线程任务完成。
try {
threadsCountDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
} System.out.println("complete"); Map<String, String> values = jedis.hgetAll("lock_test");
jedis.close(); System.out.println(values);
} static class MyRunable implements Runnable {
private Jedis jedis = null;
private CountDownLatch threadsCountDownLatch = null;
private String newLine = null; public MyRunable(Jedis jedis, CountDownLatch threadsCountDownLatch, String newLine) {
this.jedis = jedis;
this.threadsCountDownLatch = threadsCountDownLatch;
this.newLine = newLine;
} public void run() {
while (true) {
this.jedis.watch("lock_test"); Map<String, String> newValues = new HashMap<String, String>();
Map<String, String> oldValues = this.jedis.hgetAll("lock_test"); String newValueStr=""; for (FieldConfig key : keys) {
if (oldValues.containsKey(key.getKey())) {
Double oldValue = Double.valueOf(oldValues.get(key.getKey()).split(",")[key.getIndex()]);
Double newValue = Double.valueOf(newLine.split(",")[key.getIndex()]);
if (newValue > oldValue) {
newValues.put(key.getKey(), newLine);
newValueStr=newLine;
} else {
newValues.put(key.getKey(), oldValues.get(key.getKey()));
newValueStr=oldValues.get(key.getKey());
}
} else {
newValues.put(key.getKey(), newLine);
newValueStr=newLine;
}
} Transaction tx = this.jedis.multi();
tx.hmset("lock_test", newValues); List<Object> exec = tx.exec(); if (exec == null || exec.isEmpty()) {
//System.out.println(Thread.currentThread().getName() + ":" + "Error:(" + oldValueStr + "->"+newValueStr+")");
} else {
String values = "";
for (int i = 0; i < exec.size(); i++) {
values += exec.get(i).toString();
}
System.out.println(Thread.currentThread().getName() + ":" + values + ":(" + newLine +"->"+newValueStr+")");
break;
} try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
} this.jedis.close();
this.threadsCountDownLatch.countDown();
}
} static class FieldConfig {
private String key;
private Integer index; public String getKey() {
return key;
} public void setKey(String key) {
this.key = key;
} public Integer getIndex() {
return index;
} public void setIndex(Integer index) {
this.index = index;
} public FieldConfig(String key, Integer index) {
super();
this.key = key;
this.index = index;
}
}
}

测试:

Thread-:OK:(-- ::,15.0,1.0,201.0,30.476190476190474->-- ::,15.0,1.0,201.0,30.476190476190474)
Thread-:OK:(-- ::,19.2,-155.0,105.0,23.703703703703702->-- ::,15.0,1.0,201.0,30.476190476190474)
Thread-:OK:(-- ::,22.0,-299.0,1.0,20.64516129032258->-- ::,15.0,1.0,201.0,30.476190476190474)
Thread-:OK:(-- ::,15.7,-20.0,190.0,29.09090909090909->-- ::,15.0,1.0,201.0,30.476190476190474)
Thread-:OK:(-- ::,24.099999999999998,-428.0,-98.0,18.823529411764707->-- ::,15.0,1.0,201.0,30.476190476190474)
Thread-:OK:(-- ::,21.299999999999997,-260.0,30.0,21.333333333333332->-- ::,15.0,1.0,201.0,30.476190476190474)
Thread-:OK:(-- ::,26.2,-575.0,-215.0,17.2972972972973->-- ::,15.0,1.0,201.0,30.476190476190474)
Thread-:OK:(-- ::,26.9,-628.0,-258.0,16.842105263157894->-- ::,15.0,1.0,201.0,30.476190476190474)
Thread-:OK:(-- ::,16.4,-43.0,177.0,27.82608695652174->-- ::,15.0,1.0,201.0,30.476190476190474)
Thread-:OK:(-- ::,1.0,1.0,1.0,640.0->-- ::,1.0,1.0,1.0,640.0)
Thread-:OK:(-- ::,10.1,92.0,222.0,45.714285714285715->-- ::,1.0,1.0,1.0,640.0)
Thread-:OK:(-- ::,13.6,37.0,217.0,33.68421052631579->-- ::,1.0,1.0,1.0,640.0)
Thread-:OK:(-- ::,30.4,-923.0,-503.0,14.883720930232558->-- ::,1.0,1.0,1.0,640.0)
Thread-:OK:(-- ::,18.5,-124.0,126.0,24.615384615384617->-- ::,1.0,1.0,1.0,640.0)
Thread-:OK:(-- ::,31.099999999999998,-988.0,-558.0,14.545454545454545->-- ::,1.0,1.0,1.0,640.0)
Thread-:OK:(-- ::,7.3,100.0,190.0,64.0->-- ::,1.0,1.0,1.0,640.0)
Thread-:OK:(-- ::,22.7,-340.0,-30.0,20.0->-- ::,1.0,1.0,1.0,640.0)
Thread-:OK:(-- ::,33.199999999999996,-1195.0,-735.0,13.617021276595745->-- ::,1.0,1.0,1.0,640.0)
Thread-:OK:(-- ::,33.9,-1268.0,-798.0,13.333333333333334->-- ::,1.0,1.0,1.0,640.0)
Thread-:OK:(-- ::,35.3,-1420.0,-930.0,12.8->-- ::,1.0,1.0,1.0,640.0)
Thread-:OK:(-- ::,36.0,-1499.0,-999.0,12.549019607843137->-- ::,1.0,1.0,1.0,640.0)
Thread-:OK:(-- ::,36.699999999999996,-1580.0,-1070.0,12.307692307692308->-- ::,1.0,1.0,1.0,640.0)
Thread-:OK:(-- ::,37.4,-1663.0,-1143.0,12.075471698113208->-- ::,1.0,1.0,1.0,640.0)
Thread-:OK:(-- ::,38.099999999999994,-1748.0,-1218.0,11.851851851851851->-- ::,1.0,1.0,1.0,640.0)
Thread-:OK:(-- ::,38.8,-1835.0,-1295.0,11.636363636363637->-- ::,1.0,1.0,1.0,640.0)
Thread-:OK:(-- ::,39.5,-1924.0,-1374.0,11.428571428571429->-- ::,1.0,1.0,1.0,640.0)
Thread-:OK:(-- ::,40.199999999999996,-2015.0,-1455.0,11.228070175438596->-- ::,1.0,1.0,1.0,640.0)
Thread-:OK:(-- ::,40.9,-2108.0,-1538.0,11.03448275862069->-- ::,1.0,1.0,1.0,640.0)
Thread-:OK:(-- ::,41.599999999999994,-2203.0,-1623.0,10.847457627118644->-- ::,1.0,1.0,1.0,640.0)
Thread-:OK:(-- ::,42.3,-2300.0,-1710.0,10.666666666666666->-- ::,1.0,1.0,1.0,640.0)
Thread-:OK:(-- ::,43.0,-2399.0,-1799.0,10.491803278688524->-- ::,1.0,1.0,1.0,640.0)
Thread-:OK:(-- ::,43.699999999999996,-2500.0,-1890.0,10.32258064516129->-- ::,1.0,1.0,1.0,640.0)
Thread-:OK:(-- ::,44.4,-2603.0,-1983.0,10.158730158730158->-- ::,1.0,1.0,1.0,640.0)
Thread-:OK:(-- ::,6.6,97.0,177.0,71.11111111111111->-- ::,1.0,1.0,1.0,640.0)
Thread-:OK:(-- ::,27.599999999999998,-683.0,-303.0,16.41025641025641->-- ::,1.0,1.0,1.0,640.0)
Thread-:OK:(-- ::,31.799999999999997,-1055.0,-615.0,14.222222222222221->-- ::,1.0,1.0,1.0,640.0)
Thread-:OK:(-- ::,24.799999999999997,-475.0,-135.0,18.285714285714285->-- ::,1.0,1.0,1.0,640.0)
Thread-:OK:(-- ::,32.5,-1124.0,-674.0,13.91304347826087->-- ::,1.0,1.0,1.0,640.0)
Thread-:OK:(-- ::,2.4,37.0,57.0,213.33333333333334->-- ::,1.0,1.0,1.0,640.0)
Thread-:OK:(-- ::,28.299999999999997,-740.0,-350.0,16.0->-- ::,1.0,1.0,1.0,640.0)
Thread-:OK:(-- ::,4.5,76.0,126.0,106.66666666666667->-- ::,1.0,1.0,1.0,640.0)
Thread-:OK:(-- ::,5.199999999999999,85.0,145.0,91.42857142857143->-- ::,1.0,1.0,1.0,640.0)
Thread-:OK:(-- ::,29.0,-799.0,-399.0,15.609756097560975->-- ::,1.0,1.0,1.0,640.0)
Thread-:OK:(-- ::,3.8,65.0,105.0,128.0->-- ::,1.0,1.0,1.0,640.0)
Thread-:OK:(-- ::,29.7,-860.0,-450.0,15.238095238095237->-- ::,1.0,1.0,1.0,640.0)
Thread-:OK:(-- ::,11.5,76.0,226.0,40.0->-- ::,1.0,1.0,1.0,640.0)
Thread-:OK:(-- ::,12.899999999999999,52.0,222.0,35.55555555555556->-- ::,1.0,1.0,1.0,640.0)
Thread-:OK:(-- ::,9.399999999999999,97.0,217.0,49.23076923076923->-- ::,1.0,1.0,1.0,640.0)
Thread-:OK:(-- ::,17.799999999999997,-95.0,145.0,25.6->-- ::,1.0,1.0,1.0,640.0)
Thread-:OK:(-- ::,10.799999999999999,85.0,225.0,42.666666666666664->-- ::,1.0,1.0,1.0,640.0)
Thread-:OK:(-- ::,5.8999999999999995,92.0,162.0,80.0->-- ::,1.0,1.0,1.0,640.0)
Thread-:OK:(-- ::,45.099999999999994,-2708.0,-2078.0,10.0->-- ::,1.0,1.0,1.0,640.0)
Thread-:OK:(-- ::,12.2,65.0,225.0,37.64705882352941->-- ::,1.0,1.0,1.0,640.0)
Thread-:OK:(-- ::,17.099999999999998,-68.0,162.0,26.666666666666668->-- ::,1.0,1.0,1.0,640.0)
Thread-:OK:(-- ::,1.7,20.0,30.0,320.0->-- ::,1.0,1.0,1.0,640.0)
Thread-:OK:(-- ::,23.4,-383.0,-63.0,19.393939393939394->-- ::,1.0,1.0,1.0,640.0)
Thread-:OK:(-- ::,14.299999999999999,20.0,210.0,32.0->-- ::,1.0,1.0,1.0,640.0)
Thread-:OK:(-- ::,20.599999999999998,-223.0,57.0,22.06896551724138->-- ::,1.0,1.0,1.0,640.0)
Thread-:OK:(-- ::,34.599999999999994,-1343.0,-863.0,13.061224489795919->-- ::,1.0,1.0,1.0,640.0)
Thread-:OK:(-- ::,19.9,-188.0,82.0,22.857142857142858->-- ::,1.0,1.0,1.0,640.0)
Thread-:OK:(-- ::,3.0999999999999996,52.0,82.0,160.0->-- ::,1.0,1.0,1.0,640.0)
Thread-:OK:(-- ::,8.7,100.0,210.0,53.333333333333336->-- ::,1.0,1.0,1.0,640.0)
Thread-:OK:(-- ::,8.0,101.0,201.0,58.18181818181818->-- ::,1.0,1.0,1.0,640.0)
Thread-:OK:(-- ::,25.5,-524.0,-174.0,17.77777777777778->-- ::,1.0,1.0,1.0,640.0)
complete
{
=-- ::,45.099999999999994,-2708.0,-2078.0,10.0,
102=-- ::,8.0,101.0,201.0,58.18181818181818,
103=2018-07-29 00:00:00,11.5,76.0,226.0,40.0,
104=2018-07-29 00:00:00,1.0,1.0,1.0,640.0
}

参考:《通过Jedis的setnx、multi事务及watch实现三种分布式跨JVM锁的方法代码示例》注意:本章文章中介绍了三种锁的方案,但是这三种方案并不能解决topn的问题,仅供参考。

Redis:解决分布式高并发修改同一个Key的问题的更多相关文章

  1. 170222、使用Spring Session和Redis解决分布式Session跨域共享问题

    使用Spring Session和Redis解决分布式Session跨域共享问题 原创 2017-02-27 徐刘根 Java后端技术 前言 对于分布式使用Nginx+Tomcat实现负载均衡,最常用 ...

  2. 使用Spring Session和Redis解决分布式Session跨域共享问题

    http://blog.csdn.net/xlgen157387/article/details/57406162 使用Spring Session和Redis解决分布式Session跨域共享问题

  3. 利用redis + lua解决抢红包高并发的问题

    抢红包的需求分析 抢红包的场景有点像秒杀,但是要比秒杀简单点.因为秒杀通常要和库存相关.而抢红包则可以允许有些红包没有被抢到,因为发红包的人不会有损失,没抢完的钱再退回给发红包的人即可.另外像小米这样 ...

  4. 如何解决java高并发详细讲解

    对于我们开发的网站,如果网站的访问量非常大的话,那么我们就需要考虑相关的并发访问问题了.而并发问题是绝大部分的程序员头疼的问题, 但话又说回来了,既然逃避不掉,那我们就坦然面对吧~今天就让我们一起来研 ...

  5. 【高并发】Redis如何助力高并发秒杀系统,看完这篇我彻底懂了!!

    写在前面 之前,我们在<[高并发]高并发秒杀系统架构解密,不是所有的秒杀都是秒杀!>一文中,详细讲解了高并发秒杀系统的架构设计,其中,我们介绍了可以使用Redis存储秒杀商品的库存数量.很 ...

  6. 面试官问你如何解决web高并发这样回答就好了

    所谓高并发,就是同一时间有很多流量(通常指用户)访问程序的接口.页面及其他资源,解决高并发就是当流量峰值到来时保证程序的稳定性. 我们一般用QPS(每秒查询数,又叫每秒请求数)来衡量程序的综合性能,数 ...

  7. 分布式高并发物联网(车联网-JT808协议)平台架构方案

    技术支持QQ:78772895 1.车载终端网关采用mina/netty+spring架构,独立于其他应用,主要负责维护接入终端的tcp链接.上行以及下行消息的解码.编码.流量控制,黑白名单等安全控制 ...

  8. Nginx+Redis+Ehcache大型高并发高可用三层架构总结

    在生产环境中,对于高并发架构,我们知道缓存 是最重要的环节,对于大量的高并发.可以采用三层缓存架构来实现,也就是Nginx+Redis+Ehcache 对于中间件Nginx常来做流量分发,同事ngin ...

  9. 【原创】有关Buffer使用,让你的日志类库解决IO高并发写

    [本人原创],欢迎交流和分享技术,转载请附上如下内容: 作者:itshare [转自]http://www.cnblogs.com/itshare/ 通常我们知道,当一个日志借口被外部程序多个线程请求 ...

随机推荐

  1. FireDAC 下的 Sqlite [1] - 前言

    很长时间没静下心来写博客了, 现在回来, 是 Delphi 不断地进步让我感动.振奋. Delphi XE5 并入了 FireDAC, 第一印象非常好, 恐怕 dbExpress 等等都要靠边站了. ...

  2. CentOS 7解压安装PHP7.1.21

    下载php yum install -y wget wget http://cn2.php.net/distributions/php-7.1.21.tar.gz 解压 tar -zxvf php-7 ...

  3. JTAG Simplified

    JTAG Simplified So the other day, I explored the JTAG bus interface which is frequently found in CPL ...

  4. LINUX 内核 API

    http://www.compsoc.man.ac.uk/~moz/kernelnewbies/documents/kdoc/kernel-api/linuxkernelapi.html

  5. RenderPartial和RenderAction区别

    本篇参考了Shailendra Chauhan和 Jag Reehal的博文. RenderParital和RenderAction的共同点: ※ 都能返回部分视图 ※ 返回的部分视图和主视图共用一个 ...

  6. excel 鼠标上下左右移动

    .Offset用法:(如果是多选单元格,偏移后选定的依然是区域) Selection.Offset(-1).select  'up Selection.Offset(1).select   'down ...

  7. WordPress主题开发:WP_Query常用参数

    常用参数 用途 调用文章或页面 s 查询和某个关键词相关的所有的文章/页面信息 p 文章或页面id post__in 多篇id post__not_in 多篇id以外 post_type 查询的信息类 ...

  8. Spring Framework 4.1.3 还是一样给力

    Spring Framework 4.1.3 发布,此版本是 4.1.x 系列的第三个维护版本,包括超过50 个 bug 修复和改进.本来是计划月末发布,但是想早些发布,配合这周要发布的 Spring ...

  9. jquery获取table,遍历输出tr中各个td的内容(转载)

    首先,依赖jquery.. 1 $('#btntb').click(function(){ 2 $('#tab tr').each(function(i){ // 遍历 tr 3 $(this).ch ...

  10. ExpandoObject对象的JSON序列化

    如果: dynamic expando = new ExpandoObject(); d.SomeProp=SomeValueOrClass; 然后,我们在控制器中: return new JsonR ...