第一部分论述“海量事务高速处理”现阶段没有通用解决方案,尝试通用解决方案就是误区。
第二部分讲解算法问题、安全问题经验,以及一些误区。
第三部分讲解电子商务网站的核心交易系统如何随着网站的发展而演进,分成了三个发展阶段,发展过程中的一些经验和误区。
另外,具体的需求的确不在本文讨论之列,望各位读者海涵。
今天你买到票了吗?
12306.cn是中国铁路客户服务中心网站。作为铁道部唯一授权的火车票订票网站,在2012年的春运前夕几乎成为了一个家喻户晓的网站。因为从今年起,每个人都可以通过这个网站以实名制的方式预订火车票,和家人团聚,共度一个祥和幸福美满团圆的春节。与此同时,12306.cn网站也被舆论推到了风口浪尖,这并不是因为12306.cn网站满足了每一位用户回家的愿望而获得了赞誉。而是在互联网已经开始进入每个人日常生活的时代,12306.cn网站的界面丑陋、UI(用户界面)粗糙,不夸张的说,比十年前的网站还逊色。当然,作为一个功能性网站,用户也不会苛求界面的美观,UI的精致,但是让每一位用户都不可接受的是,12306.cn网站的服务器不稳定,屡屡瘫痪。笔者很荣幸应CSDN之邀撰文对本次事件做一些分析。
作为政府部门唯一授权的网站,用户没有用脚投票选择离开的权力。刚刚过去的2011年是中国电子商务网站遍地开花的一年,中国的每家电子商务网站都在比拼烧钱打广告,期望获得更高的全球Alexa排名,期望获得更高的订单数量、销售数量、用户转化率。按着笔者了解的情况,在2011年,电子商务网站每IP的广告费用平均约为0.4元,转化为注册用户时的每注册用户的广告费用平均约为4元,转化为实际销售的每个首次销售订单的广告费用平均约为40元,也就是说一位通过电子商务网站广告而访问电子商务网站产生的首次购买,如果利润低于40元的话,那么就是在赔钱。
如果从电子商务的角度来看12306.cn网站的话,情况正好相反,没做一分钱广告,仅仅几天的时间Alexa的排名飙升至260,这不是中国网站的排名,是全球网站的排名,而且还有不断上升的趋势,日订单量很可能成为全国第一,销售额都是可预知的,因为每列火车中每节车厢的每个座位都会卖出去,转化率达到了前无古人后无来者的100%,因为所有的注册用户,都是想买车票的,只能是他买不到,不可能是他不买。媒体普遍戏称12306.cn网站是中国、仍至全球有史以来最牛的电子商务网站。
作为12306.cn主管部门——铁道部,从建国之初就有一个众所周知的外号“铁老大”。但是作为12306.cn网站的制作单位——中国铁道科学研究院(简称铁科研),从一家仅为业内人所熟知的科研单位,仅仅几天的时间就受到了广泛的关注,因为每一位在订票时屡屡遇到服务器不稳定、甚至瘫痪的用户,特别是前一秒中还看到有票,因为进不去订票界面,后一秒钟就看到无票的用户,都会对这个网站的制作者产生好奇,究竟是谁做出这么一个网站?浏览到12306.cn网站页面的页脚就会看到“中国铁道科学研究院”的名字。接下来笔者将分析研发一下类似于12306.cn网站将会遇到哪些问题,以及如何通过现有的技术手段解决这些问题,并探讨在一般的电子商务网站中如何处理同类问题。
第一部分
在线处理的四种类型
互联网站除了浏览器端的展示外,在服务器端的程序归属于“在线处理”的范畴。一般来说“在线处理”可以分成“在线事务处理”(OLTP,OnLine Transaction Process)和“在线分析处理”(OLAP,OnLine Analytics Process)两类处理方式。事务(Transaction)这个概念来源于数据库,在数据库中事务(Transaction)是访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。
随着技术的发展,事务已经不仅仅局限在数据库领域。现阶段,一般用两种角度来描述一个事务,一个是从外部的角度,事务是恢复和并发控制的基本单位。另一利是内部的角度:事务,是由一系列不可分割并且不可错序的动作组成。虽然理解事务的角度随着技术的发展不断演讲,但是用于描述并评估事务的属性保持不变,一个事务应该具有4个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性。
- 原子性(Atomicity)。一个事务是一个不可分割的工作单位,事务中包括的诸操作要么都做,要么都不做。
- 一致性(Consistency)。事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。
- 隔离性(Isolation)。一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
- 持久性(Durability)。持续性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。
以12306.cn为例,为避免“黄牛”,购票系统有一个业务逻辑:一个有效身份证件同一乘车日期同一车次限购一张车票。因此购买一张车票可以简化为包含四个操作:
- 判断同一乘车日期同一车次是否有未预订的空余座位
- 判断这个有效身份证是否已购买过同一乘车日期同一车次的车票
- 车票上标注的座位标记为已预订
- 如果没有购买过,则该身份证预订一张车票
原子性是指用户提交一次购买的时候,要么该车票上标注的座位标记为已预订并且该身份证预订一张车票;要么没有标记该车票上标注的座位已预订并且该身份证预订失败。也就是第3个操作和第4个操作要么都做,要么都不做。不允许没有标记该车票上标注的座位已预订,但是该身份证预订了一张标注为这个座位的车票,或者有某一个座位被标记为预订但是该身份证预订失败。一致性也是指类似的含义。
隔离性是指如果有两个用户同时预订同一乘车日期同一车次的车票时,假设只剩一张票了,那么第一个用户判断出有未预订的空余座位,那么他就可以成功预订车票。因此既便是第二个用户在第一个用户标记该座位已预订之前判断是否有未预订的空余座位,返回给他的结果也是没有空余座位。
持久性比较好理解,如果一个用户已成功预订了一个座位,那么其他用户就不能预订同一乘车日期同一车次的同一个座位了。
对于一般的电子商务网站来说,一次购买行为可以简化为四个操作:
- 判断该商品有库存
- 判断该用户的账户余额
- 减少该用户的账户余额
- 减少该商品的库存
这样的四个操作组成了一次购买行为,也需要满足ACID属性,当然实际的场景要复杂得多。
一般来说,不具备ACID属性的在线处理都可以称之为OLAP,这不意味着OLAP比OLTP简单。通常所说的OLAP是指对一定数量的数据进行统计分析的操作,需到强大的计算资源支持。OLTP和OLAP是评估“在线处理”的一个角度,另一个角度就是对于处理速度,也就是响应速度的要求,速度一般和数据量紧密关联,相对于海量高速处理而言,海量低速处理,或者少量高速处理就容易得多。通常把海量低速处理或者少量高速处理统称为低速处理。
这样对于在线处理的两个角度就构成了四种情况:海量事务高速处理,海量分析高速处理,事务低速处理,分析低速处理四种情况。
OLTP | OLAP | |
海量高速 | 海量事务高速处理 | 海量分析高速处理 |
低速 | 事务低速处理 | 分析低速处理 |
“海量事务高速处理”与其他在线处理的区别与联系
在这四种情况中,先讲一下分析低速处理。虽然分析低速处理看起来不要求速度,也不要求事务,但是在在线处理中被纳入到分析低速处理领域的问题一般是指复杂算法方面的处理,常见的有聚类分析,在本文中不再展开讨论。下面分别针对海量高速中的OLTP和OLAP的区别展开探讨,以及OLTP中的海量高速和低速展开探讨。
对于海量高速而言,因为涉及到相当规模的运算量,远远超过单台计算机的处理能力,需要多台机算机组成一个系统,对于外界来说就像是一个系统,这样的方式被称为集群计算。集群计算除了用于解决运算量之外,同时也用于提高系统的可用性,当集群中的一台计算机出现故障时,另一台计算机可以接替它的计算任务,从外界来看,集群相对单台计算机而言,可用性提高了。
在OLAP中,因为每个在线处理过程之间都是相对独立的,不具有OLTP的ACID属性,因此使用集群计算处理分析任务时,一般的过程是把任务分发到多台计算机上,所有计算机返回计算结果后,再汇总结果返回给用户,这种方式一般被称为高速并行计算。从字面意义上看,所有的计算任务都是并行处理的,之间没有太多的依赖关系。对于高速并行计算而言,只要有足够的传输带宽、服务器够快、够贵、够多就可以解决所需要的计算量的要求。
这也是目前“海量事务高速处理”混同于“海量分析高速处理”时常见的一种误解,认为使用“高速并行计算”解决“海量分析高速处理”的方式可以用于解决“海理事务高速处理”,比如说使用单台服务器一秒钟可以处理一个交易,那么使用一万台服务器就可以一秒种处理一万个交易。或者说是只要有足够的传输带宽、服务器够快、够贵、够多就可以解决“海量事务高速处理“的问题,这也是本文要澄清的关键问题之一。这也是相当一部分电子商务网站从初期的低速处理时期向“海量高速”发展时遇到的最大技术瓶颈。
当然这并不意味着“海量分析高速处理”比“海量事务高速处理”容易,这是两个不同的方向,一个典型的例子就是搜索引擎,大型搜索引擎中的一次搜索行为并不会涉及ACID等等属性,但是仍需要将一次搜索任务分发给数百台或者更多的服务器,然后再合并这些服务器的搜索结果后返回给用户。在这方面典型的技术就是Google的MapReduce编程模型,目前有Hadoop这个开源项目实现了Google的MapReduce模型。
“海量事务高速处理”从量变到质变
由于事务具有ACID等属性,因此“海量事务高速处理”和“海量分析高速处理”需要完全不同的技术路线,在国内,12306.cn并不是第一个遭遇这个问题的网站,2008年奥运会的订票系统也遭遇过同样的窘境,只是因为用户量相对少些,和大多数人的关系不密切而没有得到广泛的关注。当然如果数据量没有那么多,速度要求也没有那么高的话,也就是在“事务低速处理”领域中,目前有着丰富的解决方案,目前主流的数据库,特别是商业数据库都重点关注这一领域。
特别是从具体的需求来看,“事务低速处理”和“海量事务高速处理”只是“量”上的区别,作为需求的“事务”本身并没有任何变化,因此一个简单的思维就是开发一个“事务低速处理”的网站,然后用“海量分析高速处理”的高速并行计算解决方案来解决“量”问题,但是结果并不如意,在辩证法中的三大定律之一——量变引起质变,也同样适用于“海理事务高速处理”。近些年来,已经有很多先行者尝试过努力,也积累了一定的经验和教训。
在通用领域中,既便是作为领头羊的IBM、HP等公司碰到这种系统,都是只卖硬件,绝不为软件系统承担任何风险和责任。说明白了,我挣硬件的钱,软件,你们谁不怕死的话谁就向前冲吧。多年来在通用领域毫无建树不意味着“海量事务高速处理”是不可解决的难题,在专用领域“海量事务高速处理”的发展就非常引人注目,其中最典型的案例就是IBM为大型金融机构提供的IT解决方案。分析这个案例有助于拨开“海量事务高速处理”的重重迷雾。
在IBM的解决方案中,其核心就是S390大型机,笔者虽然没有接触过S390大型机,但是曾经在十年前学习过AIX小型机,并取得了中级管理员证书。在这里不展开讲解具体的小型机、大型机技术,总体而言,这些设备所表现出来的卓越运算能力,都是局限在某个特定的领域。也就是说,IBM为金融领域的特定场景“定制”了一套系统,这套系统在硬件、操作系统、数据库、中间件、应用程序整个流程的每个环节都做了优化,消除了所有的瓶颈。
换句话说,虽然在这套系统中仍然可以区分出硬件、操作系统、数据库、中间件、应用程序。但已经和通用领域中的含义不同了。比如说S390上运行的数据库是DB2,事务中间件是CICS,在普通PC上也有着对应的DB2和CICS产品,但并不是简单的移植,在S390上的这些软件通过和硬件、操作系统紧密结合从而表现出卓越的性能,换句话说,在普通PC上的DB2和CICS等产品仅仅是提供给开发者便于学习和开发而已,脱离了IBM的专用硬件,这些软件的表现明显欠佳。
通过S390的例子可以看出,在专用领域中,“海量事务高速处理”需要按实际的需求“定制”,目前以及未来相当长的一段时期间不会出现通用的“海量事务高速处理”的解决方案,更不会出现相应的通用软件。当然一个现实的问题是,在很多领域中,并不会都像金融业那么有钱来支撑硬件的研发,比如说电子商务领域,既不能像金融业那样来支撑硬件的研发,同时业务量又相当于一些小规模的银行。此时就需要考虑如何在通用硬件上,借助于“定制”操作系统、数据库、中间件、应用程序来解决问题。
浅淡事务的隔离级别与锁
前面谈到了解决“海量事务高速处理”需要定制,那么接下来就需要考虑这个定制过程的核心问题是什么?要“海量高速”,就要能并行处理,而且是多台计算机并行处理,此时与事务的ACID属性关系最密切的就是隔离性。也就是说当多事务同时进行时,两个事务之间的影响情况,一般来说隔离性需要处理三个问题:脏读、不可重复读、幻读。
- 脏读:一事务对数据进行了增删改,但未提交,有可能回滚,另一事务却读取了未提交的数据
- 不可重复读:一事务对数据进行了更新或删除操作,另一事务两次查询的数据不一致
- 幻读:一事务对数据进行了新增操作,另一事务两次查询的数据不一致
以预订车票为例,脏读就是当一个预订车票的操作中,标记了某个座位已预定,但是没有为该身份证预订一张车票时,其他预订车票的操作读取该座位的状态时,不会立刻获得该座位已预定状态,而是当第一个预订车票的操作完成后,获得该座位已预定状态,或者当第一个预订车票的操作回滚后,获得该座位未预定的状态。读者可以参照不可重复读的解释构照相应的场景。
一般来说事务都需要禁止脏读和不可重复读。而对于幻读就需要依赖于实际的需求展开分析,以预订车票为例,当你查询余额的时候,看到车票有余额,但是进入预订的时候,发现没有余额,如果把你的整个操作视同一个事务的话,这就是幻读,当然一般对于用户来讲是不禁止幻读的,但是在交易系统内部则常常要禁止幻读。
在现代的数据库系统中,一般可以为事务直接设置隔离级别,以期避免相应的问题。最高的隔离级别就是串行化,也就是相当于“一个一个执行”。在单机测试中,串行化的性能低至禁止不可重复读但允许幻读时的30分之一,这也就是禁止幻读的代价。对于多台计算机而言“一个一个执行”相当于单机的效果,没能体现多台计算机的计算能力,是不可接受的,此时就需要采取其他的办法。目前主要有两个策略来解决这个问题。
以预订车票为例,串行化就是指所有的预订车票操作一个一个处理,看上去这个方式很笨拙,其实在现实生活中的排队就是典型的串行化。在现实生活中,经常会有通过预分配资源的方式,来创建多个排队来提升速度,例如在火车票还是一个小硬纸片的时代,火车站的售票大厅中每个窗口销售不同车次的火车票,这就是预分配资源,买票需要先按照车次选择窗口,然后排队购买。有过经历的读者马上就会发现,虽然预分配资源可以通过排多个队来提升效率,但是如果预分配的方案不合理,则会造成一个队排得很长,而另一个队没有人的情况。进一步的,预分配资源的方案无法避免一个人排了一个队之后,再排一遍,或者排另一个队。
一种是通过建立“快照”的方式,在需要避免“幻读”时,就创建一个快照,此后这个事务中的所有读取的操作都是针对于这个快照进行操作,因此就不会有“幻读”发生,当然在数据库中实现快照的方式并不是真的复制一遍数据库,而是通过记录数据的时间戳的方式来实现,对于单机来说,需要额外的存储和计算来记录并处理时间戳,对于多机来说,保持时间戳的同步也需要相当的代价,因此一般仅用于特定的场合。
通行的设置隔离级别的方式是加锁,锁的情况有很多种,而且不同的数据库的产品中的实现方式也不尽相同,在此不展开讲解,一般分成三种情况,数据库按照语句加锁,带有加锁命令的语句,在声明事务的同时声明隔离级别。需要注意是锁会导致死锁。此时应用中两个或多个事务发生永久阻塞(等待),每个事务都在等待其他事务占用并阻塞了的资源。例如,如果事务A锁住了数据1并等待数据2,而事务B锁住了数据2并等待数据1,这样两个数据就发生了死锁现象。
以预订车票为例,当查询该身份证是否已预订过相同乘车日期相同车次的车票时,就相当于给这个身份证加上了锁,直到预订车票的操作成功或者失败才解锁,在此期间其他查询该身份证是否已预订过相同乘车日期相同车次的车票的操作都被阻塞(等待)。当查询该车次是否有空余座位时,也就相当于给这个车次加上了锁,直到预订车票的操作成功或者失败才解锁,在此期间,其他查询该车次是否有空余座位的操作都被阻塞。
当发生死锁后,就需要解除死锁状态,否则更多事务在请求处于死锁状态的数据时,就会发生连锁反应,解除死锁状态的主要方式是选择回滚其中的某一个事务。解除死锁毕竟是一种消极的处理方式,积级的处理方式应当是预防死锁、避免死锁,同时辅助以检测死锁避免出现连锁反应。在现代的数据库系统中,一般都提供各种加锁的方式以及比较智能的处理锁。对于支持多台计算机部署的场景中,也会把锁延伸至多台计算机中,也就是说,一台计算机上的数据库加锁的时候,同时会影响到其他计算机上的数据库,从而保证在多台计算机上配置成一套数据库系统时,运行在不同计算机上的事务的隔离级别和运行在单台计算机上的隔离级别表现一致。
第二部分
电子商务中事务的核心算法问题
在电子商务的场景中,因为涉及到财务(钱)的操作,就需要保证事务的隔离级别足够高不会导致出错。在实际需求中,无论是钱的余额,还是待售商品的存量,都会涉及到在一个表中查询记录并求和这样的操作。如果应用将一个操作直接请求到数据库时,为避免在并发操作时出现两次查询的数据不一致的情况,就需要隔离级别中禁止幻读。而隔离级别禁止幻读时在多台计算机上的的性能严重下降,难以满足要求。这种现象说明不适合借助于数据库本身的锁机制来实现电子商务中“海量事务高速处理”的请求(对于数据库本身支持一定的中间件特性的情况按照中间件来讨论)。
既然应用直接访问数据库不适合“海量事务高速处理”的场景,就有必要在中间增加中间件,把事务的协调处理交给中间件处理,由于计算量大,这个中间件需要部署在多台计算机上,共同构成一个分布式事务中间件。目前市场上有很多支持分布式事务的中间件容器,例如EJB,可以通过简单的配置和编程就可以获得所需要的事务隔离级别。但是在电子商务的实际场景中,如果使用容器提供的分布式事务将会遭遇和在数据库中直接实现时相同的困境,如果不依赖于容器本身的分布式事务,而是按照需求自行管理锁的话,容器本身不仅没有起到积极的作用,而且因为引入了额外的处理过程而大大降低性能,因此目前世界上上处理速度最高的海量事务处理系统中,基本上都是自行开发。
自行开发分布式事务中间件,可以按照实际需求最大程度的优化,自行开发也不意味着没有规律可循,目前在“海量事务高速处理”中自行开发分布式事务大多基于Paxos算法。 http://zh.wikipedia.org/zh-cn/Paxos%E7%AE%97%E6%B3%95 Paxos算法的基本内容可以参考维基百科上的介绍。本文不重复理论知识,仅探讨一些在实践中可能涉及到的问题。对于没有接触过Paxos算法的朋友,首先要理解Paxos是一个怎么样的算法,Paxos算法并不像JPEG、PNG、MPEG等等算法,Paxos算法提供了一个为分布式系统如何就某个值(决议)达成一致的模型。Paxos算法证明了一个符合Paxos模型的实现必然能保证分布式一致性,不需要针对于每次的具体实现再次证明,当然,如果有时间和精力的情况下,证明一次Paxos算法有助于加深对于该算法的理解。其次要知道Paxos算法是目前解决分布式系统一致性算法中最有效的算法。
刚才提到Paxos算法解决的是分布式系统一致性,而实际场景中从分布式事务到分布式系统一致性还需要按照具体的情况拆分事务。这个拆分过程需要反复论证,任何一个环节的设计缺陷都将导致整个分布式系统一致性失效。如果您刚才已经看到了Paxos算法的简要介绍,您可能会注意到两个细节,在Paxos算法中值是保存在某一个分布式节点上,每个节点中保存的值允许是不同的,因此对于获取值这样的操作也是针对于某个节点的,放在实际的计算机环境中也就意味着,数据库被绑定在中间件上,其他的应用和中间件都从相关的中间件上获取数据,而不是访问数据库。第二个细节就是提案依赖于编号,因此在实际的计算机环境中需要保证一个能满足海量事务的编号机制。
安全问题与优化思想
除了算法问题外,还需要特别关注安全问题,“海量事务高速处理”的安全问题主要是在系统负荷暴增的情况下,或者有若干台服务器宕机时,如何保证整个系统只是慢下来,而不是崩溃。一个针对类似问题的研究曾经引起美国政界的轩然大波,一位来自中国的留学生发表了一篇《如何对美国电网的缺陷进行梯级式攻击》的论文,其核心思想就是如何用最少的代价引起一个局部系统故障,而局部系统的故障可能导致更大范围内的系统故障。在分布式系统中普遍存在这个问题,而对于基于事务的“海量事务高速处理”则更应重视这个问题。再举一个计算机的例子,Linux操作系统有一个Load值,这个值代表着一段时间内平均需要处理的进程数,保持计算机能及时处理进程的话,Load值应为总内核数的70%,而Load值超过总内核数的100%的话,计算机的性能不会获得提升,相反因为CPU需要花费更多的时间在进程调度上。
以预定车票为例,如果一个系统每秒种能处理一千次交易的话,那么一般来说,如果有700次交易随机分布在这一秒钟提交到系统的话,有一半以上的交易提交到系统的时候,系统正处于空闲状态,可以立刻处理。如果有1000次交易随机分布在这一秒种提交到系统的话,那么所有的交易都要先等待之前的交易完成后才能被系统处理。从系统的角度来说,不仅需要在处理交易的同时,阻塞后续的交易,同时还需要在完成一个交易后,选择一个没有和目前正在处理的其他交易有冲突的交易,这种判断自然要比系统空闲时不需要判断麻烦得多。如果负荷继续攀升的话,系统需要不断的阻塞后续的交易,反而减慢了正在处理的交易。目前12306.cn网站可以简单按照这种思路来理解。
刚才提到了在“海量事务高速处理”系统中,数据库已经被融入了中间件中。而中间件的设计基于Paxos算法,与传统意义上基于中间件容器的设计不同,一些在中间件容器的模型中放在中间件的功能在“海量事务高速处理”系统中可能放在应用中实现,也有可能正好相反,一些在中间件容器的模型中放在应用中的功能在“海量事务高速处理”系统中可能放在中间件中实现。更进一步,操作系统也需要按照实际的应用和中间件调优。这意味着在“海量事务高速处理”系统中,从应用到中间件到数据存储到操作系统都是一体化考虑的,唯有如此,才能达到高速。
形象的说,“海量事务高速处理”系统最后往往分不出哪一块是应用,哪一块是数据库。换言之,如果能像通用系统一有明显的应用、数据库、操作系统、中间件的界限的话,其实就失败了,因为没有优化到极致,用武侠小说的话讲以无招胜有招,通用系统就是“有招”,而“海量事务高速处理”系统必须做到“无招”,当然了,也可以称之为“屠龙之技”,因为国内应用场景很少,在国外也主要是大型企业可能涉足这个领域。
不必重蹈覆辙
既然整个系统是一体化定制,就必然会导致牵一发动全身,在实际环境中,特别是互联网瞬息万变,需求不可能一成不变,这时在需求环节中就需要精准分析需求,同时在前期要考虑前面提到的算法问题和安全问题。因为在整个后面的过程中,核心需求是定死的,不可能有任何变化的,而且部署和运维也纳入到研发中的全部定制。这么多因素一起考虑,要定制中间件、操作系统和数据库,可想而知开发难度有多大了。
在实践中,特别是在没有遇到瓶颈的时候,一个通用系统的解决方案,比如说Linux + Oracle + J2EE + Tomcat 更容易获得认可。而在遭遇瓶颈后,因为整个系统的架构已经定型,全面提升为“海量事务高速处理”系统不仅有研发的投入,还有数据遗留资产的升级等等问题。最后往往采用牺牲局部的方式不停的打补丁来解决。这也是目前国内很少见到“海量事务高速处理”系统的原因。毫不夸张的说,国内多数发展迅猛的电子商务企业都正在面临这样的困境,可以预见是否有决心定制“海量事务高速处理”系统将成为这些企业能否胜出的决定因素。
EJB方案之所以不适合,是因为所用的Oracle + J2EE + Tomcat都是通用的工具,里面有大量的为了符合通用标准而具有的模块,这些模块虽然让这些工具可以适合在多种场景下应用,但在特定场景下却成了影响系统性能的垃圾,当这些“垃圾”需要清除的时候,目前的软件公司基本上束手无措。
当然,不采用EJB方案不代表没有中间件,前面提到的Paxos算法,在实践中也是以中间件的形式体现,当然,在这种场景中的定制开发的中间件已经和通用方案中借助于中间件容器实现的中间件已经大不相同,但是有一点要值得注意,所有的“写”操作,都是借助于中间件来完成的,而不是借助于数据库中的事务操作。
从大量的经验教训来看,对于已经预见到“海量”的电子商务网站,不要采用EJB等通用中间件容器方案;而对于有着“海量”发展预期,但是目前还没有达到“海量”的电子商务网站,核心业务的所有的“写”操作要借助于中间件而不是数据库。通常“海量事务高速处理系统”造价昂贵,应用范围不广,除了电子商务公司自行研发的系统外,目前涉足该领域的研究机构也非常少,清华大学web与软件技术研究中心是中国极少的研究此技术的权威机构,有宝贵的成功经验。
第三部分
“海量事务高速处理”的演进道路
前面讲了一些经验教训,在实践中很少有像12306.cn这样的可以预见到“海量”的电子商务网站,更多的有着“海量”发展预期,但是目前还没有达到“海量”的电子商务网站,这些电子商务网站一方面受客观因素制约不可能一步到位设计“海量事务高速处理”系统,另一方面发展期间同时也是业务探索期间,也不太可能明确哪一块业务将发展成为“海量”。此时就需要在实际建设之前规划一个演讲的道路,避免陷入误区。下面讲一下“海量事务高速处理”的演进道路。
首先要特别重视需求,既然核心系统将是全部定制开发,因此需求变更如果影响到了核心系统的话,那么代价将是非常昂贵的。所以要尽可能的精确调研需求,避免模糊需求影响到核心系统,下图就是广为流传的由于需求理解的偏差,导致最终结果的严重偏差。
如果定性的话,曾经有一个研究,一个缺陷如果在需求阶段修正要花1美元的话,那么在设计与撰写阶段要花2美元,如果是在完成后修正的话,平均会花费69美元,如下图所示。这还是针对项目而言。由于互联网网站的升级必须要继承所有的数据遗留资产,因此互联网网站修正的代价将更加高昂,而对于处于核心的“海量事务高速处理”系统来说,严重缺陷的修正代价可能是不可想象的。
电子商务网站产品设计基础
既然需求对于电子商务网站是如此重要,那么该如何精确的分析需求并避免错误呢,一般来说,可以将一个网站分成如下图所示的七种产品型态。
这七种产品形态可以用如下的经验方式界定。
对于基本的电子商务网站来说,主要包含CMS、MIS、OA、ERP这四种产品形态,这四种产品形态在电子商务网站中的常见体现形式如下图所示。
从图中可以看出,核心交易系统的需求是ERP中的一部分。下面继续分析ERP产品形态。一般来说ERP系统有三个层面——Data、Process、Action,大多数的ERP系统实现了Data和Process二个层面,Action目前尚处于探索阶段。如果用一种简单的方式理解的话,Data就是与核心交易系统的MIS,而OA就是与核心交易系统的OA,如下图所示。
通过上面的介绍,读者可以看到,电子商务网站产品设计的核心之处在于如何分析出核心交易系统的需求,换句话说,如果在产品设计阶段就不重视核心交易系统的话,那么在未来也将投入巨大的成本在修正缺陷上。
初期核心交易系统程序设计示例
前面提到,为了长期的发展,从一开始所有的核心交易的“写”操作都应当通过一个交易中间件,从架构模式上来看,属于Blackboard“黑板”模式。在实践中,一开始由于需求变化频繁,在没有特殊要求的场景中,“读”操作可以直接读取数据库,为了缓解压力,也可以借助于数据库的复制功能,下图就是一个核心交易系统中间件的部署示意图。
由于需求随着发展将不断变化,因此一开始很难有清晰核心交易系统的边界,所以在程序设计上采用接口设计方式,设计接口需要遵循三个标准:
- 按照不同来源划分接口
- 接口都体现价值点。在本例中,用户的价值点除了拥有虚拟货币外,还拥有“票”(具体业务相关,不展开讨论),因此每一个接口或者和价值点有关,或者和待售物品有关
- 接口应当具有明确的业务意义
接口设计示例如下图所示。
交易系统的外部接口在处了了一些业务逻辑后,最终都将调用核心交易系统的处理过程,这个过程应当遵循如下的3个标准:
- 初期可以采用“一个一个处理”的串行化方式将涉及到同一个价值点的多个处理过程排队,以保证ACID,例如在Java中可以直接使用Synchronized关键字
- 每个处理过程应尽量为更多的接口服务,也就是说尽量把外部接口转换成最少的核心交易处理过程
- 每个处理过程应涉及两个价值点,例如虚拟货币和物品,最多不超过3个
核心交易系统处理过程的序列图示例如下图所示。
在实际的生产环境中,该系统的日最高曾经处理约10万笔交易。当然,这个数值和具体的项目需求密切相关,随着复杂程度的提高,处理能力可能会直线下降,既便如此,也可以满足起步电子商务网站的需求了。从公开的消息可以看到一些参考数据,在2011年11月11日的“双十一”促销期间,京东商城日订单量超过了40万单。
以预订车票为例,核心交易系统要处理的就是身份证、指定日期的指定车次这样一个交易。连钱都不需要处理,因为钱是在预订成功之后支付的。通过本例可以看出,传统的中间件容器方案中,为了避免负荷被中间件容器放大,一般都会仅把核心业务放在中间件容器中,而在自行研发交易中间件时,所有程序按需开发,同时为了应对未来必将出现的需求变更,将一部分需求在交易中间件中实现,也就是模糊了应用和中间件的界限。
发展期核心交易系统程序设计探讨
当电子商务网站继续发展,上面讲到的单机的核心交易系统已经无法满足需求时,就需要进一步提升核心交易系统的处理能力。一般有下面两种策略,一种是资源预分配、另一种是离线锁。
前面提到了资源预分配,也讲到了资源预分配的缺点,但是资源预分配仍是一个非常有效的提升核心交易系统处理能力的方法。以预订车票为例,既然火车的座位数都是可以预知的,那么完全可以预先为每一天的每一个车次的每一个座位准备好空位,就差填写身份证了。如果一台计算机无法处理所有的交易,可以按照每一天的每一个车次来分配在不同的计算机上,这样针对于特定日期特定车次的交易就可以定位在特定的计算机上,而该计算机只需要保证在本机上事务成功提交即可,因为按照业务逻辑,这种预分配资源的方式可以保证计算机之间不会冲突。
针对于春运期间火车票的特殊场境,资源预分配可以进一步优化,比如说对于当天有票释放出的车次,因为访问量巨大,所以尽量拆分开分配分配到独立的计算机上处理,例如每种类型的车次一个计算机集群,对于昨天有票释放出的车次,在春运期间可以认为基本上已经满了,可以把几种类型的车次合并为一个计算机集群,而对于更早释放出的车票,可以认为只剩下退票的情况了,可以把所有类型的车次合并为一个计算机集群,示意图如下所示,当然实际情况要复杂得多。
资源预分配策略可以在一定时期内有效缓解核心交易系统的压力。对于某些负荷变化大的应用,也可以采用资源预分配的策略,例如团购,每一件商品的数量是有限的,很有可能被瞬间抢购一空,此时就应该预先为每一件商品分配空位。类似的,对于秒杀来说,商品数量很少,系统瞬间负荷很大,可以为每一件商品分配一台计算机处理。当然实际情况要复杂得多。
除了资源预分配之外,还有一种处理策略是离线锁,在架构模式中,离线锁包含了乐观离线锁和悲观离线锁两种情况。离线锁设计的初衷是用来解决当客户端长期操作一个数据而锁定数据的时候,服务器端无法同时处理大量的在线锁的场景。在核心交易系统的场景中,一台响应用户请求的计算机替代了客户端的角色,每个需要保证事务的数据(通常是一组数据)对应一个锁记录,在每次请求的过程中,先在锁记录上通过一个原子操作将状态从未锁定状态置为锁定状态,然后操作数据(通常是一组数据),在操作完成后再在锁记录上通过一个原子操作将锁定状态置为未锁定状态。这就是典型悲观离线锁的使用方式。如果把锁记录独立为一个服务的话,就称为锁服务。
在实际场景中,一般是按照资源预分配策略来划分离线锁中锁定的数据粒度,比如说在电子商务网站中,一个用户所持有的所有价值点对应于一个锁,这些价值点包括他的账户、各种优惠券等等,某一个品种的普通商品对应于一个锁,当然也要考虑到前面提到的每一件商品对应于一个锁的情况。而且一般一个事务会锁定二个到三个数据,例如在一次消费过程中,先锁定用户,锁定商品,然后操作用户、操作商品,最后释放用户锁、释放商品锁。如果在操作过程中发现异常,则在释放锁之前恢复数据,保证一致性。
“海量事务高速处理”期核心交易系统分析探讨
当系统演讲到海量事务高速处理阶段,如前面所述,整个交易系统是完全按照需求定制的,下面仅就一些共性的问题做简要的探讨。主要涉及三方面的问题,系统的可用性,锁的粒度,以及事务的划分。
首先看系统的可用性,任何系统随着规模的增长,设备故障的频率也将越来越高,此时就需要有相应的容灾策略。需要消除单点、甚至于单集群故障。在前面提到的锁服务也就不能采用通用的数据库来设计。目前流行的做法是采用基于Paxos算法的锁服务,通过五台计算机(最少是三台,一般是奇数个,考虑容灾的需要一般用五台,更多的计算机会造成之间通讯量的快速增长)组成一个针对某类资源的锁服务集群。在操作某一项资源时,先在锁服务上对这一项加锁,操作之后解锁。这和悲观离线锁机制相同,当然在实际操作中,因为还要消除保存资源数据的计算机的单点故障,加锁和解锁的过程要和所有数据的一致性保持同步。
既然锁是以锁服务的形式存在,那么如何划分锁的粒度就成为效率的关键,锁的粒度太细的话,一个事务中操作锁的次数就太多了,锁的粒度太粗的话,碰撞的可能性就大得多。另外,既然锁已经独立于数据,因此一组锁服务可以为几类数据量不大的数据服务,对于数据量很大的数据可以分成几组锁服务。
锁的粒度划分又和事务紧密联系在一起,一般来说,一个事务中持有的锁越多,碰撞的机会就越多,而碰撞的结果就是回滚。从定性的分析来看,可以用N^2来评价,比如说一个事务中持有两个锁,那么碰撞的机会就是4,持有三个锁的话,碰撞的机会就是9。也就是说,如果把一个需要持有三个锁的事务划分成两个持有两个锁的阶段的话,碰撞的机会就是2*4=8<9。从这个简单的定性分析可以看出,事务应以2个锁为主,不超过3个锁。4个锁以上的事务一定要分解成多个小事务。当然经过分解之后,复杂程度也随之提升,但是因为减少了碰撞,系统的总体性能获得了提升。也就在另外的一些环节中减少了系统的复杂度。
系统的可用性、锁的粒度以及事务的划分这三个问题既相互独立,又相互联系。对于核心交易系统来说,合理的事务划分可以提升性能,从而减少计算机的数量,间接提升了可用性。
另外一些误区
在前面提到了采用类似于EJB的中间件容器或者将事务交给数据库处理是两个主要的误区,下面谈一谈其他一些认识上的误区。
加强CDN建设能提升核心交易系统性能。答案是没有直接联系,对于电子商务网站来说,CDN的价植更多体现在CMS产品上,也就是货架陈列产品上,对于涉及到事务的MIS、OA、ERP来说,不可能使用CDN的缓存,最多使用CDN的路由加速功能。这些对于核心交易系统没有直接联系。
将核心交易系统建设成开放平台有助于简化核心交易系统,提升交易性能。答案正好相反。内部系统纵然有种种弊端,但是对于任何一个细节都是可控的,而对于开放平台来说,难以控制外部的操作,为了应外可能出现的负荷变化可能要做更多的准备。换句说话,在内部系统还没有完善的时候,开放平台不仅没有帮助,反而可能放大现在的缺陷引发连锁崩溃。
对于其他的误区欢迎各位联系,我将继续补充。
作者简介
作者胡争辉,CSDN博客专家,CTO俱乐部成员。曾任完美时空(现更名为完美世界)顾问,承担互联网方面的部分管理工作。现在主要精力研究互联网产品设计,是Axure授权的高级咨询顾问和高级培训讲师。
个人博客:http://blog.csdn.net/hu_zhenghui
个人网站: http://bbs.hpx-party.org/
个人邮箱:hu@hpx-party.org huzhenghui@139.com
新浪微博:@胡争辉
QQ:443089607
GMail& GTalk: huzhengh@gmail.com
Fetion:293089651
声明
本文不是专业的学术论文,重点为介绍相关背景知识,写作过程有失严谨之处敬请同行斧正。如果各位觉得有必要继续深入探讨,我将继续写点东西和各位探讨财务系统的分析设计。
版权所有,未经允许,禁止任何形式的转载、引用、摘要。首发地址: http://bbs.hpx-party.org/thread-8488-1-1.html 。如果需要请联系作者。