当前位置:C++技术网 > 资讯 > 如何做好功能性事务机制,如何实现可靠功能逻辑

如何做好功能性事务机制,如何实现可靠功能逻辑

更新时间:2017-05-13 10:21:35浏览次数:1+次

    什么是事务?在回答这个问题的时候,你想到了什么?是不是想到了数据库的事务机制?
    大部分人都会想到数据库的事务机制。那么事务是什么呢?
    我们这里并不讨论数据库的事务,而仅仅谈论事务机制的实现的一些经验。就做个抛砖引玉吧。
    我先简单介绍一下事务机制是怎么回事吧。
    事务简单来说,就是做一件事,更确切的说,是干净的做一件事。要么就做到位,要么就不做。干净利落,干脆。事务就是这么一回事,没有什么晦涩难懂的。
    只不过,我们接触的最多的是数据库中的事务机制。要么全部成功,要么全部不成功。当然,所有的步骤还是得做的,只是如果其中一个失败了,那么其他的也会恢复原样,就跟没有做过一样。
    我们这里讨论的就是,如何在日常的开发中,如果将事务机制应用在代码中。事务是确保逻辑功能可靠的一个关键,不仅仅是数据库专有的概念。
    事务机制强调的是影响程度,要么全部成功影响,要么一个都不要影响。暗示了一个特点就是,实现的时候,不要产生额外的状态残留。在使用数据库的事务的时候,我们只需要开始事务-执行语句-检测执行的情况,如果没有错误就提交事务,如果失败,则回滚事务。这是事务机制的标准流程。
    那么我们自己实现事务机制,需要注意什么呢?
    你首先就会问,自己有必要实现事务机制吗?有必要的。我来举例说明吧。
    现在有这么一个需求,本地软件要读取一个文件,本次读取文件的这一动作的信息(读取文件的名称、文件大小、操作用户、读取时间、读取文件的分类等)要上传给服务器。这个需求让你来做,你如何确保功能完美运行。
    那么现在有这样一些问题,读取文件可能会失败,文件分类信息存储在数据,我们还要查询数据库,查询可能失败,查询的文件还有可能不存在或者没有权限,当文件读出来之后,这次动作我们还需要上传的服务器。上传服务器时可能没有网络,或者中途断网,或者上传出错,或者服务器故障。服务器在处理上报记录时也可能出错,比如数据库崩溃。
    我们要做到的效果就是,不管出现什么故障,遇到什么问题,要么能够读取文件并且将记录上传到了服务器。要么就不执行任何动作。最重要的是不要出现动作、数据等不一致的情况。
    在数据库的操作过程里,事务主要是确保数据一致性。然而在我们日常开发功能时,不仅仅有数据库。如果我们的文件被读取出来,但是数据没有上传成功。如果读取文件是计费的,那么就相当于免费阅读了。如果读取文件失败了,数据却上传了,扣费了,那么就相当于没有阅读就扣费了。而这一系列的动作,不仅仅有数据库的参与,包括文件的读取操作、本地的文件信息查询、数据库查询、数据上报服务器等。这一系列动作构成了一个完整的功能,就是一个事务。显然此时我们无法依赖数据库的事务了。
    那么你说,你要不要实现一个事务机制,来完成这个功能呢?对于初级开发者来说,通常并不清楚怎么回事。一些有经验的开发者也不是很在意,那么软件就可能出现一些奇怪的错误。
    那到底如何自己合理安排这些功能的执行顺序,以确保最大的成功率呢,降低回滚率呢?这是一个值得我们深思的问题。开发软件绝对不止是简单的实现某个功能而已。如果想做一个优秀的软件,还需要做很多思考。
    在实现这个功能前,我们要大概分析下功能块,分别有文件操作、查询文件信息、上传操作记录信息。一共就是3个大步骤。这三个步骤没有严格的先后关系。我们可以先将文件读取出来显示、再查询文件信息,并将这些信息上传到服务器。但是,如果文件数据被显示出来了,上传信息失败,这个会导致内容已经暴露。那我们如果先将文件信息查询好,上传到服务器,上传成功后再读取显示文件内容。但是如果此时文件读取失败,而且始终就是读取不出来,信息已经上传到了服务器,此时就尴尬了。
    这样的两个顺序都不是让人满意的。不管哪种,只要出现问题,都是棘手的。我了解过公交卡NFC充值的实现过程。贴卡读取卡号,然后查询数据库,显示余额。如果充值,是先用微信充值。充完之后,再写卡。如果写卡失败,可以尝试写多次。但是如果手机没有电了,或者写卡过程被中断了,怎么办?这个软件会根据后台的充值订单来管理,即使中断了,下次还是可以继续写卡。
    在选择执行顺序时,有一个利益偏向的问题。如果没有问题,那是一次性完成所有动作。如果理想的话,失败了会全部回滚到位。然而实际情况却不是这样的。就像上面的充值公交卡。回滚的话,那就是要退款。退款这个动作是非常麻烦的,甚至需要人工确认,无法程序自动进行回滚操作。我们这里遇到的问题是,跨系统的事务,回滚是很难做到的。所以,处理问题的顺序就至关重要。然而在处理问题的出发点上,通常是站在公司的利益的角度上的。
    先执行的动作通常是优先级高的,也就是优先保证利益的一方。充值公交卡时,如果充值失败,就不继续往后执行。如果充值成功,钱已入账,就算后续失败了,公司也不会着急。反正钱已经在自己手里,主动权在公司这里。只要公司确保后续有流程可以继续写卡就行。如果写卡的过程卡不小心被折断或者丢到河里了。那是用户自己的事情。剩下麻烦的事情就是用户自己来完成了。反正公司会有记录,可以给你解决,但是解决的过程的麻烦就在用户这边完成了。
    我们假设利益方在用户这边,那么也就是先确保充值公交卡成功,在微信支付。实际上,这种模式在很多场合也是有的,比如滴滴打车。先使用,后付费模式。利益方是在用户这一边。剩余的部分,公司会极力让用户付钱渠道更畅通,让付钱顺利进行,完成这个事务。所以,后续用户占有主动权,公司是被动的。所以此时,公司是会积极去处理这个事情的。
    公交卡充值和滴滴打车的模式,都是跨系统的非事务模式。不过整体来看,也算是事务。因为整次交易,虽然有多方参与完成,但是最终还是可以通过各种机制确保完成。如果你充值卡后不写卡,造成的损失用户自己负责,社会的金钱交易机制会确保用户完成这个交易的。当然也有可能例外。而滴滴打车,如果用户不支付,以后就再也无法打车了。这样也带来了信用的不良记录,这个社会有信用机制来迫使用户去完成这个事务。
    但是以上都不是强事务型的。而且也很难形成事务型流程,算是一个弱事务型的案例。这样的案例太多太多了。所以在取舍时,就是利益优先的考虑,然后加之各种制度保障。
    虽然是可以在一定程度上保障,但也不是绝对的。这个也是没有办法的。没有绝对可靠的设计,就如没有绝对安全的系统一样。然而在日常的开发过程中,很多局部的功能点,一个系统中的功能模块,在多个模块中形成的事务,我们还是可以尽量形成事务,确保功能的一致性。
    再回到最开始的读取文件到上传数据,这都是系统的功能。所以我们是可以实现事务机制的。此时的情况,我们要考虑的是,事务失败时的回滚代价。拿公交卡充值和滴滴打车来说,回滚代价很高的。退款是比较麻烦的,滴滴如果将你从A地送到B地,如果要回滚,那就要将你马上从B送到A,让你的状态恢复原样。但是此时也不是真正的原样了。因为时间不能倒流,时间无法回滚,人类活动很依赖时间。回滚的时候,汽车耗的油和司机耗的时间和精力都无法回滚。这样的情况是无法回滚的。
    所以从整体来看,这些案例都是统一的。只是回滚的代价太大,这些应用场景也就发生了质的变化。在纯功能性的回滚中,我们主要是依据混滚代价来确定执行的流程的。
    在实现功能型事务的机制时,我们可以参照下面几点:
    1.无法回滚优先级最高,回滚代价高的优先级次之,回滚代价低的优先级低。
    2.影响功能成功的变化因素不定,或者变化莫测的功能点,优先级高。影响因素变化少,或者不变化的,优先级低。
    3.回滚过程耗时长优先级高。
    4.回滚过程可能出错的,优先级高。
    5.回滚时容易产生状态残留的优先级高。
    6.回滚的代码实现复杂的优先级高。

    优先级高的,一般后执行。执行成功再往后继续执行其他的。如果执行不成功,则不再继续执行。一旦失败,优先级低的,回滚起来比较方便,而且可以确保状态不受影响。
    不过最好的方式就是,尽量让所有功能在一次操作中全部成功。可以采取的办法就是功能检测。我们实现检测各个功能的执行条件,看看是否可以执行成功。预先得到不能成功执行的模块,尽量降低失败带来的影响。所以我们可以先对各个功能模块进行测试。通过测试,我们已经大大降低了失败的风险。如果还是会失败,那也只能回滚了。
    在测试的时候,优先级高的先测试。我们最终是确保优先级高的功能一定要最大化成功率。测试也就是对功能执行需要的条件进行检测,确保执行功能的时候不要因为各种条件受限而失败。比如上传数据,我们先检测好网络连接情况,检测好服务器接口运行情况。
    我们通过功能性事务机制,合理安排回滚,再加上预先测试功能,可以尽可能的确保功能的可靠性。