在实际生活中,秒杀功能是比较常见的,如12306抢票、电商系统的秒杀活动等。所谓秒杀,从应用业务角度来看,是指在短时间内多个用户“争抢”某个资源,这里的资源在大部分秒杀场景里是商品;从技术角度来看,就是多个线程对资源进行操作。所以,要实现秒杀功能,就必须控制线程对资源的争夺,既要保证高效、并发,又要保证操作的正确性,符合实际业务需要。
对于秒杀的优化思路有:
- 写入内存。
- 实现多线程异步处理。
- 实现分布式处理。
【实例】使用多线程的方式实现1000人秒杀100部手机的实例。
(1)在pom.xml配置信息文件中,添加Redis启动器、Jedis客户端的相关依赖:
<!-- Redis启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.4.0</version>
</dependency>
<!-- Jedis客户端依赖 -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.3.0</version>
</dependency>
(2)秒杀实现(SecondKill.java):创建多线程,并利用 Redis 的事务功能,实现秒杀功能。
package com.pjb.seckill;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;
import java.util.List;
/**
* 秒杀抢购
* @author pan_junbiao
**/
public class SecondKill implements Runnable
{
String key = "iphone";
Jedis jedis = new Jedis("127.0.0.1",6379);
String userInfo;
public SecondKill(String userInfo)
{
this.userInfo = userInfo;
}
@Override
public void run()
{
try
{
jedis.watch(key); //watchkeys
String val = jedis.get(key);
int valint = Integer.valueOf(val);
if(valint<=100 && valint>=1)
{
//1、使用MULTI命令开启事务
Transaction tx = jedis.multi();
//2、事务命令入队
tx.incrBy(key,-1);
//3、使用EXEC命令执行事务
//提交事务。如果此时watchkeys被改动了,则返回null
List<Object> list = tx.exec();
if(list==null || list.size()==0)
{
String failuserinfo = "fail_" + userInfo;
String failinfo = "用户:" + failuserinfo + "商品争抢失败,抢购失败";
System.out.println(failinfo);
//抢购失败业务逻辑
jedis.setnx(failuserinfo,failinfo);
}
else
{
for(Object succ : list)
{
String succuserinfo = "succ_" + succ.toString() + "_" + userInfo;
String succinfo = "用户:" + succuserinfo + " 抢购成功,当前抢购成功人数:" + (1 -(valint -100));
System.out.println(succinfo);
//抢购成功业务逻辑
jedis.setnx(succuserinfo,succinfo);
}
}
}
else
{
String failuserinfo = "kcfail_" + userInfo;
String failinfo1 = "用户:" + failuserinfo + " 商品被抢购完毕,抢购失败";
System.out.println(failinfo1);
jedis.setnx(failuserinfo,failinfo1);
return;
}
}
catch(Exception ex)
{
ex.printStackTrace();
}
finally
{
jedis.close();
}
}
}
(3)主程序(SecondKillTest.java):秒杀功能的入口程序,用于创建线程池,同时生成用户ID,调用秒杀功能代码,完成秒杀任务。
package com.pjb.seckill;
import com.pjb.util.RedisHelper;
import redis.clients.jedis.Jedis;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Redis秒杀功能的实现,1000人抢购100部手机
* @author pan_junbiao
**/
public class SecondKillTest
{
public static void main(String[] args)
{
final String key = "iphone";
//20个线程池并发数
ExecutorService executor = Executors.newFixedThreadPool(20);
final Jedis jedis = new Jedis("127.0.0.1",6379);
jedis.del(key); //先删除
jedis.set(key,"100"); //设置起始的抢购数
jedis.close();
for(int i=0; i<1000; i++)
{
String userInfo = "pan_junbiao的博客" + i;
executor.execute(new SecondKill(userInfo));
}
executor.shutdown();
}
}
执行结果: