DevilKing's blog

冷灯看剑,剑上几分功名?炉香无需计苍生,纵一穿烟逝,万丈云埋,孤阳还照古陵

0%

解决秒杀一则

原文链接

repo

  • 300线程,循环1000次,共30w请求

秒杀后端只能部署有一个节点,因为商品的库存数据都在内存,而这些数据是不跨JVM共享的,放在内存中?

下单流程

通过JMS的消息,作为中间件来

Disruptor是LMAX公司开源的高性能内存队列。Disruptor能够让开发人员只需写单线程代码,就能够获得非常强悍的性能表现,同时避免了写并发编程的难度和坑。 其本质思想在于多线程未必比单线程跑的快。

backend利用它把从ActiveMQ Artemis获得请求串行化,判断商品库存是否充足,更新剩余库存,最后异步写入数据库。

库存充足判断、更新剩余库存的动作都是在内存中进行的,配合Disruptor绕过了并发编程的内存可见性、同步、锁等问题,性能非常强。

在本项目中商品在内存中相关类是Item.java,在利用jol-cli(点此下载)查看其memory-layout后发现,其大小为24byte:

http://central.maven.org/maven2/org/openjdk/jol/jol-cli/0.8/jol-cli-0.8-full.jar

me.chanjar.jms.server.memdb.Item object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 12 (object header) N/A 12 4 int Item.amount N/A 16 4 Long Item.id N/A 20 4 (loss due to the next object alignment) Instance size: 24 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

而Long占用的内存也为24b:

java.lang.Long object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 12 (object header) N/A 12 4 (alignment/padding gap) N/A 16 8 long Long.value N/A Instance size: 24 bytes Space losses: 4 bytes internal + 0 bytes external = 4 bytes total

假设你有100W商品需要秒杀,那么其占用内存 = 1,000,000 * (24b + 4b + 24b) = 52,000,000b = 49m。仅仅只占49m。

架构上的优化点

  1. 下单请求异步处理,请求返回的本次请求的ID,客户端拿这个ID到另行发起请求查询结果
  2. 在秒杀期间,商品库存信息在内存中,库存判断及库存扣减都在内存中进行,之后异步到数据库
  3. 利用Disruptor将并发请求串行化,同时避免了多线程编程复杂度
  4. 抛弃数据库事务,采用最终一致性

和JMS相关的优化点

  1. 重用JMS Connection、Session、MessageProducer、MessageConsumer,而不是每次都创建这些对象(Spring的JmsTemplate就是这么干的)
  2. 将JMS Session设定为transacted=false, AUTO_ACKNOWLEDGE
  3. 发送JMS消息时DeliveryMode=NON_PERSISTENT
  4. 关闭Artemis的重发、消息持久机制

下单请求的异步处理,将下单流程同单子的查询分为两个不同流程,这样进行异步操作

同时引入Disruptor,进行相关的串行化的工作