之前跟大家提过,redis本身是支持事务性操作的,但是由于其结构原因,不支持回滚操作,即:在失败之前的指令操作无法回滚;其只能保证在一个client发起的事务指令中的命令可以连续执行;
本文将以乐观锁的方式来实现秒杀抢购功能,只是以我自己的方式实现的一个小Demo,当然实际实现过程中不一定这样做,码代码也是有讲究,所谓一通百通,如果有不足的地方,还请见谅;
在秒杀抢购的事件中,具体用到了一下三点:
1、使用watch,采用乐观锁 2、不使用悲观锁,因为等待时间非常长,响应慢 3、不使用队列,因为并发量会让队列内存瞬间升高
话不多说,直接开始撸代码;
config:
@Bean public JedisPool redisPoolFactory( @Value("${spring.redis.host}") String redisHost, @Value("${spring.redis.port}") int redisPort, @Value("${spring.redis.password}") String redisPassword, @Value("${spring.redis.database}") int database , @Value("${spring.redis.jedis.pool.max-wait}") int maxWaitMillis, @Value("${spring.redis.jedis.pool.max-idle}") int maxIdle, @Value("${spring.redis.jedis.pool.max-active}") int maxActive ){ JedisPoolConfig jedisPoolConfig = new JedisPoolConfig(); jedisPoolConfig.setMaxIdle(maxIdle); jedisPoolConfig.setMaxWaitMillis(maxWaitMillis); jedisPoolConfig.setMaxTotal(maxActive); jedisPoolConfig.setMinIdle(0); jedisPoolConfig.setMaxIdle(maxIdle); JedisPool jedisPool = new JedisPool(jedisPoolConfig,redisHost,redisPort,0,redisPassword); return jedisPool; } @Bean public RedisWatchTest redisWatchTest(JedisPool jedisPool){ return new RedisWatchTest(jedisPool); }
新建类 RedisWatchTest,执行逻辑
package com.cookie.test; import com.cookie.util.UUIDUtils; import org.springframework.beans.factory.annotation.Autowired; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.Transaction; import java.util.List; /** * author : cxq * Date : 2019/8/22 */ public class RedisWatchTest implements Runnable { private JedisPool jedisPool ; public RedisWatchTest(JedisPool jedisPool) { this.jedisPool = jedisPool ; } String watchkeys = "watchkeys";// 监视keys @Override public void run() { Jedis jedis = null ; try { jedis = jedisPool.getResource(); jedis.watch(watchkeys);// watchkeys String val = jedis.get(watchkeys); int valint = Integer.valueOf(val); String userifo = UUIDUtils.getUUID(); if (valint < 20) { // 20为总的商品数量,操作这个数量即抢购失败 Transaction tx = jedis.multi();// 开启事务 tx.incr("watchkeys"); List<Object> list = tx.exec();// 提交事务,如果此时watchkeys被改动了,则返回null if (list != null) { System.out.println("用户:" + userifo + "抢购成功,当前抢购成功人数:" + (valint + 1)); /* 抢购成功业务逻辑 */ jedis.sadd("setsucc", userifo); } else { System.out.println("用户:" + userifo + "抢购失败"); /* 抢购失败业务逻辑 */ jedis.sadd("setfail", userifo); } } else { System.out.println("用户:" + userifo + "抢购失败"); jedis.sadd("setfail", userifo); // Thread.sleep(500); return; } } catch (Exception e) { e.printStackTrace(); } finally { jedis.close(); } } }
写一个调用方法:
@Autowired private RedisWatchTest redisWatchTest ; @Autowired private JedisPool jedisPool ; @GetMapping("redis") public TResult<Integer> testRedisWatch(){ String watchkeys = "watchkeys"; ExecutorService executor = Executors.newFixedThreadPool(20); Jedis jedis = jedisPool.getResource(); jedis.set(watchkeys, "0");// 重置watchkeys为0 jedis.del("setsucc", "setfail");// 清空抢成功的,与没有成功的 jedis.close(); for (int i = 0; i < 10000; i++) {// 测试一万人同时访问 executor.execute(redisWatchTest); } executor.shutdown(); return new TResult<>(1); }
控制台输出结果展示(由于10000次太大,这里用了20,分多次调用以便大家好看,展示结果为前两次调用和最后两次调用):
下面是redis控制台命令输出结果,也给大家截图展示下: