小蚂蚁 发表于 2021-12-26 14:08:54

第三方接口的自动化管理---智能路由,Android开发入门与实战

(2)业务系统触发重试策略,根据路由模块返回的备份通道请求备份通道,完成本次用户请求;
(3)图像认证技术人员立刻定位问题并联系第三方通道负责人,对方确认问题和恢复情况后反馈到图像认证负责人;
(4)图像认证技术将通道置为可用,刷新认证路由缓存接口,将线上流量放入该通道;
(5)如果通道恢复,则用户可以正常认证,本次故障结束;
(6)如果认证通道未恢复,再次去联系第三方认证通道负责人处理,如此往复,直到该通道所有认证场景恢复正常,本次故障结束。
主要完成的改进点
(1)细化监控告警维度,做到精确、实时监控告警,保证第三方通道故障的快速发现;
(2)优化处理流程,请求A 通道失败后继续请求备份通道,增加通道复查策略,提高一次请求的成功率;
(3)大幅降低处理第三方通道故障的人力成本。
半自动化阶段存在的问题
半自动化阶段已将故障处理大幅度简化,但此时的系统还存在以下问题:
(1)第三方通道恢复依赖于第三方通道技术人员的反馈,导致通道恢复耗时较久;
(2)一次第三方通道故障涉及到的系统和人员较多,人工无法保证准确性和及时的处理;
(3)虽然故障时支持自动切走,但是恢复后需要手动切回已恢复的第三方通道,人力没有完全解放出来;
由于半自动化阶段还是存在以上问题,没有达到理想状态,所以智能(全自动)路由诞生了.....

智能路由阶段
主要思想
针对线上请求流量进行监控和结果实时统计,一旦触发阈值则自动切走线上流量(将该故障第三方通道路由状态置为关闭状态),刷新路由模块缓存机制,完成关闭故障通道操作。 同时,自动化阶段也同样支持手动一键切走,并且考虑周末在外不方便,可考虑支持移动端一键切走故障通道功能(比如发送是否切换告警短信,回复1切换等思路)。
认证通道故障时,智能路由阶段处理流程如下:
![在这里插入图片3描述]
智能路由处理流程
故障通道智能化阶段,故障处理自动切走,自动切回,一次第三方通道故障的处理流程如下:
(1)实时监控模块检测到某个第三方成功率异常时,发送告警信息给业务系统技术人员,同时自动将故障通道置为不可用;
(2)同时刷新路由缓存机制,路由模块实时读取通道缓存信息从而将故障通道线上流量全部切走;
(3)监控模块在将故障通道置为不可用一段时间后(时间可配置),尝试对故障通道放部分流量进来用以检测通道是否恢复正常;
(4)如果放进来的这部分量的成功率正常,监控则继续放2倍的量,以此类推,直到通道全量,监控将通道置为可用;
(5)如果放进来的这部分量成功率异常(并且识别的请求会触发重试策略,用户无感知),则继续将该通道直接置为不可用,监控隔一段时间(时间递增)后再继续放量,直到通道恢复为可用;
(6)业务系统技术人员发现通道故障后,可以向第三方通道询问故障原因、并记录,留作日后分析(阈值分析)使用。
3、实现思路

实时监控
借助阿里Sentinel组件完成实时统计
阿里Sentinel我就不用多介绍了,不了解的建议看看,绝对实用到爆炸,哈哈。。。
Sentinel官网:https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D
Sentinel主要特性:
![在这里插入图片4描述]
下面我就介绍怎么通过Sentinel完成我们的实时统计(监控)功能:

[*]首先需要梳理出业务系统有哪些场景/服务/接口,在sentinel中被定义为资源,其实sentinel中所说的资源可以是任何对象,可以是几行代码块、一个方法、或者一个类都可以。
[*]我们把需要控制流量的资源用sentinel保护起来,也就是用 Sentinel API SphU.entry("资源名称") 和 entry.exit() 包围起来即可。
代码示例:
public static void main(String[] args) {
initFlowRules();
while (true) {
Entry entry = null;
try {
entry = SphU.entry("HelloWorld");
/您的业务逻辑 - 开始/
System.out.println("hello world");
/您的业务逻辑 - 结束/
} catch (BlockException e1) {
/流控逻辑处理 - 开始/
System.out.println("block!");
/流控逻辑处理 - 结束/
} finally {
if (entry != null) {
entry.exit();
}
}
}
}
完成以上两步后,代码端的改造就完成了。当然,我们也提供了 注解支持模块,可以以低侵入性的方式定义资源。

[*]当接口资源被保护起来,用户请求场景接口后,我们可以在日志:
~/logs/csp/${appName}-metrics.log.xxx
里看到下面的输出:
|--timestamp-|------date time----|-resource-|p |block|s |e|rt
1529998904000|2018-06-26 15:41:44|HelloWorld|20|0    |20|0|0
1529998905000|2018-06-26 15:41:45|HelloWorld|20|5579 |20|0|728
1529998906000|2018-06-26 15:41:46|HelloWorld|20|15698|20|0|0
1529998907000|2018-06-26 15:41:47|HelloWorld|20|19262|20|0|0
1529998908000|2018-06-26 15:41:48|HelloWorld|20|19502|20|0|0
1529998909000|2018-06-26 15:41:49|HelloWorld|20|18386|20|0|0
其中resource代表资源名称, p 代表通过的请求, block 代表被阻止的请求, s 代表成功执行完成的请求个数, e 代表用户自定义的异常, rt 代表平均响应时长。
可以看到,这个程序每秒稳定输出 "hello world" 20 次,这个和控制台规则中预先设定的阈值是一样的。

[*]有了上面的文件名后缀为 -metrics.log.xxx 的持久化调用记录日志后,我们可以调用Sentinel提供的[实时监控接口]( )获取持久化调用记录,并通过分类汇总得出 每个第三方下的场景(接口)的总数、异常数、耗时等信息,并通过计算得数成功率、失败率等信息,用于实时监控模块关闭通道的依据。
实时查询
相关 API: GET /metric
curl http://localhost:8719/metric?startTime=XXXX&endTime=XXXX&maxLines=XXXX   // 查询当前系统所有资源信息,智能路由使用
curl http://localhost:8719/metric?identity=XXX&startTime=XXXX&endTime=XXXX&maxLines=XXXX
需指定以下 URL 参数:
identity:资源名称
startTime:开始时间(时间戳)
endTime:结束时间
maxLines:监控数据最大行数
返回和 资源的秒级日志 格式一样的内容。例如:
1529998904000|2018-06-26 15:41:44|interface1|100|0|0|0|0
1529998905000|2018-06-26 15:41:45|interface2|4|5579|104|16|728
1529998906000|2018-06-26 15:41:46|interface3|0|15698|0|0|0
1529998907000|2018-06-26 15:41:47|interface1|0|19262|0|0|0
1529998908000|2018-06-26 15:41:48|interface1|0|19502|0|0|0
1529998909000|2018-06-26 15:41:49|interface2|0|18386|0|0|0
1529998910000|2018-06-26 15:41:50|interface4|0|19189|0|0|0
1529998911000|2018-06-26 15:41:51|interface1|0|16543|0|0|0
1529998912000|2018-06-26 15:41:52|interface1|0|18471|0|0|0
1529998913000|2018-06-26 15:41:53|interface1|0|19405|0|0|0

[*]查出来后利用Java进行分类汇总统计,实时统计方法如下:
private static final String EX = "?";
private static final String SPLIT = "\|";
/**

[*]一定时间间隔统计调用流水情况
[*]@param timeSpan 时间间隔(以秒为单位)
[*]@return
[*]@throws ParseException
*/
publicMap<String,Map<String,Long>> statisticBlockCount(int timeSpan) throws ParseException {
String result = requestSentinel(timeSpan, Calendar.SECOND,null);
if (null == result || StringUtils.isEmpty(result)) {
log.info("实时统计异常,统计结果为空,请确认线上是否有调用流水!!!");
return null;
}
String[] resultList = result.split("\n");
String[] copyOfResultList = Arrays.copyOf(resultList, (resultList.length - 1));
List<SentinelStatistic> recodes = Arrays.stream(copyOfResultList).map(resp -> {
String[] rpt = resp.split(SPLIT);
SentinelStatistic record = new SentinelStatistic();
record.setResourceName(rpt);
record.setBlockCount(Integer.parseInt(rpt));
record.setSuccessCount(Integer.parseInt(rpt) - Integer.parseInt(rpt));
record.setPassCount(Integer.parseInt(rpt));
record.setExceptionCount(Integer.parseInt(rpt));
record.setTotalCount(Integer.parseInt(rpt + Integer.parseInt(rpt)));
return record;
}).collect(Collectors.toList());
Map<String, Long> successResultMap = new HashMap<>(3 << 1);
Map<String, Long> blockResultMap = new HashMap<>(3 << 1);
Map<String, Long> exceptionResultMap = new HashMap<>(3 << 1);
Map<String, Long> totalResultMap = new HashMap<>(3 << 1);
Map<String, Long> passResultMap = new HashMap<>(3<<1);
// 分组
Map<String, List<SentinelStatistic>> countMap = recodes.stream().collect(
Collectors.groupingBy(SentinelStatistic::getResourceName));
Set<String> set = countMap.keySet();
// 遍历统计存到缓存中
for (String resourceName : set) {
List<SentinelStatistic> list = countMap.get(resourceName);
long successSum = 0;
long blockSum = 0;
long exceptionSum = 0;
long totalSum = 0;
long passSum = 0;
for (int i = 0; i < list.size(); i++) {
successSum = successSum + list.get(i).getSuccessCount();
blockSum = blockSum + list.get(i).getBlockCount();
exceptionSum = exceptionSum + list.get(i).getExceptionCount();
totalSum = totalSum + list.get(i).getTotalCount();
passSum = passSum+list.get(i).getPassCount();
}
blockResultMap.put(resourceName,blockSum);
successResultMap.put(resourceName,successSum);
exceptionResultMap.put(resourceName,exceptionSum);
totalResultMap.put(resourceName,totalSum);
passResultMap.put(resourceName,passSum);
}
Map<String, Map<String,Long>> combineResultMap = new HashMap<>(3<<1);
// blockResultMap 代表被阻止的请求资源名及总数
combineResultMap.put("blockResultMap",blockResultMap);
// blockResultMap 代表成功的资源名及总数
combineResultMap.put("successResultMap",successResultMap);
// exceptionResultMap 代表异常资源名及总数
combineResultMap.put("exceptionResultMap",exceptionResultMap);
// totalResultMap 代表总的请求总数=blockResultMap+successResultMap+exceptionResultMap+passResultMap
combineResultMap.put("totalResultMap",totalResultMap);
// passResultMap 代表通过的
combineResultMap.put("passResultMap",passResultMap);
return combineResultMap;
}
解析后的结构数据如下:
最后
文章不易,如果大家喜欢这篇文章,或者对你有帮助希望大家多多点赞转发关注哦。文章会持续更新的。绝对干货!!!
由于文章篇幅问题 查看详细文章以及获取学习笔记链接:GitHub


[*]Android进阶学习全套手册
关于实战,我想每一个做开发的都有话要说,对于小白而言,缺乏实战经验是通病,那么除了在实际工作过程当中,我们如何去更了解实战方面的内容呢?实际上,我们很有必要去看一些实战相关的电子书。目前,我手头上整理到的电子书还算比较全面,HTTP、自定义view、c++、MVP、Android源码设计模式、Android开发艺术探索、Java并发编程的艺术、Android基于Glide的二次封装、Android内存优化——常见内存泄露及优化方案、.Java编程思想 (第4版)等高级技术都囊括其中。


[*]Android高级架构师进阶知识体系图
关于视频这块,我也是自己搜集了一些,都按照Android学习路线做了一个分类。按照Android学习路线一共有八个模块,其中视频都有对应,就是为了帮助大家系统的学习。接下来看一下导图和对应系统视频吧!!!

[*]Android对标阿里P7学习视频


[*]BATJ大厂Android高频面试题
这个题库内容是比较多的,除了一些流行的热门技术面试题,如Kotlin,数据库,Java虚拟机面试题,数组,Framework ,混合跨平台开发,等



https://blog.51cto.com/u_15465267/4844922
页: [1]
查看完整版本: 第三方接口的自动化管理---智能路由,Android开发入门与实战