我们都知道,秒杀案例是电商项目最不可少的功能之一,在秒杀案例里会出现一系列的问题,比如超卖问题(不加事务的秒杀),库存遗留(施加乐观锁之后)问题。
首先讲解下悲观锁和乐观锁:悲观锁是每一次只能有一个命令去操作这个数据或者是资源,其他想要操作这个资源的命令无法操作,因为资源被上了悲观锁,只有第一个事务提交结束后悲观锁被打开,下一个事务才能继续操作这个资源。注意悲观锁甚至不允许读操作!!!而与悲观锁不同的乐观锁则是每一个事务都能够操作这个资源,但是!!!同一时间能改变资源的只有一个事务,所以乐观锁机制会给资源上一个操作标志,每一个时刻事务操作的资源标志如果等于当前资源的操作标志,就可以对它进行操作,如果不同就只能重新获取当前操作标志的资源进行操作。这就保证了可以对当前数据资源进行读取,但是数据如果在多用户的情况下实时访问的场景的话,系统实时更新数据资源的能力直接决定其他用户读取资源的正确性。
另外watch命令是监视key,监视的key相当于在这部分数据上加上了乐观锁,行锁。
测试步骤:
1、设计JedisPoolUntil类与getUid类
2、设置service层(在seckillServiceImpl类里书写秒杀具体逻辑代码)
3、设计servlet层实现web响应
1、设计JedisPoolUntil类与getUid类
JedisPoolUntil类,采取池子可以有效的处理一定请求量带来的等待超时问题的出现!
public class JedisPoolUntil {
private static JedisPool jedisPool = null;
public static JedisPool getJedisPool() {
if (jedisPool == null) {
synchronized (JedisPoolUntil.class) {
if (jedisPool == null) {
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxTotal(200);
jedisPoolConfig.setMaxIdle(32);
jedisPoolConfig.setBlockWhenExhausted(true);
jedisPoolConfig.setTestOnBorrow(false);
jedisPool = new JedisPool(jedisPoolConfig,"192.168.20.150",6379);
}
}
}
return jedisPool;
}
public void release(Jedis jedis,JedisPool jedisPool){
if (jedisPool != null){
jedis.close();
}
}
}
getUid类,随机生成user的id模拟秒杀用户多的情况
/*得到随机的用户id*/
public class getUid {
public static String Uid(){
String uid = "user:";
Random random = new Random();
for (int i=0; i<=3;i++){
uid += random.nextInt(1,9);
}
return uid;
}
}
2、设置service层
SecKillServiceImpl类实现具体的业务逻辑操作
public class seckillServiceImpl implements seckillService{
/*池化解决超时问题*/
JedisPool jedisPool = JedisPoolUntil.getJedisPool();
@Override
public Boolean seckill() {
Jedis resource = jedisPool.getResource();
resource.auth("hlc");
// 1 判断库存容量是否为 0
if (resource.get("kcKey") == null ){
System.out.println("秒杀活动结束!");
return false;
}
// 2 给库存乐观锁,防止并发情况导致数据异常
resource.watch("kcKey");
// 3 启动事务
Transaction multi = resource.multi();
try {
multi.decr("kcKey");
multi.sadd("user",getUid.Uid());
multi.exec();
System.out.println("秒杀成功!");
return true;
}catch (Exception e){
e.printStackTrace();
multi.discard();
return false;
}
}
}
3、设计servlet层实现web响应
在web.xml文件别忘记注册这个servlet类,当然如果采用springboot来测试,对于web层开发与redis操作来说会更简单。不过javaweb确实也挺好的,除了代码量稍微大一点,业务逻辑都需要自行配置环境之外也没啥缺点(好像确实javaweb又重又不好用!!!)
public class seckillServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request,response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
seckillService seckillService = new seckillServiceImpl();
Boolean seckill = seckillService.seckill();
if (seckill){
request.setAttribute("message","你已秒杀成功!");
request.getRequestDispatcher("success.jsp").forward(request,response);
}else {
request.setAttribute("message","秒杀失败!");
request.getRequestDispatcher("error.jsp").forward(request,response);
}
}
}
测试结果:
当然我这个是单机操作,没法模拟并发,我们可以通过postman测试,也可以在Linux虚拟机上利用ab命令测试,如果ab命令用不了,就要下载一下这个工具了。