一,细节
二,需要注意的细节
1.库存超卖问题
- 使用mysql数据库的 悲观锁 机制。在事务中使用 for update 语句,此时数据库会加锁,其他想要当前读的线程都会被阻塞,在事务处理完成之后释放这一条数据。该方法的缺点在于将查询和更新串行化,保证某一时刻只能有一个线程更新数据库。当突然有大量请求后,由于请求串行化,所以大多数线程会因为等待而超时。
public function mysqlLock(){
$goods_id = 26545;
$sku_id = 26545;
$price = 300;
$user = '';
StoreOrderModel::startTrans();
$nums = StoreOrderModel::where(['id'=>1])->field('number')->lock(true)->find();
$nums = $nums['number'];
if($nums > 0){
$item['goods_id'] = $goods_id;
$item['sku_id'] = $sku_id;
$item['number'] = $nums;
$item['price'] = $price;
$item['user'] = $user;
$id = StoreModel::insertGetId($item);
if($id){
StoreOrderModel::where(['id'=>1])->setDec('number');
StoreOrderModel::commit();
}else{
StoreOrderModel::rollback();
}
}else{
echo "没有库存了";
}
}
- 使用 乐观锁 来实现
乐观锁一般通过版本号来实现,数据库设计时为每一条记录加一个版本号,通过比较版本号来判断是否有人更新过数据。
update good set count = count - 1 where id = 1 and version = old_version
这个办法响应速度快,线程第一步查询时不需要阻塞等待,减少了响应时间。缺点是当同时有大量请求进来时,由于竞争激烈,绝大多数线程都不会更新成功,最终的结果就是虽然用户响应很快,但失败次数多,秒杀后还有很多库存。 最重要的是还需要添加一个version字段,所以一般不使用。 - 使用mysql 自身锁 来实现
利用MySQL自身锁来解决问题,即每次更新前判断扣减库存后是否大于零,大于零时才会进行更新。
update good set count = count - 1 where id = 1 and count - 1 > 0
此方法的优点是只需要一次查询,且性能较好。缺点是由于使用了>,所以不会走索引,数据量大时性能不高。 - 使用 redis队列 来实现
public function eq_start(){
$redis = ResRedisModel::getinstance();
$nums = $redis->lSize('store');
$goods_id = 26545;
$sku_id = 26545;
$number = 1;
$price = 300;
$user = '';
if($nums > 0){
$user = $redis->rPop('store');
if($user){
$item['goods_id'] = $goods_id;
$item['sku_id'] = $sku_id;
$item['number'] = $number;
$item['price'] = $price;
$item['user'] = $user;
StoreModel::insertGetId($item);
echo '抢购成功!';
}else{
echo '抢购失败!';
}
}else{
echo '抢购失败!';
}
}
2.限流
- nginx配置限流
令牌桶算法,修改nginx配置文件
http {
# 根据ip限制速率,zone=名称:(桶)大小,放不下会丢弃请求,rate=同IP 5次/s ,平均200ms/次
limit_req_zone $binary_remote_addr zone=ratelimit:30m rate=5r/s;
}
- nginx 负载均衡配置,分流到几个服务器
- 在代码层面,如框架的中间件中做接口请求限流
3.削峰
-
随机拒绝
假设有100万人来抢购100个商品,既然大部分人都不可能抢到,那我们完全可以在用户提交下单请求时生成一个 0 ~ 100 的随机数,如果这个数大等于 2 则直接告诉用户抢购的人太多请重试,这样能落到 Redis 的请求就只剩下了 2% 也就是 2 万人。在代码层面可以使用路由中间件来实现。 -
异步处理
先判断是否有库存和是否有抢购权限,如果有,立刻返回抢购成功,完成抢购。完成抢购后生成订单等耗时的操作使用rabbitmq等消息中间键做异步队列来执行。