故事背景:Alice和Joe将共同读取Couchbase Server中的同一个数据,然后都将对数据做出修改;接着将新的版本写入数据库。那么谁的修改将被保存?Alice还是Jone?又或是都不?还是结合了两者的。
开发者对有连续访问的共享数据使用锁。那么究竟该选择什么样的锁方案 —— 乐观或者悲观?
在看Don Pinto带来的乐观锁与悲观锁的区别以及选用建议之前,先简单介绍一下他的履历:Don Pinto,现任Couchbase产品市场经理;曾担任微软SQL Server和SQL Azure项目经理;更早阶段还曾任职于IBM(DB2 LUW软件工程师)、Honeywell(软件开发者)等公司。下面先看看乐观锁与悲观锁的区别:
乐观锁
如果我们需要建立一个在线的百科 —— 使用Couchbase Server的应用程序:用户可以修改和添加文章。假设一下Alice正在使用应用程序对“bicycles”上的一篇文章进行编辑(修改一些信息),但是在保存前Alice忽然想起一些其它的事情并离开了电脑;就在这个时候Joe也注意到了“bicycles”文章上的相同错误并且准备纠正。
如果应用程序中使用的是乐观锁,Joe就可以编辑文章并保存修改。当Alice返回并准备保存修改的时候,那么不管是Alice或者是应用程序都希望知道文档的最新状态 —— 在Alice修改文档的行动得到允许前。乐观锁的出发点在于该数据很少会因为并发修改而产生冲突,所以并发修改显得更重要一点。
悲观锁
这里不防设想一下你的业务流程需要互斥存取一个或多个文档(又或是一个graph中的文档)。参照上面的例子:当Alice正在编辑文档的时,她不想其他的用户对相同的文档进行编辑。如果这时Joe再去做同样的事情,那么他必须等待直到Alice释放锁。
通过使用悲观锁,应用程序可以实现对文档的单独占有。当用户完成文档的访问后,可以手动或者设置超时来释放锁。
那么究竟该选用什么类型的锁?这里是没有确切的答案的,因为使用什么样的锁该由具体情况决定。你需要根据你应用程序的需求选择相应类型的锁。
除非你认为一个文档会存在重度的竞争,乐观锁的开销要远低于悲观锁—— 夺取你需要的,迅速做出修改并保存。如果被系统中其他人抢夺,你只能继续尝试直到成功。
使用了悲观锁,你可以对一个指定的项目进行互斥存取 —— 当它被锁定时,其它的线程都不可以访问。这里的关键在于悲观锁在操作失败后必须得到释放。想象一下:你已经获得了对象A,但获得对象B之前你不想放弃对象A;但是另一个已经获得了对象B的用户,在获得对象A之前也不想放弃对象B。那么这时候就需要用到超时设置,它可以打破死锁和处理释放锁失败的情况。
简而起见,用户通常会在整个应用程序中使用相同的锁策略。如果在整个应用程序中都有着同样的需求,这么做也无可非议。事实上,情况不是这样的 —— 不同的应用程序对象有着不同的访问需求。下面看一下社交游戏中的两个例子:
- 角色和玩家的信息在游戏期间被频繁的访问,这就需要快速的读写访问。第一选择 —— 使用乐观锁。第二选择 —— 悲观锁。
- 用户的账号和参数只在用户登录或者是游戏开始的时候才会被读入,并且不会被频繁的修改。那么乐观锁在这里同样会有很好的表现。
在Couchbase Server中获得悲观锁
- 使用get-and-lock API获得检索给定键的值,并进行锁定
- 应用现在已经拥有了对该文档的互斥控制
在Couchbase Server中获得乐观锁
这里需要使用check-and-set(CAS)API获得CAS的版本号。
释放Couchbase Server中的锁
这里需要使用CAS来修改并释放锁
解读CAS
CAS操作是一个很简单的理念用以建立更卓越的并发性控制机构。CAS会在对一个对象进行读入和尝试保存时确认这个对象是否被其他的用户更改 —— 如果对象被另一个用户更改,那么错误将会产生而应用程序必须重新读取对象的值并重试操作。如果对象不存在高度争夺,那么数据的转换将是幂等的;而重试操作也会在不浪费太多的工作下完成,一般情况下乐观锁会是一个拥有高性能的解决方案。
与CAS的交互涉及到一个GAS回路,点击查看详情。
举个例子:下面交互图中存在的3个用户。他们3个都在使用CAS尝试去更改X对象 —— 每个客户端都成功,但是一次只有一个。
你并不需要释放锁,吩咐Couchbase就好了
当你做一个get-and-lock的操作时,你提供一个值作为锁的参数。一个键默认会被锁定15秒。15秒过后,如果你还没有手动的释放锁,那么Couchbase会自动的为你释放。
最终的见解
当然不是在所有的情况下,乐观锁都是最好的解决方案。在许多用例中乐观锁可能会有很好的表现,但在其它情况下可能就会需要像悲观锁这样更严格的方案。
当然锁也不是适合所有的情况 —— 如果出现锁竞争,你的应用程序可能就会抛锚。如果一个线程已经获得了一个锁,而OS又没有对这个锁的安排;那么其他想争用这个锁的线程将会被阻塞。当然其中的一个选择就是避免完全加锁,尽量的使用原子操作。这些API对存在高度竞争的数据是很有效的。
原文虽确定了Couchbase Server这个使用背景,但是有些思想和API还是值得借鉴的。