Implementation of high concurrent seckill in real combat: modify inventory based on Token token bucket + MQ

Keywords: Nginx Redis Mobile Database

1, Theoretical basis

1.1 front end optimization

  1. Use dynamic and static separation to store static resources in a third-party file server to achieve cdn acceleration, so as to reduce the bandwidth of second kill and rush to buy
  2. When the user clicks the seckill button, the button should be disabled to prevent repeated submission
  3. Using complex graphic verification code to prevent machine simulation
  4. Seckill details page, use timer to query seckill results according to user information
  5. Using nginx+lua+openresty to realize static page of product details page

1.2 gateway

  1. ratelimter, nginx, hystrix and redis implement the current limit token pain + copper leakage algorithm to limit the current and protect the service of the user's seckill request.
  2. User blacklist and whitelist interception

1.3 seckill interface

  1. Service degradation, isolation and fusing
  2. Get the token of seckill from redis (if you can get the token, you can succeed in seckill, otherwise you will fail in seckill!)
  3. Asynchronous use of MQ to modify inventory
  4. Provide an interface to query seckill results based on user information

1.4 project deployment

  1. Nginx+lvs for high service availability and clustering
  2. Rush purchase by time (12306 in use, noon and afternoon)

1.5 at the same time, there are 100000 requests to realize seckill, and only 100 commodity inventories. To achieve this, only 100 inventory modifications are needed

Scheme implementation process:

The corresponding tokens (100 tokens) are generated in advance from the corresponding commodity inventory, that is, token bucket. In 100000 requests, as long as anyone can get the token, he can kill in seconds. After getting the token, the mq asynchronous implementation is used to modify and subtract the library

 

2, Code implementation

Schematic diagram:

Note: in the company level seckill service, the seckill producers and consumers should be placed in different services respectively, so as to avoid that the producers hang up and the consumers hang up

2.1 producers

(1) MQ related configuration: RabbitmqConfig

@Component
public class RabbitmqConfig {

	// Add modify inventory queue
	public static final String MODIFY_INVENTORY_QUEUE = "modify_inventory_queue";
	// Switch name
	private static final String MODIFY_EXCHANGE_NAME = "modify_exchange_name";

	// 1. Add switch queue
	@Bean
	public Queue directModifyInventoryQueue() {
		return new Queue(MODIFY_INVENTORY_QUEUE);
	}

	// 2. Define switches
	@Bean
	DirectExchange directModifyExchange() {
		return new DirectExchange(MODIFY_EXCHANGE_NAME);
	}

	// 3. Modify inventory queue binding switch
	@Bean
	Binding bindingExchangeintegralDicQueue() {
		return BindingBuilder.bind(directModifyInventoryQueue()).to(directModifyExchange()).with("modifyRoutingKey");
	}

}

(2) Producer send message: spike commodityproducer

@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("-", "");
		// Encapsulation message
		Message message = MessageBuilder.withBody(jsonString.getBytes())
				.setContentType(MessageProperties.CONTENT_TYPE_JSON).setContentEncoding("utf-8").setMessageId(messAgeId)
				.build();
		// Data returned by build callback (message id)
		this.rabbitTemplate.setMandatory(true);
		this.rabbitTemplate.setConfirmCallback(this);
		CorrelationData correlationData = new CorrelationData(jsonString);
		rabbitTemplate.convertAndSend("modify_exchange_name", "modifyRoutingKey", message, correlationData);

	}

	// When the producer sends a message to the server, the producer uses the reply mechanism
	@Override
	public void confirm(CorrelationData correlationData, boolean ack, String cause) {
		String jsonString = correlationData.getId();
		System.out.println("news id:" + correlationData.getId());
		if (ack) {
			log.info(">>>Use MQ Message confirmation mechanism ensures that messages must be delivered to MQ Success in China");
			return;
		}
		JSONObject jsonObject = JSONObject.parseObject(jsonString);
		// If producer message delivery fails, recursive retry mechanism is adopted
		send(jsonObject);
		log.info(">>>Use MQ Message confirmation mechanism delivered to MQ Failure in");
	}
}

 

2.2 consumers

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<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. Get seckill id
		Long seckillId = jsonObject.getLong("seckillId");
		SeckillEntity seckillEntity = seckillMapper.findBySeckillId(seckillId);
		if (seckillEntity == null) {
			log.warn("seckillId:{},Product information does not exist!", seckillId);
			return;
		}
		Long version = seckillEntity.getVersion();
		int inventoryDeduction = seckillMapper.inventoryDeduction(seckillId, version);
		if (!toDaoResult(inventoryDeduction)) {
			log.info(">>>seckillId:{}Failed to modify inventory>>>>inventoryDeduction Return to{} Second kill failed!", seckillId, inventoryDeduction);
			return;
		}
		// 2. Add seckill order
		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(">>>Modify inventory succeeded seckillId:{}>>>>inventoryDeduction Return to{} Seckill success", seckillId, inventoryDeduction);
	}

	// Call database layer judgment
	public Boolean toDaoResult(int result) {
		return result > 0 ? true : false;
	}

}

 

 

2.3. Query seckill record according to mobile phone number and commodity inventory id

(1)OrderSeckillService :

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

}

(2)OrderSeckillServiceImpl 

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

	@Override
	public BaseResponse<JSONObject> getOrder(String phone, Long seckillId) {
		if (StringUtils.isEmpty(phone)) {
			return setResultError("Mobile number cannot be empty!");
		}
		if (seckillId == null) {
			return setResultError("Commodity inventory id Can not be empty!");
		}
		OrderEntity orderEntity = orderMapper.findByOrder(phone, seckillId);
		if (orderEntity == null) {
			return setResultError("Queuing.....");
		}
		return setResultSuccess("Congratulations on your success!");
	}

}

 

Published 67 original articles, won 120 praises and 60000 visitors+
Private letter follow

Posted by tamir_malas on Sun, 08 Mar 2020 07:01:54 -0700