1、注意点:redis watch 命令用于监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。监控一直持续到EXEC命令(事务中的命令是在EXEC之后才执行的,所以在MULTI命令后可以修改WATCH监控的键值)
2:代码如下:
public class JedisRunnable implements Runnable {
private Jedis jedis = JedisUtil.getJedis();
private String userId;
public JedisRunnable(String userId) {
this.userId = userId;
}
public void run() {
try {
// 事务状态,如果监控的key没有发生改变,那么应该返回OK,事务也可以正常执行。
jedis.watch(RedisSecKiller.WATCH_KEY);
// 获取剩余商品数
int leftGoodsNum = Integer.valueOf(jedis.get(RedisSecKiller.WATCH_KEY));
// 当剩余商品数大于0时,才进行剩余商品数减1的事务操作。
if (leftGoodsNum > 0) {
// 开启jedis事务
Transaction tx = jedis.multi();
// 方法一:在事务中对键Goods对应的值做减1操作,此时tx.exec()的返回值的第一个元素是Goods对应的当前值。
tx.decrBy(RedisSecKiller.WATCH_KEY, 1);
// 方法二:在事务中设置Goods的值为原值减1,此时tx.exec()的返回值的第一个元素是"OK"。
// tx.set(RedisSecKiller.WATCH_KEY, String.valueOf(leftGoodsNum - 1));
// 执行事务,得到返回值。
List<Object> results = tx.exec();
// leftGoodsNum比键Goods对应的值大1,因为事务中刚执行了减1操作。
// 由此可知,在当前事务中,leftGoodsNum与Goods对应的值(真实剩余商品数量)并不同步。
// System.out.println("剩余商品数量:" + leftGoodsNum);
// System.out.println("真实剩余商品数量:" + results);
// results为null或空时,表示并发情况下用户没能抢购到商品,秒杀失败。
if (results == null || results.isEmpty()) {
String failUserInfo = "fail---" + userId;
// 此时无法通过results.get(0)获取真实剩余商品数量。
String failMsg = "用户" + failUserInfo + ",抢购失败,剩余商品数量:" + leftGoodsNum +
",但此时无法获取真实剩余商品数量。";
System.out.println(failMsg);
// 将秒杀失败的用户信息存入Redis。
jedis.setnx(failUserInfo, failMsg);
} else { // 此时tx.exec()事务执行成功,会自动提交事务。
for (Object succ : results) {
String succUserInfo = "succ" + succ.toString() + "---" + userId;
String succMsg = "用户" + succUserInfo + ",抢购成功,当前抢购成功人数:" +
(10 - Integer.parseInt(results.get(0).toString())) +
",真实剩余商品数量:" + Integer.parseInt(results.get(0).toString());
System.out.println(succMsg);
// 将秒杀成功的用户信息存入Redis。
jedis.setnx(succUserInfo, succMsg);
}
}
} else { // 此时库存为0,秒杀活动结束。
String overUserInfo = "over---" + userId;
String overMsg = "用户" + overUserInfo + ",商品被抢购完毕,剩余商品数量:" + leftGoodsNum;
System.out.println(overMsg);
// 将秒杀活动结束后还在访问秒杀系统的用户信息存入Redis。
jedis.setnx(overUserInfo, overMsg);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JedisUtil.returnResource(jedis);
}
}
}
public class RedisSecKiller {
// 模拟用户抢购最大并发数
private static final int N_THREADS = 5;
// jedis通过watch方法监控WATCH_KEY,一旦发生改变,事务将失败。
public static final String WATCH_KEY = "Goods";
// 商品总数
private static final int GOODS_NUM = 1000;
// 用户数量
private static final int USER_NUM = 100;
public static void main(String[] args) {
// 创建线程池,模拟N_THREADS位用户同时抢购的过程。
ExecutorService executorService = Executors.newFixedThreadPool(N_THREADS);
Jedis jedis = JedisUtil.getJedis();
// 设置商品总数为10
jedis.set(WATCH_KEY, String.valueOf(GOODS_NUM));
jedis.close();
// 模拟USER_NUM位用户在抢购商品
for (int i = 0; i < USER_NUM; i++) {
executorService.execute(new JedisRunnable(UUID.randomUUID().toString()));
// System.out.println("==============循环分割线===============");
}
executorService.shutdown();
}
}
public class JedisUtil {
private static final String ADDR = "192.168.222.130";
private static final int PORT = 6379;
private static final boolean TEST_ON_BORROW = true;
private static final int MAX_IDLE = 200;
private static JedisPool jedisPool = null;
static {
try {
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxIdle(MAX_IDLE);
config.setTestOnBorrow(TEST_ON_BORROW);
jedisPool = new JedisPool(config, ADDR, PORT);
} catch (Exception ex) {
ex.printStackTrace();
}
}
public synchronized static Jedis getJedis() {
try {
if (jedisPool != null) {
Jedis resource = jedisPool.getResource();
return resource;
}
return null;
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
public static void returnResource(final Jedis jedis) {
if (jedis != null) {
jedis.close();
}
}
}