线程安全类在性能测试中应用

  • 2020 年 2 月 17 日
  • 筆記

最近在做一个支付成功之后回调接口的压测,场景是用户购买VIP,详情如下:

测试场景

用户支付成功之后,端上会请求后端来进行VIP开通和续费操作。

接口处理逻辑

首先验证接口参数签名是否正确,然后加锁去判断订单信息和状态,处理用户增添VIP时间事务,成功之后释放锁。锁是针对用户和订单的分布式锁,使用方案是用的redis

接口文档

接口基本信息

  1. 接口名称购买会员或续费会员
  2. 请求Url /api/member/createOrRenewMember
  3. 将请求参数转为JSON字符串走验签处理,请求方式见 加签示例

请求参数

{      "sign": "CGvmkLeGtDv/Om8RbY52pMKNMi/xdLYAgRhIxC3uPqJsEdOvHj+S6zFobN0fxA/vksmCKJHhN6hFQrFIa3C6oK6EH7/BnJEsUMmIRsXMra32lzE/Tq9nqjYIdy996qc6eJWsxshqquj9Tb78pN152ndCNgvujMYsUHT4v7QxUW4=",      "orderNo": "E2341234",      "systemId": 94848494  }  

请求参数说明

字段说明

字段名称

字段类型

备注

订单号

orderNo

string

订单编号

用户账号

systemId

number

必传

签名

sign

String

签名字符串,请用我方提供工具类生成

返回参数

{      "code": 0,      "message": "success",      "data": {          "memberId": 123123,          "systemId": 86123123      }  }  

code 等于0的时候成功

测试方案

类似方案参考如何对消息队列做性能测试

难点

  • 因为锁的关系,一个用户只能同时有一个订单在处理,压测参数必需是每次必不相同。
  • 用户必需是存在的用户,对压测用户量提出了要求。

解决方案

  • 将用户id和订单号进行参数化,使用AtomicInteger这个线程安全的类和一个提前加载好的参数数组来保证每一次参数都是唯一且相互不同。(不适用随机的方法,因为有概率重复和消耗更多性能)
  • 储备更多用户,由于获取用户是按照数组索引增大顺序获取,并不需要每一个请求都绑定一个用户。经过尝试2000个用户循环去取就能满足需求。

关于Java线程安全的问题参考:操作的原子性与线程安全快看,i++真的不安全原子操作组合与线程安全

测试脚本

保留一下调试的方法和功能,性能测试框架第三版里面有引用类的代码。

package com.okayqa.others.payyst    import com.fun.base.constaint.ThreadLimitTimesCount  import com.fun.frame.excute.Concurrent  import com.fun.frame.httpclient.FanLibrary  import com.fun.utils.ArgsUtil  import com.fun.utils.RString  import com.okayqa.common.RSAUtilLJT  import com.okayqa.common.Users  import com.alibaba.fastjson.JSONObject  import org.apache.http.client.methods.HttpPost  import org.slf4j.Logger    import java.util.concurrent.atomic.AtomicInteger    class T extends FanLibrary {static Logger logger = getLogger(T.class)          static AtomicInteger i = new AtomicInteger(111000);        publicstaticvoid main(String[] args) {          def argsUtil = new ArgsUtil(args)          def thread = argsUtil.getIntOrdefault(0, 1)          def times = argsUtil.getIntOrdefault(1, 100)          def reqs = []            thread.times {  //            def mark = new HeaderMark("requestid")              reqs << new Thr(times)          }            new Concurrent(reqs, "会员支付和续费接口").start()          testOver()      }        staticclass Thr extends ThreadLimitTimesCount {static Logger logger = getLogger(Thr.class)            public Thr(int times) {              super(null, times, null);          }            @Overrideprotectedvoid doing() throws Exception {              String url = com.okayqa.studentapd.base.OkayBase.HOST+"/api/member/createOrRenewMember"              Map<String, String> p = new HashMap<>();              p.put("days", "1");              p.put("memberId", "208");              p.put("orderNo", "F" + RString.getString(4) + i.getAndAdd(1));              p.put("orderPaySystemId", "85123213");              p.put("orderPayTime", "2020-02-09 10:00:00");              p.put("payMoney", "30");              p.put("recordSources", "3");              p.put("renewal", "false");              def user = Users.getStuUser(i.getAndAdd(1) % 2000)  //            output(user)              p.put("systemId", user);              String sign = RSAUtilLJT.sign(p, RSAUtilLJT.getPrivateKey(RSAUtilLJT.RSA_PRIVATE_KEY));              p.put("sign", sign);              HttpPost post = getHttpPost(url, JSONObject.fromObject(p).toString());              def s = "F" + getNanoMark()              post.addHeader(getHeader("requestid", s));                def simlple = FanLibrary.excuteSimlple(post)              if (!simlple.contains("success")) {                  logger.warn(s + OR + user + simlple.toString())                  fail()              }          }        }  }    

这里有一个坑,AtomicInteger类虽然是一个线程安全的类,但是并不是所有的方法都是安全的,比如get(),所以我两次都使用了getAndAdd()方法,虽然增加了用户量循环一次的速度,但准确性还是最重要的,经过试验验证2000个用户足够用。


  • 郑重声明:文章首发于公众号“FunTester”,禁止第三方(腾讯云除外)转载、发表。