java 秒杀系统(后端)实现思路

秒杀系统实现思路

秒杀系统,系统瞬间要处理大量并发,核心问题在于如何在大并发的情况下能保证 DB 能扛得住压力,因为高并发的瓶颈就在于DB。如果说请求直接从前端透传到 DB,显然,DB是无法承受几十万上百万甚至上千万的并发量的,这里就用到了另外一个非常重要的组件:消息队列。我们不是把请求直接去访问数据库,而是先把请求写到消息队列中,做一个缓存,然后再去慢慢的更新数据库。

思路

  1. 系统初始化,把商品库存数量加载到Redis上面来。
  2. 后端收到秒杀请求,Redis预减库存,如果库存已经到达临界值的时候,就不需要继续请求下去,直接返回失败,即后面的大量请求无需给系统带来压力。
  3. 判断这个秒杀订单形成没有,判断是否已经秒杀到了,避免一个账户秒杀多个商品,判断是否重复秒杀。
  4. 库存充足,且无重复秒杀,将秒杀请求封装后消息入队,同时给前端返回一个code
    (0),即代表返回排队中。(返回的并不是失败或者成功,此时还不能判断)
    前端接收到数据后,显示排队中,并根据商品id轮询请求服务器(考虑200ms轮询一次)。
  5. 后端RabbitMQ监听秒杀的订单信息,获取到传入的信息,执行真正的秒杀之前,要判断数据库的库存,判断是否重复秒杀,然后执行秒杀事务(秒杀事务是一个原子操作:库存减1,下订单,写入秒杀订单)。

代码实现

1.将要秒杀的商品生成对应商品数量token存储到 redis,减轻数据库压力

/**
	 * 采用redis数据库类型为 list类型 key为 商品库存id list 多个秒杀token
	 * 
	 * @param seckillId  商品id
	 * @param tokenQuantity 令牌数量,对应商品数量
	 * @return
	 */
	// 采用redis数据库类型为 list类型 key为 商品库存id list 多个秒杀token
	@RequestMapping("/addSpikeToken")
	public BaseResponse<JSONObject> addSpikeToken(Long seckillId, Long tokenQuantity) {
		// 1.验证参数
		if (seckillId == null) {
			return setResultError("商品库存id不能为空!");
		}
		if (tokenQuantity == null) {
			return setResultError("token数量不能为空!");
		}
		SeckillEntity seckillEntity = seckillMapper.findBySeckillId(seckillId);
		if (seckillEntity == null) {
			return setResultError("商品信息不存在!");
		}
		// 2.使用多线程异步生产令牌
		createSeckillToken(seckillId, tokenQuantity);
		return setResultSuccess("令牌正在生成中.....");
	}

这里采用异步多线程生成token

@Async
	public void createSeckillToken(Long seckillId, Long tokenQuantity) {
		generateToken.createListToken("seckill_", seckillId + "", tokenQuantity);
	}

生成token并存入redis


	public void createListToken(String keyPrefix, String redisKey, Long tokenQuantity) {
		List<String> listToken = getListToken(keyPrefix, tokenQuantity);
		redisUtil.setList(redisKey, listToken);
	}

	public List<String> getListToken(String keyPrefix, Long tokenQuantity) {
		List<String> listToken = new ArrayList<>();
		for (int i = 0; i < tokenQuantity; i++) {
			String token = keyPrefix + UUID.randomUUID().toString().replace("-", "");
			listToken.add(token);
		}
		return listToken;

	}

2.用户访问秒杀接口时,会先去redis中获取对应商品的token,

/**
	 * 注解 AOP 减少代码重复调用 使用网关开启限流
	 */
	/**
	 * 用户秒杀接口 phone和userid都可以的
	 *
	 * @phone 手机号码<br>
	 * @seckillId 要秒杀的商品id
	 * @return
	 */
	@RequestMapping("/spike")
	@Transactional(rollbackFor = Exception.class)
	public BaseResponse<JSONObject> spike(String phone, Long seckillId) {
		log.info("###>>>>>秒杀接口线程池名称:" + Thread.currentThread().getName());
		// 1.参数验证
		if (StringUtils.isEmpty(phone)) {
			return setResultError("手机号码不能为空!");
		}
		if (seckillId == null) {
			return setResultError("商品库存id不能为空!");
		}
		// 2.从redis从获取对应的秒杀token
		String seckillToken = generateToken.getListKeyToken(seckillId + "");
		if (StringUtils.isEmpty(seckillToken)) {
			log.info(">>>seckillId:{}, 亲,该秒杀已经售空,请下次再来!", seckillId);
			return setResultError("亲,该秒杀已经售空,请下次再来!");
		}

		// 3.获取到秒杀token之后,异步放入mq中实现修改商品的库存
		sendSeckillMsg(seckillId, phone);
		return setResultSuccess("正在排队中.......");
	}

这里使用leftPop取出一个token,同时redis中的token就少了一个

public String getListKeyToken(String key) {

	String value = redisUtil.getStringRedisTemplate().opsForList().leftPop(key);
	return value;
}

这里同样采用异步多线程方式发送到rabbitmq消息对队列

/**
	 * 获取到秒杀token之后,异步放入mq中实现修改商品的库存
	 */
	@Async
	public void sendSeckillMsg(Long seckillId, String phone) {
		JSONObject jsonObject = new JSONObject();
		jsonObject.put("seckillId", seckillId);
		jsonObject.put("phone", phone);
		spikeCommodityProducer.send(jsonObject);
	}
@Transactional(rollbackFor = Exception.class)
	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);

		//发送消息到rabbitmq
		rabbitTemplate.convertAndSend("modify_exchange_name", "modifyRoutingKey", message, correlationData);

	}

3.订单系统会监听rabbitmq 的消息,进行消费

/**
	 * 订单服务监听修改库存的队列
	 *
	 * @param message  队列中存储的信息
	 * @param headers
	 * @param channel
	 * @throws IOException
	 */
	@RabbitListener(queues = "modify_inventory_queue")
	@Transactional(rollbackFor = Exception.class)
	public void process(Message message, @Headers Map<String, Object> 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);
	}

  • 10
    点赞
  • 50
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Java后端实现秒杀系统的步骤如下: 1.设计数据库表结构 秒杀系统需要设计两个表,一个是商品表,另一个是秒杀订单表。商品表包含商品的id、名称、库存、价格等信息;秒杀订单表包含订单id、用户id、商品id、数量、金额、订单状态等信息。 2.实现商品列表展示 在Java代码中,通过查询商品表的数据,将商品列表展示在页面上。需要注意的是,为了防止商品被重复购买,需要在商品表中设置一个库存字段,每次购买成功后需要将库存减1。 3.实现秒杀接口 为了保证秒杀的公平性,需要在后端实现一个秒杀接口。在Java代码中,可以使用Redis等缓存工具来限制每个用户的秒杀请求频率,并且通过减少库存来防止超卖。同时,在秒杀订单表中插入一条订单记录。 4.实现订单支付 用户在秒杀成功后需要进行支付操作。可以通过调用第三方支付接口来完成支付操作。支付成功后,需要将订单状态修改为已支付,并将商品表中的库存减1。 5.实现订单查询 用户可以在页面上查询自己的订单信息。可以通过查询秒杀订单表的数据,将订单信息展示在页面上。 6.实现订单取消 用户可以在一定时间内取消订单。在Java代码中,可以通过修改订单状态为已取消来实现订单的取消操作。同时,需要将商品表中的库存加1。 以上就是Java后端实现秒杀系统的步骤,需要注意的是,为了保证系统的并发性和稳定性,需要使用高并发的技术,例如分布式缓存、消息队列等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值