测试角度的并发和幂等问题总结

未来已来2018-09-06 10:41
作者:刘静媛

前言
  从事金融业务领域的测试工作过程已经有半年时间了,在此期间遇到了各种类型的问题,个人认为在日常测试工作中,特别是涉及资金的业务领域内,测试人员需要格外关注的方面有:系统的并发问题、幂等问题、性能问题及资损风险等。下面就是这段时间以来,在并发问题和幂等问题上站在测试角色上的一些积累和总结。

一、并发问题
1、什么是并发?
   并发(Concurrency)是指系统在同一时间段内,同时执行多个计算程序。为了更好的解释并发的概念,引入并行(Parallelism)概念来进行对比分析。
下图较为形象的给出两者的工作机制:

   在多个线程同时工作时,单个CPU硬件环境下不可能真正同时的有多个线程在工作,此时并发机制实际上是将CPU的运行时间划分成若干时间段,不同的时间段交由不同的线程使用,而其他线程此时属于挂起状态。在多个CPU的硬件环境下才有可能实现多个线程真正的同时工作。

2、并发与并行特点
并发 并行
宏观层面同时执行 严格的同时执行
解决多个不相关的任务 一般解决同一个任务
资源和数据共享 资源和数据隔离
单、多处理器均可 必须多处理器
彼此需要交互通信 不需要交互通信
重点是组合协调 重点是执行
3、并发的优缺点
优点:
(1)提升资源利用:这个优点是最容易理解的,多线程并发可以提升cpu的利用率,比如在一个线程等待磁盘IO资源时,CPU可以去执行另外的任务。在任务量大的情况下,并发机制能够明显减少系统耗时。
(2)模型边界清晰:能够并发的任务,应该是尽量没有依赖关系的,是相互独立的,能够同时执行而不相互影响的。例如设计一个文件处理器,就可以使用多个线程,每个线程负责一个文件的读取和操作,在多个线程同时工作时,对每个文件的操作是相互独立的。
(3)异步事件响应:在处理异步任务时,并发编程的优点更是显而易见的,它能够避免单线程情况下资源闲置和过度等待。建立单独的监听器,在得到异步响应时随时处理响应,高效合理的利用系统资源。

缺点:
(1)安全风险:由于同一进程下的多个线程会共享地址空间和数据,而多个线程访问资源的顺序具有不确定性,因此在多线程并发时容易出现脏读、幻读等问题。(线程安全)
(2)活跃度风险(饥饿、死锁、活锁)
(3)性能风险:线程频繁调度切换同样会带来资源浪费。
(4)分析、编程和测试复杂

4 、并发编程常见的应用场景
定时任务、分布式任务、WS服务、Servlet、异步消息
多见于:多进程并发、集群中机器之间并发

5 、并发安全措施
数据库行级锁:悲观锁,乐观锁
全局分布式锁
应用同步和锁机制:——Synchronized\CAS;——java.util.concurrent.locks;——java.util.concurrent.atomic
使用线程安全的容器:java.util.concurrent

6 、测试角度看并发问题:
6 .1 难分析、难测试:
有些并发问题,是很难在平时测试过程中发现的,特别是系统底层存在的并发问题,例如:
缓存更新设计不合理导致的脏数据问题——如果缓存更新的设计模式是先清除旧的缓存再更新数据库,首先这个设计是不合理的,排除设计的正确性不谈,如果在测试人员不了解缓存更新策略的情况下,执行上层业务层面的并发测试,有很大的概率是在测试过程中并没有出现问题,但是仔细分析一下,在一个外部条件触发了缓存更新的同时,有可能出现另外两个并发操作:一个更新,一个查询。在删除缓存后,有一个查询操作访问缓存没有命中,会去查数据库,并把数据库中老的数据放进缓存中,同时更新操作会更新数据库。这样导致缓存中的数据一直是老的数据,是脏数据并且会一直脏下去。而在现实测试过程中,缓存的更新以及查询操作都发生的非常之快,很难人为造出类似或者更为复杂的并发场景。

6 .2 难定位、难复现:
大家在开发测试过程当中可能都会遇到的一个场景就是在多次执行同一个操作时,不定期的会有一个操作是执行失败了,很难找到失败的规律,测试同学提bug时也会很难再复现出bug并保留现场给开发人员排查。甚至此类情况直接出现在了生产环境,用户反馈说操作失败,但是更难说清楚在什么情况下会失败。这种问题发生的原因可能是多种多样的,可能是多线程设计的漏洞,也可能是系统交互的异常情况欠考虑引起的。当遇到这种情况时,靠碰运气和多次尝试是很笨拙的方法,关键还是分析出可能存在的并发问题,再有针对的尝试验证,确定是某种原因并分析解决后,再进行并发尝试,看是否还存在问题。

6 .3 预防和发现手段:
a.业务场景分析:实际应用场景下某功能是否会出现并发情况,对于会出现并发情况,重点关心代码设计,并准备做并发测试。
b.静态代码分析:可以借助工具 Contemplate's  ThreadSafe Solo,参考 http://www.contemplateltd.com/threadsafe
c.并发测试的注意事项:
   (1)不放过偶发的错误和失败
   (2)做好测试分析,完成非并发的功能测试后,针对测试分析时分析到的容易出并发问题的部分做独立的并发测试
   (3)并发测试时,注意并发测试设置的并发线程数需要做到可调节,通常要大于CPU数量


二、幂等问题
1、幂等的概念
幂等来源于数学概念:单目运算中,x为某集合中的任意数,f为运算子,如果满足f(x)=f(f(x)),那么f运算就是幂等的。
幂等性是系统接口的一种承诺,承诺只要调用接口成功,多次相同的输入会有相同的结果反馈和等同一次处理的影响力。声明为幂等的接口会认为外部调用失败是常态,并且失败后必然会有重试。

2、引发幂等问题的常见原因
用户重复提交——非常容易发生,前端、后端均需要控制
网络重发——容易遗漏,但有可能发生
消息重发——容易遗漏,但有可能发生
系统间重试——需要根据业务情况来判断是否需要重试,哪些情况哪些系统需要重试

3、幂等问题的控制关键
在设计资金的系统中,幂等问题有着十分重要的地位,比如我们定义一个接口withdraw
bool withdraw(accountId, amount)
withdraw的语义是从accountId对应的账户中扣除amount数额的钱;如果扣除成功则返回true,账户余额减少amount;如果扣除失败则返回false,账户余额不变。
一种典型的情况是withdraw请求已经被服务器端正确处理,但服务器端的返回结果由于网络等原因被丢掉了,导致客户端无法得知处理结果。如果是在交互设计或者前端展示上处理的不够好,用户会以为这次操作执行失败,然后刷新页面或者重复提交请求,这就导致了withdraw被调用两次,账户也被多扣了一次钱。整个过程如下图所示:


为解决此问题,需要对接口进行幂等性改造,增加一个唯一ID参数,如:
bool idempotent_withdraw(uniqId,accountId,amount)
这个ID需要全局唯一的标识一次请求,客户端的同一个业务请求只有一个uniqId,服务端在收到请求后去检查一下是否已存在这个 uniqId并且执行成功了,如果执行成功就不会再处理第二次的调用请求,如此就避免了重复扣款的问题。

总体而言,在技术实现上,控制幂等问题的关键在于唯一键+状态机。
首先,调用者在请求中携带一个唯一ID,这个ID唯一的标识一个工作单元,这个工作单元只允许被成功执行一次。
其次,接受者在收到请求,获得唯一ID时,要先去查询这个ID所标识的工作单元是否被执行过。 检查是否执行的逻辑通常是根据唯一请求ID ,在服务端查询请求是否有记录,是否有对应的响应信息,如果有,直接把响应信息查询后返回;如果没有,那么就当做新请求去处理。

4、测试角度看幂等
(1)需要更多的关注业务性质和产品设计上,是否需要做到幂等,是时间维度的幂等还是空间维度的幂等。
(2)接口的幂等测试一定不能遗漏,由于幂等场景相对容易制造出来,幂等测试的难度远远小于并发测试,因此在做接口测试时不妨对每个接口都思考一下是否需要幂等,需要的话就测试一下其幂等性
(3)业务场景,特别是涉及到资金流动的业务场景,对失败重试机制一定要慎重。



网易云大礼包:https://www.163yun.com/gift

本文来自网易实践者社区,经作者刘静媛授权发布