后台秒杀架构设计与实现(一)
本文只讲处理秒杀请求、减库存操作,前端的CDN加速,防作弊,防刷不在此列;本文利用redis watch实现乐观锁来处理减库存请求。
本文适用于用户量大,商品库存量少场景,若是库存量大场景,适合队列异步实现。
- 流程图
测试入口类
package com.liushao.redislockframework;
import java.util.Date;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import redis.clients.jedis.Jedis;
/**
* 测试抢购
*/
public class SecKillTest {
public static void main(String[] args) {
final String watchkey = "watchkey";
ExecutorService executor = Executors.newFixedThreadPool(200);
Jedis jedis = RedisUtils.getJedis();
jedis.set(watchkey, "0");// 重置watchkey为0
jedis.del("setsucc", "setfail");// 清空抢成功、失败两个set
RedisUtils.returnResource(jedis);
long starttime = new Date().getTime();
//模拟100万人抢10个商品
for (int i = 0; i < 1000000; i++) {
executor.execute(new SecKillThread(UUID.randomUUID().toString(), starttime));
}
executor.shutdown();
}
}
redis连接池
package com.liushao.redislockframework;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
public class RedisUtils {
private RedisUtils(){
}
private static JedisPool jedisPool = null;
//获取链接
public static synchronized Jedis getJedis(){
if(jedisPool==null){
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
//指定连接池中最大空闲连接数
jedisPoolConfig.setMaxIdle(10);
//链接池中创建的最大连接数
jedisPoolConfig.setMaxTotal(100);
//设置创建链接的超时时间
jedisPoolConfig.setMaxWaitMillis(2000);
//表示连接池在创建链接的时候会先测试一下链接是否可用,这样可以保证连接池中的链接都可用的。
jedisPoolConfig.setTestOnBorrow(true);
jedisPool = new JedisPool(jedisPoolConfig, "127.0.0.1", 6379);
}
return jedisPool.getResource();
}
//返回链接
public static void returnResource(Jedis jedis){
jedisPool.returnResourceObject(jedis);
}
}
具体实现类
package com.liushao.redislockframework;
import java.util.Date;
import java.util.List;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;
public class SecKillThread implements Runnable {
final String watchkey = "watchkey";// 监视keys
final int sku_num = 10; //总库存
private long starttime; //秒杀开始时间
private String userid; //用户id
private static boolean flag = true; //秒杀结束标识
public SecKillThread(String userid, long starttime) {
this.userid = userid;
this.starttime = starttime;
}
public void run() {
if (flag) {
Jedis jedis = RedisUtils.getJedis();
try {
jedis.watch(watchkey);// watchkeys
int succ_count = Integer.valueOf(jedis.get(watchkey));
if (succ_count < sku_num) {
Transaction tx = jedis.multi();// 开启事务
tx.incr(watchkey);
List<Object> list = tx.exec();// 提交事务,如果此时watchkey被改动了,则返回null
if (list != null) {
System.out.println("用户:" + userid + "抢购成功,当前抢购成功人数:"
+ (succ_count + 1));
//抢购成功业务逻辑
jedis.sadd("setsucc", userid);
//可直接入库持久化
//。。。。
} else {
System.out.println("用户:" + userid + "抢购失败");
/* 抢购失败业务逻辑 */
jedis.sadd("setfail", userid);
}
} else {
//抢购结束,拒绝后续申请
flag = false;
//System.out.println("抢购结束");
jedis.sadd("setfail", userid);
return;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
RedisUtils.returnResource(jedis);
System.out.println("总耗时:" + (new Date().getTime() - starttime));
}
}
}
}