最近在学习MongoDB的过程中,接触到一个新名词——幂等性。
先来看一下度娘的解释。
幂等
幂等(idempotent、idempotence)是一个数学与计算机学概念,常见于抽象代数中。
在编程中,一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。
幂等函数,或幂等方法,是指可以使用相同参数重复执行,并能获得相同结果的函数。这些函数不会影响系统状态,也不用担心重复执行会对系统造成改变。例如,“setTrue()”函数就是一个幂等函数,无论多次执行,其结果都是一样的.更复杂的操作幂等保证是利用唯一交易号(流水号)实现。
看懂了吗?这个解释比较抽象,不要着急。接下来看一下什么是幂等性问题。
幂等性问题
所谓幂等,简单地说,就是对接口的多次调用所产生的结果和调用一次是一致的。扩展一下,这里的接口,可以理解为对外发布的HTTP接口或者其他接口,也可以是接收消息的内部接口,甚至是一个内部方法或操作。
那么我们为什么需要接口具有幂等性呢?设想一下以下情形:
-
在App中下订单的时候,点击确认之后,没反应,就又点击了几次。在这种情况下,如果无法保证该接口的幂等性,那么将会出现重复下单问题。
-
在接收消息的时候,消息推送重复。如果处理消息的接口无法保证幂等,那么重复消费消息产生的影响可能会非常大。
在分布式环境中,网络环境更加复杂,因前端操作抖动、网络故障、消息重复、响应速度慢等原因,对接口的重复调用概率会比集中式环境下更大,尤其是重复消息在分布式环境中很难避免。
分布式环境中,有些接口是天然保证幂等性的,如查询操作。有些对数据的修改是一个常量,并且无其他记录和操作,那也可以说是具有幂等性的。其他情况下,所有涉及对数据的修改、状态的变更就都有必要防止重复性操作的发生。通过间接的实现接口的幂等性来防止重复操作所带来的影响,成为了一种有效的解决方案。
看了上面App的例子好理解多了。那幂等性在并发系统中如何保证呢?
高并发系统中如何保证数据幂等性
在系统开发过程中,经常遇到数据重复插入、重复更新、消息重发发送等等问题,因为应用系统的复杂逻辑以及网络交互存在的不确定性,会导致这一重复现象,但是有些逻辑是需要有幂等特性的,否则造成的后果会比较严重,例如订单重复创建,这时候带来的问题可是非同一般啊。
一、系统的幂等性
幂等是数据中得一个概念,表示N次变换和1次变换的结果相同。
二、高并发的系统如何保证幂等性
1、查询
查询的API,可以说是天然的幂等性,因为你查询一次和查询两次,对于系统来讲,没有任何数据的变更,所以,查询一次和查询多次一样的;
2、MVCC方案
多版本并发控制,update with condition更新带条件,这也是在系统设计的时候,合理的选择乐观锁,通过version或者其他条件,来做乐观锁,这样保证更新及时在并发的情况下,也不会有太大的问题。
例如update table_xxx set name=#name#,version=version+1 where version=#version# ,或者是 update table_xxx set quality=quality-#subQuality# where quality-#subQuality# >= 0
3、单独的去重表
如果涉及到的去重的地方特别多,例如ERP系统中有各种各样的业务单据,每一种业务单据都需要去重,这时候,可以单独搞一张去重表,在插入数据的时候,插入去重表,利用数据库的唯一索引特性,保证唯一的逻辑;
4、分布式锁
还是拿插入数据的例子,如果是分布是系统,构建唯一索引比较困难,例如唯一性的字段没法确定,这时候可以引入分布式锁,通过第三方的系统,在业务系统插入数据或者更新数据,获取分布式锁,然后做操作,之后释放锁,这样其实是把多线程并发的锁的思路,引入多多个系统,也就是分布式系统中得解决思路;
5、删除数据
删除数据,仅仅第一次删除是真正的操作数据,第二次甚至第三次删除,直接返回成功,这样保证了幂等;
6、插入数据的唯一索引
插入数据的唯一性,可以通过业务主键来进行约束,例如一个特定的业务场景,三个字段肯定确定唯一性,那么,可以在数据库表添加唯一索引来进行标示。
这里有一个场景,API层面的幂等,例如提交数据,如何控制重复提交,这里可以在提交数据的form表单或者客户端软件,增加一个唯一标示,然后服务端,根据这个UUID来进行去重,这样就能比较好的做到API层面的唯一标示
看到这里,你也许对幂等性已经有了深入的理解。最后,我们看一下MongoDB中的幂等性。
MongoDB中的幂等性
MongoDB是由C++语言所编写的一种面向文档的非关系型数据库(是一种NoSql数据库实现),也是介于关系型数据库和非关系型数据库之间的数据存储产品,其提供了高性能、高可用、高可拓展及基于分布式存储的数据库,是非关系型数据库中功能最丰富,最类似关系型数据库的一种集合、文档格式的数据库。
在MongoDB中,文档的操作不支持事务,但是其对文档的保存,修改及删除等都是原子的,也就是要么操作完成,要么操作不完成,不存在中间不确定状态,因此可以保证数据的完整性。
MongoDB官方提供了一种做法,就是分两阶段提交,基本原理就是利用写操作的幂等性,但是有个前提条件:业务实现过程中,可以忽略中间态的不一致性,但最终结果是一致的,实际上绝大多数需求,只要结果一致就可,也希望MongoDB在这方面做更好的拓展和优化。
1、数据模型
举个例子,不过我们需要为文档新增一个计数器字段,它没有实际的业务作用,只是用来作为操作原子性的标志位avaliable,具体代码如下:
{
"_id" :ObjectId("57e89964b316d2e13cc0ba9b"),
"username" :"marky@123.com",
"nickname" : "marky",
"address" : "云端路1024号,柯南私募基金大厦",
"contact" :"13141250012",
"created" : "2012-07-07",
"orders" : [
ObjectId("57e89b3ab316d2e13cc0ba9c"),
ObjectId("57e89bcfb316d2e13cc0ba9d")
],
"available": 1
}
注意:
此种添加一个计数器标志位的方式虽然可以实现原子性,但对于业务的紧密度及可理解方面不友好,所以实际上,我们只在比较敏感的文档操作时才使用,比如:金钱交易等。
2、如何操作
如何操作?其实就是使用findAndModify()方法实现即可,如果找到的文档的计数器available值不为-1为大于0时,则代表有新的操作状态,然后在更新新的操作,同时同步修改available为默认值,具体如下:
>db.user.findAndModify({query:{_id:ObjectId("57e89964b316d2e13cc0ba9b"), available:{$gt:0}},update:{$inc:{ available:-1},$set:{created:'2012-07-08'}}})
返回的结果:
{
"_id" : ObjectId("57e89964b316d2e13cc0ba9b"),
"username" :"marky@123.com",
"nickname" : "marky",
"address" : "云端路1024号,柯南私募基金大厦",
"contact" :"13141250012",
"created" :"2012-07-08",
"orders" : [
ObjectId("57e89b3ab316d2e13cc0ba9c"),
ObjectId("57e89bcfb316d2e13cc0ba9d")
],
" available" : 0
}
~~~~~~~ the end~~~~~~~~~
hoegh
2017.04.05