秒杀活动,到时间点后,用户会对商品进行购买。
综合上面问题,传统关系型数据库不能良好的支持。
这里是解决提交秒杀请求后,如何设计扣减库存的实现。
-- KEYS [good]
-- ARGV [uid]
-- return -1-库存不足 0-重复购买 1-成功
local good = KEYS[1]
local activity = ARGV[1]
local uid = ARGV[2]
local gooduids = good .. ':' .. activity .. ':uids'
local isin = redis.call('SISMEMBER', gooduids, uid)
if isin > 0 then
return 0
end
local goodstock = good .. ':' .. activity .. ':stock'
local stock = redis.call('GET', goodstock)
if not stock or tonumber(stock) <= 0 then
return -1
end
redis.call('DECR', goodstock)
redis.call('SADD', gooduids, uid)
return 1
使用Redis EVAL命令执行
EVAL script numkeys key [key …] arg [arg …]
如 eval “…” 2 goodid activityid uid
使用Jedis作为驱动
mport redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class JedisLua {
static final JedisPool pool;
static {
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(100);
pool = new JedisPool(config, "127.0.0.1");
Runtime.getRuntime().addShutdownHook(new Thread(()->pool.close()));
}
/**
* 重置商品的库存
* @param good
* @param activity
* @param stock
*/
public static final void reset(String good, String activity, int stock) {
Jedis jedis = pool.getResource();
try {
jedis.set(good + ":" + activity + ":stock", stock + "");
jedis.del(good + ":" + activity + ":uids");
} finally {
if (jedis != null) {
jedis.close();
}
}
}
public static final String RUSH_TO_BUY_LUA =
"local good = KEYS[1]\n" +
"local uid = ARGV[1]\n" +
"local activity = KEYS[2]\n" +
"local gooduids = good .. ':' .. activity .. ':uids'\n" +
"\n" +
"local isin = redis.call('SISMEMBER', gooduids, uid)\n" +
"\n" +
"if isin > 0 then\n" +
" return 0\n" +
"end\n" +
"\n" +
"local goodstock = good .. ':' .. activity .. ':stock'\n" +
"local stock = redis.call('GET', goodstock)\n" +
"\n" +
"if not stock or tonumber(stock) <= 0 then\n" +
" return -1\n" +
"end\n" +
"\n" +
"redis.call('DECR', goodstock)\n" +
"redis.call('SADD', gooduids, uid)\n" +
"return 1";
/**
* 加载lua脚本到redis中
* @return
*/
public static String rushToBuySHA1() {
Jedis jedis = pool.getResource();
try {
return jedis.scriptLoad(RUSH_TO_BUY_LUA);
} finally {
if (jedis != null) {
jedis.close();
}
}
}
/**
*
* @param good 商品编号
* @param activity 活动编号
* @param uid 用户id
* @param scriptsha1 redis lua 脚本sha1值
* @return
*/
public static int rushToBuy(String good, String activity, String uid, String scriptsha1) {
Jedis jedis = pool.getResource();
try {
if (scriptsha1 != null) {
return ((Long) jedis.evalsha(scriptsha1, Arrays.asList(good, activity), Arrays.asList(uid))).intValue();
} else {
return ((Long) jedis.eval(RUSH_TO_BUY_LUA, Arrays.asList(good, activity), Arrays.asList(uid))).intValue();
}
} finally {
if (jedis != null) {
jedis.close();
}
}
}
public static void main(String[] args) throws InterruptedException {
final String scriptsha1 = rushToBuySHA1();
final String goodid = "good0";
final String activityid = "active1";
reset(goodid, activityid, 10);
Map<String, Integer> map = new ConcurrentHashMap<>();
ExecutorService service = Executors.newCachedThreadPool();
List<Callable<Integer>> tasks = new ArrayList<>();
for (int i = 0; i < 50000; i++) {
final String uid = "uid" + i;
tasks.add(() -> {
int r = rushToBuy(goodid, activityid, "" + uid, scriptsha1);
if (r == 1) {
map.put(uid, r);
}
return r;
});
}
service.invokeAll(tasks);
System.out.println(map.size());
System.out.println(map);
service.shutdownNow();
}
}
运行main函数,控制台打印
10
{uid6=1, uid7=1, uid4=1, uid5=1, uid2=1, uid3=1, uid0=1, uid1=1, uid8=1, uid10=1}
商品10的库存,50000个请求并发,最后10个用户获取了库存。