专栏首页FunTester我的开发日记(三)

我的开发日记(三)

今天主要解决了一下测试账号登录状态的校验,我现在的方案是用户在写测试用例的时候使用特殊语法uid=123这样的形式,表示该用例字段应该去uid等于123的测试账号的登录凭证。难点在于登录凭证会过期,会被挤掉,如果维护所有测试用户的登录状态又很麻烦,对服务器性能也是一种浪费。

所以最终的方案如下:

  • A、单独用例调试过程中使用中,预留一个凭证的有效期。有效期过了之后会继续校验凭证的有效性,如果成功,则重置有效期,如果失败则从登陆接口重新获取用户登录凭证并更新有效期
  • B、运行用例集(多个用例)时,采用多线程并发执行,为了保证每个测试用户的登录凭证有效性,每次只允许一个线程去执行A的逻辑。运行用例集过程中对用户凭证进行缓存,这样就不用从数据库中重复读取了。

在并行运行测试用例的时候,如果保证所以线程都能读到最新的用户凭证,且往缓存map中读取的数据正确性,想了一个方案就是在JVM里面对每一个用户进行加锁的操作,保证每一次只有一个线程去读写用户登录凭证。这样在单个用户运行的时候就可以登录一次,短时间内不用去登录即可持续进行用例的调试。在运行用例集的时候,每次运行创建一个临时的map存放本次用到的用户登录凭证,用例集内的测试用例执行完,该对象就释放,等着被GC回收。

用户对象锁存放类

package com.okay.family.common.basedata;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.ConcurrentHashMap;

public class UserCertificate {

    private static Logger logger = LoggerFactory.getLogger(UserCertificate.class);

    private static ConcurrentHashMap<Integer, Object> certificates = new ConcurrentHashMap<>();

    /**
     * 获取锁对象,用户测试用户锁
     *
     * @param id
     * @return
     */
    public static Object get(int id) {
        certificates.compute(id, (key, value) ->
        {
            if (value == null) {
                value = new Object();
            }
            return value;
        });
        return certificates.get(id);
    }


}

业务实现类

    @Override
    @Transactional(isolation = Isolation.REPEATABLE_READ, propagation = Propagation.REQUIRES_NEW)
    public TestUserCheckBean getCertificate(int id) {
        Object o = UserCertificate.get(id);
        synchronized (o) {
            TestUserCheckBean user = testUserMapper.findUser(id);
            String create_time = user.getCreate_time();
            long create = Time.getTimestamp(create_time);
            long now = Time.getTimeStamp();
            if (now - create < OkayConstant.CERTIFICATE_TIMEOUT && user.getStatus() == UserState.OK.getCode())
                return user;
            boolean b = UserUtil.checkUserLoginStatus(user);
            if (!b) {
                UserUtil.updateUserStatus(user);
            }
            testUserMapper.updateUserStatus(user);
            return user;
        }
    }

    @Override
    public String getCertificate(int id, ConcurrentHashMap<Integer, String> map) {
        Object o = UserCertificate.get(id);
        synchronized (o) {
            if (map.contains(id)) return map.get(id);
            TestUserCheckBean user = testUserMapper.findUser(id);
            String create_time = user.getCreate_time();
            long create = Time.getTimestamp(create_time);
            long now = Time.getTimeStamp();
            if (now - create < OkayConstant.CERTIFICATE_TIMEOUT && user.getStatus() == UserState.OK.getCode()) {
                map.put(id, user.getCertificate());
                return user.getCertificate();
            }
            boolean b = UserUtil.checkUserLoginStatus(user);
            if (!b) {
                UserUtil.updateUserStatus(user);
                if (user.getStatus()!=UserState.OK.getCode()) UserStatusException.fail();
            }
            map.put(id, user.getCertificate());
            testUserMapper.updateUserStatus(user);
            return user.getCertificate();
        }
    }
  • 这里用到了事务隔离级别和事务传播行为的设置,因为本来想用这种方式达到效果,但后来发现不行,因为更新用户登录凭证涉及到好几次读写数据库,多线程处理肯定会出BUG

附上事务隔离级别和传播行为的文档

事务隔离级别

隔离级别是指若干个并发的事务之间的隔离程度,与我们开发时候主要相关的场景包括:脏读取、重复读、幻读。

  • DEFAULT :这是默认值,表示使用底层数据库的默认隔离级别。对大部分数据库而言,通常这值就是:READ_COMMITTED 。
  • READ_UNCOMMITTED :该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读和不可重复读,因此很少使用该隔离级别。
  • READ_COMMITTED :该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值。
  • REPEATABLE_READ :该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。即使在多次查询之间有新增的数据满足该查询,这些新增的记录也会被忽略。该级别可以防止脏读和不可重复读。
  • SERIALIZABLE :所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。

传播行为

所谓事务的传播行为是指,如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为。

  • REQUIRED :如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
  • SUPPORTS :如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
  • MANDATORY :如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
  • REQUIRES_NEW :创建一个新的事务,如果当前存在事务,则把当前事务挂起。
  • NOT_SUPPORTED :以非事务方式运行,如果当前存在事务,则把当前事务挂起。
  • NEVER :以非事务方式运行,如果当前存在事务,则抛出异常。
  • NESTED :如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于 REQUIRED 。

本文分享自微信公众号 - FunTester(NuclearTester),作者:八音弦

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-06-17

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 测试人员常用借口

    无论我们试图建立一个网站多么完美,我们都一定会犯一些错误。错误是不可避免的,无论多么微小。这就是为什么我们不能保证没有错误的发布,甚至在进行了不同类型的全面测试...

    FunTester
  • 如何同时压测创建和删除接口

    在最近的工作中,遇到了一批需要压测的接口,其中两个接口比较特殊:一个是创建资源接口,另外一个是删除该资源的接口。

    FunTester
  • 17种软件测试人员常用的高效技能-下

    测试人员通常被误认为是应该只测试产品的人。但是,有时开发人员可能错过了重要信息。软件测试人员应该负责这种情况,并指出缺乏信息。 此外,最重要的软件测试技能之一包...

    FunTester
  • Spring中,多个service发生嵌套,事务是怎么样的?

    最近在项目中发现了一则报错:“org.springframework.transaction.UnexpectedRollbackException: Tran...

    小忽悠
  • MySQL 在并发场景下的问题及解决思路

    对于数据库系统来说在多用户并发条件下提高并发性的同时又要保证数据的一致性一直是数据库系统追求的目标,既要满足大量并发访问的需求又必须保证在此条件下数据的安全,为...

    wangxl
  • 接受“不完美”:分布式事务学习总结

    作为一个前端专业的人来说,对于事务的理解,一直停留在“要么都成功,要么都不成功”的小白阶段。既然自己将2018年定义为”深入理解“的一年,那么就从深入理解事务开...

    司想君
  • 服务设计要解决的问题

      “业务的服务(相对于我们基础架构这边的底层技术)在技术上就需要解决三个问题:分布式、通信和存储。”

    静儿
  • 分布式事务的实现思想

    分布式事务的基本概念与本地事务类似,都保证了 ACID 特性(见[本篇第二章](# 二. 事务的特性))。随着数据的规模越来越大,就出现了对业务的解构,包括数据...

    剑影啸清寒
  • 记录分布式一致性中的几个概念

    事务是由一系列对系统中数据进行访问与更新的操作所组成的一个程序执行逻辑单元,狭义上的事务特指数据库事务。事务具有ACID属性。

    眯眯眼的猫头鹰
  • 从银行转账失败到分布式事务:总结与思考

      思考这个问题的初衷,是有一次给朋友转账,结果我的钱被扣了,朋友没收到钱。而我之前一直认为银行转账一定是由事务保证强一致性的,于是学习、总结了一下分布式事务的...

    乱敲代码

扫码关注云+社区

领取腾讯云代金券