我的方案将实现一个订单全程只进行一次insert操作,查余票无需发送select语句,完全消除由于事务、库表锁行锁带来的性能损耗。
流程:
1、将一趟车的所有票况在系统启动时存入一个该趟车专有类中,票况信息为单例。类中有4个静态的余票数组(站票、坐票、硬卧、软卧),存储该趟车各途经站点下完乘客后的剩余的座位数(余票),用的是数组而不是整数是因为现在的列车座位是会被复用的。
2、类中有两个主要方法,一个查余票,一个查余票并购票,查余票方法仅从本类的余票数组中获取余票数,购票方法在查了票以后若有余票则扣除,无论有无余票,都会发出一条insert,保存订单号、车次、乘车日期、起始站、终点站、票的种类、购票结果(成功/失败)。
3、要买该趟车的订单被排入4个队列,分别对应站票、坐票、硬卧、软卧队列。
4、每个队列挨个取订单来调用购票方法,由于操作的是对应的4个不同余票数组,所以不会出现并发操作。
5、用户请求在被排入队列时就已返回,用户持有订单号,将在若干秒后通过ajax查订单处理结果。查结果的方式为,往之前insert的表中按订单号查结果是成功还是失败(订单号为索引)。若无则判为仍在队列中,提示等待,一定时间后ajax再来。
分析下性能:
由于未动用数据库锁,购票过程就是数组值操作加一条insert,所以可以非常快,相信在调优下购票业务逻辑单机对某趟车一秒钟可以处理2000订单以上,实现各趟车分库后应该还能大大提高。一趟车大约有10000张票,若同时有10万人在秒杀该趟车次,则50秒可以全部告知用户是否秒杀成功,然后用户视结果换一种票类或换一趟车继续秒杀。
当然全国一天有近4000趟车,全都这么极端实现就得去租4000台服务器了,一台装一个数据库,一个库就为一趟车服务。就算一台100元租一天,一天要花40万租服务器,那可以考虑适当整合一下,几趟车用一台数据库服务器,性能也仍可接受,毕竟高峰期持续不长。
再提下最重要的宕机处理:
既然实时票数存在单例的类中,那就要应付可能出现的机器挂掉的情况。借鉴事件驱动思想,我的方案是:换一台业务服务器顶上,把挂掉的各趟车的类重新初始化为初始总票数,然后从库里查出所有的已成功订单来挨个算票,重新在数组里减一遍票,就能恢复到宕机前的余票情况了。一天最多数百万成功订单,全查出来再算一遍,并不需要很长时间,宕机这情况本来就少见,实在遇上了花半小时再重算下票数不过份,何况怎么可能所有算票服务器全挂了呢。
算票业务类粗略实现如下,仅供参考,未细化验证:
import java.util.HashMap;
import java.util.Map;
// 该趟车为上海始发,途经苏州、南京、天津、终点站北京
public class TicketTest {
// 用来保存购票结果的dao,库表结构为 订单id,起始站,到达站,票的类别,购票结果
private TicketDao ticketDaoImpl;
// 该趟车各类票的总数
private final static int zhanpiao = 2000;
private final static int zuopiao = 2000;
private final static int yingwo = 1000;
private final static int ruanwo = 200;
// 该趟车从始发站开至各个站点的余票(座)
private final static int[] zhanpiaoArr = new int[] { zhanpiao, zhanpiao,
zhanpiao, zhanpiao };
private final static int[] zhuopiaoArr = new int[] { zuopiao, zuopiao,
zuopiao, zuopiao };
private final static int[] yingwoArr = new int[] { yingwo, yingwo, yingwo,
yingwo };
private final static int[] ruanwoArr = new int[] { ruanwo, ruanwo, ruanwo,
ruanwo };
private final static Map<String, Integer> zhanming = new HashMap<String, Integer>();
public void init() {
// 始发站上海不用存,数组中也没有上海的余票,因为不存在从上海坐到上海这种情况
// value值,为到达该城市余票在票数数组(如zhanpiaoArr)中的下标
zhanming.put("苏州", 0);
zhanming.put("南京", 1);
zhanming.put("天津", 2);
zhanming.put("北京", 3);
}
// 获取从start站到end站的余票,type为票的种类:站、坐、硬卧、软卧
public int getRemainingTicket(String start, String end, int type) {
int startIndex = zhanming.get(start);
int endIndex = zhanming.get(end);
int remainingNumber = 0;
int[] array = new int[zhanming.size()];
// 确定从哪类余票数组中查票,并把余票初始化为该类票最大票数
switch (type) {
case 1:
array = zhanpiaoArr;
remainingNumber = zhanpiao;
break;
case 2:
array = zhuopiaoArr;
remainingNumber = zuopiao;
break;
case 3:
array = yingwoArr;
remainingNumber = yingwo;
break;
case 4:
array = ruanwoArr;
remainingNumber = ruanwo;
break;
}
// 查询从某站到某站每一站的剩余票数,以票最少的站点所剩票数为实际余票数
// 例如卖掉了一张上海到苏州硬座,那苏州余票为1999,票数数组是[1999, 2000, 2000, 2000]
// 此时若你要买上海到南京,则取途经站最小值,实际上只剩1999个座位
// 而如果你想买的是苏州到北京,取途经站最小值则是剩2000张票,因为前面那张票的人在苏州下车了
for (int i = startIndex; i <= endIndex; i++) {
// 若找到最小值,则做为余票数
if (array[i] < remainingNumber) {
remainingNumber = array[i];
}
}
return remainingNumber;
}
// 进行实时查票并购票,有4个订单队列在排队,每个队列都会逐个订单来调用此方法,因此同一种票,即同一余票数组不会被并发操作。
public void buyTicket(String start, String end, int type, String orderId) {
int remainingNumber = getRemainingTicket(start, end, type);
if (remainingNumber > 0) {
// 如果有余票,将要买的票扣除并将购买成功结果存库
int[] array = new int[zhanming.size()];
switch (type) {
case 1:
array = zhanpiaoArr;
break;
case 2:
array = zhuopiaoArr;
break;
case 3:
array = yingwoArr;
break;
case 4:
array = ruanwoArr;
break;
}
for (int i = array[zhanming.get(start)]; i < array[zhanming.get(end)]; i++) {
// 从票数数组找到所有途经站点将票数减1,到达站不用减,因为你已下车,不会占用到达站的座位
array[i] -= 1;
}
ticketDaoImpl.save(orderId, start, end, type, true);
} else {
// 无余票,直接将购买失败记录存库,这个不可省略,否则用户查询购票结果时将分不清是没买上还是仍在排队
ticketDaoImpl.save(orderId, start, end, type, false);
}
}
// set/get省略
}
分享到:
相关推荐
在提供的文件内容中,我们看到了一个关于金融产品的明细表,该表涵盖了LMAX交易公司的多种交易品种的详细资料。这些交易品种包括各种指数期货合约,以及对应的产品杠杆、合约大小、手续费、最小交易量、最大交易量、...
LMAXCollections, LMAX集合 LMAX集合高性能集合库维护者合并环缓冲:[Nick Zeeb] ( https://github.com/nickzeeb )是什么?请参见 http://nickzeeb.wordpress.com/2013/03/07/t
LMAX Net Client Library 1.8.4.0 是一个专为C#开发者设计的.NET库,用于与LMAX交易平台进行...通过充分利用.NET Framework的异步特性,开发者可以构建出高度响应和可靠的系统,满足金融领域对速度和准确性的严格要求。
Disruptor框架是由LMAX公司开发的一款高效的无锁内存队列。使用无锁的方式实现了一个环形队列。据官方描述,其性能要比BlockingQueue至少高一个数量级。根据GitHub上的最新版本源码打出的包,希望对大家有帮助。
最新lmax在线开户注册指南收集.pdf
标题 "lmax.rar_Lmax_The Signal" 暗示我们关注的是一个与寻找数据中的局部最大值(Local Maxima)以及滤除高频信号相关的MATLAB程序,具体来说,是名为 "lmax.m" 的脚本文件。在这个场景中,"lmax" 可能是一个函数...
综上所述,LMAX Disruptor是一个强大的并发工具,通过其独特的设计和优化,为高并发场景提供了高效的解决方案。了解和掌握Disruptor,能够帮助开发者构建更高效、更稳定的系统。通过提供的API、源码和示例,开发者...
LMAX是一种新型零售金融交 易平台,它能够以很低的延迟产生大量交易。这个系统是建立在JVM平台上,其核心是一个业务逻辑处理 器,它能够在一个线程里每秒处理6百万订单。业务逻辑处理器完全是运行在内存中,使用事件...
新lmax在线开户注册指南分享.pdf
LMAX Client是一款开源的金融交易平台客户端,专为与LMAX Exchange的Web服务和API交互而设计。这款应用程序的核心特点在于其免费提供,并且源代码开放,允许开发者和交易者深入理解其工作原理,同时也能根据自己的...
这种性能提升使得Disruptor成为异步事件驱动架构的理想选择,LMAX公司已经在订单匹配引擎、实时风险管理和内存事务处理系统等多个关键项目中成功应用了Disruptor,实现了前所未有的性能水平。 Disruptor并非仅限于...
LMAX Disruptor 到 Go 语言的移植。Disruptor 概述这是LMAX Disruptor到 Go 编程语言的...在 Go 中,通道 ( chan) 的当前实现对发送、接收和操作保持锁定len,无争用访问的最大速度约为每秒 2500 万条消息 — 与 Disru
本研究论文专注于提高数据库在恢复过程中的服务有效性,通过建立一种基于数据项分类标记的错误隔离模型,并提出了基于事务依赖关系的数据修复算法。这些方法有助于缩短错误修复的时间,从而提高数据库系统的整体可用...
基于Spring Boot和LMAX Disruptor的高性能并发框架 项目简介 本项目是一个基于Spring Boot和LMAX Disruptor框架的高性能并发框架,旨在提供高效的事件处理和消息传递机制。项目涵盖了并发编程的核心概念、无锁...
在LMAX公司的实际应用中,例如顺序匹配引擎、实时风险管理系统和内存事务处理系统,都基于Disruptor实现了卓越的性能提升,打破了行业标准。 Disruptor的主要特点包括: 1. **减少写入争用**:通过避免在生产者和...
Disruptor通过环形缓冲区(Ring Buffer)结构设计,摒弃了锁机制,利用了现代CPU缓存架构的优势,从而显著提高了数据在多个生产者和消费者之间的流转速度。事件处理器链的设计进一步确保了事件处理的顺序性和效率,...