不用锁表,没有异常:在高并发网络中高效的更新数据库数据的方式
很多Web系统的瓶颈在网络IO,所以很多系统都采用多Web服务器负载均衡,双DB做双机热备(其实就是只有一个DB,两台只有一台真正工作,死掉一台另一台顶上)的方式部署,在这个时候很多原本不是问题的系统也会产生很多的问题。
这里我们假设有表Product,其定义如下:
列明 |
类型 |
说明 |
Id |
Int |
自增字段,实例的ID |
ProductName |
Varchar(100) |
商品的名称 |
StoreCount |
int |
库存数量 |
。。。 |
。。。 |
。。。 |
假设很不凑巧的,3个管理员P1,P2,P3同时操作了这个表,且P1 update StoreCount=50,P2 update StoreCount=49,P3 update StoreCount=48。这个时候问题就来了,如果是让他们都同时提交进去,当然没问题,但是如果这个时候N个Web程序在读的时候就会产生每台服务器上读出来的数据都可能不一样,A服务器读出来是48,B服务器读出来是50,C服务器读出来是49。
如果我们采用数据库锁可以避免这个问题,但是随之而来的是系统效率降低和无可避免的异常,而hibernate等实现的乐观锁呢,呵呵,对不起了,在多Web服务器的时候还能起作用吗?
由此产生了以下的解决方案:
和乐观锁的实现相反,我们不反对任何一个客户端的提交,乐观锁对读取的数据增加版本号,那么这个解决方案中对提交的数据增加“版本号”其实也就是时间戳。针对上面的Product表作为例子,为了实现无锁的提交,我们需要增加一个表Product_Dirty,以后我们将称其为脏表,Product表我们称之为主表。脏表的结构和主表几乎完全一致,只是增加了一个时间戳字段用于记录详细的插入时间:
列明 |
类型 |
说明 |
Timespan |
Int |
时间戳,精确到毫秒(能到纳秒更好) |
Id |
Int |
实例的ID(这里就不是自增字段了) |
ProductName |
Varchar(100) |
商品的名称 |
StoreCount |
int |
库存数量 |
。。。 |
。。。 |
。。。 |
在发生任何update的时候都将数据直接插入这个表,不要犹疑,没锁,所以可以快速的,尽情的插入数据。这里还是保持最初的假设,P1,P2,P3同时修改,所以插入了三条数据。所谓的同时插入其实在毫秒这个级别还是有差距的,所以三条记录的时间戳是不同的。好了这个时候数据进来了,但是主表的列数据还是没有改变,先在假设A服务器和B,C服务器都同时开始读数据了。在主表的时候,如果发现脏表有数据则表明主表数据为脏(已经修改过了)这个时候我们就开始合并数据,当然这个操作是需要在一个事务里实现。合并的操作其实很简单,就是取时间戳最大的(也就是最近一次修改)更新主表的数据,同时删掉脏表里的和主表ID相等的所有数据。如果发现主表关联的脏表没数据,那么就说明主表数据正常,就直接读取主表的内容。
此解决方案来自电信营帐系统的设计,由于省电 信众多系统都是由分布很广的地市州电信业务人员操作,所以修改的时候经常存在本文要解决的问题,由于操作的人多,锁表的话会造成严重的拥塞,故产生了这个 解决方案,由于电信的业务需要后台跑了一个服务来合并数据,并且每秒定时运行,故每秒为一个业务周期。我将其修改成在读取的时候合并,更加灵活一些。
好处:不用锁表,乐观锁也不用,可以在N多服务器操作的时候使用,且大家都不会报错,简化了异常处理。
坏处:增加了表,结构复杂,如果是用于修改原有业务如果只是几个关键表的话还好,全部都采用这个方式工作量巨大(好在电信不缺钱)。
弱点:和乐观锁类似,在某些场景下仍然可能脏读,所以如果对这方面有很高的要求,还是用悲观锁吧。
posted on 2012-02-04 00:25 cn三少<script></script> 阅读(451) 评论(0) 编辑 收藏 举报