秒杀系统技术点(GLODON TOT20)

超卖问题

秒杀的特点就是,短时间内系统的访问量骤增,系统请求的并发量突然暴增的情况下,这时候我们直接操作数据库不做任何处理的情况下,容易出现库存已经卖空后,继续扣除库存的情况,即总共有100件库存,总共卖出了110件商品,这个就与事实不符。

那么这个问题有什么解决方案吗,当然有,今天就来讲讲其中的一种解决方式——乐观锁

当然,与之对应的就有悲观锁,Java中对应的就是使用synchronized关键字,悲观锁就是每次有一个线程进来操作,我就将它锁住,其它线程只能等待,只有当前线程处理完释放锁之后,其他线程才能拿到锁进行处理,这样,等库存为0之后,只有一个线程进来,发现卖完了就不进行处理,这也就防止了超卖问题的产生。

很明显,悲观锁有个很明显的缺点,就是每次只能有一个线程拿到锁进行处理,那么其他没拿到锁的线程等待时间较长,系统响应较慢。

乐观锁是什么呢?

在设计数据库时,在表里面多加一个version字段(数字Integer),用来标记当前版本号,当每一次对某一条数据进行修改时,版本号进行+1,当修改数据时,版本号与当前库里的版本号不一致则不能修改。

id

goods_name

stock

version

1

可口可乐

99

1

2

汇源果汁

87

1

每次获取一条记录时:

select id, goods_name, stock, version from goods

映射为一个Java对象Goods的goods:

扣除库存的时候,顺便更新版本号:

goods.setId(goods.getId());
goods.setGoodsName(goods.getGoodsName());
goods.setStock(goods.getStock() - 1);
goods.setVersion(goods.getVersion() + 1);

goodsDao.updateById(goods);

条件加上version,当版本号不一致,则不能修改

update .... where id = #{id} and version = #{version}

这样,当同时有多个线程进来,获取到数据,每次只能有一个线程修改数据成功,即抢购成功,其他线程修改失败,则系统提示抢购失败。

高并发限流

当突然有成千上万个请求进入系统的话,或者说,有恶意攻击者使用工具发送恶意请求,服务器的压力是很大的,这时候,我们就必须考虑到限流

限流的话,有漏斗算法令牌桶算法

漏斗算法,很形象,每次来的请求我先放在我漏斗上面的小空间内,从漏斗下面流出(通过)的速率的一定的,当漏斗上面的小空间存满的话,其他进来的请求就直接抛弃

掠夺者擎neobios设置 掠夺者bios怎么进入_掠夺者擎neobios设置

令牌桶算法,也很形象,系统以恒定速度生成一定数量的令牌,这些令牌放在一个令牌桶里,当有线程进来时,先从桶里拿令牌,拿到了令牌就进行处理,没拿到令牌就直接抛弃请求(抢购失败)

掠夺者擎neobios设置 掠夺者bios怎么进入_秒杀系统_02

springboot令牌桶的具体实现如下:

先导入pom依赖:

<dependency>
	<groupId>com.google.guava</groupId>
	<artifactId>guava</artifactId>
	<version>28.2-jre</version>
</dependency>

接着创建令牌桶:

//创建令牌桶,用于对秒杀接口进行限流
private RateLimiter rateLimiter = RateLimiter.create(30);

当请求进来时,先从桶里拿令牌,拿到令牌就处理,拿不到令牌就抛弃:

//如果3秒之内没拿到令牌,则秒杀失败
if(!rateLimiter.tryAcquire(3, TimeUnit.SECONDS)){
	log.info("秒杀失败,当前活动过于火爆,请重试");
	return new ResponseModel(1, "秒杀失败,当前活动过于火爆,请重试", null);
}
//拿到令牌则接着处理秒杀业务
return productService.secKillProduct(secKillProductBO);

redis实现倒计时

实现倒计时功能主要是根据redis中可以设置一对key-value的生存时间

setex pro1 100 hello

上面这个命令就是设置pro1:hello这个键值对的生存时间为100秒,100秒之后redis会自动删除这条数据

那么,我们可以提前将需要秒杀的商品根据id存入redis中,key可以使用指定字符串加id,如pro1、pro2等,value就随便存一个值,这里主要是根据key来判断redis中是否存在此商品信息,如下:

setex pro1 1800 product

我将id为1的商品存入redis,并设置过期时间为1800秒,即半小时

当用户秒杀时,将商品id传入系统,后天做一个简单的字符串拼接

String key = "pro" + id;

然后根据这个key去redis中查询此key是否存在,如果存在,则进行正常的秒杀流程操作数据库,如果不存在,则代表商品未上架或者秒杀已结束,直接抛弃请求。