服务端测试注意点剖析

前言

服务端测试在app测试、pc端测试过程中都紧密伴随,在整个测试周期中,服务端测试是较为重要的一环,服务端如果出现问题,可能会引发较大的影响(例如系统性崩溃服务不可用、一个简单的改动也可能引起全版本app的功能不可用等);随着用户体量的增大,对性能的要求越来越高,使用到的技术栈也越来越多,测试需要考虑的点也越来越多。本文从接口测试、脚本测试、中间件、上线流程等四个方面分析一些注意点。

一、     接口测试

1.1 初步评估是否需要压测

压测评估依据:根据客户端的请求时机,预估接口的访问量,线上类似访问量的接口高峰期tps,以及服务端技术方案中有无缓存机制等,综合确定是否需要进行接口压测

1.1.1 评估压测阶段-技术方案确认阶段:

1) 客户端接口请求的时机如果有新增、变化的情况(老接口),在技术方案实现文档中需要明确,三方一起重新评估全量之后的tps,开发与测试人员需重新评估是否要对接口进行压测。

2) 服务端逻辑改动如果影响到上游、下游业务接口时,需考虑业务整体的链路影响范围,确认上下游接口是否需要进行压测。

3) 功能在设计初期就要评估好需要准备的资源的规格,提前向运维同事申请好资源(最终可根据压测结果进行微调)。

4) 以上流程都要明确写到上线文档里。

1.1.2 压测实施阶段-上线前压测:

根据提前准备的压测文档进行压测,如压测结果未达到压测文档预定目标,需同运维开发沟通调整施压配置、资源配置、优化代码后再次进行压测,直到满足确定的性能要求。

1.2 正向用例

1)接口响应数据结果符合预期,主要参考产品需求、开发技术方案。

2)接口响应数据需要对权限进行校验,返回当前用户查询操作页面。

3)依据实际业务场景综合考虑补充测试用例。

1.3 反向用例

1)接口传入异常参数,不会导致程序崩溃等情况。

2)类型溢出,不能导致数据读出和写入不一致。

3)对象权限进行校验,不可以访问其他用户敏感信息等越权操作。

1.4 熔断

定义:

1)熔断简单来说就是在某个服务出现问题不可用时,为了避免引发更严重的问 题,导致整个服务链路不可用,可以采用熔断的方式来避免。

2)熔断一般情况下意味着服务的降级,可以理解为是一种异常兜底策略,需要服务 的上游调用方来实现。

作用:

分布式系统中存在多个服务调用时,难免会出现服务之间因为网络等问题而导致服务调用失败的情况,如果不考虑这些情况,有可能出现服务雪崩,导致系统资源被耗尽,因此,存在多个服务调用的时候,服务熔断是不可缺少的。

举例说明:

服务之间一般设置熔断时间1000ms,包括被调用方超时、报错等情况时,调用方应做的兜底逻辑,接口不报错返回默认值,测试时可让开发协助修改熔断时间,修改被调用方代码返回错误等方式辅助验证该逻辑。

评论数据的展示,需要请求用户中心获取对应uid的头像等信息,如用户中心在熔断时间内未返回数据,则调用方走兜底逻辑展示默认头像信息。

1.5 水平越权

指攻击者尝试访问与他拥有相同权限的用户信息。权限校验错误或未实现权限控制,主要有以下情形:

未实现用户、对象、动作三元组的校验,针对实现用户与权限校验的测试用例设计如下:

资源id替换后,重放请求 (用户1- 动作1-对象id1)  替换成  用户2- 动作1-对象       id1 )

用户:uid

动作:接口的查询,删除,举报等

对象:接口处理对象,比如章评、段评、作者说说等

● 接口运行异常,无法正常完成功能 -- 测试通过

● get、post参数内是否有uid,无uid提示参数错误、请先登录  --测试通过

● 接口运行效果一致 -- 存在越权

举个例子:

1、删除评论接口,传入其他用户的uid尝试删除他人评论:

预期:接口返回“参数错误”,校验uid与评论发表uid是否一致,不一致则无法删除,同时确认该条数据在数据库中未进行修改。

2、查看段评列表接口,修改传入的uid,尝试查看别人的仅自己可见数据,数据返回为空:

对于三元组中任何一个元素,缺失、替换、排列组合后的唯一性都要进行校验。

1.6 向上越权

指低权限的用户可以不受控制的访问高权限用户的资源。简单来说就是A账号有小权限,但是通过越权操作,获取了B账号的大权限。

测试方法:通过删除或替换请求中的认证信息后重放该请求,依旧可以访问、操作。

测试用例设计:

● 管理员账号有查看、添加、删除用户的权限,而普通用户只有查看权限

● 管理员登录系统,添加一个用户,抓包得到添加请求A

● 退出管理员账号,登录普通用户,得到普通用户的请求B

● 在请求B中替换成A中管理员账号的请求信息重新发起请求,如果可以成功操作,则存在向上越权

举个例子:

1、作者只能删除自己的“作者说说”。通过修改为普通读者的uid,调用删除作者说说接口

预期:有相关权限提示权限不足   pass

1.7 grpc测试方法

grpc模拟客户端/调用方发起接口调用,需要对于异常传参进行校验并提示,下方的软件可以修改各个参数值及类型,模拟发起请求,判断服务端逻辑完整性

打开Github: https://github.com/uw-labs/bloomrpc

进入页面点击:releases

根据自己的系统选择不同的安装文件

Windows下载EXE 文件,下载完成点击安装打开

打开页面如图

导入grpc接口请求文件

host绑定域名环境例: 192.16.0.0.1    www.baidu.com

修改请求参数、验签

点击TLS选择服务端验证

配置好后,点击页面中的请求按钮发起请求

二、     脚本测试

脚本测试一般从验证点、数据依赖、执行效率、上线验证流程等几方面着手。

2.1 脚本验证点

1)脚本执行之前,需要对处理数据的量级、正确性校验(对于明显量级不符、数据错误需加监控及时报警,脚本此次不执行操作)验证通过后执行脚本。

2)脚本执行中,处理数据出现异常(操作失败)发送报警邮件,验证监控邮件是否齐全,考虑脚本重跑机制,重跑之后验证数据不会有重复等问题。

3)脚本执行之后,验证脚本逻辑正确性。

2.2 脚本数据依赖

脚本执行之前,如有依赖其他脚本数据执行结果作为此次操作的前提条件时,需检查依赖前置脚本数据的正确性后再执行本次脚本,依赖定时脚本数据时,需注意定时脚本执行频率及执行时间,避免前置脚本执行未完成,后续定时脚本已开始执行。

2.3 脚本执行效率

定时脚本执行时间点及频率需要与开发共同确认,验证定时脚本执行效率,如脚本执行时间过长,会导致上线流程中断甚至出现跨天上线的情况,此时需考虑脚本提效优化。

2.4 脚本上线验证流程

1)全量数据处理的脚本,需支持对指定条件数据进行处理,修改全量数据字段,需支持对单本书、单条、某个时间段的数据修改,上线过程中,可执行指定条件数据脚本,验证脚本逻辑正确后,可执行全量脚本。

2)避免直接执行全量脚本,一旦存在问题,可能导致线上数据恢复困难、影响面广等情况。

3)脚本源站验证配置之后,上线需要更改线上配置。

举个例子:

● 支持部分指定数据,指定时间段,见下图

三、     中间件

下面分别介绍常用存储Redis、mysql、mongodb、es、go缓存以及消息队列mq/kafka

对于需求中涉及中间件的相关库表更新,用例设计时可记录每步操作后的库表更新检查脑图,例如:

3.1  Redis

Redis是一种支持key-value等多种数据结构的存储系统。可用于缓存,事件发布或订阅,高速队列等场景。支持网络,提供字符串,哈希,列表,队列,集合结构直接存取,基于内存,可持久化。测试过程中,常见的问题有缓存过期时间、热key问题、大key、缓存穿透问题、缓存一致性等。

3.1.1 缓存过期时间

根据实际业务场景设置过期时间,其设置合理性非常重要,如果原有缓存失效,所有原本应该访问缓存的请求都去查询数据库,进而对数据库CPU和内存造成巨大压力,严重的会造成数据库宕机、系统崩溃,引起缓存雪崩。

为避免缓存雪崩,有以下几种解决方案:一是在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待;二是不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀;三是做二级缓存,A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间设置为短期,A2设置为长期。

3.1.2 热key问题

在某个Key接收到的访问次数、显著高于其它Key时,我们可以将其称之为热Key,比如有几十万的请求去访问redis上的某个特定key,那么,这样会造成流量过于集中,达到物理网卡上限,从而导致这台redis的服务器宕机。

热点Key的危害有以下几点,流量过于集中,突破物理网卡的极限;请求过多,缓存分片服务被打垮,热点过于集中,热点Key缓存过多,超过目前的缓存容量,就会导致缓存分片服务被打垮;穿透DB,缓存服务崩溃,此时再有请求产生,会缓存到后台DB,导致缓存穿透,进一步还会导致缓存雪崩。

热点Key问题的解决方案有以下几点:

1)是对特定key做限流;

2)是使用二级(本地)缓存;

3)是拆key,在放入缓存时就将对应业务的缓存key拆分成多个不同的key。

对于热key业务思考:

比如某个key存了所有用户的某个数据,假设这个key名叫a:b:c,那它的查询和更新都会在集群中的某一个节点进行,会造成这个节点负载比其他大,建议可以按用户uid分多个key

3.1.3 大key

所谓的大key问题是某个key的value比较大,所以本质上是大value问题。接口访问该key时存在的I/O操作,可能影响接口响应效率,甚至对后续业务影响,最典型的就是阻塞线程,并发量下降,导致客户端超时,服务端业务成功率下降。大key产生原因,一是一直往value塞数据,没有删除机制,二是数据没有合理做分片,将大key变成小key。

大key的影响有以下几个方面:

1)执行大key命令的客户端本身,耗时明显增加,甚至超时;

2)执行大key相关读取或者删除操作时,会严重占用带宽和CPU,影响其他客户端;

3)大key本身的存储带来分布式系统中分片数据不平衡,CPU使用率也不平衡;

4)大key有时候也是热key,读取操作频繁,影响面会很大;

5)执行大key删除时,在低版本redis中可能阻塞线程。

大key问题解决方案:

1)key可删除的情况:如果发现某些大key并非热key就可以在DB中查询使用,则可以在Redis中删掉。

2)key不可删除的情况:

2.1)压缩key,当vaule是string时,比较难拆分,则使用序列化、压缩算法将key的大小控制在合理范围内,但是序列化和反序列化都会带来更多时间上的消耗。

2.2)拆分key,当value是string,压缩之后仍然是大key,则需要进行拆分,一个大key分为不同的部分,记录每个部分的key,使用multiget等操作实现事务读取;当value是list/set等集合类型时,根据预估的数据规模来进行分片,不同的元素计算后分到不同的片。

3.1.4 缓存穿透问题

缓存穿透指查询一个一定不存在的数据,由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,进而给数据库带来压力。产生原因有两个,一是自身业务代码或者数据出现问题,二是黑客非法请求攻击。

缓存穿透问题的解决方案有以下几点:

1)如果是非法请求,我们在API入口,对参数进行校验,过滤非法值;

2)如果查询数据库为空,我们可以给缓存设置个空值,或者默认值。但是如有写请求进来的话,需要更新缓存,以保证缓存一致性,同时,最后给缓存设置适当的过期时间;

3)使用布隆过滤器快速判断数据是否存在。即一个查询请求过来时,先通过布隆过滤器判断值是否存在,存在再继续往下查。

3.1.5 缓存一致性注意点

关于缓存一致性有以下几个注意点:

1)如果是读请求,先读缓存,后读数据库;

2)如果是写请求,先写数据库,再写缓存;

3)每次更新数据后,需要清除缓存;

4)缓存一般都需要设置一定的过期时间;

5)redis值同步更新其他数据库;

5.1)考虑场景:重要的并且会过期的redis数据,要考虑此问题

5.2)同步方式:第一种接口实时同步,更新频率不高的接口,可以在接口触发数据更新时,同时更新redis和其他数据库;第二种脚本定时同步,如果是触发数据更新频率较高的接口,不能同步更新mysql等其他数据库,会造成较大的压力,可以通过脚本定时更新的方式;第三种队列同步,消费者对队列中的消息依次消费更新。

3.2  mysql

mysql部分主要从索引、数据库事务及分库分表三部分进行介绍。

3.2.1 索引

索引是对数据库表中一列或多列的值进行排序的一种结构,使用索引可快速访问数据库表中的特定信息。索引的原理是以空间换时间,一般来说索引本身也很大,不可能全部存储在内存中,因此索引往往是存储在磁盘上的文件中的(可能存储在单独的索引文件中,也可能和数据一起存储在数据文件中);数据库在未添加索引进行查询的时候默认是进行全文搜索,也就是说有多少数据就进行多少次查询,然后找到相应的数据就把它们放到结果集中,直到全文扫描完毕。数据库表中不是索引越多越好,而是仅为那些常用的搜索字段建立索引效果最佳。

3.2.2 数据库事务

事务是为解决数据安全操作提出的,事务控制实际上就是控制数据的安全访问。事务有原子性、一致性、 隔离性、持久性四个特性,简称ACID 。

1)原子性:原子性是指事物包含的所有操作要么全部成功,要么全部失败回滚,因此事务的操作如果成功就必须全部应用到数据库,如果操作失败则不能对数据库有任何影响。

2)一致性:一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。

3)隔离性:隔离性是当多个用户并发访问数据库时,比如操作同一条数据,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。

4)持久性:持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。

举例说明:

1)例如评论审核状态更新,消费者处理更新各库表包括审核表,评论表状态时,必须全部成功,该条事务才算成功对数据库操作,否则需要全部回滚,避免出现部分数据状态更新问题。

2)例如审核运营人员对同一条评论同时进行不同的操作,如加精、置顶,注意库表状态更新的隔离性。

3.2.3 分库分表

3.2.3.1 分表

分表的应用场景是单表数据量增长速度过快,影响了业务接口的响应时间,但是 MySQL 实例的负载并不高,这时候只需要分表,不需要分库拆分实例。

分表方案:

1)垂直分表,表的字段太多,切分字段。将使用频率高字段放到一张表里,剩下使用频繁低的字段放到另一张表里,简称冷热分离。

2)水平分表,表的记录数太多,切分记录。可按照按主键ID拆分数据、按月分表以及MySQL分区表几种方式进行拆分。按照按主键ID拆分数据、按月水平分表之后,还要改造代码要能保证 SQL 正确的路由,执行并返回结果,这个调用链路有点长,MySQL内部也有分表的解决方案,使用 MySQL 的 HASH 分区,常规的 hash 也是基于分区个数取模(%)运算。

举例说明:

1)社区评论项目按月分表规则,根据commentid取模分表、uid取模分表等,都采用了上述方法;

注意事项:

1)对于采用按月按年等时间分表规则,需考虑后台查询时间区间跨月、跨年等情况,比如评论的审核后台查询数据需要根据审核运营人员自行修改评论时间,不受月、年的限制,固前期的按月分表查询mysql并不能实现业务需求,后续采取把评论数据同步es的方式,后台查询es不受时间限制,所以前期制定分表规则时需要考虑后续的业务可能性也很重要。

3.2.3.2 分库

当数据库的读写访问量过高,还有可能会出现数据库连接不够用的情况。这个时候我们就需要考虑分库,通过增加数据库实例的方式来获得更多的数据库连接,从而提升系统的并发性能。

分库方案:

1)垂直分库,一个数据库的表太多。此时就会按照一定业务逻辑进行垂直切,比如用户相关的表放在一个数据库里,订单相关的表放在一个数据库里。注意此时不同的数据库应该存放在不同的服务器上,此时磁盘空间、内存、TPS等等都会得到解决。

2)水平分库,水平分库理论上切分起来是比较麻烦的,它是指将单张表的数据切分到多个服务器上去,每个服务器具有相应的库与表,只是表中数据集合不同。 水平分库分表能够有效的缓解单机和单库的性能瓶颈和压力,突破IO、连接数、硬件资源等的瓶颈。

3.3 MongoDB

3.3.1 MongoDB的索引和查询注意事项

索引:

索引就是将文档按照某个(或某些)字段顺序组织起来,以便能根据该字段高效的查询。

mongodb查询注意事项:

1)   保证足够大的内存;

2)Mongodb跟mysql一样,对于常用的查询条件,该建索引的建索引;

3)  使用副本集,一个主库,进行更新等操作,一个replicaset,用于检索查询。如果使用一个实例的话,更新等操作会锁定整个库,从而影响到查询检索的速度。项目中经过测试,有副本集的库,速度要比没有副本的库快至少5倍;

4)  Mongodb对两个及以上的数组列,不能建立复合索引。也就是说,一个复合索引中,不能包含两列都是数组的列.

3.3.2 MongoDB的5种ReadPreference

1)primary:主节点,默认模式,读操作只在主节点,如果主节点不可用,报错或者抛出异常。

2)primaryPreferred:首选主节点,大多情况下读操作在主节点,如果主节点不可用,如故障转移,读操作在从节点。

3)secondary:从节点,读操作只在从节点,如果从节点不可用,报错或者抛出异常。

4)secondaryPrefarred:首选从节点,大多数情况下读操作在从节点,特殊情况(如单主节点架构)读操作在主节点。

5)  最邻近节点,读操作在最临近的成员,可能是主节点或者从节点。

举例说明:

1)不同的业务场景适用不同的读写模式,比如查询接口一般是优先查询从节点,但是某些业务场景,比如脚本从mongodb读取数据(业务需要确保是最新数据时)可能需要优先读取主节点。

3.4  ES

3.4.1 索引

在ES中每个字段都是被索引的,所以不会像MySQL中那样需要对字段进行手动的建立索引,ES在建立索引的时候采用了一种叫做倒排索引的机制,保证每次在搜索关键词的时候能够快速定位到这个关键词所属的文档。

3.4.2 查询注意事项

1)禁止直接使用/_search进行查询。直接使用/_search接口进行查询时,会对全部可查询的(一般指具有权限的)索引进行查询,造成巨大的查询压力,所以务必不要直接请求/_search接口。

2)务必带有range条件。不对查询时间区间进行声明,默认会对全部时间范围内进行查询,造成巨大的查询压力,如果使用自定义字段进行时间范围限制,一定注意检查此字段是否在es中被识别为date类型。

3)尽量避免使用wildcard或通配符查询。wildcard类似mysql中的like,用起来方便但是效率并不高,查询过程中不会使用分词器。

4)增加查询超时时间。在大部分合理请求的情况下,es可实现毫秒级响应,所以在调试查询api时,推荐使用超时时间限制,避免误操作或突发大量数据造成es集群压力骤增,影响他人使用。

5)如果业务有分页,并且使用相关性排序(_score)此时要注意_score相同的情况,需要引入第二排序字段,这个字段的值最好能唯一,不然可能会出现两次分页结果出现重复的情况

举例说明:

评论数据的最热排序,如果单从热度值作为唯一的分页,会存在两条甚至多条数据热度值一致的情况,优化后用热度拼接评论发表时间戳,还是不唯一,热度相同,发表时间相同还是会使hot_cout不唯一,目前使用热度值拼接comment_id作为hout_count(唯一标识),同一本书下的comment_id是唯一的。

3.4.3 添加字段mapping不匹配问题

添加字段有时会导致mapping不匹配带来的一些问题,有以下几种解决方案:

1)删除索引,重新生成mapping, 这样之前所有的索引数据将丢失,当数据量很少很少的时候可以采用这种方式;

2)通过新建索引,将原来数据复制过去,然后再重命名回来;

3)通过新建索引,将原来数据复制过去,升级接口。

3.5  go缓存

go缓存的使用注意点:

1)go缓存会占用服务器内存,因此不适用于用户维度或者存在多维度的key。

2)需要注意go缓存里的数据结构,如果存的是结构体并且业务读取缓存有二次处理数据的逻辑,在并发情况下可能会导致go缓存的数据被修改,需要注意使用深拷贝保障go缓存不被修改或者go缓存里就存入Marshal之后的数据,读到之后再进行Unmarshal。(但是可能会带来CPU的上涨)

3)注意go缓存的写入和读取方使用的结构是一样的。不然读取接口读到与预期不符的数据结构会崩溃。(因此一定要注意gocache的key名不要重复)

3.6  消息队列mq/kafka

使用消息队列主要是为了减少响应所需时间、削峰以及降低系统耦合性(解耦/提升系统可扩展性)。

以下从推队列和消费更新两个方面进行论述。

验证推队列功能时,需列出推队列验证点,k8s后台可以查询消费者名称停止消费者消费。验证正常推入队列case,查看未消费消息+1,积累一定量消息时,打开消费者,验证消费堆积情况下,消费速度满足要求;

对于接口操作成功,队列及后续消费者操作失败时,会导致脏数据产生,需提前确认队列堆积、消费失败监控情况以及后续清理方案。预期:消费堆积及时报警提醒,验证消费准确性,验证消费者更新或插入所做的操作,是否存在部分消费失败(正常一条消息只有失败和成功,不存在部分成功)。

四、上线流程

关于上线流程优化的建议:

1、上线文档,需要形成统一的模板,并持续改进,需要考虑的一些点如下:

1)重大功能上线前需要有上线演练,评审上线文档并进行上线预演

2)回滚方案

3)上线时间点:避免周五、周六、周日,及平时高峰时期

4)预计上线时长:一个迭代内的需求,是集中上线,还是分批上线,需要根据实际情况计划好,避免出现上线耗时过长的情况。

5)上线人员:每个组目前好像只有一个负责上线的人员,是否有backup,如果没有,那需要避免上线人员在上线当天安排其他会议或事项。

6)跨组需求:上线的先后顺序,是否需要同一时间段内完成上线。

7)基础服务修改可能影响其他项目时,需要找对应的测试人员一起测试验证。

8)各种脚本的执行(是否有遗漏,是否有先后顺序依赖,是否有一些脚本的执行会直接影响生产环境的使用)

9)各种相关配置(白名单、是否跨云访问、是否需要走专线等)

10)各种相关监控及邮件报警的添加(例如新增redis、新增kafka等,需要配置相应的监控)

11)如果涉及到较多工单执行,建议提前与运维人员沟通,上线时现场配合。工单里的SQL需要提前在测试环境实际执行验证

12)文档中每一步开发或运维的操作后,需要测试人员也列出对应的验证步骤与预期结果(同时测试人员要提前申请好相关数据库或其他中间件的查看权限、提前准备好相关测试数据)

13)文档中整理出灰度期间或全量上线后要观察的数据指标。(包括但不限于:用户反馈、请求tps、服务器负载压力、业务相关数据例如商业化实时数据及sentry情况等)

2、上线过程:

1)按照上线文档步骤执行上线,测试人员在预发布环境,先验证老功能,再针对服务端上线的新功能进行验证(回归测试服务端核心功能,保证客户端双端线上版本功能正常)(同时,测试平台可提前对影响接口预发布环境进行监控,有问题能够及时发现)

对于后台功能的验证注意点:

新功能上线之后,需要在预发布或者线上验证功能,需要同项目经理和产品沟通验证流程,提前在上线文档中标注,例如评论的发布,书友圈推荐内容的发布,作者账号发布验证,官方账号发布内容等,上线文档中产品会在上面标注发布书籍发布内容,验证完流程后是否删除等。

2)预发布环境测试无问题后,必须通过测试人员卡点,才可发布生产环境。

3)生产环境流水线可以增加灰度部署的环节,新代码先部署到少量灰度pod,让线上少量用户生效,及时观察日志和报错(sentry),以及上线文档中提到的数据指标是否符合预期,如果不符合预期,则考虑是继续排查原因还是回滚;如果符合预期则全量发布。

4)全量发布后,测试人员更新维护线上接口监控,相关人员继续观察相关数据指标,直至确认全量用户走到新的业务逻辑,并平稳度过一个晚高峰。

3、关于上线文档应避免的一些问题:

1)  前端上线目前无上线文档流程。

2)  一些小需求无上线文档流程。

3)  临上线才写上线文档,时间比较仓促,可能考虑不充分。