前言
在秒杀业务的设计中亦或是面试中,比较重要也比较容易被问道的点就是如何解决超售问题?
因为对于大部分的秒杀活动大多数都是陪本赚吆喝的买卖,所以对于超售现象也是绝对不允许的。下面来分析一下对于MysqL与Redis的解决方案。
MysqL
思考对于什么是数据库的超售呢? 就是当用户在进行查询时候发现库存还是有的,但是在下单时候可能会导致网络的延迟问题,另外一个用户提前完成了查询以及下单的功能,导致最后一件库存也卖完,导致先查询到的用户对于当前库存是并没有的,就会导致超售现象。对于这种情况,Mysql一般的理论上的方法有三种:
- 使用数据库的串行化执行,表示对于查找到库存,下单都是一个原子的操作,此时的数据库就是相当于一个单线程在运行,但是就会导致效率比较低下,若是只是在浏览商品时候,也是串行化的执行,会导致查询的效率过于低下,所以现在的业界也都没有这样设计。
- 采用加锁的方式,就是对于每一个访问的数据都加上一个锁,但是可能会导致死锁的情况出现,例如线程A访问数据X以后,将数据X进行加锁处理,此时需要去访问另外一个数据Y,但是同时线程B对数据Y进行加锁访问,同时需要去访问数据X,这时候,若是没有外力的干预,这两个线程都会处于一直等待的状态,就会出现死锁,也不推荐使用。
- 使用乐观锁机制,对于乐观锁这里一个比较通俗的理解就是加上版本号进行控制,线程对数据的访问时候也会读取到版本号,在进行更改时候同时也需要查看当前数据的版本号是否和自己刚开始对数据访问时候读取到的版本号一致,若是一致,进行更改,若是不一致,重新对数据进行访问,然后再度执行更改的功能,就可以避免对库存的超售。
Redis
对于redis的实现我们这里使用代码进行一个演示操作。
- 首先,简历maven工程,引入redis的jdeis依赖。
<dependencies>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.0.1</version>
</dependency>
</dependencies>
- 思考一下,我们模拟多线程的访问,可以是用到Java的线程池,构建一个线程池,对于线程池的参数有不了解的可以参看以下这篇文章线程池的理解与运用
- 完成以后,调用jedis,链接本地的reids,包括端口号,主机地址。
- 将一个线程任务添加到线程池里面。
public class Applicaton {
public static ThreadPoolExecutor pool=new ThreadPoolExecutor(
10,100,10, TimeUnit.SECONDS,
new LinkedBlockingDeque<>()
);
public static void main(String[] args) {
Jedis jedis=new Jedis("localhost",6379);
jedis.select(1);
jedis.set("kill_number","50");
jedis.del("kill_list");
jedis.close();
for (int i = 0; i <1000; i++) {
pool.execute(new KillTask());// 把一个线程的任务加入到线程池里面
}
}
}
- 然后我们实现继承Runable接口 实现run方法来实现多线程。
- 对于方法内部,同样是同redis服务器建立连接,选择第几个库,使用Watch() 方法来监视数据。
- 开启事务,对于redis事务中并不是属于一条命令就会执行一条命令,而是等到整个事务进行提交完成以后才会提交到服务端进行命令的执行,也就保证了原子性,同时不会出现超售的现象。
public class KillTask implements Runnable{
public void run(){
Jedis jedis =new Jedis("localhost",6379);
jedis.select(1);// 表示选择第几个数据库因为redis有16个库,这里选择第一个。
jedis.watch("kill_number","kill_list");
int num=Integer.parseInt(jedis.get("kill_number"));
if(num>0){
// 表示库存还有数量。
Transaction transaction=jedis.multi();// 开启事务
transaction.decr("kill_number");// 对数据进行减一操作。
transaction.rpush("kill_list","1001");
transaction.exec();// 提交事务
}
else {
Applicaton.pool.shutdown();// 关闭线程池
jedis.close();// 关闭jedis的链接
}
}
}