原创声明:转载请附上原文出处链接
文章章节
一、事务背景
二、思考
1、怎么区分成果为有用或无效?
2、怎么判别反常成果?
3、成果比照究竟比照什么?
三、探究类似度模型
1、参阅算法
2、参阅数学公式
3、规划思路
4、流程规划
四、类似度算法实验
1、测验数据
2、校验成果值的差异
3、校验key的差异
4、校验数据结构完好性的差异
5、测验成果
五、回放成果降噪
六、总结
1、验证定论
2、长处与缺乏
一、事务背景
流量回放时呈现许多diff失利的数据,排查和剖析时,发现许多diff失利并非真实事务场景触发的失利,反常数据的搅扰影响非常大,无法直接判别成果正确与否,对事务运用的体会性比较差
怎么做到diff失利的数据不会有非失利数据的搅扰?
二、思考
1、怎么区分成果为有用或无效?
有用成果:归于事务服务相关的反常问题,为有用成果
无效成果:非事务服务相关的反常问题,为无效成果
先把已知道的反常类型悉数列举出来:
1、执行回放的服务造成方针服务报错
2、回放的方针服务报错
3、回放的方针服务依靠的服务报错
4、回放成果数据转化报错
5、回放成果的呼应报文存在反常
6、录制的流量呼应成果存在反常
7、回放成果呼应报文与录制的呼应报文存在数据差异
8、回放环境服务不存在
9、回放成果超时
……
反常类型许多,要一个剖析很浪费时间和精力,从列举的反常类型剖析,整体能够划分为2种:事务反常和非事务反常
- 非事务反常:作为要点扫除目标,把这类反常问题悉数扫除去,对回放成果的搅扰降到最低,甚至为0,例如下图,是repeater服务调用方针回放服务引发的反常问题,并非事务本身报错反常,
判别为反常是无效反常
-
事务反常:需求区分出真的反常和“假的反常”,过滤掉许多“假的反常”,需求做扫除处理,控制这类反常问题搅扰在个位数以内
- 有用成果:例如下图,录制正常回来数据,回放时成果为空,
diff失利成果有用
录制成果呼应反常,回放成果呼应正常,
diff失利成果有用 - 有用成果:例如下图,录制正常回来数据,回放时成果为空,
2、怎么判别反常成果?
-
人工一条条数据剖析检查,得出成果成功与否
-
回放成果列表依据类型拉取diff失利和反常的数据
-
检查服务日志是否有反常报错
-
检查事务场景回放数据是否正确
-
拿请求参数单接口重新测验,再比照成果
-
……
以上做法,不论是选用哪种,都不是高效且又快速能得出可靠成果的办法,每个使命排期都是相对固定的,再投入剩余时间去剖析许多无差别的数据,脑壳不够用,彻底不够用!相比之下,或许人工测验的成果来得更可靠和直观些,至少能直接知道成果有用与否
不由堕入深思,流量回放的才能本来是要进步工作功率的,终究却因成果剖析反而成为担负 …… …… …… 张狂抓头发!!!有画面感吧,虽然离秃还差那么3000发丝
3、成果比照究竟比照什么?
成果状况?
成果值的差异?
参数key的差异?
呼应文本的差异?
感觉只比照哪个成果也不一定对,又感觉几项都缺一不可……
三、探究类似度模型
依据思考的方向,探究比对两个成果的模型,查阅许多文献之后,初步规划类似度模型:
1、参阅算法
杰卡德类似系数
两个调集A和B交集元素的个数在A、B并会集所占的份额,称为这两个调集的杰卡德系数,用符号 J(A,B) 表明。杰卡德类似系数是衡量两个调集类似度的一种目标(余弦间隔也能够用来衡量两个调集的类似度)。
杰卡德间隔
杰卡德间隔(Jaccard Distance) 是用来衡量两个调集差异性的一种目标,它是杰卡德类似系数的补集,被定义为1减去Jaccard类似系数。
杰卡德间隔用两个调集中不同元素占一切元素的份额来衡量两个调集的区分度。
2、参阅数学公式及其原理
-
修正间隔(Levenshtein 间隔):
- 公式:核算将一个字符串转化成另一个字符串所需的最少修正操作次数。
- 原理:经过比较两个字符串(在这里是JSON字符串),修正间隔公式能够衡量从一个字符串变为另一个字符串所需求的最小修正次数。这些修正或许包括添加、删除或替换一个字符。
-
Jaccard 类似度:
- 公式:Jaccard类似度 = (两个调集的交集巨细) / (两个调集的并集巨细)。
- 原理:Jaccard类似度是一个衡量两个调集类似度的目标。关于JSON目标,咱们能够将其视为键值对的调集,Jaccard类似度能够协助咱们了解两个JSON目标在键值对调集方面的类似性。
-
余弦类似度:
- 公式:余弦类似度 = 两个向量的夹角的余弦值。
- 原理:余弦类似度通常用于衡量两个向量之间的类似性。在比较JSON目标时,咱们能够将每个目标转化为一个向量,其间每个维度的值对应一个键值对的值。然后,咱们能够运用余弦类似度来比较这两个向量,然后了解两个JSON目标在值方面的类似性。
这些公式和原理都是为了协助咱们更精确地比较和评价两个JSON目标的类似性或差异性。
3、规划思路
参阅以上的算法和数学公式,主要有三个方向的规矩规划:
1.value — value:校验成果值的差异,得出百分比
- 原理:这种办法依据核算两个JSON目标中对应值的差异,然后依据这些差异核算出一个百分比。这个百分比表明了两个目标在值上的类似度。
- 公式:运用差异衡量公式,差异百分比 = (差异的总数 / 总数) * 100%
- 运用场景:当你需求比较两个JSON目标的值时,例如在数据质量检查、数据审计或数据同步的场景中。
- 处理什么问题:这种校验能够快速了解两个目标在值上的差异程度,然后能够决定是否接受、拒绝或进一步处理这些差异。
2.key — key:校验key的差异,只要相同key,且成果值不为空,则了解为相同,得出终究百分比
- 原理:这种办法重视于JSON目标的键(key)。假如两个目标有相同的键,而且对应值不为空,那么它们被视为相同。依据这个信息,能够核算出一个百分比来表明键的类似度。
- 公式:键的类似度百分比 = (相同键的数量 / 总键数) * 100%
- 运用场景:当你的重视点是保证JSON目标中有关键的数据元素,而且这些元素在两个目标中都存在时。
- 处理什么问题:这种校验能够发现丢掉或新增的键,以及那些键对应的值是否为空。这有助于保证数据的完好性。
3.structure — structure:校验json结构完好性,结构相同则为类似,得出百分比
- 原理:这种办法评价两个JSON目标的结构是否类似或相同。假如结构相同,那么能够以为这两个目标是类似的。
- 公式:运用二元判别,结构类似度百分比 = 100% – (结构差异的数量 / 总结构数量) * 100%
- 运用场景:当你需求保证两个JSON目标的结构保持共同时,例如在数据交换、数据集成或数据转化的场景中。
- 处理什么问题:这种校验能够发现结构上的差异,然后协助你辨认或许的格式问题、不共同性或数据错误。
4、流程规划
1.剖析和核算规矩
- 终究类似度核算公式:(值类似度 * 0.2 键类似度 * 0.4 结构完好性类似度 * 0.4) * 100% = 终究类似度
为什么选用此核算公式?
1)值类似度系数0.2:因为录制数据存在不同来历环境,且数据变化比较大,而回放环境仅仅测验环境,相同请求参数的条件下,终究呼应成果或许存在不同成果值比较大,为兼容多种条件,故调整成果值的系数占比为最低
2)键类似度系数0.4:评价同一个接口录制前端呼应数据的key差异性,若前后变化很大,则阐明事务有变化或者接口结构有变更
3)结构完好性类似度系数0.4:同上
-
体系diff:只要存在成果不共同则为失利,持平则为成功
-
类似度算法:校验成果值差异,参数key差异,数据结构完好性差异,三者核算成果乘以系数得出终究百分比,依照百分比分段剖析数据成果,小于80%(大于等于60%小于80%,为疑似反常,小于60%为反常)则为失利,大于或等于80%(大于等于80%小于90%,为疑似反常,大于90%为正常)则为成功
2.diff逻辑
3.diff成果剖析
四、类似度算法实验
1、测验数据
需求比照差异的两个json文本
String jsonString1 = "{"result":{"code":0,"data":[{"activityId":"123","activityLevelDesc":"C","activityStatusDesc":"未开始","activityTypeDesc":"大促","allActivityTime":null,"activityName":"活动N数不清了","cateInfo":"手机、其他网络设备","activityStatus":0,"canSignUp":1,"activityTime":["2024年01月23日 15:43-2024年01月25日 10:38","2024年01月26日 15:43-2024年01月27日 10:38"],"activityType":1,"activityLevel":4,"activity":4},{"activityId":"124","activityLevelDesc":"B-","activityStatusDesc":"进行中","activityTypeDesc":"日常活动","allActivityTime":null,"activityName":"kftest0116","cateInfo":"手机","activityStatus":1,"canSignUp":1,"activityTime":["2024年01月16日 14:04-2024年01月31日 14:04"],"activityType":0,"activityLevel":4},{"activityId":"125","activityLevelDesc":"A","activityStatusDesc":"进行中","activityTypeDesc":"日常活动","allActivityTime":null,"activityName":"kftest01221720","cateInfo":"笔记本、手机","activityStatus":1,"canSignUp":1,"activityTime":["2024年01月22日 17:21-2024年01月24日 17:21"],"activityType":0,"activityLevel":2}],"success":true,"cookieValue":null,"raw":false,"errorMsg":null,"errorJsonMsg":""},"exception":null}";
String jsonString2 = "{"result":{"code":0,"data":[{"activityId":"123","activityLevelDesc1":"C","activityStatusDesc":"未开始","activityTypeDesc":"大促","allActivityTime":null,"activityName":"活动N数不清了","cateInfo":"手机、其他网络设备","activityStatus":0,"canSignUp":1,"activityTime":["2024年01月23日 15:43-2024年01月25日 10:38","2024年01月26日 15:43-2024年01月27日 10:38"],"activityType":1,"activityLevel":6},{"activityId":"124","activityLevelDesc":"B-","activityStatusDesc":"进行中","activityTypeDesc":"日常活动","allActivityTime":null,"activityName":"kftest0116","cateInfo":"手机","activityStatus":1,"canSignUp":1,"activityTime":["2024年01月16日 14:04-2024年01月31日 14:04"],"activityType":0,"activityLevel":5},{"activityId":"125","activityLevelDesc":"A","activityStatusDesc":"进行中","activityTypeDesc":"日常活动","allActivityTime":null,"activityName":"kftest01221720","cateInfo":"笔记本、手机","activityStatus":1,"canSignUp":1,"activityTime":["2024年01月22日 17:21-2024年01月24日 17:21"],"activityType":0,"activityLevel":2}],"success":true,"cookieValue":null,"raw":false,"errorMsg":null,"errorJsonMsg":"{\"errorMsg\":null}"},"exception":null}";
两个字符串转为JSONObject目标
public boolean resultOfContrast(String recordResponse, String replayResponse){
JSONObject json1 = new JSONObject(recordResponse);
JSONObject json2 = new JSONObject(replayResponse);
……
以录制的呼应成果为作为基准(json1),比照回放的呼应成果(json2),得出两者终究类似度成果。
2、核算两个json字符串的值是否持平
判别两个json目标一切成果值,首先把两个json目标进行序列化处理,取出一切值,回来两个map目标再一一比照,得出终究值的类似度百分比
/**
* 比照两个json字符串的值是否持平,并核算出两个json字符串的类似度
* @param json1
* @param json2
* @return
*/
public static BigDecimal compareResultValues(JSONObject json1, JSONObject json2) {
Map<String, Object> allValues1 = findAllValues(json1);
Map<String, Object> allValues2 = findAllValues(json2);
// int totalKeys = allValues1.size() allValues2.size();
int totalKeys = allValues1.size();
int matchedKeys = 0;
for (Map.Entry<String, Object> entry : allValues1.entrySet()) {
if (allValues2.containsKey(entry.getKey())) {
Object value1 = entry.getValue();
Object value2 = allValues2.get(entry.getKey());
if (value1 != null && value2 != null && isValueEqual(value1, value2)) {
matchedKeys ;
}
}
}
double similarityPercentage = (double) matchedKeys / totalKeys;
return BigDecimal.valueOf(similarityPercentage).setScale(4, RoundingMode.HALF_UP);
}
private static boolean isValueEqual(Object value1, Object value2) {
if (value1 instanceof JSONObject && value2 instanceof JSONObject) {
return compareJsonObjects((JSONObject) value1, (JSONObject) value2);
} else if (value1 instanceof JSONArray && value2 instanceof JSONArray) {
return compareJsonArrays((JSONArray) value1, (JSONArray) value2);
} else {
return value1.equals(value2);
}
}
private static boolean compareJsonObjects(JSONObject json1, JSONObject json2) {
if (json1.length() != json2.length()) {
return false;
}
for (String key : json1.keySet()) {
if (!json2.has(key)) {
return false;
}
Object value1 = json1.get(key);
Object value2 = json2.get(key);
if (!isValueEqual(value1, value2)) {
return false;
}
}
return true;
}
private static boolean compareJsonArrays(JSONArray array1, JSONArray array2) {
if (array1.length() != array2.length()) {
return false;
}
for (int i = 0; i < array1.length(); i ) {
Object value1 = array1.get(i);
Object value2 = array2.get(i);
if (!isValueEqual(value1, value2)) {
return false;
}
}
return true;
}
private static Map<String, Object> findAllValues(JSONObject json) {
Map<String, Object> values = new HashMap<>();
findAllValues("", json, values);
return values;
}
private static void findAllValues(String prefix, JSONObject json, Map<String, Object> values) {
for (String key : json.keySet()) {
String path = prefix.isEmpty() ? key : prefix "." key;
Object value = json.get(key);
if (value instanceof JSONObject) {
findAllValues(path, (JSONObject) value, values);
} else if (value instanceof JSONArray) {
JSONArray array = (JSONArray) value;
for (int i = 0; i < array.length(); i ) {
Object item = ((JSONArray) value).get(i);
if (item instanceof JSONObject) {
JSONObject jsonObject = (JSONObject) item;
findAllValues(path "[" i "]", jsonObject, values);
}else {
values.put(path, value);
}
}
} else {
values.put(path, value);
}
}
}
3、核算两个json中一切非空的key的类似度
判别两个json目标非空key,首先把两个json目标进行序列化处理,再一切key(目标途径,key途径),回来两个map目标再一一比照,得出终究key的类似度百分比
/**
* 获取json中一切非空的key途径,并进行比照,一起核算出类似度
* @param json1
* @param json2
* @return
*/
public static BigDecimal compareNonEmptyKeys(JSONObject json1, JSONObject json2) {
Map<String, List<String>> nonEmptyKeyPaths1 = findNonEmptyKeyPaths(json1);
Map<String, List<String>> nonEmptyKeyPaths2 = findNonEmptyKeyPaths(json2);
// 运用流和 Collectors.toSet() 来获取一切键的调集
Set<String> keysInBothMaps = nonEmptyKeyPaths1.keySet().stream()
.filter(nonEmptyKeyPaths2::containsKey)
.collect(Collectors.toSet());
int totalNonEmptyKeys = nonEmptyKeyPaths1.size();
int matchedNonEmptyKeys = (int) keysInBothMaps.stream()
.filter(key -> nonEmptyKeyPaths1.get(key).equals(nonEmptyKeyPaths2.get(key)))
.count();
double similarityPercentage = (double) matchedNonEmptyKeys / totalNonEmptyKeys;
return BigDecimal.valueOf(similarityPercentage).setScale(4, RoundingMode.HALF_UP);
}
4、核算两个json字符串的结构完好的类似度
选用的是二元判别的办法,先把两个json目标进行序列化解析,JSONObject比照JSONObject,JSONArray比照JSONArray,key对key,回来两个map目标,再核算终究结构类似度,结构相同则为100%,不然比照两个json目标结构类似度的百分比,终究取两者交集的百分比作为结构完好性的类似度成果。
/**
* 比照两个json字符串的结构完好度,并核算出类似度
* @param json1
* @param json2
* @return
*/
public static BigDecimal compareStructuralIntegrity(JSONObject json1, JSONObject json2) {
Map<String, List<String>> structure1 = flattenJsonStructure(json1);
Map<String, List<String>> structure2 = flattenJsonStructure(json2);
int forwardTotalKeys = structure1.size();
int forwardCount = 0;
for (Map.Entry<String, List<String>> entry : structure1.entrySet()) {
if (structure2.containsKey(entry.getKey())) {
forwardCount ;
}
}
int reverseTotalKeys = structure2.size();
int reverseCount = 0;
for (Map.Entry<String, List<String>> entry : structure2.entrySet()) {
if (structure1.containsKey(entry.getKey())) {
reverseCount ;
}
}
// 正向百分比
double forwardPercentage = (double) forwardCount / forwardTotalKeys;
// 反向百分比
double reversePercentage = (double) reverseCount / reverseTotalKeys;
// 比较巨细,取交集的成果,交集即是小的值
double smallerPercentage = Math.min(forwardPercentage, reversePercentage);
return BigDecimal.valueOf(smallerPercentage).setScale(4, RoundingMode.HALF_UP);
}
5、测验成果
五、回放成果降噪
比照体系diff和类似度算法,结合实践回放数据进行剖析,以下数据为某一案例剖析成果:
| 比照项 | 回放总case数 | diff失利总数 | diff失利实践数 | diff失利中判别正确的份额 | diff成果正确率 |
|---|---|---|---|---|---|
| 体系diif | 667 | 328 | 188 | 57.32% | 53.35% |
| 类似度算法 | 667 | 218 | 188 | 86.24% | 86.89% |
| 人工判别成果 | 667 | 188 | 188 | 100% | 100% |
【成果剖析】
一共328条diff失利数据,类似度算法降噪之后还剩下218条diff失利,其间有43条剖析成果不精确,经过类似度算法328-218=110个回放成果被正确辨认为成功(体系diff为失利),且与人工判别共同
体系diff正确率:(328-(110 43))/328 = 53.35%
类似度算法正确率:(328-43)/328 = 86.89%
【成果示例】
成功列表和diff失利列表
疑似反常概况:
六、总结
1、验证定论
【算法完成】
成功完成了体系diff比照类似度算法,能够精确比较两个体系之间的差异并输出终究类似度
【测实验证】
经过许多测验用例验证了算法的精确性和稳定性,证明了其在实践运用中的可行性。
【运用场景实践】
当大批流量回放成果,事务逻辑是正确的,但成果又是Diff失利的Case,这些Case从事务视点剖析不应该作为失利的Case。这样的成果导致整体的成功率较低,一起加大了失利Case的排查难度。(比方,单据状况变更了,表单信息被修正了等等)
依据以上问题,经类似度算法剖析之后,对搅扰数据做降噪处理,削减无效diff失利的Case,一起下降失利Case的排查难度,进步回放成功率,终究表现回放成果价值。
2、长处和缺乏
【长处】
1、成果数据比照能够依据不同维度的差异,进行剖析和核算,得出终究有用成果,具备一定的筛查才能。
2、比照体系diff才能,精确率更高,且正确率高出30%以上。
3、可动态配置核算份额,更灵敏校验成果有用性。
【缺乏】
现在只判别成果值、参数key、结构完好性,无法判别成果数据是归于什么事务场景,以及事务场景是否归于正常呼应
已知缺乏的两个问题点:
-
录制呼应成果的整体结构与回放呼应成果的整体结构不共同,且成果值也存在不共同,剖析成果不一定精确
-
相同参数,因事务场景流通,前后回来成果不一样,影响校验规矩核算,剖析成果不一定精确
后续……
-
探究更多运用场景,将算法运用于更多范畴,发挥其更大的价值。
-
虽然算法能够校精确剖析两个json差异,但在处理大规模数据时功率仍是会比较低,需求进一步优化算法以进步功能。
-
现在的类似度剖析机制还较为简略,仅依据两个json目标差异进行核算,偏程序化,不能动态判别事务场景成果,未来会考虑引入更多特征以进步算法剖析的精确性。
流量回放渠道别传……
老板:快速用1万条数据去掩盖事务场景,下班前做完
你:人工点点点…… …… ……何时休?
流量回放渠道:要不来我这试试
关于作者
庄锦弟,负责测验渠道一体化才能基建
转转研发中心及业界小伙伴们的技能学习沟通渠道,定期分享一线的实战经验及业界前沿的技能论题。 重视大众号「转转技能」(综合性)、「大转转FE」(专注于FE)、「转转QA」(专注于QA),更多干货实践,欢迎沟通分享~















