最近在做一个支付成功之后回调接口的压测,场景是用户购买 VIP,详情如下:
用户支付成功之后,端上会请求后端来进行 VIP 开通和续费操作。
首先验证接口参数签名是否正确,然后加锁去判断订单信息和状态,处理用户增添 VIP 时间事务,成功之后释放锁。锁是针对用户和订单的分布式锁,使用方案是用的redis
。
{
"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 的时候成功
类似方案参考如何对消息队列做性能测试。
AtomicInteger
这个线程安全的类和一个提前加载好的参数数组来保证每一次参数都是唯一且相互不同。(不适用随机的方法,因为有概率重复和消耗更多性能)关于 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);
public static void 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()
}
static class Thr extends ThreadLimitTimesCount {
static Logger logger = getLogger(Thr.class)
public Thr(int times) {
super(null, times, null);
}
@Override
protected void 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 个用户足够用。