令牌桶算法 java实现-java实现银行家算法
发布时间:2023-03-24 11:11 浏览次数:次 作者:佚名
学习博客:如何设计一个秒杀系统(二)
用于接口限流。
通俗点来讲就是说,在调用实际业务之前,就对过来的线程请求数量做限制,防止大量的请求调用数据库产生极大的压力,也防止无用的请求对业务的调用,减轻服务器的压力令牌桶算法 java实现,把请求限制在最开始处。
如何解决接口限流
令牌桶算法和漏斗算法
漏斗算法
把所有发过来的请求放到一个漏斗里,然后漏斗一直以一个固定的速度分发请求,让请求处理业务,漏斗容量一定,等到漏斗中的请求数量超过漏斗容量,多余请求溢出令牌桶算法 java实现,也就是被抛弃。
问题
简单粗暴。如果此时商家可供买的商品有1000个,根据服务器性能等,设置漏斗的容量为500个,但是一下子来了
5000个请求,那么超过漏斗容量的4500个请求都要被丢弃,商家的商品也没有卖出去。
令牌桶算法----------因更加灵活而被广泛使用
桶按着一定时间生成一定数量的令牌,所有的请求要进行秒杀的业务处理时,都先经过令牌桶,去令牌桶里拿令牌,如果拿到令牌了,那么就执行秒杀业务,如果拿不到令牌,两种策略,一种是线程被阻塞,一直向令牌桶请求拿令牌,直到拿到为止去执行秒杀业务,一种是设置超时时间,在规定的超时时间内,如果请求拿不到令牌,那么请求被抛弃。
使用令牌桶算法解决秒杀系统中的接口限流
导入依赖
com.google.guava
guava
28.2-jre
controller层设置令牌桶算法限流策略【使用的第二个策略】
@RestController
@Slf4j//lombok提供的日志
public class StockOrderController {
@Autowired
private StockOrderService stockOrderService;
//创建令牌桶实例[每秒在令牌桶中放行10个请求]
private RateLimiter rateLimiter = RateLimiter.create(10);
/**
* 令牌桶+乐观锁解决超卖
* @param id
* @return
*/
@GetMapping("killToken")
public String killToken(@RequestParam("id") Integer id){
System.out.println("秒杀"+ id);
//令牌桶算法,加入接口限流措施,在5s内拿到令牌可以进行秒杀,
// 否则不能【意味着商品可能不会被全部卖出,因为如果业务处理时间过长,请
// 求在5s之后也不会被拿到,则会把请求抛弃,但是正常,后续实际业务中,用户可能拿到有问题商品,进行更换】
boolean tryAcquire = rateLimiter.tryAcquire(5, TimeUnit.SECONDS);
if(!tryAcquire){
System.out.println("当前请求被限流,直接抛弃,无法调用后续秒杀逻辑.....");
return "抢购失败";
}
//根据秒杀商品id,调用秒杀业务
try {
int orderId = stockOrderService.kill(id);
return "秒杀成功订单id为" + String.valueOf(orderId);
} catch (Exception e) {
e.printStackTrace();
return e.getMessage();
}
}
service.java
@Service
@Transactional
public class StockOrderServiceImpl implements StockOrderService {
@Resource
private StockDao stockDao;
@Resource
private StockOrderDao stockOrderDao;
@Override
public int kill(Integer id) {
Stock stock = checkStock(id);
updateSale(stock);
int orderId = createOrder(stock);
return orderId;
}
//校验库存
public Stock checkStock(Integer id) {
//根据商品id校验库存
Stock stockEntity = stockDao.checkStock(id);
if (stockEntity.getCount() == stockEntity.getSale()) {
//不能再秒杀
throw new RuntimeException("抢光了");
}
return stockEntity;
}
//扣除库存,
public void updateSale(Stock stockEntity) {
//继续秒杀,扣除库存
//在sql层面完成销量的+1和版本号的+1,并且根据商品id和版本号同时查询更新的商品
// stockEntity.setSale(stockEntity.getSale());
int reslut = stockDao.updateSale(stockEntity);
if(reslut == 0){
throw new RuntimeException("抢购失败,请重试");
}
}
//创建订单
public int createOrder(Stock stockEntity) {
//创建订单
StockOrder stockOrder = new StockOrder();
stockOrder.setSID(stockEntity.getId())
.setCreateTime(new Date())
.setName(stockEntity.getName());
//mybatis可以自动把生成的主键放到此对象里
stockOrderDao.createOrder(stockOrder);
//得到数据库主键生成的Id
return stockOrder.getId();
}
}
更改库存的mapper.xml
update stock
sale = #{sale} + 1,
version = #{version} + 1,
id = #{id}
and version = #{version}
结果
使用jmeter测试工具,进行测试,2000个请求,商品数量是100个,结果商品数量秒杀成功的有52个,其他的用户请求都被抛弃,正常。
如果要增大卖出的商品数量,那么就把令牌桶最开始的放行线程数量设置大一些,或者把超时时间设置长一些。