实战高并发秒杀实现(3):基于Token令牌桶+MQ实现修改库存

一、理论基础

1.1、前端优化

  1. 使用动静分离、将静态资源存放到第三方文件服务器中实现cdn加速,目的减轻秒杀抢购带宽
  2. 当用户点击秒杀按钮的时候,应该将按钮disabled  防止重复提交
  3. 使用复杂的图形验证码防止机器模拟
  4. 秒杀详情页面,使用定时器根据用户信息查询秒杀结果
  5. 商品的详情页面使用nginx+lua+openresty 实现静态化页面

1.2、 网关

  1. ratelimter、nginx、hystrix、redis实现限流 令牌痛+漏铜算法  对用户秒杀请求实现限流服务保护
  2. 用户黑名单和白名单拦截

1.3、 秒杀接口

  1. 服务降级级、隔离、熔断
  2. 从redis中获取秒杀的令牌(能够获取到令牌就能够秒杀成功,否则就秒杀失败!)
  3. 异步使用MQ执行修改库存操作
  4. 提供一个根据用户信息查询秒杀结果接口

1.4、 项目部署

  1. Nginx+lvs 实现服务高可用和集群
  2. 分时段抢购(12306在用,中午 下午

1.5、同时有10万个请求实现秒杀、商品库存只有100个,要实现只需要修改库存100

方案实现流程:

提前对应的商品库存生成好对应令牌(100个令牌)——>也就是令牌桶,在10万个请求中,只要谁能够获取到令牌谁就能够秒杀成功, 获取到秒杀令牌后,在使用mq异步实现修改减去库

 

二、代码实现

原理图:

注意:公司级秒杀服务中,秒杀生产者和消费者要分别放在不同的服务,避免生产者挂了消费者也连着挂了

实战高并发秒杀实现(3):基于Token令牌桶+MQ实现修改库存_第1张图片

2.1、生产者

(1)MQ相关配置:RabbitmqConfig

@Component
public class RabbitmqConfig {

	// 添加修改库存队列
	public static final String MODIFY_INVENTORY_QUEUE = "modify_inventory_queue";
	// 交换机名称
	private static final String MODIFY_EXCHANGE_NAME = "modify_exchange_name";

	// 1.添加交换机队列
	@Bean
	public Queue directModifyInventoryQueue() {
		return new Queue(MODIFY_INVENTORY_QUEUE);
	}

	// 2.定义交换机
	@Bean
	DirectExchange directModifyExchange() {
		return new DirectExchange(MODIFY_EXCHANGE_NAME);
	}

	// 3.修改库存队列绑定交换机
	@Bean
	Binding bindingExchangeintegralDicQueue() {
		return BindingBuilder.bind(directModifyInventoryQueue()).to(directModifyExchange()).with("modifyRoutingKey");
	}

}

(2)生产者发送消息:SpikeCommodityProducer

@Component
@Slf4j
public class SpikeCommodityProducer implements RabbitTemplate.ConfirmCallback {

	@Autowired
	private RabbitTemplate rabbitTemplate;

	@Transactional
	public void send(JSONObject jsonObject) {

		String jsonString = jsonObject.toJSONString();
		System.out.println("jsonString:" + jsonString);
		String messAgeId = UUID.randomUUID().toString().replace("-", "");
		// 封装消息
		Message message = MessageBuilder.withBody(jsonString.getBytes())
				.setContentType(MessageProperties.CONTENT_TYPE_JSON).setContentEncoding("utf-8").setMessageId(messAgeId)
				.build();
		// 构建回调返回的数据(消息id)
		this.rabbitTemplate.setMandatory(true);
		this.rabbitTemplate.setConfirmCallback(this);
		CorrelationData correlationData = new CorrelationData(jsonString);
		rabbitTemplate.convertAndSend("modify_exchange_name", "modifyRoutingKey", message, correlationData);

	}

	// 生产消息确认机制 生产者往服务器端发送消息的时候,采用应答机制
	@Override
	public void confirm(CorrelationData correlationData, boolean ack, String cause) {
		String jsonString = correlationData.getId();
		System.out.println("消息id:" + correlationData.getId());
		if (ack) {
			log.info(">>>使用MQ消息确认机制确保消息一定要投递到MQ中成功");
			return;
		}
		JSONObject jsonObject = JSONObject.parseObject(jsonString);
		// 生产者消息投递失败的话,采用递归重试机制
		send(jsonObject);
		log.info(">>>使用MQ消息确认机制投递到MQ中失败");
	}
}

 

2.2、消费者

StockConsumer

@Component
@Slf4j
public class StockConsumer {
	@Autowired
	private SeckillMapper seckillMapper;
	@Autowired
	private OrderMapper orderMapper;

	@RabbitListener(queues = "modify_inventory_queue")
	@Transactional
	public void process(Message message, @Headers Map headers, Channel channel) throws IOException {
		String messageId = message.getMessageProperties().getMessageId();
		String msg = new String(message.getBody(), "UTF-8");
		log.info(">>>messageId:{},msg:{}", messageId, msg);
		JSONObject jsonObject = JSONObject.parseObject(msg);
		// 1.获取秒杀id
		Long seckillId = jsonObject.getLong("seckillId");
		SeckillEntity seckillEntity = seckillMapper.findBySeckillId(seckillId);
		if (seckillEntity == null) {
			log.warn("seckillId:{},商品信息不存在!", seckillId);
			return;
		}
		Long version = seckillEntity.getVersion();
		int inventoryDeduction = seckillMapper.inventoryDeduction(seckillId, version);
		if (!toDaoResult(inventoryDeduction)) {
			log.info(">>>seckillId:{}修改库存失败>>>>inventoryDeduction返回为{} 秒杀失败!", seckillId, inventoryDeduction);
			return;
		}
		// 2.添加秒杀订单
		OrderEntity orderEntity = new OrderEntity();
		String phone = jsonObject.getString("phone");
		orderEntity.setUserPhone(phone);
		orderEntity.setSeckillId(seckillId);
		orderEntity.setState(1l);
		int insertOrder = orderMapper.insertOrder(orderEntity);
		if (!toDaoResult(insertOrder)) {
			return;
		}
		log.info(">>>修改库存成功seckillId:{}>>>>inventoryDeduction返回为{} 秒杀成功", seckillId, inventoryDeduction);
	}

	// 调用数据库层判断
	public Boolean toDaoResult(int result) {
		return result > 0 ? true : false;
	}

}

 

 

2.3、根据手机号码和商品库存id查询秒杀记录

(1)OrderSeckillService :

public interface OrderSeckillService {
	@RequestMapping("/getOrder")
	public BaseResponse getOrder(String phone, Long seckillId);

}

(2)OrderSeckillServiceImpl 

@RestController
public class OrderSeckillServiceImpl extends BaseApiService implements OrderSeckillService {
	@Autowired
	private OrderMapper orderMapper;

	@Override
	public BaseResponse getOrder(String phone, Long seckillId) {
		if (StringUtils.isEmpty(phone)) {
			return setResultError("手机号码不能为空!");
		}
		if (seckillId == null) {
			return setResultError("商品库存id不能为空!");
		}
		OrderEntity orderEntity = orderMapper.findByOrder(phone, seckillId);
		if (orderEntity == null) {
			return setResultError("正在排队中.....");
		}
		return setResultSuccess("恭喜你秒杀成功!");
	}

}

 

 

 

  上一篇:乐观锁实现防止库存超卖

  下一篇: 责任链模式实现网关限流

若对你有帮助,欢迎关注!!点赞!!评论!!

你可能感兴趣的:(项目实战)