上期回顾:Java Seckill Module:Static resource optimization
并发量太大的时候,仅仅是对页面进行优化还是不够的,还要对接口进行优化:
redis预减库存减少数据库访问:用户在秒杀的时候会去判断库存还有没有,这里不去访问数据库而是去访问redis。
内存标记减少redis访问:由于访问redis也是有网络开销的,应该尽量减少去访问。
rabbitMQ队列缓存、削峰、降流,异步下单:服务端收到这个秒杀请求之后不是直接访问数据库而是将请求放入队列里面去。
nginx水平扩展: 对应用进行水平扩展。
核心思路:减少数据库访问。
步骤:
在系统初始化的时候,首先将商品库存数量加载到redis,接着服务端收到请求的时候,进行下redis的预减库存,有什么好处这样,例如过来10个请求,将预减到0,如果第11个请求过来的时候就不进行往下走了,直接返回秒杀失败,如果有库存,也不是直接访问数据库,而是将请求放入队列中,接着服务端立即返回排队中,这里的返回并不是成功和失败,而是排队中,因为是在排队,所以是不知道能不能秒杀成功的,失败也暂时还不能判断出来,类似于12306买完票后进行排队中,相当于异步下单,接着就是进行请求出队,生成订单,再对数据库减少库存,记住这里不是预减库存而是真正的在数据库中减少库存,随后客户端会轮询是否秒杀成功。
安装rabbitmq,再集成rabbitmq,添加完依赖,再加完配置后,需要创建消息消费者以及消息发送者。
首先需要创建一个队列bean:
@Configuration public class MQConfig { public static final String QUEUE = "queue"; @Bean public Queue queue() { return new Queue(QUEUE, true); } }消息发送者:
@Service public class MQSender { private static Logger log = LoggerFactory.getLogger(MQSender.class); @Autowired AmqpTemplate amqpTemplate ; public void send(Object message) { String msg = RedisService.beanToString(message); log.info("send message:"+msg); amqpTemplate.convertAndSend(MQConfig.QUEUE, msg); } }解读:首先将bean转化为string,再放入指定的队列中去。
消息接收者:
private static Logger log = LoggerFactory.getLogger(MQReceiver.class); @RabbitListener(queues=MQConfig.QUEUE) public void receive(String message) { log.info("receive message:"+message); } @RequestMapping("/mq") @ResponseBody public Result<String> mq(){ sender.send("我是剑主"); return Result.success("nihao"); }上面的操作是属于mq的direct模式,直接指定队列名,然后往里面塞,这种模式下不需要将Exchange进行任何绑定(binding)操作。
【rabbitmq四种交换机模式】
Topic模式:
MQConfig:
public static final String TOPIC_QUEUE1 = "topic.queue1"; public static final String TOPIC_QUEUE2 = "topic.queue2"; public static final String TOPIC_EXCHANGE = "topicExchage"; @Bean public Queue topicQueue1() { return new Queue(TOPIC_QUEUE1, true); } @Bean public Queue topicQueue2() { return new Queue(TOPIC_QUEUE2, true); } @Bean public TopicExchange topicExchage(){ return new TopicExchange(TOPIC_EXCHANGE); }我们需要将消息放入Exchage中,Exchage再把消息放入队列中去,怎么放?需要绑定:
绑定到topicBinding1、topicQueue2上:
@Bean public Binding topicBinding1() { return BindingBuilder.bind(topicQueue1()).to(topicExchage()).with("topic.key1"); } @Bean public Binding topicBinding2() { return BindingBuilder.bind(topicQueue2()).to(topicExchage()).with("topic.#"); } 发送者: public void sendTopic(Object message) { String msg = RedisService.beanToString(message); log.info("send topic message:"+msg); amqpTemplate.convertAndSend(MQConfig.TOPIC_EXCHANGE, "topic.key1", msg+"1"); amqpTemplate.convertAndSend(MQConfig.TOPIC_EXCHANGE, "topic.key2", msg+"2"); } 接收者: @RabbitListener(queues=MQConfig.QUEUE) public void receive(String message) { log.info("receive message:"+message); } @RabbitListener(queues=MQConfig.TOPIC_QUEUE1) public void receiveTopic1(String message) { log.info(" topic queue1 message:"+message); } @RabbitListener(queues=MQConfig.TOPIC_QUEUE2) public void receiveTopic2(String message) { log.info(" topic queue2 message:"+message); }过程:做了一个key的绑定,然后一个发送一个接收。
还有Fanout模式、Header模式等等,这里不做过多的演示。