- 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。
架构上的优化点
- 下单请求异步处理,请求返回的本次请求的ID,客户端拿这个ID到另行发起请求查询结果
- 在秒杀期间,商品库存信息在内存中,库存判断及库存扣减都在内存中进行,之后异步到数据库
- 利用Disruptor将并发请求串行化,同时避免了多线程编程复杂度
- 抛弃数据库事务,采用最终一致性
和JMS相关的优化点
- 重用JMS Connection、Session、MessageProducer、MessageConsumer,而不是每次都创建这些对象(Spring的JmsTemplate就是这么干的)
- 将JMS Session设定为transacted=false, AUTO_ACKNOWLEDGE
- 发送JMS消息时DeliveryMode=NON_PERSISTENT
- 关闭Artemis的重发、消息持久机制
下单请求的异步处理,将下单流程同单子的查询分为两个不同流程,这样进行异步操作
同时引入Disruptor,进行相关的串行化的工作