1 悲观锁解决方案
悲观锁,也就是在修改数据的时候,采用锁定状态,排斥外部请求的修改。遇到加锁的状态,就必须等待。可以采用redis队列+mysql事务控制的方案,下面是流程图:
mysql的执行代码:
beginTranse(开启事务)
try{
//quantity为请求减掉的库存数量
$dbca->query('update s_store set amount = amount - quantity where postID = 12345');
$result = $dbca->query('select amount from s_store where postID = 12345');
if(result->amount < 0){
throw new Exception('库存不足');
}
}catch($e Exception){
rollBack(回滚)
}
commit(提交事务)
先执行update锁住本条记录,这样就能保证其他线程执行不了更新操作,可以避免超扣现象。
上述的方案的确解决了线程安全的问题,但是,别忘记,我们的场景是“高并发”。也就是说,会很多这样的修改请求,每个请求都需要等待“锁”,某些线程可能永远都没有机会抢到这个“锁”,这种请求就会死在那里。针对这个问题我们稍微修改一下上面的场景,我们直接将请求放入队列中的,采用FIFO(First Input First Output,先进先出),这样的话,我们就不会导致某些请求永远获取不到锁。下面是整个执行流程图:
2 乐观锁解决方案
悲观锁的解决方案解决了锁的问题,全部请求采用“先进先出”的队列方式来处理。那么新的问题来了,高并发的场景下,因为请求很多,系统处理队列内请求的速度根本无法和疯狂涌入队列中的数目相比,很可能一瞬间将队列内存“撑爆”,最终Web系统平均响应时候还是会大幅下降,系统还是陷入异常。
这个时候,我们就可以讨论一下“乐观锁”的思路了。乐观锁,是相对于“悲观锁”采用更为宽松的加锁机制,大都是采用带版本号(Version)更新。实现就是,这个数据所有请求都有资格去修改,但会获得一个该数据的版本号,只有版本号符合的才能更新成功,其他的返回抢购失败。这样的话,我们就不需要考虑队列的问题,不过,它会增大CPU的计算开销。但是,综合来说,这是一个比较好的解决方案。
乐观锁的执行流程如下:
有很多软件和服务都“乐观锁”功能的支持,例如Redis中的watch就是其中之一。通过这个实现,我们保证了数据的安全。
下面是利用Redis中的watch实现乐观锁的代码
while (true) {
System.out.println(Thread.currentThread().getName());
jedis = RedisUtil.getJedis();
try {
jedis.watch("mykey");
int stock = Integer.parseInt(jedis.get("mykey"));
if (stock > 0) {
Transaction transaction = jedis.multi();
transaction.set("mykey", String.valueOf(stock - 1));
List
参考资料:
http://www.cnblogs.com/php5/p/4362244.html
http://www.kuqin.com/shuoit/20141203/343669.html
http://www.cnblogs.com/shihaiming/p/6062663.html