秒杀系统
1.什么是秒杀系统?
秒杀系统是运营活动中的一个手段方式,其表现形式就是在一个特定的时间内开放一个参与活动的入口,参与用户多而真正获得活动的机会少。最具有代表性质的活动之一就是抢购小米手机,12306抢票。
2.秒杀系统的特点和遇到的问题
1.秒杀时有大量的用户在同一时间内进入服务,会有大量的请求到服务器。
带来的问题:会导致大量请求直接到服务器中,可由于只有少量的用户才能得到机会真正的走完后续的流程,所以需要限制大量的流量进入服务后端。 --限流
2.大量的刷新商品详情页面等, 请求一些热点的数据。
带来的问题:不断的请求会导致服务存储层压力过大。影响整个存储系统的稳定性。 --缓存,CND
3.请求的数量远远大于库存的数量
带来的问题:库存问题 ,库存必须精准,无误的返回给用户,不能出现超发和少发的情况。-异步处理,热点分片。
2.秒杀系统遇到了哪些问题和一些处理方式。
秒杀场景大部分都是读多写少的情况,不过都是高并发下处理。处理的情况有三大特征 "快准稳"
快 在短时间内必须给用户抢购结果的一个反馈
准 比如库存 就10个 不能多发也不能少发 ,最准确的结果反馈给用户
稳 在大流量请求服务中,高性能服务是必须的也是活动继续的前提
3.秒杀系统库存问题
其中最主要的是库存的处理
1.首先我们第一层直接使用mysql进行库存处理
a.设置total库存字段为unsigned
update product set total=total-1 where id=1 and total >0
如果多个用户更新同一条记录,最后提交会覆盖前面的提交结果
这样也会出现超发的情况
b.使用锁机制
用悲观锁 先锁住需要更新的商品信息
乐观锁
select version, totla from product where total >= num and id=1;
update product set total=total-num, version=version+1 where id=1 and version=version
悲观锁
select * from product where id=1 for update;
使用mysql 去处理库存, 用悲观锁 可以对单个商品的库存被锁定了,不过大量的请求去扣库存的时候被一个请求锁定了其他请求都处于等待的情况,这个时候 mysql的压力非常大,最终数据库的响应变慢 ,乐观锁也会出现这种情况 ,这时候就出现了雪崩效应。什么是雪崩效应,在我们计算机应用系统里面因为某一台计算机产生错误异常导致应用系统服务异常,因而可能会让应用系统中的所有计算机崩溃,这种就是雪崩效应,非常恐怖,让应用系统彻底无法使用
当数量为0时 就不需要处理用户的请求 直接抛商品已秒杀完
2.第二层 使用缓存处理
a.使用redis队列
规定一个的队列长度,比如长度为10,超过这个长度不参与活动没直接返回活动结束
使用先到先得的方法 比如库存只有10个 只允许前100个请求进入队列里面 后面的请求都返回秒杀失败,常驻进程异步处理队列里面前100个请求 里面的扣减对应的库存 ,库存不足返回秒杀失败
问题:整个请求的处理涉及很多服务调用也涉及很多其他的系统,也会有部先到的请求由于后面的一些排队的服务拖累,导致请求处理完成的时间反而比较后面的请求的情况。
而且是先到先得方法,还没有运气的成分
b.把商品信息同步到缓存中,每个商品的已抢购商品数量用缓存计数器单独计算起来
if ($redis_client->decrby($key, $parmas['num']) > -1) {
//减库
//处理 数据库减库存 可以同步 或者 异步处理数据库存
} else {
//购买多个时,如库存不足,需要把数量加回去,否则会出现库减库存,商品并没有卖出去
$redis_client->incrby($key, $parmas['num']);
return false;
}
4.秒杀系统其他问题
首选 如何限流 一般有三种方法,令桶法,漏桶算法,分布式锁
令牌桶
算法描述
假如用户请求的平均发送速率为r,则每隔1/r秒一个令牌被加入到桶中
假设桶中最多可以存放b个令牌。如果令牌到达时令牌桶已经满了,那么这个令牌会被丢弃
当流量以速率v进入,从桶中以速率v取令牌,拿到令牌的流量通过,拿不到令牌流量不通过,执行熔断逻辑
漏桶算法
漏桶算法思路很简单,水(请求)先进入到漏桶里,漏桶以一定的速度出水,当水流入速度过大会直接溢出,可以看出漏桶算法能强行限制数据的传输速率。
分布式锁
https://img-blog.csdn.net/20180219144927282
限流
$second = time();
$flowKey = "flowlmt_${key}_${second}";
$curQps = $redis->incr($flowKey)
// 如果redis访问异常,需要开放限制,防止阻塞后续业务
if (false === $curQps) {
$curQps = 0;
}
// 必须设置超时时间
if ($curQps < 3) {
$redis->expire($flowKey, 3);
}
防刷控制
$key = 'throt_' . $key;
$ts = $redis->get($key);
if (false === $ts) {
return true;
}
$now = now() 毫秒
if ($ts && $now - $ts < $ttl * 1000) {
return false;
}
$redis->setex($key, $now, $ttl + 2);
return true;
分布式锁 用于用户瞬间多次点击抢购或者保证用户信息被一个请求改变使用
public function lock($key, $ttl = 1) {
$result = $this->setnx($key, time() + $ttl);
if ($result == 1) {
return true;
}
$oldExpire = $this->get($key);
if (false === $oldExpire || (int)$oldExpire > time()) {
return false;
}
$currentExpire = $this->getset($key, time() + $ttl);
if (false === $currentExpire || $oldExpire != $currentExpire) {
return false;
}
return true;
}
有效时间为什么都设置了>1s ,因为怕在请求过程中 请求的处理大于1秒 ,其他请求就可以获取到该锁并改变前一个请求锁住得部分。这样就确保了只有一个请求获取了该锁。